From 238a156d9106e3c150c26aef24669407d6c743ac Mon Sep 17 00:00:00 2001 From: Julian Kropff Date: Sun, 19 Apr 2026 19:28:13 +0200 Subject: [PATCH] Fix: round transition logic in RoundManager Refs #118 --- .../domain/game/engine/RoundManager.java | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) 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 677c1b4..9602398 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 @@ -27,6 +27,12 @@ public class RoundManager { public static final int SMALL_BLIND = 100; public static final int BIG_BLIND = 200; + /** + * Starts a new hand by resetting the game state, ensuring a deck is available, dealing hole cards + * to players, posting blinds, and setting the first player to act for the preflop phase. + * + * @param state The GameState object representing the current state of the game, which will be modified to + */ public void startNewHand(GameState state) { // Use GameState's canonical reset state.startNewHand(); @@ -44,12 +50,19 @@ public class RoundManager { state.setCurrentPlayerToPreflopFirstToAct(); } + /** + * Checks if the current betting round is finished and advances the game phase if necessary. + * + * @param state The GameState object representing the current state of the game, + * which may be modified to advance the phase or end the hand. + */ public void progressIfNeeded(GameState state) { if (state.getPhase() == null) { state.setPhase(GamePhase.PREFLOP); } if (state.countNonFoldedPlayers() == 1) { + state.setHandActive(false); state.setPhase(GamePhase.FINISHED); return; } @@ -59,9 +72,27 @@ public class RoundManager { } } + /** + * Determines if the current betting round is finished by checking if all active + * players have met the current bet or are all-in and by handling special cases for + * preflop betting rounds. + * + * @param state The GameState object representing the current state of the game, + * which is used to evaluate the betting round status. + * @return true if the betting round is finished and the game can progress to the + * next phase, false otherwise. + */ private boolean isBettingRoundFinished(GameState state) { int target = state.getTableState().getCurrentBet(); + if (target == 0 && isPostflopStreet(state.getPhase()) && !allActivePlayersActedThisRound(state)) { + return false; + } + + if (isPendingBigBlindOption(state, target)) { + return false; + } + for (Player p : state.getPlayers()) { if (p == null) { continue; @@ -80,6 +111,79 @@ public class RoundManager { return true; } + /** + * Helper method to determine if the current game phase is a postflop street (FLOP, TURN, RIVER). + * + * @param phase The GamePhase to check, which is used to determine if the current street is postflop. + * @return true if the phase is FLOP, TURN, or RIVER, indicating a postflop street, false otherwise. + */ + private boolean isPostflopStreet(GamePhase phase) { + return phase == GamePhase.FLOP || phase == GamePhase.TURN || phase == GamePhase.RIVER; + } + + /** + * Helper method to check if all active players have acted in the current betting round. + * This is used to handle the special case of preflop rounds where no bets have been made yet, + * but players still need to have the opportunity to act. + * + * @param state The GameState object representing the current state of the game, which is + * used to check if all active players have acted in the current round. + * @return true if all active players have acted in the current round, false if there are + * still active players who have not acted yet. + */ + private boolean allActivePlayersActedThisRound(GameState state) { + for (Player p : state.getPlayers()) { + if (p == null || p.isFolded() || p.isAllIn()) { + continue; + } + + if (!state.hasActedThisRound(p.getId())) { + return false; + } + } + return true; + } + + /** + * Helper method to determine if the current player has a pending big blind option + * in the preflop betting round. + * + * @param state The GameState object representing the current state of the game, + * which is used to determine if the big blind has a pending option + * in the preflop round. + * @param target The current target bet that players need to meet, which is used to + * check if the big blind has a pending option when the target is + * equal to the big blind amount. + * @return true if the big blind has a pending option to act in the preflop round, + * false otherwise. + */ + private boolean isPendingBigBlindOption(GameState state, int target) { + if (state == null || state.getPhase() != GamePhase.PREFLOP) { + return false; + } + + if (target != BIG_BLIND) { + return false; + } + + int size = state.getPlayerCount(); + if (size < 2) { + return false; + } + + int dealer = state.getDealerIndex(); + int bigBlindIndex = (size == 2) ? (dealer + 1) % size : (dealer + 2) % size; + + return state.getCurrentPlayerIndex() == bigBlindIndex; + } + + /** + * Advances the game phase to the next stage (FLOP, TURN, RIVER, SHOWDOWN) based on the current phase. + * + * @param state The GameState object representing the current state of the game, which will be modified + * to advance the phase and deal community cards as needed when progressing to the + * next stage of the hand. + */ private void advancePhase(GameState state) { switch (state.getPhase()) { case PREFLOP -> dealFlop(state); @@ -93,6 +197,13 @@ public class RoundManager { } } + /** + * Ensures that a deck of cards is available in the game state. + * + * @param state The GameState object representing the current state of the game, + * which will be modified to include a new shuffled deck if one + * does not already exist. + */ private void ensureDeck(GameState state) { Deck deck = state.getDeck(); if (deck == null) { @@ -102,6 +213,13 @@ public class RoundManager { } } + /** + * Deals hole cards to each player in the game state by drawing two cards from the + * deck for each player and assigning them as their hole cards. + * + * @param state The GameState object representing the current state of the game, + * which will be modified to assign hole cards to each active player. + */ private void dealHoleCards(GameState state) { Deck deck = state.getDeck(); @@ -165,6 +283,15 @@ public class RoundManager { state.getTableState().setCurrentBet(BIG_BLIND); } + /** + * Deals the flop by drawing three community cards from the deck and adding them + * to the game state, then setting the game phase to FLOP and resetting bets for + * the new betting round. + * + * @param state The GameState object representing the current state of the game, + * which will be modified to add three community cards for the flop, + * set the phase to FLOP, and reset bets for the new betting round. + */ private void dealFlop(GameState state) { ensureDeck(state); Deck deck = state.getDeck(); @@ -180,6 +307,15 @@ public class RoundManager { state.setCurrentPlayerToPostflopFirstToAct(); } + /** + * Deals the turn by drawing one community card from the deck and adding it to + * the game state, then setting the game phase to TURN and resetting bets for + * the new betting round. + * + * @param state The GameState object representing the current state of the game, + * which will be modified to add one community card for the turn, + * set the phase to TURN, and reset bets for the new betting round. + */ private void dealTurn(GameState state) { ensureDeck(state); Deck deck = state.getDeck(); @@ -193,6 +329,15 @@ public class RoundManager { state.setCurrentPlayerToPostflopFirstToAct(); } + /** + * Deals the river by drawing one community card from the deck and adding it + * to the game state, then setting the game phase to RIVER and resetting bets + * for the new betting round. + * + * @param state The GameState object representing the current state of the game, + * which will be modified to add one community card for the river, + * set the phase to RIVER, and reset bets for the new betting round. + */ private void dealRiver(GameState state) { ensureDeck(state); Deck deck = state.getDeck(); @@ -206,6 +351,12 @@ public class RoundManager { state.setCurrentPlayerToPostflopFirstToAct(); } + /** + * Handles the showdown phase by setting the game phase to SHOWDOWN. + * + * @param state The GameState object representing the current state of the game, + * which will be modified to set the phase to SHOWDOWN. + */ private void showdown(GameState state) { state.setPhase(GamePhase.SHOWDOWN);