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 e58c13d..896e8bf 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 @@ -39,6 +39,7 @@ public class GameService { * @return The current GameState object representing the state of the game. */ public int getPot() { + ensureState(); return state.pot; } @@ -48,6 +49,7 @@ public class GameService { * @return The current GameState object representing the state of the game. */ public List getCommunityCards() { + ensureState(); return state.communityCards; } @@ -57,6 +59,7 @@ public class GameService { * @return A list of Player objects representing the players in the current game state. */ public List getPlayers() { + ensureState(); return state.players; } @@ -98,10 +101,19 @@ public class GameService { * yet. */ public Player getWinner() { + ensureState(); + if (state.winnerIndex < 0) { return null; } return state.players.get(state.winnerIndex); } + + /** Ensure that the game state has been initialized before accessing it. */ + private void ensureState() { + if (state == null) { + throw new IllegalStateException("Call refresh() first"); + } + } } 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 685e870..e8e3cae 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 @@ -34,30 +34,37 @@ public class GameClient { * @return A GameState object representing the current state of the game, as parsed */ public GameState fetchGameState() { - List responseLines = client.processCommand("GET_GAME_STATE\nGAME_ID=" + gameId); + List responseLines = client.processCommand("GET_GAME_STATE GAME_ID=" + gameId); if (responseLines == null || responseLines.isEmpty()) { throw new RuntimeException("Empty server response"); } - String fullResponse = String.join("\n", responseLines); + if (responseLines.get(0).startsWith("-ERR")) { + throw new RuntimeException("Server Error: " + String.join(" ", responseLines)); + } + if (!responseLines.get(0).startsWith("+OK")) { + throw new RuntimeException("Invalid response: missing +OK"); + } + + String fullResponse = String.join("\n", responseLines); return parse(fullResponse); } /** Send a CALL command to the server to indicate that the player wants to call */ public void sendCall() { - client.processCommand("CALL\nGAME_ID=" + gameId); + client.processCommand("CALL GAME_ID=" + gameId); } /** Send a FOLD command to the server to indicate that the player wants to fold */ public void sendFold() { - client.processCommand("FOLD\nGAME_ID=" + gameId); + client.processCommand("FOLD GAME_ID=" + gameId); } /** Send a BET command to the server to indicate that the player wants to bet */ public void sendBet(int amount) { - client.processCommand("BET\nGAME_ID=" + gameId + "\nAMOUNT=" + amount); + client.processCommand("BET GAME_ID=" + gameId + " AMOUNT=" + amount); } /** @@ -66,7 +73,7 @@ public class GameClient { * @param amount The amount to raise to */ public void sendRaise(int amount) { - client.processCommand("RAISE\nGAME_ID=" + gameId + "\nAMOUNT=" + amount); + client.processCommand("RAISE GAME_ID=" + gameId + " AMOUNT=" + amount); } /** @@ -76,8 +83,12 @@ public class GameClient { * @return A GameState object representing the current state of the game. */ private GameState parse(String input) { - GameState state = new GameState(); + if (input.startsWith("-ERR")) { + throw new RuntimeException("Server returned Error:\n" + input); + } + + GameState state = new GameState(); ParserContext ctx = new ParserContext(state); for (String raw : input.split("\n")) { @@ -156,6 +167,7 @@ public class GameClient { state.winnerIndex = intVal(line); } else { return false; + // throw new RuntimeException("Unknown global field: " + line); } return true; } @@ -171,7 +183,7 @@ public class GameClient { private boolean handleSections(String line, ParserContext ctx) { if (line.equals("END")) { - ctx.inPlayers = false; + // ctx.inPlayers = false; ctx.inPlayerCards = false; ctx.inCommunityCards = false; ctx.currentPlayer = null; 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 05abedb..226b5f1 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 @@ -48,6 +48,7 @@ public class CasinoGameController { private PlayerId myPlayerId; private TaskbarController taskbarController; private Image dealerImage; + private javafx.animation.Timeline timeline; private static final int TOTAL_SLOTS = 5; private static final int PLAYER_SLOTS = 2; @@ -166,6 +167,8 @@ public class CasinoGameController { // uiTest(); + // or + // empty display only (optional) renderCommunityCards(List.of()); renderPlayerCards(List.of()); @@ -277,42 +280,69 @@ public class CasinoGameController { */ private void startLoop() { - javafx.animation.Timeline t = + timeline = new javafx.animation.Timeline( new javafx.animation.KeyFrame( javafx.util.Duration.seconds(UI_UPDATE_INTERVAL_SECONDS), e -> updateUI())); - t.setCycleCount(javafx.animation.Animation.INDEFINITE); - t.play(); + timeline.setCycleCount(javafx.animation.Animation.INDEFINITE); + timeline.play(); } /** - * Update the UI by fetching the latest game state from the GameService and updating all - * relevant components. + * Stop the UI update loop, which can be called when the game ends or when the user exits the + * game screen to prevent unnecessary updates and resource usage. + */ + public void stop() { + if (timeline != null) { + timeline.stop(); + } + } + + /** + * Triggers an asynchronous UI update by fetching the current game state from the GameService. */ private void updateUI() { - try { - GameState s = gameService.refresh(); + java.util.concurrent.CompletableFuture.supplyAsync( + () -> { + try { + return gameService.refresh(); + } catch (Exception e) { + LOGGER.severe("GameService Error: " + e.getMessage()); + return null; + } + }) + .thenAccept( + s -> { + if (s == null) { + return; + } - if (s == null) { - return; - } + javafx.application.Platform.runLater(() -> applyState(s)); + }); + } - renderCommunityCards(s.communityCards); - renderPot(s.pot); + /** + * Updates all visual UI components with the data from the provided GameState. This includes + * community cards, pot, player information, and the taskbar. + * + * @param s The current state of the game to be rendered. + */ + private void applyState(GameState s) { - updatePlayers(s.players); - updatePlayerCards(s); + renderCommunityCards(s.communityCards); + renderPot(s.pot); - updateGameInfo(s); - highlightDealer(s); + updatePlayers(s.players); + updatePlayerCards(s); - updateTaskbar(s); + updateGameInfo(s); + highlightDealer(s); - } catch (Exception e) { - LOGGER.severe("Error: UI Update failed: " + e.getMessage()); + if (taskbarController != null) { + taskbarController.update(s, myPlayerId); } } @@ -323,14 +353,20 @@ public class CasinoGameController { */ private void updatePlayerCards(GameState s) { - int active = s.activePlayer; - - if (active >= 0 && active < s.players.size()) { - Player p = s.players.get(active); - renderPlayerCards(p.getCards()); - } else { + if (s.players == null || myPlayerId == null) { renderPlayerCards(List.of()); + return; } + + for (Player p : s.players) { + if (p.getId().equals(myPlayerId)) { + renderPlayerCards(p.getCards()); + return; + } + } + + // fallback + renderPlayerCards(List.of()); } /** @@ -357,8 +393,9 @@ public class CasinoGameController { * @param s The current game state. */ private void updateTaskbar(GameState s) { - - taskbarController.update(s, myPlayerId); + if (taskbarController != null) { + taskbarController.update(s, myPlayerId); + } } /** diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/get_lobby_status/GetLobbyStatusResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/get_lobby_status/GetLobbyStatusResponse.java index 15d82fb..5cbc6aa 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/get_lobby_status/GetLobbyStatusResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/lobby/get_lobby_status/GetLobbyStatusResponse.java @@ -27,6 +27,13 @@ public class GetLobbyStatusResponse extends SuccessResponse { "LOBBY", lb -> { lb.param("ID", lobby.getId().value()); + // STATUS indicates whether a game has been started in this + // lobby + lb.param( + "STATUS", + lobby.getGameController() == null + ? "CREATED" + : "RUNNING"); lb.param("NAME", lobby.getName()); lb.block( "PLAYERS",