diff --git a/build.gradle b/build.gradle index 4360c82..6f82563 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ dependencies { // Source: https://mvnrepository.com/artifact/org.apache.logging.log4j implementation("org.apache.logging.log4j:log4j-api:2.25.3") runtimeOnly("org.apache.logging.log4j:log4j-core:2.25.3") - + implementation("org.jspecify:jspecify:1.0.0") // Source: https://mvnrepository.com/artifact/org.fusesource.jansi/jansi runtimeOnly("org.fusesource.jansi:jansi:2.4.2") diff --git a/documents/docs/networking/Network-Protocol-Documentation.md b/documents/docs/networking/Network-Protocol-Documentation.md deleted file mode 100644 index 7099c7e..0000000 --- a/documents/docs/networking/Network-Protocol-Documentation.md +++ /dev/null @@ -1,63 +0,0 @@ -This Document states the Network-Protocol as it is currently implemented - -## GET_MESSAGE_COUNT -This Command gets the number of messages, currently stored in the queue for the specific client. -(Is used together with GET_NEXT_MESSAGE, to get all messages that are currently in the queue) - -Example: - -``` -GET_MESSAGE_COUNT - -1 -+OK -``` - -## GET_NEXT_MESSAGE -This Command gets the next Message that is being stored in the queue for the client. -(Command is being sent the amount of times, the GET_MESSAGE_COUNT Command returns) - -Example: - -``` -GET_NEXT_MESSAGE - -TYPE=LOBBY GAME=1 USER=player1 TARGET=null TIME=9:30 TEXT="Guten Tag" -+OK -``` - -## SEND_MESSAGE -This Command gets sent if the user of that client writes a message to one of the three possible chats - -(The Arguments / Parameters of the Command describe all the parameters of the Object "Message" being used internally by both the Client and the Server) - -Example: -``` -SEND_MESSAGE TYPE=LOBBY GAME=1 USER=player1 TARGET=null TIME=10:30 TEXT="Hallo Welt" - -+OK -``` - -## LOG_IN -This Command is used to create a user on the server and associate that user with a username -Server returns the username, slightly changed if it is already used by someone else, and an ID to identify the client. - -Example: - -``` -LOG_IN USERNAME="Peter" - -USERNAME="Peter" ID= -+OK -``` - -## LOG_OUT -This Command is used to quit the connection between client and server. - -Example: - -``` -LOG_OUT - -+OK -``` diff --git a/documents/docs/networking/commands/protocol-document.md b/documents/docs/networking/commands/protocol-document.md index b08cf7f..e53c3d4 100644 --- a/documents/docs/networking/commands/protocol-document.md +++ b/documents/docs/networking/commands/protocol-document.md @@ -341,4 +341,103 @@ LIST_USERS END END END +``` + +## SEND_MESSAGE command +The `SEND_MESSAGE` command is used to transfer the chat message sent by a user to the server. +### Required pre-execution checks +None. + +### Request Parameters + +| Field | Type | Description | +|:---------|:----------------|:-------------------------------------------------------------------| +| `TYPE` | `Enum getChatModelMap() { + return chatModelMap; + } + + private Map chatModelMap; + + private static final long REFRESH_TIME = 1000; + + /** + * Constructor, adds TimerTask to be sent to the server regularly + * + * @param username + * @param clientService + */ public ChatController(String username, ClientService clientService) { this.username = username; - this.clientService = clientService; - this.chatClient = new ChatClient(this.clientService); - } - - public void createChat(int game_id) { - chatModel = new ChatModel(ChatModel.ChatType.GLOBAL, username); - this.game_id = game_id; - } - - public ChatModel getChatModel() { - return chatModel; + chatClient = new ChatClient(clientService); + chatModelMap = new LinkedHashMap<>(); + this.chatBoxController = new ChatBoxController(username, this); + this.timer = new Timer(); + timer.schedule( + new TimerTask() { + @Override + public void run() { + receiveMessage(); + } + }, + 0, + REFRESH_TIME); } /** - * method to send a message, the ChatViewController received to the server + * Method to be activated, if a lobby has be chosen. It will update the UI and add a new + * ChatModel to hold the Data for the Lobby Chat + * + * @param lobbyId */ - public void sendMessage(String msg, String username) { - Message message = new Message(Message.MessageType.GLOBAL, 0, username, null, msg); - onSendToNetwork(message); + public void setLobbyChat(int lobbyId) { + this.lobbyId = lobbyId; + ChatModel lobbyChatModel = new ChatModel(ChatType.LOBBY, username, lobbyId, null); + chatModelMap.put(new ChatKey(ChatType.LOBBY), lobbyChatModel); + this.chatBoxController.addChatTab("Lobby", lobbyChatModel); } - /** - * method to get all messages from the server - */ - public Boolean receiveMessage() { + /** method to get all messages from the server */ + public void receiveMessage() { List newMessages = chatClient.getMessages(); if (!newMessages.isEmpty()) { for (Message msg : newMessages) { - chatModel.addMessage(msg); + switch (msg.getMessageType()) { + case ChatType.GLOBAL: + chatModelMap.get(new ChatKey(ChatType.GLOBAL)).addMessage(msg); + break; + case ChatType.LOBBY: + if (msg.lobbyId == lobbyId) { + chatModelMap + .computeIfAbsent( + new ChatKey(ChatType.LOBBY), + (_key) -> + new ChatModel( + ChatType.LOBBY, + username, + msg.lobbyId, + null)) + .addMessage(msg); + } + break; + case ChatType.WHISPER: + if (msg.target.equals(username)) { + if (chatModelMap.containsKey( + new ChatKey(ChatType.WHISPER, msg.sender))) { + chatModelMap + .get(new ChatKey(ChatType.WHISPER, msg.sender)) + .addMessage(msg); + } else { + chatBoxController.addWhisperChat(msg.sender); + } + } + break; + } } - return true; - } else { return false; } + } } /** * method to send a message to the server + * * @param message */ public void onSendToNetwork(Message message) { chatClient.sendMessage(message); } - } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java index 0c2f075..69cdecb 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java @@ -1,67 +1,85 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; import java.util.ArrayList; +import java.util.function.Consumer; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; /** * ChatModel, stores the data for a specific chat * - * Holds the current state of a chat + *

Holds the current state of a chat */ - public class ChatModel { + private ArrayList> listeners = new ArrayList<>(); + public ArrayList messages; - public ChatType chattype; + private final ChatType chattype; - public String username; + /** The person currently using this client */ + public final String username; - public int count; + /** The person to send the message to If the chat is a whisper chat */ + private final String target; - public enum ChatType { - GLOBAL, - LOBBY, - WHISPER - } + private final IntegerProperty count; + + public int lobbyId; /** - * Creates a new ChatModel, given a username of the client - * @param chattype - * @param username + * Constructs a new ChatModel for a specific chat type. + * + * @param chattype The type of chat (e.g., GLOBAL, LOBBY, or WHISPER). + * @param username The username of the current user. + * @param lobbyId The ID of the lobby, or -1 if not applicable. + * @param target The username of the whisper recipient, or null for other chat types. */ - - public ChatModel(ChatType chattype, String username) { + public ChatModel(ChatType chattype, String username, int lobbyId, String target) { this.messages = new ArrayList(); this.chattype = chattype; this.username = username; - this.count = 0; + this.count = new SimpleIntegerProperty(0); + this.lobbyId = lobbyId; + this.target = target; } /** - * method, used by the ChatViewController, to access all new messages, that are stored in the ChatModel + * Returns the type of chat this model represents. + * + * @return The {@link ChatType}. */ - public synchronized String viewNextMessage() { - count--; - Message msg = messages.getLast(); - return String.format("[%s] %s: %s", msg.timestamp, msg.user, msg.getMessage()); + public ChatType getChattype() { + return chattype; } /** - * Adds a new message - * method used by the ChatController - * @param msg + * Adds a new message to the history and notifies all registered listeners. This method is + * synchronized to ensure thread safety when updating the message list. + * + * @param msg The {@link Message} to be added. */ public synchronized void addMessage(Message msg) { messages.add(msg); - count++; + listeners.stream().forEach((l) -> l.accept(messages.getLast())); } /** - * method to send all current messages to the ChatViewController, if needed + * Registers a listener to be notified whenever a new message is added to this model. + * + * @param listener A {@link Consumer} that processes the new {@link Message}. */ - public void addCompleteChat() { - for (int i = 0; i < this.messages.size(); i++) { - Message msg = this.messages.get(i); - } + public void addListener(Consumer listener) { + this.listeners.add(listener); + } + + /** + * Returns the target user for this chat, primarily used for whispers. + * + * @return The target username or null. + */ + public String getTarget() { + return target; } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatType.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatType.java new file mode 100644 index 0000000..b6a7167 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatType.java @@ -0,0 +1,8 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.chat; + +/** Describing the Type of the Chat */ +public enum ChatType { + GLOBAL, + LOBBY, + WHISPER +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java index 8fb2b0e..8ed83b8 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java @@ -1,57 +1,63 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Message Object for internal handling of Chat-Messages - * TODO: Should be used on both sides of the network - */ +import java.util.List; +import java.util.Optional; +import org.jspecify.annotations.NonNull; +/** Message Object for internal handling of Chat-Messages */ public class Message { - private final MessageType type; + private final ChatType type; private final String message; - public String user; + public String sender; public String timestamp; - public int game_id = 0; + public int lobbyId = 0; public String target = null; - public enum MessageType { - GLOBAL, LOBBY, WHISPER - }; /** - * Constructor for creating Messages with all information given - * @param type - Either global, local or whisper - * @param game_id - lobby id, or null, if the type is global - * @param user - username - * @param target - username of the target user, for whisper chat - * @param message + * Constructs a Message with a provided timestamp. Typically used when reconstructing messages + * received from the server. + * + * @param type The chat category (e.g., GLOBAL, LOBBY, or WHISPER). + * @param lobbyId The ID of the lobby, or -1 if not applicable. + * @param sender The username of the message creator. + * @param target The username of the recipient (required for whispers, otherwise null). + * @param timestamp The formatted time string (e.g., "HH:mm"). + * @param message The actual text content of the message. */ - - public Message(MessageType type, int game_id, String user, String target, String timestamp, String message) { + public Message( + ChatType type, + int lobbyId, + String sender, + String target, + String timestamp, + String message) { this.type = type; - this.game_id = game_id; - this.user = user; + this.lobbyId = lobbyId; + this.sender = sender; this.target = target; this.timestamp = timestamp; this.message = message; } /** - * Constructor for creating the Messages of the current user, using this client -> time of writing is being recorded - * @param type - Either global, local or whisper - * @param game_id - lobby id, or null, if the type is global - * @param user - username - * @param target - username of the target user, for whisper chat - * @param message + * Constructs a new Message for the current user. Automatically generates a timestamp based on + * the local system time ("HH:mm"). + * + * @param type The chat category (e.g., GLOBAL, LOBBY, or WHISPER). + * @param lobbyId The ID of the lobby, or -1 if not applicable. + * @param sender The username of the current user. + * @param target The username of the recipient (for whispers). + * @param message The actual text content to be sent. */ - - public Message(MessageType type, int game_id, String user, String target, String message) { + public Message(ChatType type, int lobbyId, String sender, String target, String message) { this.type = type; - this.game_id = game_id; - this.user = user; + this.lobbyId = lobbyId; + this.sender = sender; this.target = target; this.message = message; LocalDateTime now = LocalDateTime.now(); @@ -59,78 +65,139 @@ public class Message { this.timestamp = now.format(formatter); } - + /** + * Returns the text content of the message. + * + * @return The message string. + */ public String getMessage() { return message; } - public MessageType getMessageType() { + /** + * Returns the type of chat this message belongs to. + * + * @return The {@link ChatType}. + */ + public ChatType getMessageType() { return type; } - /* - * Method to test the system - * @return - String representation of the Message instance, as for example "player1: Hello World" - - public String toString() { - return String.format("%s: %s", this.user, this.message); - } - */ /** - * Method to create the request representation of the message object, to be sent to the server - * @return - request as specified in the network protocol, as String + * Formats the message object into a string representation compatible with the network protocol + * arguments. + * + * @return A formatted string containing all message attributes for server transmission. */ public String toArgsString() { - return String.format("TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s", - this.type.toString(), this.game_id, this.user, this.target, this.timestamp, this.message); + String gameIdString = ""; + if (lobbyId >= 0) { + gameIdString = " GAME=" + lobbyId; + } else { + gameIdString = " GAME='-1'"; + } + return String.format( + "TYPE=%s%s USER='%s' TARGET='%s' TIME='%s' TEXT='%s'", + this.type.toString(), + gameIdString, + this.sender, + this.target, + this.timestamp, + this.message); } + /** + * Parses a list of network request parameters to reconstruct a Message object. Handles + * different chat types (GLOBAL, LOBBY, WHISPER) and their specific requirements. + * + * @param parameters A list of {@link RequestParameter} received from the network. + * @return A new {@link Message} instance populated with the parsed data. + */ + public static Message toMessageReqPars(List parameters) { + String typeString = getParString(parameters, "TYPE"); + ChatType type = ChatType.valueOf(typeString); + return switch (type) { + case GLOBAL -> + new Message( + ChatType.GLOBAL, + -1, + getParString(parameters, "USER"), + null, + getParString(parameters, "TIME"), + getParString(parameters, "TEXT")); + case LOBBY -> + new Message( + ChatType.LOBBY, + Integer.parseInt(getParString(parameters, "GAME")), + getParString(parameters, "USER"), + null, + getParString(parameters, "TIME"), + getParString(parameters, "TEXT")); + case WHISPER -> + new Message( + ChatType.WHISPER, + Integer.parseInt(getParString(parameters, "GAME", "-1")), + getParString(parameters, "USER"), + getParString(parameters, "TARGET"), + getParString(parameters, "TIME"), + getParString(parameters, "TEXT")); + }; + } /** - * Pattern, to analyze the response String with the given parameters + * Helper method to extract a specific parameter value by its key. + * + * @param parameters The list of parameters to search. + * @param keyString The key to look for. + * @return The value associated with the key. + * @throws RuntimeException if the key is not found. */ - public static Pattern msgRex = Pattern.compile( - "TYPE=(?\\w+) GAME=(?\\w+) USER=(?\\w+) TARGET=(?\\w+) TIME=(?

Implementations of this class use the {@code +OK} prefix. It provides a protected constructor * so subclasses can supply the response body content. */ -public abstract class SuccessResponse extends Response { +public class SuccessResponse extends Response { /** * Create a successful response with the provided body. * * @param context the RequestContext of the request * @param body the response body */ - protected SuccessResponse(RequestContext context, ResponseBody body) { + public SuccessResponse(RequestContext context, ResponseBody body) { super(context, body); } diff --git a/src/main/resources/ui-structure/Casinogameui.fxml b/src/main/resources/ui-structure/Casinogameui.fxml index 0a9e17d..db6ecca 100644 --- a/src/main/resources/ui-structure/Casinogameui.fxml +++ b/src/main/resources/ui-structure/Casinogameui.fxml @@ -5,37 +5,37 @@ - + + stylesheets="@casinogameui.css"> - + - + - + - + - + - + - + - + @@ -52,7 +52,7 @@ - + - + - + - + - + - + - + - + @@ -99,11 +99,11 @@ - + - + @@ -114,11 +114,11 @@ - + - + @@ -129,7 +129,7 @@ - + diff --git a/src/main/resources/ui-structure/Casinomainui.fxml b/src/main/resources/ui-structure/Casinomainui.fxml index 7d48eae..214c2a8 100644 --- a/src/main/resources/ui-structure/Casinomainui.fxml +++ b/src/main/resources/ui-structure/Casinomainui.fxml @@ -123,7 +123,7 @@ - + diff --git a/src/main/resources/ui-structure/components/Chatbox.fxml b/src/main/resources/ui-structure/components/Chatbox.fxml deleted file mode 100644 index 26cb448..0000000 --- a/src/main/resources/ui-structure/components/Chatbox.fxml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - -