Files
Progetto-ingegneria-del-sof…/src/main/java/it/polimi/ingsw/gc14/Model/Game.java
T
rubenpirreram 7f582f116b Totem FIx?
2026-06-19 20:39:52 +02:00

1027 lines
39 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
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<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 25, 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<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);
}
}