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 }