Implemented (almost) all required Lobby functionality. Also added Client_01 and CLient_02 for testing inside intelliJ

This commit is contained in:
Jonas 2022-04-11 23:05:39 +02:00
parent 33cbb4e57d
commit 01b08e41b8
7 changed files with 200 additions and 88 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -83,13 +83,15 @@ public class Protocol {
public static final String createNewLobby = "CRTLB"; 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 * Represents a clients' request for a list of lobbies
*/ */
public static final String listLobbies = "LISTL"; 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, * Client requests to join the Lobby with the given number, for example,
* {@code JOINL$2} means the client wants to join lobby 2. * {@code JOINL$2} means the client wants to join lobby 2.
@ -97,6 +99,12 @@ public class Protocol {
*/ */
public static final String joinLobby = "JOINL"; 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 //SERVER TO CLIENT COMMANDS

View File

@ -37,7 +37,6 @@ public class ClientHandler implements Runnable {
public ServerPinger serverPinger; public ServerPinger serverPinger;
public static HashSet<ClientHandler> connectedClients = new HashSet<>(); public static HashSet<ClientHandler> connectedClients = new HashSet<>();
public static HashSet<ClientHandler> disconnectedClients = new HashSet<>(); //todo: implement re-connection public static HashSet<ClientHandler> disconnectedClients = new HashSet<>(); //todo: implement re-connection
public static HashSet<ClientHandler> lobby = new HashSet<>();
public static HashSet<ClientHandler> ghostClients = new HashSet<>(); public static HashSet<ClientHandler> ghostClients = new HashSet<>();
/** /**
@ -81,10 +80,6 @@ public class ClientHandler implements Runnable {
return connectedClients; return connectedClients;
} }
public static HashSet<ClientHandler> getLobby() {
return lobby;
}
public static HashSet<ClientHandler> getGhostClients() { public static HashSet<ClientHandler> getGhostClients() {
return ghostClients; return ghostClients;
} }
@ -130,7 +125,7 @@ public class ClientHandler implements Runnable {
public void changeUsername(String newName) { public void changeUsername(String newName) {
String helper = this.getClientUserName(); String helper = this.getClientUserName();
this.clientUserName = nameDuplicateChecker.checkName(newName); 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) { public void setUsernameOnLogin(String name) {
this.clientUserName = nameDuplicateChecker.checkName(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 * @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) { for (ClientHandler client : connectedClients) {
client.sendMsgToClient(Protocol.printToClientChat + "$" + clientUserName + ": " + msg); 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 / * 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 * 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. * console.
* todo: this could be static! * @param msg the Message to be broadcast. Does not have to be protocol-formatted, this method will take care of that.
* @param msg the Message to be broadcast
*/ */
public void broadcastAnnouncement(String msg) { public static void broadcastAnnouncementToAll(String msg) {
System.out.println(msg); System.out.println(msg);
for (ClientHandler client : connectedClients) { for (ClientHandler client : connectedClients) {
client.sendMsgToClient(Protocol.printToClientConsole + "$" + msg); 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 * 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! * 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 * 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 * 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() { public void removeClientOnConnectionLoss() {
connectedClients.remove(this); connectedClients.remove(this);
disconnectClient(); 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); 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. * removeClientOnConnectionLoss() if the client has to be removed due to a connection loss.
*/ */
public void removeClientOnLogout() { public void removeClientOnLogout() {
broadcastAnnouncement(getClientUserName() + " has left the server."); broadcastAnnouncementToAll(getClientUserName() + " has left the server.");
sendMsgToClient(Protocol.serverConfirmQuit); //todo: protocol sendMsgToClient(Protocol.serverConfirmQuit);
connectedClients.remove(this); connectedClients.remove(this);
serverData.removeClientFromSetOfAllClients(this); //todo: delete?
serverData.removeClientFromLobby(this); //todo: do this via Lobby class directly.
disconnectClient(); disconnectClient();
} }
@ -224,11 +285,8 @@ public class ClientHandler implements Runnable {
if (Lobby.clientIsInLobby(this) == -1) { if (Lobby.clientIsInLobby(this) == -1) {
Lobby newGame = new Lobby(this); Lobby newGame = new Lobby(this);
serverData.addLobbyToListOfAllLobbies(newGame); serverData.addLobbyToListOfAllLobbies(newGame);
broadcastAnnouncement("New lobby with ID: " + newGame.getLobbyID()
+ " created by " + this.getClientUserName());
} else { } else {
sendMsgToClient(Protocol.printToClientConsole + sendAnnouncementToClient("You are already in lobby nr. " + Lobby.clientIsInLobby(this));
"$You are already in lobby nr. " + Lobby.clientIsInLobby(this));
} }
} }
@ -242,42 +300,69 @@ public class ClientHandler implements Runnable {
if (l != null) { if (l != null) {
l.addPlayer(this); l.addPlayer(this);
} else { } else {
LOGGER.debug(getClientUserName() + " tried to join Lobby nr. " sendAnnouncementToClient("Invalid Lobby nr.");
+ i + " but that doesn't exist."); 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 * Lists all lobbies and their members, along with players outside lobbies
* Lobby.getIdAndAdminForList() to build a formated list for the client. used in * to this clientHandler's client as an announcement.
* JServerProtocolParser.
* //TODO Still does not work properly!
*/ */
public void listAllLobbiesToClient() { public void listLobbies() {
StringBuilder response = new StringBuilder(); if (Lobby.lobbies.isEmpty()) {
response.append(Protocol.listLobbies); sendAnnouncementToClient("No open Lobbies.");
response.append("$");
if (serverData.getAllLobbies().isEmpty()) {
response.append("There are currently no open lobbies");
LOGGER.debug("No open lobbies");
} else { } else {
for (Lobby l : serverData.getAllLobbies()) { for (Lobby l : Lobby.lobbies) {
response.append(l.getIdAndAdminAndFormat()); 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( boolean helper = false; //used to print "Clients not in lobbies" only once, if needed.
"RESPONSE TO LISTL: " + response.toString() + " requested by: " + this.clientUserName); for (ClientHandler c: connectedClients) {
try { if (Lobby.clientIsInLobby(c) == -1) {
out.write(response.toString()); if (!helper) {
out.newLine(); helper = true;
out.flush(); sendAnnouncementToClient("Clients not in lobbies:");
} catch (IOException e) { }
LOGGER.debug(e.getMessage()); 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. * 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(); socket = this.getSocket();
in = this.getIn(); in = this.getIn();
out = this.getOut(); out = this.getOut();
serverData.removeClientFromSetOfAllClients(this);
serverData.removeClientFromLobby(this);
try { try {
Thread.sleep(100); Thread.sleep(100);
if (in != null) { if (in != null) {

View File

@ -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.BudaLogConfig;
import ch.unibas.dmi.dbis.cs108.sebaschi.Lobby;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol;
@ -36,7 +35,7 @@ public class JServerProtocolParser {
} }
switch (header) { switch (header) {
case Protocol.chatMsgToAll: case Protocol.chatMsgToAll:
h.broadcastChatMessage(msg.substring(6)); h.broadcastChatMessageToLobby(msg.substring(6));
break; break;
case Protocol.clientLogin: case Protocol.clientLogin:
h.setLoggedIn(true); h.setLoggedIn(true);
@ -71,8 +70,13 @@ public class JServerProtocolParser {
h.createNewLobby(); h.createNewLobby();
break; break;
case Protocol.listLobbies: case Protocol.listLobbies:
h.listAllLobbiesToClient(); h.listLobbies();
LOGGER.debug(Protocol.listLobbies + " command received from: " + h.getClientUserName()); break;
case Protocol.listPlayersInLobby:
h.listPlayersInLobby();
break;
case Protocol.leaveLobby:
h.leaveLobby();
break; break;
default: default:
System.out.println("Received unknown command"); System.out.println("Received unknown command");

View File

@ -13,6 +13,9 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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 * 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 * 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. * they need the same data. This Class is used to query for information in collections.

View File

@ -1,16 +1,15 @@
package ch.unibas.dmi.dbis.cs108.sebaschi; package ch.unibas.dmi.dbis.cs108.sebaschi;
import ch.unibas.dmi.dbis.cs108.BudaLogConfig; 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 ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler;
import java.util.HashSet; import java.util.HashSet;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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 * 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? * TODO: is all data in here or should GameSessionData be used to collect all relevant data?
*/ */
public class Lobby { public class Lobby {
@ -22,7 +21,7 @@ public class Lobby {
private static final int MAX_NO_OF_CLIENTS = 6; private static final int MAX_NO_OF_CLIENTS = 6;
//TODO //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 * 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.admin = admin;
this.lobbyClients.add(admin); this.lobbyClients.add(admin);
lobbies.add(this); lobbies.add(this);
this.lobbyID = lobbies.size(); int helper = 1;
admin.broadcastAnnouncement("New Lobby created by " + admin.getClientUserName() + while (getLobbyFromID(helper) != null) {
helper++;
}
this.lobbyID = helper;
admin.broadcastAnnouncementToAll("New Lobby created by " + admin.getClientUserName() +
". This lobby's ID: " + this.lobbyID); ". This lobby's ID: " + this.lobbyID);
} }
@ -80,22 +83,6 @@ public class Lobby {
return this.lobbyClients; 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. * Returns the lobby with the desired LobbyID.
* For example, getLobbyFromID(5) returns the lobby whose LobbyID is 5. * 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 (lobbyClients.size() < MAX_NO_OF_CLIENTS) {
if (clientIsInLobby(client) == -1) { if (clientIsInLobby(client) == -1) {
lobbyClients.add(client); lobbyClients.add(client);
client.broadcastAnnouncement(client.getClientUserName() + " has joined lobby " + this.getLobbyID()); client.broadcastAnnouncementToAll(client.getClientUserName() + " has joined lobby " + this.getLobbyID());
LOGGER.debug(client.getClientUserName() + " has been added to Lobby with ID: " + lobbyID //LOGGER.debug(client.getClientUserName() + " has been added to Lobby with ID: " + lobbyID
+ ". Current number of lobbyClients in this lobby: " + lobbyClients.size()); // + ". Current number of lobbyClients in this lobby: " + lobbyClients.size());
return true; return true;
} else { } else {
client.sendMsgToClient(Protocol.printToClientConsole + "$You are already in lobby nr. " client.sendAnnouncementToClient("You are already in lobby nr. " + clientIsInLobby(client));
+ clientIsInLobby(client));
} }
} else { } else {
client.sendMsgToClient(Protocol.printToClientConsole + client.sendAnnouncementToClient("This lobby is full. Please try joining a different lobby or create a new lobby");
"$This lobby is full. Please try joining a different lobby or create a new lobby");
} }
return false; return false;
} }
@ -158,14 +143,28 @@ public class Lobby {
* @return true if a player was found and removed. Used for debugging. * @return true if a player was found and removed. Used for debugging.
*/ */
public synchronized boolean removePlayer(ClientHandler player) { 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); return this.getLobbyClients().remove(player);
} }
//todo: this here? /**
public void broadcastToLounge(String msg) { * Closes the lobby.
for (ClientHandler lounger : this.getLobbyClients()) { *
*/
} 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.
*/
} }
} }