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 }