Add: rule system for game engine

This commit is contained in:
Julian Kropff
2026-04-04 23:38:19 +02:00
parent a20ba7cef5
commit 769105b87d
13 changed files with 547 additions and 0 deletions
@@ -0,0 +1,15 @@
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,42 @@
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,20 @@
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,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.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,42 @@
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,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.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,42 @@
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,32 @@
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,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.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,44 @@
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,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.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,48 @@
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,101 @@
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();
}
}