Merge remote-tracking branch 'origin/main' into feat/gameui

This commit is contained in:
Julian Kropff
2026-04-05 13:36:22 +02:00
232 changed files with 6627 additions and 47 deletions
@@ -10,15 +10,15 @@ import org.apache.logging.log4j.Logger;
/**
* Entry point for the Casono client application. Handles client startup and connection parameters.
*
* <p>Standardkonstruktor für die Anwendung.
* <p>Default constructor for the application.
*/
public class ClientApp {
private static final Logger LOGGER = LogManager.getLogger(ClientApp.class);
/** Standardkonstruktor. */
/** Default constructor. */
public ClientApp() {
// Standardkonstruktor
// Default constructor
}
/**
@@ -6,13 +6,13 @@ import javafx.application.Application;
/**
* Launcher for the Casono main UI.
*
* <p>Standardkonstruktor für die Anwendung.
* <p>Default constructor for the application.
*/
public class Launcher {
/** Standardkonstruktor. */
/** Default constructor. */
public Launcher() {
// Standardkonstruktor
// Default constructor
}
/**
@@ -10,13 +10,13 @@ import javafx.stage.Stage;
/**
* JavaFX Application class for the Casono main UI.
*
* <p>Standardkonstruktor für die Anwendung.
* <p>Default constructor for the application.
*/
public class Casinomainui extends Application {
/** Standardkonstruktor. */
/** Default constructor. */
public Casinomainui() {
// Standardkonstruktor
// Default constructor
}
private static final int SCENE_WIDTH = 1200;
@@ -58,7 +58,7 @@ public class CasinomainuiController {
@FXML
public void handleCreateLobbyButton() {
if (translationManager.isFull()) {
LOGGER.warn("Grid voll! Keine weiteren Lobbys moeglich.");
LOGGER.warn("Grid is full! No more lobbies available.");
return;
}
int buttonId = nextButtonId++;
@@ -68,7 +68,7 @@ public class CasinomainuiController {
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
gridManager.renderLobbyButtons();
} catch (Exception e) {
LOGGER.error("Fehler beim Hinzufügen: {}", e.getMessage());
LOGGER.error("Error while adding lobby button: {}", e.getMessage());
}
}
}
@@ -4,19 +4,19 @@ import java.util.HashMap;
import java.util.Map;
/**
* Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. Keine Dateioperationen,
* nur Laufzeitdatenstruktur.
* Manages the mapping between Button IDs and Lobby IDs in memory only. No file operations,
* runtime-only data structure.
*/
public class LobbyButtonTranslationManager {
// Singleton-Instanz
// Singleton instance
private static LobbyButtonTranslationManager instance;
// Singleton-Zugriff
// Singleton access
/**
* Liefert die Singleton-Instanz des Managers.
* Returns the singleton instance of the manager.
*
* @return die einzige Instanz von {@code LobbyButtonTranslationManager}
* @return the single instance of {@code LobbyButtonTranslationManager}
*/
public static LobbyButtonTranslationManager getInstance() {
if (instance == null) {
@@ -25,32 +25,32 @@ public class LobbyButtonTranslationManager {
return instance;
}
/** Maximale Anzahl an Buttons/Lobbys */
/** Maximum number of buttons/lobbies */
private static final int MAX_BUTTONS = 8;
/** Zuordnung ButtonID → LobbyID */
/** Mapping ButtonID → LobbyID */
private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>();
/** Privater Konstruktor für Singleton-Pattern */
/** Private constructor for the singleton pattern */
private LobbyButtonTranslationManager() {
// Zuordnung bleibt leer beim Start
// Mapping is empty at startup
}
/**
* Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht).
* Checks whether the grid is full (MAX_BUTTONS reached).
*
* @return true, wenn Grid voll; sonst false
* @return true if the grid is full; otherwise false
*/
public boolean isFull() {
return buttonIdToLobbyId.size() >= MAX_BUTTONS;
}
/**
* Fügt eine Zuordnung ButtonID → LobbyID hinzu.
* Adds a mapping ButtonID → LobbyID.
*
* @param buttonId Die ID des Buttons
* @param lobbyId Die ID der Lobby
* @throws Exception wenn das Grid voll ist
* @param buttonId the ID of the button
* @param lobbyId the ID of the lobby
* @throws Exception when the grid is full
*/
public void addLobbyButton(int buttonId, int lobbyId) throws Exception {
if (isFull()) {
@@ -60,28 +60,28 @@ public class LobbyButtonTranslationManager {
}
/**
* Entfernt eine Zuordnung für die gegebene ButtonID.
* Removes the mapping for the given ButtonID.
*
* @param buttonId Die ID des zu entfernenden Buttons
* @param buttonId the ID of the button to remove
*/
public void removeLobbyButton(int buttonId) {
buttonIdToLobbyId.remove(buttonId);
}
/**
* Gibt die LobbyID für eine gegebene ButtonID zurück.
* Returns the LobbyID for a given ButtonID.
*
* @param buttonId Die ButtonID
* @return Die zugehoerige LobbyID oder null, falls nicht vorhanden
* @param buttonId the ButtonID
* @return the associated LobbyID or null if not present
*/
public Integer getLobbyIdForButton(int buttonId) {
return buttonIdToLobbyId.get(buttonId);
}
/**
* Gibt die gesamte Zuordnung ButtonID → LobbyID zurück.
* Returns the full mapping ButtonID → LobbyID.
*
* @return Map aller Zuordnungen
* @return Map of all mappings
*/
public Map<Integer, Integer> getButtonIdToLobbyId() {
return buttonIdToLobbyId;
@@ -1,5 +1,11 @@
package ch.unibas.dmi.dbis.cs108.casono.server;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameParser;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingParser;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
@@ -7,6 +13,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandR
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionDisconnectJob;
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
import java.time.Duration;
@@ -58,6 +65,31 @@ public class ServerApp {
SESSION_DISCONNECT_JOB_PERIOD,
TimeUnit.SECONDS);
ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager);
registerCommands(dispatcher, router, responseDispatcher, userRegistry);
networkManager.start();
}
/**
* Registers command parsers and handlers.
*
* @param parserDispatcher the dispatcher responsible for parsing incoming commands
* @param commandRouter the router that dispatches parsed commands to appropriate handlers
* @param responseDispatcher the dispatcher responsible for sending responses back to clients
*/
private static void registerCommands(
CommandParserDispatcher parserDispatcher,
CommandRouter commandRouter,
ResponseDispatcher responseDispatcher,
UserRegistry userRegistry) {
parserDispatcher.register("PING", new PingParser());
commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher));
parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser());
commandRouter.register(
CheckUsernameRequest.class,
new CheckUsernameHandler(responseDispatcher, userRegistry));
}
}
@@ -0,0 +1,44 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
import java.util.Optional;
/** Handles {@link CheckUsernameRequest}s to check whether a username is available. */
public class CheckUsernameHandler implements CommandHandler<CheckUsernameRequest> {
private final ResponseDispatcher responseDispatcher;
private final UserRegistry userRegistry;
/**
* Creates a new handler for checking username availability.
*
* @param responseDispatcher the dispatcher used to send the response
* @param userRegistry the registry used to look up existing users
*/
public CheckUsernameHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
this.responseDispatcher = responseDispatcher;
this.userRegistry = userRegistry;
}
/**
* Executes the username availability check for the given request.
*
* <p>If no user exists for the requested username, the username is reported as {@link
* UsernameAvailability#FREE}; otherwise, it is reported as {@link UsernameAvailability#TAKEN}.
*
* @param request the request to execute
*/
@Override
public void execute(CheckUsernameRequest request) {
Optional<User> user = userRegistry.getByUsername(request.getUsername());
UsernameAvailability availability;
if (user.isEmpty()) {
availability = UsernameAvailability.FREE;
} else {
availability = UsernameAvailability.TAKEN;
}
responseDispatcher.dispatch(new CheckUsernameResponse(request.getContext(), availability));
}
}
@@ -0,0 +1,21 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
/** Parses a primitive request into a {@link CheckUsernameRequest}. */
public class CheckUsernameParser implements CommandParser<CheckUsernameRequest> {
/**
* Extracts the required {@code USERNAME} parameter from the incoming request.
*
* @param primitiveRequest the request to parse
* @return {@link CheckUsernameRequest} containing the username
*/
@Override
public CheckUsernameRequest parse(PrimitiveRequest primitiveRequest) {
RequestParameterAccessor accessor =
new RequestParameterAccessor(primitiveRequest.parameters());
return new CheckUsernameRequest(primitiveRequest.context(), accessor.require("USERNAME"));
}
}
@@ -0,0 +1,30 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
/** Request implementation used to check whether a username is available or already taken */
public class CheckUsernameRequest extends Request {
private final String username;
/**
* Constructs a new CheckUsernameRequest with the given context and username to check
*
* @param context the {@link RequestContext} containing information for responding to the
* request
* @param username the username to check for availability
*/
public CheckUsernameRequest(RequestContext context, String username) {
super(context);
this.username = username;
}
/**
* Returns the provided username in the request
*
* @return username to check
*/
public String getUsername() {
return username;
}
}
@@ -0,0 +1,18 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
/** Response indicating the availability status of a username check. */
public class CheckUsernameResponse extends SuccessResponse {
/**
* Creates a new response to respond to the username availability check to
*
* @param context the {@link RequestContext} associated with the request
* @param availability the availability status of the requested username
*/
public CheckUsernameResponse(RequestContext context, UsernameAvailability availability) {
super(context, new ResponseBodyBuilder().param("STATUS", availability).build());
}
}
@@ -0,0 +1,10 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
/** Represents the availability status of a username */
enum UsernameAvailability {
/** Username is available */
FREE,
/** Username is already in use */
TAKEN
}
@@ -0,0 +1,29 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
/** Handler for {@link PingRequest}. */
public class PingHandler implements CommandHandler<PingRequest> {
private final ResponseDispatcher responseDispatcher;
/**
* Create a new PingHandler to execute {@link PingRequest}s
*
* @param responseDispatcher dispatcher used to send responses back to clients
*/
public PingHandler(ResponseDispatcher responseDispatcher) {
this.responseDispatcher = responseDispatcher;
}
/**
* Execute the ping request.
*
* @param request the ping request to handle
*/
@Override
public void execute(PingRequest request) {
responseDispatcher.dispatch(new OkResponse(request.getContext()));
}
}
@@ -0,0 +1,22 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping;
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
/**
* Parser for the Ping command.
*
* <p>Converts a low-level {@link PrimitiveRequest} into a {@link PingRequest}.
*/
public class PingParser implements CommandParser<PingRequest> {
/**
* Parse the given primitive request into a {@link PingRequest}.
*
* @param primitiveRequest the raw request to parse
* @return {@link PingRequest}
*/
@Override
public PingRequest parse(PrimitiveRequest primitiveRequest) {
return new PingRequest(primitiveRequest.context());
}
}
@@ -0,0 +1,19 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
/**
* Represents a "PING" request sent by a client to check server availability and keep the connection
* alive.
*/
public class PingRequest extends Request {
/**
* Constructs a new PingRequest with the given context.
*
* @param context the request context associated with this request
*/
public PingRequest(RequestContext context) {
super(context);
}
}
@@ -0,0 +1,247 @@
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.
*
* <p>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,31 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
/**
* AbstractAction serves as a base class for all player actions in the poker game. It implements the
* Action interface and provides a common implementation for retrieving the player ID associated
* with the action.
*/
public abstract class AbstractAction implements Action {
protected final PlayerId playerId;
/**
* Constructs an AbstractAction with the specified player ID.
*
* @param playerId The ID of the player performing the action.
*/
public AbstractAction(PlayerId playerId) {
this.playerId = playerId;
}
/**
* Retrieves the ID of the player performing the action.
*
* @return The player ID associated with this action.
*/
@Override
public PlayerId getPlayerId() {
return playerId;
}
}
@@ -0,0 +1,17 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerId;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
/**
* The Action interface defines the structure for all player actions in the poker game. Each action
* must specify its type, provide an implementation for executing the action on the game state, and
* identify the player performing the action.
*/
public interface Action {
ActionType getType();
void execute(GameState state);
PlayerId getPlayerId();
}
@@ -0,0 +1,16 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
/**
* ActionType is an enumeration that defines the various types of actions that players can perform
* in a poker game. Each action type corresponds to a specific move or decision that a player can
* make during their turn.
*/
public enum ActionType {
FOLD,
CALL,
RAISE,
CHECK,
BET,
ALL_IN,
BLIND
}
@@ -0,0 +1,48 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
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.state.GameState;
/**
* AllInAction represents the action of a player going all-in in a poker game. When a player goes
* all-in, they bet all of their remaining chips. This action updates the player's chip count, adds
* the bet to the pot, and updates the current bet for the player in the game state.
*/
public class AllInAction extends AbstractAction {
/**
* Constructs an AllInAction for the specified player ID.
*
* @param playerId The ID of the player performing the all-in action.
*/
public AllInAction(PlayerId playerId) {
super(playerId);
}
/**
* Retrieves the type of this action, which is ALL_IN.
*
* @return The ActionType corresponding to this action.
*/
@Override
public ActionType getType() {
return ActionType.ALL_IN;
}
/**
* Executes the all-in action on the given game state. This method updates the player's chip
* count, adds the bet to the pot, and updates the current bet for the player in the game state.
*
* @param state The current game state on which to execute the action.
*/
@Override
public void execute(GameState state) {
Player player = state.getPlayer(playerId);
int chips = player.getChips();
player.removeChips(chips);
state.addToPot(chips);
state.setCurrentBet(playerId, state.getCurrentBet(playerId) + chips);
}
}
@@ -0,0 +1,64 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
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.state.GameState;
/**
* Represents a bet action where a player contributes a fixed amount of chips. The amount is
* deducted from the player's chips, added to the pot, and both the player's current bet and the
* table's current bet are set to this amount.
*/
public class BetAction extends AbstractAction {
private final int amount;
/**
* Constructs a BetAction for the specified player ID and bet amount.
*
* @param playerId The ID of the player performing the bet action.
* @param amount The amount of chips the player is betting.
*/
public BetAction(PlayerId playerId, int amount) {
super(playerId);
this.amount = amount;
}
/**
* Retrieves the amount of chips being bet in this action.
*
* @return The amount of chips being bet.
*/
public int getAmount() {
return amount;
}
/**
* Retrieves the type of this action, which is BET.
*
* @return The ActionType corresponding to this action.
*/
@Override
public ActionType getType() {
return ActionType.BET;
}
/**
* Executes the bet action on the given game state. This method updates the player's chip count,
* adds the bet to the pot, and updates the current bet for the player in the game state.
*
* @param state The current game state on which to execute the action.
*/
@Override
public void execute(GameState state) {
Player player = state.getPlayer(playerId);
player.removeChips(amount);
state.addToPot(amount);
state.setCurrentBet(playerId, amount);
state.getTableState().setCurrentBet(amount);
}
}
@@ -0,0 +1,80 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
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.state.GameState;
/**
* Executes a blind by deducting chips from the player, adding them to the pot, increasing the
* player's current bet, and updating the table's current bet if the blind exceeds the existing bet.
*/
public class BlindAction implements Action {
private final PlayerId playerId;
private final int amount;
/**
* Constructs a BlindAction for the specified player ID and blind amount.
*
* @param playerId The ID of the player posting the blind bet.
* @param amount The amount of chips the player is posting as a blind bet.
*/
public BlindAction(PlayerId playerId, int amount) {
this.playerId = playerId;
this.amount = amount;
}
/**
* Retrieves the type of this action, which is BLIND.
*
* @return The ActionType corresponding to this action.
*/
@Override
public ActionType getType() {
return ActionType.BLIND;
}
/**
* Retrieves the ID of the player performing the action.
*
* @return The player ID associated with this action.
*/
@Override
public PlayerId getPlayerId() {
return playerId;
}
/**
* Executes the blind action on the given game state. This method updates the player's chip
* count, adds the blind bet to the pot, and updates the current bet for the player in the game
* state.
*
* @param state The current game state on which to execute the action.
*/
@Override
public void execute(GameState state) {
Player player = state.getPlayer(playerId);
player.removeChips(amount);
state.addToPot(amount);
int alreadyPaid = state.getCurrentBet(playerId);
state.setCurrentBet(playerId, alreadyPaid + amount);
if (state.getTableState().getCurrentBet() < amount) {
state.getTableState().setCurrentBet(amount);
}
}
/**
* Retrieves the amount of chips being posted as a blind bet in this action.
*
* @return The amount of chips being posted as a blind bet.
*/
public int getAmount() {
return amount;
}
}
@@ -0,0 +1,64 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
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.state.GameState;
/**
* CallAction represents the action of a player calling in a poker game. When a player calls, they
* match the current bet on the table by paying the difference between their current bet and the
* table's current bet. This action updates the player's chip count, adds the required amount to the
* pot, and updates the player's current bet accordingly.
*/
public class CallAction extends AbstractAction {
/**
* Constructs a CallAction for the specified player.
*
* @param playerId the ID of the player performing the call action
*/
public CallAction(PlayerId playerId) {
super(playerId);
}
/**
* Returns the type of this action, which is CALL.
*
* @return the ActionType representing a call action
*/
@Override
public ActionType getType() {
return ActionType.CALL;
}
/**
* Executes the call action on the given game state. This method checks if the player is folded,
* calculates the amount to call, updates the player's chip count, adds the amount to the pot,
* and updates the player's current bet.
*
* @param state the current game state on which to execute the call action
*/
@Override
public void execute(GameState state) {
Player player = state.getPlayer(playerId);
if (player.isFolded()) {
return;
}
int tableBet = state.getTableState().getCurrentBet();
int playerBet = state.getCurrentBet(playerId);
int toCall = tableBet - playerBet;
if (toCall <= 0) {
return;
}
player.removeChips(toCall);
state.addToPot(toCall);
state.setCurrentBet(playerId, playerBet + toCall);
}
}
@@ -0,0 +1,44 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
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.state.GameState;
/**
* FoldAction represents the action of a player folding in a poker game. When a player folds, they
* forfeit their hand and are no longer active in the current round. This action updates the
* player's status to indicate that they have folded.
*/
public class FoldAction extends AbstractAction {
/**
* Constructs a FoldAction for the specified player ID.
*
* @param playerId The ID of the player performing the fold action.
*/
public FoldAction(PlayerId playerId) {
super(playerId);
}
/**
* Retrieves the type of this action, which is FOLD.
*
* @return The ActionType corresponding to this action.
*/
@Override
public ActionType getType() {
return ActionType.FOLD;
}
/**
* Executes the fold action on the given game state. This method updates the player's status to
* indicate that they have folded.
*
* @param state The current game state on which to execute the action.
*/
@Override
public void execute(GameState state) {
Player player = state.getPlayer(playerId);
player.setFolded(true);
}
}
@@ -0,0 +1,66 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action;
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.state.GameState;
public class RaiseAction extends AbstractAction {
private final int raiseAmount;
/**
* Constructs a RaiseAction for the specified player ID and raise amount.
*
* @param playerId The ID of the player performing the raise action.
* @param raiseAmount The amount of chips the player is raising.
*/
public RaiseAction(PlayerId playerId, int raiseAmount) {
super(playerId);
this.raiseAmount = raiseAmount;
}
/**
* Retrieves the amount of chips being raised in this action.
*
* @return The amount of chips being raised.
*/
public int getAmount() {
return raiseAmount;
}
/**
* Retrieves the type of this action, which is RAISE.
*
* @return The ActionType corresponding to this action.
*/
@Override
public ActionType getType() {
return ActionType.RAISE;
}
/**
* Executes the raise action on the given game state. This method updates the player's chip
* count, adds the raise amount to the pot, and updates the current bet for the player in the
* game state.
*
* @param state The current game state on which to execute the action.
*/
@Override
public void execute(GameState state) {
Player player = state.getPlayer(playerId);
int alreadyPaid = state.getCurrentBet(playerId);
int toCall = state.getTableState().getCurrentBet() - alreadyPaid;
int total = toCall + raiseAmount;
player.removeChips(total);
state.addToPot(total);
int newBet = state.getTableState().getCurrentBet() + raiseAmount;
state.getTableState().setCurrentBet(newBet);
state.setCurrentBet(playerId, alreadyPaid + total);
}
}
@@ -0,0 +1,52 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck;
/**
* The Card class represents a single playing card in a standard deck of cards. Each card has a suit
* (Hearts, Diamonds, Clubs, Spades) and a rank (2-10, Jack, Queen, King, Ace). This class provides
* methods to retrieve the suit and rank of the card, as well as a string representation of the
* card.
*/
public class Card {
private Suit suit;
private Rank rank;
/**
* Constructs a Card with the specified suit and rank.
*
* @param suit The suit of the card (Hearts, Diamonds, Clubs, Spades).
* @param rank The rank of the card (2-10, Jack, Queen, King, Ace).
*/
public Card(Suit suit, Rank rank) {
this.suit = suit;
this.rank = rank;
}
/**
* Retrieves the suit of the card.
*
* @return The suit of the card.
*/
public Suit getSuit() {
return suit;
}
/**
* Retrieves the rank of the card.
*
* @return The rank of the card.
*/
public Rank getRank() {
return rank;
}
/**
* Returns a string representation of the card, combining its rank and suit.
*
* @return A string representation of the card (e.g., "Ace of Spades").
*/
@Override
public String toString() {
return rank + " of " + suit;
}
}
@@ -0,0 +1,62 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* The Deck class represents a standard deck of playing cards. It provides functionality to create a
* full deck of 52 cards, shuffle the deck, and draw cards from it. The deck is initialized with all
* combinations of suits and ranks, and can be manipulated through its methods.
*/
public class Deck {
private List<Card> cards;
/**
* Constructs a new Deck object and initializes it with a standard set of 52 playing cards. The
* deck is created by iterating through all suits and ranks, creating a Card object for each
* combination, and adding it to the list of cards.
*/
public Deck() {
cards = new ArrayList<>();
for (Suit suit : Suit.values()) {
for (Rank rank : Rank.values()) {
cards.add(new Card(suit, rank));
}
}
}
/**
* Shuffles the deck of cards using the Collections.shuffle method, which randomly permutes the
* list of cards. This method can be called to randomize the order of the cards in the deck
* before drawing.
*/
public void shuffle() {
Collections.shuffle(cards);
}
/**
* Draws and removes the last card in the list, which represents the top of the deck. This
* method removes and returns the last card in the list of cards, which represents the top of
* the deck. If the deck is empty, this method will throw an IndexOutOfBoundsException.
*
* @return The Card object that was drawn from the deck.
*/
public Card draw() {
return cards.remove(cards.size() - 1);
}
/**
* Sets the cards of the deck. This method replaces the internal list
* with a copy of the provided list.
*
* @param cards the new list of cards to set
*/
public void setCards(List<Card> cards) {
this.cards = new LinkedList<>(cards);
}
}
@@ -0,0 +1,22 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck;
/**
* The Rank enumeration represents the rank of a playing card in a standard deck of cards. It
* includes the ranks from Two to Ace, which are used to determine the value of the card in various
* card games, including poker.
*/
public enum Rank {
TWO,
THREE,
FOUR,
FIVE,
SIX,
SEVEN,
EIGHT,
NINE,
TEN,
JACK,
QUEEN,
KING,
ACE
}
@@ -0,0 +1,13 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck;
/**
* The Suit enumeration represents the four suits of a standard deck of playing cards: Hearts,
* Diamonds, Clubs, and Spades. Each suit is used to categorize the cards and can have different
* values in various card games, including poker.
*/
public enum Suit {
HEARTS,
DIAMONDS,
CLUBS,
SPADES
}
@@ -0,0 +1,104 @@
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,133 @@
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,26 @@
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);
}
}
@@ -0,0 +1,378 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Card;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Suit;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* The HandEvaluator class provides functionality to evaluate a poker hand and determine its rank.
* It analyzes a list of cards and identifies the best possible hand according to standard poker
* rules, such as flushes, straights, pairs, and more. The evaluation process involves counting card
* ranks, grouping by suits, and checking for specific hand combinations to assign the correct hand
* rank.
*/
public class HandEvaluator {
private static final int RANK_OFFSET = 2;
private static final int MIN_FLUSH_SIZE = 5;
private static final int ROYAL_FLUSH_ACE = 14;
private static final int ROYAL_FLUSH_KING = 13;
private static final int ROYAL_FLUSH_QUEEN = 12;
private static final int ROYAL_FLUSH_JACK = 11;
private static final int ROYAL_FLUSH_TEN = 10;
private static final long FOUR_OF_A_KIND_COUNT = 4L;
private static final int FOUR_OF_A_KIND = 4;
private static final int THREE_OF_A_KIND = 3;
private static final int PAIR = 2;
private static final int FIRST_INDEX = 0;
private static final int CARD_VALUE_OFFSET = 2;
private static final int HAND_SIZE = 5;
private static final long THREE_OF_A_KIND_COUNT = 3L;
private static final int THREE_OF_A_KIND_RANK = 3;
private static final int ONE_PAIR = 1;
private static final int DEFAULT_VALUE = 0;
private static final int STRAIGHT_LENGTH = 5;
private static final int INITIAL_STREAK = 1;
private static final int START_INDEX = 1;
private static final int CARD_DIFFERENCE = 1;
private static final int PREVIOUS_INDEX_OFFSET = -1;
/**
* Evaluates a list of cards and determines the best possible hand rank.
*
* @param cards A list of Card objects representing the player's hand.
* @return A HandRank object representing the evaluated hand rank.
*/
public static HandRank evaluate(List<Card> cards) {
List<Integer> ranks = getSortedRanks(cards);
Map<Integer, Long> rankCount = getRankCount(ranks);
Map<Suit, List<Card>> suits = groupBySuit(cards);
List<Card> flushCards = getFlushCards(suits);
boolean flush = flushCards != null;
boolean straight = isStraight(ranks);
HandRank straightFlush = checkStraightFlush(flushCards);
if (straightFlush != null) {
return straightFlush;
}
HandRank fourKind = checkFourOfAKind(rankCount);
if (fourKind != null) {
return fourKind;
}
HandRank fullHouse = checkFullHouse(rankCount);
if (fullHouse != null) {
return fullHouse;
}
if (flush) {
return buildFlush(flushCards);
}
if (straight) {
return new HandRank(HandRank.Type.STRAIGHT, ranks);
}
HandRank threeKind = checkThreeOfAKind(rankCount);
if (threeKind != null) {
return threeKind;
}
HandRank twoPair = checkTwoPair(rankCount);
if (twoPair != null) {
return twoPair;
}
HandRank onePair = checkOnePair(rankCount);
if (onePair != null) {
return onePair;
}
return new HandRank(HandRank.Type.HIGH_CARD, ranks.stream().limit(HAND_SIZE).toList());
}
/**
* Helper method to extract and sort the ranks of the cards in descending order.
*
* @param cards A list of Card objects representing the player's hand.
* @return A sorted list of integer ranks corresponding to the cards.
*/
private static List<Integer> getSortedRanks(List<Card> cards) {
return cards.stream()
.map(c -> c.getRank().ordinal() + RANK_OFFSET)
.sorted(Comparator.reverseOrder())
.toList();
}
/**
* Helper method to count the occurrences of each card rank in the hand.
*
* @param ranks A list of integer ranks representing the cards in the hand.
* @return A map where the key is the card rank and the value is the count of occurrences.
*/
private static Map<Integer, Long> getRankCount(List<Integer> ranks) {
return ranks.stream().collect(Collectors.groupingBy(r -> r, Collectors.counting()));
}
/**
* Helper method to group the cards by their suits.
*
* @param cards A list of Card objects representing the player's hand.
* @return A map where the key is the Suit and the value is a list of Cards belonging to that
* suit.
*/
private static Map<Suit, List<Card>> groupBySuit(List<Card> cards) {
return cards.stream().collect(Collectors.groupingBy(Card::getSuit));
}
/**
* Helper method to identify if there is a flush in the hand and return the corresponding cards.
*
* @param suits A map where the key is the Suit and the value is a list of Cards belonging to
* that suit.
* @return A list of Card objects that form a flush, or null if no flush is found.
*/
private static List<Card> getFlushCards(Map<Suit, List<Card>> suits) {
return suits.values().stream()
.filter(s -> s.size() >= MIN_FLUSH_SIZE)
.findFirst()
.orElse(null);
}
/**
* Helper method to build a HandRank object for a flush hand.
*
* @param flushCards A list of Card objects that form a flush.
* @return A HandRank object representing the flush hand rank.
*/
private static HandRank buildFlush(List<Card> flushCards) {
List<Integer> flushRanks =
flushCards.stream()
.map(c -> c.getRank().ordinal() + CARD_VALUE_OFFSET)
.sorted(Comparator.reverseOrder())
.limit(HAND_SIZE)
.toList();
return new HandRank(HandRank.Type.FLUSH, flushRanks);
}
/**
* Helper method to check for a straight flush or royal flush in the hand.
*
* @param flushCards A list of Card objects that form a flush.
* @return A HandRank object representing the straight flush or royal flush hand rank, or null
* if neither is found.
*/
private static HandRank checkStraightFlush(List<Card> flushCards) {
if (flushCards == null) {
return null;
}
List<Integer> flushRanks =
flushCards.stream()
.map(c -> c.getRank().ordinal() + RANK_OFFSET)
.sorted(Comparator.reverseOrder())
.toList();
if (!isStraight(flushRanks)) {
return null;
}
if (flushRanks.containsAll(
List.of(
ROYAL_FLUSH_ACE,
ROYAL_FLUSH_KING,
ROYAL_FLUSH_QUEEN,
ROYAL_FLUSH_JACK,
ROYAL_FLUSH_TEN))) {
return new HandRank(HandRank.Type.ROYAL_FLUSH, flushRanks);
}
return new HandRank(HandRank.Type.STRAIGHT_FLUSH, flushRanks);
}
/**
* Helper method to check for a four of a kind hand rank.
*
* @param rankCount A map where the key is the card rank and the value is the count of
* occurrences.
* @return A HandRank object representing the four of a kind hand rank, or null if not found.
*/
private static HandRank checkFourOfAKind(Map<Integer, Long> rankCount) {
if (!rankCount.containsValue(FOUR_OF_A_KIND_COUNT)) {
return null;
}
int quad = getRank(rankCount, FOUR_OF_A_KIND);
return new HandRank(HandRank.Type.FOUR_OF_A_KIND, List.of(quad));
}
/**
* Helper method to check for a full house hand rank.
*
* @param rankCount A map where the key is the card rank and the value is the count of
* occurrences.
* @return A HandRank object representing the full house hand rank, or null if not found.
*/
private static HandRank checkFullHouse(Map<Integer, Long> rankCount) {
List<Integer> trips =
rankCount.entrySet().stream()
.filter(e -> e.getValue() >= THREE_OF_A_KIND)
.map(Map.Entry::getKey)
.sorted(Comparator.reverseOrder())
.toList();
List<Integer> pairs =
rankCount.entrySet().stream()
.filter(e -> e.getValue() >= PAIR)
.map(Map.Entry::getKey)
.sorted(Comparator.reverseOrder())
.toList();
if (trips.isEmpty()) {
return null;
}
int three = trips.get(FIRST_INDEX);
Integer pair = pairs.stream().filter(p -> p != three).findFirst().orElse(null);
if (pair == null) {
return null;
}
return new HandRank(HandRank.Type.FULL_HOUSE, List.of(three, pair));
}
/**
* Helper method to check for a three of a kind hand rank.
*
* @param rankCount A map where the key is the card rank and the value is the count of
* occurrences.
* @return A HandRank object representing the three of a kind hand rank, or null if not found.
*/
private static HandRank checkThreeOfAKind(Map<Integer, Long> rankCount) {
if (!rankCount.containsValue(THREE_OF_A_KIND_COUNT)) {
return null;
}
int tripsRank = getRank(rankCount, THREE_OF_A_KIND_RANK);
return new HandRank(HandRank.Type.THREE_OF_A_KIND, List.of(tripsRank));
}
/**
* Helper method to check for a two pair hand rank.
*
* @param rankCount A map where the key is the card rank and the value is the count of
* occurrences.
* @return A HandRank object representing the two pair hand rank, or null if not found.
*/
private static HandRank checkTwoPair(Map<Integer, Long> rankCount) {
long pairCount = rankCount.values().stream().filter(v -> v == PAIR).count();
if (pairCount < PAIR) {
return null;
}
List<Integer> pairRanks =
rankCount.entrySet().stream()
.filter(e -> e.getValue() == PAIR)
.map(Map.Entry::getKey)
.sorted(Comparator.reverseOrder())
.limit(PAIR)
.toList();
return new HandRank(HandRank.Type.TWO_PAIR, pairRanks);
}
/**
* Helper method to check for a one pair hand rank.
*
* @param rankCount A map where the key is the card rank and the value is the count of
* occurrences.
* @return A HandRank object representing the one pair hand rank, or null if not found.
*/
private static HandRank checkOnePair(Map<Integer, Long> rankCount) {
long pairCount = rankCount.values().stream().filter(v -> v == PAIR).count();
if (pairCount != ONE_PAIR) {
return null;
}
int pair = getRank(rankCount, PAIR);
return new HandRank(HandRank.Type.ONE_PAIR, List.of(pair));
}
/**
* Helper method to get the rank of a card based on the count of occurrences in the hand.
*
* @param map A map where the key is the card rank and the value is the count of occurrences.
* @param count The specific count to look for (e.g., 2 for pairs, 3 for three of a kind).
* @return The rank of the card that matches the specified count, or 0 if none found.
*/
private static int getRank(Map<Integer, Long> map, int count) {
return map.entrySet().stream()
.filter(e -> e.getValue() == count)
.map(Map.Entry::getKey)
.max(Integer::compare)
.orElse(DEFAULT_VALUE);
}
/**
* Helper method to determine if a list of card ranks forms a straight.
*
* @param ranks A list of integer ranks representing the cards in the hand.
* @return True if the ranks form a straight, false otherwise.
*/
private static boolean isStraight(List<Integer> ranks) {
Set<Integer> unique = new HashSet<>(ranks);
// Wheel Straight A-2-3-4-5
if (unique.containsAll(
List.of(
ROYAL_FLUSH_ACE,
ROYAL_FLUSH_KING,
ROYAL_FLUSH_QUEEN,
ROYAL_FLUSH_JACK,
ROYAL_FLUSH_TEN))) {
return true;
}
List<Integer> sorted = unique.stream().sorted().collect(Collectors.toList());
int streak = INITIAL_STREAK;
for (int i = START_INDEX; i < sorted.size(); i++) {
if (sorted.get(i) == sorted.get(i + PREVIOUS_INDEX_OFFSET) + CARD_DIFFERENCE) {
streak++;
if (streak >= STRAIGHT_LENGTH) {
return true;
}
} else {
streak = INITIAL_STREAK;
}
}
return false;
}
}
@@ -0,0 +1,99 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.evaluator;
import java.util.List;
/**
* HandRank represents the rank of a poker hand, including its type (e.g., flush, straight) and the
* kickers used for tie-breaking. It implements the Comparable interface to allow for easy
* comparison between different hand ranks.
*/
public class HandRank implements Comparable<HandRank> {
/**
* The Type enumeration defines the different types of poker hands, each with an associated
* strength value for comparison purposes.
*/
public enum Type {
HIGH_CARD(1),
ONE_PAIR(2),
TWO_PAIR(3),
THREE_OF_A_KIND(4),
STRAIGHT(5),
FLUSH(6),
FULL_HOUSE(7),
FOUR_OF_A_KIND(8),
STRAIGHT_FLUSH(9),
ROYAL_FLUSH(10);
private final int strength;
Type(int strength) {
this.strength = strength;
}
public int getStrength() {
return strength;
}
}
private final Type type;
private final List<Integer> kickers;
/**
* Constructs a HandRank with the specified type and kickers.
*
* @param type The type of the hand (e.g., flush, straight).
* @param kickers A list of integers representing the kickers for tie-breaking.
*/
public HandRank(Type type, List<Integer> kickers) {
this.type = type;
this.kickers = kickers;
}
/**
* Retrieves the type of the hand.
*
* @return The type of the hand (e.g., flush, straight).
*/
public Type getType() {
return type;
}
/**
* Retrieves the list of kickers for tie-breaking.
*
* @return A list of integers representing the kickers for tie-breaking.
*/
public List<Integer> getKickers() {
return kickers;
}
/**
* Compares this HandRank with another HandRank for ordering. The comparison is based first on
* the type of the hand and then on the kickers for tie-breaking.
*
* @param other The other HandRank to compare against.
* @return A negative integer, zero, or a positive integer as this HandRank is less than, equal
* to, or greater than the specified HandRank.
*/
@Override
public int compareTo(HandRank other) {
int typeCompare = Integer.compare(this.type.getStrength(), other.type.getStrength());
if (typeCompare != 0) {
return typeCompare;
}
for (int i = 0; i < Math.min(kickers.size(), other.kickers.size()); i++) {
int cmp = Integer.compare(kickers.get(i), other.kickers.get(i));
if (cmp != 0) {
return cmp;
}
}
return 0;
}
}
@@ -0,0 +1,150 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Card;
import java.util.ArrayList;
import java.util.List;
/**
* The Player class represents a participant in the poker game. It holds information about the
* player's identity, chip count, status, and hand of cards. The class provides methods for managing
* the player's chips, hand, and status during the game.
*/
public class Player {
private PlayerId id;
private int chips;
private PlayerStatus status;
private boolean folded = false;
private List<Card> hand = new ArrayList<>();
/**
* Constructs a Player with the specified ID and initial chip count. The player's status is set
* to ACTIVE by default.
*
* @param id The unique identifier for the player.
* @param chips The initial number of chips the player has.
*/
public Player(PlayerId id, int chips) {
this.id = id;
this.chips = chips;
this.status = PlayerStatus.ACTIVE;
}
/**
* Checks if the player is all-in, meaning they have no chips left to bet.
*
* @return true if the player is all-in, false otherwise.
*/
public boolean isAllIn() {
return chips == 0;
}
/**
* Retrieves the unique identifier of the player.
*
* @return The player's ID.
*/
public PlayerId getId() {
return id;
}
/**
* Returns the display name of the player. Currently identical to the player ID.
*
* @return The player's name.
*/
public String getName() {
return id.value();
}
/**
* Retrieves the current chip count of the player.
*
* @return The number of chips the player has.
*/
public int getChips() {
return chips;
}
/**
* Removes a specified amount of chips from the player's total. If the amount exceeds the
* player's current chips, it sets the chip count to zero.
*
* @param amount The number of chips to remove from the player.
*/
public void removeChips(int amount) {
chips -= amount;
if (chips < 0) {
chips = 0;
}
}
/**
* Adds a specified amount of chips to the player's total.
*
* @param amount The number of chips to add to the player.
*/
public void addChips(int amount) {
chips += amount;
}
/**
* Sets the player's status to the specified value.
*
* @param status The new status for the player.
*/
public void setStatus(PlayerStatus status) {
this.status = status;
}
/**
* Retrieves the current status of the player.
*
* @return The player's status.
*/
public PlayerStatus getStatus() {
return status;
}
/**
* Retrieves the player's current hand of cards.
*
* @return A list of Card objects representing the player's hand.
*/
public List<Card> getHand() {
return hand;
}
/**
* Adds a card to the player's hand.
*
* @param card The Card object to be added to the player's hand.
*/
public void giveCard(Card card) {
hand.add(card);
}
/** Clears the player's hand of cards, removing all cards from the hand. */
public void clearHand() {
hand.clear();
}
/**
* Checks if the player has folded in the current round.
*
* @return true if the player has folded, false otherwise.
*/
public boolean isFolded() {
return folded;
}
/**
* Sets the player's folded status to the specified value.
*
* @param folded The new folded status for the player.
*/
public void setFolded(boolean folded) {
this.folded = folded;
}
}
@@ -0,0 +1,73 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player;
import java.util.Objects;
/**
* PlayerId is a value object that represents the unique identifier of a player in the game. It
* encapsulates a string value and provides validation to ensure that it is not null. The PlayerId
* class also includes a factory method for creating instances and overrides the toString method for
* easy representation.
*/
public record PlayerId(String value) {
/**
* Constructs a PlayerId with the specified value. The constructor validates that the value is
* not null.
*
* @param value the string value representing the player's unique identifier
* @throws NullPointerException if the value is null
*/
public PlayerId {
Objects.requireNonNull(value, "PlayerId cannot be null");
}
/**
* Factory method to create a PlayerId instance from a string value.
*
* @param value the string value representing the player's unique identifier
* @return a new PlayerId instance with the specified value
*/
public static PlayerId of(String value) {
return new PlayerId(value);
}
/**
* Returns the string representation of the PlayerId, which is the encapsulated value.
*
* @return the string value of the PlayerId
*/
@Override
public String toString() {
return value;
}
/**
* Compares this PlayerId with another object for equality.
*
* @param o the object to compare to
* @return true if this PlayerId is equal to the given object, false otherwise
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PlayerId playerId = (PlayerId) o;
return Objects.equals(value, playerId.value);
}
/**
* Returns the hash code for this PlayerId.
*
* @return the hash code of the PlayerId
*/
@Override
public int hashCode() {
return Objects.hash(value);
}
}
@@ -0,0 +1,13 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player;
/**
* The PlayerStatus enumeration represents the various states a player can be in during a poker
* game. It helps to track the player's current status, such as whether they are actively
* participating in the hand, have folded, are all-in, or have been eliminated from the game.
*/
public enum PlayerStatus {
ACTIVE,
FOLDED,
ALL_IN,
OUT
}
@@ -0,0 +1,14 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
/**
* The Rule interface defines the structure for all rules in the poker game. Each rule must provide
* a validate method that checks if a given action is valid based on the current game state. If the
* action violates the rule, a RuleViolationException should be thrown to indicate the specific rule
* that was violated.
*/
public interface Rule {
void validate(GameState state, Action action);
}
@@ -0,0 +1,39 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
import java.util.List;
/**
* The RuleEngine class is responsible for managing and validating a list of rules in the poker
* game. It provides a method to validate a given action against all the rules, ensuring that the
* action complies with the game's regulations. If any rule is violated during validation, a
* RuleViolationException will be thrown, indicating the specific rule that was not followed.
*/
public class RuleEngine {
private final List<Rule> rules;
/**
* Constructs a RuleEngine with the specified list of rules.
*
* @param rules The list of rules to be managed by the RuleEngine.
*/
public RuleEngine(List<Rule> rules) {
this.rules = rules;
}
/**
* Validates the given action against all the rules in the RuleEngine. If any rule is violated,
* a RuleViolationException will be thrown, indicating the specific rule that was not followed.
*
* @param state The current state of the game.
* @param action The action to be validated against the rules.
* @throws RuleViolationException if any rule is violated during validation.
*/
public void validate(GameState state, Action action) {
for (Rule rule : rules) {
rule.validate(state, action);
}
}
}
@@ -0,0 +1,19 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules;
/**
* RuleViolationException is a custom exception that is thrown when a player action violates a
* specific rule in the poker game. This exception provides a message detailing the nature of the
* rule violation, allowing for better error handling and user feedback when invalid actions are
* attempted.
*/
public class RuleViolationException extends RuntimeException {
/**
* Constructs a new RuleViolationException with the specified detail message.
*
* @param message The detail message explaining the reason for the rule violation.
*/
public RuleViolationException(String message) {
super(message);
}
}
@@ -0,0 +1,35 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.ActionType;
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;
/**
* The AcceptedActionRule class implements the Rule interface and defines the validation logic for
* accepted player actions in a poker game. It checks whether the action type is one of the allowed
* types (FOLD, CALL, RAISE, CHECK, BET, ALL_IN) and throws a RuleViolationException if the action
* is not allowed.
*/
public class AcceptedActionRule implements Rule {
/**
* Validates the given action against the accepted action types for a poker game. If the action
* type is not one of the allowed types, a RuleViolationException is thrown.
*
* @param state The current state of the game.
* @param action The action to be validated.
* @throws RuleViolationException if the action type is not allowed.
*/
@Override
public void validate(GameState state, Action action) {
ActionType type = action.getType();
switch (type) {
case FOLD, CALL, RAISE, CHECK, BET, ALL_IN -> {}
default -> throw new RuleViolationException("Action not allowed");
}
}
}
@@ -0,0 +1,41 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
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.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
* ensuring that players take their turns in the correct order during a poker game. It checks
* whether the player performing the action is the current player whose turn it is, and throws a
* RuleViolationException if the action is attempted out of turn.
*/
public class ActionOrderRule implements Rule {
/**
* Validates that the player performing the action is the current player whose turn it is in the
* game. If the action is attempted by a player who is not the current player, a
* RuleViolationException is thrown.
*
* @param state The current state of the game.
* @param action The action to be validated.
* @throws RuleViolationException if the action is attempted out of turn.
*/
@Override
public void validate(GameState state, Action action) {
PlayerId actingPlayerId = action.getPlayerId();
List<Player> playerList = new ArrayList<>(state.getPlayers());
Player current = playerList.get(state.getCurrentPlayerIndex());
if (!current.getId().equals(actingPlayerId)) {
throw new RuleViolationException("Not your turn");
}
}
}
@@ -0,0 +1,37 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.AllInAction;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.Player;
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;
/**
* The AllInRule class implements the Rule interface and defines the validation logic for the
* "all-in" action in a poker game. It checks whether the player attempting to go all-in has any
* chips left, and throws a RuleViolationException if the player has no chips to bet.
*/
public class AllInRule implements Rule {
/**
* Validates the "all-in" action by checking if the player has any chips left. If the player has
* no chips, a RuleViolationException is thrown.
*
* @param state The current state of the game.
* @param action The action to be validated, which should be an instance of AllInAction.
* @throws RuleViolationException if the player has no chips to go all-in.
*/
@Override
public void validate(GameState state, Action action) {
if (action instanceof AllInAction allIn) {
Player player = state.getPlayer(allIn.getPlayerId());
if (player.getChips() <= 0) {
throw new RuleViolationException("No chips for all-in");
}
}
}
}
@@ -0,0 +1,38 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.RaiseAction;
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;
/**
* The BindingDeclarationRule class implements the Rule interface and defines the validation logic
* for binding declarations in a poker game. It checks whether a raise action is an under-raise or
* string bet by comparing the raise amount to the current bet commitment of the player. If the
* raise amount is less than the committed amount, a RuleViolationException is thrown.
*/
public class BindingDeclarationRule implements Rule {
/**
* Validates the raise action by checking if the raise amount is less than the current bet
* commitment of the player. If it is, a RuleViolationException is thrown, indicating an illegal
* under-raise or string bet.
*
* @param state The current state of the game.
* @param action The action to be validated, which should be an instance of RaiseAction.
* @throws RuleViolationException if the raise amount is less than the current bet commitment.
*/
@Override
public void validate(GameState state, Action action) {
if (action instanceof RaiseAction raise) {
int committed = state.getCurrentBetCommitment(action.getPlayerId());
if (raise.getAmount() < committed) {
throw new RuleViolationException("Illegal under-raise / string bet");
}
}
}
}
@@ -0,0 +1,31 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
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;
/**
* The HandActiveRule class implements the Rule interface and defines the validation logic for
* checking if there is an active hand in a poker game. It ensures that players can only perform
* actions when there is an active hand, and throws a RuleViolationException if there is no active
* hand.
*/
public class HandActiveRule implements Rule {
/**
* Validates that there is an active hand in the game. If there is no active hand, a
* RuleViolationException is thrown, indicating that players cannot perform actions without an
* active hand.
*
* @param state The current state of the game.
* @param action The action to be validated.
* @throws RuleViolationException if there is no active hand in the game.
*/
@Override
public void validate(GameState state, Action action) {
if (!state.isHandActive()) {
throw new RuleViolationException("No active hand");
}
}
}
@@ -0,0 +1,37 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.BetAction;
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;
/**
* The MinimumBetRule class implements the Rule interface and defines the validation logic for
* ensuring that a player's bet meets the minimum bet requirement in a poker game. It checks if the
* bet amount is less than the minimum bet (usually determined by the big blind) and throws a
* RuleViolationException if the bet is below the minimum.
*/
public class MinimumBetRule implements Rule {
/**
* Validates the bet action by checking if the bet amount is less than the minimum bet. If it
* is, a RuleViolationException is thrown, indicating that the bet is below the minimum.
*
* @param state The current state of the game.
* @param action The action to be validated, which should be an instance of BetAction.
* @throws RuleViolationException if the bet amount is below the minimum bet.
*/
@Override
public void validate(GameState state, Action action) {
if (action instanceof BetAction bet) {
int minBet = state.getTableState().getBigBlind();
if (bet.getAmount() < minBet) {
throw new RuleViolationException("Bet below minimum");
}
}
}
}
@@ -0,0 +1,40 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.RaiseAction;
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;
/**
* The MinimumRaiseRule class implements the Rule interface and defines the validation logic for
* ensuring that a raise action in a poker game meets the minimum raise requirement. It checks if
* the amount of the raise is less than the minimum raise defined in the game state, and if so, it
* throws a RuleViolationException indicating that the raise is too small.
*/
public class MinimumRaiseRule implements Rule {
/**
* Validates the raise action by checking if the raise amount is less than the minimum raise
* defined in the game state. If it is, a RuleViolationException is thrown, indicating that the
* raise is too small.
*
* @param state The current state of the game.
* @param action The action to be validated, which should be an instance of RaiseAction.
* @throws RuleViolationException if the raise amount is less than the minimum raise.
*/
@Override
public void validate(GameState state, Action action) {
if (!(action instanceof RaiseAction raise)) {
return;
}
int minRaise = state.getTableState().getMinRaise();
int amount = raise.getAmount();
if (amount < minRaise) {
throw new RuleViolationException("Raise too small. Min is " + minRaise);
}
}
}
@@ -0,0 +1,41 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.ActionType;
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;
/**
* The OutOfTurnRule class implements the Rule interface and defines the validation logic for
* out-of-turn actions in a poker game. It checks whether out-of-turn actions are allowed in the
* current game state and, if so, whether the action type is permitted (e.g., only fold allowed out
* of turn). If an out-of-turn action is not allowed or if the action type is not permitted, a
* RuleViolationException is thrown.
*/
public class OutOfTurnRule implements Rule {
/**
* Validates the given action against the rules for out-of-turn actions in a poker game. If
* out-of-turn actions are not allowed, the method returns without throwing an exception. If
* out-of-turn actions are allowed, it checks whether the action type is permitted (e.g., only
* fold allowed out of turn) and throws a RuleViolationException if the action type is not
* permitted.
*
* @param state The current state of the game.
* @param action The action to be validated.
* @throws RuleViolationException if the action is not allowed out of turn or if the action type
* is not permitted.
*/
@Override
public void validate(GameState state, Action action) {
if (!state.isAllowOutOfTurn()) {
return; // strikt Mode
}
if (action.getType() != ActionType.FOLD) {
throw new RuleViolationException("Only fold allowed out of turn");
}
}
}
@@ -0,0 +1,43 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.betting;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.RaiseAction;
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;
/**
* The RaiseReopenRule class implements the Rule interface and defines the validation logic for
* allowing raises to reopen betting in a poker game. It checks if the action is a RaiseAction and
* if the betting is open for raising. If betting is not open or if reopening betting is not
* allowed, it throws a RuleViolationException with an appropriate message.
*/
public class RaiseReopenRule implements Rule {
/**
* Validates the raise action by checking if the betting is open for raising and if reopening
* betting is allowed. If betting is not open, a RuleViolationException is thrown with the
* message "Betting not open for raise". If reopening betting is not allowed, a
* RuleViolationException is thrown with the message "Raise not allowed anymore".
*
* @param state The current state of the game.
* @param action The action to be validated, which should be an instance of RaiseAction.
* @throws RuleViolationException if betting is not open for raising or if reopening betting is
* not allowed.
*/
@Override
public void validate(GameState state, Action action) {
if (!(action instanceof RaiseAction)) {
return;
}
if (!state.getTableState().isBettingOpen()) {
throw new RuleViolationException("Betting not open for raise");
}
if (!state.getTableState().canReopenBetting()) {
throw new RuleViolationException("Raise not allowed anymore");
}
}
}
@@ -0,0 +1,96 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.showdown;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.action.Action;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Card;
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.Player;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.player.PlayerStatus;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.Rule;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
import java.util.ArrayList;
import java.util.List;
/**
* The CardsSpeakRule class implements the Rule interface and defines the logic for determining the
* winner of a poker hand based on the players' hole cards and the community cards. It evaluates
* each player's hand, compares their ranks, and awards the pot to the player with the best hand.
*/
public class CardsSpeakRule implements Rule {
/**
* Validates the action for the showdown phase. In this implementation, the CardsSpeakRule does
* not perform any validation, as it is responsible for determining the winner based on the
* players' hands. The validation logic for player actions during the showdown phase should be
* handled by other rules.
*
* @param state The current state of the game.
* @param action The action to be validated.
*/
@Override
public void validate(GameState state, Action action) {
// No validation needed; rule is only applied at showdown
}
/**
* Determines the winner of the poker hand by evaluating each player's hand rank based on their
* hole cards and the community cards. It compares the hand ranks of all active players and
* returns the player with the best hand.
*
* @param state The current state of the game, which includes player information, hole cards,
* and community cards.
* @return The player with the best hand, or null if there are no active players.
*/
public Player determineWinner(GameState state) {
List<Player> players = new ArrayList<>(state.getPlayers());
Player bestPlayer = null;
HandRank bestRank = null;
for (Player player : players) {
if (player.getStatus() == PlayerStatus.FOLDED) {
continue;
}
List<Card> cards = new ArrayList<>();
cards.addAll(state.getHoleCards(player.getId()));
cards.addAll(state.getCommunityCards());
HandRank rank = HandEvaluator.evaluate(cards);
if (bestRank == null || rank.compareTo(bestRank) > 0) {
bestRank = rank;
bestPlayer = player;
}
}
return bestPlayer;
}
/**
* Awards the pot to the winner of the poker hand. It determines the winner(s) using the
* determineWinner method, retrieves the total amount in the pot, and adds it to the winner's
* chips. Finally, it resets the pot for the next hand.
*
* @param state The current state of the game, which includes player information and pot
* details.
*/
public void awardPot(GameState state) {
Player winner = determineWinner(state);
if (winner == null) {
return;
}
int pot = state.getPot().getAmount();
winner.addChips(pot);
state.getPot().reset();
}
}
@@ -0,0 +1,18 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state;
import java.util.HashMap;
import java.util.Map;
/**
* Represents the betting state of the current hand, including the pot size, the current highest
* bet, and the individual bets made by each player. It keeps track of the total pot, the current
* bet amount, and the bets made by each player. This class is essential for managing the betting
* rounds and ensuring that all players' bets are accounted for during the game.
*/
public class BettingState {
private int pot;
private int currentBet;
private Map<String, Integer> playerBets = new HashMap<>();
}
@@ -0,0 +1,17 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state;
/**
* The GamePhase enumeration represents the different phases of a poker game. Each phase corresponds
* to a specific stage in the game, such as waiting for players, dealing cards, and determining the
* winner. This enumeration is essential for managing the flow of the game and ensuring that actions
* are performed at the appropriate times.
*/
public enum GamePhase {
WAITING,
PREFLOP,
FLOP,
TURN,
RIVER,
SHOWDOWN,
FINISHED
}
@@ -0,0 +1,490 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
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.
*/
public class GameState {
private Map<PlayerId, Player> players = new HashMap<>();
private List<PlayerId> playerOrder = new ArrayList<>();
private Pot pot = new Pot();
private 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 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 Deck deck;
// Getter
/**
* Returns the list of players currently in the game.
*
* @return A list of Player objects representing the players in the game.
*/
public Collection<Player> getPlayers() {
return players.values();
}
/**
* 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.
*/
public List<Card> getHoleCards(PlayerId playerId) {
return holeCards.getOrDefault(playerId, 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.
*/
public void setCurrentPlayerIndex(int index) {
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 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);
playerOrder.add(id);
currentBets.put(id, 0);
playerBetCommitments.put(id, 0);
}
// 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.
*/
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.
*/
public void resetBets() {
currentBets.clear();
}
/**
* 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.
*/
public Player getPlayer(PlayerId id) {
Player player = players.get(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.
*/
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
/**
* 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.
*/
public void giveHoleCards(PlayerId playerId, Card c1, Card c2) {
List<Card> cards = new ArrayList<>();
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.
*/
public void nextPlayer() {
int start = currentPlayerIndex;
do {
currentPlayerIndex = (currentPlayerIndex + 1) % playerOrder.size();
Player p = getCurrentPlayer();
if (!p.isFolded() && !p.isAllIn()) {
return;
}
} while (currentPlayerIndex != start);
}
// Dealer Rotation
/**
* 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
/**
* 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.
*/
public void startNewHand() {
handActive = true;
phase = GamePhase.PREFLOP;
pot = new Pot();
resetBets();
playerBetCommitments.clear();
resetCommunityCards();
holeCards.clear();
for (Player player : players.values()) {
player.setFolded(false);
}
deck = new Deck();
deck.shuffle();
currentPlayerIndex = (dealerIndex + 1) % playerOrder.size();
}
/**
* 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();
}
}
@@ -0,0 +1,42 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state;
/**
* The Pot class represents the total amount of chips that players have bet in a poker game. It
* provides methods to add chips to the pot, retrieve the current amount, and reset the pot for a
* new round. This class is essential for managing the betting aspect of the game and ensuring that
* the pot is accurately maintained throughout the game.
*/
public class Pot {
// Disabled because it caused the port to be set to 0
// private int total;
private int amount = 0;
/**
* Adds the specified amount of chips to the pot.
*
* @param chips The number of chips to add to the pot.
*/
public void add(int chips) {
amount += chips;
}
/**
* Retrieves the current amount of chips in the pot.
*
* @return The current amount of chips in the pot.
*/
public int getAmount() {
return amount;
}
// Disabled because it caused the port to be set to 0
// public int getTotal() {
// return total;
// }
/** Resets the pot to zero, typically used at the end of a round or when starting a new game. */
public void reset() {
amount = 0;
}
}
@@ -0,0 +1,150 @@
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state;
/**
* The TableState class represents the current state of the poker table during a game. It keeps
* track of the current bet, the last raise size, the minimum raise amount, and the big blind.
* Additionally, it manages the betting status, including whether betting is currently open and if
* it can be reopened after being closed. The class also tracks the last aggressor's ID to determine
* who made the most recent aggressive action (like a raise) in the betting round.
*/
public class TableState {
private int currentBet;
private int lastRaiseSize;
private int minRaise;
private int bigBlind;
private boolean bettingOpen;
private boolean canReopenBetting;
private String lastAggressorId;
/**
* Retrieves the current bet amount for the table. This value represents the highest bet that
* has been placed in the current betting round and is used to determine how much players need
* to call or raise to stay in the hand.
*
* @return The current bet amount for the table.
*/
public int getCurrentBet() {
return currentBet;
}
/**
* Sets the current bet amount for the table. This method is typically called when a player
* places a bet or raises, updating the current bet to reflect the new amount.
*
* @param currentBet The new current bet amount to set for the table.
*/
public void setCurrentBet(int currentBet) {
this.currentBet = currentBet;
}
/**
* Returns the big blind amount. The big blind serves as a baseline for betting and is often
* used to determine the minimum bet or raise.
*
* @return The big blind amount.
*/
public int getBigBlind() {
return bigBlind;
}
/**
* Sets the big blind amount for the game. The big blind is a forced bet that players must post
* before the cards are dealt, and it serves as a baseline for betting in the game.
*
* @param bigBlind The amount to set as the big blind for the game.
*/
public void setBigBlind(int bigBlind) {
this.bigBlind = bigBlind;
}
/**
* Retrieves the minimum raise amount for the current betting round. The minimum raise is
* typically determined by the size of the last raise or the big blind, ensuring that players
* must raise by at least a certain amount.
*
* @return The minimum raise amount for the current betting round.
*/
public int getMinRaise() {
return minRaise;
}
/**
* Sets the minimum raise amount for the current betting round. This method is typically called
* after a raise is made, updating the minimum raise to ensure that subsequent raises meet the
* required amount.
*
* @param minRaise The new minimum raise amount to set for the current betting round.
*/
public void setMinRaise(int minRaise) {
this.minRaise = minRaise;
}
/**
* Checks if betting is currently open at the table. This status indicates whether players are
* allowed to place bets or raises during the current betting round.
*
* @return true if betting is open, false otherwise.
*/
public boolean isBettingOpen() {
return bettingOpen;
}
/**
* Sets the betting status for the table. This method can be used to open or close betting
* during a betting round, controlling whether players can place bets or raises.
*
* @param bettingOpen The new betting status to set for the table (true for open, false for
* closed).
*/
public void setBettingOpen(boolean bettingOpen) {
this.bettingOpen = bettingOpen;
}
/**
* Checks if betting can be reopened after being closed. This status allows for scenarios where
* betting may be temporarily closed (e.g., after a raise) but can be reopened to allow other
* players to respond.
*
* @return true if betting can be reopened, false otherwise.
*/
public boolean canReopenBetting() {
return canReopenBetting;
}
/**
* Sets whether betting can be reopened after being closed. This method is typically called
* after a raise is made, allowing for the possibility of reopening betting to let other players
* respond to the raise.
*
* @param canReopenBetting The new status indicating whether betting can be reopened (true or
* false).
*/
public void setCanReopenBetting(boolean canReopenBetting) {
this.canReopenBetting = canReopenBetting;
}
/**
* Retrieves the ID of the last aggressor in the current betting round. The last aggressor is
* the player who made the most recent aggressive action (like a raise) and is important for
* determining the flow of betting and who may have the opportunity to respond.
*
* @return The ID of the last aggressor in the current betting round.
*/
public String getLastAggressorId() {
return lastAggressorId;
}
/**
* Sets the ID of the last aggressor in the current betting round. This method is typically
* called after a player makes an aggressive action (like a raise), updating the last aggressor
* ID to reflect the most recent aggressive action.
*
* @param lastAggressorId The new ID of the last aggressor to set for the current betting round.
*/
public void setLastAggressorId(String lastAggressorId) {
this.lastAggressorId = lastAggressorId;
}
}
@@ -86,15 +86,38 @@ public class UserRegistry {
}
/**
* Looks up a user by their session ID.
* Looks up a user by their {@link SessionId}.
*
* @param sessionId the session ID to look up
* @return an Optional containing the user, or empty if no user is associated with this session
* @param sessionId the SessionId to look up
* @return an Optional containing the {@link User}, or empty if no user is associated with this
* SessionId
*/
public Optional<User> findBySessionId(SessionId sessionId) {
public Optional<User> getBySessionId(SessionId sessionId) {
return Optional.ofNullable(bySessionId.get(sessionId));
}
/**
* Looks up a user by their {@link UserId}.
*
* @param userId the UserId to look up
* @return an Optional containing the {@link User}, or empty if no user is associated with this
* UserId
*/
public Optional<User> getByUserId(UserId userId) {
return Optional.ofNullable(byId.get(userId));
}
/**
* Looks up a user by their username.
*
* @param username the username to look up
* @return an Optional containing the {@link User}, or empty if no user is associated with this
* username
*/
public Optional<User> getByUsername(String username) {
return Optional.ofNullable(byName.get(username));
}
/**
* Returns all currently registered users.
*
@@ -0,0 +1,7 @@
package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher;
public class ResponseDispatchException extends RuntimeException {
public ResponseDispatchException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -27,12 +27,17 @@ public class ResponseDispatcher {
* the target session's response queue.
*
* @param response the response to dispatch
* @throws InterruptedException if the thread is interrupted while waiting to enqueue the
* primitive response
* @throws ResponseDispatchException wraps any exceptions that occur during dispatching, such as
* the {@link InterruptedException}
*/
public void dispatch(Response response) throws InterruptedException {
public void dispatch(Response response) {
PrimitiveResponse primitiveResponse = ResponseEncoder.encode(response);
Session session = sessionManager.getSessionById(response.getSessionId());
session.getResponseQueue().put(primitiveResponse);
try {
session.getResponseQueue().put(primitiveResponse);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ResponseDispatchException("Interrupted while dispatching response", e);
}
}
}
@@ -12,8 +12,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Primitive
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RawRequest;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.MissingParameterException;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.PrimitiveResponse;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatchException;
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseEncoder;
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
@@ -88,6 +90,19 @@ public class SessionReader implements Runnable {
"UNKNOWN_COMMAND",
"This command is unknown to the server."));
} catch (ResponseDispatchException e) {
logger.error(
"Unexpected ResponseDispatchException exception while dispatching request",
e);
} catch (MissingParameterException e) {
logger.error(
"Recieved request for command '{}' was missing the '{}' parameter",
rawRequest.command(),
e.getParameterKey());
sendErrorResponse(
new ErrorResponse(requestContext, "MISSING_PARAMETER", e.getMessage()));
} catch (IOException e) {
logger.error("Unexpected IO exception while reading from transport", e);
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Some files were not shown because too many files have changed in this diff Show More