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 db72bfa..8a237b5 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 @@ -10,11 +10,8 @@ import org.apache.logging.log4j.Logger; /** * Entry point and bootstrap helper for the Casono client application. * - *

- * Responsibilities: - Parse CLI args (host:port, optional username) - Store - * username centrally - * so UI can reuse it - Create a shared ClientService and do startup LOGIN - * (optional) + *

Responsibilities: - Parse CLI args (host:port, optional username) - Store username centrally + * so UI can reuse it - Create a shared ClientService and do startup LOGIN (optional) */ public class ClientApp { @@ -81,13 +78,14 @@ public class ClientApp { ClientService clientService = new ClientService(host, port); setSharedClientService(clientService); - java.util.concurrent.ExecutorService bg = java.util.concurrent.Executors.newSingleThreadExecutor( - r -> { - Thread t = new Thread(r); - t.setDaemon(true); - t.setName("casono-startup-login"); - return t; - }); + java.util.concurrent.ExecutorService bg = + java.util.concurrent.Executors.newSingleThreadExecutor( + r -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("casono-startup-login"); + return t; + }); bg.submit( () -> { @@ -115,16 +113,15 @@ public class ClientApp { }); } catch (RuntimeException e) { LOGGER.warn( - "Could not establish initial connection for startup login: {}", - e.getMessage()); + "Could not establish initial connection for startup login: {}", e.getMessage()); } } private static void launchUI(String arg, String username) { if (username != null && !username.isBlank()) { - Launcher.main(new String[] { arg, username }); + Launcher.main(new String[] {arg, username}); } else { - Launcher.main(new String[] { arg }); + Launcher.main(new String[] {arg}); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameService.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameService.java index d63b175..7c12039 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameService.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/game/GameService.java @@ -18,7 +18,10 @@ public class GameService { * @param client The GameClient used to communicate with the server. Must not be null. */ public GameService(GameClient client) { - if (client == null) throw new IllegalArgumentException("GameClient must not be null"); + if (client == null) { + throw new IllegalArgumentException("GameClient must not be null"); + } + this.client = client; LOG.info("GameService created"); } @@ -47,10 +50,16 @@ public class GameService { this.state = newState; - LOG.info(() -> "State updated: phase=" + state.phase - + " pot=" + state.pot - + " players=" + (state.players != null ? state.players.size() : 0) - + " community=" + (state.communityCards != null ? state.communityCards.size() : 0)); + LOG.info( + () -> + "State updated: phase=" + + state.phase + + " pot=" + + state.pot + + " players=" + + (state.players != null ? state.players.size() : 0) + + " community=" + + (state.communityCards != null ? state.communityCards.size() : 0)); return state; } @@ -78,7 +87,8 @@ public class GameService { /** * Retrieves the list of players currently in the game. * - * @return A list of Player objects representing the players in the game. A list of Player objects representing the players in the game. + * @return A list of Player objects representing the players in the game. A list of Player + * objects representing the players in the game. */ public List getPlayers() { ensureState(); @@ -92,22 +102,20 @@ public class GameService { */ public Player getWinner() { ensureState(); - if (state.winnerIndex < 0 || state.players == null || state.winnerIndex >= state.players.size()) { + if (state.winnerIndex < 0 + || state.players == null + || state.winnerIndex >= state.players.size()) { return null; } return state.players.get(state.winnerIndex); } - /** - * Retrieves the current phase of the game. - */ + /** Retrieves the current phase of the game. */ public void call() { client.sendCall(); } - /** - * Retrieves the current phase of the game. - */ + /** Retrieves the current phase of the game. */ public void fold() { client.sendFold(); } @@ -130,9 +138,7 @@ public class GameService { client.sendRaise(amount); } - /** - * Ensures that the game state has been initialized before accessing it. - */ + /** Ensures that the game state has been initialized before accessing it. */ private void ensureState() { if (state == null) { throw new IllegalStateException("GameService used before any successful refresh()"); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java index 95d3902..2ab6f0c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java @@ -26,10 +26,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** - * The ClientService class is responsible for managing the connection to the - * server, sending - * commands, and receiving responses. It uses a TcpTransport to communicate with - * the server and an + * The ClientService class is responsible for managing the connection to the server, sending + * commands, and receiving responses. It uses a TcpTransport to communicate with the server and an * ExecutorService to handle asynchronous requests. */ public class ClientService { @@ -43,20 +41,20 @@ public class ClientService { private final AtomicInteger idGenerator; private Logger logger; - private final Map> pendingResponses = new ConcurrentHashMap<>(); - private final CopyOnWriteArrayList>> eventListeners = new CopyOnWriteArrayList<>(); + private final Map> pendingResponses = + new ConcurrentHashMap<>(); + private final CopyOnWriteArrayList>> eventListeners = + new CopyOnWriteArrayList<>(); private Thread readerThread = null; private final AtomicBoolean running = new AtomicBoolean(false); private static final int READER_JOIN_TIMEOUT_MS = 500; /** - * Constructs a ClientService with the given server IP and port. It establishes - * a socket - * connection to the server and initializes the TcpTransport and ExecutorService - * for + * Constructs a ClientService with the given server IP and port. It establishes a socket + * connection to the server and initializes the TcpTransport and ExecutorService for * communication. * - * @param ip The IP address of the server to connect to. + * @param ip The IP address of the server to connect to. * @param port The port number of the server to connect to. */ public ClientService(String ip, int port) { @@ -81,24 +79,25 @@ public class ClientService { private void startReaderThread() { running.set(true); - readerThread = new Thread( - () -> { - while (running.get()) { - try { - RawPacket rp = clienttcptransport.read(); - processRawPacket(rp); - } catch (IOException e) { - if (running.get()) { - logger.warn("IO error on transport reader", e); + readerThread = + new Thread( + () -> { + while (running.get()) { + try { + RawPacket rp = clienttcptransport.read(); + processRawPacket(rp); + } catch (IOException e) { + if (running.get()) { + logger.warn("IO error on transport reader", e); + } + break; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } } - break; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - }, - "casono-client-reader"); + }, + "casono-client-reader"); readerThread.setDaemon(true); readerThread.start(); } @@ -139,8 +138,9 @@ public class ClientService { continue; } - if (trimmed.isEmpty()) + if (trimmed.isEmpty()) { continue; + } line = line.replaceFirst("^\\s+", ""); trimmed = line.trim(); @@ -210,11 +210,9 @@ public class ClientService { } } - private record ParsedPacket(boolean success, List lines, Deque blockStack) { - } + private record ParsedPacket(boolean success, List lines, Deque blockStack) {} - private record StatusCheckResult(boolean success) { - } + private record StatusCheckResult(boolean success) {} private boolean isContainerStart(String token) { return token.equals("LOBBIES") @@ -228,8 +226,7 @@ public class ClientService { } /** - * Constructs a ClientService in offline mode. No network connection will be - * attempted and calls + * Constructs a ClientService in offline mode. No network connection will be attempted and calls * to processCommand will throw a RuntimeException. * * @param offline true to create an offline (no-network) client service @@ -242,21 +239,19 @@ public class ClientService { this.executor = Executors.newSingleThreadExecutor(); } - /** - * Returns true if this ClientService is running in offline mode (no network). - */ + /** Returns true if this ClientService is running in offline mode (no network). */ public boolean isOffline() { return offlineMode; } // Allow primitive values to contain hyphens (UUIDs) in addition to // digits/words/colons - static Pattern responseRex = Pattern.compile( - "(?\\w+)=(('(?([^']|\\')+)')|(?[+-]?[-\\d\\w:]+))"); + static Pattern responseRex = + Pattern.compile( + "(?\\w+)=(('(?([^']|\\')+)')|(?[+-]?[-\\d\\w:]+))"); /** - * Removes escape characters from a string, specifically converting escaped - * single quotes (\') + * Removes escape characters from a string, specifically converting escaped single quotes (\') * back to regular single quotes ('). * * @param input The escaped string to process. @@ -267,10 +262,8 @@ public class ClientService { } /** - * Converts a list of raw string parameters into a list of - * {@link RequestParameter} objects. It - * uses a regex matcher to distinguish between quoted strings (which are - * unescaped) and + * Converts a list of raw string parameters into a list of {@link RequestParameter} objects. It + * uses a regex matcher to distinguish between quoted strings (which are unescaped) and * primitive values. * * @param input A list of raw strings to be parsed. @@ -295,12 +288,10 @@ public class ClientService { .toList(); } - private static record ParsedResponse(boolean success, List lines) { - } + private static record ParsedResponse(boolean success, List lines) {} /** - * Register an event listener that receives unsolicited event payload lines (no - * status prefix). + * Register an event listener that receives unsolicited event payload lines (no status prefix). */ public void addEventListener(Consumer> listener) { eventListeners.add(listener); @@ -311,18 +302,14 @@ public class ClientService { } /** - * Sends a command to the server and processes the multi-line response. It - * handles the protocol - * handshake (expecting +OK), strips leading tabs from response lines, and - * collects them until + * Sends a command to the server and processes the multi-line response. It handles the protocol + * handshake (expecting +OK), strips leading tabs from response lines, and collects them until * the "END" marker is reached. * * @param message The raw command string to be sent to the transport layer. - * @return A list of response lines received from the server (excluding protocol - * markers). - * @throws RuntimeException if the server responds with an error or if a - * communication failure - * occurs. + * @return A list of response lines received from the server (excluding protocol markers). + * @throws RuntimeException if the server responds with an error or if a communication failure + * occurs. */ protected List processCommand(String message) { if (offlineMode) { @@ -333,14 +320,15 @@ public class ClientService { ArrayBlockingQueue q = new ArrayBlockingQueue<>(1); pendingResponses.put(reqId, q); - Future writeFuture = executor.submit( - () -> { - try { - clienttcptransport.write(new RawPacket(reqId, message)); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + Future writeFuture = + executor.submit( + () -> { + try { + clienttcptransport.write(new RawPacket(reqId, message)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); try { writeFuture.get(); @@ -372,15 +360,11 @@ public class ClientService { } /** - * Helper method to send a request to the server using the ExecutorService. It - * submits the - * request as a Runnable task and waits for its completion. If the task is - * interrupted or - * encounters an execution exception, it throws a RuntimeException with the - * appropriate cause. + * Helper method to send a request to the server using the ExecutorService. It submits the + * request as a Runnable task and waits for its completion. If the task is interrupted or + * encounters an execution exception, it throws a RuntimeException with the appropriate cause. * - * @param request The Runnable task representing the request to be sent to the - * server. + * @param request The Runnable task representing the request to be sent to the server. */ private void sendRequest(Runnable request) { Future future = executor.submit(request); @@ -394,12 +378,9 @@ public class ClientService { } /** - * Helper method to extract the cause of an exception and return it as a - * RuntimeException. If - * the cause is null, it returns the original exception as a RuntimeException. - * If the cause is - * already a RuntimeException, it returns it directly. Otherwise, it wraps the - * cause in a new + * Helper method to extract the cause of an exception and return it as a RuntimeException. If + * the cause is null, it returns the original exception as a RuntimeException. If the cause is + * already a RuntimeException, it returns it directly. Otherwise, it wraps the cause in a new * RuntimeException and returns it. * * @param e The exception from which to extract the cause. @@ -417,10 +398,8 @@ public class ClientService { } /** - * Closes the socket connection to the server and shuts down the - * ExecutorService. It also closes - * the TcpTransport used for communication. If any IOException occurs during - * this process, it + * Closes the socket connection to the server and shuts down the ExecutorService. It also closes + * the TcpTransport used for communication. If any IOException occurs during this process, it * prints the exception to the console. */ public void closeSocket() { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java index b5934ab..e2b34e6 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/GameClient.java @@ -13,20 +13,13 @@ import java.util.logging.Logger; /** * Fetches and parses game state from server. * - *

- * Protocol notes (based on your current server implementation): - Success - * replies contain: - * PHASE, POT, CURRENT_BET, DEALER, ACTIVE_PLAYER, then blocks: - CARD ... END - * (community cards on - * root level) - PLAYER ... (NAME/CHIPS/BET/STATE + optional CARD blocks for - * requesting player) ... + *

Protocol notes (based on your current server implementation): - Success replies contain: + * PHASE, POT, CURRENT_BET, DEALER, ACTIVE_PLAYER, then blocks: - CARD ... END (community cards on + * root level) - PLAYER ... (NAME/CHIPS/BET/STATE + optional CARD blocks for requesting player) ... * END - Error replies contain: -ERR then CODE=..., MSG=..., END * - *

- * Important: - The server does NOT wrap cards in a "CARDS" container. - CARD - * blocks appear - * either: - at root level -> community cards - inside PLAYER -> hole cards for - * that player (usually + *

Important: - The server does NOT wrap cards in a "CARDS" container. - CARD blocks appear + * either: - at root level -> community cards - inside PLAYER -> hole cards for that player (usually * only for the requesting user) */ public class GameClient { @@ -37,8 +30,10 @@ public class GameClient { private final int gameId; public GameClient(ClientService client, int gameId) { - if (client == null) + if (client == null) { throw new IllegalArgumentException("ClientService must not be null"); + } + this.client = client; this.gameId = gameId; LOG.info(() -> "GameClient initialized for gameId=" + gameId); @@ -78,16 +73,17 @@ public class GameClient { try { GameState state = parseGameState(joined); LOG.info( - () -> "Parsed state: phase=" - + state.phase - + " pot=" - + state.pot - + " players=" - + (state.players != null ? state.players.size() : 0) - + " community=" - + (state.communityCards != null - ? state.communityCards.size() - : 0)); + () -> + "Parsed state: phase=" + + state.phase + + " pot=" + + state.pot + + " players=" + + (state.players != null ? state.players.size() : 0) + + " community=" + + (state.communityCards != null + ? state.communityCards.size() + : 0)); return state; } catch (Exception e) { LOG.log(Level.SEVERE, "Failed parsing game state. Payload=\n" + joined, e); @@ -119,8 +115,9 @@ public class GameClient { ParseState state = new ParseState(); for (String raw : input.split("\n")) { String line = raw.trim(); - if (line.isEmpty() || line.startsWith("+OK")) + if (line.isEmpty() || line.startsWith("+OK")) { continue; + } if (!parseGlobalField(s, line) && !parsePlayerStructure(s, state, line) @@ -170,8 +167,11 @@ public class GameClient { state.insidePlayer = true; return true; } - if (line.equals("CARDS")) + + if (line.equals("CARDS")) { return true; + } + if (line.equals("CARD")) { state.currentCard = new Card("", ""); if (state.insidePlayer && state.currentPlayer != null) { @@ -181,6 +181,7 @@ public class GameClient { } return true; } + if (line.equals("END")) { if (state.currentCard != null) { state.currentCard = null; @@ -194,8 +195,10 @@ public class GameClient { } private boolean parseCardData(ParseState state, String line) { - if (state.currentCard == null) + if (state.currentCard == null) { return false; + } + if (line.startsWith("VALUE=")) { state.currentCard.setValue(value(line)); return true; @@ -208,24 +211,30 @@ public class GameClient { } private boolean parsePlayerData(ParseState state, String line) { - if (state.currentPlayer == null) + if (state.currentPlayer == null) { return false; + } + if (line.startsWith("USERNAME=") || line.startsWith("NAME=")) { state.currentPlayer.setId(PlayerId.of(value(line))); return true; } + if (line.startsWith("CHIPS=")) { state.currentPlayer.setChips(intVal(line)); return true; } + if (line.startsWith("BET=")) { state.currentPlayer.setBet(intVal(line)); return true; } + if (line.startsWith("STATE=")) { state.currentPlayer.setState(parseState(value(line))); return true; } + return false; } @@ -244,8 +253,10 @@ public class GameClient { } private static PlayerState parseState(String s) { - if (s == null) + if (s == null) { return PlayerState.ACTIVE; + } + return switch (s.trim().toUpperCase()) { case "ACTIVE" -> PlayerState.ACTIVE; case "FOLDED" -> PlayerState.FOLDED; @@ -255,8 +266,10 @@ public class GameClient { } private static String extractValue(String joined, String key) { - if (joined == null) + if (joined == null) { return null; + } + for (String raw : joined.split("\n")) { String line = raw.trim(); if (line.startsWith(key + "=")) { 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 4805d44..addd9fa 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 @@ -46,10 +46,12 @@ public class ChatBoxController { private String ressource = "/ui-structure/components/chatui/chattab.fxml"; /** - * Constructor for the ChatBoxController, initializes the necessary fields and data structures for managing chat tabs and whisper chats. + * Constructor for the ChatBoxController, initializes the necessary fields and data structures + * for managing chat tabs and whisper chats. * * @param username The username of the current user. - * @param chatController The ChatController instance responsible for handling chat logic and communication with the server. + * @param chatController The ChatController instance responsible for handling chat logic and + * communication with the server. */ public ChatBoxController(String username, ChatController chatController) { this.username = username; 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 1e85435..c43b059 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 @@ -136,6 +136,8 @@ public class CasinoGameController { private static final long CHIP_SCALE_DURATION_MS = 220; private static final long CHIP_DROP_DURATION_MS = 250; private static final long CHIP_STAGGER_DELAY_MULTIPLIER = 35L; + private static final double CHAT_WIDTH = 400; + private static final double CHAT_HEIGHT = 600; private ChatController chatController; private String chatUsername; @@ -155,7 +157,10 @@ public class CasinoGameController { * @return A string key representing the card, formatted as "suit:value". */ private String cardKey(Card c) { - if (c == null) return "null"; + if (c == null) { + return "null"; + } + String suit = (c.getSuit() == null) ? "" : c.getSuit().toLowerCase(); String value = (c.getValue() == null) ? "" : c.getValue().toLowerCase(); return suit + ":" + value; @@ -238,11 +243,15 @@ public class CasinoGameController { } /** - * Set the context for the chat functionality by providing the username, ClientService, and lobby ID. + * Set the context for the chat functionality by providing the username, ClientService, and + * lobby ID. * - * @param username The username of the player, used for chat identification. Must not be null or blank. - * @param clientService The ClientService instance used for communicating with the server. Must not be null. - * @param lobbyId The ID of the lobby associated with the chat, used to determine the chat context. Must be a non-negative integer. + * @param username The username of the player, used for chat identification. Must not be null or + * blank. + * @param clientService The ClientService instance used for communicating with the server. Must + * not be null. + * @param lobbyId The ID of the lobby associated with the chat, used to determine the chat + * context. Must be a non-negative integer. */ public void setChatContext(String username, ClientService clientService, int lobbyId) { this.chatUsername = username; @@ -252,7 +261,8 @@ public class CasinoGameController { } /** - * Initialize the chat UI if all necessary information (username, ClientService, and lobby ID) is available and the chat has not already been initialized. + * Initialize the chat UI if all necessary information (username, ClientService, and lobby ID) + * is available and the chat has not already been initialized. */ private void initializeChatIfPossible() { if (chatInitialized || chatClientService == null || chatUsername == null) { @@ -278,8 +288,8 @@ public class CasinoGameController { Scene scene = new Scene(root); chatStage.setScene(scene); - chatStage.setWidth(400); - chatStage.setHeight(600); + chatStage.setWidth(CHAT_WIDTH); + chatStage.setHeight(CHAT_HEIGHT); chatStage.setOnCloseRequest(event -> chatInitialized = false); @@ -435,32 +445,34 @@ public class CasinoGameController { return; } - CompletableFuture - .supplyAsync(() -> { - try { - return gameService.refresh(); - } catch (Exception e) { - LOGGER.severe("refresh failed: " + e.getMessage()); - e.printStackTrace(); - return null; - } - }) - .thenAccept(state -> { + CompletableFuture.supplyAsync( + () -> { + try { + return gameService.refresh(); + } catch (Exception e) { + LOGGER.severe("refresh failed: " + e.getMessage()); + e.printStackTrace(); + return null; + } + }) + .thenAccept( + state -> { + if (state == null) { + LOGGER.info("No state received yet."); + return; + } - if (state == null) { - LOGGER.info("No state received yet."); - return; - } - - javafx.application.Platform.runLater(() -> { - applyState(state); - }); - }) - .exceptionally(ex -> { - LOGGER.severe("UI update Error: " + ex.getMessage()); - ex.printStackTrace(); - return null; - }); + javafx.application.Platform.runLater( + () -> { + applyState(state); + }); + }) + .exceptionally( + ex -> { + LOGGER.severe("UI update Error: " + ex.getMessage()); + ex.printStackTrace(); + return null; + }); } /** @@ -471,11 +483,17 @@ public class CasinoGameController { */ private void applyState(GameState s) { - if (s == null) return; + if (s == null) { + return; + } - LOGGER.info("applyState -> phase=" + s.phase + - " pot=" + s.pot + - " players=" + (s.players != null ? s.players.size() : 0)); + LOGGER.info( + "applyState -> phase=" + + s.phase + + " pot=" + + s.pot + + " players=" + + (s.players != null ? s.players.size() : 0)); List players = (s.players != null) ? s.players : List.of(); List community = (s.communityCards != null) ? s.communityCards : List.of(); @@ -507,17 +525,25 @@ public class CasinoGameController { LOGGER.info("myPlayerId=" + myPlayerId); for (int i = 0; i < players.size(); i++) { Player p = players.get(i); - LOGGER.info("state.players[" + i + "] id=" + (p != null ? p.getId() : null) - + " chips=" + (p != null ? p.getChips() : null) - + " bet=" + (p != null ? p.getBet() : null) - + " state=" + (p != null ? p.getState() : null)); + LOGGER.info( + "state.players[" + + i + + "] id=" + + (p != null ? p.getId() : null) + + " chips=" + + (p != null ? p.getChips() : null) + + " bet=" + + (p != null ? p.getBet() : null) + + " state=" + + (p != null ? p.getState() : null)); } } /** * Synchronize the whisper chat targets with the current list of players. * - * @param players The list of players currently in the game, used to update the whisper chat targets in the chat controller. + * @param players The list of players currently in the game, used to update the whisper chat + * targets in the chat controller. */ private void syncWhisperTargetsFromPlayers(List players) { if (chatController == null || players == null || players.isEmpty()) { @@ -536,10 +562,13 @@ public class CasinoGameController { } /** - * Retrieve the hole cards of the current player based on their PlayerId and the list of players in the game state. + * Retrieve the hole cards of the current player based on their PlayerId and the list of players + * in the game state. * - * @param players The list of players currently in the game, used to find the player matching myPlayerId and retrieve their hole cards. - * @return A list of Card objects representing the current player's hole cards, or an empty list if the player is not found or has no cards. + * @param players The list of players currently in the game, used to find the player matching + * myPlayerId and retrieve their hole cards. + * @return A list of Card objects representing the current player's hole cards, or an empty list + * if the player is not found or has no cards. */ private List getMyCards(List players) { @@ -613,9 +642,13 @@ public class CasinoGameController { } /** - * Update the opponent player slots based on the list of players in the game state. This method identifies the opponents by comparing their PlayerIds with myPlayerId and updates the corresponding PlayerStatusControllers for each opponent slot. + * Update the opponent player slots based on the list of players in the game state. This method + * identifies the opponents by comparing their PlayerIds with myPlayerId and updates the + * corresponding PlayerStatusControllers for each opponent slot. * - * @param players The list of players currently in the game, used to determine which players are opponents and update their display in the UI accordingly. If the list is null or empty, all opponent slots will be cleared. + * @param players The list of players currently in the game, used to determine which players are + * opponents and update their display in the UI accordingly. If the list is null or empty, + * all opponent slots will be cleared. */ private void updatePlayers(List players) { if (players == null || players.isEmpty()) { @@ -626,10 +659,15 @@ public class CasinoGameController { java.util.List opponents = buildOpponents(players); boolean meFound = opponents.size() != players.size(); - LOGGER.info("updatePlayers: total=" + players.size() - + " myPlayerId=" + myPlayerId - + " meFound=" + meFound - + " opponents=" + opponents.size()); + LOGGER.info( + "updatePlayers: total=" + + players.size() + + " myPlayerId=" + + myPlayerId + + " meFound=" + + meFound + + " opponents=" + + opponents.size()); setOpponent(player1Controller, opponents, 0); setOpponent(player2Controller, opponents, 1); @@ -641,19 +679,25 @@ public class CasinoGameController { } /** - * Build a list of opponent players by filtering out the current player (identified by myPlayerId) from the provided list of players. If the current player is not found in the list, it assumes all players are opponents and returns the original list. + * Build a list of opponent players by filtering out the current player (identified by + * myPlayerId) from the provided list of players. If the current player is not found in the + * list, it assumes all players are opponents and returns the original list. * - * @param players The list of players to filter, which may include the current player and their opponents. - * This list is typically obtained from the game state and may be null or empty, in which case an empty list of opponents will be returned. - * @return A list of Player objects representing the opponents, excluding the current player if found. - * If the current player is not found, the original list of players is returned as opponents. + * @param players The list of players to filter, which may include the current player and their + * opponents. This list is typically obtained from the game state and may be null or empty, + * in which case an empty list of opponents will be returned. + * @return A list of Player objects representing the opponents, excluding the current player if + * found. If the current player is not found, the original list of players is returned as + * opponents. */ private java.util.List buildOpponents(List players) { java.util.List opponents = new java.util.ArrayList<>(); boolean meFound = false; for (Player p : players) { - if (p == null) continue; + if (p == null) { + continue; + } PlayerId pid = p.getId(); boolean isMe = myPlayerId != null && pid != null && pid.equals(myPlayerId); @@ -674,34 +718,57 @@ public class CasinoGameController { } /** - * Set the opponent player information in the specified PlayerStatusController based on the provided list of opponents and the index of the opponent to display. + * Set the opponent player information in the specified PlayerStatusController based on the + * provided list of opponents and the index of the opponent to display. * - * @param slot The PlayerStatusController instance representing the opponent slot in the UI, which will be updated with the player information if available. - * @param list The list of opponent players to choose from, which is typically built by filtering the game state's player list to exclude the current player. + * @param slot The PlayerStatusController instance representing the opponent slot in the UI, + * which will be updated with the player information if available. + * @param list The list of opponent players to choose from, which is typically built by + * filtering the game state's player list to exclude the current player. * @param index The index of the opponent in the list to display in the specified slot. */ private void setOpponent(PlayerStatusController slot, java.util.List list, int index) { - if (slot == null) return; + if (slot == null) { + return; + } + slot.setPlayer(index < list.size() ? list.get(index) : null); } /** - * Safely refresh the PlayerStatusController by calling its refresh method, while catching and ignoring any exceptions that may occur during the refresh process. + * Safely refresh the PlayerStatusController by calling its refresh method, while catching and + * ignoring any exceptions that may occur during the refresh process. * * @param c The PlayerStatusController instance to refresh, which may be null. */ private void safeRefresh(PlayerStatusController c) { - if (c == null) return; - try { c.refresh(); } catch (Exception ignored) {} + if (c == null) { + return; + } + + try { + c.refresh(); + } catch (Exception ignored) { + } } /** - * Clear all opponent slots in the UI by setting their player information to null and refreshing their display. + * Clear all opponent slots in the UI by setting their player information to null and refreshing + * their display. */ private void clearOpponentSlots() { - if (player1Controller != null) player1Controller.setPlayer(null); - if (player2Controller != null) player2Controller.setPlayer(null); - if (player3Controller != null) player3Controller.setPlayer(null); + if (player1Controller != null) { + player1Controller.setPlayer(null); + } + + if (player2Controller != null) { + player2Controller.setPlayer(null); + } + + if (player3Controller != null) { + player3Controller.setPlayer(null); + } + safeRefresh(player1Controller); safeRefresh(player2Controller); safeRefresh(player3Controller); @@ -713,9 +780,17 @@ public class CasinoGameController { * @param s The current game state containing the dealer index. */ private void highlightDealer(GameState s) { - if (player1Controller != null) player1Controller.setDealer(false); - if (player2Controller != null) player2Controller.setDealer(false); - if (player3Controller != null) player3Controller.setDealer(false); + if (player1Controller != null) { + player1Controller.setDealer(false); + } + + if (player2Controller != null) { + player2Controller.setDealer(false); + } + + if (player3Controller != null) { + player3Controller.setDealer(false); + } myDealerIcon.setVisible(false); @@ -737,23 +812,31 @@ public class CasinoGameController { } java.util.List opponents = buildOpponents(s.players); - if (opponents.size() > 0 && samePlayer(opponents.get(0), dealerPlayer) && player1Controller != null) { + if (opponents.size() > 0 + && samePlayer(opponents.get(0), dealerPlayer) + && player1Controller != null) { player1Controller.setDealer(true); } - if (opponents.size() > 1 && samePlayer(opponents.get(1), dealerPlayer) && player2Controller != null) { + if (opponents.size() > 1 + && samePlayer(opponents.get(1), dealerPlayer) + && player2Controller != null) { player2Controller.setDealer(true); } - if (opponents.size() > 2 && samePlayer(opponents.get(2), dealerPlayer) && player3Controller != null) { + if (opponents.size() > 2 + && samePlayer(opponents.get(2), dealerPlayer) + && player3Controller != null) { player3Controller.setDealer(true); } } /** - * Compare two Player objects to determine if they represent the same player based on their PlayerIds. This method checks for null values and compares the IDs for equality. + * Compare two Player objects to determine if they represent the same player based on their + * PlayerIds. This method checks for null values and compares the IDs for equality. * * @param a The first Player object to compare, which may be null. * @param b The second Player object to compare, which may be null. - * @return true if both Player objects are non-null and have the same non-null PlayerId, false otherwise. + * @return true if both Player objects are non-null and have the same non-null PlayerId, false + * otherwise. */ private boolean samePlayer(Player a, Player b) { if (a == null || b == null || a.getId() == null || b.getId() == null) { @@ -1225,9 +1308,11 @@ public class CasinoGameController { } /** - * Set the PlayerId of the current player, which is used to identify the player's hole cards and update the taskbar with the correct player information. + * Set the PlayerId of the current player, which is used to identify the player's hole cards and + * update the taskbar with the correct player information. * - * @param id The PlayerId of the current player, which should match the PlayerId in the game state for the player's own information to be displayed correctly. + * @param id The PlayerId of the current player, which should match the PlayerId in the game + * state for the player's own information to be displayed correctly. */ public void setMyPlayerId(PlayerId id) { this.myPlayerId = id; @@ -1238,9 +1323,11 @@ public class CasinoGameController { } /** - * Resolve the TaskbarController instance to be used for updating the taskbar with game information. + * Resolve the TaskbarController instance to be used for updating the taskbar with game + * information. * - * @return The TaskbarController instance to use for taskbar updates, which may be the directly injected taskbarController or the one obtained from the taskbarIncludeController. + * @return The TaskbarController instance to use for taskbar updates, which may be the directly + * injected taskbarController or the one obtained from the taskbarIncludeController. */ private TaskbarController resolveTaskbarController() { if (taskbarController != 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 d670df1..f5fe36c 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 @@ -30,10 +30,9 @@ public class CasinoGameUI extends Application { private static final int DEFAULT_WIDTH = 1200; private static final int DEFAULT_HEIGHT = 800; + private static final int GUEST_ID_LENGTH = 8; - /** - * Default constructor for the CasinoGameUI application. - */ + /** Default constructor for the CasinoGameUI application. */ public CasinoGameUI() { // default no-arg constructor } @@ -66,24 +65,26 @@ public class CasinoGameUI extends Application { } /** - * The main entry point for the JavaFX application. This method is called after the application is + * The main entry point for the JavaFX application. This method is called after the application + * is * - * @param stage the primary stage for this application, onto which - * the application scene can be set. - * Applications may create other stages, if needed, but they will not be - * primary stages. + * @param stage the primary stage for this application, onto which the application scene can be + * set. Applications may create other stages, if needed, but they will not be primary + * stages. * @throws IOException */ @Override public void start(Stage stage) throws IOException { if (clientService == null) { - clientService = ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedClientService(); + clientService = + ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedClientService(); } if (clientService == null) { throw new IllegalStateException( "CasinoGameUI: clientService is null. " - + "Call CasinoGameUI.setClientService(...) or start via ClientApp with a shared connection."); + + "Call CasinoGameUI.setClientService(...)" + + " or start via ClientApp with a shared connection."); } String effectiveUsername = normalize(username); @@ -94,13 +95,19 @@ public class CasinoGameUI extends Application { } if (effectiveUsername == null) { - effectiveUsername = "Guest-" + UUID.randomUUID().toString().substring(0, 8); + effectiveUsername = + "Guest-" + UUID.randomUUID().toString().substring(0, GUEST_ID_LENGTH); } - LOG.info("CasinoGameUI starting: effectiveUsername='" + effectiveUsername - + "', injectedUsername='" + username - + "', sharedUsername='" + ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername() - + "', hasClientService=" + (clientService != null)); + LOG.info( + "CasinoGameUI starting: effectiveUsername='" + + effectiveUsername + + "', injectedUsername='" + + username + + "', sharedUsername='" + + ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername() + + "', hasClientService=" + + (clientService != null)); FXMLLoader fxmlLoader = new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml")); @@ -136,13 +143,17 @@ public class CasinoGameUI extends Application { * @return the normalized string, or null if the input is null or blank */ private static String normalize(String s) { - if (s == null) return null; + if (s == null) { + return null; + } + String t = s.trim(); return t.isBlank() ? null : t; } /** - * The main method serves as the entry point for the application. It launches the JavaFX application. + * The main method serves as the entry point for the application. It launches the JavaFX + * application. * * @param args command line arguments (not used) */ diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java index ce49841..9fef1e4 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java @@ -361,7 +361,9 @@ public class TaskbarController { } if (state == null || state.currentBet <= 0) { - LOGGER.warn("Raise not possible: current bet is {}", state != null ? state.currentBet : null); + LOGGER.warn( + "Raise not possible: current bet is {}", + state != null ? state.currentBet : null); return; } 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 349023c..80498df 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 @@ -9,6 +9,7 @@ import java.net.URL; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; +import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; @@ -17,7 +18,6 @@ import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; -import javafx.scene.Node; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import org.apache.logging.log4j.LogManager; @@ -108,7 +108,8 @@ public class CasinomainuiController { } /** - * Initializes the chat UI if a valid ClientService is available. If the client is offline or the + * Initializes the chat UI if a valid ClientService is available. If the client is offline or + * the * * @param clientService */ @@ -134,7 +135,8 @@ public class CasinomainuiController { } /** - * Resolves the username to be used in the chat. It first checks for a shared username set at the + * Resolves the username to be used in the chat. It first checks for a shared username set at + * the * * @return The resolved username, or "Guest" if no valid username is found. */ @@ -143,7 +145,9 @@ public class CasinomainuiController { if (shared != null && !shared.isBlank()) { return shared.trim(); } - if (usernameField != null && usernameField.getText() != null && !usernameField.getText().isBlank()) { + if (usernameField != null + && usernameField.getText() != null + && !usernameField.getText().isBlank()) { return usernameField.getText().trim(); } return "Guest"; 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 e875f43..116686f 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 @@ -2,7 +2,6 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; 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; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter; import java.util.ArrayList; import java.util.Collections; @@ -30,6 +29,7 @@ public class LobbyButtonGridManager { private static final int INITIAL_DELAY_SECONDS = 5; private static final int COLS = 4; private static final int MAX_BUTTONS = 8; + private static final int GUEST_ID_LENGTH = 8; private static final Logger LOGGER = LogManager.getLogger(LobbyButtonGridManager.class); @@ -553,16 +553,28 @@ public class LobbyButtonGridManager { try { var cs = lobbyClient.getClientService(); - ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setClientService(cs); - ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setLobbyId(lobbyId); + ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI + .setClientService(cs); + ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setLobbyId( + lobbyId); - String username = ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername(); + String username = + ch.unibas.dmi.dbis.cs108.casono.client.ClientApp + .getSharedUsername(); if (username == null || username.isBlank()) { - username = "Guest-" + java.util.UUID.randomUUID().toString().substring(0, 8); + username = + "Guest-" + + java.util + .UUID + .randomUUID() + .toString() + .substring(0, GUEST_ID_LENGTH); } - ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setUsername(username); + ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setUsername( + username); - new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI().start(gameStage); + new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI() + .start(gameStage); } catch (Exception e) { LOGGER.error("Game UI failed: {}", e.getMessage()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateHandler.java index 4ea677a..b43f322 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateHandler.java @@ -78,7 +78,8 @@ public class GetGameStateHandler extends CommandHandler { return username.trim(); } - return userRegistry.getBySessionId(request.getSessionId()) + return userRegistry + .getBySessionId(request.getSessionId()) .map(User::getName) .map(String::trim) .filter(name -> !name.isEmpty()) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateResponse.java index 8fee488..479169e 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/game/get_game_state/GetGameStateResponse.java @@ -17,38 +17,24 @@ import java.util.Objects; /** * Response carrying a snapshot of the current game state using the project's wire format. * - * Wire format (relevant parts): + *

Wire format (relevant parts): * - * +OK - * PHASE=... - * POT=... - * CURRENT_BET=... - * DEALER=... - * ACTIVE_PLAYER=... - * CARD - * VALUE=... - * SUIT=... - * END - * PLAYER - * NAME=... - * CHIPS=... - * BET=... - * STATE=... - * CARD (only for requesting user) - * VALUE=... - * SUIT=... - * END - * END - * END + *

+OK PHASE=... POT=... CURRENT_BET=... DEALER=... ACTIVE_PLAYER=... CARD VALUE=... SUIT=... END + * PLAYER NAME=... CHIPS=... BET=... STATE=... CARD (only for requesting user) VALUE=... SUIT=... + * END END END * - * Notes: - * - Community cards are always emitted at top-level (0..5). If none exist (e.g. PREFLOP), none are emitted. - * - Hole cards are only emitted for the requesting player (privacy). + *

Notes: - Community cards are always emitted at top-level (0..5). If none exist (e.g. PREFLOP), + * none are emitted. - Hole cards are only emitted for the requesting player (privacy). */ public class GetGameStateResponse extends SuccessResponse { - public GetGameStateResponse(RequestContext context, GameController game, String requestingUsername) { - super(context, buildBody(Objects.requireNonNull(game, "game must not be null").getState(), requestingUsername)); + public GetGameStateResponse( + RequestContext context, GameController game, String requestingUsername) { + super( + context, + buildBody( + Objects.requireNonNull(game, "game must not be null").getState(), + requestingUsername)); } private static ResponseBody buildBody(GameState state, String requestingUsername) { @@ -72,9 +58,14 @@ public class GetGameStateResponse extends SuccessResponse { private static int computeGlobalCurrentBet(GameState state) { int globalCurrentBet = 0; for (Player p : state.getPlayers()) { - if (p == null) continue; + if (p == null) { + continue; + } + int b = state.getCurrentBet(p.getId()); - if (b > globalCurrentBet) globalCurrentBet = b; + if (b > globalCurrentBet) { + globalCurrentBet = b; + } } return globalCurrentBet; } @@ -87,48 +78,66 @@ public class GetGameStateResponse extends SuccessResponse { } for (Card c : community) { - if (c == null) continue; - builder.block("CARD", card -> { - card.param("VALUE", rankToWire(c.getRank())); - card.param("SUIT", suitToWire(c.getSuit())); - }); + if (c == null) { + continue; + } + + builder.block( + "CARD", + card -> { + card.param("VALUE", rankToWire(c.getRank())); + card.param("SUIT", suitToWire(c.getSuit())); + }); } } - private static void appendPlayers(ResponseBodyBuilder builder, GameState state, String requestingUsername) { + private static void appendPlayers( + ResponseBodyBuilder builder, GameState state, String requestingUsername) { String req = (requestingUsername == null) ? null : requestingUsername.trim(); for (Player p : state.getPlayers()) { - if (p == null) continue; + if (p == null) { + continue; + } PlayerId pid = p.getId(); String name = (pid == null || pid.value() == null) ? "" : pid.value().trim(); - builder.block("PLAYER", pblock -> { - pblock.param("NAME", name); - pblock.param("CHIPS", p.getChips()); - pblock.param("BET", pid == null ? 0 : state.getCurrentBet(pid)); - pblock.param("STATE", p.isFolded() ? "FOLDED" : "ACTIVE"); + builder.block( + "PLAYER", + pblock -> { + pblock.param("NAME", name); + pblock.param("CHIPS", p.getChips()); + pblock.param("BET", pid == null ? 0 : state.getCurrentBet(pid)); + pblock.param("STATE", p.isFolded() ? "FOLDED" : "ACTIVE"); - if (req != null && pid != null && req.equals(name)) { - List hole = state.getHoleCards(pid); + if (req != null && pid != null && req.equals(name)) { + List hole = state.getHoleCards(pid); - if (hole != null) { - for (Card hc : hole) { - if (hc == null) continue; - pblock.block("CARD", cblock -> { - cblock.param("VALUE", rankToWire(hc.getRank())); - cblock.param("SUIT", suitToWire(hc.getSuit())); - }); + if (hole != null) { + for (Card hc : hole) { + if (hc == null) { + continue; + } + + pblock.block( + "CARD", + cblock -> { + cblock.param("VALUE", rankToWire(hc.getRank())); + cblock.param("SUIT", suitToWire(hc.getSuit())); + }); + } + } } - } - } - }); + }); } } private static String rankToWire(Rank r) { - if (r == null) return ""; + if (r == null) { + return ""; + } + return switch (r) { case TWO -> "2"; case THREE -> "3"; @@ -147,7 +156,10 @@ public class GetGameStateResponse extends SuccessResponse { } private static String suitToWire(Suit s) { - if (s == null) return ""; + if (s == null) { + return ""; + } + return switch (s) { case HEARTS -> "H"; case DIAMONDS -> "D"; diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/engine/RoundManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/engine/RoundManager.java index 5faa01f..4853946 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/engine/RoundManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/game/engine/RoundManager.java @@ -11,13 +11,15 @@ import java.util.ArrayList; import java.util.List; /** - * RoundManager manages hand lifecycle and phase progression (PREFLOP -> FLOP -> TURN -> RIVER -> SHOWDOWN). + * RoundManager manages hand lifecycle and phase progression (PREFLOP -> FLOP -> TURN -> RIVER -> + * SHOWDOWN). * *

This implementation: + * *

*/ public class RoundManager { @@ -56,10 +58,14 @@ public class RoundManager { int target = state.getTableState().getCurrentBet(); for (Player p : state.getPlayers()) { - if (p == null) continue; + if (p == null) { + continue; + } // be tolerant if codebase mixes status + boolean flags - if (p.getStatus() == PlayerStatus.FOLDED || p.isFolded()) continue; + if (p.getStatus() == PlayerStatus.FOLDED || p.isFolded()) { + continue; + } int bet = state.getCurrentBet(p.getId()); if (bet < target && !p.isAllIn()) { @@ -75,7 +81,9 @@ public class RoundManager { case FLOP -> dealTurn(state); case TURN -> dealRiver(state); case RIVER -> showdown(state); - case SHOWDOWN -> { /* nothing */ } + case SHOWDOWN -> { + /* nothing */ + } } } @@ -92,7 +100,9 @@ public class RoundManager { Deck deck = state.getDeck(); for (Player p : state.getPlayers()) { - if (p == null) continue; + if (p == null) { + continue; + } PlayerId pid = p.getId(); Card c1 = deck.draw(); @@ -103,20 +113,28 @@ public class RoundManager { } /** - * NOTE: Blind assignment here is intentionally minimal because GameState does not expose seating order. - * If you want correct dealer-relative blinds, add helpers in GameState (e.g. getPlayerIdAt(int)). + * NOTE: Blind assignment here is intentionally minimal because GameState does not expose + * seating order. If you want correct dealer-relative blinds, add helpers in GameState (e.g. + * getPlayerIdAt(int)). */ private void postBlinds(GameState state) { List playerList = new ArrayList<>(state.getPlayers()); - if (playerList.size() < 2) return; + if (playerList.size() < 2) { + return; + } // pick first two non-folded players as SB/BB Player sb = null; Player bb = null; for (Player p : playerList) { - if (p == null) continue; - if (p.isFolded()) continue; + if (p == null) { + continue; + } + + if (p.isFolded()) { + continue; + } if (sb == null) { sb = p; @@ -126,7 +144,9 @@ public class RoundManager { } } - if (sb == null || bb == null) return; + if (sb == null || bb == null) { + return; + } sb.removeChips(SMALL_BLIND); bb.removeChips(BIG_BLIND); @@ -186,4 +206,4 @@ public class RoundManager { // winner evaluation is elsewhere (rules/controller) // do NOT reset cards here; clients still need board visible during showdown } -} \ No newline at end of file +} 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 9b614e5..2e2d054 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 @@ -14,10 +14,12 @@ import java.util.Map; * The GameState class encapsulates the entire state of a poker game at any given moment. * *

Important invariants this implementation maintains: + * *

*/ public class GameState { @@ -42,6 +44,8 @@ public class GameState { private Deck deck; + private static final int DEALER_OFFSET = 3; + // Getters public Collection getPlayers() { List ordered = new ArrayList<>(playerOrder.size()); @@ -121,7 +125,7 @@ public class GameState { } // Heads-up: dealer (small blind) acts first preflop. - int first = (size == 2) ? dealerIndex : (dealerIndex + 3) % size; + int first = (size == 2) ? dealerIndex : (dealerIndex + DEALER_OFFSET) % size; currentPlayerIndex = first; if (!canPlayerAct(currentPlayerIndex)) { nextPlayer(); @@ -180,8 +184,7 @@ public class GameState { } /** - * Reset bets to 0 for ALL players (do not clear the map). - * Also resets table current bet to 0. + * Reset bets to 0 for ALL players (do not clear the map). Also resets table current bet to 0. */ public void resetBets() { for (PlayerId id : playerOrder) { @@ -205,7 +208,10 @@ public class GameState { // Player helpers public Player getPlayer(PlayerId id) { Player player = players.get(id); - if (player == null) throw new RuntimeException("Player not found: " + id); + if (player == null) { + throw new RuntimeException("Player not found: " + id); + } + return player; } @@ -221,8 +227,8 @@ public class GameState { // Cards /** - * Gives (overwrites) the two hole cards for the given player. - * Ensures stable list identity (important if other code holds references). + * Gives (overwrites) the two hole cards for the given player. Ensures stable list identity + * (important if other code holds references). */ public void giveHoleCards(PlayerId playerId, Card c1, Card c2) { List cards = holeCards.computeIfAbsent(playerId, k -> new ArrayList<>()); @@ -282,11 +288,12 @@ public class GameState { * Starts a new hand by resetting the game state for the next round of poker. * *

This: + * *

    - *
  • sets {@code phase=PREFLOP}
  • - *
  • resets pot/bets/commitments
  • - *
  • clears community + hole cards
  • - *
  • creates & shuffles a new deck
  • + *
  • sets {@code phase=PREFLOP} + *
  • resets pot/bets/commitments + *
  • clears community + hole cards + *
  • creates & shuffles a new deck *
*/ public void startNewHand() {