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";
/**
* 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

View File

@ -37,7 +37,6 @@ public class ClientHandler implements Runnable {
public ServerPinger serverPinger;
public static HashSet<ClientHandler> connectedClients = new HashSet<>();
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<>();
/**
@ -81,10 +80,6 @@ public class ClientHandler implements Runnable {
return connectedClients;
}
public static HashSet<ClientHandler> getLobby() {
return lobby;
}
public static HashSet<ClientHandler> 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) {

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.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");

View File

@ -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.

View File

@ -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.
*/
}
}