Feat/Game Engine #233
@@ -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;
|
||||
}
|
||||
}
|
||||
+108
@@ -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;
|
||||
}
|
||||
}
|
||||
+144
@@ -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) {
|
||||
}
|
||||
}
|
||||
+31
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user