Coverage Summary for Class: PlayerBoard (it.polimi.ingsw.Model)
Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
PlayerBoard |
100%
(1/1)
|
90%
(18/20)
|
82%
(41/50)
|
87,9%
(58/66)
|
1 package it.polimi.ingsw.Model;
2
3 import it.polimi.ingsw.Exceptions.Container.EmptyContainerException;
4 import it.polimi.ingsw.Exceptions.Container.FullContainerException;
5 import it.polimi.ingsw.Exceptions.Container.InvalidContainerIndexException;
6 import it.polimi.ingsw.Exceptions.Input.InvalidElementException;
7 import it.polimi.ingsw.Exceptions.Operation.ForbiddenOperationException;
8 import it.polimi.ingsw.Logger;
9 import it.polimi.ingsw.Misc.OptionalValue;
10 import it.polimi.ingsw.Model.Enums.PawnColour;
11
12 import java.io.Serial;
13 import java.io.Serializable;
14 import java.util.*;
15
16 /**
17 * This class represents the Player's part of the board
18 */
19 public class PlayerBoard implements Serializable {
20 @Serial
21 private static final long serialVersionUID = 126L; // convention: 1 for model, (01 -> 99) for objects
22 private final String nickname;
23 private final AssistantCard[] assistantCards;
24 private final Map<PawnColour, Integer> diningRoom;
25 private final List<OptionalValue<PawnColour>> entrance;
26 private final int id;
27 private int coinBalance;
28
29 /**
30 * Generates a board and initializes its structures
31 *
32 * @param id the ID of the player, used to reference the board through various classes.
33 * @param numOfPlayers when creating a board, the number of players becomes important during initialization of various
34 * internal structures
35 * @param nickname every player has a nickname and the board can store it. Nicknames are not guaranteed to be unique, so
36 * identifying a board through nicknames is highly insecure.
37 * @param studentBag used during the initialization of the board.
38 */
39 public PlayerBoard(int id, int numOfPlayers, String nickname, StudentBag studentBag) {
40 this.nickname = nickname;
41 this.assistantCards = new AssistantCard[10];
42 for (int i = 1; i <= 10; i++) {
43 assistantCards[i - 1] = new AssistantCard(i);
44 }
45 this.coinBalance = 1;
46 this.id = id;
47 this.diningRoom = new EnumMap<>(PawnColour.class);
48 for (PawnColour p : PawnColour.values()) {
49 diningRoom.put(p, 0);
50 }
51 this.entrance = new ArrayList<>(numOfPlayers == 3 ? 9 : 7);
52 if (numOfPlayers >= 2 && numOfPlayers <= 4) {
53 for (int i = 0; i < (numOfPlayers == 3 ? 9 : 7); i++) {
54 try {
55 entrance.add(OptionalValue.of(studentBag.extract()));
56 } catch (EmptyContainerException e) {
57 // should never happen
58 Logger.severe("student bag was found empty while adding a student to an student entrance. Critical, unrecoverable, error");
59 throw new RuntimeException(e);
60 }
61 }
62 } else {
63 throw new RuntimeException("Inconsistent number of players");
64 }
65 }
66
67 /**
68 * Get the {@link AssistantCard}s linked to the player
69 *
70 * @return an Unmodifiable {@link List} of {@link AssistantCard} linked to the player
71 */
72 public List<AssistantCard> getMutableAssistantCards() {
73 return Arrays.stream(assistantCards).toList();
74 }
75
76 /**
77 * Get the current coin balance of the player
78 *
79 * @return an integer representing the amount of coins owned by the player
80 */
81 public int getCoinBalance() {
82 return coinBalance;
83 }
84
85 /**
86 * Sets the coin balance for the PlayerBoard. <br>
87 * NOTE: This method should only be called by tests
88 *
89 * @param balance the amount of coins you want to set the {@link PlayerBoard}'s balance to
90 */
91 public void setCoinBalance(int balance) {
92 this.coinBalance = balance;
93 }
94
95 /**
96 * Get the mappings from {@link PawnColour} to number of students of that colour in the Dining room
97 *
98 * @return an Unmodifiable {@link Map} from {@link PawnColour} to {@link Integer}
99 */
100 public Map<PawnColour, Integer> getDiningRoom() {
101 return Map.copyOf(diningRoom);
102 }
103
104 /**
105 * Get a list of the active slots usable in the student entrance
106 *
107 * @return an Unmodifiable {@link List} representing each slot of the Entrance
108 */
109 public List<OptionalValue<PawnColour>> getEntranceStudents() {
110 return List.copyOf(entrance);
111 }
112
113 /**
114 * Get the ID of the player
115 *
116 * @return the ID of the PlayerBoard
117 */
118 public int getId() {
119 return id;
120 }
121
122 /**
123 * Get the Nickname of the player
124 *
125 * @return the Nickname of the PlayerBoard
126 */
127 public String getNickname() {
128 return nickname;
129 }
130
131 /**
132 * Unsafely add a student to the Dining room. The addition is unsafe because it doesn't track gained coins or teachers.
133 * This method is used by {@link Model#addStudentToDiningRoom(PawnColour, PlayerBoard)} which is the "safe" version.
134 *
135 * @param colour the colour of the student to add to the dining room
136 * @return true if a new coin is to be added to the player's balance
137 * @throws FullContainerException if the dining room is full on the lane of colour before the addition
138 */
139 protected boolean unsafeAddStudentToDiningRoom(PawnColour colour) throws FullContainerException {
140 if (this.isDiningRoomFull(colour)) {
141 throw new FullContainerException("Dining Room");
142 }
143 this.diningRoom.merge(colour, 1, Integer::sum);
144 return this.diningRoom.get(colour) % 3 == 0; // returns true when a coin should be added
145 }
146
147 /**
148 * Check to see if the dining room can accommodate more students on a lane
149 *
150 * @param student selects the lane of the dining room to inspect
151 * @return true if the dining room's lane is full, false otherwise
152 */
153 public boolean isDiningRoomFull(PawnColour student) {
154 return getDiningRoomCount(student) >= 10;
155 }
156
157 /**
158 * Get the amount of students in a lane of the dining room
159 *
160 * @param colour selects the lane of the dining room to inspect
161 * @return the count of students in the lane selected by colour
162 */
163 public int getDiningRoomCount(PawnColour colour) {
164 return diningRoom.get(colour);
165 }
166
167 /**
168 * Unsafely remove a student from the Dining room. The removal is unsafe because it doesn't track teachers.
169 * This method is used by {@link Model#removeStudentFromDiningRoom(PawnColour, PlayerBoard)} which is the "safe" version.
170 *
171 * @param colour the colour of the student to remove the dining room
172 * @throws EmptyContainerException if the dining room is empty on the lane of colour before the removal
173 */
174 public void unsafeRemoveStudentFromDiningRoom(PawnColour colour) throws EmptyContainerException {
175 if (this.getDiningRoomCount(colour) == 0) {
176 throw new EmptyContainerException("Dining Room");
177 } else {
178 this.diningRoom.merge(colour, -1, Integer::sum);
179 }
180 }
181
182 /**
183 * Add multiple students to slots in the entrance
184 *
185 * @param students a {@link List} of {@link PawnColour} containing the students to add to the entrance
186 * @throws FullContainerException if the entrance isn's capable of holding all the students, the students are not added and the
187 * exception is thrown
188 */
189 public void addStudentsToEntrance(List<PawnColour> students) throws FullContainerException {
190 if (students.size() > 0) {
191 if (students.size() > this.getEntranceSpaceLeft()) {
192 // 2 & 4 players -> 7 students placed on entrance, 3 players -> 9 students placed on entrance
193 throw new FullContainerException("Entrance");
194 }
195 int cont = 0;
196 for (int i = 0; i < this.getEntranceSize(); i++) {
197 if (this.entrance.get(i).isEmpty()) {
198 this.entrance.set(i, OptionalValue.of(students.get(cont++)));
199 if (cont == students.size()) {
200 break;
201 }
202 }
203 }
204 }
205 }
206
207 /**
208 * Get the amount of free slots in the student entrance
209 *
210 * @return the count of free slots in the entrance
211 */
212 public int getEntranceSpaceLeft() {
213 return (int) entrance.stream()
214 .filter(OptionalValue::isEmpty)
215 .count();
216 }
217
218 /**
219 * Get the size of the entrance (can change depending on the number of players)
220 *
221 * @return the size of the entrance
222 */
223 public int getEntranceSize() {
224 return entrance.size();
225 }
226
227 /**
228 * Add a single student to a slot in the entrance
229 *
230 * @param student a {@link PawnColour} representing the student to add to the entrance
231 * @throws FullContainerException if the entrance isn's capable of the student, the student is not added and the
232 * exception is thrown
233 */
234 public void addStudentToEntrance(PawnColour student) throws FullContainerException {
235 if (this.getEntranceSpaceLeft() == 0) {
236 // 2 & 4 players -> 7 students placed on entrance, 3 players -> 9 students placed on entrance
237 throw new FullContainerException("Entrance");
238 }
239 for (int i = 0; i < this.getEntranceSize(); i++) {
240 if (this.entrance.get(i).isEmpty()) {
241 this.entrance.set(i, OptionalValue.of(student));
242 return;
243 }
244 }
245 }
246
247 /**
248 * Removes a single student from the entrance
249 *
250 * @param pos the index of the slot from which to remove the student
251 * @return the {@link PawnColour} of the removed student
252 * @throws InvalidContainerIndexException if the index is out of bounds or the slot was empty before removal
253 */
254 public PawnColour removeStudentFromEntrance(int pos) throws InvalidContainerIndexException {
255 if (pos < 0 || pos >= this.getEntranceSize() || this.entrance.get(pos).isEmpty()) {
256 throw new InvalidContainerIndexException("Entrance");
257 }
258 PawnColour student = this.entrance.get(pos).get();
259 this.entrance.set(pos, OptionalValue.empty());
260 return student;
261 }
262
263 /**
264 * Removes a single student from the entrance
265 *
266 * @param colour the {@link PawnColour} of the student to remove from the entrance
267 * @throws InvalidElementException if no students of the same colour could be found in the entrance
268 */
269 public void removeStudentFromEntrance(PawnColour colour) throws InvalidElementException {
270 for (int i = 0; i < this.getEntranceSize(); i++) {
271 if (entrance.get(i).equals(OptionalValue.of(colour))) {
272 entrance.set(i, OptionalValue.empty());
273 return;
274 }
275 }
276 throw new InvalidElementException("Target Pawn Colour");
277 }
278
279 /**
280 * Adds a coin to the balance
281 */
282 protected void addCoin() {
283 this.coinBalance += 1;
284 }
285
286 /**
287 * Removes as many coins from the balance as the cost of the paid card.
288 *
289 * @param card the {@link CharacterCard} to pay for
290 * @throws ForbiddenOperationException if the balance cannot accommodate for the cost of the card
291 */
292 public void payCharacterEffect(CharacterCard card) throws ForbiddenOperationException {
293 if (this.coinBalance >= card.getCost()) {
294 this.coinBalance -= card.getCost();
295 } else {
296 throw new ForbiddenOperationException("payCharacterEffect", "player coin balance too low");
297 }
298 }
299
300 }
301