From 1cddcf665760fb3d0c11290d75da076f50ce6191 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Wed, 22 Apr 2026 14:45:41 +0200 Subject: [PATCH 01/33] Feat: Add field ChatController to LobbyButtonGridManager --- .../casono/client/ui/lobbyui/LobbyButtonGridManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index e3f1d01..719dc98 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient; @@ -43,6 +44,8 @@ public class LobbyButtonGridManager { private final LobbyButtonTranslationManager translationManager; private final LobbyClient lobbyClient; + private ChatController chatController; + private final ConcurrentHashMap imageCache = new ConcurrentHashMap<>(); private final ExecutorService executor = Executors.newCachedThreadPool(); @@ -557,6 +560,7 @@ public class LobbyButtonGridManager { CasinoGameUI.setClientService(cs); CasinoGameUI.setLobbyId(lobbyId); + CasinoGameUI.setChatController(chatController); String username = ClientApp.getSharedUsername(); if (username == null || username.isBlank()) { @@ -586,4 +590,6 @@ public class LobbyButtonGridManager { public LobbyClient getLobbyClient() { return lobbyClient; } + + public void setChatController(ChatController chatController) { this.chatController = chatController; } } From 90b2f3bb3a74b0c7dfb9148e15c2dd987a5158f0 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Wed, 22 Apr 2026 15:04:10 +0200 Subject: [PATCH 02/33] Feat: Add chat box into game ui --- .../ui/gameui/CasinoGameController.java | 22 +++++++++++-------- .../casono/client/ui/gameui/CasinoGameUI.java | 8 +++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 1a65be2..5ef57da 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; import java.io.IOException; @@ -18,6 +19,7 @@ import java.util.concurrent.CompletableFuture; import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Label; @@ -239,7 +241,6 @@ public class CasinoGameController { // empty display only (optional) renderCommunityCards(List.of()); renderPlayerCards(List.of()); - initializeChatIfPossible(); } /** @@ -257,7 +258,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - initializeChatIfPossible(); + //initializeChatIfPossible(); } /** @@ -270,16 +271,14 @@ public class CasinoGameController { } try { - chatController = new ChatController(chatUsername, chatClientService); - + ChatBoxController chatBoxController = new ChatBoxController(chatUsername, chatController); URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); - loader.setController(chatController.getChatBoxController()); + loader.setController(chatBoxController); - Parent root = loader.load(); - - Stage chatStage = new Stage(); + chatContainer.getChildren().add(loader.load()); + /* chatStage.setTitle("Casono"); String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm(); @@ -294,7 +293,7 @@ public class CasinoGameController { chatStage.setOnCloseRequest(event -> chatInitialized = false); chatStage.show(); - + */ if (chatLobbyId >= 0) { chatController.setLobbyChat(chatLobbyId); } @@ -1340,4 +1339,9 @@ public class CasinoGameController { } return null; } + + public void startChat(ChatController chatController) { + this.chatController = chatController; + initializeChatIfPossible(); + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index d6908c1..15f7d27 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; @@ -25,6 +26,7 @@ public class CasinoGameUI extends Application { private static final Logger LOG = Logger.getLogger(CasinoGameUI.class.getName()); private static ClientService clientService; + private static ChatController chatController; private static String username; private static int lobbyId = -1; @@ -65,6 +67,10 @@ public class CasinoGameUI extends Application { CasinoGameUI.lobbyId = lobbyId; } + public static void setChatController(ChatController chatController) { + CasinoGameUI.chatController = chatController; + } + /** * The main entry point for the JavaFX application. This method is called after the application * is @@ -113,6 +119,8 @@ public class CasinoGameUI extends Application { Parent root = fxmlLoader.load(); CasinoGameController controller = fxmlLoader.getController(); + controller.startChat(chatController); + if (lobbyId <= 0) { throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()"); } From 36348d0d93afd4ce359b7f6d719361f4ca157f87 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:57:48 +0200 Subject: [PATCH 03/33] Feat: Add chat box into the game screen Refs #107 --- .../ui/gameui/CasinoGameController.java | 31 +++++++++++-------- .../casono/client/ui/gameui/CasinoGameUI.java | 10 +++--- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 5ef57da..fe0f782 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -69,6 +69,7 @@ public class CasinoGameController { private java.util.List lastCommunityKeys = java.util.List.of(); private java.util.List lastMyCardKeys = java.util.List.of(); private int lastPot = Integer.MIN_VALUE; + private ChatController chatController; private static final int TOTAL_SLOTS = 5; private static final int PLAYER_SLOTS = 2; @@ -141,7 +142,6 @@ public class CasinoGameController { private static final double CHAT_WIDTH = 400; private static final double CHAT_HEIGHT = 600; - private ChatController chatController; private String chatUsername; private ClientService chatClientService; private int chatLobbyId = -1; @@ -199,6 +199,11 @@ public class CasinoGameController { } } + public void startChat(ChatController chatController) { + this.chatController = chatController; + initializeChatIfPossible(); + } + /** Set the PlayerId of the current player. */ @FXML public void initialize() { @@ -241,6 +246,8 @@ public class CasinoGameController { // empty display only (optional) renderCommunityCards(List.of()); renderPlayerCards(List.of()); + + chatContainer.toFront(); } /** @@ -258,7 +265,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - //initializeChatIfPossible(); + //startChat(chatController); } /** @@ -272,18 +279,19 @@ public class CasinoGameController { try { ChatBoxController chatBoxController = new ChatBoxController(chatUsername, chatController); + + chatController.setChatBoxController(chatBoxController); + URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); loader.setController(chatBoxController); - chatContainer.getChildren().add(loader.load()); + chatBoxController.loadChats(); + Node chatBox = loader.load(); + + chatContainer.getChildren().add(chatBox); /* - chatStage.setTitle("Casono"); - - String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm(); - chatStage.getIcons().add(new Image(iconPath)); - Scene scene = new Scene(root); chatStage.setScene(scene); @@ -293,7 +301,8 @@ public class CasinoGameController { chatStage.setOnCloseRequest(event -> chatInitialized = false); chatStage.show(); - */ + */ + if (chatLobbyId >= 0) { chatController.setLobbyChat(chatLobbyId); } @@ -1340,8 +1349,4 @@ public class CasinoGameController { return null; } - public void startChat(ChatController chatController) { - this.chatController = chatController; - initializeChatIfPossible(); - } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 15f7d27..76f55db 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -58,6 +58,8 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } + public static void setChatController(ChatController chatController) {CasinoGameUI.chatController = chatController;} + /** * Sets the lobby ID to be used by the application. * @@ -67,10 +69,6 @@ public class CasinoGameUI extends Application { CasinoGameUI.lobbyId = lobbyId; } - public static void setChatController(ChatController chatController) { - CasinoGameUI.chatController = chatController; - } - /** * The main entry point for the JavaFX application. This method is called after the application * is @@ -119,8 +117,6 @@ public class CasinoGameUI extends Application { Parent root = fxmlLoader.load(); CasinoGameController controller = fxmlLoader.getController(); - controller.startChat(chatController); - if (lobbyId <= 0) { throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()"); } @@ -132,6 +128,8 @@ public class CasinoGameUI extends Application { controller.setMyPlayerId(PlayerId.of(effectiveUsername)); controller.setChatContext(effectiveUsername, clientService, lobbyId); + controller.startChat(chatController); + Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT); stage.setTitle("Casono"); From 66ee8bec46cc72a62dab8a553113cfb8794c46c7 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:58:12 +0200 Subject: [PATCH 04/33] Feat: Forward ChatController to all instances where its needed Refs #107 --- .../cs108/casono/client/ui/lobbyui/CasinomainuiController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java index b3c2297..518b8cf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -141,6 +141,7 @@ public class CasinomainuiController { AnchorPane.setBottomAnchor(chatNode, 0.0); AnchorPane.setLeftAnchor(chatNode, 0.0); AnchorPane.setRightAnchor(chatNode, 0.0); + gridManager.setChatController(chatController); } catch (IOException e) { LOGGER.warn("Could not initialize lobby chat UI: {}", e.getMessage()); } From c5f37392efa1a5e2ad3cda1b447a9c8c4e283517 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:59:48 +0200 Subject: [PATCH 05/33] Feat: Add load method to restore all chat message of a chat Refs #107 --- .../casono/client/chat/ChatController.java | 16 +++++++- .../client/ui/chatui/ChatBoxController.java | 41 +++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index b07c712..41c5f37 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -4,6 +4,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.network.ChatClient; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; import java.util.ArrayList; import java.util.HashSet; @@ -14,6 +15,7 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; +import java.util.HashMap; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -39,7 +41,9 @@ public class ChatController { return chatBoxController; } - private final ChatBoxController chatBoxController; + public void setChatBoxController(ChatBoxController chatBoxController) {this.chatBoxController = chatBoxController; } + + private ChatBoxController chatBoxController; private int lobbyId = -1; private final Timer timer; private final Consumer> serverEventListener; @@ -61,6 +65,13 @@ public class ChatController { /** List of all users connected to the server, to safe them locally on the client */ private final List localUserList; + public List getLocalUserList() { + return this.localUserList; + } + + public final Map activeChatControllers; + + private final Logger logger; /** @@ -78,6 +89,7 @@ public class ChatController { this.chatBoxController = new ChatBoxController(username, this); this.logger = LogManager.getLogger(ChatController.class); this.serverEventListener = this::handleServerEvent; + this.activeChatControllers = new HashMap<>(); registerAsActiveController(clientService); clientService.addEventListener(serverEventListener); @@ -126,7 +138,7 @@ public class ChatController { } ChatModel lobbyChatModel = new ChatModel(ChatType.LOBBY, username, lobbyId, null); chatModelMap.put(key, lobbyChatModel); - this.chatBoxController.addChatTab("Lobby", lobbyChatModel); + this.chatBoxController.addChatTab("LOBBY", lobbyChatModel, ChatType.LOBBY); } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 1864601..0947daf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; + +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -27,7 +29,7 @@ public class ChatBoxController { private String username; - private ChatController chatController; + private final ChatController chatController; @FXML private VBox chatBox; @@ -43,7 +45,6 @@ public class ChatBoxController { @FXML private final Map usernameTabMap; - private String ressource = "/ui-structure/components/chatui/chattab.fxml"; /** * Constructor for the ChatBoxController, initializes the necessary fields and data structures @@ -73,11 +74,12 @@ public class ChatBoxController { */ @FXML public void initialize() { - ChatModel globalChatModel = new ChatModel(ChatType.GLOBAL, username, -1, null); + ChatType global = ChatType.GLOBAL; + ChatModel globalChatModel = new ChatModel(global, username, -1, null); chatController .getChatModelMap() - .put(new ChatController.ChatKey(ChatType.GLOBAL), globalChatModel); - addChatTab("GLOBAL", globalChatModel); + .put(new ChatController.ChatKey(global), globalChatModel); + addChatTab("GLOBAL", globalChatModel, global); addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show()); } @@ -166,10 +168,11 @@ public class ChatBoxController { public void addWhisperChat(String target, ChatModel chatModel) { if (!activeWhisperChats.contains(target)) { activeWhisperChats.add(target); + ChatType whisper = ChatType.WHISPER; chatController .getChatModelMap() - .put(new ChatController.ChatKey(ChatType.WHISPER, target), chatModel); - addChatTab(target, chatModel); + .put(new ChatController.ChatKey(whisper, target), chatModel); + addChatTab(target, chatModel, whisper); } else { chatTabPane.getSelectionModel().select(usernameTabMap.get(target)); } @@ -184,7 +187,8 @@ public class ChatBoxController { * @param chatModel The {@link ChatModel} containing the data and logic for this specific chat. * @throws RuntimeException If the FXML resource for the chat tab cannot be loaded. */ - public void addChatTab(String title, ChatModel chatModel) { + public void addChatTab(String title, ChatModel chatModel, ChatType chatType) { + String ressource = "/ui-structure/components/chatui/chattab.fxml"; URL resource = getClass().getResource(ressource); FXMLLoader fxmlLoader = new FXMLLoader(resource); runOnPlatformSynchronized( @@ -193,6 +197,7 @@ public class ChatBoxController { ChatViewController chatViewController = new ChatViewController( this.chatController, chatModel, this.username); + chatController.activeChatControllers.put(new ChatController.ChatKey(chatType), chatViewController); fxmlLoader.setController(chatViewController); Node load = fxmlLoader.load(); VBox.setVgrow(load, Priority.ALWAYS); @@ -239,4 +244,24 @@ public class ChatBoxController { } } } + + public void loadChats() { + Map chatModelMap = chatController.getChatModelMap(); + ChatController.ChatKey key = new ChatController.ChatKey(ChatType.GLOBAL); + ChatModel global = chatModelMap.get(key); + ChatViewController globalController = chatController.activeChatControllers.get(key); + for (Message msg : global.messages) { + globalController.showMessage(msg); + } + for (String user : chatController.getLocalUserList()) { + ChatController.ChatKey whisperUserKey = new ChatController.ChatKey(ChatType.WHISPER, user); + if (chatModelMap.containsKey(whisperUserKey)) { + ChatModel whisperChatModel = chatModelMap.get(whisperUserKey); + addWhisperChat(user, whisperChatModel); + for (Message msg : whisperChatModel.messages) { + chatController.activeChatControllers.get(whisperUserKey).showMessage(msg); + } + } + } + } } From d094df16da89653216a9b372a2458fe734de3b41 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Thu, 23 Apr 2026 19:55:57 +0200 Subject: [PATCH 06/33] Fix: correct layout layering in Game UI --- .../resources/ui-structure/Casinogameui.fxml | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/main/resources/ui-structure/Casinogameui.fxml b/src/main/resources/ui-structure/Casinogameui.fxml index a5d5514..6e4369f 100644 --- a/src/main/resources/ui-structure/Casinogameui.fxml +++ b/src/main/resources/ui-structure/Casinogameui.fxml @@ -76,12 +76,14 @@ @@ -99,6 +101,7 @@ styleClass="dealer-box" fitWidth="100" fitHeight="100" + GridPane.columnIndex="2" preserveRatio="true" visible="false"/> @@ -117,56 +120,48 @@ + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 33998e4a885091941157d1a103f8f12f1ff937e5 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Thu, 23 Apr 2026 19:59:55 +0200 Subject: [PATCH 07/33] Fix: Chat UI initialization improved in Game UI --- .../ui/gameui/CasinoGameController.java | 39 +++++-------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index fe0f782..3a89aef 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -278,37 +278,18 @@ public class CasinoGameController { } try { - ChatBoxController chatBoxController = new ChatBoxController(chatUsername, chatController); - - chatController.setChatBoxController(chatBoxController); - + String username = chatUsername; + // chatController = new ChatController(username, chatClientService); URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); - loader.setController(chatBoxController); - - chatBoxController.loadChats(); - - Node chatBox = loader.load(); - - chatContainer.getChildren().add(chatBox); - /* - Scene scene = new Scene(root); - chatStage.setScene(scene); - - chatStage.setWidth(CHAT_WIDTH); - chatStage.setHeight(CHAT_HEIGHT); - - chatStage.setOnCloseRequest(event -> chatInitialized = false); - - chatStage.show(); - */ - - if (chatLobbyId >= 0) { - chatController.setLobbyChat(chatLobbyId); - } - - chatInitialized = true; - + loader.setController(chatController.getChatBoxController()); + Node chatNode = loader.load(); + chatContainer.getChildren().setAll(chatNode); + AnchorPane.setTopAnchor(chatNode, 6.0); + AnchorPane.setBottomAnchor(chatNode, 6.0); + AnchorPane.setLeftAnchor(chatNode, 0.0); + AnchorPane.setRightAnchor(chatNode, 6.0); + // gridManager.setChatController(chatController); } catch (IOException e) { LOGGER.warning("Could not initialize game chat UI: " + e.getMessage()); } From 8bb0bfaf09b5b8f9e87e1a6fcf52b97d17ef4d53 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Fri, 24 Apr 2026 11:16:34 +0200 Subject: [PATCH 08/33] Fix: whisper chat not working in game UI --- .../client/ui/chatui/ChatBoxController.java | 5 +++++ .../ui/gameui/CasinoGameController.java | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 0947daf..4e0cd7f 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -250,6 +250,11 @@ public class ChatBoxController { ChatController.ChatKey key = new ChatController.ChatKey(ChatType.GLOBAL); ChatModel global = chatModelMap.get(key); ChatViewController globalController = chatController.activeChatControllers.get(key); + + for (String user : chatController.getLocalUserList()) { + addWhisperUser(user); + } + for (Message msg : global.messages) { globalController.showMessage(msg); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 3a89aef..07d4cd8 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -265,7 +265,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - //startChat(chatController); + initializeChatIfPossible(); } /** @@ -273,13 +273,16 @@ public class CasinoGameController { * is available and the chat has not already been initialized. */ private void initializeChatIfPossible() { - if (chatInitialized || chatClientService == null || chatUsername == null) { + if (chatInitialized + || chatClientService == null + || chatUsername == null + || chatController == null) { return; } try { - String username = chatUsername; - // chatController = new ChatController(username, chatClientService); + chatController.updateUsername(chatUsername); + URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); loader.setController(chatController.getChatBoxController()); @@ -289,7 +292,13 @@ public class CasinoGameController { AnchorPane.setBottomAnchor(chatNode, 6.0); AnchorPane.setLeftAnchor(chatNode, 0.0); AnchorPane.setRightAnchor(chatNode, 6.0); - // gridManager.setChatController(chatController); + + chatController.getChatBoxController().loadChats(); + chatInitialized = true; + + if (chatLobbyId >= 0) { + chatController.setLobbyChat(chatLobbyId); + } } catch (IOException e) { LOGGER.warning("Could not initialize game chat UI: " + e.getMessage()); } From 9488ef41c39b09e371cea2f815c0314322363084 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Fri, 24 Apr 2026 11:46:32 +0200 Subject: [PATCH 09/33] Style: Apply Spotless --- .../cs108/casono/client/chat/ChatController.java | 7 ++++--- .../casono/client/ui/chatui/ChatBoxController.java | 14 ++++++-------- .../client/ui/gameui/CasinoGameController.java | 12 ++++-------- .../casono/client/ui/gameui/CasinoGameUI.java | 6 ++++-- .../client/ui/lobbyui/LobbyButtonGridManager.java | 6 ++++-- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index 41c5f37..09d7838 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -15,7 +16,6 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; -import java.util.HashMap; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,7 +41,9 @@ public class ChatController { return chatBoxController; } - public void setChatBoxController(ChatBoxController chatBoxController) {this.chatBoxController = chatBoxController; } + public void setChatBoxController(ChatBoxController chatBoxController) { + this.chatBoxController = chatBoxController; + } private ChatBoxController chatBoxController; private int lobbyId = -1; @@ -71,7 +73,6 @@ public class ChatController { public final Map activeChatControllers; - private final Logger logger; /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 4e0cd7f..26aa545 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -3,6 +3,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import java.io.IOException; import java.net.URL; import java.util.ArrayList; @@ -11,8 +12,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; - -import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -45,7 +44,6 @@ public class ChatBoxController { @FXML private final Map usernameTabMap; - /** * Constructor for the ChatBoxController, initializes the necessary fields and data structures * for managing chat tabs and whisper chats. @@ -76,9 +74,7 @@ public class ChatBoxController { public void initialize() { ChatType global = ChatType.GLOBAL; ChatModel globalChatModel = new ChatModel(global, username, -1, null); - chatController - .getChatModelMap() - .put(new ChatController.ChatKey(global), globalChatModel); + chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel); addChatTab("GLOBAL", globalChatModel, global); addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show()); } @@ -197,7 +193,8 @@ public class ChatBoxController { ChatViewController chatViewController = new ChatViewController( this.chatController, chatModel, this.username); - chatController.activeChatControllers.put(new ChatController.ChatKey(chatType), chatViewController); + chatController.activeChatControllers.put( + new ChatController.ChatKey(chatType), chatViewController); fxmlLoader.setController(chatViewController); Node load = fxmlLoader.load(); VBox.setVgrow(load, Priority.ALWAYS); @@ -259,7 +256,8 @@ public class ChatBoxController { globalController.showMessage(msg); } for (String user : chatController.getLocalUserList()) { - ChatController.ChatKey whisperUserKey = new ChatController.ChatKey(ChatType.WHISPER, user); + ChatController.ChatKey whisperUserKey = + new ChatController.ChatKey(ChatType.WHISPER, user); if (chatModelMap.containsKey(whisperUserKey)) { ChatModel whisperChatModel = chatModelMap.get(whisperUserKey); addWhisperChat(user, whisperChatModel); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 07d4cd8..eabb465 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -7,7 +7,6 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; -import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; import java.io.IOException; @@ -20,15 +19,12 @@ import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafx.stage.Stage; /** * Controller for the casino gaming area. @@ -71,6 +67,7 @@ public class CasinoGameController { private int lastPot = Integer.MIN_VALUE; private ChatController chatController; + public static final double CHAT_CONTAINER_CONSTANT = 6.0; private static final int TOTAL_SLOTS = 5; private static final int PLAYER_SLOTS = 2; private int pot = 0; @@ -288,10 +285,10 @@ public class CasinoGameController { loader.setController(chatController.getChatBoxController()); Node chatNode = loader.load(); chatContainer.getChildren().setAll(chatNode); - AnchorPane.setTopAnchor(chatNode, 6.0); - AnchorPane.setBottomAnchor(chatNode, 6.0); + AnchorPane.setTopAnchor(chatNode, CHAT_CONTAINER_CONSTANT); + AnchorPane.setBottomAnchor(chatNode, CHAT_CONTAINER_CONSTANT); AnchorPane.setLeftAnchor(chatNode, 0.0); - AnchorPane.setRightAnchor(chatNode, 6.0); + AnchorPane.setRightAnchor(chatNode, CHAT_CONTAINER_CONSTANT); chatController.getChatBoxController().loadChats(); chatInitialized = true; @@ -1338,5 +1335,4 @@ public class CasinoGameController { } return null; } - } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 76f55db..c82dfe1 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -1,7 +1,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; -import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; @@ -58,7 +58,9 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } - public static void setChatController(ChatController chatController) {CasinoGameUI.chatController = chatController;} + public static void setChatController(ChatController chatController) { + CasinoGameUI.chatController = chatController; + } /** * Sets the lobby ID to be used by the application. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index 719dc98..c2c5163 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -1,7 +1,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; -import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI; @@ -591,5 +591,7 @@ public class LobbyButtonGridManager { return lobbyClient; } - public void setChatController(ChatController chatController) { this.chatController = chatController; } + public void setChatController(ChatController chatController) { + this.chatController = chatController; + } } From 69fc00a3b2c60606e3524fa86f32d2205ead1658 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Wed, 22 Apr 2026 14:45:41 +0200 Subject: [PATCH 10/33] Feat: Add field ChatController to LobbyButtonGridManager --- .../casono/client/ui/lobbyui/LobbyButtonGridManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index e3f1d01..719dc98 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient; @@ -43,6 +44,8 @@ public class LobbyButtonGridManager { private final LobbyButtonTranslationManager translationManager; private final LobbyClient lobbyClient; + private ChatController chatController; + private final ConcurrentHashMap imageCache = new ConcurrentHashMap<>(); private final ExecutorService executor = Executors.newCachedThreadPool(); @@ -557,6 +560,7 @@ public class LobbyButtonGridManager { CasinoGameUI.setClientService(cs); CasinoGameUI.setLobbyId(lobbyId); + CasinoGameUI.setChatController(chatController); String username = ClientApp.getSharedUsername(); if (username == null || username.isBlank()) { @@ -586,4 +590,6 @@ public class LobbyButtonGridManager { public LobbyClient getLobbyClient() { return lobbyClient; } + + public void setChatController(ChatController chatController) { this.chatController = chatController; } } From 7eafaba65a703bdd7e282bf8bfccc6f36f6eb377 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Wed, 22 Apr 2026 15:04:10 +0200 Subject: [PATCH 11/33] Feat: Add chat box into game ui --- .../ui/gameui/CasinoGameController.java | 22 +++++++++++-------- .../casono/client/ui/gameui/CasinoGameUI.java | 8 +++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 1a65be2..5ef57da 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; import java.io.IOException; @@ -18,6 +19,7 @@ import java.util.concurrent.CompletableFuture; import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Label; @@ -239,7 +241,6 @@ public class CasinoGameController { // empty display only (optional) renderCommunityCards(List.of()); renderPlayerCards(List.of()); - initializeChatIfPossible(); } /** @@ -257,7 +258,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - initializeChatIfPossible(); + //initializeChatIfPossible(); } /** @@ -270,16 +271,14 @@ public class CasinoGameController { } try { - chatController = new ChatController(chatUsername, chatClientService); - + ChatBoxController chatBoxController = new ChatBoxController(chatUsername, chatController); URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); - loader.setController(chatController.getChatBoxController()); + loader.setController(chatBoxController); - Parent root = loader.load(); - - Stage chatStage = new Stage(); + chatContainer.getChildren().add(loader.load()); + /* chatStage.setTitle("Casono"); String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm(); @@ -294,7 +293,7 @@ public class CasinoGameController { chatStage.setOnCloseRequest(event -> chatInitialized = false); chatStage.show(); - + */ if (chatLobbyId >= 0) { chatController.setLobbyChat(chatLobbyId); } @@ -1340,4 +1339,9 @@ public class CasinoGameController { } return null; } + + public void startChat(ChatController chatController) { + this.chatController = chatController; + initializeChatIfPossible(); + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index d6908c1..15f7d27 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; @@ -25,6 +26,7 @@ public class CasinoGameUI extends Application { private static final Logger LOG = Logger.getLogger(CasinoGameUI.class.getName()); private static ClientService clientService; + private static ChatController chatController; private static String username; private static int lobbyId = -1; @@ -65,6 +67,10 @@ public class CasinoGameUI extends Application { CasinoGameUI.lobbyId = lobbyId; } + public static void setChatController(ChatController chatController) { + CasinoGameUI.chatController = chatController; + } + /** * The main entry point for the JavaFX application. This method is called after the application * is @@ -113,6 +119,8 @@ public class CasinoGameUI extends Application { Parent root = fxmlLoader.load(); CasinoGameController controller = fxmlLoader.getController(); + controller.startChat(chatController); + if (lobbyId <= 0) { throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()"); } From 2a03571cd1d8357fa2721ec2511e085a6b797d71 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:57:48 +0200 Subject: [PATCH 12/33] Feat: Add chat box into the game screen Refs #107 --- .../ui/gameui/CasinoGameController.java | 31 +++++++++++-------- .../casono/client/ui/gameui/CasinoGameUI.java | 10 +++--- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 5ef57da..fe0f782 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -69,6 +69,7 @@ public class CasinoGameController { private java.util.List lastCommunityKeys = java.util.List.of(); private java.util.List lastMyCardKeys = java.util.List.of(); private int lastPot = Integer.MIN_VALUE; + private ChatController chatController; private static final int TOTAL_SLOTS = 5; private static final int PLAYER_SLOTS = 2; @@ -141,7 +142,6 @@ public class CasinoGameController { private static final double CHAT_WIDTH = 400; private static final double CHAT_HEIGHT = 600; - private ChatController chatController; private String chatUsername; private ClientService chatClientService; private int chatLobbyId = -1; @@ -199,6 +199,11 @@ public class CasinoGameController { } } + public void startChat(ChatController chatController) { + this.chatController = chatController; + initializeChatIfPossible(); + } + /** Set the PlayerId of the current player. */ @FXML public void initialize() { @@ -241,6 +246,8 @@ public class CasinoGameController { // empty display only (optional) renderCommunityCards(List.of()); renderPlayerCards(List.of()); + + chatContainer.toFront(); } /** @@ -258,7 +265,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - //initializeChatIfPossible(); + //startChat(chatController); } /** @@ -272,18 +279,19 @@ public class CasinoGameController { try { ChatBoxController chatBoxController = new ChatBoxController(chatUsername, chatController); + + chatController.setChatBoxController(chatBoxController); + URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); loader.setController(chatBoxController); - chatContainer.getChildren().add(loader.load()); + chatBoxController.loadChats(); + Node chatBox = loader.load(); + + chatContainer.getChildren().add(chatBox); /* - chatStage.setTitle("Casono"); - - String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm(); - chatStage.getIcons().add(new Image(iconPath)); - Scene scene = new Scene(root); chatStage.setScene(scene); @@ -293,7 +301,8 @@ public class CasinoGameController { chatStage.setOnCloseRequest(event -> chatInitialized = false); chatStage.show(); - */ + */ + if (chatLobbyId >= 0) { chatController.setLobbyChat(chatLobbyId); } @@ -1340,8 +1349,4 @@ public class CasinoGameController { return null; } - public void startChat(ChatController chatController) { - this.chatController = chatController; - initializeChatIfPossible(); - } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 15f7d27..76f55db 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -58,6 +58,8 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } + public static void setChatController(ChatController chatController) {CasinoGameUI.chatController = chatController;} + /** * Sets the lobby ID to be used by the application. * @@ -67,10 +69,6 @@ public class CasinoGameUI extends Application { CasinoGameUI.lobbyId = lobbyId; } - public static void setChatController(ChatController chatController) { - CasinoGameUI.chatController = chatController; - } - /** * The main entry point for the JavaFX application. This method is called after the application * is @@ -119,8 +117,6 @@ public class CasinoGameUI extends Application { Parent root = fxmlLoader.load(); CasinoGameController controller = fxmlLoader.getController(); - controller.startChat(chatController); - if (lobbyId <= 0) { throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()"); } @@ -132,6 +128,8 @@ public class CasinoGameUI extends Application { controller.setMyPlayerId(PlayerId.of(effectiveUsername)); controller.setChatContext(effectiveUsername, clientService, lobbyId); + controller.startChat(chatController); + Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT); stage.setTitle("Casono"); From 67a0f237e61ba9892ae89f02da3ad895630e42d2 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:58:12 +0200 Subject: [PATCH 13/33] Feat: Forward ChatController to all instances where its needed Refs #107 --- .../cs108/casono/client/ui/lobbyui/CasinomainuiController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java index b3c2297..518b8cf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -141,6 +141,7 @@ public class CasinomainuiController { AnchorPane.setBottomAnchor(chatNode, 0.0); AnchorPane.setLeftAnchor(chatNode, 0.0); AnchorPane.setRightAnchor(chatNode, 0.0); + gridManager.setChatController(chatController); } catch (IOException e) { LOGGER.warn("Could not initialize lobby chat UI: {}", e.getMessage()); } From 826e58644a5dd5d3088d8ca1d844c4f73c7066e9 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:59:48 +0200 Subject: [PATCH 14/33] Feat: Add load method to restore all chat message of a chat Refs #107 --- .../casono/client/chat/ChatController.java | 16 +++++++- .../client/ui/chatui/ChatBoxController.java | 41 +++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index b07c712..41c5f37 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -4,6 +4,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.network.ChatClient; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; import java.util.ArrayList; import java.util.HashSet; @@ -14,6 +15,7 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; +import java.util.HashMap; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -39,7 +41,9 @@ public class ChatController { return chatBoxController; } - private final ChatBoxController chatBoxController; + public void setChatBoxController(ChatBoxController chatBoxController) {this.chatBoxController = chatBoxController; } + + private ChatBoxController chatBoxController; private int lobbyId = -1; private final Timer timer; private final Consumer> serverEventListener; @@ -61,6 +65,13 @@ public class ChatController { /** List of all users connected to the server, to safe them locally on the client */ private final List localUserList; + public List getLocalUserList() { + return this.localUserList; + } + + public final Map activeChatControllers; + + private final Logger logger; /** @@ -78,6 +89,7 @@ public class ChatController { this.chatBoxController = new ChatBoxController(username, this); this.logger = LogManager.getLogger(ChatController.class); this.serverEventListener = this::handleServerEvent; + this.activeChatControllers = new HashMap<>(); registerAsActiveController(clientService); clientService.addEventListener(serverEventListener); @@ -126,7 +138,7 @@ public class ChatController { } ChatModel lobbyChatModel = new ChatModel(ChatType.LOBBY, username, lobbyId, null); chatModelMap.put(key, lobbyChatModel); - this.chatBoxController.addChatTab("Lobby", lobbyChatModel); + this.chatBoxController.addChatTab("LOBBY", lobbyChatModel, ChatType.LOBBY); } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 1864601..0947daf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -11,6 +11,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; + +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -27,7 +29,7 @@ public class ChatBoxController { private String username; - private ChatController chatController; + private final ChatController chatController; @FXML private VBox chatBox; @@ -43,7 +45,6 @@ public class ChatBoxController { @FXML private final Map usernameTabMap; - private String ressource = "/ui-structure/components/chatui/chattab.fxml"; /** * Constructor for the ChatBoxController, initializes the necessary fields and data structures @@ -73,11 +74,12 @@ public class ChatBoxController { */ @FXML public void initialize() { - ChatModel globalChatModel = new ChatModel(ChatType.GLOBAL, username, -1, null); + ChatType global = ChatType.GLOBAL; + ChatModel globalChatModel = new ChatModel(global, username, -1, null); chatController .getChatModelMap() - .put(new ChatController.ChatKey(ChatType.GLOBAL), globalChatModel); - addChatTab("GLOBAL", globalChatModel); + .put(new ChatController.ChatKey(global), globalChatModel); + addChatTab("GLOBAL", globalChatModel, global); addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show()); } @@ -166,10 +168,11 @@ public class ChatBoxController { public void addWhisperChat(String target, ChatModel chatModel) { if (!activeWhisperChats.contains(target)) { activeWhisperChats.add(target); + ChatType whisper = ChatType.WHISPER; chatController .getChatModelMap() - .put(new ChatController.ChatKey(ChatType.WHISPER, target), chatModel); - addChatTab(target, chatModel); + .put(new ChatController.ChatKey(whisper, target), chatModel); + addChatTab(target, chatModel, whisper); } else { chatTabPane.getSelectionModel().select(usernameTabMap.get(target)); } @@ -184,7 +187,8 @@ public class ChatBoxController { * @param chatModel The {@link ChatModel} containing the data and logic for this specific chat. * @throws RuntimeException If the FXML resource for the chat tab cannot be loaded. */ - public void addChatTab(String title, ChatModel chatModel) { + public void addChatTab(String title, ChatModel chatModel, ChatType chatType) { + String ressource = "/ui-structure/components/chatui/chattab.fxml"; URL resource = getClass().getResource(ressource); FXMLLoader fxmlLoader = new FXMLLoader(resource); runOnPlatformSynchronized( @@ -193,6 +197,7 @@ public class ChatBoxController { ChatViewController chatViewController = new ChatViewController( this.chatController, chatModel, this.username); + chatController.activeChatControllers.put(new ChatController.ChatKey(chatType), chatViewController); fxmlLoader.setController(chatViewController); Node load = fxmlLoader.load(); VBox.setVgrow(load, Priority.ALWAYS); @@ -239,4 +244,24 @@ public class ChatBoxController { } } } + + public void loadChats() { + Map chatModelMap = chatController.getChatModelMap(); + ChatController.ChatKey key = new ChatController.ChatKey(ChatType.GLOBAL); + ChatModel global = chatModelMap.get(key); + ChatViewController globalController = chatController.activeChatControllers.get(key); + for (Message msg : global.messages) { + globalController.showMessage(msg); + } + for (String user : chatController.getLocalUserList()) { + ChatController.ChatKey whisperUserKey = new ChatController.ChatKey(ChatType.WHISPER, user); + if (chatModelMap.containsKey(whisperUserKey)) { + ChatModel whisperChatModel = chatModelMap.get(whisperUserKey); + addWhisperChat(user, whisperChatModel); + for (Message msg : whisperChatModel.messages) { + chatController.activeChatControllers.get(whisperUserKey).showMessage(msg); + } + } + } + } } From 6471749133becae32ae453390a7265a57659d090 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Thu, 23 Apr 2026 19:55:57 +0200 Subject: [PATCH 15/33] Fix: correct layout layering in Game UI --- .../resources/ui-structure/Casinogameui.fxml | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/main/resources/ui-structure/Casinogameui.fxml b/src/main/resources/ui-structure/Casinogameui.fxml index a5d5514..6e4369f 100644 --- a/src/main/resources/ui-structure/Casinogameui.fxml +++ b/src/main/resources/ui-structure/Casinogameui.fxml @@ -76,12 +76,14 @@ @@ -99,6 +101,7 @@ styleClass="dealer-box" fitWidth="100" fitHeight="100" + GridPane.columnIndex="2" preserveRatio="true" visible="false"/> @@ -117,56 +120,48 @@ + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 48476a67f36af699231660244c326a3a081aaaa6 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Thu, 23 Apr 2026 19:59:55 +0200 Subject: [PATCH 16/33] Fix: Chat UI initialization improved in Game UI --- .../ui/gameui/CasinoGameController.java | 39 +++++-------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index fe0f782..3a89aef 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -278,37 +278,18 @@ public class CasinoGameController { } try { - ChatBoxController chatBoxController = new ChatBoxController(chatUsername, chatController); - - chatController.setChatBoxController(chatBoxController); - + String username = chatUsername; + // chatController = new ChatController(username, chatClientService); URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); - loader.setController(chatBoxController); - - chatBoxController.loadChats(); - - Node chatBox = loader.load(); - - chatContainer.getChildren().add(chatBox); - /* - Scene scene = new Scene(root); - chatStage.setScene(scene); - - chatStage.setWidth(CHAT_WIDTH); - chatStage.setHeight(CHAT_HEIGHT); - - chatStage.setOnCloseRequest(event -> chatInitialized = false); - - chatStage.show(); - */ - - if (chatLobbyId >= 0) { - chatController.setLobbyChat(chatLobbyId); - } - - chatInitialized = true; - + loader.setController(chatController.getChatBoxController()); + Node chatNode = loader.load(); + chatContainer.getChildren().setAll(chatNode); + AnchorPane.setTopAnchor(chatNode, 6.0); + AnchorPane.setBottomAnchor(chatNode, 6.0); + AnchorPane.setLeftAnchor(chatNode, 0.0); + AnchorPane.setRightAnchor(chatNode, 6.0); + // gridManager.setChatController(chatController); } catch (IOException e) { LOGGER.warning("Could not initialize game chat UI: " + e.getMessage()); } From c2d0af03b77fce6aff3fe6d3c7b08def0b3dac88 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Fri, 24 Apr 2026 11:16:34 +0200 Subject: [PATCH 17/33] Fix: whisper chat not working in game UI --- .../client/ui/chatui/ChatBoxController.java | 5 +++++ .../ui/gameui/CasinoGameController.java | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 0947daf..4e0cd7f 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -250,6 +250,11 @@ public class ChatBoxController { ChatController.ChatKey key = new ChatController.ChatKey(ChatType.GLOBAL); ChatModel global = chatModelMap.get(key); ChatViewController globalController = chatController.activeChatControllers.get(key); + + for (String user : chatController.getLocalUserList()) { + addWhisperUser(user); + } + for (Message msg : global.messages) { globalController.showMessage(msg); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 3a89aef..07d4cd8 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -265,7 +265,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - //startChat(chatController); + initializeChatIfPossible(); } /** @@ -273,13 +273,16 @@ public class CasinoGameController { * is available and the chat has not already been initialized. */ private void initializeChatIfPossible() { - if (chatInitialized || chatClientService == null || chatUsername == null) { + if (chatInitialized + || chatClientService == null + || chatUsername == null + || chatController == null) { return; } try { - String username = chatUsername; - // chatController = new ChatController(username, chatClientService); + chatController.updateUsername(chatUsername); + URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); FXMLLoader loader = new FXMLLoader(resource); loader.setController(chatController.getChatBoxController()); @@ -289,7 +292,13 @@ public class CasinoGameController { AnchorPane.setBottomAnchor(chatNode, 6.0); AnchorPane.setLeftAnchor(chatNode, 0.0); AnchorPane.setRightAnchor(chatNode, 6.0); - // gridManager.setChatController(chatController); + + chatController.getChatBoxController().loadChats(); + chatInitialized = true; + + if (chatLobbyId >= 0) { + chatController.setLobbyChat(chatLobbyId); + } } catch (IOException e) { LOGGER.warning("Could not initialize game chat UI: " + e.getMessage()); } From 1d4cf2d8469474b28300191754c9fa59cb6e10eb Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Fri, 24 Apr 2026 11:46:32 +0200 Subject: [PATCH 18/33] Style: Apply Spotless --- .../cs108/casono/client/chat/ChatController.java | 7 ++++--- .../casono/client/ui/chatui/ChatBoxController.java | 14 ++++++-------- .../client/ui/gameui/CasinoGameController.java | 12 ++++-------- .../casono/client/ui/gameui/CasinoGameUI.java | 6 ++++-- .../client/ui/lobbyui/LobbyButtonGridManager.java | 6 ++++-- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index 41c5f37..09d7838 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -15,7 +16,6 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; -import java.util.HashMap; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,7 +41,9 @@ public class ChatController { return chatBoxController; } - public void setChatBoxController(ChatBoxController chatBoxController) {this.chatBoxController = chatBoxController; } + public void setChatBoxController(ChatBoxController chatBoxController) { + this.chatBoxController = chatBoxController; + } private ChatBoxController chatBoxController; private int lobbyId = -1; @@ -71,7 +73,6 @@ public class ChatController { public final Map activeChatControllers; - private final Logger logger; /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 4e0cd7f..26aa545 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -3,6 +3,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import java.io.IOException; import java.net.URL; import java.util.ArrayList; @@ -11,8 +12,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; - -import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -45,7 +44,6 @@ public class ChatBoxController { @FXML private final Map usernameTabMap; - /** * Constructor for the ChatBoxController, initializes the necessary fields and data structures * for managing chat tabs and whisper chats. @@ -76,9 +74,7 @@ public class ChatBoxController { public void initialize() { ChatType global = ChatType.GLOBAL; ChatModel globalChatModel = new ChatModel(global, username, -1, null); - chatController - .getChatModelMap() - .put(new ChatController.ChatKey(global), globalChatModel); + chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel); addChatTab("GLOBAL", globalChatModel, global); addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show()); } @@ -197,7 +193,8 @@ public class ChatBoxController { ChatViewController chatViewController = new ChatViewController( this.chatController, chatModel, this.username); - chatController.activeChatControllers.put(new ChatController.ChatKey(chatType), chatViewController); + chatController.activeChatControllers.put( + new ChatController.ChatKey(chatType), chatViewController); fxmlLoader.setController(chatViewController); Node load = fxmlLoader.load(); VBox.setVgrow(load, Priority.ALWAYS); @@ -259,7 +256,8 @@ public class ChatBoxController { globalController.showMessage(msg); } for (String user : chatController.getLocalUserList()) { - ChatController.ChatKey whisperUserKey = new ChatController.ChatKey(ChatType.WHISPER, user); + ChatController.ChatKey whisperUserKey = + new ChatController.ChatKey(ChatType.WHISPER, user); if (chatModelMap.containsKey(whisperUserKey)) { ChatModel whisperChatModel = chatModelMap.get(whisperUserKey); addWhisperChat(user, whisperChatModel); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 07d4cd8..eabb465 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -7,7 +7,6 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; -import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; import java.io.IOException; @@ -20,15 +19,12 @@ import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafx.stage.Stage; /** * Controller for the casino gaming area. @@ -71,6 +67,7 @@ public class CasinoGameController { private int lastPot = Integer.MIN_VALUE; private ChatController chatController; + public static final double CHAT_CONTAINER_CONSTANT = 6.0; private static final int TOTAL_SLOTS = 5; private static final int PLAYER_SLOTS = 2; private int pot = 0; @@ -288,10 +285,10 @@ public class CasinoGameController { loader.setController(chatController.getChatBoxController()); Node chatNode = loader.load(); chatContainer.getChildren().setAll(chatNode); - AnchorPane.setTopAnchor(chatNode, 6.0); - AnchorPane.setBottomAnchor(chatNode, 6.0); + AnchorPane.setTopAnchor(chatNode, CHAT_CONTAINER_CONSTANT); + AnchorPane.setBottomAnchor(chatNode, CHAT_CONTAINER_CONSTANT); AnchorPane.setLeftAnchor(chatNode, 0.0); - AnchorPane.setRightAnchor(chatNode, 6.0); + AnchorPane.setRightAnchor(chatNode, CHAT_CONTAINER_CONSTANT); chatController.getChatBoxController().loadChats(); chatInitialized = true; @@ -1338,5 +1335,4 @@ public class CasinoGameController { } return null; } - } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 76f55db..c82dfe1 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -1,7 +1,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; -import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; @@ -58,7 +58,9 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } - public static void setChatController(ChatController chatController) {CasinoGameUI.chatController = chatController;} + public static void setChatController(ChatController chatController) { + CasinoGameUI.chatController = chatController; + } /** * Sets the lobby ID to be used by the application. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index 719dc98..c2c5163 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -1,7 +1,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; -import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI; @@ -591,5 +591,7 @@ public class LobbyButtonGridManager { return lobbyClient; } - public void setChatController(ChatController chatController) { this.chatController = chatController; } + public void setChatController(ChatController chatController) { + this.chatController = chatController; + } } From 88b797ba1d8b320c4b60086673fe2c63fe740e40 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Wed, 22 Apr 2026 14:45:41 +0200 Subject: [PATCH 19/33] Feat: Add field ChatController to LobbyButtonGridManager --- .../casono/client/ui/lobbyui/LobbyButtonGridManager.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index c2c5163..2c0d3ba 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -591,7 +591,5 @@ public class LobbyButtonGridManager { return lobbyClient; } - public void setChatController(ChatController chatController) { - this.chatController = chatController; - } + public void setChatController(ChatController chatController) { this.chatController = chatController; } } From 277b776fecdc7bdfd479bb3b501d47a9f3d9f8c3 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Wed, 22 Apr 2026 15:04:10 +0200 Subject: [PATCH 20/33] Feat: Add chat box into game ui --- .../casono/client/ui/gameui/CasinoGameController.java | 6 +++++- .../dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index eabb465..56bab62 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; +import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; import java.io.IOException; @@ -19,12 +20,15 @@ import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import javafx.stage.Stage; /** * Controller for the casino gaming area. @@ -262,7 +266,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - initializeChatIfPossible(); + //initializeChatIfPossible(); } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index c82dfe1..a392fcc 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; @@ -71,6 +72,10 @@ public class CasinoGameUI extends Application { CasinoGameUI.lobbyId = lobbyId; } + public static void setChatController(ChatController chatController) { + CasinoGameUI.chatController = chatController; + } + /** * The main entry point for the JavaFX application. This method is called after the application * is @@ -119,6 +124,8 @@ public class CasinoGameUI extends Application { Parent root = fxmlLoader.load(); CasinoGameController controller = fxmlLoader.getController(); + controller.startChat(chatController); + if (lobbyId <= 0) { throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()"); } From 40e558be18c0856fff9d672835c35bb21eaac799 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:57:48 +0200 Subject: [PATCH 21/33] Feat: Add chat box into the game screen Refs #107 --- .../cs108/casono/client/ui/gameui/CasinoGameController.java | 3 ++- .../dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 56bab62..b5849f9 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -143,6 +143,7 @@ public class CasinoGameController { private static final double CHAT_WIDTH = 400; private static final double CHAT_HEIGHT = 600; + private ChatController chatController; private String chatUsername; private ClientService chatClientService; private int chatLobbyId = -1; @@ -266,7 +267,7 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - //initializeChatIfPossible(); + //startChat(chatController); } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index a392fcc..3f39656 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -59,9 +59,6 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } - public static void setChatController(ChatController chatController) { - CasinoGameUI.chatController = chatController; - } /** * Sets the lobby ID to be used by the application. @@ -124,8 +121,6 @@ public class CasinoGameUI extends Application { Parent root = fxmlLoader.load(); CasinoGameController controller = fxmlLoader.getController(); - controller.startChat(chatController); - if (lobbyId <= 0) { throw new IllegalStateException("CasinoGameUI: lobbyId must be set before start()"); } From b1d099f102fbb637f054cbfae66b182193d3e405 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Thu, 23 Apr 2026 11:59:48 +0200 Subject: [PATCH 22/33] Feat: Add load method to restore all chat message of a chat Refs #107 --- .../dmi/dbis/cs108/casono/client/chat/ChatController.java | 4 +--- .../cs108/casono/client/ui/chatui/ChatBoxController.java | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index 09d7838..aeb0680 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -41,9 +41,7 @@ public class ChatController { return chatBoxController; } - public void setChatBoxController(ChatBoxController chatBoxController) { - this.chatBoxController = chatBoxController; - } + public void setChatBoxController(ChatBoxController chatBoxController) {this.chatBoxController = chatBoxController; } private ChatBoxController chatBoxController; private int lobbyId = -1; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 26aa545..735f22d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -3,7 +3,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType; -import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import java.io.IOException; import java.net.URL; import java.util.ArrayList; @@ -12,6 +11,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; + +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -74,7 +75,9 @@ public class ChatBoxController { public void initialize() { ChatType global = ChatType.GLOBAL; ChatModel globalChatModel = new ChatModel(global, username, -1, null); - chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel); + chatController + .getChatModelMap() + .put(new ChatController.ChatKey(global), globalChatModel); addChatTab("GLOBAL", globalChatModel, global); addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show()); } From 0f4d048b767023d0bb5e1b713433f4c39c474105 Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Thu, 23 Apr 2026 19:59:55 +0200 Subject: [PATCH 23/33] Fix: Chat UI initialization improved in Game UI --- .../dbis/cs108/casono/client/ui/gameui/CasinoGameController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index b5849f9..4e3c431 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -143,7 +143,6 @@ public class CasinoGameController { private static final double CHAT_WIDTH = 400; private static final double CHAT_HEIGHT = 600; - private ChatController chatController; private String chatUsername; private ClientService chatClientService; private int chatLobbyId = -1; From 5e91cefabb8ba750327a1bfd43ef353edffedbce Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Fri, 24 Apr 2026 11:16:34 +0200 Subject: [PATCH 24/33] Fix: whisper chat not working in game UI --- .../dbis/cs108/casono/client/ui/gameui/CasinoGameController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 4e3c431..0eef2d1 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -1339,4 +1339,5 @@ public class CasinoGameController { } return null; } + } From 01ecbb0497db43fc4f038f3c07410bd5b7797df7 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Fri, 24 Apr 2026 11:46:32 +0200 Subject: [PATCH 25/33] Style: Apply Spotless --- .../dmi/dbis/cs108/casono/client/chat/ChatController.java | 4 +++- .../cs108/casono/client/ui/chatui/ChatBoxController.java | 7 ++----- .../casono/client/ui/gameui/CasinoGameController.java | 5 ----- .../dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java | 6 +----- .../casono/client/ui/lobbyui/LobbyButtonGridManager.java | 4 +++- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index aeb0680..09d7838 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -41,7 +41,9 @@ public class ChatController { return chatBoxController; } - public void setChatBoxController(ChatBoxController chatBoxController) {this.chatBoxController = chatBoxController; } + public void setChatBoxController(ChatBoxController chatBoxController) { + this.chatBoxController = chatBoxController; + } private ChatBoxController chatBoxController; private int lobbyId = -1; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java index 735f22d..26aa545 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatBoxController.java @@ -3,6 +3,7 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatType; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import java.io.IOException; import java.net.URL; import java.util.ArrayList; @@ -11,8 +12,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; - -import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -75,9 +74,7 @@ public class ChatBoxController { public void initialize() { ChatType global = ChatType.GLOBAL; ChatModel globalChatModel = new ChatModel(global, username, -1, null); - chatController - .getChatModelMap() - .put(new ChatController.ChatKey(global), globalChatModel); + chatController.getChatModelMap().put(new ChatController.ChatKey(global), globalChatModel); addChatTab("GLOBAL", globalChatModel, global); addWhisperChatButton.setOnAction(event -> addWhisperChatButton.show()); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 0eef2d1..6826032 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -7,7 +7,6 @@ import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; -import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; import java.io.IOException; @@ -20,15 +19,12 @@ import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafx.stage.Stage; /** * Controller for the casino gaming area. @@ -1339,5 +1335,4 @@ public class CasinoGameController { } return null; } - } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 3f39656..c278c84 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -1,6 +1,5 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; -import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; @@ -59,6 +58,7 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } + public static void setChatController(ChatController chatController) {CasinoGameUI.chatController = chatController;} /** * Sets the lobby ID to be used by the application. @@ -69,10 +69,6 @@ public class CasinoGameUI extends Application { CasinoGameUI.lobbyId = lobbyId; } - public static void setChatController(ChatController chatController) { - CasinoGameUI.chatController = chatController; - } - /** * The main entry point for the JavaFX application. This method is called after the application * is diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index 2c0d3ba..c2c5163 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -591,5 +591,7 @@ public class LobbyButtonGridManager { return lobbyClient; } - public void setChatController(ChatController chatController) { this.chatController = chatController; } + public void setChatController(ChatController chatController) { + this.chatController = chatController; + } } From 4a3c885b682f70c95564963c49af31fac3ad7756 Mon Sep 17 00:00:00 2001 From: Mathis Ginkel Date: Fri, 24 Apr 2026 14:04:12 +0200 Subject: [PATCH 26/33] Style: Apply Spotless --- .../cs108/casono/client/ui/gameui/CasinoGameController.java | 1 - .../dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index 6826032..f3ab44d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -262,7 +262,6 @@ public class CasinoGameController { this.chatUsername = username; this.chatClientService = clientService; this.chatLobbyId = lobbyId; - //startChat(chatController); } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index c278c84..c82dfe1 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -58,7 +58,9 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } - public static void setChatController(ChatController chatController) {CasinoGameUI.chatController = chatController;} + public static void setChatController(ChatController chatController) { + CasinoGameUI.chatController = chatController; + } /** * Sets the lobby ID to be used by the application. From c04d4fca768b4d61aca681acb88e9e554f741a35 Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Fri, 24 Apr 2026 14:14:59 +0200 Subject: [PATCH 27/33] Refactor: Add LobbyCleanupJob for periodic expired lobby cleanup - isolate failures per lobby during cleanup - notify connected sessions about closed lobbies Refs #124 Co-authored-by: Copilot --- .../server/domain/lobby/LobbyCleanupJob.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyCleanupJob.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyCleanupJob.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyCleanupJob.java new file mode 100644 index 0000000..47c0413 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyCleanupJob.java @@ -0,0 +1,100 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; +import java.time.Duration; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** Periodically removes expired empty lobbies and notifies connected sessions. */ +public class LobbyCleanupJob implements Runnable { + + private static final Logger LOGGER = LogManager.getLogger(LobbyCleanupJob.class); + + private final LobbyManager lobbyManager; + private final SessionManager sessionManager; + private final ResponseDispatcher responseDispatcher; + private final Duration expiryThreshold; + + /** + * Creates a new cleanup job for expired empty lobbies. + * + * @param lobbyManager manager used to find and remove expired lobbies + * @param sessionManager manager used to resolve connected sessions + * @param responseDispatcher dispatcher used to notify clients about closed lobbies + * @param expiryThreshold age threshold that marks an empty lobby as expired + */ + public LobbyCleanupJob( + LobbyManager lobbyManager, + SessionManager sessionManager, + ResponseDispatcher responseDispatcher, + Duration expiryThreshold) { + this.lobbyManager = lobbyManager; + this.sessionManager = sessionManager; + this.responseDispatcher = responseDispatcher; + this.expiryThreshold = expiryThreshold; + } + + /** + * Runs one cleanup cycle. + * + *

The method first fetches all expired empty lobbies. Each lobby is then processed + * independently so that a failure for one lobby does not stop the remaining cleanups. + */ + @Override + public void run() { + LOGGER.debug("Job started."); + try { + List expired; + try { + expired = lobbyManager.findEmptyLobbiesOlderThan(expiryThreshold); + } catch (Exception e) { + LOGGER.error("Lobby expiry job failed: could not fetch expired lobbies", e); + return; + } + + for (LobbyId lobbyId : expired) { + try { + lobbyManager.removeLobby(lobbyId); + broadcastLobbyClosed(lobbyId); + } catch (RuntimeException e) { + LOGGER.warn("Failed to process expired lobby {}", lobbyId.value(), e); + } + } + } finally { + LOGGER.debug("Job finished."); + } + } + + /** + * Broadcasts a lobby-closed event to all currently connected sessions. + * + * @param lobbyId id of the lobby that was closed + */ + private void broadcastLobbyClosed(LobbyId lobbyId) { + for (Session session : sessionManager.getAllSessions()) { + try { + RequestContext ctx = new RequestContext(session.getId(), 0); + SuccessResponse event = + new SuccessResponse( + ctx, + ResponseBody.builder() + .param("EVENT", "LOBBY_CLOSED") + .param("LOBBY_ID", lobbyId.value()) + .build()) {}; + responseDispatcher.dispatch(event); + } catch (RuntimeException e) { + LOGGER.warn( + "Failed to notify session {} about closed lobby {}", + session.getId().value(), + lobbyId.value(), + e); + } + } + } +} From 9023263b012c8873ffd0cf230fd618a73883c4db Mon Sep 17 00:00:00 2001 From: Jona Walpert Date: Fri, 24 Apr 2026 14:16:12 +0200 Subject: [PATCH 28/33] Refacor: Schedule LobbyCleanupJob instead of cleanup lambda Refs #124 Co-authored-by: Copilot --- .../dbis/cs108/casono/server/ServerApp.java | 40 +++---------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index f93ef7c..a9a46c7 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -63,6 +63,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.send_message.SendMessageRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyCleanupJob; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.LobbyManager; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; @@ -73,11 +74,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandR import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher; import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; -import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; -import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse; -import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; -import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionDisconnectJob; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; import java.time.Duration; @@ -141,37 +138,12 @@ public class ServerApp { userRegistry, new CommandContext(lobbyManager, sessionManager)); - // Periodic cleanup: remove empty lobbies older than 30s and notify affected - // users scheduler.scheduleAtFixedRate( - () -> { - try { - var expired = - lobbyManager.findEmptyLobbiesOlderThan( - Duration.ofSeconds(LOBBY_EXPIRY_SECONDS)); - for (var lid : expired) { - // remove lobby from manager first - lobbyManager.removeLobby(lid); - - // broadcast LOBBY_CLOSED event to all connected sessions - // (requestId=0) - for (Session s : sessionManager.getAllSessions()) { - RequestContext ctx = new RequestContext(s.getId(), 0); - SuccessResponse ev = - new SuccessResponse( - ctx, - ResponseBody.builder() - .param("EVENT", "LOBBY_CLOSED") - .param("LOBBY_ID", lid.value()) - .build()) {}; - - responseDispatcher.dispatch(ev); - } - } - } catch (Exception e) { - logger.warn("Lobby expiry job failed", e); - } - }, + new LobbyCleanupJob( + lobbyManager, + sessionManager, + responseDispatcher, + Duration.ofSeconds(LOBBY_EXPIRY_SECONDS)), LOBBY_CLEANUP_INITIAL_DELAY_SECONDS, LOBBY_CLEANUP_PERIOD_SECONDS, TimeUnit.SECONDS); From d49f18c3aa2343e20665b85ffb79292e8c24cb7c Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 25 Apr 2026 10:44:09 +0200 Subject: [PATCH 29/33] Test: Unit tests for Tokenizer --- .../parser/tokenizer/TokenizerTest.java | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java new file mode 100644 index 0000000..5c5a2d3 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java @@ -0,0 +1,204 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class TokenizerTest { + @Test + void testEofAlwaysPresent() { + List tokens = Tokenizer.tokenize(""); + + assertEquals(1, tokens.size()); + assertEquals(RawTokenType.EOF, tokens.get(0).type()); + } + + @Test + void testSimpleCommand() { + List tokens = Tokenizer.tokenize("PING"); + assertEquals(2, tokens.size()); + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("PING", tokens.get(0).value()); + assertEquals(RawTokenType.EOF, tokens.get(1).type()); + } + + @Test + void testSimpleCommandWithUnderscore() { + List tokens = Tokenizer.tokenize("JOIN_GAME"); + assertEquals(2, tokens.size()); + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("JOIN_GAME", tokens.get(0).value()); + assertEquals(RawTokenType.EOF, tokens.get(1).type()); + } + + @Test + void testCommandWithOneParameter() { + List tokens = Tokenizer.tokenize("GET VALUE=42"); + assertEquals(5, tokens.size()); + + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("GET", tokens.get(0).value()); + + // Parameter + assertEquals(RawTokenType.WORD, tokens.get(1).type()); + assertEquals("VALUE", tokens.get(1).value()); + + assertEquals(RawTokenType.SEPARATOR, tokens.get(2).type()); + assertEquals("=", tokens.get(2).value()); + + assertEquals(RawTokenType.WORD, tokens.get(3).type()); + assertEquals("42", tokens.get(3).value()); + + assertEquals(RawTokenType.EOF, tokens.get(4).type()); + } + + @Test + void testCommandWithOneParameterWhitespacesBetweenSeperator() { + List tokens = Tokenizer.tokenize("GET VALUE = 42"); + assertEquals(5, tokens.size()); + + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("GET", tokens.get(0).value()); + + // Parameter + assertEquals(RawTokenType.WORD, tokens.get(1).type()); + assertEquals("VALUE", tokens.get(1).value()); + + assertEquals(RawTokenType.SEPARATOR, tokens.get(2).type()); + assertEquals("=", tokens.get(2).value()); + + assertEquals(RawTokenType.WORD, tokens.get(3).type()); + assertEquals("42", tokens.get(3).value()); + + assertEquals(RawTokenType.EOF, tokens.get(4).type()); + } + + @Test + void testCommandWithOneParameterAndNewline() { + List tokens = Tokenizer.tokenize("GET\nVALUE=42"); + assertEquals(6, tokens.size()); + + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("GET", tokens.get(0).value()); + + // Parameter + assertEquals(RawTokenType.WORD, tokens.get(2).type()); + assertEquals("VALUE", tokens.get(2).value()); + + assertEquals(RawTokenType.SEPARATOR, tokens.get(3).type()); + assertEquals("=", tokens.get(3).value()); + + assertEquals(RawTokenType.WORD, tokens.get(4).type()); + assertEquals("42", tokens.get(4).value()); + + assertEquals(RawTokenType.EOF, tokens.get(5).type()); + } + + @Test + void testCommandWithStringParameter() { + List tokens = Tokenizer.tokenize("GREET MSG='Hello World'"); + assertEquals(5, tokens.size()); + + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("GREET", tokens.get(0).value()); + + // First parameter + assertEquals(RawTokenType.WORD, tokens.get(1).type()); + assertEquals("MSG", tokens.get(1).value()); + + assertEquals(RawTokenType.SEPARATOR, tokens.get(2).type()); + assertEquals("=", tokens.get(2).value()); + + assertEquals(RawTokenType.STRING, tokens.get(3).type()); + assertEquals("Hello World", tokens.get(3).value()); + + assertEquals(RawTokenType.EOF, tokens.get(4).type()); + } + + @Test + void testCommandWithMultilineStringParameter() { + List tokens = Tokenizer.tokenize("GREET MSG='Hello\nWorld'"); + assertEquals(5, tokens.size()); + + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("GREET", tokens.get(0).value()); + + // First parameter + assertEquals(RawTokenType.WORD, tokens.get(1).type()); + assertEquals("MSG", tokens.get(1).value()); + + assertEquals(RawTokenType.SEPARATOR, tokens.get(2).type()); + assertEquals("=", tokens.get(2).value()); + + assertEquals(RawTokenType.STRING, tokens.get(3).type()); + assertEquals("Hello\nWorld", tokens.get(3).value()); + + assertEquals(RawTokenType.EOF, tokens.get(4).type()); + } + + @Test + void testWhitespace() { + List tokens = Tokenizer.tokenize(" \t PING"); + assertEquals(2, tokens.size()); + assertEquals(RawTokenType.WORD, tokens.get(0).type()); + assertEquals("PING", tokens.get(0).value()); + } + + @Test + void testStringWithEscapedQuote() { + List tokens = Tokenizer.tokenize("WONDERFUL_GREETING MSG='it\\'s a wonderful day'"); + assertEquals(5, tokens.size()); + assertEquals(RawTokenType.STRING, tokens.get(3).type()); + assertEquals("it's a wonderful day", tokens.get(3).value()); + } + + @Test + void testStringWithWronglyEscapedQuote() { + TokenizerException ex = assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("WONDERFUL_GREETING MSG='it\'s a wonderful day'")); + assertEquals("Unterminated string literal", ex.getMessage()); + } + + @Test + void testStringWithEscapedBackslash() { + List tokens = Tokenizer.tokenize("EXECUTE_COMMAND CMD='\\whoami'"); + assertEquals(5, tokens.size()); + assertEquals(RawTokenType.STRING, tokens.get(3).type()); + assertEquals("\\whoami", tokens.get(3).value()); + } + + @Test + void testColumnTracking() { + List tokens = Tokenizer.tokenize("GET VAL=1"); + assertEquals(5, tokens.size()); + assertEquals(1, tokens.get(0).column()); + assertEquals(5, tokens.get(1).column()); + assertEquals(8, tokens.get(2).column()); + assertEquals(9, tokens.get(3).column()); + } + + @Test + void testUnterminatedStringThrows() { + TokenizerException ex = assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("GREETING = 'unclosed")); + assertEquals(1, ex.getLine()); + assertEquals(12, ex.getColumn()); + assertTrue(ex.getMessage().contains("Unterminated string literal")); + } + + @Test + void testUnexpectedCharacterThrows() { + TokenizerException ex = assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("CMD @ KEY=VALUE")); + assertTrue(ex.getMessage().contains("Unexpected character '@'")); + assertEquals(1, ex.getLine()); + } + + @Test + void testEmptyStringValue() { + List tokens = Tokenizer.tokenize("cmd = ''"); + assertEquals(4, tokens.size()); + assertEquals(RawTokenType.STRING, tokens.get(2).type()); + assertEquals("", tokens.get(2).value()); + } +} From 4bfb59f234b54de0a2c2a14bae85ca8822cd710d Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 25 Apr 2026 13:28:10 +0200 Subject: [PATCH 30/33] Test: Unit tests for TokenClassifier Co-authored-by: Copilot --- .../parser/tokenizer/TokenClassifierTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java new file mode 100644 index 0000000..9a3dc21 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java @@ -0,0 +1,85 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.parser.tokenizer; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TokenClassifierTest { + @Test + void testClassifySimpleCommand() { + List raw = Tokenizer.tokenize("PING"); + List tokens = TokenClassifier.classify(raw); + + assertEquals(2, tokens.size()); + assertEquals(TokenType.COMMAND, tokens.get(0).type()); + assertEquals("PING", tokens.get(0).value()); + assertEquals(TokenType.EOF, tokens.get(1).type()); + } + + @Test + void testCommandWithParameter() { + List raw = Tokenizer.tokenize("ANSWER VALUE=42"); + List tokens = TokenClassifier.classify(raw); + + assertEquals(5, tokens.size()); + assertEquals(TokenType.COMMAND, tokens.get(0).type()); + assertEquals(TokenType.KEY, tokens.get(1).type()); + assertEquals(TokenType.SEPARATOR, tokens.get(2).type()); + assertEquals(TokenType.VALUE, tokens.get(3).type()); + assertEquals("42", tokens.get(3).value()); + assertEquals(TokenType.EOF, tokens.get(4).type()); + } + + @Test + void testStringValue() { + List raw = Tokenizer.tokenize("GREET MSG='Hello World'"); + List tokens = TokenClassifier.classify(raw); + + assertEquals(5, tokens.size()); + assertEquals(TokenType.COMMAND, tokens.get(0).type()); + assertEquals(TokenType.KEY, tokens.get(1).type()); + assertEquals(TokenType.SEPARATOR, tokens.get(2).type()); + assertEquals(TokenType.VALUE, tokens.get(3).type()); + assertEquals("Hello World", tokens.get(3).value()); + } + + @Test + void testUnexpectedStringLiteralThrows() { + List raw = Tokenizer.tokenize("CMD 'oops'"); + + TokenizerException ex = + assertThrows(TokenizerException.class, () -> TokenClassifier.classify(raw)); + assertTrue(ex.getMessage().contains("Unexpected string literal")); + } + + @Test + void testMissingValueAfterSeparatorThrows() { + List raw = Tokenizer.tokenize("CMD KEY="); + + TokenizerException ex = + assertThrows(TokenizerException.class, () -> TokenClassifier.classify(raw)); + assertEquals("Expected VALUE after '='", ex.getMessage()); + } + + @Test + void testNextWordIsKeyThrows() { + List raw = Tokenizer.tokenize("CMD KEY1=KEY2=42"); + + TokenizerException ex = + assertThrows(TokenizerException.class, () -> TokenClassifier.classify(raw)); + assertEquals("Expected VALUE after '='", ex.getMessage()); + } + + @Test + void testEmptyRawTokensThrows() { + List raw = new ArrayList<>(); + + TokenizerException ex = + assertThrows(TokenizerException.class, () -> TokenClassifier.classify(raw)); + assertEquals("Expected COMMAND as first token", ex.getMessage()); + assertEquals(1, ex.getLine()); + assertEquals(1, ex.getColumn()); + } +} From 2190fccd8f990e9de0bc75b0cc35b93058ef80fb Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 25 Apr 2026 13:28:34 +0200 Subject: [PATCH 31/33] Style: Apply Spotless --- .../protocol/parser/tokenizer/TokenizerTest.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java index 5c5a2d3..bd5e486 100644 --- a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenizerTest.java @@ -149,7 +149,8 @@ class TokenizerTest { @Test void testStringWithEscapedQuote() { - List tokens = Tokenizer.tokenize("WONDERFUL_GREETING MSG='it\\'s a wonderful day'"); + List tokens = + Tokenizer.tokenize("WONDERFUL_GREETING MSG='it\\'s a wonderful day'"); assertEquals(5, tokens.size()); assertEquals(RawTokenType.STRING, tokens.get(3).type()); assertEquals("it's a wonderful day", tokens.get(3).value()); @@ -157,7 +158,10 @@ class TokenizerTest { @Test void testStringWithWronglyEscapedQuote() { - TokenizerException ex = assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("WONDERFUL_GREETING MSG='it\'s a wonderful day'")); + TokenizerException ex = + assertThrows( + TokenizerException.class, + () -> Tokenizer.tokenize("WONDERFUL_GREETING MSG='it\'s a wonderful day'")); assertEquals("Unterminated string literal", ex.getMessage()); } @@ -181,7 +185,9 @@ class TokenizerTest { @Test void testUnterminatedStringThrows() { - TokenizerException ex = assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("GREETING = 'unclosed")); + TokenizerException ex = + assertThrows( + TokenizerException.class, () -> Tokenizer.tokenize("GREETING = 'unclosed")); assertEquals(1, ex.getLine()); assertEquals(12, ex.getColumn()); assertTrue(ex.getMessage().contains("Unterminated string literal")); @@ -189,7 +195,8 @@ class TokenizerTest { @Test void testUnexpectedCharacterThrows() { - TokenizerException ex = assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("CMD @ KEY=VALUE")); + TokenizerException ex = + assertThrows(TokenizerException.class, () -> Tokenizer.tokenize("CMD @ KEY=VALUE")); assertTrue(ex.getMessage().contains("Unexpected character '@'")); assertEquals(1, ex.getLine()); } From 89fd6aef05a990f553b63fec1f4f625be963c496 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 25 Apr 2026 14:26:22 +0200 Subject: [PATCH 32/33] Fix: Detect missing key when validating separator --- .../network/protocol/parser/tokenizer/TokenClassifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifier.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifier.java index 330dcbb..2545fc3 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifier.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifier.java @@ -48,7 +48,7 @@ public class TokenClassifier { } private static void validateSeparator(List rawTokens, int index) { - boolean missingKey = index == 0 || rawTokens.get(index - 1).type() != RawTokenType.WORD; + boolean missingKey = index <= 1 || rawTokens.get(index - 1).type() != RawTokenType.WORD; boolean missingValue = index + 1 >= rawTokens.size() From 572b66889da91e6c33e0ef6d3f6fa6a9086e019d Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 25 Apr 2026 14:27:42 +0200 Subject: [PATCH 33/33] Test: Add test for missing key before separator in TokenClassifier --- .../protocol/parser/tokenizer/TokenClassifierTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java index 9a3dc21..8c8748f 100644 --- a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/server/network/protocol/parser/tokenizer/TokenClassifierTest.java @@ -63,6 +63,15 @@ public class TokenClassifierTest { assertEquals("Expected VALUE after '='", ex.getMessage()); } + @Test + void testMissingKeyBeforeSeparatorThrows() { + List raw = Tokenizer.tokenize("CMD =VALUE"); + + TokenizerException ex = + assertThrows(TokenizerException.class, () -> TokenClassifier.classify(raw)); + assertEquals("Expected KEY before '='", ex.getMessage()); + } + @Test void testNextWordIsKeyThrows() { List raw = Tokenizer.tokenize("CMD KEY1=KEY2=42");