Feat/Game Engine #233

Merged
j.kropff merged 18 commits from feat/game-engine into main 2026-04-05 11:26:12 +02:00
4 changed files with 540 additions and 0 deletions
Showing only changes of commit fb71c6de7b - Show all commits
@@ -0,0 +1,257 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.BlindAction;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.CallAction;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.FoldAction;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.RaiseAction;
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.engine.GameEngine;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator.HandEvaluator;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator.HandRank;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
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;
import java.util.Map;
/**
* GameController is responsible for managing the flow of the poker game. It
* interacts with the GameEngine to process player actions and update the game
* state accordingly.
*/
public class GameController {
private final GameEngine engine;
private final List<PlayerId> players = new ArrayList<>();
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;
private static final int SMALL_BLIND = 100;
private static final int BIG_BLIND = 200;
/**
* Initializes the GameController with a reference to the GameEngine.
*
* @param engine The GameEngine instance that manages the game state and logic.
*/
public GameController(GameEngine engine) {
this.engine = engine;
}
/**
* Adds a player to the game with the specified name and initial chip count.
*
* @param name The name of the player to add.
* @param chips The initial number of chips the player has.
*/
public void addPlayer(PlayerId name, int chips) {
// PlayerId id = PlayerId.of(name);
//
// players.add(id);
// engine.getState().addPlayer(id, chips);
players.add(name);
engine.getState().addPlayer(name, chips);
}
/**
* Initializes a new hand by preparing the deck, setting the phase to PREFLOP,
* rotating the dealer, dealing hole cards, posting blinds,
* and setting the first active player.
*/
public void startGame() {
if (engine.getState().getDeck() == null) {
Deck deck = new Deck();
deck.shuffle();
engine.getState().setDeck(deck);
}
engine.getState().setHandActive(true);
engine.getState().setPhase(GamePhase.PREFLOP);
rotateDealer();
dealHoleCards();
postBlinds();
int nextPlayerIndex = (dealerIndex + NEXT_PLAYER_OFFSET) % players.size();
engine.getState().setCurrentPlayerIndex(nextPlayerIndex);
}
/**
* Rotates the dealer position to the next player in the list.
*/
private void rotateDealer() {
dealerIndex = (dealerIndex + DEALER_OFFSET) % players.size();
}
/**
* Returns the player currently acting as dealer.
*
* @return The name of the current dealer.
*/
public PlayerId getDealer() {
return players.get(dealerIndex);
}
/**
* Determines the small and big blind players relative to the dealer
* and submits the corresponding blind actions to the engine.
*/
public void postBlinds() {
PlayerId smallBlind = players.get((dealerIndex + SMALL_BLIND_OFFSET) % players.size());
PlayerId bigBlind = players.get((dealerIndex + BIG_BLIND_OFFSET) % players.size());
engine.processAction(new BlindAction(smallBlind, SMALL_BLIND));
engine.processAction(new BlindAction(bigBlind, BIG_BLIND));
}
/**
* Deals hole cards to each player from the deck.
*/
public void dealHoleCards() {
Deck deck = engine.getState().getDeck();
for (PlayerId player : players) {
Card c1 = deck.draw();
Card c2 = deck.draw();
engine.getState().giveHoleCards(player, c1, c2);
}
}
/**
* Draws three cards from the deck and adds them as community cards (flop).
*/
public void dealFlop() {
Deck deck = engine.getState().getDeck();
engine.getState().addCommunityCard(deck.draw());
engine.getState().addCommunityCard(deck.draw());
engine.getState().addCommunityCard(deck.draw());
}
/**
* Deals the turn by drawing one community card from the deck and adding it to
* the game state.
*/
public void dealTurn() {
engine.getState().addCommunityCard(engine.getState().getDeck().draw());
}
/**
* Deals the river by drawing one community card from the deck and adding it to
* the game state.
*/
public void dealRiver() {
engine.getState().addCommunityCard(engine.getState().getDeck().draw());
}
/**
* Processes a player's fold action by sending a FoldAction to the GameEngine.
*
* @param playerId The ID of the player who is folding.
*/
public void playerFold(PlayerId playerId) {
// engine.processAction(new FoldAction(PlayerId.of(playerId)));
engine.processAction(new FoldAction(playerId));
}
/**
* Processes a player's call action by sending a CallAction to the GameEngine.
*
* @param playerId The ID of the player who is calling.
*/
public void playerCall(PlayerId playerId) {
// engine.processAction(new CallAction(PlayerId.of(playerId)));
engine.processAction(new CallAction(playerId));
}
/**
* Processes a player's raise action by sending a RaiseAction to the GameEngine.
*
* @param playerId The ID of the player who is raising.
* @param amount The amount the player is raising.
*/
public void playerRaise(PlayerId playerId, int amount) {
// engine.processAction(new RaiseAction(PlayerId.of(playerId), amount));
engine.processAction(new RaiseAction(playerId, amount));
}
/**
* Retrieves the current game state from the GameEngine.
*
* @return The current GameState object representing the state of the game.
*/
public GameState getState() {
return engine.getState();
}
/**
* Retrieves the list of community cards currently on the table.
*
* @return A list of Card objects representing the community cards.
*/
public List<Card> getCommunityCards() {
return engine.getState().getCommunityCards();
}
/**
* Retrieves the hole cards for each player in the game.
*
* @return A map where the key is the player's name and the value is a list of
* Card objects representing the player's hole cards.
*/
public Map<PlayerId, List<Card>> getPlayerCards() {
return engine.getState().getPlayerCards();
}
/**
* Determines the winner of the current hand by evaluating the best possible
* poker hand for each active player.
*
* The evaluation is based on the player's two hole cards combined with the
* five community cards on the board.
*
* @return The ID of the winning player.
*/
public PlayerId determineWinner() {
List<Card> board = engine.getState().getCommunityCards();
GameState state = engine.getState();
PlayerId bestPlayer = null;
HandRank bestRank = null;
for (PlayerId player : players) {
if (state.isFolded(player)) {
continue;
}
List<Card> cards = new ArrayList<>(board);
cards.addAll(state.getHoleCards(player));
HandRank rank = HandEvaluator.evaluate(cards);
if (bestRank == null || rank.compareTo(bestRank) > 0) {
bestRank = rank;
bestPlayer = player;
}
}
return bestPlayer;
}
}
@@ -0,0 +1,108 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
/**
* The GameEngine class is responsible for managing the core logic of the poker
* game. It integrates the RuleEngine, RoundManager, and TurnManager to
* orchestrate the flow of the game. The GameEngine processes player actions,
* updates the game state accordingly, and ensures that the game progresses
* through its various stages (pre-flop, flop, turn, river) while adhering to
* the rules of poker.
*/
public class GameEngine {
private final RuleEngine ruleEngine;
private final RoundManager roundManager;
private final TurnManager turnManager;
private final GameState state;
/**
* Constructs a GameEngine with the specified game state, rule engine, round
* manager, and turn manager.
*
* @param state The initial game state to be managed by the engine.
* @param ruleEngine The RuleEngine instance responsible for validating player
* actions.
* @param roundManager The RoundManager instance responsible for managing the
* progression of rounds.
* @param turnManager The TurnManager instance responsible for managing player
* turns.
*/
public GameEngine(GameState state,
RuleEngine ruleEngine,
RoundManager roundManager,
TurnManager turnManager) {
this.state = state;
this.ruleEngine = ruleEngine;
this.roundManager = roundManager;
this.turnManager = turnManager;
}
/**
* Starts a new hand with the provided game state. This method initializes the
* round manager to begin a new hand and sets up the game state accordingly.
*
* @param state The game state to be used for starting the new hand.
*/
public void startNewHand(GameState state) {
roundManager.startNewHand(state);
}
/**
* Starts a new hand using the current game state. This method is a convenience
* method that calls the startNewHand method with the current state of the game.
*/
public void startNewHand() {
roundManager.startNewHand(state);
}
/**
* Processes a player action by validating it against the game rules, executing
* the action, progressing to the next player's turn, and checking if the
* round should progress to the next stage.
*
* @param action The player action to be processed.
*/
public void processAction(Action action) {
handleAction(state, action);
}
/**
* Handles a player action by performing the following steps:
* 1. Validates the action against the game rules using the RuleEngine.
* 2. Executes the action, which updates the game state accordingly.
* 3. Advances to the next player's turn using the TurnManager.
* 4. Checks if the round should progress to the next stage (e.g., from pre-flop
* to flop) using the RoundManager.
*
* @param state The current game state on which to execute the action.
* @param action The player action to be processed.
*/
public void handleAction(GameState state, Action action) {
// 1.Check the rules
ruleEngine.validate(state, action);
// 2. Perform action
action.execute(state);
// 3. Next turn
turnManager.nextPlayer(state);
// 4. Check loop logic
roundManager.progressIfNeeded(state);
}
/**
* Retrieves the current game state managed by the GameEngine.
*
* @return The current GameState instance.
*/
public GameState getState() {
return state;
}
}
@@ -0,0 +1,144 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerStatus;
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.
*/
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();
dealCards(state);
postBlinds(state);
}
/**
* 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 (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.getStatus() == PlayerStatus.FOLDED) {
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);
}
}
/**
* 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.
*/
private void postBlinds(GameState state) {
List<Player> playerList = new ArrayList<>(state.getPlayers());
Player sb = playerList.get(0);
Player bb = playerList.get(1);
int smallBlind = SMALL_BLIND;
int bigBlind = BIG_BLIND;
sb.removeChips(smallBlind);
bb.removeChips(bigBlind);
state.addToPot(smallBlind + bigBlind);
state.setCurrentBet(sb.getId(), smallBlind);
state.setCurrentBet(bb.getId(), bigBlind);
state.getTableState().setCurrentBet(bigBlind);
}
private void dealCards(GameState state) {
}
private void dealFlop(GameState state) {
}
private void dealTurn(GameState state) {
}
private void dealRiver(GameState state) {
}
private void showdown(GameState state) {
}
}
@@ -0,0 +1,31 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
/**
* The TurnManager class is responsible for managing the flow of turns in a
* poker
* game. It provides functionality to determine the next player in the sequence
* and update the game state accordingly. The TurnManager ensures that players
* take their turns in the correct order, allowing for a smooth and organized
* gameplay experience.
*/
public class TurnManager {
/**
* Advances the game state to the next player's turn. This method calculates
* the index of the next player based on the current player index and the total
* number of players in the game. It then updates the game state to reflect
* the new current player.
*
* @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);
}
}