Fix: round transition logic in RoundManager

Refs #118
This commit is contained in:
Julian Kropff
2026-04-19 19:28:13 +02:00
parent 621843167e
commit 238a156d91
@@ -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);