Fix: Resolve blocking issues to enable full game loop
This commit is contained in:
@@ -6,6 +6,8 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
@@ -110,8 +112,7 @@ public class ClientService {
|
||||
|
||||
List<String> lines = new ArrayList<>();
|
||||
|
||||
int depth = 0;
|
||||
boolean inPlayer = false;
|
||||
Deque<String> blockStack = new ArrayDeque<>();
|
||||
|
||||
for (String rawLine : responseText.split("\\n")) {
|
||||
String line = rawLine;
|
||||
@@ -136,27 +137,15 @@ public class ClientService {
|
||||
line = line.replaceFirst("^\\s+", "");
|
||||
trimmed = line.trim();
|
||||
|
||||
if ("PLAYER".equals(trimmed)) {
|
||||
inPlayer = true;
|
||||
lines.add("PLAYER");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isContainerStart(trimmed)) {
|
||||
depth++;
|
||||
blockStack.push(trimmed);
|
||||
lines.add(trimmed);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ("END".equals(trimmed)) {
|
||||
if (inPlayer) {
|
||||
inPlayer = false;
|
||||
lines.add("END");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (depth > 0) {
|
||||
depth--;
|
||||
if (!blockStack.isEmpty()) {
|
||||
blockStack.pop();
|
||||
lines.add("END");
|
||||
continue;
|
||||
}
|
||||
@@ -180,8 +169,8 @@ public class ClientService {
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Parsed response lines (rid={}, success={}, depthEnd={}, inPlayerEnd={}): {}",
|
||||
rid, success, depth, inPlayer, lines);
|
||||
logger.debug("Parsed response lines (rid={}, success={}, openBlocks={}): {}",
|
||||
rid, success, blockStack.size(), lines);
|
||||
|
||||
if (rid == 0) {
|
||||
for (Consumer<List<String>> l : eventListeners) {
|
||||
@@ -205,8 +194,9 @@ public class ClientService {
|
||||
return token.equals("LOBBIES")
|
||||
|| token.equals("LOBBY")
|
||||
|| token.equals("PLAYERS")
|
||||
|| token.equals("CARDS");
|
||||
// NOTE: PLAYER deliberately excluded
|
||||
|| token.equals("PLAYER")
|
||||
|| token.equals("CARDS")
|
||||
|| token.equals("CARD");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -107,6 +107,7 @@ public class GameClient {
|
||||
|
||||
Player currentPlayer = null;
|
||||
Card currentCard = null;
|
||||
boolean insidePlayer = false;
|
||||
|
||||
for (String raw : input.split("\n")) {
|
||||
String line = raw.trim();
|
||||
@@ -143,6 +144,7 @@ public class GameClient {
|
||||
currentPlayer = new Player();
|
||||
s.players.add(currentPlayer);
|
||||
currentCard = null;
|
||||
insidePlayer = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -153,10 +155,10 @@ public class GameClient {
|
||||
if (line.equals("CARD")) {
|
||||
currentCard = new Card("", "");
|
||||
|
||||
if (currentPlayer != null) {
|
||||
currentPlayer.addCard(currentCard); // hole card
|
||||
if (insidePlayer && currentPlayer != null) {
|
||||
currentPlayer.addCard(currentCard); // hole card
|
||||
} else {
|
||||
s.communityCards.add(currentCard); // community card
|
||||
s.communityCards.add(currentCard); // community card
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -171,17 +173,16 @@ public class GameClient {
|
||||
}
|
||||
|
||||
if (line.equals("END")) {
|
||||
if (currentCard != null) {
|
||||
if (currentCard != null) { // schließt CARD
|
||||
currentCard = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentPlayer != null) {
|
||||
if (insidePlayer) { // schließt PLAYER
|
||||
insidePlayer = false;
|
||||
currentPlayer = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
continue; // root END
|
||||
}
|
||||
|
||||
if (currentPlayer != null) {
|
||||
|
||||
+45
-10
@@ -47,6 +47,7 @@ public class CasinoGameController {
|
||||
|
||||
private GameService gameService;
|
||||
private PlayerId myPlayerId;
|
||||
@FXML private TaskbarController taskbarIncludeController;
|
||||
private TaskbarController taskbarController;
|
||||
private Image dealerImage;
|
||||
private javafx.animation.Timeline timeline;
|
||||
@@ -154,6 +155,10 @@ public class CasinoGameController {
|
||||
*/
|
||||
public void setGameService(GameService gameService) {
|
||||
this.gameService = gameService;
|
||||
TaskbarController controller = resolveTaskbarController();
|
||||
if (controller != null && myPlayerId != null) {
|
||||
controller.setGameService(gameService, myPlayerId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the PlayerId of the current player. */
|
||||
@@ -161,6 +166,11 @@ public class CasinoGameController {
|
||||
public void initialize() {
|
||||
LOGGER.info("INIT UI");
|
||||
|
||||
taskbarController = resolveTaskbarController();
|
||||
if (taskbarController != null && gameService != null && myPlayerId != null) {
|
||||
taskbarController.setGameService(gameService, myPlayerId);
|
||||
}
|
||||
|
||||
if (communityCardsBox == null) {
|
||||
LOGGER.warning("communityCardsBox is NULL");
|
||||
return;
|
||||
@@ -471,8 +481,12 @@ public class CasinoGameController {
|
||||
* @param s The current game state.
|
||||
*/
|
||||
private void updateTaskbar(GameState s) {
|
||||
if (taskbarController != null) {
|
||||
taskbarController.update(s, myPlayerId);
|
||||
TaskbarController controller = resolveTaskbarController();
|
||||
if (controller != null) {
|
||||
if (gameService != null && myPlayerId != null) {
|
||||
controller.setGameService(gameService, myPlayerId);
|
||||
}
|
||||
controller.update(s, myPlayerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,23 +882,29 @@ public class CasinoGameController {
|
||||
return BACKSIDE;
|
||||
}
|
||||
|
||||
String rawSuit = card.getSuit();
|
||||
String rawValue = card.getValue();
|
||||
if (rawSuit == null || rawValue == null) {
|
||||
return BACKSIDE;
|
||||
}
|
||||
|
||||
String suit =
|
||||
switch (card.getSuit().toLowerCase()) {
|
||||
case "hearts" -> "heart";
|
||||
case "diamonds" -> "diamond";
|
||||
case "clubs", "cross" -> "cross";
|
||||
case "spades", "pik" -> "pik";
|
||||
default -> card.getSuit().toLowerCase();
|
||||
switch (rawSuit.trim().toLowerCase()) {
|
||||
case "h", "hearts", "heart" -> "heart";
|
||||
case "d", "diamonds", "diamond" -> "diamond";
|
||||
case "c", "clubs", "club", "cross" -> "cross";
|
||||
case "s", "spades", "spade", "pik" -> "pik";
|
||||
default -> rawSuit.trim().toLowerCase();
|
||||
};
|
||||
|
||||
String value =
|
||||
switch (card.getValue().toLowerCase()) {
|
||||
switch (rawValue.trim().toLowerCase()) {
|
||||
case "a", "ace" -> "ace";
|
||||
case "k", "king" -> "king";
|
||||
case "q", "queen" -> "queen";
|
||||
case "j", "jack" -> "jack";
|
||||
case "10", "t" -> "10";
|
||||
default -> card.getValue().toLowerCase();
|
||||
default -> rawValue.trim().toLowerCase();
|
||||
};
|
||||
|
||||
String path = "/images/card-" + suit + "-" + value + "-3.png";
|
||||
@@ -1026,5 +1046,20 @@ public class CasinoGameController {
|
||||
|
||||
public void setMyPlayerId(PlayerId id) {
|
||||
this.myPlayerId = id;
|
||||
TaskbarController controller = resolveTaskbarController();
|
||||
if (controller != null && gameService != null && myPlayerId != null) {
|
||||
controller.setGameService(gameService, myPlayerId);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskbarController resolveTaskbarController() {
|
||||
if (taskbarController != null) {
|
||||
return taskbarController;
|
||||
}
|
||||
if (taskbarIncludeController != null) {
|
||||
taskbarController = taskbarIncludeController;
|
||||
return taskbarController;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class ServerApp {
|
||||
private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10;
|
||||
private static final int SESSION_DISCONNECT_JOB_DELAY = 0;
|
||||
private static final int SESSION_DISCONNECT_JOB_PERIOD = 2;
|
||||
private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 30000;
|
||||
private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 30;
|
||||
private static final int LOBBY_EXPIRY_SECONDS = 30;
|
||||
private static final int LOBBY_CLEANUP_INITIAL_DELAY_SECONDS = 5;
|
||||
private static final int LOBBY_CLEANUP_PERIOD_SECONDS = 5;
|
||||
|
||||
+15
-2
@@ -28,9 +28,9 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
|
||||
@Override
|
||||
public void execute(GetGameStateRequest request) {
|
||||
Integer gameId = request.getGameId();
|
||||
String username = request.getUsername();
|
||||
String username = resolveUsername(request);
|
||||
|
||||
Lobby lobby = null;
|
||||
Lobby lobby;
|
||||
if (gameId != null) {
|
||||
lobby = lobbyManager.getLobby(LobbyId.of(gameId));
|
||||
if (lobby == null) {
|
||||
@@ -71,4 +71,17 @@ public class GetGameStateHandler extends CommandHandler<GetGameStateRequest> {
|
||||
|
||||
responseDispatcher.dispatch(new GetGameStateResponse(request.getContext(), game, username));
|
||||
}
|
||||
|
||||
private String resolveUsername(GetGameStateRequest request) {
|
||||
String username = request.getUsername();
|
||||
if (username != null && !username.isBlank()) {
|
||||
return username.trim();
|
||||
}
|
||||
|
||||
return userRegistry.getBySessionId(request.getSessionId())
|
||||
.map(User::getName)
|
||||
.map(String::trim)
|
||||
.filter(name -> !name.isEmpty())
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
+83
-39
@@ -12,25 +12,58 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessR
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Response carrying a snapshot of the current game state using the project's wire format. */
|
||||
/**
|
||||
* Response carrying a snapshot of the current game state using the project's wire format.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 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(game.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) {
|
||||
int globalCurrentBet = computeGlobalCurrentBet(state);
|
||||
Objects.requireNonNull(state, "state must not be null");
|
||||
|
||||
ResponseBodyBuilder builder = ResponseBody.builder();
|
||||
|
||||
builder.param("PHASE", state.getPhase() == null ? "UNKNOWN" : state.getPhase().name());
|
||||
builder.param("POT", state.getPot().getAmount());
|
||||
builder.param("CURRENT_BET", globalCurrentBet);
|
||||
builder.param("POT", state.getPot() == null ? 0 : state.getPot().getAmount());
|
||||
builder.param("CURRENT_BET", computeGlobalCurrentBet(state));
|
||||
builder.param("DEALER", state.getDealerIndex());
|
||||
builder.param("ACTIVE_PLAYER", state.getCurrentPlayerIndex());
|
||||
|
||||
appendCommunityCards(builder, state);
|
||||
|
||||
appendPlayers(builder, state, requestingUsername);
|
||||
|
||||
return builder.build();
|
||||
@@ -39,53 +72,63 @@ public class GetGameStateResponse extends SuccessResponse {
|
||||
private static int computeGlobalCurrentBet(GameState state) {
|
||||
int globalCurrentBet = 0;
|
||||
for (Player p : state.getPlayers()) {
|
||||
if (p == null) continue;
|
||||
int b = state.getCurrentBet(p.getId());
|
||||
if (b > globalCurrentBet) {
|
||||
globalCurrentBet = b;
|
||||
}
|
||||
if (b > globalCurrentBet) globalCurrentBet = b;
|
||||
}
|
||||
return globalCurrentBet;
|
||||
}
|
||||
|
||||
private static void appendCommunityCards(ResponseBodyBuilder builder, GameState state) {
|
||||
for (Card c : state.getCommunityCards()) {
|
||||
builder.block(
|
||||
"CARD",
|
||||
card -> {
|
||||
card.param("VALUE", rankToWire(c.getRank()));
|
||||
card.param("SUIT", suitToWire(c.getSuit()));
|
||||
});
|
||||
List<Card> community = state.getCommunityCards();
|
||||
|
||||
if (community == null || community.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Card c : community) {
|
||||
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) {
|
||||
for (Player p : state.getPlayers()) {
|
||||
PlayerId pid = p.getId();
|
||||
builder.block(
|
||||
"PLAYER",
|
||||
pblock -> {
|
||||
pblock.param("NAME", pid.value());
|
||||
pblock.param("CHIPS", p.getChips());
|
||||
pblock.param("BET", state.getCurrentBet(pid));
|
||||
pblock.param("STATE", p.isFolded() ? "FOLDED" : "ACTIVE");
|
||||
private static void appendPlayers(ResponseBodyBuilder builder, GameState state, String requestingUsername) {
|
||||
String req = (requestingUsername == null) ? null : requestingUsername.trim();
|
||||
|
||||
if (requestingUsername != null && requestingUsername.equals(pid.value())) {
|
||||
List<Card> hole = state.getHoleCards(pid);
|
||||
for (Card hc : hole) {
|
||||
pblock.block(
|
||||
"CARD",
|
||||
cblock -> {
|
||||
cblock.param("VALUE", rankToWire(hc.getRank()));
|
||||
cblock.param("SUIT", suitToWire(hc.getSuit()));
|
||||
});
|
||||
}
|
||||
for (Player p : state.getPlayers()) {
|
||||
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");
|
||||
|
||||
if (req != null && pid != null && req.equals(name)) {
|
||||
List<Card> 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()));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static String rankToWire(Rank r) {
|
||||
if (r == null) return "";
|
||||
return switch (r) {
|
||||
case TWO -> "2";
|
||||
case THREE -> "3";
|
||||
@@ -104,6 +147,7 @@ public class GetGameStateResponse extends SuccessResponse {
|
||||
}
|
||||
|
||||
private static String suitToWire(Suit s) {
|
||||
if (s == null) return "";
|
||||
return switch (s) {
|
||||
case HEARTS -> "H";
|
||||
case DIAMONDS -> "D";
|
||||
|
||||
+2
-3
@@ -29,7 +29,6 @@ public class GameController {
|
||||
|
||||
private int dealerIndex = 0;
|
||||
|
||||
private static final int NEXT_PLAYER_OFFSET = 3;
|
||||
private static final int DEALER_OFFSET = 1;
|
||||
private static final int SMALL_BLIND_OFFSET = 1;
|
||||
private static final int BIG_BLIND_OFFSET = 2;
|
||||
@@ -81,13 +80,13 @@ public class GameController {
|
||||
dealHoleCards();
|
||||
postBlinds();
|
||||
|
||||
int nextPlayerIndex = (dealerIndex + NEXT_PLAYER_OFFSET) % players.size();
|
||||
engine.getState().setCurrentPlayerIndex(nextPlayerIndex);
|
||||
engine.getState().setCurrentPlayerToPreflopFirstToAct();
|
||||
}
|
||||
|
||||
/** Rotates the dealer position to the next player in the list. */
|
||||
private void rotateDealer() {
|
||||
dealerIndex = (dealerIndex + DEALER_OFFSET) % players.size();
|
||||
engine.getState().setDealerIndex(dealerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+122
-66
@@ -1,133 +1,189 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Card;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Deck;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerStatus;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GamePhase;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* RoundManager is responsible for managing the flow of a poker game round. It handles the
|
||||
* progression of the game through its various phases (pre-flop, flop, turn, river) and manages
|
||||
* player actions such as posting blinds and dealing cards. The RoundManager ensures that the game
|
||||
* state is updated correctly based on player actions and the current phase of the game.
|
||||
* RoundManager manages hand lifecycle and phase progression (PREFLOP -> FLOP -> TURN -> RIVER -> SHOWDOWN).
|
||||
*
|
||||
* <p>This implementation:
|
||||
* <ul>
|
||||
* <li>starts a new hand (reset + hole cards + blinds)</li>
|
||||
* <li>progresses phases once betting round is finished</li>
|
||||
* <li>deals community cards (3/1/1)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class RoundManager {
|
||||
|
||||
public static final int SMALL_BLIND = 100;
|
||||
public static final int BIG_BLIND = 200;
|
||||
|
||||
/**
|
||||
* Starts a new hand by initializing the game state, dealing cards to players, and posting
|
||||
* blinds. This method sets the hand as active and resets any necessary state variables to
|
||||
* prepare for a new round of play.
|
||||
*
|
||||
* @param state The game state to be used for starting the new hand.
|
||||
*/
|
||||
public void startNewHand(GameState state) {
|
||||
state.setHandActive(true);
|
||||
state.resetBets();
|
||||
// Use GameState's canonical reset
|
||||
state.startNewHand();
|
||||
|
||||
dealCards(state);
|
||||
// Ensure deck exists
|
||||
ensureDeck(state);
|
||||
|
||||
// Deal hole cards
|
||||
dealHoleCards(state);
|
||||
|
||||
// Post blinds
|
||||
postBlinds(state);
|
||||
|
||||
// Preflop always starts left of BB (or dealer in heads-up).
|
||||
state.setCurrentPlayerToPreflopFirstToAct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the betting round is finished and advances the game phase if necessary. This method
|
||||
* evaluates the current bets of all active players and determines if the betting round can be
|
||||
* concluded. If all players have met the current bet or are all-in, the game phase is advanced
|
||||
* to the next stage.
|
||||
*
|
||||
* @param state The current game state to be evaluated for betting round progression.
|
||||
*/
|
||||
public void progressIfNeeded(GameState state) {
|
||||
if (state.getPhase() == null) {
|
||||
state.setPhase(GamePhase.PREFLOP);
|
||||
}
|
||||
|
||||
if (isBettingRoundFinished(state)) {
|
||||
advancePhase(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the betting round is finished by checking if all active players have met the
|
||||
* current bet or are all-in. This method iterates through all players in the game state and
|
||||
* evaluates their bets against the current bet on the table.
|
||||
*
|
||||
* @param state The current game state to be evaluated for betting round completion.
|
||||
* @return true if the betting round is finished, false otherwise.
|
||||
*/
|
||||
private boolean isBettingRoundFinished(GameState state) {
|
||||
|
||||
int target = state.getTableState().getCurrentBet();
|
||||
|
||||
for (Player p : state.getPlayers()) {
|
||||
if (p == null) continue;
|
||||
|
||||
if (p.getStatus() == PlayerStatus.FOLDED) {
|
||||
continue;
|
||||
}
|
||||
// be tolerant if codebase mixes status + boolean flags
|
||||
if (p.getStatus() == PlayerStatus.FOLDED || p.isFolded()) continue;
|
||||
|
||||
int bet = state.getCurrentBet(p.getId());
|
||||
|
||||
// The player must have placed at least one bet
|
||||
if (bet < target && !p.isAllIn()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the game phase to the next stage (flop, turn, river, or showdown) based on the
|
||||
* current phase of the game. This method is called when the betting round is finished and
|
||||
* updates the game state accordingly to reflect the new phase of play.
|
||||
*
|
||||
* @param state The current game state to be updated with the new phase.
|
||||
*/
|
||||
private void advancePhase(GameState state) {
|
||||
|
||||
switch (state.getPhase()) {
|
||||
case PREFLOP -> dealFlop(state);
|
||||
case FLOP -> dealTurn(state);
|
||||
case TURN -> dealRiver(state);
|
||||
case RIVER -> showdown(state);
|
||||
case SHOWDOWN -> { /* nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureDeck(GameState state) {
|
||||
Deck deck = state.getDeck();
|
||||
if (deck == null) {
|
||||
deck = new Deck();
|
||||
deck.shuffle();
|
||||
state.setDeck(deck);
|
||||
}
|
||||
}
|
||||
|
||||
private void dealHoleCards(GameState state) {
|
||||
Deck deck = state.getDeck();
|
||||
|
||||
for (Player p : state.getPlayers()) {
|
||||
if (p == null) continue;
|
||||
|
||||
PlayerId pid = p.getId();
|
||||
Card c1 = deck.draw();
|
||||
Card c2 = deck.draw();
|
||||
|
||||
state.giveHoleCards(pid, c1, c2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the posting of blinds at the start of a new hand. This method identifies the players
|
||||
* responsible for posting the small and big blinds, updates their chip counts, adds the blind
|
||||
* amounts to the pot, and updates the current bets for those players in the game state.
|
||||
*
|
||||
* @param state The current game state to be updated with the posted blinds.
|
||||
* 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<Player> playerList = new ArrayList<>(state.getPlayers());
|
||||
if (playerList.size() < 2) return;
|
||||
|
||||
Player sb = playerList.get(0);
|
||||
Player bb = playerList.get(1);
|
||||
// pick first two non-folded players as SB/BB
|
||||
Player sb = null;
|
||||
Player bb = null;
|
||||
|
||||
int smallBlind = SMALL_BLIND;
|
||||
int bigBlind = BIG_BLIND;
|
||||
for (Player p : playerList) {
|
||||
if (p == null) continue;
|
||||
if (p.isFolded()) continue;
|
||||
|
||||
sb.removeChips(smallBlind);
|
||||
bb.removeChips(bigBlind);
|
||||
if (sb == null) {
|
||||
sb = p;
|
||||
} else {
|
||||
bb = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
state.addToPot(smallBlind + bigBlind);
|
||||
if (sb == null || bb == null) return;
|
||||
|
||||
state.setCurrentBet(sb.getId(), smallBlind);
|
||||
state.setCurrentBet(bb.getId(), bigBlind);
|
||||
sb.removeChips(SMALL_BLIND);
|
||||
bb.removeChips(BIG_BLIND);
|
||||
|
||||
state.getTableState().setCurrentBet(bigBlind);
|
||||
state.addToPot(SMALL_BLIND + BIG_BLIND);
|
||||
|
||||
state.setCurrentBet(sb.getId(), SMALL_BLIND);
|
||||
state.setCurrentBet(bb.getId(), BIG_BLIND);
|
||||
|
||||
state.getTableState().setCurrentBet(BIG_BLIND);
|
||||
}
|
||||
|
||||
private void dealCards(GameState state) {}
|
||||
private void dealFlop(GameState state) {
|
||||
ensureDeck(state);
|
||||
Deck deck = state.getDeck();
|
||||
|
||||
private void dealFlop(GameState state) {}
|
||||
state.addCommunityCard(deck.draw());
|
||||
state.addCommunityCard(deck.draw());
|
||||
state.addCommunityCard(deck.draw());
|
||||
|
||||
private void dealTurn(GameState state) {}
|
||||
state.setPhase(GamePhase.FLOP);
|
||||
|
||||
private void dealRiver(GameState state) {}
|
||||
// new betting round
|
||||
state.resetBets();
|
||||
state.setCurrentPlayerToPostflopFirstToAct();
|
||||
}
|
||||
|
||||
private void showdown(GameState state) {}
|
||||
}
|
||||
private void dealTurn(GameState state) {
|
||||
ensureDeck(state);
|
||||
Deck deck = state.getDeck();
|
||||
|
||||
state.addCommunityCard(deck.draw());
|
||||
|
||||
state.setPhase(GamePhase.TURN);
|
||||
|
||||
// new betting round
|
||||
state.resetBets();
|
||||
state.setCurrentPlayerToPostflopFirstToAct();
|
||||
}
|
||||
|
||||
private void dealRiver(GameState state) {
|
||||
ensureDeck(state);
|
||||
Deck deck = state.getDeck();
|
||||
|
||||
state.addCommunityCard(deck.draw());
|
||||
|
||||
state.setPhase(GamePhase.RIVER);
|
||||
|
||||
// new betting round
|
||||
state.resetBets();
|
||||
state.setCurrentPlayerToPostflopFirstToAct();
|
||||
}
|
||||
|
||||
private void showdown(GameState state) {
|
||||
state.setPhase(GamePhase.SHOWDOWN);
|
||||
|
||||
// winner evaluation is elsewhere (rules/controller)
|
||||
// do NOT reset cards here; clients still need board visible during showdown
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -18,9 +18,6 @@ public class TurnManager {
|
||||
* @param state The current game state that will be updated to reflect the next player's turn.
|
||||
*/
|
||||
public void nextPlayer(GameState state) {
|
||||
|
||||
int next = (state.getCurrentPlayerIndex() + 1) % state.getPlayers().size();
|
||||
|
||||
state.setCurrentPlayerIndex(next);
|
||||
state.nextPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -6,8 +6,6 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.Rule;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleViolationException;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The ActionOrderRule class implements the Rule interface and defines the validation logic for
|
||||
@@ -31,8 +29,7 @@ public class ActionOrderRule implements Rule {
|
||||
|
||||
PlayerId actingPlayerId = action.getPlayerId();
|
||||
|
||||
List<Player> playerList = new ArrayList<>(state.getPlayers());
|
||||
Player current = playerList.get(state.getCurrentPlayerIndex());
|
||||
Player current = state.getCurrentPlayer();
|
||||
|
||||
if (!current.getId().equals(actingPlayerId)) {
|
||||
throw new RuleViolationException("Not your turn");
|
||||
|
||||
+108
-273
@@ -11,225 +11,154 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The GameState class encapsulates the entire state of a poker game at any given moment. It
|
||||
* maintains information about the players, the pot, the current phase of the game, the dealer
|
||||
* position, and the cards in play. This class is central to managing the flow of the game and
|
||||
* ensuring that all actions and decisions are based on an accurate representation of the current
|
||||
* game state.
|
||||
* The GameState class encapsulates the entire state of a poker game at any given moment.
|
||||
*
|
||||
* <p>Important invariants this implementation maintains:
|
||||
* <ul>
|
||||
* <li>{@code playerOrder} defines the stable seating/turn order.</li>
|
||||
* <li>{@code currentBets} always contains an entry for every player in {@code playerOrder}.</li>
|
||||
* <li>{@code holeCards} maps every player to a mutable list; if not present, it is created on demand.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class GameState {
|
||||
|
||||
private Map<PlayerId, Player> players = new HashMap<>();
|
||||
|
||||
private List<PlayerId> playerOrder = new ArrayList<>();
|
||||
private final Map<PlayerId, Player> players = new HashMap<>();
|
||||
private final List<PlayerId> playerOrder = new ArrayList<>();
|
||||
|
||||
private Pot pot = new Pot();
|
||||
|
||||
private TableState tableState = new TableState();
|
||||
private final TableState tableState = new TableState();
|
||||
|
||||
private int currentPlayerIndex;
|
||||
|
||||
private boolean handActive;
|
||||
|
||||
private boolean allowOutOfTurn;
|
||||
|
||||
private GamePhase phase;
|
||||
|
||||
private int dealerIndex;
|
||||
|
||||
private Map<PlayerId, Integer> currentBets = new HashMap<>();
|
||||
private final Map<PlayerId, Integer> currentBets = new HashMap<>();
|
||||
private final Map<PlayerId, Integer> playerBetCommitments = new HashMap<>();
|
||||
|
||||
private Map<PlayerId, Integer> playerBetCommitments = new HashMap<>();
|
||||
|
||||
private Map<PlayerId, List<Card>> holeCards = new HashMap<>();
|
||||
|
||||
private List<Card> communityCards = new ArrayList<>();
|
||||
|
||||
// private final Set<PlayerId> foldedPlayers = new HashSet<>();
|
||||
private final Map<PlayerId, List<Card>> holeCards = new HashMap<>();
|
||||
private final List<Card> communityCards = new ArrayList<>();
|
||||
|
||||
private Deck deck;
|
||||
|
||||
// Getter
|
||||
|
||||
/**
|
||||
* Returns the list of players currently in the game.
|
||||
*
|
||||
* @return A list of Player objects representing the players in the game.
|
||||
*/
|
||||
// Getters
|
||||
public Collection<Player> getPlayers() {
|
||||
return players.values();
|
||||
List<Player> ordered = new ArrayList<>(playerOrder.size());
|
||||
for (PlayerId id : playerOrder) {
|
||||
Player p = players.get(id);
|
||||
if (p != null) {
|
||||
ordered.add(p);
|
||||
}
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current player whose turn it is to act.
|
||||
*
|
||||
* @return The Player object representing the current player.
|
||||
*/
|
||||
public Player getCurrentPlayer() {
|
||||
PlayerId id = playerOrder.get(currentPlayerIndex);
|
||||
return players.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the current player in the players list.
|
||||
*
|
||||
* @return An integer representing the index of the current player.
|
||||
*/
|
||||
public int getCurrentPlayerIndex() {
|
||||
return currentPlayerIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a hand is currently active in the game.
|
||||
*
|
||||
* @return true if a hand is active, false otherwise.
|
||||
*/
|
||||
public boolean isHandActive() {
|
||||
return handActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current phase of the game (e.g., PREFLOP, FLOP, TURN, RIVER).
|
||||
*
|
||||
* @return The GamePhase enum value representing the current phase of the game.
|
||||
*/
|
||||
public GamePhase getPhase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the table, including player statuses and positions.
|
||||
*
|
||||
* @return A TableState object representing the current state of the table.
|
||||
*/
|
||||
public TableState getTableState() {
|
||||
return tableState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current pot, which contains the total amount of chips bet by players in the
|
||||
* current hand.
|
||||
*
|
||||
* @return A Pot object representing the current pot.
|
||||
*/
|
||||
public Pot getPot() {
|
||||
return pot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current deck of cards being used in the game.
|
||||
*
|
||||
* @return A Deck object representing the current deck of cards.
|
||||
*/
|
||||
public Deck getDeck() {
|
||||
return deck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of community cards currently on the table.
|
||||
*
|
||||
* @return A list of Card objects representing the community cards.
|
||||
*/
|
||||
public List<Card> getCommunityCards() {
|
||||
return communityCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hole cards for a specific player based on their ID.
|
||||
*
|
||||
* @param playerId The ID of the player whose hole cards are being requested.
|
||||
* @return A list of Card objects representing the player's hole cards, or an empty list if the
|
||||
* player has no hole cards.
|
||||
*/
|
||||
/** Always returns a mutable list (never null). */
|
||||
public List<Card> getHoleCards(PlayerId playerId) {
|
||||
return holeCards.getOrDefault(playerId, new ArrayList<>());
|
||||
return holeCards.computeIfAbsent(playerId, k -> new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the dealer in the players list.
|
||||
*
|
||||
* @return An integer representing the index of the dealer.
|
||||
*/
|
||||
public int getDealerIndex() {
|
||||
return dealerIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of player IDs to their current hole cards.
|
||||
*
|
||||
* @return A map where the key is the player ID and the value is a list of Card objects
|
||||
* representing the player's hole cards.
|
||||
*/
|
||||
public Map<PlayerId, List<Card>> getPlayerCards() {
|
||||
return holeCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of players currently in the game.
|
||||
*
|
||||
* @return An integer representing the number of players in the game.
|
||||
*/
|
||||
public int getPlayerCount() {
|
||||
return players.size();
|
||||
}
|
||||
|
||||
// SETTER
|
||||
|
||||
/**
|
||||
* Sets the index of the current player in the players list.
|
||||
*
|
||||
* @param index An integer representing the index of the current player.
|
||||
*/
|
||||
// Setters / Mutators
|
||||
public void setCurrentPlayerIndex(int index) {
|
||||
if (playerOrder.isEmpty()) {
|
||||
this.currentPlayerIndex = 0;
|
||||
return;
|
||||
}
|
||||
this.currentPlayerIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a hand is currently active in the game.
|
||||
*
|
||||
* @param handActive A boolean value indicating whether a hand is active.
|
||||
*/
|
||||
public void setCurrentPlayerToPreflopFirstToAct() {
|
||||
int size = playerOrder.size();
|
||||
if (size == 0) {
|
||||
currentPlayerIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Heads-up: dealer (small blind) acts first preflop.
|
||||
int first = (size == 2) ? dealerIndex : (dealerIndex + 3) % size;
|
||||
currentPlayerIndex = first;
|
||||
if (!canPlayerAct(currentPlayerIndex)) {
|
||||
nextPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentPlayerToPostflopFirstToAct() {
|
||||
int size = playerOrder.size();
|
||||
if (size == 0) {
|
||||
currentPlayerIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int first = (dealerIndex + 1) % size;
|
||||
currentPlayerIndex = first;
|
||||
if (!canPlayerAct(currentPlayerIndex)) {
|
||||
nextPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
public void setHandActive(boolean handActive) {
|
||||
this.handActive = handActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current phase of the game.
|
||||
*
|
||||
* @param phase The GamePhase enum value representing the new phase of the game.
|
||||
*/
|
||||
public void setPhase(GamePhase phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the dealer in the players list.
|
||||
*
|
||||
* @param dealerIndex An integer representing the index of the dealer.
|
||||
*/
|
||||
public void setDealerIndex(int dealerIndex) {
|
||||
this.dealerIndex = dealerIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current deck of cards being used in the game.
|
||||
*
|
||||
* @param deck A Deck object representing the new deck of cards to be used in the game.
|
||||
*/
|
||||
public void setDeck(Deck deck) {
|
||||
this.deck = deck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a player to the game with the specified ID and initial chip count. This method creates a
|
||||
* new Player object, adds it to the list of players, and initializes the player's current bet
|
||||
* and bet commitment in the game state.
|
||||
*
|
||||
* @param id The ID of the player to add.
|
||||
* @param chips The initial number of chips the player has.
|
||||
*/
|
||||
public void addPlayer(PlayerId id, int chips) {
|
||||
|
||||
Player player = new Player(id, chips);
|
||||
|
||||
players.put(id, player);
|
||||
@@ -237,213 +166,130 @@ public class GameState {
|
||||
|
||||
currentBets.put(id, 0);
|
||||
playerBetCommitments.put(id, 0);
|
||||
|
||||
holeCards.computeIfAbsent(id, k -> new ArrayList<>());
|
||||
}
|
||||
|
||||
// BETTING LOGIC
|
||||
|
||||
/**
|
||||
* Retrieves the current bet amount for a specific player based on their ID.
|
||||
*
|
||||
* @param playerId The ID of the player whose current bet is being requested.
|
||||
* @return An integer representing the current bet amount for the specified player, or 0 if the
|
||||
* player has not placed any bets.
|
||||
*/
|
||||
// Betting
|
||||
public int getCurrentBet(PlayerId playerId) {
|
||||
return currentBets.getOrDefault(playerId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current bet amount for a specific player based on their ID. This method updates the
|
||||
* currentBets map with the new bet amount for the specified player.
|
||||
*
|
||||
* @param playerId The ID of the player whose current bet is being set.
|
||||
* @param amount The new bet amount to be set for the specified player.
|
||||
*/
|
||||
public void setCurrentBet(PlayerId playerId, int amount) {
|
||||
currentBets.put(playerId, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current bets for all players by clearing the currentBets map. This method is
|
||||
* typically called at the start of a new hand to ensure that all players' bets are reset to
|
||||
* zero.
|
||||
* Reset bets to 0 for ALL players (do not clear the map).
|
||||
* Also resets table current bet to 0.
|
||||
*/
|
||||
public void resetBets() {
|
||||
currentBets.clear();
|
||||
for (PlayerId id : playerOrder) {
|
||||
currentBets.put(id, 0);
|
||||
}
|
||||
tableState.setCurrentBet(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a specified amount to the pot. This method updates the total amount in the pot by adding
|
||||
* the given amount to it.
|
||||
*
|
||||
* @param amount The amount of chips to be added to the pot.
|
||||
*/
|
||||
public void addToPot(int amount) {
|
||||
pot.add(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether out-of-turn actions are allowed in the game. Out-of-turn actions refer to
|
||||
* players being able to act when it is not their turn, which can be a feature in some poker
|
||||
* variants or game modes.
|
||||
*
|
||||
* @return true if out-of-turn actions are allowed, false otherwise.
|
||||
*/
|
||||
public boolean isAllowOutOfTurn() {
|
||||
return allowOutOfTurn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether out-of-turn actions are allowed in the game. This method updates the
|
||||
* allowOutOfTurn flag, which determines if players can act when it is not their turn.
|
||||
*
|
||||
* @param allowOutOfTurn A boolean value indicating whether out-of-turn actions should be
|
||||
* allowed in the game.
|
||||
*/
|
||||
public void setAllowOutOfTurn(boolean allowOutOfTurn) {
|
||||
this.allowOutOfTurn = allowOutOfTurn;
|
||||
}
|
||||
|
||||
// PLAYER HELPERS
|
||||
|
||||
/**
|
||||
* Retrieves a player from the game based on their ID. This method searches the list of players
|
||||
* for a player with the specified ID and returns it. If no player with the given ID is found, a
|
||||
* RuntimeException is thrown.
|
||||
*
|
||||
* @param id The ID of the player to retrieve.
|
||||
* @return The Player object corresponding to the specified ID.
|
||||
* @throws RuntimeException if no player with the given ID is found in the game.
|
||||
*/
|
||||
// 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;
|
||||
}
|
||||
|
||||
// BET COMMITMENTS
|
||||
|
||||
/**
|
||||
* Retrieves the current bet commitment for a specific player based on their ID. A bet
|
||||
* commitment represents the total amount a player has committed to the pot in the current hand,
|
||||
* including all bets, raises, and calls they have made.
|
||||
*
|
||||
* @param playerId The ID of the player whose current bet commitment is being requested.
|
||||
* @return An integer representing the current bet commitment for the specified player, or 0 if
|
||||
* the player has not made any bet commitments.
|
||||
*/
|
||||
// Bet commitments
|
||||
public int getCurrentBetCommitment(PlayerId playerId) {
|
||||
return playerBetCommitments.getOrDefault(playerId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current bet commitment for a specific player based on their ID. This method updates
|
||||
* the playerBetCommitments map with the new bet commitment amount for the specified player.
|
||||
*
|
||||
* @param playerId The ID of the player whose current bet commitment is being set.
|
||||
* @param amount The new bet commitment amount to be set for the specified player.
|
||||
*/
|
||||
public void setCurrentBetCommitment(PlayerId playerId, int amount) {
|
||||
playerBetCommitments.put(playerId, amount);
|
||||
}
|
||||
|
||||
// CARDS
|
||||
// Cards
|
||||
|
||||
/**
|
||||
* Gives hole cards to a specific player based on their ID. This method takes two Card objects
|
||||
* representing the player's hole cards and adds them to the holeCards map under the player's
|
||||
* ID.
|
||||
*
|
||||
* @param playerId The ID of the player to whom the hole cards are being given.
|
||||
* @param c1 The first Card object representing one of the player's hole cards.
|
||||
* @param c2 The second Card object representing the other hole card for the player.
|
||||
* 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<Card> cards = new ArrayList<>();
|
||||
List<Card> cards = holeCards.computeIfAbsent(playerId, k -> new ArrayList<>());
|
||||
cards.clear();
|
||||
cards.add(c1);
|
||||
cards.add(c2);
|
||||
|
||||
holeCards.put(playerId, cards);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a community card to the game state. This method takes a Card object representing a
|
||||
* community card and adds it to the list of community cards on the table.
|
||||
*
|
||||
* @param card The Card object representing the community card to be added to the game state.
|
||||
*/
|
||||
public void addCommunityCard(Card card) {
|
||||
communityCards.add(card);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the community cards by clearing the list of community cards. This method is typically
|
||||
* called at the start of a new hand to ensure that all community cards from the previous hand
|
||||
* are removed from the game state.
|
||||
*/
|
||||
public void resetCommunityCards() {
|
||||
communityCards.clear();
|
||||
}
|
||||
|
||||
// TURN MANAGEMENT
|
||||
|
||||
/**
|
||||
* Advances the turn to the next player in the players list. This method updates the
|
||||
* currentPlayerIndex by incrementing it and wrapping around to the start of the list if
|
||||
* necessary, ensuring that the turn order is maintained correctly throughout the game.
|
||||
*/
|
||||
// Turn / dealer management
|
||||
public void nextPlayer() {
|
||||
if (playerOrder.isEmpty()) {
|
||||
currentPlayerIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int start = currentPlayerIndex;
|
||||
|
||||
do {
|
||||
currentPlayerIndex = (currentPlayerIndex + 1) % playerOrder.size();
|
||||
|
||||
Player p = getCurrentPlayer();
|
||||
|
||||
if (!p.isFolded() && !p.isAllIn()) {
|
||||
if (canPlayerAct(currentPlayerIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
} while (currentPlayerIndex != start);
|
||||
}
|
||||
|
||||
// Dealer Rotation
|
||||
private boolean canPlayerAct(int index) {
|
||||
if (index < 0 || index >= playerOrder.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerId id = playerOrder.get(index);
|
||||
Player p = players.get(id);
|
||||
return p != null && !p.isFolded() && !p.isAllIn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the dealer position to the next player in the players list. This method updates the
|
||||
* dealerIndex by incrementing it and wrapping around to the start of the list if necessary,
|
||||
* ensuring that the dealer position rotates correctly after each hand.
|
||||
*/
|
||||
public void rotateDealer() {
|
||||
dealerIndex = (dealerIndex + 1) % playerOrder.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current dealer based on the dealerIndex. This method returns the Player object
|
||||
* corresponding to the current dealer position in the players list.
|
||||
*
|
||||
* @return The Player object representing the current dealer.
|
||||
*/
|
||||
public Player getDealer() {
|
||||
PlayerId id = playerOrder.get(dealerIndex);
|
||||
return players.get(id);
|
||||
}
|
||||
|
||||
// HAND RESET
|
||||
// Hand reset / lifecycle
|
||||
|
||||
/**
|
||||
* Starts a new hand by resetting the game state for the next round of poker. This method sets
|
||||
* the handActive flag to true, resets the game phase to PREFLOP, clears the pot, resets all
|
||||
* player bets and bet commitments, clears the community cards, and initializes a new shuffled
|
||||
* deck of cards for the new hand.
|
||||
* Starts a new hand by resetting the game state for the next round of poker.
|
||||
*
|
||||
* <p>This:
|
||||
* <ul>
|
||||
* <li>sets {@code phase=PREFLOP}</li>
|
||||
* <li>resets pot/bets/commitments</li>
|
||||
* <li>clears community + hole cards</li>
|
||||
* <li>creates & shuffles a new deck</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void startNewHand() {
|
||||
|
||||
handActive = true;
|
||||
phase = GamePhase.PREFLOP;
|
||||
|
||||
@@ -455,6 +301,10 @@ public class GameState {
|
||||
resetCommunityCards();
|
||||
holeCards.clear();
|
||||
|
||||
for (PlayerId id : playerOrder) {
|
||||
holeCards.put(id, new ArrayList<>());
|
||||
}
|
||||
|
||||
for (Player player : players.values()) {
|
||||
player.setFolded(false);
|
||||
}
|
||||
@@ -462,28 +312,13 @@ public class GameState {
|
||||
deck = new Deck();
|
||||
deck.shuffle();
|
||||
|
||||
currentPlayerIndex = (dealerIndex + 1) % playerOrder.size();
|
||||
setCurrentPlayerToPreflopFirstToAct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Folds a player in the current hand. This method adds the specified player's ID to the set of
|
||||
* folded players, indicating that the player has folded and is no longer active in the current
|
||||
* hand.
|
||||
*
|
||||
* @param playerId The ID of the player who is folding.
|
||||
*/
|
||||
public void foldPlayer(PlayerId playerId) {
|
||||
getPlayer(playerId).setFolded(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific player has folded in the current hand. This method checks if the
|
||||
* specified player's ID is present in the set of folded players, indicating that the player has
|
||||
* folded.
|
||||
*
|
||||
* @param playerId The ID of the player to check for folding status.
|
||||
* @return true if the player has folded, false otherwise.
|
||||
*/
|
||||
public boolean isFolded(PlayerId playerId) {
|
||||
return getPlayer(playerId).isFolded();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user