Merge remote-tracking branch 'origin/master'

This commit is contained in:
Seraina 2022-04-08 12:42:25 +02:00
commit 33d8f779a2
8 changed files with 185 additions and 40 deletions

View File

@ -4,6 +4,7 @@ import ch.unibas.dmi.dbis.cs108.BudaLogConfig;
import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ClientPinger; import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.ClientPinger;
import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol;
import java.net.Socket; import java.net.Socket;
import java.io.*; import java.io.*;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -36,7 +37,7 @@ public class Client {
systemName = "U.N. Owen"; systemName = "U.N. Owen";
} }
if (systemName == null) systemName = "U.N. Owen"; if (systemName == null) systemName = "U.N. Owen";
sendMsgToServer("LOGON$" + systemName); sendMsgToServer(Protocol.clientLogin + "$" + systemName);
clientPinger = new ClientPinger(this, this.socket); clientPinger = new ClientPinger(this, this.socket);
} catch (IOException e) { } catch (IOException e) {

View File

@ -1,6 +1,7 @@
package ch.unibas.dmi.dbis.cs108.multiplayer.client; package ch.unibas.dmi.dbis.cs108.multiplayer.client;
import ch.unibas.dmi.dbis.cs108.BudaLogConfig; import ch.unibas.dmi.dbis.cs108.BudaLogConfig;
import ch.unibas.dmi.dbis.cs108.multiplayer.helpers.Protocol;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -10,7 +11,7 @@ public class MessageFormatter {
/** /**
* Takes a given Message and reformats it to where the JServerProtocolParser.parse() method can * Takes a given Message and reformats it to where the JServerProtocolParser.parse() method can
* handle it (see Protocol.txt). May need to be redesigned once the games uses a GUI * handle it (see Protocol.java). May need to be redesigned once the games uses a GUI
* *
* @param msg the Messaged to be reformatted * @param msg the Messaged to be reformatted
* @return the reformatted message in the form HEADR$msg * @return the reformatted message in the form HEADR$msg
@ -27,7 +28,7 @@ public class MessageFormatter {
} }
switch (header) { switch (header) {
case "/c": case "/c":
stringBuilder.append("CHATA$"); stringBuilder.append(Protocol.chatMsgToAll + "$");
try { try {
s = msg.substring(3); s = msg.substring(3);
} catch (Exception e) { } catch (Exception e) {
@ -35,15 +36,15 @@ public class MessageFormatter {
} }
break; break;
case "/q": case "/q":
stringBuilder.append("QUITS$"); stringBuilder.append(Protocol.clientQuitRequest + "$");
s = ""; s = "";
break; break;
case "/n": case "/n":
stringBuilder.append("NAMEC$"); stringBuilder.append(Protocol.nameChange + "$");
try { try {
s = msg.substring(3); s = msg.substring(3);
} catch (Exception e) { } catch (Exception e) {
System.out.println("Well what do you want your name to be?"); s = "U.N. Owen";
} }
break; break;
default: default:

View File

@ -0,0 +1,66 @@
package ch.unibas.dmi.dbis.cs108.multiplayer.helpers;
/**
* This class is where the Protocol commands are saved as strings.
* The idea is that every class that uses protocol messages does not
* directly use e.g. "CHATA" in the code but rather uses
* Protocol.chatMsgToAll. This improves legibility as well as
* allowing for quick modification of Protocol messages if needed.
* Most importantly, the Protocol commands can be documented in this
* class, and then in every other class that uses protocol messages,
* documentation for a command can be viewed by hovering over the
* specific variable, rather than having to document everywhere.
*/
public class Protocol {
//BIDIRECTIONAL COMMANDS:
/**
* Ping-back message from client to server / server to client.
* To be sent upon receiving "CPING" / "SPING".
* The other party then registers this in its ClientPinger / ServerPinger
* thread.
*/
public static final String pingBack = "PINGB";
//CLIENT TO SERVER COMMANDS:
/**
* When the server receives this, it broadcasts a chat message
* to all clients. The message has to be given in the protocol
* message after {@code CHATA$}, for example the protocol message
* {@code CHATA$Hello everybody!}, if sent from the user named Poirot,
* will print {@code Poirot: Hello everybody!} to every connected
* client's chat console (note the absence / presence of spaces).
*/
public static final String chatMsgToAll = "CHATA";
/**
* The message sent by the client on login to set their name. For example,
* {@code LOGIN$Poirot} will use the clientHandler.setUsernameOnLogin() method
* to set this client's username to Poirot, and broadcast the announcement:
* {@code "Poirot has joined the Server"}. Also, it will set this clientHandler's
* loggedIn boolean to true, which could be used later to refuse access to users
* who haven't formally logged in using this command => //todo: shun non-logged-in users
*
*/
public static final String clientLogin = "LOGIN";
/**
* todo:doc
*
*/
public static final String nameChange = "NAMEC";
/**
* todo:doc
*/
public static final String pingFromClient = "CPING";
/**
* todo: doc
*/
public static final String clientQuitRequest = "QUITS";
}

View File

@ -19,8 +19,15 @@ public class ClientHandler implements Runnable {
private BufferedReader in; private BufferedReader in;
private Socket socket; private Socket socket;
private InetAddress ip; private InetAddress ip;
private
Scanner sc; /**
* notes if the client has formally logged in yet. If connecting through the normal Client class,
* the client is logged in automatically, if connecting though some external application, the
* client has to use the {@code Protocol.clientLogin} command.
*/
private boolean loggedIn;
private Scanner sc;
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
@ -38,6 +45,7 @@ public class ClientHandler implements Runnable {
this.socket = socket; this.socket = socket;
this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
this.in = new BufferedReader(new InputStreamReader((socket.getInputStream()))); this.in = new BufferedReader(new InputStreamReader((socket.getInputStream())));
this.loggedIn = false;
this.clientUserName = nameDuplicateChecker.checkName("U.N. Owen"); this.clientUserName = nameDuplicateChecker.checkName("U.N. Owen");
connectedClients.add(this); connectedClients.add(this);
serverPinger = new ServerPinger(socket, this); serverPinger = new ServerPinger(socket, this);
@ -73,7 +81,18 @@ public class ClientHandler implements Runnable {
return ghostClients; return ghostClients;
} }
//Setters public boolean isLoggedIn() {
return loggedIn;
}
public void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
}
//Setters:
public String getClientUserName() {
return clientUserName;
}
@Override @Override
@ -81,7 +100,7 @@ public class ClientHandler implements Runnable {
String msg; String msg;
while (socket.isConnected() && !socket.isClosed()) { while (socket.isConnected() && !socket.isClosed()) {
try { try {
msg = in.readLine(); //todo: here is where the server throws an exception when the client quits msg = in.readLine();
JServerProtocolParser.parse(msg, this); JServerProtocolParser.parse(msg, this);
} catch (IOException e) { } catch (IOException e) {
//e.printStackTrace(); //e.printStackTrace();
@ -92,9 +111,7 @@ public class ClientHandler implements Runnable {
} }
} }
public String getClientUserName() {
return clientUserName;
}
/** /**
* Lets the client change their username, if the username is already taken, a similar * Lets the client change their username, if the username is already taken, a similar
@ -148,12 +165,10 @@ public class ClientHandler implements Runnable {
/** 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!
* todo: check for exception if out is closed.
* @param msg the given message. Should already be protocol-formatted. * @param msg the given message. Should already be protocol-formatted.
*/ */
public void sendMsgToClient(String msg) { public void sendMsgToClient(String msg) {
try { try {
//todo: socket closed handling
out.write(msg); out.write(msg);
out.newLine(); out.newLine();
out.flush(); out.flush();

View File

@ -4,6 +4,7 @@ package ch.unibas.dmi.dbis.cs108.multiplayer.server;
import ch.unibas.dmi.dbis.cs108.BudaLogConfig; import ch.unibas.dmi.dbis.cs108.BudaLogConfig;
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;
public class JServerProtocolParser { public class JServerProtocolParser {
public static final Logger LOGGER = LogManager.getLogger(); public static final Logger LOGGER = LogManager.getLogger();
@ -17,6 +18,9 @@ public class JServerProtocolParser {
/** /**
* Used by the server (i.e. ClientHandler) to parse an incoming protocol message. * Used by the server (i.e. ClientHandler) to parse an incoming protocol message.
* For documentation on the individual Protocol messages, view the Protocol.java
* class or hover over the commands (e.g. Protocol.chatMsgToAll) with your mouse
* in this class.
* *
* @param msg the encoded message that needs to be parsed * @param msg the encoded message that needs to be parsed
* @param h this ClientHandler (required so this method can access the ClientHandler's methods) * @param h this ClientHandler (required so this method can access the ClientHandler's methods)
@ -30,33 +34,27 @@ public class JServerProtocolParser {
System.out.println("Received unknown command"); System.out.println("Received unknown command");
} }
switch (header) { switch (header) {
case CHATA: case Protocol.chatMsgToAll:
//sends chat message to all connected clients
h.broadcastChatMessage(msg.substring(6)); h.broadcastChatMessage(msg.substring(6));
break; break;
case "LOGON": case Protocol.clientLogin:
//sets name to whatever follows LOGON$ h.setLoggedIn(true);
try { try {
h.setUsernameOnLogin(msg.substring(6)); h.setUsernameOnLogin(msg.substring(6));
} catch (Exception e) { } catch (Exception e) {
h.setUsernameOnLogin("U.N. Owen"); h.setUsernameOnLogin("U.N. Owen");
} }
break; break;
case "NAMEC": case Protocol.nameChange:
//changes name to whatever follows NAMEC$. If the new name is already in use, it will append
//random numbers to the name.
h.changeUsername(msg.substring(6)); h.changeUsername(msg.substring(6));
break; break;
case "CPING": case Protocol.pingFromClient:
//sends a pingback to the client h.sendMsgToClient(Protocol.pingBack);
h.sendMsgToClient("PINGB");
break; break;
case "PINGB": case Protocol.pingBack:
//registers pingback from client
h.serverPinger.setGotPingBack(true); h.serverPinger.setGotPingBack(true);
break; break;
case "QUITS": case Protocol.clientQuitRequest:
//safely disconnects the user
h.removeClientOnLogout(); h.removeClientOnLogout();
break; break;
default: default:

View File

@ -54,13 +54,14 @@ public class nameDuplicateChecker {
* Also, if the name is empty, it assigns a default value ("U.N. Owen"). * Also, if the name is empty, it assigns a default value ("U.N. Owen").
*/ */
public static String checkName(String name) { public static String checkName(String name) {
String rtrn = name; //if this line is used, only duplicate names get a suffix. String tempname = name; //if this line is used, only duplicate names get a suffix.
//String rtrn = extendName(name); //if this line is used, all clients get a suffix //String tempname = extendName(name); //if this line is used, all clients get a suffix
rtrn = rtrn.replace("$",""); tempname = tempname.replace("$","");
rtrn = rtrn.replace(":",""); tempname = tempname.replace(":","");
if (rtrn.equalsIgnoreCase("")) {rtrn = "U.N. Owen";} if (tempname.equalsIgnoreCase("")) {tempname = "U.N. Owen";}
String rtrn = tempname;
while (isTaken(rtrn)) { //todo: handle the (very unlikely) case that all names are taken. while (isTaken(rtrn)) { //todo: handle the (very unlikely) case that all names are taken.
rtrn = extendName(name); rtrn = extendName(tempname);
} }
return rtrn; return rtrn;
} }

View File

@ -3,6 +3,7 @@ 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.gamelogic.Game; import ch.unibas.dmi.dbis.cs108.gamelogic.Game;
import ch.unibas.dmi.dbis.cs108.multiplayer.client.Client; import ch.unibas.dmi.dbis.cs108.multiplayer.client.Client;
import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler;
import java.net.Socket; import java.net.Socket;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -11,7 +12,7 @@ import org.apache.logging.log4j.Logger;
/** /**
* 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 uf * 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.
*/ */
public class CentralServerData { public class CentralServerData {
@ -19,11 +20,11 @@ public class CentralServerData {
public static final Logger LOGGER = LogManager.getLogger(); public static final Logger LOGGER = LogManager.getLogger();
public static final BudaLogConfig l = new BudaLogConfig(LOGGER); public static final BudaLogConfig l = new BudaLogConfig(LOGGER);
private Set<Client> clientsOnServer; private Set<ClientHandler> clientsOnServer;
private Set<Game> activeGames; private Set<Game> activeGames;
private Set<Game> gamesOpenToJoin; private Set<Game> gamesOpenToJoin;
private Map<Client, Socket> clientSocketMap; private Map<ClientHandler, Socket> clientSocketMap;
private Map<Socket, Client> socketClientMap; private Map<Socket, ClientHandler> socketClientMap;
private Map<Game, Client> gameClientMap; private Map<Game, ClientHandler> gameClientMap;
} }

View File

@ -1,5 +1,67 @@
package ch.unibas.dmi.dbis.cs108.sebaschi; package ch.unibas.dmi.dbis.cs108.sebaschi;
import ch.unibas.dmi.dbis.cs108.multiplayer.server.ClientHandler;
import java.util.ArrayList;
import java.util.List;
/**
* The Lobby one is in after a client sends the CRTGM command. THe Server
*/
public class Lobby { public class Lobby {
private static final int MAX_NO_OF_CLIENTS = 6;
private static int lobbies;
/**
* The Person who created the game and can configure it and decide to start once enough players
* have entered the lobby.
*/
private final ClientHandler admin;
/**
* Everyone who's in the lobby.
*/
private List<ClientHandler> players = new ArrayList<>(6);
private int numberOfPlayersInLobby;
private final int lobbyID = lobbies++;
static {
lobbies = 0;
}
/**
* Constructor. Sets the admin to who created the lobby. Adds the admin to the list of players.
* Increases the number of players from 0 to 1.
*
* @param admin the Client who called CRTGM
*/
public Lobby(ClientHandler admin) {
this.admin = admin;
this.players.add(admin);
this.numberOfPlayersInLobby = 1;
lobbies++;
}
/**
* Getter
*
* @return the admin of the lobby.
*/
public ClientHandler getAdmin() {
return this.admin;
}
/**
* Adds a player to the lobby.
*
* @param player who wants to join the lobby.
*/
public void addPlayer(ClientHandler player) {
players.add(player);
}
} }