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

Class Class, % Method, % Branch, % Line, %
TurnOrder 100% (1/1) 93,8% (15/16) 62,5% (20/32) 84,9% (45/53)


1 package it.polimi.ingsw.Model; 2  3 import it.polimi.ingsw.Exceptions.Operation.ForbiddenOperationException; 4 import it.polimi.ingsw.Misc.OptionalValue; 5 import it.polimi.ingsw.Misc.Utils; 6 import it.polimi.ingsw.Model.Enums.GamePhase; 7  8 import java.io.Serial; 9 import java.io.Serializable; 10 import java.util.*; 11 import java.util.stream.Collectors; 12  13 /** 14  * Represents the order in which players will play a round, and organizes the next round based on played {@link AssistantCard}s 15  */ 16 public class TurnOrder implements Serializable { 17  @Serial 18  private static final long serialVersionUID = 134L; // convention: 1 for model, (01 -> 99) for objects 19  private final Map<PlayerBoard, OptionalValue<AssistantCard>> selectedCards; // used to generate the new turn order 20  // if a playerboard is associated with an empty optional then their card has not yet been chosen for the turn 21  // or said player is currently being skipped 22  private int currentTurnPosition; // selects the current player from currentTurnOrder 23  private List<PlayerBoard> currentTurnOrder; // represents the order for the turn in play 24  private GamePhase gamePhase; 25  26  /** 27  * Creates the turn order object and assigns a random starting turn formation for players. 28  * 29  * @param playerBoards the players in the game 30  */ 31  public TurnOrder(List<PlayerBoard> playerBoards) { 32  if (playerBoards != null && playerBoards.size() >= 2 && playerBoards.size() <= 4) { 33  // add all players to their cards map and set them to not skipped 34  this.selectedCards = new HashMap<>(playerBoards.size()); 35  for (PlayerBoard pb : 36  playerBoards) { 37  this.selectedCards.put(pb, OptionalValue.empty()); 38  } 39  // create turn order 40  this.currentTurnOrder = new ArrayList<>(playerBoards); 41  Utils.shuffle(currentTurnOrder); // starting order for first turn is randomized 42  // set current turn position 43  this.currentTurnPosition = 0; 44  // set game phase 45  this.gamePhase = GamePhase.SETUP; 46  } else { 47  throw new RuntimeException("Inconsistent amount of Playerboards"); 48  } 49  } 50  51  /** 52  * Get the current pecking order for the turn 53  * 54  * @return an Unmodifiable {@link List} ordered from index 0 being the first player, onwards 55  */ 56  public List<PlayerBoard> getCurrentTurnOrder() { 57  return List.copyOf(currentTurnOrder); 58  } 59  60  /** 61  * Get the card a user played to define the pecking order 62  * 63  * @param pb the player to filter the played {@link AssistantCard}s for 64  * @return a {@link OptionalValue} containing the selected {@link AssistantCard}, if one has been played by the user this round. 65  */ 66  public OptionalValue<AssistantCard> getMutableSelectedCard(PlayerBoard pb) { 67  return this.selectedCards.get(pb); 68  } 69  70  /** 71  * Select the {@link AssistantCard} used by the player this round 72  * 73  * @param pb the player to set the card for 74  * @param ac the card selected by the player 75  * @throws ForbiddenOperationException if the card was already used, if the {@link GamePhase} is not in {@link GamePhase#SETUP} 76  * or if it's not the player's turn yet, if the card or the player were null or if 77  * the player could have played a different, not yet played by him or anyone else (during this turn) card. 78  */ 79  public void setSelectedCard(PlayerBoard pb, AssistantCard ac) throws ForbiddenOperationException { 80  if (pb == null) { // not null contract 81  throw new ForbiddenOperationException("PlayerBoard pb", "can't be null"); 82  } 83  if (getGamePhase() != GamePhase.SETUP || !isOwnTurn(pb)) { // correct phase and turn contract 84  throw new ForbiddenOperationException("Game phase or turn", "wrong game phase or turn for player"); 85  } 86  if (ac == null) { // not null contract 87  throw new ForbiddenOperationException("AssistantCard ac", "can't be null"); 88  } 89  if (ac.getUsed()) { // no reuse card contract 90  throw new ForbiddenOperationException("setSelectedCard", "can't have been previously used"); 91  } 92  if (isAlreadyInSelection(ac) && canPlayUniqueCard(pb)) { // no duplicate cards contract 93  throw new ForbiddenOperationException("AssistantCard ac", "should be a unique card whenever possible"); 94  } 95  96  // validation passed: 97  ac.setUsed(); 98  this.selectedCards.put(pb, OptionalValue.of(ac)); 99  } 100  101  /** 102  * Get the phase of the current round 103  * 104  * @return the {@link GamePhase} of the current round 105  */ 106  public GamePhase getGamePhase() { 107  return gamePhase; 108  } 109  110  /** 111  * Finds if it is a player's own turn yet 112  * 113  * @param pb the player to filter for 114  * @return true if it is the player's turn, false otherwise 115  */ 116  public boolean isOwnTurn(PlayerBoard pb) { 117  return getMutableCurrentPlayer() == pb; 118  } 119  120  /** 121  * Check if a card has already been played this round 122  * 123  * @param ac the card to filter for 124  * @return true if the selected card was already submitted as a selection in {@link #setSelectedCard(PlayerBoard, AssistantCard)} 125  * during this round 126  */ 127  public boolean isAlreadyInSelection(AssistantCard ac) { 128  return getSelectedCards().stream() 129  .anyMatch(selected -> selected.getPriority() == ac.getPriority()); 130  } 131  132  /** 133  * Check to see if the player can still play a card that is unique this turn 134  * 135  * @param pb the player to filter cards for 136  * @return true if the player can play at least one not yet selected card this round, false otherwise 137  */ 138  public boolean canPlayUniqueCard(PlayerBoard pb) { 139  return !pb.getMutableAssistantCards().stream().allMatch(this::isAlreadyInSelection); 140  } 141  142  /** 143  * Get a reference to the current player 144  * 145  * @return a reference to the {@link PlayerBoard} of the current player 146  */ 147  public PlayerBoard getMutableCurrentPlayer() { 148  return this.currentTurnOrder.get(this.currentTurnPosition); 149  } 150  151  /** 152  * Get all of the assistant cards played this round 153  * 154  * @return an Unmodifiable {@link List} of the {@link AssistantCard}s played this round as of yet 155  */ 156  public List<AssistantCard> getSelectedCards() { 157  return selectedCards.values().stream() 158  .filter(OptionalValue::isPresent) 159  .map(OptionalValue::get) 160  .toList(); // immutable list 161  } 162  163  /** 164  * Proceed to the next player in the turn order 165  */ 166  public void stepToNextPlayer() { 167  // for all players except the last in turn 168  if (currentTurnPosition < currentTurnOrder.size() - 1) { 169  currentTurnPosition++; 170  } else { // last player to call this resets the turn and steps to next phase 171  currentTurnPosition = 0; 172  stepNextGamePhase(); 173  } 174  } 175  176  /** 177  * During the round, switches between {@link GamePhase}s 178  */ 179  private void stepNextGamePhase() { 180  // if stepping from setup to action 181  // there is a need to commit the new turn order 182  if (getGamePhase() == GamePhase.SETUP) { 183  commitTurnOrder(); 184  gamePhase = GamePhase.ACTION; 185  } else { // when coming back to the setup phase the selected cards map must be reset 186  cleanSelectedCards(); 187  gamePhase = GamePhase.SETUP; 188  } 189  } 190  191  /** 192  * Based on the {@link #getSelectedCards()} set the new turn order for the next round. Players that have not selected a card 193  * will be put last in the order. 194  */ 195  public void commitTurnOrder() { 196  // the starting elements of playersInOrder are players that have not been skipped 197  // the last elements of playersInOrder are all the players that have been skipped 198  this.currentTurnOrder = selectedCards.entrySet().stream() 199  .sorted(Comparator.comparingInt(t -> // sort based on priority 200  t.getValue() 201  .flatMap(ac -> OptionalValue.of( 202  ac.getPriority())) // if a card was selected extract the priority 203  .orElse(100))) // otherwise use a priority level that is higher than any other card 204  .map(Map.Entry::getKey) 205  .collect(Collectors.toList()); 206  } 207  208  /** 209  * Removes the selected cards from memory 210  */ 211  private void cleanSelectedCards() { 212  selectedCards.replaceAll((k, v) -> OptionalValue.empty()); 213  } 214 }