Add: complete JavaDoc for networking, heartbeat, totem flow and game events

This commit is contained in:
MatteoPellegrino05
2026-05-18 18:52:29 +02:00
parent 07e7028ac9
commit e28e7bab6d
26 changed files with 890 additions and 199 deletions
@@ -26,6 +26,9 @@ public class ClientController {
/** Network client (either TCP or RMI) */ /** Network client (either TCP or RMI) */
private IClient client; private IClient client;
/**
* The username of the client associated with this event.
*/
public String myUsername; public String myUsername;
@@ -65,6 +68,11 @@ public class ClientController {
view.setModel(miniModel); view.setModel(miniModel);
} }
/**
* Sets the username of the client associated with this event.
*
* @param username the username to set.
*/
public void setMyUsername (String username) { public void setMyUsername (String username) {
this.myUsername=username; this.myUsername=username;
} }
@@ -170,7 +178,16 @@ public class ClientController {
} }
} }
//TODO /**
* Handles the selection of a totem by the specified player.
*
* <p>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) { public void totemChoice(String playerUsername,int pos) {
if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName())) if(!Objects.equals(playerUsername, miniModel.currentState.getCurrentPlayer().getUserName()))
view.showError("It's not your turn!"); view.showError("It's not your turn!");
@@ -30,7 +30,14 @@ public class GameController {
*/ */
public 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) public boolean DisconnectedPlayer(String username)
{ {
Player player= model.getPlayerByUsername(username); Player player= model.getPlayerByUsername(username);
@@ -38,7 +45,14 @@ public class GameController {
return false; return false;
return model.DisconnectedPlayer(player); 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) public boolean ReconnectPlayer(String username)
{ {
Player player= model.getPlayerByUsername(username); Player player= model.getPlayerByUsername(username);
@@ -46,6 +60,7 @@ public class GameController {
return false; return false;
return model.ReconnectPlayer(player); return model.ReconnectPlayer(player);
} }
/** /**
* Returns the game model managed by this controller. * Returns the game model managed by this controller.
* *
@@ -166,7 +181,14 @@ public class GameController {
return model.SlotChoiceByIndex(model.getPlayerByUsername(playerUsername),pos); 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) { public boolean TotemChoice(String playerUsername,String totem) {
Player player= model.getPlayerByUsername(playerUsername); Player player= model.getPlayerByUsername(playerUsername);
if(player==null) if(player==null)
@@ -4,5 +4,34 @@ package it.polimi.ingsw.gc14.Model.Cards.Building;
* Represents the possible effect types of building cards. * Represents the possible effect types of building cards.
*/ */
public enum EffectType { 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
}
@@ -4,5 +4,34 @@ package it.polimi.ingsw.gc14.Model.Cards.TribeCards;
* Represents the different types of character cards available in the game. * Represents the different types of character cards available in the game.
*/ */
public enum CharacterType { 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
} }
@@ -4,5 +4,24 @@ package it.polimi.ingsw.gc14.Model.Cards.TribeCards;
* Represents the different types of event cards available in the game. * Represents the different types of event cards available in the game.
*/ */
public enum EventType { 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
}
@@ -29,11 +29,20 @@ import java.util.stream.Stream;
*/ */
public class Game implements Serializable { public class Game implements Serializable {
/**
* The final ranking of players at the end of the game.
*/
private ArrayList<Player> playerStanding; private ArrayList<Player> playerStanding;
/**
* Returns the final ranking of players.
*
* @return the list of players ordered according to their final standing.
*/
public ArrayList<Player> getPlayerStanding() { public ArrayList<Player> getPlayerStanding() {
return playerStanding; return playerStanding;
} }
/** /**
* Returns the list of players participating in the game. * Returns the list of players participating in the game.
* *
@@ -42,16 +51,38 @@ public class Game implements Serializable {
public List<Player> getPlayers() { public List<Player> getPlayers() {
return playersList; return playersList;
} }
//TODO
/**
* Returns the list of totems that have not yet been assigned to any player.
*
* @return the list of currently available totems.
*/
public List<Totems>getAvailableTotems() { public List<Totems>getAvailableTotems() {
List<Totems> totems=new ArrayList<>(List.of(Totems.values())); List<Totems> totems=new ArrayList<>(List.of(Totems.values()));
playersList.forEach(player -> {if(player.totem!=null)totems.remove(player.totem);}); playersList.forEach(player -> {if(player.totem!=null)totems.remove(player.totem);});
return totems; return totems;
} }
//TODO
/**
* Queue containing the players who still have to choose their totem.
*/
private Queue<Player> totemChoiceQueue = new LinkedList<>(); private Queue<Player> totemChoiceQueue = new LinkedList<>();
//TODO /**
* Assigns the selected totem to the specified player during the totem choice phase.
*
* <p>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.
*
* <p>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) { public boolean TotemChoice(Player player,Totems totem) {
if(!currentState.getGameStage().equals(GameStages.TOTEM_CHOICE)) if(!currentState.getGameStage().equals(GameStages.TOTEM_CHOICE))
return false; return false;
@@ -87,9 +118,23 @@ public class Game implements Serializable {
return true; return true;
} }
//TODO /**
* Map tracking the players who are currently disconnected.
*/
public Map<Player,Boolean> disconnetedPlayers = new HashMap<>(); public Map<Player,Boolean> disconnetedPlayers = new HashMap<>();
//TODO
/**
* Marks the specified player as disconnected and updates the game flow accordingly.
*
* <p>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) public boolean DisconnectedPlayer(Player player)
{ {
if(disconnetedPlayers.containsKey(player) && disconnetedPlayers.get(player)) if(disconnetedPlayers.containsKey(player) && disconnetedPlayers.get(player))
@@ -121,7 +166,17 @@ public class Game implements Serializable {
return true; return true;
} }
//TODO /**
* Marks the specified player as reconnected.
*
* <p>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) public boolean ReconnectPlayer(Player player)
{ {
if(!disconnetedPlayers.containsKey(player)) if(!disconnetedPlayers.containsKey(player))
@@ -140,12 +195,14 @@ public class Game implements Serializable {
return true; return true;
} }
//Totem /**
//TODO * Clears the collection of disconnected players.
*/
public void ClearDisconnected() public void ClearDisconnected()
{ {
disconnetedPlayers.clear(); disconnetedPlayers.clear();
} }
/** /**
* Returns the current number of players participating in the game. * Returns the current number of players participating in the game.
* @return the current number of players. * @return the current number of players.
@@ -195,7 +252,12 @@ public class Game implements Serializable {
* The board associated with this game. * The board associated with this game.
*/ */
private Board board; private Board board;
//TODO
/**
* Returns the game board.
*
* @return the board associated with the game.
*/
public Board getBoard() {return board;} public Board getBoard() {return board;}
/** /**
@@ -925,6 +987,13 @@ public class Game implements Serializable {
return true; return true;
} }
/**
* Ends the game by forfeit and determines the final player standing.
*
* <p>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() { public void EndGameForFeit() {
Player winner=playersList.stream().filter(x->!disconnetedPlayers.containsValue(x)||!disconnetedPlayers.get(x)).toList().get(0); Player winner=playersList.stream().filter(x->!disconnetedPlayers.containsValue(x)||!disconnetedPlayers.get(x)).toList().get(0);
currentState.GameStageUpdate(GameStages.ENDED); currentState.GameStageUpdate(GameStages.ENDED);
@@ -4,5 +4,43 @@ package it.polimi.ingsw.gc14.Model.GamePackage;
* Represents the possible stages of a game. * Represents the possible stages of a game.
*/ */
public enum GameStages { 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
}
@@ -1,5 +1,32 @@
package it.polimi.ingsw.gc14.Model; package it.polimi.ingsw.gc14.Model;
/**
* Represents the different totem colors available in the game.
*/
public enum Totems { public enum Totems {
YELLOW, BLUE, ORANGE, WHITE, PURPLE
/**
* Yellow totem.
*/
YELLOW,
/**
* Blue totem.
*/
BLUE,
/**
* Orange totem.
*/
ORANGE,
/**
* White totem.
*/
WHITE,
/**
* Purple totem.
*/
PURPLE
} }
@@ -5,16 +5,64 @@ package it.polimi.ingsw.gc14.Network;
* client and server. * client and server.
*/ */
public enum EventType { public enum EventType {
/**
* Event used to add a player to the game.
*/
ADD_PLAYER, ADD_PLAYER,
/**
* Event used to submit a player's totem choice.
*/
TOTEM_CHOICE, TOTEM_CHOICE,
/**
* Event used to submit a player's slot choice.
*/
SLOT_CHOICE, SLOT_CHOICE,
/**
* Event used to draw a tribe card from the upper card list.
*/
DRAW_UPPER_TRIBE, DRAW_UPPER_TRIBE,
/**
* Event used to draw a tribe card from the lower card list.
*/
DRAW_LOWER_TRIBE, DRAW_LOWER_TRIBE,
/**
* Event used to draw a building card from the upper card list.
*/
DRAW_UPPER_BUILD, DRAW_UPPER_BUILD,
/**
* Event used to draw a building card from the lower card list.
*/
DRAW_LOWER_BUILD, DRAW_LOWER_BUILD,
/**
* Event used to skip the current turn or optional action.
*/
SKIP_TURN, SKIP_TURN,
/**
* Event used to notify that a player has disconnected.
*/
DISCONNECTED_PLAYER, DISCONNECTED_PLAYER,
/**
* Event used to notify that a player has reconnected.
*/
RECONNECT_PLAYER, RECONNECT_PLAYER,
/**
* Event used to apply the transition to the next round.
*/
NEXT_ROUND, NEXT_ROUND,
/**
* Event used to notify that the game has ended.
*/
ENDED_GAME ENDED_GAME
} }
@@ -1,25 +1,73 @@
package it.polimi.ingsw.gc14.Network; package it.polimi.ingsw.gc14.Network;
import it.polimi.ingsw.gc14.Network.NetworkEvents.*; /**
* Defines the operations that a client can request during the game.
import java.rmi.RemoteException; */
import java.util.Objects;
public interface IClient { public interface IClient {
public boolean connect(String username,int preferredInt);
public void drawUpperTribeCard(String playerUsername, int pos) ; /**
* Connects the client to the server using the specified username
* and preferred connection type.
*
* @param username the username chosen by the player.
* @param preferredInt the preferred connection type selected by the client.
* @return {@code true} if the connection is established successfully,
* {@code false} otherwise.
*/
boolean connect(String username, int preferredInt);
public void drawLowerTribeCard(String playerUsername,int pos) ; /**
* Requests to draw a tribe card from the upper tribe card list.
*
* @param playerUsername the username of the player performing the action.
* @param pos the position of the selected card in the upper tribe card list.
*/
void drawUpperTribeCard(String playerUsername, int pos);
public void drawUpperBuildingCard(String playerUsername,int pos) ; /**
* 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 in the lower tribe card list.
*/
void drawLowerTribeCard(String playerUsername, int pos);
/**
* 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 in the upper building card list.
*/
void drawUpperBuildingCard(String playerUsername, int pos);
public void drawLowerBuildingCard(String playerUsername,int pos) ; /**
* 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 in the lower building card list.
*/
void drawLowerBuildingCard(String playerUsername, int pos);
public void skipTurn(String playerUsername); /**
* Requests to skip the current player's optional action.
*
* @param playerUsername the username of the player skipping the action.
*/
void skipTurn(String playerUsername);
/**
* 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.
*/
void slotChoice(String playerUsername, int pos);
public void slotChoice(String playerUsername,int pos) ; /**
public void totemChoice(String playerUsername,String totem) ; * Requests to assign the selected totem to the specified player.
*
* @param playerUsername the username of the player making the totem choice.
* @param totem the name of the selected totem.
*/
void totemChoice(String playerUsername, String totem);
} }
@@ -11,59 +11,113 @@ import java.io.Serializable;
import java.util.Map; import java.util.Map;
/** /**
* Represents an event sent over the network * Represents an event sent over the network.
*
* <p>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 { public abstract class NetworkEvent implements Serializable {
/** Username of the player requesting the event */
/**
* Username of the player requesting the event.
*/
protected String username; 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() { public String getUsername() {
return username; return username;
} }
/** EventType of the event */ /**
* Type of the network event.
*/
protected EventType eventType; protected EventType eventType;
/**
* Map associating each occupied slot with the corresponding player.
*/
protected Map<Slot, Player> slotPlayerMap; protected Map<Slot, Player> slotPlayerMap;
/**
* Order logic card used to manage the player turn order.
*/
protected OrderLogicCard orderLogicCard; protected OrderLogicCard orderLogicCard;
/**
* Current state of the game associated with the event.
*/
protected CurrentState currentState; protected CurrentState currentState;
/**
* Player directly involved in the event, when required.
*/
protected Player player; protected Player player;
public void setData(Map<Slot, Player> slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState,Player player) { /**
* Sets the game data associated with this network event.
*
* @param slotPlayerMap the map associating each slot with the player occupying it.
* @param orderLogicCard the order logic card used to manage turn order.
* @param currentState the current state of the game.
* @param player the player directly associated with the event.
*/
public void setData(Map<Slot, Player> slotPlayerMap,
OrderLogicCard orderLogicCard,
CurrentState currentState,
Player player) {
this.slotPlayerMap = slotPlayerMap; this.slotPlayerMap = slotPlayerMap;
this.orderLogicCard = orderLogicCard; this.orderLogicCard = orderLogicCard;
this.currentState = currentState; this.currentState = currentState;
this.player = player; this.player = player;
} }
/**
* @return the type of the event
*/
public EventType getEventType() {return eventType;}
/** Flag signaling whether the event could not be applied to the server model */ /**
* Returns the type of this event.
*
* @return the event type.
*/
public EventType getEventType() {
return eventType;
}
/**
* Flag indicating whether the event could not be applied successfully.
*/
protected boolean isError; protected boolean isError;
/** /**
* @return the flag signaling whether the event could be applied to the server model * Returns whether the event represents an error.
*
* @return {@code true} if the event could not be applied successfully,
* {@code false} otherwise.
*/ */
public boolean getIsError() {return isError;} public boolean getIsError() {
return isError;
}
/** /**
* Set the isError flag. * Sets whether the event represents an error.
* @param isError the value to set *
* @param isError {@code true} if the event could not be applied successfully,
* {@code false} otherwise.
*/ */
public void setIsError(boolean isError) {this.isError = isError;} public void setIsError(boolean isError) {
this.isError = isError;
}
/** /**
* Class constructor. * Constructs a network event.
* Initializes all attributes. *
* @param username the username of the player requesting the event * @param username the username of the player requesting the event.
* @param eventType the type of the event * @param eventType the type of the event.
* @param isError the flag signaling if the event could be applied to the server model * @param isError {@code true} if the event represents an error,
* {@code false} otherwise.
*/ */
protected NetworkEvent(String username, EventType eventType, boolean isError) { protected NetworkEvent(String username, EventType eventType, boolean isError) {
this.username = username; this.username = username;
@@ -71,26 +125,35 @@ public abstract class NetworkEvent implements Serializable {
this.isError = isError; this.isError = isError;
} }
/** /**
* @return a string describing the name of the event and whether it is an error or not * Returns a textual description of the event and its outcome.
*
* @return a string describing the event type and whether it represents an error.
*/ */
@Override @Override
public String toString() { public String toString() {
if(isError) { if (isError) {
return ("ERROR: action " + eventType.toString()); return "ERROR: action " + eventType;
} else { } else {
return ("ACTION: action " + eventType.toString()); return "ACTION: action " + eventType;
} }
} }
/** /**
* The method to apply the current event to the specified Game Controller. * Applies this event to the specified server-side game controller.
* @param gameController the Game Controller on which to apply the event *
* @return true if the event could be applied; false otherwise * @param gameController the game controller on which the event must be applied.
* @return {@code true} if the event is applied successfully,
* {@code false} otherwise.
*/ */
public abstract boolean apply(GameController gameController); public abstract boolean apply(GameController gameController);
//TODO /**
* Applies this event to the specified client-side mini model.
*
* @param model the mini model on which the event must be applied.
* @return {@code true} if the event is applied successfully,
* {@code false} otherwise.
*/
public abstract boolean apply(MiniModel model); public abstract boolean apply(MiniModel model);
} }
@@ -20,7 +20,15 @@ import java.util.Map;
public class ApplyNextRound extends NetworkEvent implements Serializable{ public class ApplyNextRound extends NetworkEvent implements Serializable{
List<Player> players; List<Player> players;
//TODO
/**
* Constructs an event that updates the game state for the next round.
*
* @param slotPlayerMap the map associating each slot with the player occupying it.
* @param orderLogicCard the order logic card that determines the player order.
* @param currentState the current state of the game.
* @param players the list of players in the game.
*/
public ApplyNextRound(Map<Slot, Player> slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState, List<Player> players){ public ApplyNextRound(Map<Slot, Player> slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState, List<Player> players){
super("SERVER",EventType.NEXT_ROUND,false); super("SERVER",EventType.NEXT_ROUND,false);
this.slotPlayerMap = slotPlayerMap; this.slotPlayerMap = slotPlayerMap;
@@ -21,7 +21,15 @@ import java.util.Map;
public class EndedGame extends NetworkEvent implements Serializable{ public class EndedGame extends NetworkEvent implements Serializable{
ArrayList<Player> players; ArrayList<Player> players;
//TODO
/**
* Constructs an event containing the final game state.
*
* @param slotPlayerMap the map associating each slot with the player occupying it.
* @param orderLogicCard the order logic card that determines the player order.
* @param currentState the current state of the game.
* @param players the list of players at the end of the game.
*/
public EndedGame(Map<Slot, Player> slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState, ArrayList<Player> players){ public EndedGame(Map<Slot, Player> slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState, ArrayList<Player> players){
super("SERVER",EventType.ENDED_GAME,false); super("SERVER",EventType.ENDED_GAME,false);
this.slotPlayerMap = slotPlayerMap; this.slotPlayerMap = slotPlayerMap;
@@ -7,39 +7,54 @@ import it.polimi.ingsw.gc14.Network.NetworkEvent;
import java.io.Serializable; import java.io.Serializable;
//TODO /**
public class ReconnectPlayer extends NetworkEvent implements Serializable{ * Network event used to notify that a player has reconnected to the game.
*/
public class ReconnectPlayer extends NetworkEvent implements Serializable {
/** /**
* Class constructor. * Constructs a reconnection event for the specified player.
* Initializes all the attributes. *
* @param username the name of the player requesting the event * @param username the username of the player who reconnected.
*/ */
public ReconnectPlayer(String username){ public ReconnectPlayer(String username) {
super(username, EventType.RECONNECT_PLAYER, false); super(username, EventType.RECONNECT_PLAYER, false);
} }
/** /**
* @param gameController the Game Controller on which to apply the event * Applies the reconnection event to the server-side game controller.
* @return true if the player could skipTheTurn, false otherwise *
* @param gameController the game controller on which to apply the event.
* @return {@code true} if the player reconnection is handled successfully,
* {@code false} otherwise.
*/ */
@Override @Override
public boolean apply(GameController gameController){ public boolean apply(GameController gameController) {
return gameController.ReconnectPlayer(username); return gameController.ReconnectPlayer(username);
} }
//TODO /**
* Applies the reconnection update to the client-side mini model.
*
* <p>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 @Override
public boolean apply(MiniModel miniModel){ public boolean apply(MiniModel miniModel) {
if(isError) if (isError)
return false; return false;
if(miniModel!=null) {
if (miniModel != null) {
miniModel.setPlayer(player); miniModel.setPlayer(player);
miniModel.setOrderLogicCard(orderLogicCard); miniModel.setOrderLogicCard(orderLogicCard);
miniModel.setCurrentState(currentState); miniModel.setCurrentState(currentState);
miniModel.setSlotPlayerMap(slotPlayerMap); miniModel.setSlotPlayerMap(slotPlayerMap);
} }
return true; return true;
} }
} }
@@ -5,49 +5,78 @@ import it.polimi.ingsw.gc14.Model.MiniModel;
import it.polimi.ingsw.gc14.Model.Totems; import it.polimi.ingsw.gc14.Model.Totems;
import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.EventType;
import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.View.IView;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; 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 String totem;
private List<Totems>availableTotems;
/**
* List of totems still available after the choice has been processed.
*/
private List<Totems> availableTotems;
/**
* Sets the list of currently available totems.
*
* @param availableTotems the totems still available for selection.
*/
public void setAvailableTotems(List<Totems> availableTotems) { public void setAvailableTotems(List<Totems> availableTotems) {
this.availableTotems = availableTotems; this.availableTotems = availableTotems;
} }
//TODO
/**
* Constructs a totem choice event for the specified player.
*
* @param username the username of the player making the choice.
* @param totem the name of the selected totem.
*/
public TotemChoice(String username, String totem) { public TotemChoice(String username, String totem) {
super(username, EventType.TOTEM_CHOICE, false); super(username, EventType.TOTEM_CHOICE, false);
this.totem = totem; this.totem = totem;
} }
/** /**
* @param gameController the Game Controller on which to apply the event * Applies the totem choice event to the server-side game controller.
* @return true if the player could draw the card, false otherwise *
* @param gameController the game controller on which to apply the event.
* @return {@code true} if the totem choice is handled successfully,
* {@code false} otherwise.
*/ */
@Override @Override
public boolean apply(GameController gameController) { public boolean apply(GameController gameController) {
return gameController.TotemChoice(username, totem); return gameController.TotemChoice(username, totem);
} }
//TODO /**
* Applies the totem choice update to the client-side mini model.
*
* <p>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 @Override
public boolean apply(MiniModel miniModel){ public boolean apply(MiniModel miniModel) {
if(isError) if (isError)
return false; return false;
miniModel.setPlayer(player); miniModel.setPlayer(player);
miniModel.setOrderLogicCard(orderLogicCard); miniModel.setOrderLogicCard(orderLogicCard);
miniModel.setCurrentState(currentState); miniModel.setCurrentState(currentState);
miniModel.setSlotPlayerMap(slotPlayerMap); miniModel.setSlotPlayerMap(slotPlayerMap);
miniModel.setAvailableTotems(availableTotems); miniModel.setAvailableTotems(availableTotems);
return true; return true;
} }
} }
@@ -1,18 +1,13 @@
package it.polimi.ingsw.gc14.Network.RMI.Client; package it.polimi.ingsw.gc14.Network.RMI.Client;
import java.net.InetAddress;
import java.rmi.RemoteException; import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry; import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry; import java.rmi.registry.Registry;
import java.util.Objects;
import java.util.concurrent.*; import java.util.concurrent.*;
import it.polimi.ingsw.gc14.Controller.ClientController; import it.polimi.ingsw.gc14.Controller.ClientController;
import it.polimi.ingsw.gc14.Network.IClient; 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.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.Common.IGameServer;
import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
/** /**
* Client RMI. Uses the methods exposed by the server RMI. * 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. */ /** Scheduler that fires ping() every PING_INTERVAL_S seconds. */
private ScheduledExecutorService pingSender; 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) { public RMIClient(ClientController controller, String host, int port, String myIP) {
this.controller = controller; this.controller = controller;
this.host = host; this.host = host;
@@ -21,7 +21,7 @@ public interface IClientCallback extends Remote, Serializable {
/** /**
* Notifies the client that the game model has been initialized or updated. * 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. * @throws RemoteException if an RMI communication error occurs.
*/ */
void onGameInit(MiniModel miniModel) throws RemoteException; void onGameInit(MiniModel miniModel) throws RemoteException;
@@ -1,7 +1,5 @@
package it.polimi.ingsw.gc14.Network.RMI.Common; package it.polimi.ingsw.gc14.Network.RMI.Common;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import java.rmi.*; import java.rmi.*;
/** /**
@@ -9,22 +7,89 @@ import java.rmi.*;
*/ */
public interface IGameServer extends Remote { 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 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. * Requests to draw a tribe card from the upper tribe card list.
* Mirrors the PING/PONG mechanism used in the TCP heartbeat channel.
* *
* @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.
*
* <p>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. * @throws RemoteException if an RMI communication error occurs.
*/ */
void ping(String username) throws RemoteException; void ping(String username) throws RemoteException;
@@ -1,7 +1,6 @@
package it.polimi.ingsw.gc14.Network.RMI.Server; package it.polimi.ingsw.gc14.Network.RMI.Server;
import it.polimi.ingsw.gc14.LimitedMap; 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.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer; 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()} * 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. * being called by {@link RMIServer#ping(String)} every time the client sends a ping.
* *
* <p>If no ping is received within {@value SILENCE_THRESHOLD_MS} ms the player is * <p>If no ping is received within {@value SILENCE_THRESHOLD_MS} ms, the player is
* considered disconnected and {@link #disconnect()} is invoked, which: * considered disconnected and {@link #disconnect()} is invoked, which:
* <ul> * <ul>
* <li>stops the watchdog;</li> * <li>stops the watchdog;</li>
* <li>marks the player as offline in {@code playerList};</li> * <li>marks the player as offline in {@code playerList};</li>
* <li>removes the callback from {@code clients};</li> * <li>removes the callback from {@code clients};</li>
* <li>optionally pushes a {@link DisconnectedPlayer} event if it was that * <li>adds a {@link DisconnectedPlayer} event to the action queue.</li>
* player's turn.</li>
* </ul> * </ul>
*/ */
public class RMIHeartbeat { public class RMIHeartbeat {
@@ -38,9 +36,6 @@ public class RMIHeartbeat {
private volatile long lastPingTime = System.currentTimeMillis(); private volatile long lastPingTime = System.currentTimeMillis();
private volatile boolean running = true; 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 = private final ScheduledExecutorService watchdog =
Executors.newSingleThreadScheduledExecutor(r -> { Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "rmi-watchdog-" + username); Thread t = new Thread(r, "rmi-watchdog-" + username);
@@ -48,6 +43,14 @@ public class RMIHeartbeat {
return t; 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( public RMIHeartbeat(
String username, String username,
LimitedMap<String, Boolean> playerList, LimitedMap<String, Boolean> playerList,
@@ -80,15 +83,12 @@ public class RMIHeartbeat {
} }
/** /**
* Allows the server to keep the watchdog up-to-date with the current game * Disconnects the monitored RMI client.
* model (needed to check whose turn it is on disconnect). *
* <p>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() { private void disconnect() {
if (!running) return; if (!running) return;
running = false; running = false;
@@ -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.
*
* <p>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 { public class RMIServer extends UnicastRemoteObject implements IGameServer {
private String host; private String host;
@@ -43,10 +48,26 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
private LimitedMap<String, Boolean> playerList; private LimitedMap<String, Boolean> playerList;
private boolean serverCrashed; private boolean serverCrashed;
/**
* Sets whether the server is recovering from a previous crash.
*
* @param serverCrashed {@code true} if the server is in crash-recovery mode,
*/
public void setServerCrashed(boolean serverCrashed) { public void setServerCrashed(boolean serverCrashed) {
this.serverCrashed = serverCrashed; this.serverCrashed = serverCrashed;
} }
/**
* Constructs an RMI server with the required game and network components.
*
* @param controller the game controller used to manage the game logic.
* @param nPort the port used by the RMI registry.
* @param actionQueue the queue containing incoming network events.
* @param playerList the map storing the connection status of the players.
* @param host the hostname or IP address exposed by the RMI server.
* @throws RemoteException if the remote object cannot be exported.
*/
public RMIServer(GameController controller, int nPort, public RMIServer(GameController controller, int nPort,
BlockingQueue<NetworkEvent> actionQueue, BlockingQueue<NetworkEvent> actionQueue,
LimitedMap<String, Boolean> playerList, LimitedMap<String, Boolean> playerList,
@@ -148,9 +169,13 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Notifies all clients of a new event. * Notifies connected RMI clients of a new network event.
* Also updates every watchdog with the latest model so disconnect logic *
* knows whose turn it is. * <p>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){ public void notifyAll(NetworkEvent action){
for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) { for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) {
@@ -168,15 +193,15 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
} }
/** /**
* Notifies all clients of a new game model and keeps watchdogs up-to-date. * Notifies all connected RMI clients of a new game model.
* Mirrors {@code TCPServer.notifyAll(Game)} + the {@code ClientHandler.notifyModel} *
* call that stores the model for disconnect-turn checking. * <p>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) { 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()) { for (IClientCallback cb : clients.values()) {
try{ try{
cb.onGameInit(model); 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. * Requests to draw a tribe card from the upper list.
* Creates a NetworkEvent and sends it through the network client. * 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. * Starts the RMI server and binds it to the configured registry.
* @return true if the server starts successfully, false otherwise *
* <p>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) { public boolean start(boolean serverCrashed) {
this.serverCrashed = 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.
*
* <p>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() { public boolean stop() {
try { try {
registry.unbind("RMIGameServer"); registry.unbind("RMIGameServer");
UnicastRemoteObject.unexportObject(this, true); UnicastRemoteObject.unexportObject(this, true);
watchdogs.values().forEach(wd -> { /* watchdogs shut themselves down */ }); watchdogs.values().forEach(wd -> { /* watchdogs shut themselves down */ });
System.out.println("RMI Server fermato"); System.out.println("RMI Server stopped");
return true; return true;
} catch (RemoteException | NotBoundException e) { } catch (RemoteException | NotBoundException e) {
e.printStackTrace(); e.printStackTrace();
@@ -303,17 +333,13 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
/** /**
* Creates and starts a {@link RMIHeartbeat} for {@code username}. * Creates and starts an {@link RMIHeartbeat} for the specified player.
* Also seeds the watchdog with the current model if one already exists *
* (reconnection case). * @param username the username of the player monitored by the heartbeat watchdog.
*/ */
private void startWatchdog(String username) { private void startWatchdog(String username) {
RMIHeartbeat wd = new RMIHeartbeat( RMIHeartbeat wd = new RMIHeartbeat(
username, playerList, clients, actionQueue); username, playerList, clients, actionQueue);
synchronized (controller)
{
if (controller.getModel() != null) wd.setGame(controller.getModel());
}
watchdogs.put(username, wd); watchdogs.put(username, wd);
wd.start(); wd.start();
} }
@@ -46,10 +46,12 @@ public class TCPClient implements IClient {
/** /**
* Class constructor that initializes the attributes. * Constructs a TCP client and initializes its connection parameters.
* @param controller The game controller *
* @param hostname The IP address of the server * @param controller the client controller.
* @param port The TCP port of the server * @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 ) { public TCPClient(ClientController controller, String hostname, int mainPort,int heartbeatPort ) {
this.controller = controller; this.controller = controller;
@@ -63,6 +63,7 @@ public class ClientHandler implements Runnable {
* @param out the output stream used to send data to the client. * @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 in the input stream used to receive data from the client.
* @param clientHandlers the shared list of all active client handlers. * @param clientHandlers the shared list of all active client handlers.
* @param playersMap the shared map containing the connected players.
* @param actionQueue the queue containing incoming events. * @param actionQueue the queue containing incoming events.
*/ */
public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List<ClientHandler> clientHandlers, LimitedMap<String,Boolean> playersMap, BlockingQueue<NetworkEvent> actionQueue) { public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List<ClientHandler> clientHandlers, LimitedMap<String,Boolean> playersMap, BlockingQueue<NetworkEvent> actionQueue) {
@@ -130,6 +131,13 @@ public class ClientHandler implements Runnable {
} }
} }
/**
* Disconnects the client from the server.
*
* <p>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() { public void disconnect() {
running = false; running = false;
clientHandlers.remove(this); clientHandlers.remove(this);
@@ -4,12 +4,26 @@ import java.io.*;
import java.net.*; import java.net.*;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/**
* Handles the heartbeat communication associated with a TCP client.
*
* <p>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 { 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; private static final long SILENCE_THRESHOLD_MS = 5_000;
/**
* Expected interval between heartbeat messages.
*/
private static final long KEEPALIVE_INTERVAL_MS = 3_000; private static final long KEEPALIVE_INTERVAL_MS = 3_000;
private final String username; private final String username;
@@ -17,18 +31,46 @@ public class HeartbeatHandler implements Runnable {
private final InputStream in; private final InputStream in;
private final OutputStream out; private final OutputStream out;
/**
* Byte value representing a heartbeat ping message.
*/
private static final int PING = 1; private static final int PING = 1;
/**
* Byte value representing a heartbeat pong response.
*/
private static final int PONG = 2; 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; private final ClientHandler mainHandler;
/**
* Timestamp of the last received heartbeat message.
*/
private volatile long lastReceivedTime = System.currentTimeMillis(); private volatile long lastReceivedTime = System.currentTimeMillis();
/**
* Indicates whether the heartbeat handler is still active.
*/
private volatile boolean running = true; private volatile boolean running = true;
/**
* Executor used to periodically check heartbeat timeouts.
*/
private final ScheduledExecutorService watchdog = private final ScheduledExecutorService watchdog =
Executors.newSingleThreadScheduledExecutor(); 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) public HeartbeatHandler(String username, Socket socket, ClientHandler mainHandler)
throws IOException { throws IOException {
this.username = username; this.username = username;
@@ -38,6 +80,14 @@ public class HeartbeatHandler implements Runnable {
this.out = socket.getOutputStream(); this.out = socket.getOutputStream();
} }
/**
* Starts the heartbeat listening loop.
*
* <p>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 @Override
public void run() { public void run() {
startWatchdog(); startWatchdog();
@@ -56,6 +106,12 @@ public class HeartbeatHandler implements Runnable {
} }
} }
/**
* Starts the periodic watchdog task that detects heartbeat timeouts.
*
* <p>If no heartbeat message is received within the configured silence threshold,
* the associated client is disconnected.
*/
private void startWatchdog() { private void startWatchdog() {
watchdog.scheduleAtFixedRate(() -> { watchdog.scheduleAtFixedRate(() -> {
if (System.currentTimeMillis() - lastReceivedTime > SILENCE_THRESHOLD_MS) { if (System.currentTimeMillis() - lastReceivedTime > SILENCE_THRESHOLD_MS) {
@@ -65,7 +121,12 @@ public class HeartbeatHandler implements Runnable {
}, 1, 1, TimeUnit.SECONDS); }, 1, 1, TimeUnit.SECONDS);
} }
/**
* Disconnects the heartbeat channel and the associated main client connection.
*
* <p>The handler is stopped, the watchdog task is terminated, the main
* {@link ClientHandler} is disconnected, and the heartbeat socket is closed.
*/
private void disconnect() { private void disconnect() {
running = false; running = false;
watchdog.shutdownNow(); watchdog.shutdownNow();
@@ -192,15 +192,26 @@ public class ServerLauncher {
/** /**
* The first method executed when the server program is launched. * Entry point of the server application.
* 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 * <p>The method initializes the shared player list, the network event queue,
* @throws RemoteException if this exception is issued by run method * 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.
*
* <p>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.
*
* <p>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<String,Boolean>(5, ()->{}); playerList = new LimitedMap<String,Boolean>(5, ()->{});
BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>(); BlockingQueue<NetworkEvent> actionQueue = new LinkedBlockingQueue<>();
GameController gameController = new GameController(); GameController gameController = new GameController();
@@ -261,10 +272,13 @@ public class ServerLauncher {
/** /**
* Creates and executes the game. * Executes the main game loop.
* Game creation: TCP/RMI servers send the game model to all players. *
* Game execution: repeatedly calls doFirstEvent() to process the events in the actionQueue. * <p>The method repeatedly processes the first event in the action queue
* @throws InterruptedException if the TCP server thread is interrupted * and, when a view is available, updates the rendered game state.
*
* <p>If the thread is interrupted while waiting for an event,
* the interruption status is restored and the loop terminates.
*/ */
public void run() { public void run() {
while (true) { while (true) {
@@ -282,6 +296,18 @@ public class ServerLauncher {
} }
} }
/**
* Lets the user choose the network interface to be used by the server.
*
* <p>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 { public static String chooseNetworkInterface(Scanner scanner) throws Exception {
List<String> ips = new ArrayList<>(); List<String> ips = new ArrayList<>();
@@ -315,6 +341,16 @@ public class ServerLauncher {
return ips.get(choice); return ips.get(choice);
} }
/**
* Saves the current game model to a local file.
*
* <p>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(){ private boolean gameSave(){
try{ try{
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); 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.
*
* <p>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(){ private Game loadSave(){
try { try {
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); 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(){ private boolean deleteSave(){
try{ try{
Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); Path jarPath = Paths.get(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
@@ -16,7 +16,6 @@ import javafx.stage.Screen;
import javafx.util.Duration; import javafx.util.Duration;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
public class TotemFXMLController { public class TotemFXMLController {
@@ -185,6 +185,13 @@ public class TUI implements IView {
System.out.println(AsciiTable.sideBySide(lines, lines2, 3)); System.out.println(AsciiTable.sideBySide(lines, lines2, 3));
} }
/**
* Renders the available totems in the terminal interface.
*
* <p>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() { public void renderTotems() {
clearTerminal(); clearTerminal();
var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size()); var table = new AsciiTable(BorderStyle.ROUNDED, model.availableTotems.size());