package it.polimi.ingsw.gc14.Model; import it.polimi.ingsw.gc14.Model.Cards.Building.EffectType; import it.polimi.ingsw.gc14.Model.Cards.BuildingCard; import it.polimi.ingsw.gc14.Model.Cards.TribeCard; import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Character; import it.polimi.ingsw.gc14.Model.Cards.TribeCards.CharacterType; import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Characters.Builder; import it.polimi.ingsw.gc14.Model.Cards.TribeCards.Characters.Inventor; import it.polimi.ingsw.gc14.Model.Cards.TribeCards.EventCard; import it.polimi.ingsw.gc14.Model.Cards.TribeCards.EventType; import it.polimi.ingsw.gc14.Model.GamePackage.Board; import it.polimi.ingsw.gc14.Model.GamePackage.CurrentState; import it.polimi.ingsw.gc14.Model.Orders.Order2; import it.polimi.ingsw.gc14.Model.Orders.Order3; import it.polimi.ingsw.gc14.Model.Orders.Order4; import it.polimi.ingsw.gc14.Model.Orders.Order5; import it.polimi.ingsw.gc14.Model.GamePackage.GameStages; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Represents the main game model. * A Game object stores the players, the current state of the match, * the slot assignments, the board, and the logic required to manage the game flow. */ public class Game implements Serializable { /** * The final ranking of players at the end of the game. */ private ArrayList playerStanding; /** * Returns the final ranking of players. * * @return the list of players ordered according to their final standing. */ public ArrayList getPlayerStanding() { return playerStanding; } /** * Returns the list of players participating in the game. * * @return a copy of the list of players currently participating in the game. */ public List getPlayers() { return playersList; } /** * Returns the list of totems that have not yet been assigned to any player. * * @return the list of currently available totems. */ public ListgetAvailableTotems() { List totems=new ArrayList<>(List.of(Totems.values())); playersList.forEach(player -> {if(player.getTotem()!=null)totems.remove(player.getTotem());}); return totems; } /** * Queue containing the players who still have to choose their totem. */ private final Queue totemChoiceQueue = new LinkedList<>(); /** * Assigns the selected totem to the specified player during the totem choice phase. * *

The choice is accepted only if the game is currently in the * {@link GameStages#TOTEM_CHOICE} stage, the player is the current one, * and the selected totem is still available. * *

After a valid choice, the method advances to the next player in the queue. * If all players have completed the selection, the game moves to the slot choice phase. * Disconnected players are automatically assigned a random available totem. * * @param player the player making the totem choice. * @param totem the selected totem. * @return {@code true} if the choice is applied successfully, {@code false} otherwise. */ public synchronized boolean totemChoice(Player player,Totems totem) { if(currentState.getGameStage() != GameStages.TOTEM_CHOICE) return false; if(!getCurrentState().getCurrentPlayer().equals(player)) return false; if(!getAvailableTotems().contains(totem)) { return false; } player.setTotem(totem); Player nextPlayer= totemChoiceQueue.poll(); if(nextPlayer==null) { currentState.gameStageUpdate(GameStages.SLOT_CHOICE); currentState.playerUpdate(orderLogicCard.pull(),null); } else { while(disconnectedPlayers.containsKey(nextPlayer)&& disconnectedPlayers.get(nextPlayer)) { nextPlayer.setTotem(getAvailableTotems().get(new Random().nextInt(0,getAvailableTotems().size()))); nextPlayer=totemChoiceQueue.poll(); if(nextPlayer==null) { currentState.gameStageUpdate(GameStages.SLOT_CHOICE); nextPlayerSetup(); return true; } } currentState.playerUpdate(nextPlayer,null); } return true; } /** * Map tracking the players who are currently disconnected. * Key: player; value: {@code true} if currently disconnected, {@code false} if reconnected. */ private final Map disconnectedPlayers = new HashMap<>(); /** * Returns an unmodifiable view of the disconnected-players map. * * @return map from player to disconnection status. */ public Map getDisconnectedPlayers() { return Collections.unmodifiableMap(disconnectedPlayers); } /** * Marks the specified player as disconnected and updates the game flow accordingly. * *

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 synchronized boolean disconnectedPlayer(Player player) { if(disconnectedPlayers.containsKey(player) && disconnectedPlayers.get(player)) { return false; } disconnectedPlayers.put(player,true); if (currentState.getGameStage() == GameStages.WAITING) { playersList.remove(player); totemChoiceQueue.remove(player); return true; } if(currentState.getCurrentPlayer().equals(player)) { if (currentState.getGameStage() != GameStages.TOTEM_CHOICE) { nextPlayerSetup(); } else { totemChoiceQueue.removeIf(x->x.getUserName().equals(player.getUserName())); player.setTotem(getAvailableTotems().get(new Random().nextInt(getAvailableTotems().size()))); if (totemChoiceQueue.isEmpty()) { player.setTotem(getAvailableTotems().get(new Random().nextInt(getAvailableTotems().size()))); currentState.gameStageUpdate(GameStages.SLOT_CHOICE); nextPlayerSetup(); return true; } currentState.playerUpdate(totemChoiceQueue.poll(), null); } return true; } return true; } /** * Marks the specified player as reconnected. * *

If the player reconnects during the slot choice phase, they are removed * from the disconnected players map and reinserted into the order logic card * when necessary. * * @param player the player who reconnected. * @return {@code true} if the reconnection is handled successfully, * {@code false} if the player was not previously marked as disconnected. */ public synchronized boolean reconnectPlayer(Player player) { if(!disconnectedPlayers.containsKey(player)) { return false; } disconnectedPlayers.put(player,false); if(currentState.getGameStage() == GameStages.SLOT_CHOICE) { disconnectedPlayers.remove(player); if(!orderLogicCard.containsInQueue(player)) { orderLogicCard.pushNoEffect(player); } } else if(currentState.getGameStage() == GameStages.RES_ACTIONS) { if(slotMap.containsValue(player)) { disconnectedPlayers.remove(player); } } else if(currentState.getGameStage() == GameStages.TOTEM_CHOICE) { totemChoiceQueue.removeIf(x->x.getUserName().equals(player.getUserName())); totemChoiceQueue.add(player); } return true; } /** * Clears the collection of disconnected players. */ public synchronized void clearDisconnected() { disconnectedPlayers.clear(); } /** * Returns the current number of players participating in the game. * @return the current number of players. */ public int getCurrentPlayerNumber() { return playersList.size(); } /** * The list of players participating in the game. */ private ArrayList playersList; /** * The current state of the game. */ private CurrentState currentState; /** * The mapping between slots and the players assigned to them. */ private HashMap slotMap; /** * Returns the map of slots assigned to players. * * @return the map that associates each slot with the corresponding player. */ public HashMap getSlotMap() {return slotMap;} /** * The configured number of players for this game. */ private int nPlayers; /** * The queue of players involved in optional card resolution. */ private Queue optionalCardQueue; /** * The order logic card associated with this game. */ private OrderLogicCard orderLogicCard; /** * Returns the order logic card associated with this game. * * @return the order logic card. */ public OrderLogicCard getOrderLogicCard() { return orderLogicCard; } /** * The board associated with this game. */ private Board board; /** * Returns the game board. * * @return the board associated with the game. */ public Board getBoard() {return board;} /** * Returns a copy of the upper tribe cards currently available on the board. * * @return a new list containing the upper tribe cards currently on the board. */ public ArrayList getUpperListTribeCards() { return new ArrayList<>(board.getUpperListTribe()); } /** * Returns a copy of the lower tribe cards currently available on the board. * * @return a new list containing the lower tribe cards currently on the board. */ public ArrayList getLowerListTribeCards() { return new ArrayList<>(board.getLowerListTribe()); } /** * Returns a copy of the upper building cards currently available on the board. * * @return a new list containing the upper building cards currently on the board. */ public ArrayList getUpperListBuilding() { return new ArrayList<>(board.getUpperListBuilding()); } /** * Returns a copy of the lower building cards currently available on the board. * * @return a new list containing the lower building cards currently on the board. */ public ArrayList getLowerListBuilding() { return new ArrayList<>(board.getLowerListBuilding()); } /** * Returns the current state of the game. * * @return the current state of the game. */ public CurrentState getCurrentState() { return currentState; } /** * Returns the player with the specified username, if present. * * @param username the username of the player to search for. * @return the player with the specified username, or {@code null} if no such player exists. * @throws IndexOutOfBoundsException if an index access error occurs. */ public Player getPlayerByUsername(String username) throws IndexOutOfBoundsException { return playersList.stream().filter(x->x.getUserName().equals(username)).findFirst().orElse(null); } /** * Returns the configured number of players for this game. * * @return the configured number of players for this game. */ public int getNPlayers() { return nPlayers; } /** * Creates a game with the specified number of players. * * @param nPlayers the configured number of players for the game. * @throws IllegalArgumentException if {@code nPlayers < 0} or {@code nPlayers > 5}. */ public Game(int nPlayers) throws IllegalArgumentException{ if(nPlayers !=0 && (nPlayers < 2 || nPlayers > 5)) { throw new IllegalArgumentException(); } this.nPlayers = nPlayers; board=new Board(nPlayers); slotMap = new LinkedHashMap<>(); for(Slot s :board.getSlotList()) { slotMap.put(s,null); } currentState= new CurrentState(); playersList = new ArrayList<>(); optionalCardQueue = new LinkedList<>(); } /** * Creates a game with 0 configured players. */ public Game() { this(0); } /** * Attempts to add the specified player to the game. * The operation succeeds only if the configured number of players is not 0, * the current game stage is {@code WAITING}, and the player is not already present. * If the number of players reaches the configured maximum, the game is initialized. * * @param player the player to add to the game. * @return {@code true} if the player is successfully added, {@code false} otherwise. */ public boolean addPlayer(Player player) { if(this.nPlayers==0) { return false; } if(currentState.getGameStage()!= GameStages.WAITING) return false; if(getPlayerByUsername(player.getUserName())!=null) return false; playersList.add(player); totemChoiceQueue.add(player); if(playersList.size()>=nPlayers) { init(); } return true; } /** * Initializes the game after all required players have been added. * The method creates the appropriate order logic card according to the number of players, * selects the first current player, and updates the game stage to {@code SLOT_CHOICE}. */ public void init() { switch (nPlayers) { case 2: orderLogicCard=new Order2(playersList); break; case 3: orderLogicCard=new Order3(playersList); break; case 4: orderLogicCard=new Order4(playersList); break; case 5: orderLogicCard=new Order5(playersList); break; default: throw new IllegalStateException("nPlayers must be 2–5, got: " + nPlayers); } for(Player player : playersList) { switch(orderLogicCard.getPosition(player.getUserName())) { case 0: player.addFood(2); break; case 1,2: player.addFood(3); break; case 3,4: player.addFood(4); break; } } currentState.playerUpdate(totemChoiceQueue.poll(),null); currentState.gameStageUpdate(GameStages.TOTEM_CHOICE); } //region Controller Methods /** * Attempts to assign the slot at the specified index to the specified player. * The operation succeeds only if the index is valid, the current game stage is {@code SLOT_CHOICE}, * the specified player is the current player, and the selected slot is not already assigned. * If the slot is successfully assigned, the next player setup is triggered. * * @param player the player performing the slot choice. * @param slotIndex the index of the selected slot. * @return {@code true} if the slot choice succeeds, {@code false} otherwise. */ public boolean slotChoiceByIndex(Player player, int slotIndex) { if(slotIndex<0 || slotIndex>=slotMap.size()) return false; if(currentState.getGameStage()!= GameStages.SLOT_CHOICE) { return false; } if(!player.equals(currentState.getCurrentPlayer())) { return false; } Map.Entry slotPlayerEntry = new ArrayList<>(slotMap.entrySet()).get(slotIndex); if(slotPlayerEntry.getValue()!=null) { return false; } slotMap.put(slotPlayerEntry.getKey(),player); nextPlayerSetup(); return true; } //region Drawing Methods /** * Attempts to draw the upper tribe card at the specified index for the specified player. * The operation succeeds only if the index is valid, the game stage is {@code RESOLVING_ACTIONS}, * the specified player is the current player, at least one upper card draw is still available, * and the selected tribe card is not an event card. * If successful, the card is inserted into the player's collection, removed from the board, * and the number of remaining upper draws is decremented. * If both upper and lower draws become zero, the next player setup is triggered. * * @param player the player performing the draw. * @param cardIndex the index of the upper tribe card to draw. * @return {@code true} if the draw succeeds, {@code false} otherwise. */ public boolean drawUpperTribeCardByIndex(Player player,int cardIndex) { if( cardIndex<0 || cardIndex >=board.getUpperListTribe().size()) return false; if(currentState.getGameStage()!= GameStages.RES_ACTIONS && currentState.getGameStage()!=GameStages.OPT_CARD_E) { return false; } if(!player.equals(currentState.getCurrentPlayer())) { return false; } if(currentState.getNUpper() <1 && currentState.getGameStage()==GameStages.RES_ACTIONS) return false; TribeCard tribeCard = board.getUpperListTribe().get(cardIndex); if(tribeCard.isEventCard()) return false; Character tempCard = (Character) tribeCard; tempCard.insert(player); board.removeUpperTribeCard(tempCard); if(currentState.getGameStage()==GameStages.RES_ACTIONS) { currentState.upperDrawn(); if((currentState.getNLower() ==0 ||( !hasDrawableDown() && getLowerListBuilding().isEmpty())) && ((currentState.getNUpper() ==0)||(!hasDrawableUp() && getUpperListBuilding().isEmpty()))) nextPlayerSetup(); } else { nextPlayerSetup(); } return true; } /** * Skips the turn for the specified player when no drawable cards are available. * The operation succeeds only if the current game stage is {@code RESOLVING_ACTIONS}, * the specified player is the current player, * and the player can't draw tribe card (i.e. all remaining lower tribe cards are event cards). * @param player the player skipping the turn . * @return {@code true} if the skip succeeds, {@code false} otherwise. */ public boolean skipTurn(Player player) { if(currentState.getGameStage() != GameStages.RES_ACTIONS && currentState.getGameStage() != GameStages.OPT_CARD_E) { return false; } if(!player.equals(currentState.getCurrentPlayer())) { return false; } if(currentState.getGameStage()==GameStages.RES_ACTIONS) { if(hasDrawableDown() && currentState.getNLower()>0 ) return false; if(hasDrawableUp() && currentState.getNUpper()>0) return false; while(currentState.getNLower()>0) currentState.lowerDrawn(); while(currentState.getNUpper()>0) currentState.upperDrawn(); nextPlayerSetup(); } else { nextPlayerSetup(); } return true; } /** * Attempts to draw the lower tribe card at the specified index for the specified player. * The operation succeeds only if the index is valid, the game stage is {@code RESOLVING_ACTIONS}, * the specified player is the current player, at least one lower card draw is still available, * and the selected tribe card is not an event card. * If successful, the card is inserted into the player's collection, removed from the board, * and the number of remaining lower draws is decremented. * If both lower and upper draws become zero, the next player setup is triggered. * * @param player the player performing the draw. * @param cardIndex the index of the lower tribe card to draw. * @return {@code true} if the draw succeeds, {@code false} otherwise. */ public boolean drawLowerTribeCardByIndex(Player player, int cardIndex) { if( cardIndex<0 || cardIndex >=board.getLowerListTribe().size()) return false; if(currentState.getGameStage()!= GameStages.RES_ACTIONS) { return false; } if(!player.equals(currentState.getCurrentPlayer())) { return false; } if(currentState.getNLower() <1) return false; TribeCard tribeCard = board.getLowerListTribe().get(cardIndex); if(tribeCard.isEventCard()) return false; Character tempCard = (Character) tribeCard; tempCard.insert(player); board.removeLowerTribeCard(tempCard); currentState.lowerDrawn(); if((currentState.getNLower() ==0 ||( !hasDrawableDown() && getLowerListBuilding().isEmpty())) && ((currentState.getNUpper() ==0)||(!hasDrawableUp() && getUpperListBuilding().isEmpty()))) nextPlayerSetup(); return true; } /** * Attempts to draw the upper building card at the specified index for the specified player. * The operation succeeds only if the index is valid, the game stage is {@code RESOLVING_ACTIONS}, * the specified player is the current player, at least one upper card draw is still available, * and the selected building card can be bought by the player. * If successful, the building card is removed from the board and the number of remaining upper draws is decremented. * If both upper and lower draws become zero, the next player setup is triggered. * * @param player the player performing the draw. * @param cardIndex the index of the upper building card to draw. * @return {@code true} if the draw succeeds, {@code false} otherwise. */ public boolean drawUpperBuildingCardByIndex(Player player,int cardIndex) { if( cardIndex<0 || cardIndex >=board.getUpperListBuilding().size()) return false; if(currentState.getGameStage()!= GameStages.RES_ACTIONS && currentState.getGameStage()!=GameStages.OPT_CARD_E) { return false; } if(!player.equals(currentState.getCurrentPlayer())) { return false; } BuildingCard buildingCard = board.getUpperListBuilding().get(cardIndex); if(currentState.getNUpper() <1 && currentState.getGameStage()==GameStages.RES_ACTIONS) return false; if(!buildingCard.buy(player)) { return false; } board.removeUpperBuildingCard(buildingCard); if(currentState.getGameStage() == GameStages.RES_ACTIONS) { currentState.upperDrawn(); if((currentState.getNLower() ==0 ||( !hasDrawableDown() && getLowerListBuilding().isEmpty())) && ((currentState.getNUpper() ==0)||(!hasDrawableUp() && getUpperListBuilding().isEmpty()))) nextPlayerSetup(); } else { nextPlayerSetup(); } return true; } /** * Attempts to draw the lower building card at the specified index for the specified player. * The operation succeeds only if the index is valid, the game stage is {@code RESOLVING_ACTIONS}, * the specified player is the current player, at least one lower card draw is still available, * and the selected building card can be bought by the player. * If successful, the building card is removed from the board and the number of remaining lower draws is decremented. * If both lower and upper draws become zero, the next player setup is triggered. * * @param player the player performing the draw. * @param cardIndex the index of the lower building card to draw. * @return {@code true} if the draw succeeds, {@code false} otherwise. */ public boolean drawLowerBuildingCardByIndex(Player player,int cardIndex) { if( cardIndex<0 || cardIndex >=board.getLowerListBuilding().size()) return false; if(currentState.getGameStage()!= GameStages.RES_ACTIONS) { return false; } if(!player.equals(currentState.getCurrentPlayer())) { return false; } BuildingCard buildingCard = board.getLowerListBuilding().get(cardIndex); if(currentState.getNLower() <1) return false; if(buildingCard.buy(player)) { currentState.lowerDrawn(); board.removeLowerBuildingCard(buildingCard); } else return false; if((currentState.getNLower() ==0 ||( !hasDrawableDown() && getLowerListBuilding().isEmpty())) && ((currentState.getNUpper() ==0)||(!hasDrawableUp() && getUpperListBuilding().isEmpty()))) nextPlayerSetup(); return true; } //region Optional Card Methods /** * Prepares the next player and updates the game state according to the current game stage. * If the current stage is {@code SLOT_CHOICE}, the next player is taken from the order logic card. * If no player is available, the game stage is updated to {@code RESOLVING_ACTIONS} * and the first assigned slot is selected. * If the current stage is {@code RESOLVING_ACTIONS}, the current player is pushed back * into the order logic card, the current slot is freed, and the next assigned slot is selected. * If no assigned slots remain, the game stage is updated to {@code OPTIONAL_CARD_EFFECT}, * the optional card queue is built from players owning building cards with effect id equal to 12, * and the first player in that queue is selected. * If no player is available for optional card resolution, the game stage is updated to * {@code RESOLVING_EVENT}; then, if the round number is less than 10, the next round is prepared, * otherwise event resolution is performed, the game stage is updated to {@code ENDING}, * and the game is ended. * If the current stage is {@code OPTIONAL_CARD_EFFECT}, the next player is taken from the optional card queue. * If no player is available, the game stage is updated to {@code RESOLVING_EVENT}. */ private synchronized void nextPlayerSetup() { if (GameStages.SLOT_CHOICE == currentState.getGameStage()) { Player tempPlayer; while (true) { tempPlayer = orderLogicCard.pull(); if (tempPlayer == null) break; if (!disconnectedPlayers.containsKey(tempPlayer) || !disconnectedPlayers.get(tempPlayer)) break; } if (tempPlayer != null) { currentState.playerUpdate(tempPlayer, null); return; } // All players have chosen a slot → switch to RES_ACTIONS currentState.gameStageUpdate(GameStages.RES_ACTIONS); boolean anyAssigned = false; for (Map.Entry entry : slotMap.entrySet()) { if (entry.getValue() != null) { if(entry.getKey().getSlotId()=='A') { entry.getValue().addFood(3); orderLogicCard.push(entry.getValue()); entry.setValue(null); continue; } currentState.playerUpdate(entry.getValue(), entry.getKey()); boolean hasDrawableLower = currentState.getNLower() > 0 && (hasDrawableDown() || !getLowerListBuilding().isEmpty()); boolean hasDrawableUpper = currentState.getNUpper() > 0 && (hasDrawableUp() || !getUpperListBuilding().isEmpty()); if (!hasDrawableLower && !hasDrawableUpper) { // This player has nothing drawable, skip them immediately orderLogicCard.push(currentState.getCurrentPlayer()); slotMap.put(currentState.getSlot(), null); } else { anyAssigned = true; break; } } } // If every player was skipped, jump straight to optional phase if (!anyAssigned) { transitionToOptionalOrNextRound(); } return; } if (GameStages.RES_ACTIONS == currentState.getGameStage()) { orderLogicCard.push(currentState.getCurrentPlayer()); slotMap.put(currentState.getSlot(), null); for (Map.Entry entry : slotMap.entrySet()) { if (entry.getValue() != null) { if(disconnectedPlayers.containsKey(entry.getValue())&& disconnectedPlayers.get(entry.getValue())) { orderLogicCard.push(entry.getValue()); entry.setValue(null); continue; } currentState.playerUpdate(entry.getValue(), entry.getKey()); boolean hasDrawableLower = currentState.getNLower() > 0 && (hasDrawableDown() || !getLowerListBuilding().isEmpty()); boolean hasDrawableUpper = currentState.getNUpper() > 0 && (hasDrawableUp() || !getUpperListBuilding().isEmpty()); if (!hasDrawableLower && !hasDrawableUpper) { // This player also has nothing, skip and continue the loop orderLogicCard.push(entry.getValue()); entry.setValue(null); } else { // Found a player with something to do, stop here return; } } } // All slots are now null → every player is done for this round transitionToOptionalOrNextRound(); return; } if (GameStages.OPT_CARD_E == currentState.getGameStage()) { Player optionalPlayer = optionalCardQueue.poll(); if (optionalPlayer != null) { currentState.playerUpdate(optionalPlayer, null); if(disconnectedPlayers.containsKey(currentState.getCurrentPlayer())&& disconnectedPlayers.get(currentState.getCurrentPlayer())) { nextPlayerSetup(); } return; } currentState.gameStageUpdate(GameStages.RES_EVENT); if (currentState.getRound() < 10) { nextRound(); currentState.playerUpdate(orderLogicCard.pull(), null); currentState.gameStageUpdate(GameStages.SLOT_CHOICE); } else { currentState.gameStageUpdate(GameStages.ENDING); endGame(); } } } /** * Transitions the game to the optional card phase or directly to the next round. * *

Builds a queue of non-disconnected players who own Building 12 (optional card effect). * If such players exist, the first one is set as the current player for their optional action. * Otherwise, if the round counter is below 10, the next round begins and the game stage * is set to {@link GameStages#SLOT_CHOICE}; if round 10 has been completed, the game ends. */ private synchronized void transitionToOptionalOrNextRound() { currentState.gameStageUpdate(GameStages.OPT_CARD_E); optionalCardQueue = new LinkedList<>(); for (Player p :playersList) { long count = p.getBuildingCards().stream().filter(x -> x.getEffectId() == 12).count(); if (count > 0) { if(disconnectedPlayers.containsKey(p)&& disconnectedPlayers.get(p)) { continue; } optionalCardQueue.add(p); } } Player optionalPlayer = optionalCardQueue.poll(); if (optionalPlayer != null) { currentState.playerUpdate(optionalPlayer, null); return; } if (currentState.getRound() < 10) { nextRound(); for (Map.Entry entry : new ArrayList<>(disconnectedPlayers.entrySet())) { if (!entry.getValue()) { orderLogicCard.pushNoEffect(entry.getKey()); disconnectedPlayers.remove(entry.getKey()); } else { orderLogicCard.removeFromQueue(entry.getKey()); } } currentState.playerUpdate(orderLogicCard.pull(), null); currentState.gameStageUpdate(GameStages.SLOT_CHOICE); } else { currentState.gameStageUpdate(GameStages.ENDING); endGame(); } } /** * Checks whether there are any drawable upper tribe cards on the board, * i.e. upper tribe cards that are not event cards. * * @return {@code true} if at least one non-event upper tribe card is available, {@code false} otherwise. */ private boolean hasDrawableUp() { return getUpperListTribeCards().stream().anyMatch(x -> !x.isEventCard()); } /** * Checks whether there are any drawable lower tribe cards on the board, * i.e. lower tribe cards that are not event cards. * * @return {@code true} if at least one non-event lower tribe card is available, {@code false} otherwise. */ private boolean hasDrawableDown() { return getLowerListTribeCards().stream().anyMatch(x -> !x.isEventCard()); } /** * Resolves all pending event cards if the current game stage is {@code RESOLVING_EVENT}. * All pending events are activated on the player list. * Event cards of type {@code SUSTENANCE} are resolved after all other pending events. */ private void eventResolution() { currentState.gameStageUpdate(GameStages.RES_EVENT); Queue events; events=board.getPendingEvents(); ArrayListsustenance=events.stream().filter(x->x.getType().equals(EventType.SUSTENANCE)).collect(Collectors.toCollection(ArrayList::new)); events.removeAll(sustenance); for(EventCard event:events) { event.activateEvent(playersList); } for(EventCard e : sustenance) { e.activateEvent(playersList); } } /** * Advances the game to the next round. * The method first resolves pending events. * If the current round is 10, the game stage is updated to {@code ENDING} and the game is ended. * Otherwise, the era is updated if the board changes era, and the round number is incremented. */ private void nextRound() { eventResolution(); if(currentState.getEra()!= board.nextRound()) { currentState.eraUpdate(); } currentState.roundUpdate(); } /** * Ends the game by applying all final building effects owned by each player * and updating the game stage to {@code ENDED}. */ private void endGame() { Queue events; events=Stream.concat(board.getLowerListTribe().stream().filter(TribeCard::isEventCard),board.getUpperListTribe().stream().filter(TribeCard::isEventCard)).map(x->((EventCard)x)).collect(Collectors.toCollection(LinkedList::new)); ArrayListsustenance=events.stream().filter(x->x.getType().equals(EventType.SUSTENANCE)).collect(Collectors.toCollection(ArrayList::new)); events.removeAll(sustenance); events.forEach(event->event.activateEvent(playersList)); sustenance.forEach(event->event.activateEvent(playersList)); playersList.forEach(p->{ int temp= p.getBuilders().stream().mapToInt(Builder::getPrestigeValue).sum(); p.addPrestige(temp); }); playersList.forEach(p->{ int temp=(int) p.getInventors().stream().mapToInt(Inventor::getIcon).distinct().count(); p.addPrestige(temp*p.getNType(CharacterType.INVENTOR)); }); playersList.forEach(p->{ p.addPrestige(10 * (p.getNType(CharacterType.ARTIST)/2)); }); playersList.forEach(p->{ int temp= p.getBuildingCards().stream().mapToInt(BuildingCard::getPrestigeValue).sum(); p.addPrestige(temp); }); playersList.forEach( p -> p.getBuildingCards().stream().filter(x -> x.getEffectType() == EffectType.FINAL). forEach(x -> x.applyEffect(p)) ); currentState.gameStageUpdate(GameStages.ENDED); playerStanding=new ArrayList<>(playersList); playerStanding.sort((y,x)->x.getPrestigeValue()==y.getPrestigeValue()?Integer.compare(x.getFoodValue(),y.getFoodValue()):Integer.compare(x.getPrestigeValue(),y.getPrestigeValue())); } /** * Sets the configured number of players for this game. * The operation succeeds only if the current configured number of players is 0. * * @param nPlayers the new configured number of players. * @return {@code true} if the number of players is updated, {@code false} otherwise. */ public boolean setNPlayer(int nPlayers) { if (this.nPlayers != 0) { return false; } if (nPlayers < 2 || nPlayers > 5) { return false; } this.nPlayers = nPlayers; board = new Board(nPlayers); slotMap = new LinkedHashMap<>(); for (Slot s : board.getSlotList()) { slotMap.put(s, null); } currentState = new CurrentState(); playersList = new ArrayList<>(); optionalCardQueue = new LinkedList<>(); return true; } /** * Ends the game by forfeit and determines the final player standing. * *

The player who is still connected is declared the winner and placed * in the first position of the final ranking. The remaining players are * ordered by prestige value and, in case of a tie, by food value. */ public synchronized void endGameForfeit() { Player winner = playersList.stream() .filter(x -> !disconnectedPlayers.containsKey(x) || !disconnectedPlayers.get(x)) .findFirst().orElse(null); if (winner == null) return; currentState.gameStageUpdate(GameStages.ENDED); playerStanding = new ArrayList<>(playersList); playerStanding.remove(winner); playerStanding.sort((y, x) -> x.getPrestigeValue() == y.getPrestigeValue() ? Integer.compare(x.getFoodValue(), y.getFoodValue()) : Integer.compare(x.getPrestigeValue(), y.getPrestigeValue())); playerStanding.add(0, winner); } }