From 4434053138a5de36191061733a80a213bddd9a97 Mon Sep 17 00:00:00 2001 From: MatteoPellegrino05 Date: Mon, 18 May 2026 18:59:31 +0200 Subject: [PATCH] Add: complete JavaDoc for MiniModel and TCPServer --- .../it/polimi/ingsw/gc14/Model/MiniModel.java | 122 +++++- .../gc14/Network/TCP/Server/TCPServer.java | 379 ++++++++++++------ 2 files changed, 376 insertions(+), 125 deletions(-) diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/MiniModel.java b/src/main/java/it/polimi/ingsw/gc14/Model/MiniModel.java index 9aef2c0..653c90d 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Model/MiniModel.java +++ b/src/main/java/it/polimi/ingsw/gc14/Model/MiniModel.java @@ -1,37 +1,86 @@ package it.polimi.ingsw.gc14.Model; -import it.polimi.ingsw.gc14.Model.Cards.BuildingCard; -import it.polimi.ingsw.gc14.Model.Cards.TribeCard; -import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Character; import it.polimi.ingsw.gc14.Model.GamePackage.Board; import it.polimi.ingsw.gc14.Model.GamePackage.CurrentState; - import java.io.Serializable; import java.util.*; +/** + * Lightweight representation of the game model shared with clients. + * + *

The mini model contains only the information required by the client-side + * view and controllers to render the current match state and process incoming + * network updates. + */ public class MiniModel implements Serializable { + /** + * Current game board. + */ public Board board; + + /** + * Map associating each occupied slot with the corresponding player. + */ public Map slotPlayerMap; + + /** + * Card used to manage the player turn order. + */ public OrderLogicCard orderLogicCard; + + /** + * Current state of the game. + */ public CurrentState currentState; + + /** + * Map of players indexed by username. + */ public Map players; + + /** + * List of totems still available for selection. + */ public List availableTotems; + + /** + * Final standing of the players at the end of the game. + */ public List standingPlayers; - // --- Costruttori --- - + /** + * Constructs an empty mini model. + */ public MiniModel() { - } + /** + * Constructs a mini model containing only the game board. + * + * @param board the current game board. + */ public MiniModel(Board board) { this.board = board; } - public MiniModel(Board board, Map slotPlayerMap, OrderLogicCard orderLogicCard, - CurrentState currentState, List players, List availableTotems) { + /** + * Constructs a complete mini model from the current server-side game data. + * + * @param board the current game board. + * @param slotPlayerMap the map associating occupied slots with players. + * @param orderLogicCard the card managing player turn order. + * @param currentState the current state of the game. + * @param players the list of players in the match. + * @param availableTotems the list of totems still available for selection. + */ + public MiniModel(Board board, + Map slotPlayerMap, + OrderLogicCard orderLogicCard, + CurrentState currentState, + List players, + List availableTotems) { this.board = board; this.slotPlayerMap = slotPlayerMap; this.orderLogicCard = orderLogicCard; @@ -42,43 +91,90 @@ public class MiniModel implements Serializable { setPlayers(players); } - - // --- Setter --- - + /** + * Sets the current game board. + * + * @param board the board to assign. + */ public void setBoard(Board board) { this.board = board; } + /** + * Sets the list of totems still available for selection. + * + * @param availableTotems the available totems. + */ public void setAvailableTotems(List availableTotems) { this.availableTotems = availableTotems; } + /** + * Sets the map associating occupied slots with players. + * + * @param slotPlayerMap the slot-player map to assign. + */ public void setSlotPlayerMap(Map slotPlayerMap) { this.slotPlayerMap = slotPlayerMap; } + /** + * Sets the card used to manage the player turn order. + * + * @param orderLogicCard the order logic card to assign. + */ public void setOrderLogicCard(OrderLogicCard orderLogicCard) { this.orderLogicCard = orderLogicCard; } + /** + * Sets the current state of the game. + * + * @param currentState the current game state. + */ public void setCurrentState(CurrentState currentState) { this.currentState = currentState; } + /** + * Replaces or updates the players stored in the mini model. + * + *

Each player is indexed by username. + * + * @param players the list of players to store. + */ public void setPlayers(List players) { for (Player player : players) { this.players.put(player.getUserName(), player); } } + /** + * Adds or updates a single player in the mini model. + * + * @param player the player to store. + */ public void setPlayer(Player player) { players.put(player.getUserName(), player); } + + /** + * Sets the final standing of the players. + * + * @param standingPlayers the final ordered list of players. + */ public void setStandingPlayers(ArrayList standingPlayers) { this.standingPlayers = standingPlayers; } - + /** + * Returns the position of the player with the specified username + * within the player collection. + * + * @param username the username of the player to search for. + * @return the zero-based position of the player, or {@code -1} + * if no matching username is found. + */ public int getPositionByUsername(String username) { int i = 0; for (Player player : players.values()) { 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 d06db96..f5ff513 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 @@ -18,33 +18,96 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; /** - * Server TCP. Accepts connections and manages all client handlers. + * TCP server responsible for accepting client connections, + * handling player registration and reconnection, and sending + * game updates to connected clients. + * + *

The server also manages a dedicated heartbeat channel used + * to detect disconnected clients and associate each heartbeat + * connection with the corresponding {@link ClientHandler}. */ public class TCPServer { + /** + * Main TCP port used for standard client-server communication. + */ int port; - int heartbeatPort; // ← nuova porta + /** + * TCP port dedicated to heartbeat communication. + */ + int heartbeatPort; + + /** + * Number of players that have successfully connected. + */ int connectedPlayers; - ServerSocket socketTCP; - ServerSocket heartbeatSocketTCP; // ← nuovo ServerSocket + /** + * Main server socket used to accept client connections. + */ + ServerSocket socketTCP; + + /** + * Server socket used to accept heartbeat connections. + */ + ServerSocket heartbeatSocketTCP; + + /** + * Game controller used to manage the server-side game logic. + */ final GameController controller; + + /** + * Queue containing network events received from clients. + */ BlockingQueue actionQueue; + + /** + * Map storing the online/offline status of connected players. + */ LimitedMap playerList; + + /** + * List of active TCP client handlers. + */ List clientHandlers; + /** + * Flag indicating whether the server is recovering from a previous crash. + */ boolean serverCrashed; + + /** + * Sets whether the server is recovering from a previous crash. + * + * @param serverCrashed {@code true} if the server is in crash-recovery mode, + * {@code false} otherwise. + */ public void setServerCrashed(boolean serverCrashed) { this.serverCrashed = serverCrashed; } - // Mappa temporanea: username → ClientHandler - // Serve per associare il socket heartbeat al giusto ClientHandler + /** + * Temporary map associating each username with the corresponding + * {@link ClientHandler} waiting for its heartbeat connection. + */ private final Map pendingHeartbeat = new ConcurrentHashMap<>(); - public TCPServer(GameController controller, int port, int heartbeatPort, - BlockingQueue actionQueue, LimitedMap playerList) { + /** + * Constructs a TCP server with the required game and network components. + * + * @param controller the game controller used to manage the game logic. + * @param port the main TCP port used for client communication. + * @param heartbeatPort the TCP port dedicated to heartbeat connections. + * @param actionQueue the queue containing incoming network events. + * @param playerList the map storing the connection status of the players. + */ + public TCPServer(GameController controller, + int port, + int heartbeatPort, + BlockingQueue actionQueue, + LimitedMap playerList) { this.port = port; this.heartbeatPort = heartbeatPort; this.connectedPlayers = 0; @@ -54,8 +117,24 @@ public class TCPServer { this.clientHandlers = new ArrayList<>(); } + /** + * Starts the TCP server and begins accepting client connections. + * + *

The method opens both the main TCP server socket and the dedicated + * heartbeat server socket. It then starts a separate thread for heartbeat + * connections and continuously waits for new players or reconnecting clients. + * + *

When a new connection is received, the first event must be an + * {@link AddPlayer} request. Depending on the current server state, + * the connection is handled either as a new player joining the game + * or as a reconnection attempt. + * + * @param serverCrashed {@code true} if the server is being restarted after a crash, + * {@code false} otherwise. + */ public void start(boolean serverCrashed) { this.serverCrashed = serverCrashed; + try { socketTCP = new ServerSocket(port); heartbeatSocketTCP = new ServerSocket(heartbeatPort); @@ -68,16 +147,19 @@ public class TCPServer { System.out.println("TCP server started on port: " + port); System.out.println("Heartbeat server started on port: " + heartbeatPort); - // Thread separato per accettare le connessioni heartbeat new Thread(this::acceptHeartbeat, "heartbeat-acceptor").start(); - // Loop principale — invariato nella logica, cambia solo la creazione del ClientHandler while (true) { try { Socket clientSocket = socketTCP.accept(); - ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream()); - ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream()); + + ObjectOutputStream clientSend = + new ObjectOutputStream(clientSocket.getOutputStream()); + ObjectInputStream clientReceive = + new ObjectInputStream(clientSocket.getInputStream()); + NetworkEvent event = (NetworkEvent) clientReceive.readObject(); + if (!(event.getEventType() == EventType.ADD_PLAYER)) { clientSocket.getOutputStream().write(-1); clientSocket.close(); @@ -87,136 +169,195 @@ public class TCPServer { AddPlayer eventAddPlayer = (AddPlayer) event; - if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) { + if (eventAddPlayer.getProposedNPlayer() < 2 + || eventAddPlayer.getProposedNPlayer() > 5) { clientSocket.getOutputStream().write(-1); clientSocket.close(); System.out.println("Invalid parameters. Connection terminated."); continue; } - synchronized (controller) { + synchronized (controller) { + String username = eventAddPlayer.getUsername(); - String username = eventAddPlayer.getUsername(); - if(serverCrashed){ - if(controller.getModel().getPlayers().stream().anyMatch(p -> p.getUserName().equals(username))&& !playerList.containsKey(username)){ - playerList.put(username, true); - System.out.println("(After crash)Reconnected player: " + username); - ClientHandler handler = new ClientHandler( - username, clientSocket, clientSend, clientReceive, - clientHandlers,playerList, actionQueue - ); - clientSocket.getOutputStream().write(1); - pendingHeartbeat.put(username, handler); - Thread thread = new Thread(handler); - thread.start(); - clientHandlers.add(handler); - connectedPlayers++; - } - else if(playerList.containsKey(username) && !playerList.get(username)){ - // riconnessione - playerList.put(username, true); - System.out.println("Reconnected player: " + username); + if (serverCrashed) { + if (controller.getModel().getPlayers().stream() + .anyMatch(p -> p.getUserName().equals(username)) + && !playerList.containsKey(username)) { - ClientHandler handler = new ClientHandler( - username, clientSocket, clientSend, clientReceive, - clientHandlers, playerList, actionQueue - ); - clientSocket.getOutputStream().write(1); - pendingHeartbeat.put(username, handler); - Game game = controller.getModel(); - handler.notifyMiniModel(new MiniModel(game.getBoard(),game.getSlotMap(), game.orderLogicCard,game.getCurrentState(),game.getPlayers(),game.getAvailableTotems())); - Thread thread = new Thread(handler); - thread.start(); - clientHandlers.add(handler); - connectedPlayers++; - actionQueue.add(new ReconnectPlayer(username)); - } - else - { - clientSocket.getOutputStream().write(-1); - clientSocket.close(); - System.out.println("Player could not be added. Connection terminated."); - } - } - else - { - if (playerList.isEmpty()) { - Game model = new Game(eventAddPlayer.getProposedNPlayer()); - controller.setModel(model); - playerList.setLimit(eventAddPlayer.getProposedNPlayer()); - } - if (controller.addPlayer(username)) { - // nuovo giocatore - playerList.put(username, true); - System.out.println("Accepted player: " + username); - ClientHandler handler = new ClientHandler( - username, clientSocket, clientSend, clientReceive, - clientHandlers,playerList, actionQueue - ); - clientSocket.getOutputStream().write(1); - pendingHeartbeat.put(username, handler); - Thread thread = new Thread(handler); - thread.start(); - clientHandlers.add(handler); - connectedPlayers++; - // metti in attesa del socket heartbeat - } - else if(playerList.containsKey(username) && !playerList.get(username)){ - // riconnessione - playerList.put(username, true); - System.out.println("Reconnected player: " + username); + playerList.put(username, true); + System.out.println("(After crash)Reconnected player: " + username); - ClientHandler handler = new ClientHandler( - username, clientSocket, clientSend, clientReceive, - clientHandlers, playerList, actionQueue - ); - clientSocket.getOutputStream().write(1); - pendingHeartbeat.put(username, handler); - Game game = controller.getModel(); - handler.notifyMiniModel(new MiniModel(game.getBoard(),game.getSlotMap(), game.orderLogicCard,game.getCurrentState(),game.getPlayers(),game.getAvailableTotems())); - Thread thread = new Thread(handler); - thread.start(); - clientHandlers.add(handler); - connectedPlayers++; - actionQueue.add(new ReconnectPlayer(username)); - } - else{ - clientSocket.getOutputStream().write(-1); - clientSocket.close(); - System.out.println("Player could not be added. Connection terminated."); - } - } + ClientHandler handler = new ClientHandler( + username, + clientSocket, + clientSend, + clientReceive, + clientHandlers, + playerList, + actionQueue + ); - } + clientSocket.getOutputStream().write(1); + pendingHeartbeat.put(username, handler); + Thread thread = new Thread(handler); + thread.start(); - } - catch(IOException | ClassNotFoundException e){ + clientHandlers.add(handler); + connectedPlayers++; + + } else if (playerList.containsKey(username) + && !playerList.get(username)) { + + playerList.put(username, true); + System.out.println("Reconnected player: " + username); + + ClientHandler handler = new ClientHandler( + username, + clientSocket, + clientSend, + clientReceive, + clientHandlers, + playerList, + actionQueue + ); + + clientSocket.getOutputStream().write(1); + pendingHeartbeat.put(username, handler); + + Game game = controller.getModel(); + handler.notifyMiniModel(new MiniModel( + game.getBoard(), + game.getSlotMap(), + game.orderLogicCard, + game.getCurrentState(), + game.getPlayers(), + game.getAvailableTotems() + )); + + Thread thread = new Thread(handler); + thread.start(); + + clientHandlers.add(handler); + connectedPlayers++; + actionQueue.add(new ReconnectPlayer(username)); + + } else { + clientSocket.getOutputStream().write(-1); + clientSocket.close(); + System.out.println("Player could not be added. Connection terminated."); + } + + } else { + if (playerList.isEmpty()) { + Game model = new Game(eventAddPlayer.getProposedNPlayer()); + controller.setModel(model); + playerList.setLimit(eventAddPlayer.getProposedNPlayer()); + } + + if (controller.addPlayer(username)) { + playerList.put(username, true); + System.out.println("Accepted player: " + username); + + ClientHandler handler = new ClientHandler( + username, + clientSocket, + clientSend, + clientReceive, + clientHandlers, + playerList, + actionQueue + ); + + clientSocket.getOutputStream().write(1); + pendingHeartbeat.put(username, handler); + + Thread thread = new Thread(handler); + thread.start(); + + clientHandlers.add(handler); + connectedPlayers++; + + } else if (playerList.containsKey(username) + && !playerList.get(username)) { + + playerList.put(username, true); + System.out.println("Reconnected player: " + username); + + ClientHandler handler = new ClientHandler( + username, + clientSocket, + clientSend, + clientReceive, + clientHandlers, + playerList, + actionQueue + ); + + clientSocket.getOutputStream().write(1); + pendingHeartbeat.put(username, handler); + + Game game = controller.getModel(); + handler.notifyMiniModel(new MiniModel( + game.getBoard(), + game.getSlotMap(), + game.orderLogicCard, + game.getCurrentState(), + game.getPlayers(), + game.getAvailableTotems() + )); + + Thread thread = new Thread(handler); + thread.start(); + + clientHandlers.add(handler); + connectedPlayers++; + actionQueue.add(new ReconnectPlayer(username)); + + } else { + clientSocket.getOutputStream().write(-1); + clientSocket.close(); + System.out.println("Player could not be added. Connection terminated."); + } + } + } + + } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } /** - * Accetta connessioni sul socket heartbeat e le associa al ClientHandler giusto. - * Il client manda subito il proprio username per identificarsi. + * Accepts heartbeat connections and associates them with the correct client handler. + * + *

Each client immediately sends its username on the heartbeat channel. + * The method uses that username to retrieve the pending {@link ClientHandler} + * and starts a dedicated {@link HeartbeatHandler}. If no pending handler is found, + * the heartbeat socket is closed. */ private void acceptHeartbeat() { while (true) { try { Socket hbSocket = heartbeatSocketTCP.accept(); - ObjectInputStream hbIn = new ObjectInputStream(hbSocket.getInputStream()); + ObjectInputStream hbIn = + new ObjectInputStream(hbSocket.getInputStream()); - // il client manda subito il suo username String username = (String) hbIn.readObject(); ClientHandler handler = pendingHeartbeat.remove(username); + if (handler != null) { - HeartbeatHandler hb = new HeartbeatHandler(username, hbSocket, handler); + HeartbeatHandler hb = + new HeartbeatHandler(username, hbSocket, handler); + new Thread(hb, "heartbeat-" + username).start(); System.out.println("Heartbeat connected for: " + username); } else { - System.out.println("No pending handler for: " + username + ", closing heartbeat."); + System.out.println( + "No pending handler for: " + username + ", closing heartbeat." + ); hbSocket.close(); } @@ -226,13 +367,27 @@ public class TCPServer { } } + /** + * Notifies connected TCP clients of a new network event. + * + *

If the event does not represent an error, it is sent to all connected clients. + * If it represents an error, it is sent only to the client that requested the action. + * + * @param event the network event to send to the clients. + */ public void notifyAll(NetworkEvent event) { clientHandlers.forEach(h -> { - if (!event.getIsError() || event.getUsername().equals(h.getUsername())) + if (!event.getIsError() || event.getUsername().equals(h.getUsername())) { h.notifyEvent(event); + } }); } + /** + * Notifies all connected TCP clients of a new game model. + * + * @param model the updated mini model to send to the clients. + */ public void notifyAll(MiniModel model) { clientHandlers.forEach(h -> h.notifyMiniModel(model)); }