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 }