Fix: improve round management, betting validation and game engine test coverage #290

Merged
j.kropff merged 13 commits from fix/game-turn-and-action-bugs into main 2026-04-19 20:31:45 +02:00
Showing only changes of commit 8099228d4f - Show all commits
@@ -37,6 +37,125 @@ public class GameControllerTest {
private static final Logger LOGGER = Logger.getLogger(GameControllerTest.class.getName()); private static final Logger LOGGER = Logger.getLogger(GameControllerTest.class.getName());
/**
* Helper method to retrieve the current player's ID from the game state.
*
* @param game The GameController instance from which to retrieve the current player's ID.
* @return The PlayerId of the current player in the game.
*/
private static PlayerId currentPlayerId(GameController game) {
return game.getState().getCurrentPlayer().getId();
}
/**
* Helper method to simulate a call action for the current player in the game.
*
* @param game The GameController instance on which to perform the call action for the current player.
*/
private static void callCurrent(GameController game) {
game.playerCall(currentPlayerId(game));
}
/**
* Helper method to simulate a fold action for the current player in the game.
*
* @param game The GameController instance on which to perform the fold action for the current player.
*/
private static void foldCurrent(GameController game) {
game.playerFold(currentPlayerId(game));
}
/**
* Verifies that the preflop phase ends once all active (non-folded) players
* have completed their required actions for the current betting round.
*/
@Test
public void testPreflopEndsAfterOneActionPerActivePlayer() {
GameState state = new GameState();
GameEngine engine =
new GameEngine(
state,
new RuleEngine(new ArrayList<>()),
new RoundManager(),
new TurnManager());
GameController game = new GameController(engine);
game.addPlayer(PlayerId.of("P1"), 10000);
game.addPlayer(PlayerId.of("P2"), 10000);
game.addPlayer(PlayerId.of("P3"), 10000);
game.addPlayer(PlayerId.of("P4"), 10000);
game.startGame();
assertEquals(GamePhase.PREFLOP, game.getState().getPhase());
assertEquals(0, game.getCommunityCards().size());
callCurrent(game);
callCurrent(game);
callCurrent(game);
assertEquals(GamePhase.PREFLOP, game.getState().getPhase());
assertEquals(0, game.getCommunityCards().size());
callCurrent(game);
assertEquals(GamePhase.FLOP, game.getState().getPhase());
assertEquals(3, game.getCommunityCards().size());
callCurrent(game);
callCurrent(game);
callCurrent(game);
callCurrent(game);
assertEquals(GamePhase.TURN, game.getState().getPhase());
assertEquals(4, game.getCommunityCards().size());
callCurrent(game);
callCurrent(game);
callCurrent(game);
callCurrent(game);
assertEquals(GamePhase.RIVER, game.getState().getPhase());
assertEquals(5, game.getCommunityCards().size());
}
/**
* Ensures that during the preflop phase, folded players are excluded
* from progression checks and do not block phase advancement.
*/
@Test
public void testPreflopCountsOnlyNonFoldedPlayersForProgress() {
GameState state = new GameState();
GameEngine engine =
new GameEngine(
state,
new RuleEngine(new ArrayList<>()),
new RoundManager(),
new TurnManager());
GameController game = new GameController(engine);
game.addPlayer(PlayerId.of("A"), 10000);
game.addPlayer(PlayerId.of("B"), 10000);
game.addPlayer(PlayerId.of("C"), 10000);
game.addPlayer(PlayerId.of("D"), 10000);
game.startGame();
assertEquals(GamePhase.PREFLOP, game.getState().getPhase());
callCurrent(game);
foldCurrent(game);
callCurrent(game);
assertEquals(GamePhase.PREFLOP, game.getState().getPhase());
callCurrent(game);
assertEquals(GamePhase.FLOP, game.getState().getPhase());
assertEquals(3, game.getCommunityCards().size());
}
/** /**
* This test simulates a specific game scenario where Julian is expected to win with a Two Pair * This test simulates a specific game scenario where Julian is expected to win with a Two Pair
* hand. It sets up a fixed deck to ensure that the desired cards are dealt to the players and * hand. It sets up a fixed deck to ensure that the desired cards are dealt to the players and
@@ -83,11 +202,14 @@ public class GameControllerTest {
} }
} }
game.playerCall(PlayerId.of("Julian")); callCurrent(game);
game.playerFold(PlayerId.of("Mathis")); game.playerFold(PlayerId.of("Mathis"));
game.playerCall(PlayerId.of("Jona")); callCurrent(game);
game.playerRaise(PlayerId.of("Lars"), 1200); game.playerRaise(PlayerId.of("Lars"), 1200);
// New street model: once all non-folded players acted, preflop is complete.
assertEquals(GamePhase.FLOP, game.getState().getPhase());
List<Card> board = game.getCommunityCards(); List<Card> board = game.getCommunityCards();
assertTrue(board.size() >= 3 && board.size() <= 5); assertTrue(board.size() >= 3 && board.size() <= 5);
@@ -170,10 +292,10 @@ public class GameControllerTest {
int potBefore = game.getState().getPot().getAmount(); int potBefore = game.getState().getPot().getAmount();
game.playerRaise(PlayerId.of("Lars"), 1200); game.playerRaise(currentPlayerId(game), 1200);
game.playerCall(PlayerId.of("Julian")); callCurrent(game);
game.playerFold(PlayerId.of("Mathis")); foldCurrent(game);
game.playerCall(PlayerId.of("Jona")); callCurrent(game);
System.out.println("Pot after actions: " + game.getState().getPot().getAmount()); System.out.println("Pot after actions: " + game.getState().getPot().getAmount());
@@ -183,11 +305,15 @@ public class GameControllerTest {
assertEquals(3, game.getCommunityCards().size(), "The flop must have 3 cards"); assertEquals(3, game.getCommunityCards().size(), "The flop must have 3 cards");
game.playerCall(PlayerId.of("Julian")); for (int i = 0; i < 10 && game.getCommunityCards().size() < 4; i++) {
callCurrent(game);
}
assertEquals(4, game.getCommunityCards().size(), "A turn must result in 4 cards"); assertEquals(4, game.getCommunityCards().size(), "A turn must result in 4 cards");
game.playerCall(PlayerId.of("Jona")); for (int i = 0; i < 10 && game.getCommunityCards().size() < 5; i++) {
callCurrent(game);
}
assertEquals(5, game.getCommunityCards().size(), "The river must consist of 5 cards"); assertEquals(5, game.getCommunityCards().size(), "The river must consist of 5 cards");
@@ -278,13 +404,12 @@ public class GameControllerTest {
game.playerCall(PlayerId.of("Jona")); game.playerCall(PlayerId.of("Jona"));
game.playerRaise(PlayerId.of("Lars"), 1200); game.playerRaise(PlayerId.of("Lars"), 1200);
game.playerCall(PlayerId.of("Julian")); assertEquals(GamePhase.FLOP, game.getState().getPhase());
game.playerCall(PlayerId.of("Jona"));
assertTrue(game.getCommunityCards().size() >= 3); assertTrue(game.getCommunityCards().size() >= 3);
for (int i = 0; i < 10 && game.getCommunityCards().size() < 5; i++) { for (int i = 0; i < 10 && game.getCommunityCards().size() < 5; i++) {
game.playerCall(PlayerId.of("Julian")); callCurrent(game);
} }
assertEquals(5, game.getCommunityCards().size()); assertEquals(5, game.getCommunityCards().size());
@@ -367,10 +492,14 @@ public class GameControllerTest {
assertEquals(3, game.getCommunityCards().size()); assertEquals(3, game.getCommunityCards().size());
game.playerCall(PlayerId.of("Julian")); for (int i = 0; i < 10 && game.getCommunityCards().size() < 4; i++) {
callCurrent(game);
}
assertEquals(4, game.getCommunityCards().size()); assertEquals(4, game.getCommunityCards().size());
game.playerCall(PlayerId.of("Mathis")); for (int i = 0; i < 10 && game.getCommunityCards().size() < 5; i++) {
callCurrent(game);
}
assertEquals(5, game.getCommunityCards().size()); assertEquals(5, game.getCommunityCards().size());
Set<String> boardSeen = new HashSet<>(); Set<String> boardSeen = new HashSet<>();
@@ -424,10 +553,10 @@ public class GameControllerTest {
game.startGame(); game.startGame();
game.playerRaise(PlayerId.of("BigStack"), 1000); game.playerRaise(currentPlayerId(game), 1000);
game.playerCall(PlayerId.of("Caller")); callCurrent(game);
game.playerCall(PlayerId.of("MidStack")); callCurrent(game);
game.playerCall(PlayerId.of("ShortStack")); callCurrent(game);
for (Player p : game.getState().getPlayers()) { for (Player p : game.getState().getPlayers()) {
assertTrue(p.getChips() >= 0, "Negative chips detected for " + p.getId()); assertTrue(p.getChips() >= 0, "Negative chips detected for " + p.getId());
@@ -435,13 +564,13 @@ public class GameControllerTest {
assertTrue(game.getState().getPot().getAmount() > 0); assertTrue(game.getState().getPot().getAmount() > 0);
game.playerRaise(PlayerId.of("BigStack"), 100); game.playerRaise(currentPlayerId(game), 100);
int pot = game.getState().getPot().getAmount(); int pot = game.getState().getPot().getAmount();
assertTrue(pot > 0, "Pot must remain valid after re-raises"); assertTrue(pot > 0, "Pot must remain valid after re-raises");
game.playerFold(PlayerId.of("Caller")); foldCurrent(game);
game.playerFold(PlayerId.of("MidStack")); foldCurrent(game);
long activePlayers = long activePlayers =
game.getState().getPlayers().stream().filter(p -> p.getChips() > 0).count(); game.getState().getPlayers().stream().filter(p -> p.getChips() > 0).count();
@@ -451,7 +580,7 @@ public class GameControllerTest {
assertTrue(game.getCommunityCards().size() >= 3); assertTrue(game.getCommunityCards().size() >= 3);
for (int i = 0; i < 10 && game.getCommunityCards().size() < 5; i++) { for (int i = 0; i < 10 && game.getCommunityCards().size() < 5; i++) {
game.playerCall(PlayerId.of("BigStack")); callCurrent(game);
} }
assertEquals(5, game.getCommunityCards().size()); assertEquals(5, game.getCommunityCards().size());