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