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 }