diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java index 8a237b5..2cd1df0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ClientApp.java @@ -39,6 +39,10 @@ public class ClientApp { return sharedUsername; } + public static void updateSharedUsername(String username) { + setSharedUsername(username != null && !username.isBlank() ? username.trim() : null); + } + private static void setSharedUsername(String username) { sharedUsername = username; LOGGER.info("sharedUsername set to '{}'", getSharedUsername()); 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 d3ac3c0..b07c712 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 @@ -1,15 +1,20 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; +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.server.network.command.parsing.RequestParameter; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.WeakHashMap; +import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jspecify.annotations.Nullable; @@ -24,7 +29,7 @@ public class ChatController { private static final Map ACTIVE_CONTROLLERS = new WeakHashMap<>(); - private final String username; + private volatile String username; private final ClientService clientService; @@ -37,6 +42,7 @@ public class ChatController { private final ChatBoxController chatBoxController; private int lobbyId = -1; private final Timer timer; + private final Consumer> serverEventListener; public record ChatKey(ChatType type, @Nullable String targetUser) { public ChatKey(ChatType type) { @@ -71,8 +77,10 @@ public class ChatController { localUserList = new ArrayList<>(); this.chatBoxController = new ChatBoxController(username, this); this.logger = LogManager.getLogger(ChatController.class); + this.serverEventListener = this::handleServerEvent; registerAsActiveController(clientService); + clientService.addEventListener(serverEventListener); this.timer = new Timer(true); timer.schedule( @@ -131,6 +139,7 @@ public class ChatController { *

All Messages get added to a particular {@link ChatModel}, if the checks passed. */ public void receiveMessage() { + String currentUsername = getCurrentUsername(); List newMessages = chatClient.getMessages(); if (!newMessages.isEmpty()) { for (Message msg : newMessages) { @@ -146,16 +155,17 @@ public class ChatController { (_key) -> new ChatModel( ChatType.LOBBY, - username, + currentUsername, msg.lobbyId, null)) .addMessage(msg); } break; case ChatType.WHISPER: - if (msg.target.equals(username) || msg.sender.equals(username)) { + if (msg.target.equals(currentUsername) + || msg.sender.equals(currentUsername)) { ChatKey key; - if (msg.target.equals(username)) { + if (msg.target.equals(currentUsername)) { key = new ChatKey(ChatType.WHISPER, msg.sender); } else { key = new ChatKey(ChatType.WHISPER, msg.target); @@ -166,7 +176,7 @@ public class ChatController { ChatModel chatModel = new ChatModel( ChatType.WHISPER, - username, + currentUsername, lobbyId, key.targetUser()); chatBoxController.addWhisperChat(key.targetUser(), chatModel); @@ -203,13 +213,29 @@ public class ChatController { */ public synchronized void checkWhisperUsers() { List users = chatClient.getUsers(); - logger.info(users); - if (!users.isEmpty()) { - for (String user : users) { - String value = user.split("\\=")[1]; - logger.info(value); - addWhisperUser(value); + Set remoteUsers = new HashSet<>(); + String currentUsername = getCurrentUsername(); + + for (String user : users) { + if (user == null || !user.contains("=")) { + continue; } + String value = user.split("\\=", 2)[1].trim(); + if (value.isBlank() || value.equals(currentUsername)) { + continue; + } + remoteUsers.add(value); + } + + for (String known : new ArrayList<>(localUserList)) { + if (!remoteUsers.contains(known)) { + localUserList.remove(known); + chatBoxController.removeWhisperUser(known); + } + } + + for (String remote : remoteUsers) { + addWhisperUser(remote); } } @@ -222,16 +248,110 @@ public class ChatController { if (user == null || user.isBlank()) { return; } - if (!(localUserList.contains(user) || user.equals(username))) { + if (!(localUserList.contains(user) || user.equals(getCurrentUsername()))) { localUserList.add(user); logger.info("adding new whisper user"); chatBoxController.addWhisperUser(user); } } + public synchronized void updateUsername(String newUsername) { + if (newUsername == null || newUsername.isBlank()) { + return; + } + String oldUsername = this.username; + this.username = newUsername.trim(); + chatBoxController.setUsername(this.username); + + if (oldUsername != null && !oldUsername.equals(this.username)) { + localUserList.remove(oldUsername); + chatBoxController.removeWhisperUser(oldUsername); + } + } + + private void handleServerEvent(List lines) { + if (lines == null || lines.isEmpty()) { + return; + } + + List params; + try { + params = ClientService.convertToRequestParameters(lines); + } catch (RuntimeException e) { + return; + } + String event = null; + String oldUsername = null; + String newUsername = null; + + for (RequestParameter p : params) { + if ("EVENT".equalsIgnoreCase(p.key())) { + event = p.value(); + } else if ("OLD_USERNAME".equalsIgnoreCase(p.key())) { + oldUsername = p.value(); + } else if ("NEW_USERNAME".equalsIgnoreCase(p.key())) { + newUsername = p.value(); + } + } + + if (!"USERNAME_CHANGED".equalsIgnoreCase(event)) { + return; + } + + applyUsernameMigration(oldUsername, newUsername); + } + + private synchronized void applyUsernameMigration(String oldUsername, String newUsername) { + if (oldUsername == null + || newUsername == null + || oldUsername.isBlank() + || newUsername.isBlank() + || oldUsername.equals(newUsername)) { + return; + } + + String currentUsername = getCurrentUsername(); + if (oldUsername.equals(currentUsername)) { + updateUsername(newUsername); + } + + ChatKey oldKey = new ChatKey(ChatType.WHISPER, oldUsername); + ChatKey newKey = new ChatKey(ChatType.WHISPER, newUsername); + + ChatModel oldModel = chatModelMap.remove(oldKey); + ChatModel existingNewModel = chatModelMap.get(newKey); + if (oldModel != null) { + oldModel.setTarget(newUsername); + if (existingNewModel == null) { + chatModelMap.put(newKey, oldModel); + } else { + // Merge possible parallel history into the already existing new-key model. + for (Message msg : oldModel.messages) { + existingNewModel.addMessage(msg); + } + } + chatBoxController.renameWhisperUser(oldUsername, newUsername); + } + + localUserList.remove(oldUsername); + chatBoxController.removeWhisperUser(oldUsername); + if (!newUsername.equals(getCurrentUsername())) { + addWhisperUser(newUsername); + } + } + + public String getCurrentUsername() { + String shared = ClientApp.getSharedUsername(); + if (shared != null && !shared.isBlank()) { + return shared.trim(); + } + return username; + } + /** Stops polling background tasks for this chat controller instance. */ public void shutdown() { timer.cancel(); + clientService.removeEventListener(serverEventListener); synchronized (ACTIVE_CONTROLLERS) { if (ACTIVE_CONTROLLERS.get(clientService) == this) { ACTIVE_CONTROLLERS.remove(clientService); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java index 69cdecb..6cbca3c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatModel.java @@ -22,7 +22,7 @@ public class ChatModel { public final String username; /** The person to send the message to If the chat is a whisper chat */ - private final String target; + private String target; private final IntegerProperty count; @@ -82,4 +82,8 @@ public class ChatModel { public String getTarget() { return target; } + + public void setTarget(String target) { + this.target = target; + } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java index 0e9ae35..82cde9e 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/LobbyClient.java @@ -131,6 +131,29 @@ public class LobbyClient { return new LoginResult(assigned, id); } + /** + * Changes the username for the currently logged-in session. + * + * @param newUsername desired new username + * @return a {@link LoginResult} containing assigned username and id as returned by the server + */ + public LoginResult changeUsername(String newUsername) { + List lines = client.processCommand("CHANGE_USERNAME USERNAME=" + newUsername); + + List params = ClientService.convertToRequestParameters(lines); + + String assigned = newUsername; + String id = null; + for (RequestParameter p : params) { + if ("USERNAME".equalsIgnoreCase(p.key())) { + assigned = p.value(); + } else if ("ID".equalsIgnoreCase(p.key())) { + id = p.value(); + } + } + return new LoginResult(assigned, id); + } + /** * Request the server for the list of available lobbies. * 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 addd9fa..1864601 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 @@ -60,6 +60,12 @@ public class ChatBoxController { usernameTabMap = new HashMap<>(); } + public void setUsername(String username) { + if (username != null && !username.isBlank()) { + this.username = username.trim(); + } + } + /** * Initializes the chat interface by creating the global chat model and adding the corresponding * "GLOBAL" tab to the interface. It also registers the global chat in the {@link @@ -84,6 +90,11 @@ public class ChatBoxController { public void addWhisperUser(String targetUserName) { Platform.runLater( () -> { + for (MenuItem existing : addWhisperChatButton.getItems()) { + if (targetUserName.equals(existing.getText())) { + return; + } + } MenuItem menuItem = new MenuItem(targetUserName); addWhisperChatButton.getItems().add(menuItem); menuItem.setOnAction( @@ -98,6 +109,54 @@ public class ChatBoxController { }); } + public void removeWhisperUser(String targetUserName) { + Platform.runLater( + () -> + addWhisperChatButton + .getItems() + .removeIf(item -> targetUserName.equals(item.getText()))); + } + + public void renameWhisperUser(String oldUsername, String newUsername) { + if (oldUsername == null + || newUsername == null + || oldUsername.isBlank() + || newUsername.isBlank() + || oldUsername.equals(newUsername)) { + return; + } + + Platform.runLater( + () -> { + for (MenuItem item : addWhisperChatButton.getItems()) { + if (oldUsername.equals(item.getText())) { + item.setText(newUsername); + item.setOnAction( + event -> + addWhisperChat( + newUsername, + new ChatModel( + ChatType.WHISPER, + username, + -1, + newUsername))); + break; + } + } + + Tab tab = usernameTabMap.remove(oldUsername); + if (tab != null) { + tab.setText(newUsername); + usernameTabMap.put(newUsername, tab); + } + + int idx = activeWhisperChats.indexOf(oldUsername); + if (idx >= 0) { + activeWhisperChats.set(idx, newUsername); + } + }); + } + /** * Creates a new private (whisper) chat model for a specific target user, registers it within * the chat system, and opens a new chat tab. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatViewController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatViewController.java index 397e8f3..2bad460 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatViewController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/chatui/ChatViewController.java @@ -68,11 +68,12 @@ public class ChatViewController implements Initializable { String message = inputField.getText().trim(); if (!message.isEmpty()) { inputField.clear(); + String currentUsername = controller.getCurrentUsername(); Message msg = new Message( chatModel.getChattype(), chatModel.lobbyId, - username, + currentUsername, chatModel.getTarget(), message); controller.onSendToNetwork(msg); 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 80498df..37587cc 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 @@ -104,6 +104,13 @@ public class CasinomainuiController { casinoTable.getChildren().add(gridManager.getGridPane()); gridManager.renderLobbyButtons(); + String sharedUsername = ClientApp.getSharedUsername(); + updateUsernameFieldPresentation(sharedUsername); + if (loginButton != null) { + loginButton.setText( + sharedUsername != null && !sharedUsername.isBlank() ? "Change Name" : "Login"); + } + initializeChat(clientService); } @@ -156,7 +163,7 @@ public class CasinomainuiController { /** Handles the login button action. Validates input and calls LobbyClient.login(). */ @FXML public void handleLoginButton() { - String username = usernameField.getText(); + String username = usernameField == null ? null : usernameField.getText(); if (username == null || username.isBlank()) { showAlert("Please enter a username."); return; @@ -170,12 +177,63 @@ public class CasinomainuiController { showAlert("Offline mode: cannot send login to server."); return; } + + String trimmed = username.trim(); try { - lobbyClient.login(username); - showAlert("Login sent: " + username); + var result = lobbyClient.login(trimmed); + String assigned = result != null ? result.getUsername() : trimmed; + ClientApp.updateSharedUsername(assigned); + if (chatController != null) { + chatController.updateUsername(assigned); + } + updateUsernameFieldPresentation(assigned); + if (loginButton != null) { + loginButton.setText("Change Name"); + } + return; + } catch (RuntimeException loginError) { + if (!containsProtocolError(loginError, "ALREADY_LOGGED_IN")) { + LOGGER.error("Login failed: {}", loginError.getMessage()); + showAlert("Login failed: " + loginError.getMessage()); + return; + } + LOGGER.info("Session already logged in, trying username change"); + } + + try { + var result = lobbyClient.changeUsername(trimmed); + String assigned = result != null ? result.getUsername() : trimmed; + ClientApp.updateSharedUsername(assigned); + if (chatController != null) { + chatController.updateUsername(assigned); + } + updateUsernameFieldPresentation(assigned); + if (loginButton != null) { + loginButton.setText("Change Name"); + } } catch (RuntimeException e) { - LOGGER.error("Login failed: {}", e.getMessage()); - showAlert("Login failed: " + e.getMessage()); + LOGGER.error("Change username failed: {}", e.getMessage()); + showAlert("Change username failed: " + e.getMessage()); + } + } + + private boolean containsProtocolError(RuntimeException error, String code) { + String msg = error.getMessage(); + return msg != null && msg.contains(code); + } + + private void updateUsernameFieldPresentation(String username) { + if (usernameField == null) { + return; + } + + if (username != null && !username.isBlank()) { + usernameField.setText(username.trim()); + usernameField.positionCaret(usernameField.getText().length()); + usernameField.setPromptText("Username"); + } else { + usernameField.clear(); + usernameField.setPromptText("Username"); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageHandler.java index a2bd3ad..680819a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/send_message/SendMessageHandler.java @@ -9,6 +9,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import java.util.Optional; public class SendMessageHandler extends CommandHandler { private final UserRegistry userRegistry; @@ -39,6 +40,11 @@ public class SendMessageHandler extends CommandHandler { @Override public void execute(SendMessageRequest request) { Message message = request.getMessage(); + Optional senderUser = + userRegistry.getBySessionId(request.getSessionId()); + if (senderUser.isPresent()) { + message.sender = senderUser.get().getName(); + } broadcast(request, message); OkResponse response = new OkResponse(request.getContext()); responseDispatcher.dispatch(response); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/GameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/GameController.java index b90de4f..361879d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/GameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/GameController.java @@ -61,6 +61,35 @@ public class GameController { engine.getState().addPlayer(name, chips); } + /** + * Renames a player id in the controller list and underlying game state. + * + * @param oldId old player id + * @param newId new player id + * @return true if rename succeeded + */ + public boolean renamePlayer(PlayerId oldId, PlayerId newId) { + if (oldId == null || newId == null) { + return false; + } + if (oldId.equals(newId)) { + return true; + } + + int idx = players.indexOf(oldId); + if (idx < 0 || players.contains(newId)) { + return false; + } + + boolean stateRenamed = engine.getState().renamePlayerId(oldId, newId); + if (!stateRenamed) { + return false; + } + + players.set(idx, newId); + return true; + } + /** * Initializes a new hand by preparing the deck, setting the phase to PREFLOP, rotating the * dealer, dealing hole cards, posting blinds, and setting the first active player. diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/player/Player.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/player/Player.java index f937e44..81576dd 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/player/Player.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/player/Player.java @@ -49,6 +49,16 @@ public class Player { return id; } + /** + * Updates the player's id. This is used when a username change is propagated into an already + * running game. + * + * @param id new player id + */ + public void setId(PlayerId id) { + this.id = id; + } + /** * Returns the display name of the player. Currently identical to the player ID. * diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/state/GameState.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/state/GameState.java index a018d6a..1732072 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/state/GameState.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/state/GameState.java @@ -174,6 +174,51 @@ public class GameState { holeCards.computeIfAbsent(id, k -> new ArrayList<>()); } + /** + * Renames a player id across all game-state structures. + * + * @param oldId existing player id + * @param newId new player id + * @return true if the rename was applied, false otherwise + */ + public synchronized boolean renamePlayerId(PlayerId oldId, PlayerId newId) { + if (oldId == null || newId == null) { + return false; + } + if (!players.containsKey(oldId)) { + return false; + } + if (oldId.equals(newId)) { + return true; + } + if (players.containsKey(newId)) { + return false; + } + + Player player = players.remove(oldId); + if (player == null) { + return false; + } + player.setId(newId); + players.put(newId, player); + + int idx = playerOrder.indexOf(oldId); + if (idx >= 0) { + playerOrder.set(idx, newId); + } + + moveMapEntry(currentBets, oldId, newId, 0); + moveMapEntry(playerBetCommitments, oldId, newId, 0); + moveMapEntry(holeCards, oldId, newId, new ArrayList<>()); + return true; + } + + private void moveMapEntry( + Map map, PlayerId oldId, PlayerId newId, T fallback) { + T value = map.remove(oldId); + map.put(newId, value != null ? value : fallback); + } + // Betting public int getCurrentBet(PlayerId playerId) { return currentBets.getOrDefault(playerId, 0); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java index 7653685..7aec356 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/Lobby.java @@ -70,6 +70,30 @@ public class Lobby { return playerNames.remove(playerName); } + /** + * Renames a player in this lobby's player list. + * + * @param oldName old username + * @param newName new username + * @return true if renamed successfully + */ + public boolean renamePlayer(String oldName, String newName) { + if (oldName == null || newName == null) { + return false; + } + synchronized (playerNames) { + if (oldName.equals(newName)) { + return playerNames.contains(oldName); + } + int idx = playerNames.indexOf(oldName); + if (idx < 0 || playerNames.contains(newName)) { + return false; + } + playerNames.set(idx, newName); + return true; + } + } + public void initGame(GameController controller) { this.gameController = controller; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java index cdeb574..0e58adf 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/lobby/LobbyManager.java @@ -1,5 +1,6 @@ package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId; import ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby.Lobby.AddResult; import java.time.Duration; import java.time.Instant; @@ -170,6 +171,56 @@ public class LobbyManager { } } + /** + * Renames a player across lobby mapping, lobby player list and running game ids. + * + * @param oldUsername old username + * @param newUsername new username + * @return true if rename was applied + */ + public synchronized boolean renamePlayer(String oldUsername, String newUsername) { + if (oldUsername == null || newUsername == null) { + return false; + } + if (oldUsername.equals(newUsername)) { + return true; + } + + LobbyId lobbyId = playerToLobby.get(oldUsername); + if (lobbyId == null) { + return true; + } + if (playerToLobby.containsKey(newUsername)) { + return false; + } + + Lobby lobby = activeLobbies.get(lobbyId); + if (lobby == null) { + playerToLobby.remove(oldUsername); + return true; + } + + boolean lobbyRenamed = lobby.renamePlayer(oldUsername, newUsername); + if (!lobbyRenamed) { + return false; + } + + if (lobby.getGameController() != null) { + boolean gameRenamed = + lobby.getGameController() + .renamePlayer(PlayerId.of(oldUsername), PlayerId.of(newUsername)); + if (!gameRenamed) { + // Best-effort rollback to keep structures consistent. + lobby.renamePlayer(newUsername, oldUsername); + return false; + } + } + + playerToLobby.remove(oldUsername); + playerToLobby.put(newUsername, lobbyId); + return true; + } + /** * Apply the given action to every player username in the lobby identified by {@code lobbyId}. * This is a small helper that keeps iteration logic centralized and avoids leaking internal diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java index 277ad8b..35a7451 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java @@ -200,6 +200,10 @@ public class UserRegistry { return false; } + if (user.getName().equals(newName)) { + return true; + } + if (byName.containsKey(newName)) { return false; } diff --git a/src/main/resources/ui-structure/Casinomainui.fxml b/src/main/resources/ui-structure/Casinomainui.fxml index 971a69d..ee3b43a 100644 --- a/src/main/resources/ui-structure/Casinomainui.fxml +++ b/src/main/resources/ui-structure/Casinomainui.fxml @@ -81,25 +81,24 @@ - - - - - - - - - - - - - - - - - - - + + + + + + +