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 70cc100..8e39e4a 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Controller/ClientController.java +++ b/src/main/java/it/polimi/ingsw/gc14/Controller/ClientController.java @@ -26,6 +26,9 @@ public class ClientController { /** Network client (either TCP or RMI) */ private IClient client; + /** + * The username of the client associated with this event. + */ public String myUsername; @@ -65,6 +68,11 @@ public class ClientController { view.setModel(miniModel); } + /** + * Sets the username of the client associated with this event. + * + * @param username the username to set. + */ public void setMyUsername (String username) { this.myUsername=username; } @@ -170,7 +178,16 @@ public class ClientController { } } - //TODO + /** + * Handles the selection of a totem by the specified player. + * + *
If the player is not the current one, an error message is shown.
+ * Otherwise, the selected totem is retrieved from the available totems
+ * and the choice is forwarded to the client.
+ *
+ * @param playerUsername the username of the player making the choice.
+ * @param pos the position of the selected totem in the available totems list.
+ */
public void totemChoice(String playerUsername,int pos) {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!");
diff --git a/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java b/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java
index 0a5ade5..382b2f6 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java
@@ -30,7 +30,14 @@ public class GameController {
*/
public GameController() {
}
- //TODO
+
+ /**
+ * Marks the player associated with the specified username as disconnected.
+ *
+ * @param username the username of the player who disconnected.
+ * @return {@code true} if the disconnection is handled successfully,
+ * {@code false} if no player with the specified username exists.
+ */
public boolean DisconnectedPlayer(String username)
{
Player player= model.getPlayerByUsername(username);
@@ -38,7 +45,14 @@ public class GameController {
return false;
return model.DisconnectedPlayer(player);
}
- //TODO
+
+ /**
+ * Marks the player associated with the specified username as reconnected.
+ *
+ * @param username the username of the player who reconnected.
+ * @return {@code true} if the reconnection is handled successfully,
+ * {@code false} if no player with the specified username exists.
+ */
public boolean ReconnectPlayer(String username)
{
Player player= model.getPlayerByUsername(username);
@@ -46,6 +60,7 @@ public class GameController {
return false;
return model.ReconnectPlayer(player);
}
+
/**
* Returns the game model managed by this controller.
*
@@ -166,7 +181,14 @@ public class GameController {
return model.SlotChoiceByIndex(model.getPlayerByUsername(playerUsername),pos);
}
- //TODO
+ /**
+ * Applies the totem choice made by the player associated with the specified username.
+ *
+ * @param playerUsername the username of the player making the choice.
+ * @param totem the name of the selected totem.
+ * @return {@code true} if the choice is handled successfully,
+ * {@code false} if no player with the specified username exists.
+ */
public boolean TotemChoice(String playerUsername,String totem) {
Player player= model.getPlayerByUsername(playerUsername);
if(player==null)
diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/Cards/Building/EffectType.java b/src/main/java/it/polimi/ingsw/gc14/Model/Cards/Building/EffectType.java
index ed7ba06..6377ae2 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Model/Cards/Building/EffectType.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Model/Cards/Building/EffectType.java
@@ -4,5 +4,34 @@ package it.polimi.ingsw.gc14.Model.Cards.Building;
* Represents the possible effect types of building cards.
*/
public enum EffectType {
- FINAL, CARD_SET, INVENTOR_PAIR, ON_EVENT, ON_END_TURN, ON_ROUND_END
-}
+
+ /**
+ * Effect applied during the final scoring phase.
+ */
+ FINAL,
+
+ /**
+ * Effect based on collecting a specific set of cards.
+ */
+ CARD_SET,
+
+ /**
+ * Effect based on pairs of Inventor cards.
+ */
+ INVENTOR_PAIR,
+
+ /**
+ * Effect triggered when an event card is resolved.
+ */
+ ON_EVENT,
+
+ /**
+ * Effect triggered at the end of a player's turn.
+ */
+ ON_END_TURN,
+
+ /**
+ * Effect triggered at the end of a round.
+ */
+ ON_ROUND_END
+ }
diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/CharacterType.java b/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/CharacterType.java
index 68f3962..f86db05 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/CharacterType.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/CharacterType.java
@@ -4,5 +4,34 @@ package it.polimi.ingsw.gc14.Model.Cards.TribeCards;
* Represents the different types of character cards available in the game.
*/
public enum CharacterType {
- INVENTOR, BUILDER, GATHERER, ARTIST, SHAMAN, HUNTER
+
+ /**
+ * Character card representing an Inventor.
+ */
+ INVENTOR,
+
+ /**
+ * Character card representing a Builder.
+ */
+ BUILDER,
+
+ /**
+ * Character card representing a Gatherer.
+ */
+ GATHERER,
+
+ /**
+ * Character card representing an Artist.
+ */
+ ARTIST,
+
+ /**
+ * Character card representing a Shaman.
+ */
+ SHAMAN,
+
+ /**
+ * Character card representing a Hunter.
+ */
+ HUNTER
}
diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/EventType.java b/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/EventType.java
index 1b6720a..9752352 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/EventType.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Model/Cards/TribeCards/EventType.java
@@ -4,5 +4,24 @@ package it.polimi.ingsw.gc14.Model.Cards.TribeCards;
* Represents the different types of event cards available in the game.
*/
public enum EventType {
- SUSTENANCE, SHAMANIC_RITUAL, CAVE_PAINTINGS, HUNT
-}
+
+ /**
+ * Event card representing Sustenance.
+ */
+ SUSTENANCE,
+
+ /**
+ * Event card representing the Shamanic Ritual.
+ */
+ SHAMANIC_RITUAL,
+
+ /**
+ * Event card representing Cave Paintings.
+ */
+ CAVE_PAINTINGS,
+
+ /**
+ * Event card representing the Hunt.
+ */
+ HUNT
+ }
\ No newline at end of file
diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/Game.java b/src/main/java/it/polimi/ingsw/gc14/Model/Game.java
index 776ebf3..5fd6418 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Model/Game.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Model/Game.java
@@ -29,11 +29,20 @@ import java.util.stream.Stream;
*/
public class Game implements Serializable {
-
+ /**
+ * The final ranking of players at the end of the game.
+ */
private ArrayList The choice is accepted only if the game is currently in the
+ * {@link GameStages#TOTEM_CHOICE} stage, the player is the current one,
+ * and the selected totem is still available.
+ *
+ * After a valid choice, the method advances to the next player in the queue.
+ * If all players have completed the selection, the game moves to the slot choice phase.
+ * Disconnected players are automatically assigned a random available totem.
+ *
+ * @param player the player making the totem choice.
+ * @param totem the selected totem.
+ * @return {@code true} if the choice is applied successfully, {@code false} otherwise.
+ */
public boolean TotemChoice(Player player,Totems totem) {
if(!currentState.getGameStage().equals(GameStages.TOTEM_CHOICE))
return false;
@@ -87,9 +118,23 @@ public class Game implements Serializable {
return true;
}
- //TODO
+ /**
+ * Map tracking the players who are currently disconnected.
+ */
public Map If the player disconnects during the waiting phase, they are removed from
+ * the player list and from the totem choice queue. If the disconnected player is
+ * the current one, the game advances to the next suitable player or, during the
+ * totem choice phase, handles the remaining selection flow automatically.
+ *
+ * @param player the player who disconnected.
+ * @return {@code true} if the disconnection is handled successfully,
+ * {@code false} if the player was already marked as disconnected.
+ */
public boolean DisconnectedPlayer(Player player)
{
if(disconnetedPlayers.containsKey(player) && disconnetedPlayers.get(player))
@@ -121,7 +166,17 @@ public class Game implements Serializable {
return true;
}
- //TODO
+ /**
+ * Marks the specified player as reconnected.
+ *
+ * If the player reconnects during the slot choice phase, they are removed
+ * from the disconnected players map and reinserted into the order logic card
+ * when necessary.
+ *
+ * @param player the player who reconnected.
+ * @return {@code true} if the reconnection is handled successfully,
+ * {@code false} if the player was not previously marked as disconnected.
+ */
public boolean ReconnectPlayer(Player player)
{
if(!disconnetedPlayers.containsKey(player))
@@ -140,12 +195,14 @@ public class Game implements Serializable {
return true;
}
- //Totem
- //TODO
+ /**
+ * Clears the collection of disconnected players.
+ */
public void ClearDisconnected()
{
disconnetedPlayers.clear();
}
+
/**
* Returns the current number of players participating in the game.
* @return the current number of players.
@@ -195,7 +252,12 @@ public class Game implements Serializable {
* The board associated with this game.
*/
private Board board;
- //TODO
+
+ /**
+ * Returns the game board.
+ *
+ * @return the board associated with the game.
+ */
public Board getBoard() {return board;}
/**
@@ -925,6 +987,13 @@ public class Game implements Serializable {
return true;
}
+ /**
+ * Ends the game by forfeit and determines the final player standing.
+ *
+ * The player who is still connected is declared the winner and placed
+ * in the first position of the final ranking. The remaining players are
+ * ordered by prestige value and, in case of a tie, by food value.
+ */
public void EndGameForFeit() {
Player winner=playersList.stream().filter(x->!disconnetedPlayers.containsKey(x)||!disconnetedPlayers.get(x)).toList().get(0);
currentState.GameStageUpdate(GameStages.ENDED);
diff --git a/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/GameStages.java b/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/GameStages.java
index c0ba67e..fad51b1 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/GameStages.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Model/GamePackage/GameStages.java
@@ -4,5 +4,43 @@ package it.polimi.ingsw.gc14.Model.GamePackage;
* Represents the possible stages of a game.
*/
public enum GameStages {
- WAITING,TOTEM_CHOICE, SLOT_CHOICE, RES_ACTIONS, OPT_CARD_E, RES_EVENT, ENDING, ENDED
-}
+ /**
+ * Stage in which the game is waiting for players to join.
+ */
+ WAITING,
+
+ /**
+ * Stage in which players choose their totems.
+ */
+ TOTEM_CHOICE,
+
+ /**
+ * Stage in which players select their action slots.
+ */
+ SLOT_CHOICE,
+
+ /**
+ * Stage in which the mandatory actions associated with the chosen slots are resolved.
+ */
+ RES_ACTIONS,
+
+ /**
+ * Stage in which an optional card effect can be resolved.
+ */
+ OPT_CARD_E,
+
+ /**
+ * Stage in which an event card is being resolved.
+ */
+ RES_EVENT,
+
+ /**
+ * Stage in which end-of-round or end-of-game operations are processed.
+ */
+ ENDING,
+
+ /**
+ * Stage indicating that the game has ended.
+ */
+ ENDED
+ }
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 Each player is indexed by username.
+ *
+ * @param players the list of players to store.
+ */
public void setPlayers(List A network event contains the information required to identify
+ * the requested action, determine whether it produced an error,
+ * and apply it either to the server-side {@link GameController}
+ * or to the client-side {@link MiniModel}.
*/
public abstract class NetworkEvent implements Serializable {
- /** Username of the player requesting the event */
+
+ /**
+ * Username of the player requesting the event.
+ */
protected String username;
/**
- * @return the username of the player requesting the event
+ * Returns the username of the player requesting the event.
+ *
+ * @return the username associated with the event.
*/
public String getUsername() {
return username;
}
- /** EventType of the event */
+ /**
+ * Type of the network event.
+ */
protected EventType eventType;
+ /**
+ * Map associating each occupied slot with the corresponding player.
+ */
protected Map If the event does not represent an error, the player data,
+ * turn order, current game state, and slot assignments are updated.
+ *
+ * @param miniModel the mini model on which to apply the event.
+ * @return {@code true} if the update is applied successfully,
+ * {@code false} if the event represents an error.
+ */
@Override
- public boolean apply(MiniModel miniModel){
- if(isError)
+ public boolean apply(MiniModel miniModel) {
+ if (isError)
return false;
- if(miniModel!=null) {
+
+ if (miniModel != null) {
miniModel.setPlayer(player);
miniModel.setOrderLogicCard(orderLogicCard);
miniModel.setCurrentState(currentState);
miniModel.setSlotPlayerMap(slotPlayerMap);
}
+
return true;
}
-
}
\ No newline at end of file
diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/TotemChoice.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/TotemChoice.java
index e911423..04ace18 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/TotemChoice.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/TotemChoice.java
@@ -5,49 +5,78 @@ import it.polimi.ingsw.gc14.Model.MiniModel;
import it.polimi.ingsw.gc14.Model.Totems;
import it.polimi.ingsw.gc14.Network.EventType;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
-import it.polimi.ingsw.gc14.View.IView;
import java.io.Serializable;
import java.util.List;
/**
- * NetworkEvent to select a slot where to place the player totem.
+ * Network event used to handle a player's totem choice.
*/
-public class TotemChoice extends NetworkEvent implements Serializable {
+public class TotemChoice extends NetworkEvent implements Serializable {
- /** Index of the card to draw */
+ /**
+ * Name of the totem selected by the player.
+ */
private String totem;
- private List If the event does not represent an error, the player data,
+ * turn order, current game state, slot assignments, and available
+ * totems are updated.
+ *
+ * @param miniModel the mini model on which to apply the event.
+ * @return {@code true} if the update is applied successfully,
+ * {@code false} if the event represents an error.
+ */
@Override
- public boolean apply(MiniModel miniModel){
- if(isError)
+ public boolean apply(MiniModel miniModel) {
+ if (isError)
return false;
+
miniModel.setPlayer(player);
miniModel.setOrderLogicCard(orderLogicCard);
miniModel.setCurrentState(currentState);
miniModel.setSlotPlayerMap(slotPlayerMap);
miniModel.setAvailableTotems(availableTotems);
return true;
-
}
-
-}
+}
\ No newline at end of file
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 313e605..44f425a 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,18 +1,13 @@
package it.polimi.ingsw.gc14.Network.RMI.Client;
-import java.net.InetAddress;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
-import java.util.Objects;
import java.util.concurrent.*;
import it.polimi.ingsw.gc14.Controller.ClientController;
import it.polimi.ingsw.gc14.Network.IClient;
-import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
-import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback;
import it.polimi.ingsw.gc14.Network.RMI.Common.IGameServer;
-import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
/**
* Client RMI. Uses the methods exposed by the server RMI.
@@ -34,7 +29,14 @@ public class RMIClient implements IClient {
/** Scheduler that fires ping() every PING_INTERVAL_S seconds. */
private ScheduledExecutorService pingSender;
-
+ /**
+ * Constructs an RMI client and initializes its connection parameters.
+ *
+ * @param controller the client controller associated with this RMI client.
+ * @param host the hostname or IP address of the RMI server.
+ * @param port the port used to connect to the RMI registry.
+ * @param myIP the IP address of the client.
+ */
public RMIClient(ClientController controller, String host, int port, String myIP) {
this.controller = controller;
this.host = host;
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 c68c5b9..e09a065 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
@@ -21,7 +21,7 @@ public interface IClientCallback extends Remote, Serializable {
/**
* Notifies the client that the game model has been initialized or updated.
*
- * @param model the current game model.
+ * @param miniModel the current mini model of the game.
* @throws RemoteException if an RMI communication error occurs.
*/
void onGameInit(MiniModel miniModel) throws RemoteException;
diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IGameServer.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IGameServer.java
index 64177ab..aef508d 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IGameServer.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Common/IGameServer.java
@@ -1,7 +1,5 @@
package it.polimi.ingsw.gc14.Network.RMI.Common;
-import it.polimi.ingsw.gc14.Network.NetworkEvent;
-
import java.rmi.*;
/**
@@ -9,22 +7,89 @@ import java.rmi.*;
*/
public interface IGameServer extends Remote {
+ /**
+ * Adds a player to the game and registers the callback used by the server
+ * to notify the corresponding RMI client.
+ *
+ * @param username the username chosen by the player.
+ * @param preferredInt the preferred number of players for the game.
+ * @param callback the remote callback associated with the client.
+ * @return {@code true} if the player joins the game successfully,
+ * {@code false} otherwise.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
boolean joinGame(String username, int preferredInt, IClientCallback callback) throws RemoteException;
- boolean doEvent(NetworkEvent event) throws RemoteException;
-
- void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException;
- void drawLowerTribeCard(String playerUsername, int pos) throws RemoteException;
- void drawUpperBuildingCard(String playerUsername, int pos) throws RemoteException;
- void drawLowerBuildingCard(String playerUsername, int pos) throws RemoteException;
- void skipTurn(String playerUsername) throws RemoteException;
- void slotChoice(String playerUsername, int pos) throws RemoteException;
- void totemChoice(String playerUsername, String totems) throws RemoteException;
/**
- * Heartbeat: called periodically by the client to signal it is still alive.
- * Mirrors the PING/PONG mechanism used in the TCP heartbeat channel.
+ * Requests to draw a tribe card from the upper tribe card list.
*
- * @param username the username of the client sending the ping.
+ * @param playerUsername the username of the player performing the action.
+ * @param pos the position of the selected card.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException;
+
+ /**
+ * Requests to draw a tribe card from the lower tribe card list.
+ *
+ * @param playerUsername the username of the player performing the action.
+ * @param pos the position of the selected card.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void drawLowerTribeCard(String playerUsername, int pos) throws RemoteException;
+
+ /**
+ * Requests to draw a building card from the upper building card list.
+ *
+ * @param playerUsername the username of the player performing the action.
+ * @param pos the position of the selected card.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void drawUpperBuildingCard(String playerUsername, int pos) throws RemoteException;
+
+ /**
+ * Requests to draw a building card from the lower building card list.
+ *
+ * @param playerUsername the username of the player performing the action.
+ * @param pos the position of the selected card.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void drawLowerBuildingCard(String playerUsername, int pos) throws RemoteException;
+
+ /**
+ * Requests to skip the current player's optional action.
+ *
+ * @param playerUsername the username of the player skipping the action.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void skipTurn(String playerUsername) throws RemoteException;
+
+ /**
+ * Requests to assign the specified player to a slot.
+ *
+ * @param playerUsername the username of the player making the slot choice.
+ * @param pos the position of the selected slot.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void slotChoice(String playerUsername, int pos) throws RemoteException;
+
+ /**
+ * Requests to assign the selected totem to the specified player.
+ *
+ * @param playerUsername the username of the player making the totem choice.
+ * @param totems the name of the selected totem.
+ * @throws RemoteException if an RMI communication error occurs.
+ */
+ void totemChoice(String playerUsername, String totems) throws RemoteException;
+
+ /**
+ * Heartbeat method called periodically by the client to signal
+ * that it is still connected.
+ *
+ * This method mirrors the PING/PONG mechanism used in the TCP
+ * heartbeat channel.
+ *
+ * @param username the username of the client sending the heartbeat ping.
* @throws RemoteException if an RMI communication error occurs.
*/
void ping(String username) throws RemoteException;
diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIHeartbeat.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIHeartbeat.java
index e9ee1f0..4d29816 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIHeartbeat.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIHeartbeat.java
@@ -1,7 +1,6 @@
package it.polimi.ingsw.gc14.Network.RMI.Server;
import it.polimi.ingsw.gc14.LimitedMap;
-import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer;
@@ -15,14 +14,13 @@ import java.util.concurrent.*;
* instead of reading raw bytes from a dedicated socket, it relies on {@link #receivePing()}
* being called by {@link RMIServer#ping(String)} every time the client sends a ping.
*
- * If no ping is received within {@value SILENCE_THRESHOLD_MS} ms the player is
+ * If no ping is received within {@value SILENCE_THRESHOLD_MS} ms, the player is
* considered disconnected and {@link #disconnect()} is invoked, which:
* The heartbeat handler is stopped, the player is marked as offline,
+ * the associated RMI callback is removed, and a disconnection event is
+ * added to the action queue for server-side processing.
*/
- public void setGame(Game game) {
- this.game = game;
- }
-
- // -------------------------------------------------------------------------
-
private void disconnect() {
if (!running) return;
running = false;
watchdog.shutdownNow();
- // Mark player as offline
playerList.put(username, false);
- // Remove RMI callback so notifyAll skips this client
clients.remove(username);
actionQueue.add(new DisconnectedPlayer(username));
System.out.println("RMI disconnected: " + username);
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 1f8110b..5c10a9a 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
@@ -21,7 +21,12 @@ import java.rmi.*;
/**
- * Server RMI. Exposes a method to join the game and one to execute an event.
+ * RMI server responsible for handling remote client connections,
+ * receiving player actions, and propagating game updates.
+ *
+ * The server manages player registration, reconnection handling,
+ * heartbeat monitoring, and the forwarding of client requests to the
+ * shared network event queue.
*/
public class RMIServer extends UnicastRemoteObject implements IGameServer {
private String host;
@@ -43,10 +48,26 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
private LimitedMap If the event does not represent an error, it is sent to all registered clients.
+ * If it represents an error, it is sent only to the client that requested the action.
+ * Communication errors are logged without interrupting the notification process.
+ *
+ * @param action the network event to send to the clients.
*/
public void notifyAll(NetworkEvent action){
for (Map.Entry The updated mini model is sent to each registered client callback.
+ * If a communication error occurs for a client, the exception is logged
+ * without interrupting the notification of the remaining clients.
+ *
+ * @param model the updated mini model to send to the connected clients.
*/
public void notifyAll(MiniModel model) {
- // Keep every watchdog's game reference up to date
- synchronized (controller){
- watchdogs.values().forEach(wd -> wd.setGame(controller.getModel()));
- }
for (IClientCallback cb : clients.values()) {
try{
cb.onGameInit(model);
@@ -188,17 +213,6 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
}
}
-
-
- /**
- * Push an action in actionQueue.
- * @param action The desired actio
- * @return true if the action was successfully added, false otherwise
- */
- public boolean doEvent(NetworkEvent action) {
- return actionQueue.offer(action);
- }
-
/**
* Requests to draw a tribe card from the upper list.
* Creates a NetworkEvent and sends it through the network client.
@@ -269,10 +283,17 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
}
- // Metodi per avviare server RMI
/**
- * Starts the RMI server.
- * @return true if the server starts successfully, false otherwise
+ * Starts the RMI server and binds it to the configured registry.
+ *
+ * The method configures the server hostname, creates the RMI registry
+ * on the specified port, and registers this server instance under the
+ * {@code RMIGameServer} name.
+ *
+ * @param serverCrashed {@code true} if the server is being restarted after a crash,
+ * {@code false} otherwise.
+ * @return {@code true} if the server starts successfully,
+ * {@code false} otherwise.
*/
public boolean start(boolean serverCrashed) {
this.serverCrashed = serverCrashed;
@@ -288,12 +309,21 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
}
}
+ /**
+ * Stops the RMI server and removes its binding from the registry.
+ *
+ * The remote server object is unexported, the registry entry associated
+ * with {@code RMIGameServer} is removed, and the server shutdown is logged.
+ *
+ * @return {@code true} if the server is stopped successfully,
+ * {@code false} otherwise.
+ */
public boolean stop() {
try {
registry.unbind("RMIGameServer");
UnicastRemoteObject.unexportObject(this, true);
watchdogs.values().forEach(wd -> { /* watchdogs shut themselves down */ });
- System.out.println("RMI Server fermato");
+ System.out.println("RMI Server stopped");
return true;
} catch (RemoteException | NotBoundException e) {
e.printStackTrace();
@@ -303,17 +333,13 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
/**
- * Creates and starts a {@link RMIHeartbeat} for {@code username}.
- * Also seeds the watchdog with the current model if one already exists
- * (reconnection case).
+ * Creates and starts an {@link RMIHeartbeat} for the specified player.
+ *
+ * @param username the username of the player monitored by the heartbeat watchdog.
*/
private void startWatchdog(String username) {
RMIHeartbeat wd = new RMIHeartbeat(
username, playerList, clients, actionQueue);
- synchronized (controller)
- {
- if (controller.getModel() != null) wd.setGame(controller.getModel());
- }
watchdogs.put(username, wd);
wd.start();
}
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 efd91db..28598d4 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
@@ -46,10 +46,12 @@ public class TCPClient implements IClient {
/**
- * 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
+ * Constructs a TCP client and initializes its connection parameters.
+ *
+ * @param controller the client controller.
+ * @param hostname the IP address or hostname of the server.
+ * @param mainPort the main TCP port of the server.
+ * @param heartbeatPort the TCP port used for heartbeat communication.
*/
public TCPClient(ClientController controller, String hostname, int mainPort,int heartbeatPort ) {
this.controller = controller;
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 15773f7..8e36e01 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
@@ -58,12 +58,13 @@ public class ClientHandler implements Runnable {
/**
* Class constructor that initializes the attributes.
*
- * @param username the username associated with the client.
- * @param clientSocket the socket representing the client's TCP connection.
- * @param out the output stream used to send data to the client.
- * @param in the input stream used to receive data from the client.
+ * @param username the username associated with the client.
+ * @param clientSocket the socket representing the client's TCP connection.
+ * @param out the output stream used to send data to the client.
+ * @param in the input stream used to receive data from the client.
* @param clientHandlers the shared list of all active client handlers.
- * @param actionQueue the queue containing incoming events.
+ * @param playersMap the shared map containing the connected players.
+ * @param actionQueue the queue containing incoming events.
*/
public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List The client handler is stopped and removed from the list of active handlers.
+ * The player is marked as disconnected, a {@link DisconnectedPlayer} event is
+ * added to the action queue, and the associated socket is closed.
+ */
public void disconnect() {
running = false;
clientHandlers.remove(this);
diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java
index a0dde68..fdf9f8e 100644
--- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java
+++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java
@@ -4,12 +4,26 @@ import java.io.*;
import java.net.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
+/**
+ * Handles the heartbeat communication associated with a TCP client.
+ *
+ * This component listens for heartbeat {@code PING} messages from the client,
+ * replies with {@code PONG} messages, and periodically checks whether the client
+ * has remained silent for too long. If a timeout or communication error occurs,
+ * both the heartbeat connection and the main client connection are disconnected.
+ */
public class HeartbeatHandler implements Runnable {
+ /**
+ * Maximum allowed time without receiving heartbeat messages before disconnecting the client.
+ */
private static final long SILENCE_THRESHOLD_MS = 5_000;
+
+ /**
+ * Expected interval between heartbeat messages.
+ */
private static final long KEEPALIVE_INTERVAL_MS = 3_000;
private final String username;
@@ -17,18 +31,46 @@ public class HeartbeatHandler implements Runnable {
private final InputStream in;
private final OutputStream out;
+ /**
+ * Byte value representing a heartbeat ping message.
+ */
private static final int PING = 1;
+
+ /**
+ * Byte value representing a heartbeat pong response.
+ */
private static final int PONG = 2;
- // riferimento al ClientHandler principale per disconnetterlo insieme
+ /**
+ * Main client handler associated with this heartbeat connection.
+ */
private final ClientHandler mainHandler;
+ /**
+ * Timestamp of the last received heartbeat message.
+ */
private volatile long lastReceivedTime = System.currentTimeMillis();
+
+ /**
+ * Indicates whether the heartbeat handler is still active.
+ */
private volatile boolean running = true;
+
+ /**
+ * Executor used to periodically check heartbeat timeouts.
+ */
private final ScheduledExecutorService watchdog =
Executors.newSingleThreadScheduledExecutor();
+ /**
+ * Constructs a heartbeat handler for the specified client.
+ *
+ * @param username the username of the connected client.
+ * @param socket the socket dedicated to heartbeat communication.
+ * @param mainHandler the main client handler associated with the same player.
+ * @throws IOException if the input or output stream cannot be obtained from the socket.
+ */
public HeartbeatHandler(String username, Socket socket, ClientHandler mainHandler)
throws IOException {
this.username = username;
@@ -38,6 +80,14 @@ public class HeartbeatHandler implements Runnable {
this.out = socket.getOutputStream();
}
+ /**
+ * Starts the heartbeat listening loop.
+ *
+ * The method activates the watchdog task, then waits for incoming heartbeat
+ * messages. When a {@code PING} is received, the last-received timestamp is
+ * updated and a {@code PONG} response is sent back to the client.
+ * If the stream is closed or an I/O error occurs, the client is disconnected.
+ */
@Override
public void run() {
startWatchdog();
@@ -56,6 +106,12 @@ public class HeartbeatHandler implements Runnable {
}
}
+ /**
+ * Starts the periodic watchdog task that detects heartbeat timeouts.
+ *
+ * If no heartbeat message is received within the configured silence threshold,
+ * the associated client is disconnected.
+ */
private void startWatchdog() {
watchdog.scheduleAtFixedRate(() -> {
if (System.currentTimeMillis() - lastReceivedTime > SILENCE_THRESHOLD_MS) {
@@ -65,7 +121,12 @@ public class HeartbeatHandler implements Runnable {
}, 1, 1, TimeUnit.SECONDS);
}
-
+ /**
+ * Disconnects the heartbeat channel and the associated main client connection.
+ *
+ * The handler is stopped, the watchdog task is terminated, the main
+ * {@link ClientHandler} is disconnected, and the heartbeat socket is closed.
+ */
private void disconnect() {
running = false;
watchdog.shutdownNow();
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 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));
}
diff --git a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java
index 64fd1e6..25b1f5e 100644
--- a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java
+++ b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java
@@ -192,15 +192,26 @@ public class ServerLauncher {
/**
- * 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.
+ * Entry point of the server application.
*
- * @throws InterruptedException if this exception is issued by run method
- * @throws RemoteException if this exception is issued by run method
+ * The method initializes the shared player list, the network event queue,
+ * the game controller, the RMI server, the TCP server, and the server launcher.
+ * It also selects the network interface to expose, configures the server crash
+ * recovery state, and defines the action to execute once the required number
+ * of players has joined the game.
+ *
+ * When the player list reaches its limit, the current game model is converted
+ * into a {@link MiniModel}, sent to all connected clients through both RMI and TCP,
+ * and rendered on the server-side TUI. Finally, the event-processing launcher
+ * and both network servers are started.
+ *
+ * The game model itself is initialized by the TCP or RMI server when the first
+ * player joins and selects the total number of players.
+ *
+ * @param args the command-line arguments passed to the server application.
+ * @throws RemoteException if the RMI server cannot be created.
*/
- public static void main(String[] args) throws InterruptedException, RemoteException {
+ public static void main(String[] args) throws RemoteException {
playerList = new LimitedMap The method repeatedly processes the first event in the action queue
+ * and, when a view is available, updates the rendered game state.
+ *
+ * If the thread is interrupted while waiting for an event,
+ * the interruption status is restored and the loop terminates.
*/
public void run() {
while (true) {
@@ -282,6 +296,18 @@ public class ServerLauncher {
}
}
+ /**
+ * Lets the user choose the network interface to be used by the server.
+ *
+ * The method scans all active, non-loopback, and non-virtual network
+ * interfaces, collecting their IPv4 addresses. If only one valid address is
+ * found, it is selected automatically. Otherwise, the available addresses are
+ * printed and the user is asked to choose one by index.
+ *
+ * @param scanner the scanner used to read the user's selection.
+ * @return the IPv4 address of the selected network interface.
+ * @throws Exception if no valid network interface is available.
+ */
public static String chooseNetworkInterface(Scanner scanner) throws Exception {
List The save file is stored in the {@code GameSaves/save.dat} path relative
+ * to the server executable location. If the directory does not exist, it is
+ * created before writing the serialized game model.
+ *
+ * @return {@code true} if the game is saved successfully,
+ * {@code false} otherwise.
+ */
private boolean gameSave(){
try{
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
@@ -340,6 +376,16 @@ public class ServerLauncher {
}
}
+ /**
+ * Loads a previously saved game model from the local save file.
+ *
+ * The method attempts to deserialize the game stored in
+ * {@code GameSaves/save.dat}. If no save file exists or an I/O error occurs,
+ * {@code null} is returned.
+ *
+ * @return the loaded {@link Game} instance, or {@code null} if no valid save
+ * can be loaded.
+ */
private Game loadSave(){
try {
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
@@ -363,6 +409,12 @@ public class ServerLauncher {
}
}
+ /**
+ * Deletes the current local game save file.
+ *
+ * @return {@code true} if the save file is deleted successfully,
+ * {@code false} otherwise.
+ */
private boolean deleteSave(){
try{
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
diff --git a/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java b/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java
index 1241146..c9bd7b9 100644
--- a/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java
+++ b/src/main/java/it/polimi/ingsw/gc14/View/GUI/TotemFXMLController.java
@@ -16,7 +16,6 @@ import javafx.stage.Screen;
import javafx.util.Duration;
import java.util.Locale;
-import java.util.Objects;
public class TotemFXMLController {
diff --git a/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java b/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java
index 6914e40..504ee6e 100644
--- a/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java
+++ b/src/main/java/it/polimi/ingsw/gc14/View/TUI/TUI.java
@@ -185,6 +185,13 @@ public class TUI implements IView {
System.out.println(AsciiTable.sideBySide(lines, lines2, 3));
}
+ /**
+ * Renders the available totems in the terminal interface.
+ *
+ * The method clears the terminal, builds a table containing the currently
+ * available totems together with their selection positions, and displays the
+ * instructions required to choose one.
+ */
public void renderTotems() {
clearTerminal();
var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size());
*
*/
public class RMIHeartbeat {
@@ -38,9 +36,6 @@ public class RMIHeartbeat {
private volatile long lastPingTime = System.currentTimeMillis();
private volatile boolean running = true;
- /** Reference to the current game model — needed to check whose turn it is. */
- private volatile Game game;
-
private final ScheduledExecutorService watchdog =
Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "rmi-watchdog-" + username);
@@ -48,6 +43,14 @@ public class RMIHeartbeat {
return t;
});
+ /**
+ * Constructs an RMI heartbeat handler for the specified client.
+ *
+ * @param username the username of the client monitored by the heartbeat.
+ * @param playerList the map storing the connection status of the players.
+ * @param clients the collection of currently registered RMI clients.
+ * @param actionQueue the queue containing network events to be processed.
+ */
public RMIHeartbeat(
String username,
LimitedMap