diff --git a/src/main/java/it/polimi/ingsw/gc14/ErrorType.java b/src/main/java/it/polimi/ingsw/gc14/ErrorType.java index f9fc7f1..ff597cc 100644 --- a/src/main/java/it/polimi/ingsw/gc14/ErrorType.java +++ b/src/main/java/it/polimi/ingsw/gc14/ErrorType.java @@ -10,7 +10,7 @@ public enum ErrorType { GENERIC_ERROR("Generic error"), GAME_ALREADY_STARTED("Game already started"), WRONG_ACTION("Wrong action"); - private String description; + private final String description; ErrorType(String description) { this.description = description; } diff --git a/src/main/java/it/polimi/ingsw/gc14/GameEventProcessor.java b/src/main/java/it/polimi/ingsw/gc14/GameEventProcessor.java index f6f6f9c..ea61e7a 100644 --- a/src/main/java/it/polimi/ingsw/gc14/GameEventProcessor.java +++ b/src/main/java/it/polimi/ingsw/gc14/GameEventProcessor.java @@ -194,7 +194,7 @@ public class GameEventProcessor { event.setIsError(!event.apply(gameController)); Game game = gameController.getModel(); - if (event.getIsError()) { + if (event.isError()) { broadcaster.notifyAll(event); return; } 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 dc1043f..12f4219 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Model/MiniModel.java +++ b/src/main/java/it/polimi/ingsw/gc14/Model/MiniModel.java @@ -191,10 +191,82 @@ public class MiniModel implements Serializable { * * @param standingPlayers the final ordered list of players. */ - public void setStandingPlayers(ArrayList standingPlayers) { + public void setStandingPlayers(List standingPlayers) { this.standingPlayers = standingPlayers; } + /** + * Replaces the upper row of tribe cards on the board. + * + * @param cards the new upper tribe card list. + */ + public void setUpperListTribeCards(ArrayList cards) { + this.upperListTribeCards = cards; + } + + /** + * Replaces the lower row of tribe cards on the board. + * + * @param cards the new lower tribe card list. + */ + public void setLowerListTribeCards(ArrayList cards) { + this.lowerListTribeCards = cards; + } + + /** + * Replaces the upper row of building cards on the board. + * + * @param cards the new upper building card list. + */ + public void setUpperListBuildingCards(ArrayList cards) { + this.upperListBuildingCards = cards; + } + + /** + * Replaces the lower row of building cards on the board. + * + * @param cards the new lower building card list. + */ + public void setLowerListBuildingCards(ArrayList cards) { + this.lowerListBuildingCards = cards; + } + + /** + * Removes the card at the given position from the upper tribe card list. + * + * @param pos the zero-based index of the card to remove. + */ + public void removeUpperTribeCard(int pos) { + if (upperListTribeCards != null) upperListTribeCards.remove(pos); + } + + /** + * Removes the card at the given position from the lower tribe card list. + * + * @param pos the zero-based index of the card to remove. + */ + public void removeLowerTribeCard(int pos) { + if (lowerListTribeCards != null) lowerListTribeCards.remove(pos); + } + + /** + * Removes the card at the given position from the upper building card list. + * + * @param pos the zero-based index of the card to remove. + */ + public void removeUpperBuildingCard(int pos) { + if (upperListBuildingCards != null) upperListBuildingCards.remove(pos); + } + + /** + * Removes the card at the given position from the lower building card list. + * + * @param pos the zero-based index of the card to remove. + */ + public void removeLowerBuildingCard(int pos) { + if (lowerListBuildingCards != null) lowerListBuildingCards.remove(pos); + } + /** * Returns the position of the player with the specified username * within the player collection. diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java b/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java index 91e1174..2d1d5ce 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java @@ -66,7 +66,7 @@ public enum EventType { */ ENDED_GAME("Ended Game"); - private String description; + private final String description; private EventType(String description) { this.description = description; } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/IClient.java b/src/main/java/it/polimi/ingsw/gc14/Network/IClient.java index cb0fbca..c712b19 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/IClient.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/IClient.java @@ -12,11 +12,11 @@ public interface IClient { * and preferred connection type. * * @param username the username chosen by the player. - * @param preferredInt the preferred connection type selected by the client. + * @param proposedNPlayers the desired number of players proposed by this client. * @return {@code true} if the connection is established successfully, * {@code false} otherwise. */ - ErrorType connect(String username, int preferredInt); + ErrorType connect(String username, int proposedNPlayers); /** * Requests to draw a tribe card from the upper tribe card list. diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkConfig.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkConfig.java index 42166dd..348f89e 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkConfig.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkConfig.java @@ -12,6 +12,10 @@ public final class NetworkConfig { public static final long SILENCE_THRESHOLD_MS = 5_000; /** Milliseconds between keep-alive pings sent by the client. */ public static final long KEEPALIVE_INTERVAL_MS = 3_000; + /** Minimum number of players required to start a game. */ + public static final int MIN_PLAYERS = 2; + /** Maximum number of players allowed in a game. */ + public static final int MAX_PLAYERS = 5; private NetworkConfig() {} } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java index b918666..4f1ee90 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvent.java @@ -80,15 +80,6 @@ public abstract class NetworkEvent implements Serializable { */ protected ArrayList disconnectedPlayers; - /** - * Sets the list of currently disconnected player usernames. - * - * @param players the list of disconnected player usernames to attach to this event. - */ - public void setDisconnected(ArrayList players) { - this.disconnectedPlayers = players; - } - /** * Populates this event with the current game state so clients can * update their local model after receiving it. @@ -140,7 +131,7 @@ public abstract class NetworkEvent implements Serializable { * @return {@code true} if the event could not be applied successfully, * {@code false} otherwise. */ - public boolean getIsError() { + public boolean isError() { return isError; } @@ -154,6 +145,15 @@ public abstract class NetworkEvent implements Serializable { this.isError = isError; } + /** + * Overrides the error type for this event. + * + * @param errorType the error type to set. + */ + public void setErrorType(ErrorType errorType) { + this.errorType = errorType; + } + /** * Constructs a network event. * @@ -162,7 +162,7 @@ public abstract class NetworkEvent implements Serializable { * @param isError {@code true} if the event represents an error, * {@code false} otherwise. */ - protected NetworkEvent(String username, EventType eventType, boolean isError,ErrorType errorType) { + protected NetworkEvent(String username, EventType eventType, boolean isError, ErrorType errorType) { this.username = username; this.eventType = eventType; this.isError = isError; diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java index 0dd4d0b..34f2bdd 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/AddPlayer.java @@ -7,20 +7,15 @@ import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.NetworkEvent; /** - * NetworkEvent to add a player. + * Network event used to register a new player in the lobby. + * + *

The first player to join also proposes the desired number of players + * for the match. Subsequent joiners ignore {@code proposedNPlayer} if the + * game has already been created. */ public class AddPlayer extends NetworkEvent { - /** Number of proposed players to add to the match */ + /** Number of players proposed by this player; used only when creating a new game. */ private int proposedNPlayer; - /** - * Overrides the default error type for this event. - * - * @param errorType the error type to set. - */ - public void setErrorType(ErrorType errorType) - { - this.errorType = errorType; - } /** * @return the number of proposed players to add to the match diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ApplyNextRound.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ApplyNextRound.java index 8ce76ad..dd736ed 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ApplyNextRound.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ApplyNextRound.java @@ -17,45 +17,60 @@ import java.util.List; import java.util.Map; /** - * NetworkEvent to draw a tribe card from the lower card list. + * Network event that transitions all clients to the next round. + * + *

Carries refreshed card lists for all four board rows in addition + * to the standard game state (slot map, turn order, current state, + * player list). Only applied client-side; {@link #apply(GameController)} + * always returns {@code false}. */ -public class ApplyNextRound extends NetworkEvent{ +public class ApplyNextRound extends NetworkEvent { - List players; - ArrayList upperListTribeCards; - ArrayList lowerListTribeCards; - ArrayList upperListBuildingCards; - ArrayList lowerListBuildingCards; + private ArrayList upperListTribeCards; + private ArrayList lowerListTribeCards; + private ArrayList upperListBuildingCards; + private ArrayList lowerListBuildingCards; /** * 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. + * @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. + * @param upperListTribeCards the upper row of tribe cards for the new round. + * @param lowerListTribeCards the lower row of tribe cards for the new round. + * @param upperListBuildingCards the upper row of building cards for the new round. + * @param lowerListBuildingCards the lower row of building cards for the new round. */ - public ApplyNextRound(Map slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState, List players, ArrayList upperListTribeCards, ArrayListlowerListTribeCards, ArrayList upperListBuildingCards, ArrayListlowerListBuildingCards) { - super("SERVER",EventType.NEXT_ROUND,false, ErrorType.WRONG_ACTION); + public ApplyNextRound(Map slotPlayerMap, + OrderLogicCard orderLogicCard, + CurrentState currentState, + List players, + ArrayList upperListTribeCards, + ArrayList lowerListTribeCards, + ArrayList upperListBuildingCards, + ArrayList lowerListBuildingCards) { + super("SERVER", EventType.NEXT_ROUND, false, ErrorType.WRONG_ACTION); + setData(slotPlayerMap, orderLogicCard, currentState, players); this.upperListBuildingCards = upperListBuildingCards; this.lowerListBuildingCards = lowerListBuildingCards; this.upperListTribeCards = upperListTribeCards; this.lowerListTribeCards = lowerListTribeCards; - this.slotPlayerMap = slotPlayerMap; - this.orderLogicCard = orderLogicCard; - this.currentState = currentState; - this.players = players; } /** - * @param gameController the Game Controller on which to apply the event - * @return true if the player could draw the card, false otherwise + * Not applicable server-side. + * + * @param gameController the game controller (unused). + * @return {@code false} always. */ @Override - public boolean apply(GameController gameController){ + public boolean apply(GameController gameController) { return false; } + /** * Applies this event to the client-side mini model, updating all board lists, * slot map, turn order, and game state for the new round. @@ -63,20 +78,19 @@ public class ApplyNextRound extends NetworkEvent{ * @param miniModel the client-side model to update. * @return {@code true} always (this event cannot produce an error). */ - public boolean apply(MiniModel miniModel) - { + @Override + public boolean apply(MiniModel miniModel) { synchronized (miniModel) { miniModel.setSlotPlayerMap(slotPlayerMap); miniModel.setOrderLogicCard(orderLogicCard); miniModel.setCurrentState(currentState); - miniModel.setPlayers(players); - miniModel.upperListTribeCards = upperListTribeCards; - miniModel.lowerListTribeCards = lowerListTribeCards; - miniModel.upperListBuildingCards = upperListBuildingCards; - miniModel.lowerListBuildingCards = lowerListBuildingCards; + miniModel.setPlayers(playerList); + miniModel.setUpperListTribeCards(upperListTribeCards); + miniModel.setLowerListTribeCards(lowerListTribeCards); + miniModel.setUpperListBuildingCards(upperListBuildingCards); + miniModel.setLowerListBuildingCards(lowerListBuildingCards); miniModel.setLastEvent(this); return true; } } - } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DisconnectedPlayer.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DisconnectedPlayer.java index 48fef06..adc81f1 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DisconnectedPlayer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DisconnectedPlayer.java @@ -9,14 +9,16 @@ import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Model.Totems; -import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** - * NetworkEvent to avoid drawing a card from the lower card list + * Network event used to notify all clients that a player has disconnected. + * + *

Also carries the updated list of available totems, since a disconnection + * during totem selection may free a totem for other players. */ -public class DisconnectedPlayer extends NetworkEvent implements Serializable{ +public class DisconnectedPlayer extends NetworkEvent { private List availableTotems; @@ -29,18 +31,19 @@ public class DisconnectedPlayer extends NetworkEvent implements Serializable{ } /** - * Class constructor. - * Initializes all the attributes. - * @param username the name of the player requesting the event + * Constructs a disconnection event for the specified player. + * + * @param username the username of the player who disconnected. */ - public DisconnectedPlayer(String username){ + public DisconnectedPlayer(String username) { super(username, EventType.DISCONNECTED_PLAYER, false, ErrorType.GENERIC_ERROR); } - /** - * @param gameController the Game Controller on which to apply the event - * @return true if the player could skipTheTurn, false otherwise + * Applies this disconnection event to the server-side game controller. + * + * @param gameController the game controller on which to apply the event. + * @return {@code true} if the disconnection is handled successfully. */ @Override public boolean apply(GameController gameController){ diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java index 938fa8a..e0f819c 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerBuildingCard.java @@ -16,7 +16,7 @@ public class DrawLowerBuildingCard extends NetworkEvent{ /** * Class constructor. - * Initialized all the attributes. + * Initializes all the attributes. * @param username the name of the player requesting the event * @param pos the index of the card to draw */ @@ -48,7 +48,7 @@ public class DrawLowerBuildingCard extends NetworkEvent{ if (isError) return false; - miniModel.lowerListBuildingCards.remove(pos); + miniModel.removeLowerBuildingCard(pos); miniModel.setPlayers(playerList); miniModel.setOrderLogicCard(orderLogicCard); miniModel.setCurrentState(currentState); diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java index c6f175c..d1d1813 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawLowerTribeCard.java @@ -17,7 +17,7 @@ public class DrawLowerTribeCard extends NetworkEvent{ /** * Class constructor. - * Initialized all the attributes. + * Initializes all the attributes. * @param username the name of the player requesting the event * @param pos the index of the card to draw */ @@ -47,7 +47,7 @@ public class DrawLowerTribeCard extends NetworkEvent{ if (isError) return false; - miniModel.lowerListTribeCards.remove(pos); + miniModel.removeLowerTribeCard(pos); miniModel.setPlayers(playerList); miniModel.setOrderLogicCard(orderLogicCard); miniModel.setCurrentState(currentState); diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java index 41de04f..e366280 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperBuildingCard.java @@ -17,7 +17,7 @@ public class DrawUpperBuildingCard extends NetworkEvent{ /** * Class constructor. - * Initialized all the attributes. + * Initializes all the attributes. * @param username the name of the player requesting the event * @param pos the index of the card to draw */ @@ -48,7 +48,7 @@ public class DrawUpperBuildingCard extends NetworkEvent{ if (isError) return false; - miniModel.upperListBuildingCards.remove(pos); + miniModel.removeUpperBuildingCard(pos); miniModel.setPlayers(playerList); miniModel.setOrderLogicCard(orderLogicCard); miniModel.setCurrentState(currentState); diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java index e16e3c5..6f4d3b1 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/DrawUpperTribeCard.java @@ -17,7 +17,7 @@ public class DrawUpperTribeCard extends NetworkEvent{ /** * Class constructor. - * Initialized all the attributes. + * Initializes all the attributes. * @param username the name of the player requesting the event * @param pos the index of the card to draw */ @@ -46,7 +46,7 @@ public class DrawUpperTribeCard extends NetworkEvent{ synchronized (miniModel) { if (isError) return false; - miniModel.upperListTribeCards.remove(pos); + miniModel.removeUpperTribeCard(pos); miniModel.setPlayers(playerList); miniModel.setOrderLogicCard(orderLogicCard); miniModel.setCurrentState(currentState); diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/EndedGame.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/EndedGame.java index 791ab8f..b5e9e2d 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/EndedGame.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/EndedGame.java @@ -11,58 +11,59 @@ import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.NetworkEvent; import java.util.ArrayList; -import java.util.List; import java.util.Map; /** - * NetworkEvent to draw a tribe card from the lower card list. + * Network event that signals the end of the game and delivers the final standings. + * + *

Only applied client-side; {@link #apply(GameController)} always returns {@code false}. */ -public class EndedGame extends NetworkEvent{ - - ArrayList players; +public class EndedGame extends NetworkEvent { /** * Constructs an event containing the final game state. * - * @param slotPlayerMap the map associating each slot with the player occupying it. + * @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. + * @param currentState the current state of the game. + * @param players the final list of players, used as the standings. */ - public EndedGame(Map slotPlayerMap, OrderLogicCard orderLogicCard, CurrentState currentState, ArrayList players){ - super("SERVER",EventType.ENDED_GAME,false, ErrorType.GENERIC_ERROR); - this.slotPlayerMap = slotPlayerMap; - this.orderLogicCard = orderLogicCard; - this.currentState = currentState; - this.players = players; + public EndedGame(Map slotPlayerMap, + OrderLogicCard orderLogicCard, + CurrentState currentState, + ArrayList players) { + super("SERVER", EventType.ENDED_GAME, false, ErrorType.GENERIC_ERROR); + setData(slotPlayerMap, orderLogicCard, currentState, players); } /** - * @param gameController the Game Controller on which to apply the event - * @return true if the player could draw the card, false otherwise + * Not applicable server-side. + * + * @param gameController the game controller (unused). + * @return {@code false} always. */ @Override - public boolean apply(GameController gameController){ + public boolean apply(GameController gameController) { return false; } + /** * Applies this event to the client-side mini model, updating the final - * game state and setting the standing players list for the leaderboard. + * game state and setting the standings list for the leaderboard. * * @param miniModel the client-side model to update. * @return {@code true} always (this event cannot produce an error). */ - public boolean apply(MiniModel miniModel) - { + @Override + public boolean apply(MiniModel miniModel) { synchronized (miniModel) { miniModel.setSlotPlayerMap(slotPlayerMap); miniModel.setOrderLogicCard(orderLogicCard); miniModel.setCurrentState(currentState); - miniModel.setPlayers(players); - miniModel.setStandingPlayers(players); + miniModel.setPlayers(playerList); + miniModel.setStandingPlayers(playerList); miniModel.setLastEvent(this); return true; } } - } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ReconnectPlayer.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ReconnectPlayer.java index 7518563..e2e9598 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ReconnectPlayer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/ReconnectPlayer.java @@ -7,13 +7,12 @@ import it.polimi.ingsw.gc14.Model.MiniModel; import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.NetworkEvent; -import java.io.Serializable; import java.util.ArrayList; /** * Network event used to notify that a player has reconnected to the game. */ -public class ReconnectPlayer extends NetworkEvent implements Serializable { +public class ReconnectPlayer extends NetworkEvent { @Override public void enrichWithGameState(Game game, ArrayList disconnectedUsernames) { diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipTurn.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipTurn.java index a36e818..907fbfa 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipTurn.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipTurn.java @@ -6,12 +6,10 @@ import it.polimi.ingsw.gc14.Model.MiniModel; import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.NetworkEvent; -import java.io.Serializable; - /** - * NetworkEvent to avoid drawing a card from the lower card list + * Network event used to skip the current player's optional card action. */ -public class SkipTurn extends NetworkEvent implements Serializable{ +public class SkipTurn extends NetworkEvent { /** * Class constructor. diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java index a1c5b51..61aa5d3 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SlotChoice.java @@ -12,14 +12,14 @@ import it.polimi.ingsw.gc14.Network.NetworkEvent; */ public class SlotChoice extends NetworkEvent { - /** Index of the card to draw */ + /** Index of the selected slot. */ private int pos; /** * Class constructor. * Initializes all the attributes. * @param username the name of the player requesting the event - * @param pos the index of the card to draw + * @param pos the index of the selected slot */ public SlotChoice(String username, int pos) { super(username, EventType.SLOT_CHOICE, false, ErrorType.WRONG_ACTION); @@ -27,8 +27,8 @@ public class SlotChoice extends NetworkEvent { } /** - * @param gameController the Game Controller on which to apply the event - * @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 slot choice is handled successfully, {@code false} otherwise. */ @Override public boolean apply(GameController gameController) { diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java index b0a06d5..e659f44 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Client/ClientCallbackImpl.java @@ -6,7 +6,6 @@ import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback; import javafx.application.Platform; -import java.io.Serializable; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; @@ -15,7 +14,7 @@ import java.rmi.server.UnicastRemoteObject; * RMI client callback implementation of {@link IClientCallback}. * Receives notifications from the server and updates the client game model. */ -public class ClientCallbackImpl extends UnicastRemoteObject implements IClientCallback, Serializable { +public class ClientCallbackImpl extends UnicastRemoteObject implements IClientCallback { /** The client controller used to apply events and update the model */ private final ClientController clientController; @@ -53,7 +52,7 @@ public class ClientCallbackImpl extends UnicastRemoteObject implements IClientCa */ @Override public void onAction(NetworkEvent event) throws RemoteException { - if(event.getIsError()) { + if(event.isError()) { clientController.getView().showError(event.getErrorType(),event.toString()); } else { event.apply(clientController.getMiniModel()); 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 b00ca28..3eb575b 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 @@ -20,7 +20,7 @@ public class RMIClient implements IClient { private final int port; private IGameServer stub; private ClientController controller; - private String myIP; + private String myIp; private String username; private volatile boolean running = false; @@ -35,13 +35,13 @@ public class RMIClient implements IClient { * @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. + * @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.host = host; this.port = port; - this.myIP = myIP; + this.myIp = myIp; } @@ -53,15 +53,15 @@ public class RMIClient implements IClient { * opening a second socket). */ @Override - public ErrorType connect(String username, int preferredInt) { + public ErrorType connect(String username, int proposedNPlayers) { try { - System.setProperty("java.rmi.server.hostname", this.myIP); + System.setProperty("java.rmi.server.hostname", this.myIp); Registry registry = LocateRegistry.getRegistry(host, port); this.stub = (IGameServer) registry.lookup("RMIGameServer"); this.username = username; ClientCallbackImpl callback = new ClientCallbackImpl(controller); - ErrorType status = stub.joinGame(username, preferredInt, callback); + ErrorType status = stub.joinGame(username, proposedNPlayers, callback); if(status==null) { running = true; @@ -250,12 +250,12 @@ public class RMIClient implements IClient { * the keep-alive scheduler. */ public void notifyDisconnection() { + running = false; if (pingSender != null) pingSender.shutdownNow(); if (executor != null) executor.shutdownNow(); - try{ + try { stub.disconnectPlayer(username); - } - catch (RemoteException e){ + } catch (RemoteException e) { System.out.println("Error during remote disconnection"); } } 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 b692ab9..3bb8450 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 @@ -14,10 +14,10 @@ public interface IGameServer extends Remote { * 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 preferredInt the desired number of players proposed by this client. * @param callback the remote callback associated with the client. - * @return {@code true} if the player joins the game successfully, - * {@code false} otherwise. + * @return {@code null} if the player joins successfully; + * an {@link it.polimi.ingsw.gc14.ErrorType} value describing the rejection otherwise. * @throws RemoteException if an RMI communication error occurs. */ ErrorType joinGame(String username, int preferredInt, IClientCallback callback) throws RemoteException; @@ -96,5 +96,11 @@ public interface IGameServer extends Remote { */ void ping(String username) throws RemoteException; + /** + * Notifies the server of a voluntary disconnection for the specified player. + * + * @param username the username of the player disconnecting. + * @throws RemoteException if an RMI communication error occurs. + */ void disconnectPlayer(String username) throws RemoteException; } \ No newline at end of file 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 f2f0f52..ec3125d 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 @@ -4,6 +4,7 @@ import it.polimi.ingsw.gc14.LimitedMap; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkConfig; import it.polimi.ingsw.gc14.Network.NetworkEvents.DisconnectedPlayer; +import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback; import java.util.Map; import java.util.concurrent.*; @@ -28,7 +29,7 @@ public class RMIHeartbeat { private String username = ""; private final LimitedMap playerList; - private final Map clients; // ConcurrentHashMap + private final Map clients; private final BlockingQueue actionQueue; /** Last time a ping was received from this client. */ @@ -53,7 +54,7 @@ public class RMIHeartbeat { public RMIHeartbeat( String username, LimitedMap playerList, - Map clients, + Map clients, BlockingQueue actionQueue) { this.username = 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 390528e..c566dd1 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 @@ -6,6 +6,7 @@ import it.polimi.ingsw.gc14.LimitedMap; import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Model.GamePackage.GameStages; import it.polimi.ingsw.gc14.Model.MiniModel; +import it.polimi.ingsw.gc14.Network.NetworkConfig; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvents.*; import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback; @@ -33,10 +34,10 @@ import java.util.stream.Collectors; * shared network event queue. */ public class RMIServer extends UnicastRemoteObject implements IGameServer { - private String host; + private final String host; private final GameController controller; private Registry registry; - private int nPort; + private final int nPort; /** username → callback */ private final Map clients = new ConcurrentHashMap<>(); @@ -48,8 +49,8 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { */ private final Map watchdogs = new ConcurrentHashMap<>(); - BlockingQueue actionQueue; - private LimitedMap playerList; + private final BlockingQueue actionQueue; + private final LimitedMap playerList; /** @@ -80,14 +81,17 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { /** * {@inheritDoc} * - *

After a successful join a {@link RMIHeartbeat} is created and + *

After a successful join an {@link RMIHeartbeat} watchdog is created and * started for the new player — mirrors creating a {@code HeartbeatHandler} in * {@code TCPServer.acceptHeartbeat()}. + * + * @return {@code null} on success; an {@link it.polimi.ingsw.gc14.ErrorType} value on rejection. */ @Override public ErrorType joinGame(String username, int preferredInt, IClientCallback callback) throws RemoteException { - if (preferredInt < 2 || preferredInt > 5) return ErrorType.WRONG_PLAYER_NUMBER; + if (preferredInt < NetworkConfig.MIN_PLAYERS || preferredInt > NetworkConfig.MAX_PLAYERS) + return ErrorType.WRONG_PLAYER_NUMBER; synchronized (controller) { @@ -97,7 +101,7 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { System.out.println("Game Created With :"+preferredInt+" Players"); } if(controller.getModel().getCurrentState().getGameStage()!= GameStages.WAITING ) { - if (!controller.getModel().getPlayers().stream().anyMatch(p -> p.getUserName().equals(username))) { + if (controller.getModel().getPlayers().stream().noneMatch(p -> p.getUserName().equals(username))) { return ErrorType.GAME_ALREADY_STARTED; } } @@ -129,7 +133,12 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { System.out.println("Reconnected player: " + username); startWatchdog(username); Game game = controller.getModel(); - callback.onGameInit(new MiniModel(game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(), game.getPlayers(), game.getAvailableTotems(), game.getUpperListTribeCards(), game.getLowerListTribeCards(), game.getUpperListBuilding(), game.getLowerListBuilding(), game.getDisconnectedPlayers().entrySet().stream().filter(Map.Entry::getValue).map(x -> x.getKey().getUserName()).collect(Collectors.toCollection(ArrayList::new)))); + callback.onGameInit(new MiniModel( + game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(), + game.getPlayers(), game.getAvailableTotems(), + game.getUpperListTribeCards(), game.getLowerListTribeCards(), + game.getUpperListBuilding(), game.getLowerListBuilding(), + disconnectedUsernames(game))); System.out.println("Model sent: " + username); actionQueue.add(new ReconnectPlayer(username)); return null; @@ -172,8 +181,7 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { */ public void notifyAll(NetworkEvent action){ for (Map.Entry entry : clients.entrySet()) { - if (!action.getIsError() || - (action.getIsError() && action.getUsername().equals(entry.getKey()))) { + if (!action.isError() || action.getUsername().equals(entry.getKey())) { try{ entry.getValue().onAction(action); } @@ -300,7 +308,7 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { registry.rebind("RMIGameServer", this); System.out.println("RMI Server started on port: " + nPort); return true; - } catch (Exception e) { + } catch (RemoteException e) { e.printStackTrace(); return false; } @@ -319,7 +327,8 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { try { registry.unbind("RMIGameServer"); UnicastRemoteObject.unexportObject(this, true); - watchdogs.values().forEach(wd -> { /* watchdogs shut themselves down */ }); + watchdogs.values().forEach(RMIHeartbeat::disconnect); + watchdogs.clear(); System.out.println("RMI Server stopped"); return true; } catch (RemoteException | NotBoundException e) { @@ -346,11 +355,20 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { * * @param username the username of the player to disconnect. */ + @Override public void disconnectPlayer(String username) { RMIHeartbeat wd = watchdogs.get(username); if (wd != null) wd.disconnect(); } + /** Returns usernames of players currently marked as disconnected in the given game. */ + private static ArrayList disconnectedUsernames(Game game) { + return game.getDisconnectedPlayers().entrySet().stream() + .filter(Map.Entry::getValue) + .map(e -> e.getKey().getUserName()) + .collect(Collectors.toCollection(ArrayList::new)); + } + } 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 9703033..e6e6732 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 @@ -22,25 +22,25 @@ public class TCPClient implements IClient { private static final int PONG = 2; /** Socket TCP */ - Socket communicationSocket; + private Socket communicationSocket; /** Input stream used receive objects from the server */ - ObjectInputStream socketReceive; + private ObjectInputStream socketReceive; /** Output stream used to send objects to the server */ - ObjectOutputStream socketSend; + private ObjectOutputStream socketSend; /** Client game's controller */ - ClientController controller; + private ClientController controller; /** IP address of the server to connect to */ - String hostname; + private String hostname; private boolean running; /** TCP port */ - int mainPort; + private int mainPort; - int heartbeatPort; + private int heartbeatPort; private Socket heartbeatSocket; private OutputStream heartbeatOut; private InputStream heartbeatIn; @@ -80,26 +80,27 @@ public class TCPClient implements IClient { try { if( socketReceive.readObject() instanceof AddPlayer x) { - if(x.getIsError()) + if(x.isError()) return x.getErrorType(); } } catch (ClassNotFoundException e) { return ErrorType.GENERIC_ERROR; } + running = true; + new Thread(this::receiveMessage, "tcp-reader").start(); - // Socket heartbeat + // heartbeat socket this.heartbeatSocket = new Socket(hostname, heartbeatPort); - this.heartbeatOut =heartbeatSocket.getOutputStream() ; + this.heartbeatOut = heartbeatSocket.getOutputStream(); this.heartbeatIn = heartbeatSocket.getInputStream(); - // manda subito username per associare i due socket lato server + // send username immediately so server can associate the two sockets new ObjectOutputStream(heartbeatSocket.getOutputStream()).writeObject(user); heartbeatOut.flush(); new Thread(this::heartbeatLoop, "heartbeat").start(); - running = true; return null; } catch (IOException e) { @@ -135,23 +136,20 @@ public class TCPClient implements IClient { int b = heartbeatIn.read(); if (b == -1 || b != PONG) { disconnect(); - controller.getView().showError(ErrorType.SERVER_CRASHED,ErrorType.SERVER_CRASHED.toString()); + controller.getView().showError(ErrorType.SERVER_CRASHED, ErrorType.SERVER_CRASHED.toString()); break; } - // pong ricevuto → server vivo + // pong received: server is alive } } catch (SocketTimeoutException e) { System.out.println("Server heartbeat timeout"); disconnect(); - controller.getView().showError(ErrorType.SERVER_CRASHED,ErrorType.SERVER_CRASHED.toString()); - + controller.getView().showError(ErrorType.SERVER_CRASHED, ErrorType.SERVER_CRASHED.toString()); } catch (IOException e) { - if(running) - { + if (running) { disconnect(); - controller.getView().showError(ErrorType.SERVER_CRASHED,ErrorType.SERVER_CRASHED.toString()); + controller.getView().showError(ErrorType.SERVER_CRASHED, ErrorType.SERVER_CRASHED.toString()); } - } finally { sender.shutdownNow(); } @@ -170,7 +168,7 @@ public class TCPClient implements IClient { * Listens continuously for incoming objects from the server. * - If the received object is a {@link NetworkEvent} flagged as an error, it is printed. * - If the received object is a valid {@link NetworkEvent}, it is applied to the game controller. - * - If the received object is a {@link Game} model, the controller's model is set. + * - If the received object is a {@link MiniModel}, the controller's model is set. */ private void receiveMessage() { while (true) { @@ -179,11 +177,12 @@ public class TCPClient implements IClient { try { read = socketReceive.readObject(); } catch (IOException e) { + if (running) disconnect(); break; } if (read instanceof NetworkEvent event) { - if (event.getIsError()) { + if (event.isError()) { if(event.getErrorType() == ErrorType.WRONG_ACTION) { controller.getView().showError(ErrorType.WRONG_ACTION,event.toString()); } 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 fb54e13..a10f832 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 @@ -23,7 +23,7 @@ public class ClientHandler implements Runnable { */ private final String username; - private boolean running ; + private volatile boolean running; /** * Returns the username associated with this client. @@ -37,22 +37,22 @@ public class ClientHandler implements Runnable { private Socket clientSocket; /** Input stream used to receive objects from the client */ - public ObjectInputStream in; + private ObjectInputStream in; /** Output stream used to send objects to the client */ - public ObjectOutputStream out; + private ObjectOutputStream out; /** * Shared list of all client handlers. * This handler removes itself from the list when disconnected. */ - List clientHandlers; + private List clientHandlers; /** Maps each connected player's username to their connection state (true = connected). */ - LimitedMap limitedMap; + private LimitedMap limitedMap; /** Queue containing the events to be applied to the game model */ - BlockingQueue actionQueue; + private BlockingQueue actionQueue; /** @@ -87,7 +87,7 @@ public class ClientHandler implements Runnable { try { while (running) { NetworkEvent event = (NetworkEvent) in.readObject(); - if (!actionQueue.add(event)) { + if (!actionQueue.offer(event)) { System.out.println("Error inserting action into queue"); } } 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 d9fb40a..16e6a58 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 @@ -84,14 +84,11 @@ public class HeartbeatHandler implements Runnable { startWatchdog(); try { while (running) { - int b = in.read(); // blocca finché non arriva un byte + int b = in.read(); // blocks until a byte arrives if (b == -1) { - synchronized (this) - { - out.write(-1); - } - disconnect(); break; - } // stream chiusa + disconnect(); + break; // stream closed + } if (b == PING) { lastReceivedTime = System.currentTimeMillis(); out.write(PONG); 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 04585c2..2e10325 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 @@ -7,6 +7,7 @@ import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Model.GamePackage.GameStages; import it.polimi.ingsw.gc14.Model.MiniModel; import it.polimi.ingsw.gc14.Network.EventType; +import it.polimi.ingsw.gc14.Network.NetworkConfig; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer; import it.polimi.ingsw.gc14.Network.NetworkEvents.ReconnectPlayer; @@ -34,47 +35,42 @@ public class TCPServer { /** * Main TCP port used for standard client-server communication. */ - int port; + private int port; /** * TCP port dedicated to heartbeat communication. */ - int heartbeatPort; - - /** - * Number of players that have successfully connected. - */ - int connectedPlayers; + private int heartbeatPort; /** * Main server socket used to accept client connections. */ - ServerSocket socketTCP; + private ServerSocket socketTCP; /** * Server socket used to accept heartbeat connections. */ - ServerSocket heartbeatSocketTCP; + private ServerSocket heartbeatSocketTCP; /** * Game controller used to manage the server-side game logic. */ - final GameController controller; + private final GameController controller; /** * Queue containing network events received from clients. */ - BlockingQueue actionQueue; + private BlockingQueue actionQueue; /** * Map storing the online/offline status of connected players. */ - LimitedMap playerList; + private LimitedMap playerList; /** * List of active TCP client handlers. */ - CopyOnWriteArrayList clientHandlers; + private CopyOnWriteArrayList clientHandlers; /** @@ -99,7 +95,6 @@ public class TCPServer { LimitedMap playerList) { this.port = port; this.heartbeatPort = heartbeatPort; - this.connectedPlayers = 0; this.controller = controller; this.actionQueue = actionQueue; this.playerList = playerList; @@ -152,8 +147,8 @@ public class TCPServer { AddPlayer eventAddPlayer = (AddPlayer) event; - if (eventAddPlayer.getProposedNPlayer() < 2 - || eventAddPlayer.getProposedNPlayer() > 5) { + if (eventAddPlayer.getProposedNPlayer() < NetworkConfig.MIN_PLAYERS + || eventAddPlayer.getProposedNPlayer() > NetworkConfig.MAX_PLAYERS) { eventAddPlayer.setErrorType(ErrorType.WRONG_PLAYER_NUMBER); eventAddPlayer.setIsError(true); clientSend.writeObject(eventAddPlayer); @@ -172,7 +167,7 @@ public class TCPServer { System.out.println("Game Created With :"+eventAddPlayer.getProposedNPlayer()+" Players"); } if(controller.getModel().getCurrentState().getGameStage()!= GameStages.WAITING ) { - if (!controller.getModel().getPlayers().stream().anyMatch(p -> p.getUserName().equals(username))) { + if (controller.getModel().getPlayers().stream().noneMatch(p -> p.getUserName().equals(username))) { eventAddPlayer.setErrorType(ErrorType.GAME_ALREADY_STARTED); eventAddPlayer.setIsError(true); clientSend.writeObject(eventAddPlayer); @@ -181,33 +176,16 @@ public class TCPServer { continue; } } - else{ + else { 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 - ); - clientSend.writeObject(eventAddPlayer); - pendingHeartbeat.put(username, handler); - - Thread thread = new Thread(handler); - thread.start(); - - clientHandlers.add(handler); - connectedPlayers++; + ClientHandler handler = createAndRegisterHandler( + username, clientSocket, clientSend, clientReceive); + new Thread(handler).start(); continue; - - } - else { - + } else { eventAddPlayer.setErrorType(ErrorType.USERNAME_ALREADY_USED); eventAddPlayer.setIsError(true); clientSend.writeObject(eventAddPlayer); @@ -217,73 +195,36 @@ public class TCPServer { } } - if (!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 - ); - clientSend.writeObject(eventAddPlayer); - pendingHeartbeat.put(username, handler); - - Thread thread = new Thread(handler); - thread.start(); - - clientHandlers.add(handler); - connectedPlayers++; - } - //reconnect a previously disconnected player - else{ - if (!playerList.get(username)) { - - playerList.put(username, true); - System.out.println("Reconnected player: " + username); - ClientHandler handler = new ClientHandler( - username, - clientSocket, - clientSend, - clientReceive, - clientHandlers, - playerList, - actionQueue - ); - - clientSend.writeObject(eventAddPlayer); - pendingHeartbeat.put(username, handler); - - Game game = controller.getModel(); - handler.notifyMiniModel(new MiniModel( - game.getSlotMap(), - game.getOrderLogicCard(), - game.getCurrentState(), - game.getPlayers(), - game.getAvailableTotems(), - game.getUpperListTribeCards(), game.getLowerListTribeCards(), game.getUpperListBuilding(), game.getLowerListBuilding(), game.getDisconnectedPlayers().entrySet().stream().filter(Map.Entry::getValue).map(x -> x.getKey().getUserName()).collect(Collectors.toCollection(ArrayList::new)) - )); - - Thread thread = new Thread(handler); - thread.start(); - - clientHandlers.add(handler); - connectedPlayers++; - actionQueue.add(new ReconnectPlayer(username)); - - } else { - - eventAddPlayer.setErrorType(ErrorType.USER_ALREADY_CONNECTED); - eventAddPlayer.setIsError(true); - clientSend.writeObject(eventAddPlayer); - clientSocket.close(); - System.out.println("Invalid parameters. Connection terminated."); - } + ClientHandler handler = createAndRegisterHandler( + username, clientSocket, clientSend, clientReceive); + new Thread(handler).start(); + } else if (!playerList.get(username)) { + // reconnect a previously disconnected player + playerList.put(username, true); + System.out.println("Reconnected player: " + username); + clientSend.writeObject(eventAddPlayer); + ClientHandler handler = createAndRegisterHandler( + username, clientSocket, clientSend, clientReceive); + Game game = controller.getModel(); + handler.notifyMiniModel(new MiniModel( + game.getSlotMap(), game.getOrderLogicCard(), + game.getCurrentState(), game.getPlayers(), + game.getAvailableTotems(), + game.getUpperListTribeCards(), game.getLowerListTribeCards(), + game.getUpperListBuilding(), game.getLowerListBuilding(), + disconnectedUsernames(game))); + new Thread(handler).start(); + actionQueue.add(new ReconnectPlayer(username)); + } else { + eventAddPlayer.setErrorType(ErrorType.USER_ALREADY_CONNECTED); + eventAddPlayer.setIsError(true); + clientSend.writeObject(eventAddPlayer); + clientSocket.close(); + System.out.println("Invalid parameters. Connection terminated."); } } } catch (IOException | ClassNotFoundException e) { @@ -340,7 +281,7 @@ public class TCPServer { */ public void notifyAll(NetworkEvent event) { clientHandlers.forEach(h -> { - if (!event.getIsError() || event.getUsername().equals(h.getUsername())) { + if (!event.isError() || event.getUsername().equals(h.getUsername())) { h.notifyEvent(event); } }); @@ -354,4 +295,28 @@ public class TCPServer { public void notifyAll(MiniModel model) { clientHandlers.forEach(h -> h.notifyMiniModel(model)); } + + /** + * Creates a {@link ClientHandler}, registers it in {@code pendingHeartbeat} + * and {@code clientHandlers}, but does NOT start its thread — callers start + * the thread after any extra setup (e.g. sending a model snapshot). + */ + private ClientHandler createAndRegisterHandler(String username, + Socket socket, + ObjectOutputStream out, + ObjectInputStream in) { + ClientHandler handler = new ClientHandler( + username, socket, out, in, clientHandlers, playerList, actionQueue); + pendingHeartbeat.put(username, handler); + clientHandlers.add(handler); + return handler; + } + + /** Returns usernames of players currently marked as disconnected in the given game. */ + private static ArrayList disconnectedUsernames(Game game) { + return game.getDisconnectedPlayers().entrySet().stream() + .filter(Map.Entry::getValue) + .map(e -> e.getKey().getUserName()) + .collect(Collectors.toCollection(ArrayList::new)); + } } \ No newline at end of file