Coverage Summary for Class: Game (it.polimi.ingsw.gc14.Model)

Class Class, % Method, % Branch, % Line, %
Game 100% (1/1) 90.9% (40/44) 68.4% (210/307) 83.1% (285/343)


 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<Player> playerStanding;
 
     /**
      * Returns the final ranking of players.
      *
      * @return the list of players ordered according to their final standing.
      */
     public ArrayList<Player> 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<Player> 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 List<Totems>getAvailableTotems() {
         List<Totems> 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<Player> totemChoiceQueue = new LinkedList<>();
 
     /**
      * 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 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<Player,Boolean> disconnectedPlayers = new HashMap<>();
 
     /**
      * Returns an unmodifiable view of the disconnected-players map.
      *
      * @return map from player to disconnection status.
      */
     public Map<Player,Boolean> getDisconnectedPlayers() {
         return Collections.unmodifiableMap(disconnectedPlayers);
     }
 
     /**
      * 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 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.
      *
      * <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 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);
             }
         }
         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<Player> playersList;
 
     /**
      * The current state of the game.
      */
     private CurrentState currentState;
 
     /**
      * The mapping between slots and the players assigned to them.
      */
     private HashMap<Slot,Player> slotMap;
 
     /**
      * Returns the map of slots assigned to players.
      *
      * @return the map that associates each slot with the corresponding player.
      */
     public HashMap<Slot,Player> 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<Player> 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<TribeCard> 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<TribeCard> 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<BuildingCard> 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<BuildingCard> 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);
     }
 
 
     /**
      * 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<Slot, Player> 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<Slot,Player> 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<Slot,Player> 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.
      *
      * <p>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<Player, Boolean> 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<EventCard> events;
         events=board.getPendingEvents();
         ArrayList<EventCard>sustenance=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<EventCard> 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));
         ArrayList<EventCard>sustenance=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.
      *
      * <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 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);
     }
 
 
 }