diff --git a/src/main/java/it/polimi/ingsw/gc14/ClientLauncher.java b/src/main/java/it/polimi/ingsw/gc14/ClientLauncher.java deleted file mode 100644 index fd8a3f8..0000000 --- a/src/main/java/it/polimi/ingsw/gc14/ClientLauncher.java +++ /dev/null @@ -1,28 +0,0 @@ -package it.polimi.ingsw.gc14; -import java.util.Scanner; - -public class ClientLauncher { - public void main() { - Scanner scanner = new Scanner(System.in); - - System.out.println("Selezionare nome utente: "); - String username = scanner.next(); - System.out.println(username); - - System.out.println("Selezionare numero di giocatori desiderato: "); - int proposedNumPlayers = scanner.nextInt(); - System.out.println(proposedNumPlayers); - - System.out.println("Selezionare RMI[0] o TCP[1]: "); - int networkType = scanner.nextInt(); - System.out.println(networkType); - - scanner.close(); // chiudi solo alla fine - - if (networkType == 0) { - return; - } else if (networkType == 1) { - return; - } - } -} \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java b/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java new file mode 100644 index 0000000..8da0d9e --- /dev/null +++ b/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java @@ -0,0 +1,53 @@ +package it.polimi.ingsw.gc14; +import it.polimi.ingsw.gc14.Controller.ClientController; +import it.polimi.ingsw.gc14.Network.RMI.Client.RMIClient; +import it.polimi.ingsw.gc14.View.IView; + +import java.util.Scanner; + +public class ClientLauncherTUI { + public void main() throws InterruptedException { + ClientController controller = new ClientController(); + + Scanner scanner = new Scanner(System.in); + + System.out.println("Selezionare nome utente: "); + String username = scanner.next(); + System.out.println(username); + + System.out.println("Selezionare numero di giocatori desiderato: "); + int proposedNumPlayers = scanner.nextInt(); + System.out.println(proposedNumPlayers); + + System.out.println("Selezionare RMI[0] o TCP[1]: "); + int networkType = scanner.nextInt(); + System.out.println(networkType); + + scanner.close(); + + if (networkType == 0) { + RMIClient client = new RMIClient("localhost", 1099); + if (client.connect(username, proposedNumPlayers, controller)) { + System.out.println("Succesfully connected to RMI server\n\n"); + } else { + System.out.println("RMI connection refused\n\n"); + } + + while(true) { + System.out.flush(); + if (controller.localModel!=null) { + break; + } + Thread.sleep(500); + } + + System.out.println("Model set\n\n"); + + + + + } else if (networkType == 1) { + return; + } + } +} \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/Controller/ClientController.java b/src/main/java/it/polimi/ingsw/gc14/Controller/ClientController.java index d206d22..1da7933 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Controller/ClientController.java +++ b/src/main/java/it/polimi/ingsw/gc14/Controller/ClientController.java @@ -6,9 +6,9 @@ import it.polimi.ingsw.gc14.View.IView; public class ClientController { - private Game localModel; + public Game localModel; public GameController localController; - public final IView view; + public IView view=null; public ClientController(IView view,Game localModel) { this.view = view; @@ -16,12 +16,18 @@ public class ClientController { this.localController = new GameController(localModel); } + public ClientController() { + this.localController = new GameController(localModel); + } + public void setModel(Game model) { this.localModel = model; localController.setModel(model); - localModel.addObserver((Observer) view); // registra la view come observer + // localModel.addObserver((Observer) view); // registra la view come observer } + + public void onError(String message) { view.showError(message); } diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/Board.java b/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/Board.java index 55a17ee..4fda653 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/Board.java +++ b/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/Board.java @@ -111,9 +111,9 @@ public class Board implements Serializable { ArrayList buildingDeck = new ArrayList<>(DecksCreator.loadBuildingDeckByEra(1)); Collections.shuffle(buildingDeck); if(nTotem==2) - upperListBuilding= buildingDeck.subList(0,1); + upperListBuilding = new ArrayList<>(buildingDeck.subList(0,1)); else - upperListBuilding= buildingDeck.subList(0,2); + upperListBuilding = new ArrayList<>(buildingDeck.subList(0,2)); } /** diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java index abc510c..1a5d50c 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java @@ -1,7 +1,6 @@ package it.polimi.ingsw.gc14.Network; import it.polimi.ingsw.gc14.Controller.GameController; -import javafx.event.Event; import java.io.Serializable; @@ -12,9 +11,23 @@ public abstract class NetworkEvent implements Serializable { } protected EventType eventType; public EventType getEventType() {return eventType;} - protected NetworkEvent(String username, EventType eventType) { + protected boolean isError; + public boolean getIsError() {return isError;} + public void setIsError(boolean isError) {this.isError = isError;} + + protected NetworkEvent(String username, EventType eventType, boolean isError) { this.username = username; this.eventType = eventType; + this.isError = isError; + } + + @Override + public String toString() { + if(isError) { + return ("ERROR: action " + eventType.toString()); + } else { + return ("ACTION: action " + eventType.toString()); + } } public abstract boolean apply(GameController gameController); diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java index d8ab797..8466942 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java @@ -13,7 +13,7 @@ public class AddPlayer extends NetworkEvent implements Serializable { return proposedNPlayer; } public AddPlayer(String username, int proposedNPlayer) { - super(username, EventType.ADD_PLAYER); + super(username, EventType.ADD_PLAYER, false); this.proposedNPlayer = proposedNPlayer; } @Override diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java index 2456a6a..5656ac8 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java @@ -11,7 +11,7 @@ public class DrawLowerBuildingCard extends NetworkEvent implements Serializable private int pos; public DrawLowerBuildingCard(String username, int pos){ - super(username, EventType.DRAW_LOWER_BUILD); + super(username, EventType.DRAW_LOWER_BUILD, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java index 64b0a17..277627a 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java @@ -11,7 +11,7 @@ public class DrawLowerTribeCard extends NetworkEvent implements Serializable{ private int pos; public DrawLowerTribeCard(String username, int pos){ - super(username, EventType.DRAW_LOWER_TRIBE); + super(username, EventType.DRAW_LOWER_TRIBE, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java index 5f0a985..6fb06cf 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java @@ -11,7 +11,7 @@ public class DrawUpperBuildingCard extends NetworkEvent implements Serializable private int pos; public DrawUpperBuildingCard(String username, int pos){ - super(username, EventType.DRAW_UPPER_BUILD); + super(username, EventType.DRAW_UPPER_BUILD, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java index 69ab281..8d37f02 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java @@ -11,7 +11,7 @@ public class DrawUpperTribeCard extends NetworkEvent implements Serializable{ private int pos; public DrawUpperTribeCard(String username, int pos){ - super(username, EventType.DRAW_UPPER_TRIBE); + super(username, EventType.DRAW_UPPER_TRIBE, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalBuildingCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalBuildingCard.java index a1fde73..36be45c 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalBuildingCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalBuildingCard.java @@ -11,7 +11,7 @@ public class PickOptionalBuildingCard extends NetworkEvent implements Serializa private int pos; public PickOptionalBuildingCard(String username, int pos){ - super(username, EventType.PICK_OPTIONAL_BUILD); + super(username, EventType.PICK_OPTIONAL_BUILD, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalTribeCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalTribeCard.java index d739e3c..f2dadac 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalTribeCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/PickOptionalTribeCard.java @@ -11,7 +11,7 @@ public class PickOptionalTribeCard extends NetworkEvent implements Serializable private int pos; public PickOptionalTribeCard(String username, int pos){ - super(username, EventType.PICK_OPTIONAL_TRIBE); + super(username, EventType.PICK_OPTIONAL_TRIBE, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java index 97000d9..4bf0d12 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java @@ -11,7 +11,7 @@ public class SlotChoice extends NetworkEvent implements Serializable { private int pos; public SlotChoice(String username, int pos) { - super(username, EventType.SLOT_CHOICE); + super(username, EventType.SLOT_CHOICE, false); this.pos = pos; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java index d6b54d7..5b16b76 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java @@ -5,31 +5,57 @@ import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback; +import java.io.Serializable; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; -public class ClientCallbackImpl extends UnicastRemoteObject implements IClientCallback { +/** + * RMI client callback implementation of {@link IClientCallback}. + * Receives notifications from the server and updates the client game model. + */ +public class ClientCallbackImpl extends UnicastRemoteObject implements IClientCallback, Serializable { + + /** The client controller used to apply events and update the model */ private final ClientController clientController; + + /** + * Class constructor. + * @param clientController the client controller + * @throws RemoteException if any RMI error occurs + */ public ClientCallbackImpl(ClientController clientController) throws RemoteException { this.clientController = clientController; } + + /** + * Called by the server when the game is initialized. + * Sets the client game model in the {@link ClientController}. + * @param model the initialized {@link Game} model + * @throws RemoteException if any RMI error occurs + */ @Override public void onGameInit(Game model) throws RemoteException { - clientController.setModel(model); // setta il model + clientController.setModel(model); } + + /** + * Called by the server when an action has been accepted. + * If the event contains an error, it is printed to the console. + * Otherwise, the event is applied to the local model. + * @param event the {@link NetworkEvent} sent by the server + * @throws RemoteException if any RMI error occurs + */ @Override public void onAction(NetworkEvent event) throws RemoteException { - event.apply(clientController.localController); // delega tutto al controller - //clientController.view.update(); TODO + if(event.getIsError()) { + System.out.println(event.toString()); + } else { + event.apply(clientController.localController); + //clientController.view.update(); TODO + } } - - @Override - public void onError(String message) throws RemoteException { - clientController.onError(message); - } - } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/RMIClient.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/RMIClient.java index 8d0721c..19bd574 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/RMIClient.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/RMIClient.java @@ -1,43 +1,55 @@ package it.polimi.ingsw.gc14.Network.RMI.Client; +import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import it.polimi.ingsw.gc14.Controller.ClientController; import it.polimi.ingsw.gc14.Network.NetworkEvent; +import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback; import it.polimi.ingsw.gc14.Network.RMI.Common.IGameServer; -public class RMIClient { +/** + * Client RMI. Uses the methods exposed by the server RMI. + */ +public class RMIClient { + /** The host address of the RMI server */ private final String host; + + /** The port of the RMI server */ private final int port; + + /** The remote stub used to call methods on the server */ private IGameServer stub; + + /** + * Class constructor. + * @param host the host address of the RMI server + * @param port the port of the RMI server + */ public RMIClient(String host, int port) { this.host = host; this.port = port; } - public boolean connect(String username,int preferredInt,ClientController clientController) { - // 1. Connettiti al registry + /** + * Connects to the RMI server and attempts to join the game. + * Looks up the RMI registry to retrieve the {@link IGameServer} stub. + * Then, creates a {@link ClientCallbackImpl} and calls {@link RMIServer#joinGame(String, int, IClientCallback)}. + * @param username the player's username + * @param preferredInt the desired number of players + * @param clientController the client controller used to create the callback + * @return true if the player successfully joined the game, false otherwise + */ + public boolean connect(String username,int preferredInt, ClientController clientController) { try { Registry registry = LocateRegistry.getRegistry(host, port); - - // 2. Prendi lo stub del server this.stub = (IGameServer) registry.lookup("RMIGameServer"); - - // 3. Crea il callback e registralo ClientCallbackImpl callback = new ClientCallbackImpl(clientController); - if (!stub.joinGame(username,preferredInt, callback)) - { - stub = null; - return false; - } - else - { - return true; - } + return stub.joinGame(username, preferredInt, callback); } catch (Exception e) { e.printStackTrace(); @@ -46,8 +58,12 @@ public class RMIClient { } - - public void doEvent(NetworkEvent event) throws Exception { + /** + * Sends a {@link NetworkEvent} to the server. + * @param event the event to send + * @throws RemoteException if any RMI error occurs + */ + public void doEvent(NetworkEvent event) throws RemoteException { stub.doEvent(event); } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IClientCallback.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IClientCallback.java index 5ff2b0b..c7fa94d 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IClientCallback.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IClientCallback.java @@ -3,10 +3,10 @@ package it.polimi.ingsw.gc14.Network.RMI.Common; import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Network.NetworkEvent; +import java.io.Serializable; import java.rmi.*; -public interface IClientCallback extends Remote { +public interface IClientCallback extends Remote, Serializable { void onGameInit(Game model) throws RemoteException; void onAction(NetworkEvent action) throws RemoteException; - void onError(String message) throws RemoteException; } \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java index fc74f3c..6fadf03 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java @@ -15,19 +15,47 @@ import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; - import java.rmi.*; -public class RMIServer implements IGameServer { +/** + * Server RMI. Exposes a method to join the game and one to execute an event. + */ +public class RMIServer extends UnicastRemoteObject implements IGameServer { + + /** Server game's controller */ private GameController controller; + + /** Server game's model */ + private Game model; + + /** RMI registry */ private Registry registry; + + /** RMI port */ private int nPort; + + /** Map containing the associations between a player's username and its callback */ private final Map clients = new ConcurrentHashMap<>(); + + /** Queue containing the events to be applied to the game model */ BlockingQueue actionQueue; + + /** + * List containing the usernames of joined players. + * {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}. + */ private LimitedList playerList; - // Costruttore + + /** + * Class constructor that initializes the attributes. + * @param controller The game controller + * @param nPort The RMI port + * @param actionQueue The action queue + * @param playerList The player's usernames list + * @throws RemoteException if an RMI error occurs + */ public RMIServer(GameController controller, int nPort, BlockingQueue actionQueue,LimitedList playerList) throws RemoteException { this.controller = controller; this.nPort = nPort; @@ -37,67 +65,85 @@ public class RMIServer implements IGameServer { - - - - // Metodi esposti RMI - public void setController (GameController controller) { - this.controller = controller; - } + // RMI's exposed methods + /** + * Allows a player to join the game. + * If the desired number of player is invalid, the request is rejected. + * If this is the first player, a new game model is created and passed to the controller. Additionally, the playerList's limit is set. + * Then, if the controller successfully adds the player, the username is added to {@link #playerList} and {@link #clients}. + * @param username The player's name + * @param preferredInt The desired number of players + * @param callback The client's callback interface + * @return true if the player successfully joined the game, false otherwise + */ @Override - public boolean joinGame(String username, int preferredInt,IClientCallback callback) { + public boolean joinGame(String username, int preferredInt, IClientCallback callback) { + if (preferredInt<2 || preferredInt>5) { + return false; + } synchronized (controller) { - if(playerList.size()==0 && (preferredInt<2||preferredInt>5)) - return false; + if(playerList.isEmpty()){ + model = new Game(preferredInt); + controller.setModel(model); + playerList.setLimit(preferredInt); + } if (controller.addPlayer(username)) { - if(playerList.size()==0) - { - Game game= new Game(preferredInt); - controller.setModel(game); - playerList.setLimit(preferredInt); - } - playerList.add(username); clients.put(username, callback); + playerList.add(username); return true; } return false; } + } + + + /** + * Push an action in actionQueue. + * @param action The desired actio + * @return true if the action was successfully added, false otherwise + */ @Override - public boolean doEvent(NetworkEvent event) throws RemoteException { - return actionQueue.offer(event); + public boolean doEvent(NetworkEvent action) { + return actionQueue.offer(action); } - - - // Metodi interni del server + // RMI's internal methods + /** + * Sends an action to all RMI clients. + * @param action The desired action + */ public void notifyAll(NetworkEvent action) throws RemoteException { for (IClientCallback cb : clients.values()) { cb.onAction(action); } } + + + /** + * Sends a game model to all RMI clients. + * @param model The desired model + */ public void notifyAll(Game model) throws RemoteException { for (IClientCallback cb : clients.values()) { cb.onGameInit(model); } } - public void notifyError(String username, String message) throws RemoteException { - IClientCallback cb = clients.get(username); - if (cb != null) cb.onError(message); - } - - // Metodi per avviare server RMI + /** + * Starts the RMI server. + * @return true if the server starts successfully, false otherwise + */ public boolean start() { try { registry = LocateRegistry.createRegistry(nPort); registry.rebind("RMIGameServer", this); - System.out.println("RMI Server avviato sulla porta "+nPort); + System.out.println("RMI Server started on port: "+nPort); return true; } catch (Exception e) { @@ -105,6 +151,12 @@ public class RMIServer implements IGameServer { return false; } } + + + /** + * Stops the RMI server. + * @return true if the server stops successfully, false otherwise + */ public boolean stop() { try { registry.unbind("RMIGameServer"); diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java index d29de4b..c69734e 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java @@ -1,70 +1,118 @@ package it.polimi.ingsw.gc14.Network.TCP.Client; import it.polimi.ingsw.gc14.Controller.GameController; +import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer; import java.io.*; import java.net.*; -public class TCPClient implements Serializable{ - Socket communicationSocket = null; +/** + * Client TCP. Sends and receives messages with the TCP server. + */ +public class TCPClient { + + /** Socket TCP */ + Socket communicationSocket; + + /** Input stream used receive objects from the server */ ObjectInputStream socketReceive; + + /** Output stream used to send objects to the server */ ObjectOutputStream socketSend; + /** Client game's controller */ GameController controller; + + /** IP address of the server to connect to */ String hostname; + + /** TCP port */ int port; - public TCPClient(GameController controller, String hostname, int port){ + + /** + * Class constructor that initializes the attributes. + * @param controller The game controller + * @param hostname The IP address of the server + * @param port The TCP port of the server + */ + public TCPClient(GameController controller, String hostname, int port) { this.controller = controller; this.hostname = hostname; this.port = port; } - public boolean start(String user, int players){ - try{ + + /** + * Starts the TCP connection with the server. + * Sends an {@link AddPlayer} event, if the server responds with {@code -1}, the connection is refused and the method returns {@code false}. + * Otherwise, a listener thread is started. + * @param user The username of the player + * @param proposedNPlayers The desired number of players for the game + * @return true if the connection is successful, false otherwise. + */ + public boolean start(String user, int proposedNPlayers) { + try { communicationSocket = new Socket(hostname, port); socketSend = new ObjectOutputStream(communicationSocket.getOutputStream()); socketReceive = new ObjectInputStream(communicationSocket.getInputStream()); - socketSend.writeObject(new AddPlayer(user, players)); - if(communicationSocket.getInputStream().read() == -1){ + sendEvent(new AddPlayer(user, proposedNPlayers)); + if (communicationSocket.getInputStream().read() == -1) { + System.out.println("Could not connect to server"); return false; - } - else{ - Thread listener = new Thread(() -> ReceiveMessage()); + } else { + Thread listener = new Thread(() -> receiveMessage()); listener.start(); return true; } - } - catch(Exception e){ + } catch (IOException e) { + e.printStackTrace(); return false; } } - private void ReceiveMessage(){ - while(true){ - try{ - ((NetworkEvent)(socketReceive.readObject())).apply(controller); - } - catch(IOException e){ + + /** + * Listens continuously for incoming objects from the server. + * - If the received object is a {@link NetworkEvent} flagged as an error, it is printed. + * - If the received object is a valid {@link NetworkEvent}, it is applied to the game controller. + * - If the received object is a {@link Game} model, the controller's model is set. + */ + private void receiveMessage() { + while (true) { + try { + Object read = socketReceive.readObject(); + if (read instanceof NetworkEvent event) { //TODO: avoid instanceof + if (event.getIsError()) { + System.out.println(event); + } else { + event.apply(controller); + //clientController.view.update(); TODO + } + } else if (read instanceof Game model) { + controller.setModel(model); + } + } catch (IOException e) { e.printStackTrace(); - } - catch(ClassNotFoundException e){ + } catch (ClassNotFoundException e) { throw new RuntimeException(e); } - return; } } - private void SendEvent(NetworkEvent event){ - try{ + + /** + * Sends a {@link NetworkEvent} to the server. + * @param event The NetworkEvent to send. + */ + private void sendEvent(NetworkEvent event) { + try { socketSend.writeObject(event); - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); } } - -} +} \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java index d471ab0..07864f0 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java @@ -1,91 +1,105 @@ package it.polimi.ingsw.gc14.Network.TCP.Server; -import it.polimi.ingsw.gc14.Controller.GameController; import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Network.NetworkEvent; -import it.polimi.ingsw.gc14.Network.EventType; import java.io.*; import java.net.*; import java.util.List; import java.util.concurrent.BlockingQueue; +/** + * Handles the TCP connection with a single client. + * Each instance runs on a dedicated thread and is responsible for receiving {@link NetworkEvent} from its client and adding them + * to the {@link #actionQueue}. + * It is also responsible to send events and game model updates back to the client. + */ public class ClientHandler implements Runnable { + + /** The TCP socket */ private Socket clientSocket; - private TCPServer server; - public ObjectInputStream in = null; - public ObjectOutputStream out = null; + + /** Input stream used to receive objects from the client */ + public ObjectInputStream in; + + /** Output stream used to send objects to the client */ + public ObjectOutputStream out; + + /** + * Shared list of all client handlers. + * This handler removes itself from the list when disconnected. + */ List clientHandlers; + + /** Queue containing the events to be applied to the game model */ BlockingQueue actionQueue; - private EventType eventType; - public Socket getClientSocket() { - return clientSocket; - } + /** + * Class constructor that initializes the attributes. + * @param clientSocket The socket representing the client's TCP connection + * @param clientHandlers The shared list of all active client handlers + * @param actionQueue The queue containing incoming events + */ public ClientHandler(Socket clientSocket, List clientHandlers, BlockingQueue actionQueue) { this.clientSocket = clientSocket; this.clientHandlers = clientHandlers; this.actionQueue = actionQueue; } + + /** + * Listens for incoming {@link NetworkEvent}s from the client and adds them to the {@link #actionQueue}. + * Upon disconnection, this handler removes itself from {@link #clientHandlers}. + */ @Override - public void run(){ - clientLoop(); - } - - private void clientLoop(){ - try{ - NetworkEvent input = null; - synchronized(in){ - in = new ObjectInputStream(clientSocket.getInputStream()); + public void run() { + try { + in = new ObjectInputStream(clientSocket.getInputStream()); + out = new ObjectOutputStream(clientSocket.getOutputStream()); + while (true) { + NetworkEvent event = (NetworkEvent) in.readObject(); + if (!actionQueue.add(event)) { + System.out.println("Error inserting action into queue"); + } } - while(true){ - try{ - input = (NetworkEvent) (in.readObject()); - if(actionQueue.add(input)){ - server.broadcastUpdate(input); - } - } - catch(java.io.IOException e){ - e.printStackTrace(); - } - catch (ClassNotFoundException e){ - throw new RuntimeException(e); - } - - - } - } - catch (IOException e) { + } catch (IOException e) { + clientHandlers.remove(this); e.printStackTrace(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); } } - public void notifyEvent(NetworkEvent event){ - synchronized(out){ - try{ + + /** + * Sends a {@link NetworkEvent} to the client. + * @param event The network event to send to the client. + */ + public void notifyEvent(NetworkEvent event) { + synchronized (out) { + try { out = new ObjectOutputStream(clientSocket.getOutputStream()); out.writeObject(event); - } - catch(IOException e){ + } catch (IOException e) { e.printStackTrace(); } } } - public void notifyModel(Game game){ - synchronized(out){ - try{ + + /** + * Sends the current game model to this client. + * @param game The current state of the game to send to the client. + */ + public void notifyModel(Game game) { + synchronized (out) { + try { out = new ObjectOutputStream(clientSocket.getOutputStream()); out.writeObject(game); - } - catch(IOException e){ + } catch (IOException e) { e.printStackTrace(); } } } - -} - - +} \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java index 7c928c3..16c4cfb 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java @@ -3,6 +3,7 @@ package it.polimi.ingsw.gc14.Network.TCP.Server; import it.polimi.ingsw.gc14.Controller.GameController; import it.polimi.ingsw.gc14.LimitedList; import it.polimi.ingsw.gc14.Model.Game; +import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer; @@ -12,71 +13,120 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; - +/** + * Server TCP. Accepts connections and manages all client handlers. + */ public class TCPServer { - int port = -1; - int ConnectedPlayers = 0; - ServerSocket serverTCP = null; - GameController gameController; + + /** TCP port */ + int port; + + /** Number of currently connected clients */ + int ConnectedPlayers; + + /** Socket TCP */ + ServerSocket socketTCP; + + /** Server game's controller */ + GameController controller; + + /** Queue containing the events to be applied to the game model */ BlockingQueue actionQueue; + + /** + * List containing the usernames of joined players. + * {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}. + */ private LimitedList playerList; + + /** List containing all client's handlers */ private List clientHandlers; - - private int getConnectedPlayers(){ - return ConnectedPlayers; + /** + * Class constructor that initializes the attributes. + * @param controller The game controller + * @param port The TCP port + * @param actionQueue The action queue + * @param playerList The player's usernames list + */ + public TCPServer(GameController controller, int port, BlockingQueue actionQueue, LimitedList playerList){ + this.port = port; + this.ConnectedPlayers = 0; + this.socketTCP = null; + this.controller = controller; + this.actionQueue = actionQueue; + this.playerList = playerList; + this.clientHandlers = new ArrayList<>(); } + + /** + * Starts the TCP server. + * If the first event is not AddPlayer, the request is rejected. + * If the desired number of player is invalid, the request is rejected. + * + * If this is the first player to connect, a new game model is created and passed to the controller. Additionally, the playerList's limit is set. + * If the controller successfully adds the player, the username is added to {@link #playerList} and the handler is added to {@link #clientHandlers}. + * + * If any error occurs, the server sends -1 back to the client. Otherwise, it sends 1. + */ public void start(){ - clientHandlers = new ArrayList<>(); try{ - serverTCP = new ServerSocket(port); + socketTCP = new ServerSocket(port); } catch (IOException e){ - System.out.println("Could not listen on port: " + port); + System.out.println("Could not start the server TCP on port: " + port); e.printStackTrace(); return; } - System.out.println("Listening on port: " + port); + System.out.println("Server TCP started on port: " + port); + Socket clientSocket; while(true){ - Socket clientSocket = null; try{ - clientSocket = serverTCP.accept(); + clientSocket = socketTCP.accept(); ObjectInputStream clientSocketObj = new ObjectInputStream(clientSocket.getInputStream()); NetworkEvent event = (NetworkEvent) clientSocketObj.readObject(); - synchronized (gameController){ - if(!(event instanceof AddPlayer) || (gameController.getModel() != null && gameController.getModel().getCurrentPlayerNumber() >= gameController.getModel().getNPlayers())){ - clientSocket.getOutputStream().write((int)(-1)); + if(!(event.getEventType() == EventType.ADD_PLAYER)){ + clientSocket.getOutputStream().write((int)(-1)); + clientSocket.close(); + System.out.println("Invalid parameters. Connection terminated.\n"); + } + else{ + AddPlayer eventAddPlayer = (AddPlayer) event; + if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) { + clientSocket.getOutputStream().write((int) (-1)); clientSocket.close(); System.out.println("Invalid parameters. Connection terminated.\n"); } - else{ - synchronized (gameController) { - AddPlayer addPlayer = (AddPlayer) event; - if(playerList.isEmpty() && (addPlayer.getProposedNPlayer() < 2 || addPlayer.getProposedNPlayer() > 5)){ - clientSocket.getOutputStream().write((int)(-1)); - clientSocket.close(); - System.out.println("Invalid parameters. Connection terminated.\n"); - } - else if(gameController.addPlayer(addPlayer.getUsername())){ - if(playerList.isEmpty()){ - Game game = new Game(addPlayer.getProposedNPlayer()); - gameController.setModel(game); - playerList.setLimit(addPlayer.getProposedNPlayer()); - } - playerList.add(addPlayer.getUsername()); - clientSocket.getOutputStream().write((int)(1)); - } + synchronized (controller) { + if (playerList.isEmpty()){ + Game model = new Game(eventAddPlayer.getProposedNPlayer()); + controller.setModel(model); + playerList.setLimit(eventAddPlayer.getProposedNPlayer()); } + if (controller.addPlayer(eventAddPlayer.getUsername())) { + playerList.add(eventAddPlayer.getUsername()); + clientSocket.getOutputStream().write((int) (1)); + System.out.println("Accepted player: " + eventAddPlayer.getUsername()); + ClientHandler clientHandler = new ClientHandler(clientSocket, clientHandlers, actionQueue); + clientHandlers.add(clientHandler); + ConnectedPlayers++; + + Thread t = new Thread(clientHandler); + t.start(); + } else { + clientSocket.getOutputStream().write((int) (-1)); + clientSocket.close(); + System.out.println("Player could not be added. Connection terminated.\n"); + } } } - // gestione di ADD_PLAYER } catch(IOException e){ e.printStackTrace(); @@ -84,45 +134,18 @@ public class TCPServer { catch(ClassNotFoundException e){ throw new RuntimeException(e); } - - System.out.println("Accepted player: " + gameController.getModel().getPlayerByUsername(clientSocket.getInetAddress().toString())); - - ConnectedPlayers++; - ClientHandler clientHandler = new ClientHandler(clientSocket, clientHandlers, actionQueue); - clientHandlers.add(clientHandler); - - //Sending model to clients - if(ConnectedPlayers == gameController.getModel().getNPlayers()){ - for (ClientHandler handler : clientHandlers) { - try { - synchronized(handler.out){ - ObjectOutputStream socketTx = new ObjectOutputStream(handler.getClientSocket().getOutputStream()); - socketTx.writeObject(gameController.getModel()); - } - } - catch(IOException e){ - e.printStackTrace(); - } - } - } - - Thread t = new Thread(clientHandler); - t.start(); } } - public TCPServer(GameController gameController, int port, BlockingQueue actionQueue, LimitedList players){ - this.port = port; - this.gameController = gameController; - this.actionQueue = actionQueue; - this.playerList = players; - } - public void broadcastUpdate(NetworkEvent event){ + /** Sends an action to all TCP clients */ + public void notifyAll(NetworkEvent event){ clientHandlers.forEach((x) -> x.notifyEvent(event)); } - public void broadcastModel(Game model){ + + /** Sends a game model to all TCP clients */ + public void notifyAll(Game model){ clientHandlers.forEach((x) -> x.notifyModel(model)); } } diff --git a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java index 67d4dfd..9c8a10e 100644 --- a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java +++ b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java @@ -12,16 +12,58 @@ import java.rmi.RemoteException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; + +/** + * Main server launcher that handles both TCP and RMI connections. + * The workflow is divided into two parts: game creation and game execution. + * + * The process flow for game creation is as follows: + * - The first client (TCP/RMI) requests to join the game by providing a username and the desired number of players + * - The TCP/RMI server checks {@link #playerList} and, if it is empty, sets the number of players according to the first user's request using {@link LimitedList#setLimit(int)} + * - The TCP/RMI server creates the {@link Game} with the requested number of players and adds the player to {@link #playerList} + * - Other players request to join the game (their requested number of players is ignored) + * - When the number of players in {@link #playerList} reaches the {@link LimitedList}'s limit, the list calls {@link #run()} + * - All players are notified of the {@link Game} + * + * The process flow for game execution is as follows: + * - The TCP/RMI server receives a {@link NetworkEvent} from a client and adds it to the {@link #actionQueue} + * - The {@link #run()} method repeatedly calls {@link #doFirstEvent()}, which takes the first event in the {@link #actionQueue} and tries to apply it + * - If the event cannot be successfully applied to the model, its {@code isError} flag is set to {@code true} + * - All players are notified of the event + */ public class ServerLauncher { + /** + * Queue containinetworkTypeng the events to be applied to the game model. + * Thread safe by design. + */ BlockingQueue actionQueue; + + /** Game controller. Used to apply events */ GameController gameController; + + /** Server RMI. Handles RMI clients */ RMIServer serverRMI; + + /** Server TCP. Handles TCP clients */ TCPServer serverTCP; - static LimitedList players; - + + /** + * List containing the usernames of joined players. + * {@link LimitedList}'s limit defines at which size the list calls its action + * Both the limit and the action can be set using {@link LimitedList#setLimit(int)} and {@link LimitedList#setAction(Runnable)} + * The limit is set by the first player joining the game. The action consists in calling {@link #run()} + */ + static LimitedList playerList; + /** + * Class constructor that initializes the attributes. + * @param actionQueue The queue containing the events + * @param gameController The game controller + * @param serverRMI The server RMI + * @param serverTCP The server TCP + */ public ServerLauncher(BlockingQueue actionQueue, GameController gameController, RMIServer serverRMI, TCPServer serverTCP) { this.actionQueue = actionQueue; this.serverRMI = serverRMI; @@ -29,56 +71,77 @@ public class ServerLauncher { this.serverTCP = serverTCP; } - public boolean doFirstEven() throws InterruptedException, RemoteException { + + /** + * Takes the first event in the actionQueue and attempts to apply it to the game controller. + * If the event cannot be applied, its isError flag is set to true; otherwise, it is set to false. + * All clients (both TCP and RMI) are notified of the event + * @return the outcome of applying the event to the controller + * @throws InterruptedException if an error occurs while accessing the actionQueue + * @throws RemoteException if an RMI error occurs + */ + public boolean doFirstEvent() throws InterruptedException, RemoteException { NetworkEvent event = actionQueue.take(); - if(event.apply(gameController)) { - serverRMI.notifyAll(event); - serverTCP.broadcastUpdate(event); - return true; - } else { - serverRMI.notifyError(event.getUsername(), "Mossa non valida"); // TODO Converrebbe mettere in network event un booleano che dice se รจ stato accettato e fare una notifyAll anche per errori - //serverTCP // TODO non esiste un notify error (guarda sopra) - return false; - } + event.setIsError(!event.apply(gameController)); + + serverRMI.notifyAll(event); + serverTCP.notifyAll(event); + + return !event.getIsError(); } - public static void main() throws RemoteException, InterruptedException { - // TODO - // Deve avere una coda con gli eventi. - // Il server RMI e il server TCP quando ricevono un doEvent devono aggiungere l'evento alla coda condivisa - // Questa classe esegue gli eventi nella coda e chiama il notify all e notify error sia RMI che TCP - - players = new LimitedList<>(5, ()->{}); + /** + * The first method executed when the server program is launched. + * It creates all the objects needed: playerList, actionQueue, gameController, serverRMI, serverTCP, launcher. + * Then sets the playerList's action to execute launcher.run() and starts the TCP/RMI servers. + * Note: the model is initialized and set in the controller in TCP/RMI servers when the first user decides the number of players. + * + * @throws InterruptedException if this exception is issued by run method + * @throws RemoteException if this exception is issued by run method + */ + public static void main(String[] args) throws InterruptedException, RemoteException { + playerList = new LimitedList<>(5, ()->{}); BlockingQueue actionQueue = new LinkedBlockingQueue<>(); GameController gameController = new GameController(); - RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, players); - TCPServer serverTCP = new TCPServer(gameController, 8080, actionQueue, players); + RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, playerList); + TCPServer serverTCP = new TCPServer(gameController, 8080, actionQueue, playerList); ServerLauncher launcher = new ServerLauncher(actionQueue, gameController, serverRMI, serverTCP); - players.setAction(()->{ - try { - launcher.run(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (RemoteException e) { - throw new RuntimeException(e); - } + playerList.setAction(()->{ + new Thread(()->{ + try { + launcher.run(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }).start(); }); serverRMI.start(); new Thread(()->{serverTCP.start();}).start(); } + + /** + * Creates and executes the game. + * Game creation: TCP/RMI servers send the game model to all players. + * Game execution: repeatedly calls doFirstEvent() to process the events in the actionQueue. + * @throws InterruptedException if the TCP server thread is interrupted + * @throws RemoteException if an RMI error occurs + */ public void run() throws InterruptedException, RemoteException { - + // Game creation + System.out.println("\n\nNotifying model"); serverRMI.notifyAll(gameController.getModel()); - serverTCP.broadcastModel(gameController.getModel()); - + serverTCP.notifyAll(gameController.getModel()); + // Game execution while (true) { try { - this.doFirstEven(); + this.doFirstEvent(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 732a715..e178647 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -8,7 +8,14 @@ module it.polimi.ingsw.gc14 { opens it.polimi.ingsw.gc14 to javafx.fxml, com.google.gson; opens it.polimi.ingsw.gc14.Model to com.google.gson; + opens it.polimi.ingsw.gc14.Model.GamePackage to com.google.gson; exports it.polimi.ingsw.gc14; - opens it.polimi.ingsw.gc14.Model.GamePackage to com.google.gson; + + // RMI + exports it.polimi.ingsw.gc14.Network.RMI.Common to java.rmi; + exports it.polimi.ingsw.gc14.Network.RMI.Server to java.rmi; + exports it.polimi.ingsw.gc14.Network.RMI.Client to java.rmi; + exports it.polimi.ingsw.gc14.Network to java.rmi; + exports it.polimi.ingsw.gc14.Model to java.rmi, com.google.gson; } \ No newline at end of file