Coverage Summary for Class: LobbyServer (it.polimi.ingsw.Server)
| Class | Method, % | Branch, % | Line, % |
|---|---|---|---|
| LobbyServer | 0% (0/6) | 0% (0/40) | 0% (0/138) |
| LobbyServer$1 | 0% (0/1) | 0% (0/1) | |
| LobbyServer$State | 0% (0/2) | 0% (0/5) | |
| Total | 0% (0/9) | 0% (0/40) | 0% (0/144) |
1 package it.polimi.ingsw.Server; 2 3 import it.polimi.ingsw.Controller.Actions.PlayerAction; 4 import it.polimi.ingsw.Exceptions.Input.InputValidationException; 5 import it.polimi.ingsw.Exceptions.Operation.OperationException; 6 import it.polimi.ingsw.Logger; 7 import it.polimi.ingsw.Network.SocketWrapper; 8 import it.polimi.ingsw.Server.Messages.Events.ClientEvent; 9 import it.polimi.ingsw.Server.Messages.Events.Internal.*; 10 import it.polimi.ingsw.Server.Messages.Events.Requests.*; 11 import it.polimi.ingsw.Server.Messages.ServerResponses.*; 12 import it.polimi.ingsw.Server.Messages.ServerResponses.SupportStructures.LobbyInfo; 13 14 import java.io.IOException; 15 import java.util.*; 16 import java.util.concurrent.ArrayBlockingQueue; 17 import java.util.concurrent.BlockingQueue; 18 import java.util.concurrent.ConcurrentHashMap; 19 import java.util.function.Predicate; 20 21 /** 22 * Handler of game events, responsible for communication of game related information with a single client. 23 */ 24 public class LobbyServer implements Runnable { 25 protected static final Map<UUID, Lobby> lobbyMap = new ConcurrentHashMap<>(); 26 private static final Set<String> connectedNicknames = new HashSet<>(); 27 private final SocketWrapper sw; 28 private final BlockingQueue<ClientEvent> eventQueue; 29 30 /** 31 * Creates the server around a {@link SocketWrapper} 32 * 33 * @param sw the socket to use to communicate with the client 34 */ 35 private LobbyServer(SocketWrapper sw) { 36 this.sw = sw; 37 this.eventQueue = new ArrayBlockingQueue<>(10); 38 } 39 40 /** 41 * Start a server on the provided socket wrapper 42 * 43 * @param socketWrapper the wrapped connection to the client of the server 44 */ 45 public static void spawn(SocketWrapper socketWrapper) { 46 LobbyServer lobbyServer = new LobbyServer(socketWrapper); 47 new Thread(lobbyServer).start(); 48 SocketListener.subscribe(socketWrapper, lobbyServer.getEventQueue()); 49 } 50 51 /** 52 * Get the event queue this server listens on 53 * 54 * @return the {@link BlockingQueue<ClientEvent>} linked to this server 55 */ 56 private BlockingQueue<ClientEvent> getEventQueue() { 57 return this.eventQueue; 58 } 59 60 /** 61 * When run, polls for events from the client and sends appropriate responses while handling interactions with the game 62 */ 63 @Override 64 public void run() { 65 String nickname = "unknown"; 66 int playerID = -1; 67 Lobby currentLobby = null; 68 State state = State.ACCEPT_PHASE; 69 while (true) { 70 try { 71 ClientEvent event = this.eventQueue.take(); 72 Logger.info("Lobby server received a new Event: " + event.getClass()); 73 if (event instanceof SocketClosedEvent) { 74 if (currentLobby != null) { 75 currentLobby.disconnectPlayer(nickname); 76 } 77 synchronized (connectedNicknames) { 78 connectedNicknames.remove(nickname); 79 } 80 Logger.info("Lobby server was closed for player: " + 81 nickname + 82 " on address " + 83 this.sw.getInetAddress()); 84 return; 85 } else { 86 switch (state) { 87 case ACCEPT_PHASE -> { 88 if (event instanceof DeclarePlayerRequest castedEvent) { 89 nickname = castedEvent.getNickname(); 90 synchronized (connectedNicknames) { 91 if (connectedNicknames.contains(nickname)) { 92 sw.sendMessage(LobbyServerAccept.fail()); 93 } else { 94 connectedNicknames.add(nickname); 95 List<LobbyInfo> publicLobbies = lobbyMap.values().stream() 96 .filter(Lobby::isPublic) 97 .filter(Predicate.not(Lobby::isGameInProgress)) 98 .map(LobbyInfo::new) 99 .toList(); 100 sw.sendMessage(LobbyServerAccept.success(publicLobbies)); 101 state = State.REDIRECT_PHASE; 102 } 103 } 104 } else { 105 sw.sendMessage(new InvalidRequest()); 106 } 107 } 108 case REDIRECT_PHASE -> { 109 // redirect phase: wait for valid lobby action 110 // either: 111 // - create 112 // - join or rejoin 113 switch (event) { 114 case CreateLobbyRequest castedEvent -> { 115 if (castedEvent.getMaxPlayers() < 1 || castedEvent.getMaxPlayers() > 4) { 116 sw.sendMessage(LobbyConnected.fail()); 117 break; 118 } 119 UUID lobbyID = generateUUID(); 120 currentLobby = new Lobby( 121 lobbyID, 122 castedEvent.isPublic(), 123 castedEvent.getMaxPlayers(), 124 nickname 125 ); 126 currentLobby.addPlayer(nickname, this.getEventQueue()); 127 lobbyMap.put(lobbyID, currentLobby); 128 state = State.GAME_START_PHASE; 129 sw.sendMessage(LobbyConnected.success(lobbyID, currentLobby.getAdmin())); 130 } 131 case ConnectLobbyRequest castedEvent -> { 132 UUID lobbyID = castedEvent.getCode(); 133 if (!lobbyMap.containsKey(lobbyID) || !lobbyMap.get(lobbyID).addPlayer(nickname, this.getEventQueue())) { 134 sw.sendMessage(LobbyConnected.fail()); 135 break; 136 } 137 currentLobby = lobbyMap.get(lobbyID); 138 sw.sendMessage(LobbyConnected.success(lobbyID, currentLobby.getAdmin())); 139 state = State.GAME_START_PHASE; 140 } 141 case default -> sw.sendMessage(new InvalidRequest()); 142 } 143 } 144 case GAME_START_PHASE -> { 145 // wait phase: wait for valid lobby action 146 // either: 147 // - start (only from admin) 148 // - start (as admin event reaction) 149 switch (event) { 150 case LobbyClosedEvent ignored -> { 151 currentLobby = null; 152 state = State.REDIRECT_PHASE; 153 sw.sendMessage(new LobbyClosed()); 154 } 155 case ClientConnectEvent clientConnectedEvent -> 156 sw.sendMessage(new ClientConnected(clientConnectedEvent.getNickname(), clientConnectedEvent.getPlayers())); 157 case ClientDisconnectEvent clientDisconnectedEvent -> 158 sw.sendMessage(new ClientDisconnected(clientDisconnectedEvent.getNickname(), clientDisconnectedEvent.getPlayers())); 159 case StartGameRequest castedEvent -> { 160 if (!currentLobby.getAdmin().equals(nickname)) { 161 sw.sendMessage(GameInit.fail("Only the admin of the lobby can start the game.")); 162 break; 163 } 164 if (!currentLobby.isLobbyFull()) { 165 sw.sendMessage(GameInit.fail("The lobby has not been filled")); 166 break; 167 } 168 try { 169 currentLobby.startGame(castedEvent.getGameMode()); 170 } catch (InputValidationException e) { 171 sw.sendMessage(GameInit.fail(e.getMessage())); 172 break; 173 } 174 // code executes only when a gameLobby was created 175 sw.sendMessage(GameInit.success()); 176 } 177 case GameStartEvent gameStartEvent -> { 178 state = State.GAME_IN_PROGRESS_PHASE; 179 playerID = gameStartEvent.nickToID().get(nickname); 180 sw.sendMessage(new GameStarted()); 181 } 182 case default -> sw.sendMessage(new InvalidRequest()); 183 } 184 } 185 case GAME_IN_PROGRESS_PHASE -> { 186 // wait phase: wait for valid lobby action 187 // either: 188 // - start (only from admin) 189 // - start (as admin event reaction) 190 switch (event) { 191 case LobbyClosedEvent ignored -> { 192 currentLobby = null; 193 state = State.REDIRECT_PHASE; 194 sw.sendMessage(new LobbyClosed()); 195 } 196 case ModelUpdateEvent modelUpdateEvent -> 197 sw.sendMessage(new ModelUpdated(modelUpdateEvent.getModel())); 198 case GameOverEvent gameOverEvent -> { 199 sw.sendMessage(new GameOver(gameOverEvent.winners())); 200 currentLobby.close(); 201 } 202 case PlayerActionRequest playerActionRequest -> { 203 try { 204 PlayerAction pa = playerActionRequest.getAction(); 205 if (pa.getPlayerBoardID() == playerID) { 206 PlayerActionFeedback feedback; 207 try { 208 currentLobby.executeAction(pa); 209 feedback = PlayerActionFeedback.success(); 210 } catch (InputValidationException e) { 211 feedback = PlayerActionFeedback.fail(e.getMessage()); 212 } 213 sw.sendMessage(feedback); 214 } else { 215 sw.sendMessage(PlayerActionFeedback.fail("You shall not impersonate others.")); 216 } 217 } catch (OperationException e) { 218 Logger.severe("Supposedly unreachable statement was reached:\n" + e.getMessage()); 219 sw.sendMessage(PlayerActionFeedback.fail(e.getMessage())); 220 throw new RuntimeException(e); 221 } 222 } 223 case ClientDisconnectEvent clientDisconnectedEvent -> 224 sw.sendMessage(new ClientDisconnected(clientDisconnectedEvent.getNickname(), clientDisconnectedEvent.getPlayers())); 225 case default -> sw.sendMessage(new InvalidRequest()); 226 } 227 } 228 } 229 } 230 } catch (Exception e) { 231 Logger.severe(e.getMessage()); 232 try { 233 this.sw.close(); 234 } catch (IOException ex) { 235 throw new RuntimeException(ex); 236 } 237 } 238 } 239 } 240 241 /** 242 * generates a unique UUID for a lobby 243 * 244 * @return a not yet in use UUID for the lobby 245 */ 246 private static UUID generateUUID() { 247 UUID id = UUID.randomUUID(); 248 while (lobbyMap.containsKey(id)) { 249 id = UUID.randomUUID(); 250 } 251 return id; 252 } 253 254 /** 255 * Represents the various stages of the connection between client and server 256 */ 257 private enum State { 258 /** 259 * client has not identified itself yet 260 */ 261 ACCEPT_PHASE, 262 /** 263 * client now has a name, but is not part of a lobby 264 */ 265 REDIRECT_PHASE, 266 /** 267 * client is part of a lobby, waiting for the game to start 268 */ 269 GAME_START_PHASE, 270 /** 271 * client is now in a game, playing 272 */ 273 GAME_IN_PROGRESS_PHASE, 274 } 275 }