Coverage Summary for Class: Lobby (it.polimi.ingsw.Server)

Class Class, % Method, % Branch, % Line, %
Lobby 100% (1/1) 14,3% (2/14) 5% (1/20) 18,6% (11/59)


1 package it.polimi.ingsw.Server; 2  3 import it.polimi.ingsw.Controller.Actions.PlayerAction; 4 import it.polimi.ingsw.Controller.Controller; 5 import it.polimi.ingsw.Exceptions.Input.InputValidationException; 6 import it.polimi.ingsw.Exceptions.Operation.ForbiddenOperationException; 7 import it.polimi.ingsw.Exceptions.Operation.OperationException; 8 import it.polimi.ingsw.Misc.OptionalValue; 9 import it.polimi.ingsw.Model.Enums.GameMode; 10 import it.polimi.ingsw.Server.Messages.Events.ClientEvent; 11 import it.polimi.ingsw.Server.Messages.Events.Internal.ClientConnectEvent; 12 import it.polimi.ingsw.Server.Messages.Events.Internal.ClientDisconnectEvent; 13 import it.polimi.ingsw.Server.Messages.Events.Internal.GameStartEvent; 14 import it.polimi.ingsw.Server.Messages.Events.Internal.LobbyClosedEvent; 15  16 import java.util.*; 17 import java.util.concurrent.BlockingQueue; 18 import java.util.concurrent.ConcurrentHashMap; 19  20 import static it.polimi.ingsw.Server.LobbyServer.lobbyMap; 21  22 /** 23  * Multiple {@link LobbyServer} instances will need to communicate to run the game. This class groups the the {@link BlockingQueue<ClientEvent>} 24  * used by each server to dispatch gameLobby-wide events. 25  */ 26 public class Lobby { 27  private final UUID id; 28  private final String admin; 29  private final boolean isPublic; 30  private final int maxPlayers; 31  private final List<String> players; 32  private final Map<String, BlockingQueue<ClientEvent>> playerEventQueues; 33  private boolean isClosed; 34  private Controller controller; 35  36  /** 37  * Create a new lobby 38  * 39  * @param id a unique ID used to refer to the lobby 40  * @param isPublic if the lobby is supposed to be publicly available to new clients 41  * @param maxPlayers the amount of clients the game connected to the lobby will host 42  * @param admin the name of the admin client. The admin owns the game and even though everyone can start a session, if 43  * the admin disconnects while in the waiting lobby, the lobby is closed. 44  */ 45  public Lobby(UUID id, boolean isPublic, int maxPlayers, String admin) { 46  this.id = id; 47  this.admin = admin; 48  this.isPublic = isPublic; 49  this.isClosed = false; 50  this.maxPlayers = maxPlayers; 51  this.players = new ArrayList<>(); 52  this.playerEventQueues = new ConcurrentHashMap<>(); 53  } 54  55  /** 56  * Attempts to forward an action to the game's controller 57  * 58  * @param pa the action to forward 59  * @throws InputValidationException if the controller does not validate the action positively 60  * @throws OperationException if no controller is online or if a validated action failed to run properly 61  */ 62  public synchronized void executeAction(PlayerAction pa) throws InputValidationException, OperationException { 63  if (controller == null) { 64  throw new ForbiddenOperationException("Game lobby", "Lobby is in waiting state, no game is running"); 65  } 66  controller.executeAction(pa); 67  } 68  69  /** 70  * Get the id of the lobby 71  * 72  * @return the {@link UUID} of the lobby 73  */ 74  public UUID getId() { 75  return id; 76  } 77  78  /** 79  * Get the name of the admin 80  * 81  * @return the nickname of the admin client 82  */ 83  public String getAdmin() { 84  return admin; 85  } 86  87  /** 88  * Check if the lobby is public or not 89  * 90  * @return true if the lobby is public, false otherwise 91  */ 92  public boolean isPublic() { 93  return isPublic; 94  } 95  96  /** 97  * Check to see if the lobby is full 98  * 99  * @return true if the lobby is full, false otherwise 100  */ 101  public boolean isLobbyFull() { 102  return this.players.size() == maxPlayers; 103  } 104  105  /** 106  * Get the maximum amount of players for the lobby 107  * 108  * @return the max amount of clients that can connect to the lobby 109  */ 110  public int getMaxPlayers() { 111  return maxPlayers; 112  } 113  114  /** 115  * Get a list of connected players 116  * 117  * @return a {@link List} of the nicknames of the connected players 118  */ 119  public List<String> getPlayers() { 120  synchronized (this.players) { 121  return List.copyOf(this.players); 122  } 123  } 124  125  /** 126  * Connect a player to the lobby 127  * 128  * @param nick the nickname of the player to connect 129  * @param playerChannel the queue to forward new {@link ClientEvent}s to 130  * @return true if the player was successfully connected, false otherwise 131  */ 132  public boolean addPlayer(String nick, BlockingQueue<ClientEvent> playerChannel) { 133  synchronized (this.players) { 134  if (this.isClosed) { 135  return false; 136  } 137  // in case of new connection check for max players and check that the game has not yet started 138  if (this.controller == null && this.maxPlayers > this.players.size()) { 139  this.players.add(nick); 140  this.playerEventQueues.put(nick, playerChannel); 141  notifyPlayers(new ClientConnectEvent(nick, List.copyOf(this.players))); 142  return true; 143  } else { 144  return false; 145  } 146  } 147  } 148  149  /** 150  * Propagates a {@link ClientEvent} to all players 151  * 152  * @param event the event to propagate to all players 153  */ 154  public void notifyPlayers(ClientEvent event) { 155  synchronized (this.players) { 156  for (BlockingQueue<ClientEvent> queue : this.playerEventQueues.values()) { 157  try { 158  queue.put(event); 159  } catch (InterruptedException e) { 160  e.printStackTrace(); 161  } 162  } 163  } 164  } 165  166  /** 167  * Removes a player from the lobby. If the game has started for the lobby or the admin decided to leave, the lobby is closed 168  * and players are notified through {@link LobbyClosedEvent} 169  * 170  * @param nick the nickname of the player to remove from the lobby 171  */ 172  protected void disconnectPlayer(String nick) { 173  synchronized (this.players) { 174  this.playerEventQueues.remove(nick); 175  this.players.remove(nick); 176  notifyPlayers(new ClientDisconnectEvent(nick, List.copyOf(this.players))); 177  if (this.isGameInProgress() || this.admin.equals(nick)) { 178  lobbyMap.remove(this.id); 179  this.close(); 180  } 181  } 182  } 183  184  /** 185  * Check to see if the game has started or if clients are still waiting for the game to start 186  * 187  * @return true if the game is on, otherwise false 188  */ 189  public boolean isGameInProgress() { 190  return this.controller != null; 191  } 192  193  /** 194  * Closes the lobby and notifies players through {@link LobbyClosedEvent} 195  */ 196  protected void close() { 197  notifyPlayers(new LobbyClosedEvent()); 198  this.players.clear(); 199  this.playerEventQueues.clear(); 200  this.isClosed = true; 201  } 202  203  /** 204  * Starts the game. {@link LobbyServer}s will receive a {@link GameStartEvent} 205  * 206  * @param gameMode the {@link GameMode} to start the game in 207  * @throws InputValidationException if the players in lobby are more than 4 or less than 2 208  */ 209  protected void startGame(GameMode gameMode) throws InputValidationException { 210  synchronized (this.players) { 211  Map<String, Integer> nickToID = new HashMap<>(this.players.size()); 212  for (int i = 0; i < this.players.size(); i++) { 213  nickToID.put(this.players.get(i), i); 214  } 215  notifyPlayers(new GameStartEvent(nickToID)); 216  this.controller = Controller.createGame(gameMode, 217  OptionalValue.of(this), 218  players.toArray(String[]::new) 219  ); 220  } 221  } 222  223 }