From 01b08e41b8f6d695dc1daa580a5250db6b706b29 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 11 Apr 2022 23:05:39 +0200 Subject: [PATCH] Implemented (almost) all required Lobby functionality. Also added Client_01 and CLient_02 for testing inside intelliJ --- .../cs108/multiplayer/client/Client_01.java | 8 + .../cs108/multiplayer/client/Client_02.java | 7 + .../cs108/multiplayer/helpers/Protocol.java | 14 +- .../multiplayer/server/ClientHandler.java | 177 +++++++++++++----- .../server/JServerProtocolParser.java | 12 +- .../cs108/sebaschi/CentralServerData.java | 3 + .../unibas/dmi/dbis/cs108/sebaschi/Lobby.java | 67 ++++--- 7 files changed, 200 insertions(+), 88 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java new file mode 100644 index 0000000..bd2e499 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_01.java @@ -0,0 +1,8 @@ +package ch.unibas.dmi.dbis.cs108.multiplayer.client; + +public class Client_01 { + public static void main(String[] args) { + Client.main(args); + } + +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java new file mode 100644 index 0000000..23c9122 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/client/Client_02.java @@ -0,0 +1,7 @@ +package ch.unibas.dmi.dbis.cs108.multiplayer.client; + +public class Client_02 { + public static void main(String[] args) { + Client.main(args); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java index 5538819..73c0e32 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/helpers/Protocol.java @@ -83,13 +83,15 @@ public class Protocol { public static final String createNewLobby = "CRTLB"; /** - * TODO: implement in {@link ch.unibas.dmi.dbis.cs108.multiplayer.client.MessageFormatter} - * TODO: imlement in {@link ch.unibas.dmi.dbis.cs108.multiplayer.server.JServerProtocolParser} - * TODO: add the Servers reaction, i.e. sending a list of lobbies. * Represents a clients' request for a list of lobbies */ public static final String listLobbies = "LISTL"; + /** + * Represents a clients' request for a list of all players within the lobby. + */ + public static final String listPlayersInLobby = "LISTP"; + /** * Client requests to join the Lobby with the given number, for example, * {@code JOINL$2} means the client wants to join lobby 2. @@ -97,6 +99,12 @@ public class Protocol { */ public static final String joinLobby = "JOINL"; + /** + * Client requests to leave whatever lobby they're in. + */ + public static final String leaveLobby = "LEAVL"; + + //SERVER TO CLIENT COMMANDS diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java index 7071a51..def8fef 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/ClientHandler.java @@ -37,7 +37,6 @@ public class ClientHandler implements Runnable { public ServerPinger serverPinger; public static HashSet connectedClients = new HashSet<>(); public static HashSet disconnectedClients = new HashSet<>(); //todo: implement re-connection - public static HashSet lobby = new HashSet<>(); public static HashSet ghostClients = new HashSet<>(); /** @@ -81,10 +80,6 @@ public class ClientHandler implements Runnable { return connectedClients; } - public static HashSet getLobby() { - return lobby; - } - public static HashSet getGhostClients() { return ghostClients; } @@ -130,7 +125,7 @@ public class ClientHandler implements Runnable { public void changeUsername(String newName) { String helper = this.getClientUserName(); this.clientUserName = nameDuplicateChecker.checkName(newName); - broadcastAnnouncement(helper + " has changed their nickname to " + clientUserName); + broadcastAnnouncementToAll(helper + " has changed their nickname to " + clientUserName); } /** @@ -142,15 +137,53 @@ public class ClientHandler implements Runnable { */ public void setUsernameOnLogin(String name) { this.clientUserName = nameDuplicateChecker.checkName(name); - broadcastAnnouncement(clientUserName + " has joined the Server"); + broadcastAnnouncementToAll(clientUserName + " has joined the Server"); + + /* The following lines could be un-commented to provide Lobby information on login + sendAnnouncementToClient("Welcome, " + clientUserName + "!"); + sendAnnouncementToClient("Here are the currently open Lobbies:"); + listLobbies(); + */ } /** - * Broadcasts a chat Message to all active clients in the form "Username: @msg" - * + * Returns the Lobby this ClientHandler is in. If this ClientHandler is not in a Lobby, + * it returns null. + * @return + */ + public Lobby getLobby() { + try { + Lobby l = Lobby.getLobbyFromID(Lobby.clientIsInLobby(this)); + return l; + } catch (Exception e) { + return null; + } + } + + /** + * Broadcasts a chat Message to all clients in the same lobby in the form "Username: @msg" + * If this client isn't in a lobby, it instead defers the message to broadcastChatMessageToAll * @param msg the Message to be broadcast */ - public void broadcastChatMessage(String msg) { + public void broadcastChatMessageToLobby(String msg) { + Lobby l = getLobby(); + if (l != null) { + for (ClientHandler client : l.getLobbyClients()) { + client.sendMsgToClient(Protocol.printToClientChat + "$" + clientUserName + ": " + msg); + } + } else { + LOGGER.debug("Could not send chat message; probably client isn't in a lobby." + + "Will broadcast across all lobbies now."); + broadcastChatMessageToAll(msg); + } + } + + /** + * Broadcasts a chat Message to all clients across all lobbies & clients who are not in a lobby + * in the form "Username: @msg" + * @param msg the Message to be broadcast + */ + public void broadcastChatMessageToAll(String msg) { for (ClientHandler client : connectedClients) { client.sendMsgToClient(Protocol.printToClientChat + "$" + clientUserName + ": " + msg); } @@ -159,18 +192,38 @@ public class ClientHandler implements Runnable { /** * Broadcasts a non-chat Message to all active clients. This can be used for server messages / * announcements rather than chat messages. The message will be printed to the user exactly as it - * is given to this method. Unlike broadcastChatMessage, it will also be printed onto the server + * is given to this method. Unlike eg. broadcastChatMessageToLobby, it will also be printed onto the server * console. - * todo: this could be static! - * @param msg the Message to be broadcast + * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method will take care of that. */ - public void broadcastAnnouncement(String msg) { + public static void broadcastAnnouncementToAll(String msg) { System.out.println(msg); for (ClientHandler client : connectedClients) { client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); } } + /** + * Broadcasts a non-chat Message to all clients in the same lobby. This can be used for server messages / + * announcements rather than chat messages. The message will be printed to the user exactly as it + * is given to this method. The announcement will not be printed on the server console. + * + * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method will take care of that. + */ + public void broadcastAnnouncementToLobby(String msg) { + Lobby l = getLobby(); + if (l != null) { + //System.out.println(msg); we can-comment this if you want lobby-announcements to print on the server console as well. + for (ClientHandler client : l.getLobbyClients()) { + client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); + } + } else { + LOGGER.debug("Could not send announcements; probably client isn't in a lobby." + + "Will broadcast across all lobbies now."); + broadcastAnnouncementToAll(msg); + } + } + /** * Sends a given message to client. The message has to already be protocol-formatted. ALL * communication with the client has to happen via this method! @@ -190,6 +243,10 @@ public class ClientHandler implements Runnable { } } + public void sendAnnouncementToClient(String msg) { + sendMsgToClient(Protocol.printToClientConsole + "$" + msg); + } + /** * Removes & disconnects the client. To be used if a severe connection loss is detected (i.e. if * trying to send / receive a message throws an exception, not just if ping-pong detects a @@ -200,7 +257,9 @@ public class ClientHandler implements Runnable { public void removeClientOnConnectionLoss() { connectedClients.remove(this); disconnectClient(); - broadcastAnnouncement(getClientUserName() + " has left the server due to a connection loss."); + serverData.removeClientFromSetOfAllClients(this); //todo: delete? + serverData.removeClientFromLobby(this); //todo: do this via Lobby class directly. + broadcastAnnouncementToAll(getClientUserName() + " has left the server due to a connection loss."); disconnectedClients.add(this); } @@ -210,9 +269,11 @@ public class ClientHandler implements Runnable { * removeClientOnConnectionLoss() if the client has to be removed due to a connection loss. */ public void removeClientOnLogout() { - broadcastAnnouncement(getClientUserName() + " has left the server."); - sendMsgToClient(Protocol.serverConfirmQuit); //todo: protocol + broadcastAnnouncementToAll(getClientUserName() + " has left the server."); + sendMsgToClient(Protocol.serverConfirmQuit); connectedClients.remove(this); + serverData.removeClientFromSetOfAllClients(this); //todo: delete? + serverData.removeClientFromLobby(this); //todo: do this via Lobby class directly. disconnectClient(); } @@ -224,11 +285,8 @@ public class ClientHandler implements Runnable { if (Lobby.clientIsInLobby(this) == -1) { Lobby newGame = new Lobby(this); serverData.addLobbyToListOfAllLobbies(newGame); - broadcastAnnouncement("New lobby with ID: " + newGame.getLobbyID() - + " created by " + this.getClientUserName()); } else { - sendMsgToClient(Protocol.printToClientConsole + - "$You are already in lobby nr. " + Lobby.clientIsInLobby(this)); + sendAnnouncementToClient("You are already in lobby nr. " + Lobby.clientIsInLobby(this)); } } @@ -242,42 +300,69 @@ public class ClientHandler implements Runnable { if (l != null) { l.addPlayer(this); } else { - LOGGER.debug(getClientUserName() + " tried to join Lobby nr. " - + i + " but that doesn't exist."); + sendAnnouncementToClient("Invalid Lobby nr."); + sendAnnouncementToClient("use LISTL to list lobbies"); } } + public void leaveLobby() { + for (Lobby l : Lobby.lobbies) { + boolean b = l.removePlayer(this); + if (b) broadcastAnnouncementToAll(this.getClientUserName() + " has left lobby nr. " + l.getLobbyID()); + } + } + + /** - * Creates a list of all lobbies to send to client after LISTL command. Uses - * Lobby.getIdAndAdminForList() to build a formated list for the client. used in - * JServerProtocolParser. - * //TODO Still does not work properly! + * Lists all lobbies and their members, along with players outside lobbies + * to this clientHandler's client as an announcement. */ - public void listAllLobbiesToClient() { - StringBuilder response = new StringBuilder(); - response.append(Protocol.listLobbies); - response.append("$"); - if (serverData.getAllLobbies().isEmpty()) { - response.append("There are currently no open lobbies"); - LOGGER.debug("No open lobbies"); + public void listLobbies() { + if (Lobby.lobbies.isEmpty()) { + sendAnnouncementToClient("No open Lobbies."); } else { - for (Lobby l : serverData.getAllLobbies()) { - response.append(l.getIdAndAdminAndFormat()); + for (Lobby l : Lobby.lobbies) { + sendAnnouncementToClient("Lobby nr. " + l.getLobbyID() + ":"); + for (ClientHandler c : l.getLobbyClients()) { + if (c.equals(l.getAdmin())) { + sendAnnouncementToClient(" -" + c.getClientUserName() + " (admin)"); + } else { + sendAnnouncementToClient(" -" + c.getClientUserName()); + } + } } } - LOGGER.debug( - "RESPONSE TO LISTL: " + response.toString() + " requested by: " + this.clientUserName); - try { - out.write(response.toString()); - out.newLine(); - out.flush(); - } catch (IOException e) { - LOGGER.debug(e.getMessage()); + boolean helper = false; //used to print "Clients not in lobbies" only once, if needed. + for (ClientHandler c: connectedClients) { + if (Lobby.clientIsInLobby(c) == -1) { + if (!helper) { + helper = true; + sendAnnouncementToClient("Clients not in lobbies:"); + } + sendAnnouncementToClient(" -" + c.getClientUserName()); + } + } + if (!helper) { + sendAnnouncementToClient("No clients outside of lobbies"); } - } + public void listPlayersInLobby() { + Lobby l = getLobby(); + if (l != null) { + sendAnnouncementToClient("Players in lobby nr. " + l.getLobbyID() + ":"); + for (ClientHandler c : l.getLobbyClients()) { + if (c.equals(l.getAdmin())) { + sendAnnouncementToClient(" -" + c.getClientUserName() + " (admin)"); + } else { + sendAnnouncementToClient(" -" + c.getClientUserName()); + } + } + } else { + sendAnnouncementToClient("You are not in a Lobby."); + } + } /** * Closes the client's socket, in, and out. and removes from global list of clients. @@ -286,8 +371,6 @@ public class ClientHandler implements Runnable { socket = this.getSocket(); in = this.getIn(); out = this.getOut(); - serverData.removeClientFromSetOfAllClients(this); - serverData.removeClientFromLobby(this); try { Thread.sleep(100); if (in != null) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java index 19df647..e12d3ce 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/multiplayer/server/JServerProtocolParser.java @@ -2,7 +2,6 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.server; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; -import ch.unibas.dmi.dbis.cs108.sebaschi.Lobby; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; @@ -36,7 +35,7 @@ public class JServerProtocolParser { } switch (header) { case Protocol.chatMsgToAll: - h.broadcastChatMessage(msg.substring(6)); + h.broadcastChatMessageToLobby(msg.substring(6)); break; case Protocol.clientLogin: h.setLoggedIn(true); @@ -71,8 +70,13 @@ public class JServerProtocolParser { h.createNewLobby(); break; case Protocol.listLobbies: - h.listAllLobbiesToClient(); - LOGGER.debug(Protocol.listLobbies + " command received from: " + h.getClientUserName()); + h.listLobbies(); + break; + case Protocol.listPlayersInLobby: + h.listPlayersInLobby(); + break; + case Protocol.leaveLobby: + h.leaveLobby(); break; default: System.out.println("Received unknown command"); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java index cfb895a..94df874 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/CentralServerData.java @@ -13,6 +13,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** + * todo: this class is not meaningfully used now, we could probably delete it? + * + * * This Class Represents an Object containing different Maps, Lists and Sets wherein a server object * can find all needed data. An instance of this object can also be passed to other class-objects if * they need the same data. This Class is used to query for information in collections. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java index 5ee9a07..18ce3cf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/sebaschi/Lobby.java @@ -1,16 +1,15 @@ package ch.unibas.dmi.dbis.cs108.sebaschi; import ch.unibas.dmi.dbis.cs108.BudaLogConfig; -import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler; import java.util.HashSet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** - * Use: If a client sends a CRTGM command the server should create a lobby with the client as admin. + * Use: If a client sends a CRTLB command the server should create a lobby with the client as admin. * In this state, up to 5 other clients (so 6 in total) are able to join this lobby. Once the admin - * feels like it, he can start a game. + * feels like it, they can start a game. * TODO: is all data in here or should GameSessionData be used to collect all relevant data? */ public class Lobby { @@ -22,7 +21,7 @@ public class Lobby { private static final int MAX_NO_OF_CLIENTS = 6; //TODO - CentralServerData serverData; + CentralServerData serverData; //todo: do we need this? /** * The Person who created the game and can configure it and decide to start once enough lobbyClients @@ -48,8 +47,12 @@ public class Lobby { this.admin = admin; this.lobbyClients.add(admin); lobbies.add(this); - this.lobbyID = lobbies.size(); - admin.broadcastAnnouncement("New Lobby created by " + admin.getClientUserName() + + int helper = 1; + while (getLobbyFromID(helper) != null) { + helper++; + } + this.lobbyID = helper; + admin.broadcastAnnouncementToAll("New Lobby created by " + admin.getClientUserName() + ". This lobby's ID: " + this.lobbyID); } @@ -80,22 +83,6 @@ public class Lobby { return this.lobbyClients; } - /** - * Builds a message for the LISTL command. - * - * @return a string formatted for the clients convenients. - */ - public String getIdAndAdminAndFormat() { - StringBuilder response = new StringBuilder(); - response.append("Lobby ID: "); - response.append(this.lobbyID); - response.append(" Admin Username: "); - response.append(getAdmin().getClientUserName()); - response.append(System.lineSeparator()); - LOGGER.info(response.toString()); - return response.toString(); - } - /** * Returns the lobby with the desired LobbyID. * For example, getLobbyFromID(5) returns the lobby whose LobbyID is 5. @@ -134,17 +121,15 @@ public class Lobby { if (lobbyClients.size() < MAX_NO_OF_CLIENTS) { if (clientIsInLobby(client) == -1) { lobbyClients.add(client); - client.broadcastAnnouncement(client.getClientUserName() + " has joined lobby " + this.getLobbyID()); - LOGGER.debug(client.getClientUserName() + " has been added to Lobby with ID: " + lobbyID - + ". Current number of lobbyClients in this lobby: " + lobbyClients.size()); + client.broadcastAnnouncementToAll(client.getClientUserName() + " has joined lobby " + this.getLobbyID()); + //LOGGER.debug(client.getClientUserName() + " has been added to Lobby with ID: " + lobbyID + // + ". Current number of lobbyClients in this lobby: " + lobbyClients.size()); return true; } else { - client.sendMsgToClient(Protocol.printToClientConsole + "$You are already in lobby nr. " - + clientIsInLobby(client)); + client.sendAnnouncementToClient("You are already in lobby nr. " + clientIsInLobby(client)); } } else { - client.sendMsgToClient(Protocol.printToClientConsole + - "$This lobby is full. Please try joining a different lobby or create a new lobby"); + client.sendAnnouncementToClient("This lobby is full. Please try joining a different lobby or create a new lobby"); } return false; } @@ -158,14 +143,28 @@ public class Lobby { * @return true if a player was found and removed. Used for debugging. */ public synchronized boolean removePlayer(ClientHandler player) { + //if the player who leaves the lobby is the admin, the lobby is closed. + if (player.equals(getAdmin())) { + closeLobby(); + } return this.getLobbyClients().remove(player); } - //todo: this here? - public void broadcastToLounge(String msg) { - for (ClientHandler lounger : this.getLobbyClients()) { - - } + /** + * Closes the lobby. + * + */ + public void closeLobby() { + lobbies.remove(this); + ClientHandler.broadcastAnnouncementToAll("Lobby nr. " + this.getLobbyID() + " has been closed."); + /* + Todo: theoretically, this is enough to close a lobby. + ClientHandlers dont have to manually be removed from the lobby + since if the lobby is removed from the lobbies + hash set then e.g. clientIsInLobby will not be able to tell that these clients + are theoretically still in this lobby. However, in the future we might implement + removing the clients anyways. + */ } }