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 }