Feat/Game Engine #233
@@ -0,0 +1,6 @@
|
||||
# Casono Game Engine
|
||||
|
||||
- [Casono TDA Rules, Version 1.0 (2024)](casono-tda-rules-version-1-2024.md)
|
||||
- [Game Engine Architecture](game-engine-architecture.md)
|
||||
- [Casono Rules Easy Description](casono-rules-easy-description.md)
|
||||
- [Game engine implementation](game-engine-implementation.md)
|
||||
@@ -0,0 +1,323 @@
|
||||
# Casono Rules Easy Description
|
||||
|
||||
> Source: https://www.youtube.com/watch?v=h-1WyU5Wqsw&pp=ygUZbGVybmVuIHBva2VyIHRleGFzIGhvbGRlbQ%3D%3D
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Deustch](#deustch)
|
||||
* [Blinds (Small Blind & Big Blind)](#blinds-small-blind--big-blind)
|
||||
* [Erste Setzrunde (Preflop)](#erste-setzrunde-preflop)
|
||||
* [Beispiel Preflop](#beispiel-preflop)
|
||||
* [Flop (3 Gemeinschaftskarten)](#flop-3-gemeinschaftskarten)
|
||||
* [Beispiel Flop](#beispiel-flop)
|
||||
* [Turn (4. Karte)](#turn-4-karte)
|
||||
* [River (5. Karte)](#river-5-karte)
|
||||
* [Showdown (Gewinnentscheidung)](#showdown-gewinnentscheidung)
|
||||
* [Poker Hand Rankings (Gewichtung)](#poker-hand-rankings-gewichtung)
|
||||
* [Fazit](#fazit)
|
||||
* [English](#english)
|
||||
* [Blinds (Small Blind & Big Blind)](#blinds-small-blind--big-blind-1)
|
||||
* [First Betting Round (Preflop)](#first-betting-round-preflop)
|
||||
* [Preflop Example](#preflop-example)
|
||||
* [Flop (3 Community Cards)](#flop-3-community-cards)
|
||||
* [Flop Example](#flop-example)
|
||||
* [Turn (4th Card)](#turn-4th-card)
|
||||
* [River (5th Card)](#river-5th-card)
|
||||
* [Showdown (Winning Decision)](#showdown-winning-decision)
|
||||
* [Poker Hand Rankings (Hierarchy)](#poker-hand-rankings-hierarchy)
|
||||
* [Conclusion](#conclusion)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## Deutsch
|
||||
|
||||
Der Pokertisch ist ein unglaublich faszinierender Erlebnisraum, in dem man sehr viel lernen kann: über sich selbst, über andere Menschen und über Fragen wie: wie treffe ich eigentlich Entscheidungen, wie gehe ich mit Stress und Unsicherheit um und wie gut ich darin bin, mich in andere hineinzuversetzen und Situationen richtig einzuschätzen.
|
||||
|
||||
Damit Du in diesem Erlebnisraum starten kannst, ist es – wie bei jedem Spiel – notwendig, zuerst die Grundregeln und den Spielablauf zu verstehen.
|
||||
|
||||
Also los geht es:
|
||||
|
||||
Wir haben am Tisch **4 Spieler**: Julian, Mathis, Jona und Lars. Jeder Spieler startet mit **20.000 Chips**. Jeder bekommt **2 Karten auf die Hand** und es gibt zusätzlich **5 Gemeinschaftskarten**, die später in der Mitte aufgedeckt werden.
|
||||
|
||||

|
||||
|
||||
Die Spieler sitzen in folgender Reihenfolge: Julian, Mathis, Jona und Lars. Einer davon hat den Dealer-Button, der bestimmt, wer die Karten austeilt und von wo die Runde beginnt. Dieser Button wandert nach jeder Runde im Uhrzeigersinn weiter und verändert damit die Position ständig.
|
||||
|
||||
Regel: *34 Button Placement and Movement 🟢*
|
||||
|
||||
### Blinds (Small Blind & Big Blind)
|
||||
|
||||
Bevor die Karten verteilt werden, gibt es zwei Pflicht-Einsätze:
|
||||
|
||||
Der Small Blind und der Big Blind. Der Big Blind ist immer doppelt so hoch wie der Small Blind.
|
||||
|
||||

|
||||
|
||||
Regel: *32 Dead Button 🟡*
|
||||
|
||||
Diese Einsätze sorgen dafür, dass sofort ein Pot entsteht und das Spiel überhaupt beginnt, weil jeder schon “im Spiel” ist.
|
||||
|
||||
Danach werden die Karten verteilt: zuerst Small Blind, dann Big Blind und dann im Uhrzeigersinn alle anderen Spieler.
|
||||
|
||||
### Erste Setzrunde (Preflop)
|
||||
|
||||
Die erste Setzrunde beginnt immer bei dem Spieler links vom Big Blind.
|
||||
|
||||
Jetzt muss jeder Spieler entscheiden:
|
||||
|
||||
* Fold (aussteigen)
|
||||
* Call (mitgehen)
|
||||
* Raise (erhöhen)
|
||||
|
||||
Regel: *40 Methods of Betting 🟢*
|
||||
Regel: *41 Methods of Calling 🟢*
|
||||
Regel: *42 Methods of Raising 🟢*
|
||||
Regel: *50 Acting in Turn 🟢*
|
||||
|
||||
### Beispiel Preflop
|
||||
|
||||
Julian schaut seine Karten an und entscheidet sich direkt für einen Call von **600 Chips**.
|
||||
|
||||

|
||||
|
||||
Mathis sieht seine Karten an und merkt, dass sie nicht gut sind, also foldet er und steigt aus.
|
||||
|
||||

|
||||
|
||||
Jona ist nun dran und entscheidet sich ebenfalls für einen Call, weil seine Hand spielbar ist.
|
||||
|
||||

|
||||
|
||||
Lars schaut seine Karten an, erkennt eine starke Hand und erhöht auf **1200 Chips**.
|
||||
|
||||

|
||||
|
||||
Damit verändert sich sofort die Situation: Julian und Jona müssen entscheiden, ob sie diesen Raise bezahlen, selbst erhöhen oder aussteigen.
|
||||
|
||||
### Flop (3 Gemeinschaftskarten)
|
||||
|
||||
Jetzt werden **3 Gemeinschaftskarten** in die Mitte gelegt. Ab hier verändert sich das Spiel komplett, weil alle Spieler zusätzliche Informationen bekommen.
|
||||
|
||||

|
||||
|
||||
Es beginnt eine neue Setzrunde.
|
||||
|
||||
Regel: *49 Accepted Action 🟢*
|
||||
|
||||
### Beispiel Flop
|
||||
|
||||
Lars setzt **1000 Chips** als Erstes. Julian entscheidet sich mitzugehen (Call), weil seine Karten durch die Gemeinschaftskarten stärker geworden sind.
|
||||
|
||||

|
||||
|
||||
Jona steigt aus, weil er keine gute Verbindung mehr sieht. Mathis ist bereits raus.
|
||||
|
||||
### Turn (4. Karte)
|
||||
|
||||
Jetzt kommt die **4. Gemeinschaftskarte**.
|
||||
|
||||
Wieder beginnt eine neue Setzrunde.
|
||||
|
||||
Lars setzt diesmal **3000 Chips**. Julian bezahlt erneut (Call), weil seine Hand weiterhin gut spielbar ist.
|
||||
|
||||

|
||||
|
||||
Regel: *53 Action Out of Turn 🟡*
|
||||
|
||||
### River (5. Karte)
|
||||
|
||||
Jetzt wird die letzte Gemeinschaftskarte aufgedeckt.
|
||||
|
||||
Dies ist die letzte Entscheidung im Spiel.
|
||||
|
||||
Lars setzt **5000 Chips**.
|
||||
|
||||

|
||||
|
||||
Julian muss jetzt entscheiden: Fold, Call oder Raise auf **10000 Chips**.
|
||||
|
||||
Regel: *54 Pot Size Bets 🟡*
|
||||
|
||||
### Showdown (Gewinnentscheidung)
|
||||
|
||||
Wenn nach der letzten Setzrunde noch zwei Spieler übrig sind, kommt es zum Showdown.
|
||||
|
||||
Beide Spieler zeigen ihre Karten offen. Gewonnen hat die **beste 5-Karten-Kombination aus Handkarten und Gemeinschaftskarten**.
|
||||
|
||||

|
||||
|
||||
Regel: *12 Cards Speak at Showdown 🟢*
|
||||
Regel: *16 Face Up for All-Ins 🟢*
|
||||
Regel: *17 Non All-In Showdowns 🟢*
|
||||
|
||||
Wenn Julian den letzten Einsatz bezahlt, werden die Hände verglichen. Wenn er foldet, gewinnt Lars automatisch den gesamten Pot.
|
||||
|
||||
### Poker Hand Rankings (Gewichtung)
|
||||
|
||||
Die Kartenkombinationen sind klar geordnet – von schwach bis extrem stark:
|
||||
|
||||

|
||||
|
||||
Je höher die Kombination, desto stärker die Hand und desto wahrscheinlicher der Gewinn.
|
||||
|
||||
Julian: 2. Paar:
|
||||
|
||||

|
||||
|
||||
Lars: 1. Paar:
|
||||
|
||||

|
||||
|
||||
Da zwei Paare in der Rangfolge über einem einzelnen Paar stehen, gewinnt Julian diese Runde.
|
||||
|
||||
### Fazit
|
||||
|
||||
Poker ist kein Glücksspiel im klassischen Sinn, sondern ein Spiel aus Strategie, Psychologie und Mathematik. Jede Entscheidung von Julian, Mathis, Jona oder Lars verändert die komplette Dynamik am Tisch. Wer die Regeln versteht, versteht nicht nur Karten, sondern auch Menschen und Entscheidungen unter Druck.
|
||||
|
||||
Regel: *67 One Player One Hand 🟢*
|
||||
Regel: *52 Incorrect Bets 🟡*
|
||||
Regel: *57 Non-Standard Betting 🟡*
|
||||
|
||||
## English
|
||||
|
||||
The poker table is an incredibly fascinating experiential space where you can learn a great deal: about yourself, about other people, and about questions such as: How do I actually make decisions? How do I handle stress and uncertainty? And how good am I at empathizing with others and correctly assessing situations?
|
||||
|
||||
To enter this experiential space, it’s necessary—like with any game—to first understand the basic rules and game flow.
|
||||
|
||||
So let’s begin:
|
||||
|
||||
We have **4 players** at the table: Julian, Mathis, Jona, and Lars. Each player starts with **20,000 chips**. Each receives **2 hole cards**, and there are **5 community cards** that will later be revealed in the center.
|
||||
|
||||

|
||||
|
||||
The players are seated as follows: Julian, Mathis, Jona, and Lars. One of them holds the dealer button, which determines who deals the cards and where the round begins. This button moves clockwise after each hand, constantly changing the positions.
|
||||
|
||||
Rule: *34 Button Placement and Movement 🟢*
|
||||
|
||||
### Blinds (Small Blind & Big Blind)
|
||||
|
||||
Before cards are dealt, there are two mandatory bets:
|
||||
|
||||
The Small Blind and the Big Blind. The Big Blind is always double the amount of the Small Blind.
|
||||
|
||||

|
||||
|
||||
Rule: *32 Dead Button 🟡*
|
||||
|
||||
These forced bets ensure that a pot exists from the start, making the game begin with everyone already involved.
|
||||
|
||||
After this, the cards are dealt: first to the Small Blind, then the Big Blind, and then clockwise to all other players.
|
||||
|
||||
### First Betting Round (Preflop)
|
||||
|
||||
The first betting round always begins with the player to the left of the Big Blind.
|
||||
|
||||
Now each player must decide:
|
||||
|
||||
* Fold (exit the hand)
|
||||
* Call (match the current bet)
|
||||
* Raise (increase the bet)
|
||||
|
||||
Rule: *40 Methods of Betting 🟢*
|
||||
Rule: *41 Methods of Calling 🟢*
|
||||
Rule: *42 Methods of Raising 🟢*
|
||||
Rule: *50 Acting in Turn 🟢*
|
||||
|
||||
### Preflop Example
|
||||
|
||||
Julian looks at his cards and decides to Call for **600 chips**.
|
||||
|
||||

|
||||
|
||||
Mathis sees his cards and realizes they aren’t strong, so he Folds and exits the hand.
|
||||
|
||||

|
||||
|
||||
Jona is next and decides to Call as well, because his hand is playable.
|
||||
|
||||

|
||||
|
||||
Lars looks at his cards, recognizes a strong hand, and Raises to **1200 chips**.
|
||||
|
||||

|
||||
|
||||
This immediately changes the situation: Julian and Jona must now decide whether to call this raise, re-raise, or fold.
|
||||
|
||||
### Flop (3 Community Cards)
|
||||
|
||||
Now **3 community cards** are placed face-up in the center. From this point, the game changes completely, as all players gain additional information.
|
||||
|
||||

|
||||
|
||||
A new betting round begins.
|
||||
|
||||
Rule: *49 Accepted Action 🟢*
|
||||
|
||||
### Flop Example
|
||||
|
||||
Lars bets **1,000 chips** first. Julian decides to Call because his hand has improved with the community cards.
|
||||
|
||||

|
||||
|
||||
Jona folds, as he no longer sees a strong connection. Mathis is already out.
|
||||
|
||||
### Turn (4th Card)
|
||||
|
||||
Now the **4th community card** is revealed.
|
||||
|
||||
Another betting round begins.
|
||||
|
||||
Lars bets **3,000 chips** this time. Julian calls again, as his hand remains strong.
|
||||
|
||||

|
||||
|
||||
Rule: *53 Action Out of Turn 🟡*
|
||||
|
||||
### River (5th Card)
|
||||
|
||||
Now the final community card is revealed.
|
||||
|
||||
This is the last decision point in the hand.
|
||||
|
||||
Lars bets **5,000 chips**.
|
||||
|
||||

|
||||
|
||||
Julian must now decide: Fold, Call, or even Raise to **10,000 chips**.
|
||||
|
||||
Rule: *54 Pot Size Bets 🟡*
|
||||
|
||||
### Showdown (Winning Decision)
|
||||
|
||||
If two or more players remain after the final betting round, a showdown occurs.
|
||||
|
||||
All active players reveal their cards. The winner is the one with the **best 5-card combination using any combination of their hole cards and the community cards**.
|
||||
|
||||

|
||||
|
||||
Rule: *12 Cards Speak at Showdown 🟢*
|
||||
Rule: *16 Face Up for All-Ins 🟢*
|
||||
Rule: *17 Non All-In Showdowns 🟢*
|
||||
|
||||
If Julian calls the final bet, the hands are compared. If he folds, Lars wins the entire pot automatically.
|
||||
|
||||
### Poker Hand Rankings (Hierarchy)
|
||||
|
||||
The card combinations are clearly ranked—from weakest to strongest:
|
||||
|
||||

|
||||
|
||||
The higher the combination, the stronger the hand, and the greater the chance of winning.
|
||||
|
||||
Julian: Two Pair:
|
||||
|
||||

|
||||
|
||||
Lars: One Pair:
|
||||
|
||||

|
||||
|
||||
Since two pair ranks higher than one pair, Julian wins this round.
|
||||
|
||||
### Conclusion
|
||||
|
||||
Poker is not gambling in the traditional sense, but rather a game of strategy, psychology, and mathematics. Every decision made by Julian, Mathis, Jona, or Lars changes the entire dynamic at the table. Understanding the rules means not only understanding cards, but also people and decision-making under pressure.
|
||||
@@ -0,0 +1,740 @@
|
||||
# View Casono TDA Rules, Procedures, & Addendum
|
||||
|
||||
> The following game rules are based exclusively on the official rule set of the Tournament Directors Association (TDA) – Poker TDA Rules, Version 1.0 (2024) – which serves as the primary reference framework for professional Texas Hold’em tournament standards.
|
||||
>
|
||||
> For the game Casono, it is set that all game mechanics, in particular gameplay procedures, betting structures, time handling and core game flow, will be in accordance with the TDA Rules 2024 as closely as possible.
|
||||
>
|
||||
> For implementation purposes, all rules are categorized by priority:
|
||||
> - 🟢 Green rules are mandatory and must be fully implemented as core game logic
|
||||
> - 🟡 Yellow rules are optional extensions that may be implemented if time and resources allow
|
||||
> - 🔴 Red rules are considered non-essential for the core gameplay and may be omitted as they primarily relate to tournament administration, floor decisions or procedural etiquette.
|
||||
>
|
||||
> Individual house rules, gameplay simplifications or project-specific modifications are only permitted within the boundaries of the above priority system, provided they do not conflict with 🟢 Green rules. In case of ambiguity, the original intent of the TDA Rules shall be used as the guiding reference.
|
||||
>
|
||||
> Authoritative source: https://www.pokertda.com/view-poker-tda-rules/
|
||||
|
||||
---
|
||||
|
||||
> 2024 Rules, Version 1.0. Oct 9, 2024
|
||||
> Longform Version Includes: Recommended Procedures and Illustration Addendum
|
||||
> Last updated: 31.03.2026 - 17:00
|
||||
|
||||
---
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [General Concepts](#general-concepts)
|
||||
* [1: Floor Decisions 🔴](#1-floor-decisions-)
|
||||
* [2: Player Responsibilities 🔴](#2-player-responsibilities-)
|
||||
* [3: Official Terminology and Gestures 🔴](#3-official-terminology-and-gestures-)
|
||||
* [4: Player Identity 🔴](#4-player-identity-)
|
||||
* [5: Electronic Devices and Communication 🔴](#5-electronic-devices-and-communication-)
|
||||
* [6: Official Language 🔴](#6-official-language-)
|
||||
* [Seating, Breaking and Balancing Tables](#seating-breaking-and-balancing-tables)
|
||||
* [7: Random Correct Seating 🟡](#7-random-correct-seating-)
|
||||
* [8: Alternates, Late Registration, and Re-Entries 🟡](#8-alternates-late-registration-and-re-entries-)
|
||||
* [9: Special Needs 🔴](#9-special-needs-)
|
||||
* [10: New Players and Players from Broken Tables 🔴](#10-new-players-and-players-from-broken-tables-)
|
||||
* [11: Balancing Tables and Halting Play 🟡](#11-balancing-tables-and-halting-play-)
|
||||
* [Pots / Showdown](#pots--showdown)
|
||||
* [12: Declarations. Cards Speak at Showdown 🟢](#12-declarations-cards-speak-at-showdown-)
|
||||
* [13: Tabling Cards and Killing Winning Hand 🔴](#13-tabling-cards-and-killing-winning-hand-)
|
||||
* [14: Live Cards at Showdown 🔴](#14-live-cards-at-showdown-)
|
||||
* [15: Showdown and Discarding Irregularities 🔴](#15-showdown-and-discarding-irregularities-)
|
||||
* [16: Face Up for All-Ins 🟢](#16-face-up-for-all-ins-)
|
||||
* [17: Non All-In Showdowns and Showdown Order 🟢](#17-non-all-in-showdowns-and-showdown-order-)
|
||||
* [18: Asking to See a Hand 🔴](#18-asking-to-see-a-hand-)
|
||||
* [19: Playing the Board at Showdown 🔴](#19-playing-the-board-at-showdown-)
|
||||
* [20: Awarding Odd Chips 🔴](#20-awarding-odd-chips-)
|
||||
* [21: Side Pots 🟡](#21-side-pots-)
|
||||
* [22: Disputed Hands and Pots 🔴](#22-disputed-hands-and-pots-)
|
||||
* [General Procedures](#general-procedures)
|
||||
* [23: New Hand and New Limits 🟢](#23-new-hand-and-new-limits-)
|
||||
* [24: Chip Race, Scheduled Color Ups 🟡](#24-chip-race-scheduled-color-ups-)
|
||||
* [25: Cards and Chips Kept Visible, Countable, and Manageable. Discretionary Color-Ups 🔴](#25-cards-and-chips-kept-visible-countable-and-manageable-discretionary-color-ups-)
|
||||
* [26: Deck Changes 🔴](#26-deck-changes-)
|
||||
* [27: Re-buys 🔴](#27-re-buys-)
|
||||
* [28: Rabbit Hunting 🔴](#28-rabbit-hunting-)
|
||||
* [29: Calling for a Clock 🟡](#29-calling-for-a-clock-)
|
||||
* [Player Present / Eligible for Hand](#player-present--eligible-for-hand)
|
||||
* [30: At Your Seat and Live Hands 🟢](#30-at-your-seat-and-live-hands-)
|
||||
* [31: At the Table with Action Pending 🔴](#31-at-the-table-with-action-pending-)
|
||||
* [Button / Blinds](#button--blinds)
|
||||
* [32: Dead Button 🟡](#32-dead-button-)
|
||||
* [33: Dodging Blinds 🔴](#33-dodging-blinds-)
|
||||
* [34: Button Placement and Movement 🟢](#34-button-placement-and-movement-)
|
||||
* [Dealing Rules](#dealing-rules)
|
||||
* [35: Misdeals and Fouled Decks 🔴](#35-misdeals-and-fouled-decks-)
|
||||
* [36: Substantial Action (SA) 🟡](#36-substantial-action-sa-)
|
||||
* [37: Button with Too Few Cards 🔴](#37-button-with-too-few-cards-)
|
||||
* [38: Burns After Substantial Action 🔴](#38-burns-after-substantial-action-)
|
||||
* [39: Irregular Flops and Premature-Dealt Cards 🔴](#39-irregular-flops-and-premature-dealt-cards-)
|
||||
* [Play: Bets and Raises](#play-bets-and-raises)
|
||||
* [40: Methods of Betting: Verbal and Chips 🟢](#40-methods-of-betting-verbal-and-chips-)
|
||||
* [41: Methods of Calling 🟢](#41-methods-of-calling-)
|
||||
* [42: Methods of Raising 🟢](#42-methods-of-raising-)
|
||||
* [43: Raise Amounts 🟢](#43-raise-amounts-)
|
||||
* [44: Oversized Chip Betting (Overchips) 🔴](#44-oversized-chip-betting-overchips-)
|
||||
* [45: Multiple Chip Betting 🔴](#45-multiple-chip-betting-)
|
||||
* [46: Prior Bet Chips Not Pulled In 🔴](#46-prior-bet-chips-not-pulled-in-)
|
||||
* [47: Re-Opening the Bet. 🟡](#47-re-opening-the-bet-)
|
||||
* [48: Number of Allowable Raises 🟡](#48-number-of-allowable-raises-)
|
||||
* [49: Accepted Action 🟢](#49-accepted-action-)
|
||||
* [50: Acting in Turn 🟢](#50-acting-in-turn-)
|
||||
* [51: Binding Declarations / Undercalls in Turn 🟡](#51-binding-declarations--undercalls-in-turn-)
|
||||
* [52: Incorrect Bets, Underbets and Underraises 🟡](#52-incorrect-bets-underbets-and-underraises-)
|
||||
* [53: Action Out of Turn (OOT) 🟡](#53-action-out-of-turn-oot-)
|
||||
* [54: Pot Size and Pot-Limit Bets 🟡](#54-pot-size-and-pot-limit-bets-)
|
||||
* [55: Invalid Bet Declarations 🔴](#55-invalid-bet-declarations-)
|
||||
* [56: String Bets and Raises 🟡](#56-string-bets-and-raises-)
|
||||
* [57: Non-Standard and Unclear Betting 🟡](#57-non-standard-and-unclear-betting-)
|
||||
* [58: Non-Standard Folds 🔴](#58-non-standard-folds-)
|
||||
* [59: Conditional and Premature Declarations 🟡](#59-conditional-and-premature-declarations-)
|
||||
* [60: Count of Opponent’s Chip Stack 🟡](#60-count-of-opponents-chip-stack-)
|
||||
* [61: Over-Betting Expecting Change 🔴](#61-over-betting-expecting-change-)
|
||||
* [62: All-In with Chips Found Behind Later 🟡](#62-all-in-with-chips-found-behind-later-)
|
||||
* [Play: Other](#play-other)
|
||||
* [63: Chips Out of View and in Transit 🔴](#63-chips-out-of-view-and-in-transit-)
|
||||
* [64: Lost and Found Chips 🔴](#64-lost-and-found-chips-)
|
||||
* [65: Accidentally Killed / Fouled / Exposed Hands 🟢](#65-accidentally-killed--fouled--exposed-hands-)
|
||||
* [66: Dead Hands and Mucking in Stud 🔴](#66-dead-hands-and-mucking-in-stud-)
|
||||
* [Etiquette and Penalties](#etiquette-and-penalties)
|
||||
* [67: No Disclosure. One Player to a Hand 🟢](#67-no-disclosure-one-player-to-a-hand-)
|
||||
* [68: Exposing Cards and Proper Folding 🔴](#68-exposing-cards-and-proper-folding-)
|
||||
* [69: Ethical Play 🔴](#69-ethical-play-)
|
||||
* [70: Etiquette Violations 🔴](#70-etiquette-violations-)
|
||||
* [71: Warnings, Penalties, and Disqualification 🔴](#71-warnings-penalties-and-disqualification-)
|
||||
* [2024 Recommended Procedures](#2024-recommended-procedures)
|
||||
* [RP-1. All-In Buttons 🔴](#rp-1-all-in-buttons-)
|
||||
* [RP-2. Bringing in Bets is Discouraged 🔴](#rp-2-bringing-in-bets-is-discouraged-)
|
||||
* [RP-3. Personal Belongings 🔴](#rp-3-personal-belongings-)
|
||||
* [RP-4. Disordered Stub 🔴](#rp-4-disordered-stub-)
|
||||
* [RP-5. Prematurely Dealt Cards 🔴](#rp-5-prematurely-dealt-cards-)
|
||||
* [RP-6. Efficient Movement of Players 🔴](#rp-6-efficient-movement-of-players-)
|
||||
* [RP-7. Timing of Dealer Pushes 🔴](#rp-7-timing-of-dealer-pushes-)
|
||||
* [RP-8: Hand for Hand Procedures 🔴](#rp-8-hand-for-hand-procedures-)
|
||||
* [RP-9: Number of Players at Final Table 🔴](#rp-9-number-of-players-at-final-table-)
|
||||
* [RP-10: Tournament Stud Dealing Procedures 🔴](#rp-10-tournament-stud-dealing-procedures-)
|
||||
* [RP-11: Ante Formats and No Ante Reduction 🔴](#rp-11-ante-formats-and-no-ante-reduction-)
|
||||
* [RP-12: Dealers Should Announce Bets and Raises 🔴](#rp-12-dealers-should-announce-bets-and-raises-)
|
||||
* [RP-13: Dealers Should Stack Chips in Split-Pot Games 🔴](#rp-13-dealers-should-stack-chips-in-split-pot-games-)
|
||||
* [RP-14: Randomness May be Applied to Special Situations 🔴](#rp-14-randomness-may-be-applied-to-special-situations-)
|
||||
* [RP-15: Proper Tournament Staff Communication 🔴](#rp-15-proper-tournament-staff-communication-)
|
||||
* [RP-16: Player Absent on a Breaking Table 🔴](#rp-16-player-absent-on-a-breaking-table-)
|
||||
* [RP-17: Tournament Draw Betting Procedures 🔴](#rp-17-tournament-draw-betting-procedures-)
|
||||
* [RP-18: Order of Mixed Games 🔴](#rp-18-order-of-mixed-games-)
|
||||
* [RP-19: Reducing Stalling 🔴](#rp-19-reducing-stalling-)
|
||||
* [RP-20: Cards Ready for Shuffle 🔴](#rp-20-cards-ready-for-shuffle-)
|
||||
* [RP-21: Spreading the Pot 🔴](#rp-21-spreading-the-pot-)
|
||||
* [RP-22: Betting Non-Denominational Items (Bounty chips, clock tokens etc) 🔴](#rp-22-betting-non-denominational-items-bounty-chips-clock-tokens-etc-)
|
||||
* [Illustration Addendum 2024 Rules](#illustration-addendum-2024-rules)
|
||||
* [Rule 10: Breaking Tables, 2-Step Random Process. 🔴](#rule-10-breaking-tables-2-step-random-process-)
|
||||
* [Rule 11-D: Balancing Tables and Halting Play. 🔴](#rule-11-d-balancing-tables-and-halting-play-)
|
||||
* [Rule 16: Face Up for All-Ins. 🔴](#rule-16-face-up-for-all-ins-)
|
||||
* [Rule 18: Asking to See a Hand 🔴](#rule-18-asking-to-see-a-hand-)
|
||||
* [Rule 38: Burns After Substantial Action 🔴](#rule-38-burns-after-substantial-action-)
|
||||
* [Rule 40-A: Methods of Betting, Unclear or Contradictory Bets. 🟢](#rule-40-a-methods-of-betting-unclear-or-contradictory-bets-)
|
||||
* [Rule 43: Raise Amounts. “The largest prior full bet or raise of the current betting round”. 🟢](#rule-43-raise-amounts-the-largest-prior-full-bet-or-raise-of-the-current-betting-round-)
|
||||
* [Rule 45: Multiple Chip Betting. 🟡](#rule-45-multiple-chip-betting-)
|
||||
* [Rule 46: Prior Bet Chips Not Pulled In, situation examples. 🔴](#rule-46-prior-bet-chips-not-pulled-in-situation-examples-)
|
||||
* [Rule 47: Re-opening the bet. 🟡](#rule-47-re-opening-the-bet-)
|
||||
* [Rule 51: Binding Declarations / Undercalls in Turn 🟡](#rule-51-binding-declarations--undercalls-in-turn-)
|
||||
* [Rule 52-B: Incorrect Bet Amounts, Pot-Limit Games 🟡](#rule-52-b-incorrect-bet-amounts-pot-limit-games-)
|
||||
* [Rule 53-A: Action Out of Turn (OOT) 🟡](#rule-53-a-action-out-of-turn-oot-)
|
||||
* [Rule 53-B: Substantial Action Out of Turn (OOT). 🟡](#rule-53-b-substantial-action-out-of-turn-oot-)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## General Concepts
|
||||
|
||||
### 1: Floor Decisions 🔴
|
||||
~~The best interest of the game and fairness are top priorities in decision-making. Unusual circumstances occasionally dictate that common-sense decisions in the interest of fairness take priority over technical rules. Floor decisions are final.~~
|
||||
|
||||
### 2: Player Responsibilities 🔴
|
||||
~~Players should verify registration data and seat assignments, verify they’re dealt the correct number of cards before SA occurs, protect their hands, make their intentions clear, follow the action, act in turn with proper terminology and gestures, defend their right to act, keep cards visible and chips correctly stacked, remain at the table with a live hand, table all cards properly when competing at showdown, speak up if they see a mistake, play in a timely manner, call for a clock when warranted, transfer tables promptly, follow one player to a hand, know and comply with the rules, practice proper etiquette, inform the house if they see or experience discriminatory or offensive behavior, and generally contribute to an orderly event where all players feel welcome.~~
|
||||
|
||||
### 3: Official Terminology and Gestures 🔴
|
||||
~~Official betting terms are simple, unmistakable, time-honored declarations like bet, raise, call, fold, check, all-in, complete, and pot (pot-limit only). Regional terms may also meet this test. Also, players must use gestures with caution when facing action; tapping the table is a check. It is the responsibility of players to make their intentions clear: using non-standard terms or gestures is at player’s risk and may result in a ruling other than what the player intended. See also Rules 2 and 42.~~
|
||||
|
||||
### 4: Player Identity 🔴
|
||||
~~Players must be clearly identifiable at all times. Tournament staff may request a player to remove any item (sunglasses, hood, or other facial covering) which inhibits their identification or is a distraction to other participants.~~
|
||||
|
||||
### 5: Electronic Devices and Communication 🔴
|
||||
- ~~A: Players may not talk on a phone at the table. Ring tones, music, images, video etc. should be inaudible and non-disturbing to others. These and other devices, tools, photography, videography, and communication must not create a nuisance, delay the game or create competitive advantage and are subject to house and gaming regulations.~~
|
||||
|
||||
- ~~B. Phones and other devices may not rest on the table.~~
|
||||
|
||||
- ~~C: Players with live hands may not interact with or operate an electronic or communication device. The definition of such devices may include new technologies and shall be as updated by the TD.~~
|
||||
|
||||
- ~~D: Betting apps, charts, and other poker strategy tools may not be used at the table. Nor may players receive or use poker strategy data from another person or source. Violations of Rule 5 may be subject to penalties in Rule 71.~~
|
||||
|
||||
### 6: Official Language 🔴
|
||||
~~The house will clearly post and announce acceptable language(s) at the table.~~
|
||||
|
||||
## Seating, Breaking and Balancing Tables
|
||||
|
||||
### 7: Random Correct Seating 🟡
|
||||
Tournament and satellite seats will be randomly assigned. A player starting in a wrong seat with a correct chip stack will move to the correct seat with their current total chip stack.
|
||||
|
||||
### 8: Alternates, Late Registration, and Re-Entries 🟡
|
||||
- A: Alternates, players registering late, and re-entries will be sold full stacks. They will randomly draw a seat and table by the same process and from the same seat pool then in place for new players and are dealt in except between the small blind and button.
|
||||
|
||||
- B: In re-entry events, if a player is permitted to forfeit chips and buy a new stack, the forfeited chips will be removed from play.
|
||||
|
||||
### 9: Special Needs 🔴
|
||||
~~Accommodations for players with special needs will be made when possible.~~
|
||||
|
||||
### 10: New Players and Players from Broken Tables 🔴
|
||||
- ~~A: New players entering the tournament and players from broken tables can get any seat including the small or big blind or the button and be dealt in except between the SB and button.~~
|
||||
|
||||
- ~~B: Players from a broken table will be assigned new tables and seats by a 2-step random process. See Illustration Addendum.~~
|
||||
|
||||
### 11: Balancing Tables and Halting Play 🟡
|
||||
- A: To balance in flop and mixed-games, the player to be big blind next moves to the worst position, including single big blind if available, even if that means the seat is big blind twice. Worst position is never the small blind. In stud-only, players move by position (last seat open at the short table is the seat filled).
|
||||
|
||||
- B: In mixed games (ex: HORSE), when the game shifts from hold’em to stud, after the last hold’em hand the button moves to the position it would be if the next hand was hold’em and is frozen there during stud. The player moved in stud is the player who would be big blind if the game were hold’em for that hand. Shifting to hold’em the button starts where it was frozen.
|
||||
|
||||
- C: The table from which a player is moved will be specified by a predetermined procedure.
|
||||
|
||||
- D: Play will halt on tables 3 or more players short (by elimination) than the table with the most players once the blinds are impacted (See Illustration Addendum). Play halts on other formats (ex: 6-hand and turbos) at TDs discretion. TDs may waive halting play and waiver is not a misdeal. As the event progresses, at TD’s discretion tables should be more tightly balanced.
|
||||
|
||||
## Pots / Showdown
|
||||
|
||||
### 12: Declarations. Cards Speak at Showdown 🟢
|
||||
Cards speak to determine the winner. Verbal declarations of hand value are not binding at showdown but deliberately miscalling a hand may be penalized. Dealers should read and announce hand values at showdown. Any player, in the hand or not, should speak up if they think a mistake is made in reading hands or calculating and awarding the pot.
|
||||
|
||||
### 13: Tabling Cards and Killing Winning Hand 🔴
|
||||
- ~~A: Proper tabling is both 1) turning all cards face up on the table and 2) allowing the dealer and players to read the hand clearly. “All cards” means both hole cards in hold’em, all 4 hole cards in Omaha, all 7 cards in 7-stud, etc.~~
|
||||
|
||||
- ~~B: At showdown players must protect their hands while waiting for cards to be read (See also Rule 65). Players who don’t fully table all cards, then muck thinking they’ve won, do so at their own risk. If a hand is not 100% retrievable and identifiable and the TD rules it was not clearly read, the player has no claim to the pot. The TDs decision on whether a hand was sufficiently tabled is final.~~
|
||||
|
||||
- ~~C: Dealers cannot kill a properly tabled hand that was obviously the winner.~~
|
||||
|
||||
### 14: Live Cards at Showdown 🔴
|
||||
~~Discarding non-tabled cards face down does not automatically kill them; players may change their minds and table cards that remain 100% identifiable and retrievable. Cards are killed by the dealer when pushed into the muck or otherwise rendered irretrievable and unidentifiable.~~
|
||||
|
||||
### 15: Showdown and Discarding Irregularities 🔴
|
||||
- ~~A: If a player tables one card that would make a winning hand, the dealer should advise the player to table all cards. If the player refuses, the floor should be called.~~
|
||||
|
||||
- ~~B: If a player bets then discards thinking they have won (forgetting another player is still in the hand), the dealer should hold the cards and call the floor (a Rule 58 exception). If cards are mucked and not retrievable and identifiable to 100% certainty, the player is out and not entitled to a refund of called bets. If cards are mucked and the player initiated a bet or raise not yet called, the uncalled amount will be returned.~~
|
||||
|
||||
### 16: Face Up for All-Ins 🟢
|
||||
All hands will be tabled without delay once a player is all-in and all betting action by all other players in the hand is complete. No player who is either all-in or has called all betting action may muck their hand without tabling. All hands in both the main and side pot(s) must be tabled and are live. See Illustration Addendum.
|
||||
|
||||
### 17: Non All-In Showdowns and Showdown Order 🟢
|
||||
- A: In a non all-in showdown, if cards are not spontaneously tabled or discarded, the TD may enforce an order of show. The last aggressive player on the final betting round (final street) must table first. If there was no final round bet, the player who would act first in a final betting round must table first (i.e. first seat left of the button in flop games, high hand showing in stud, low hand in razz, etc.).
|
||||
|
||||
- B: A non all-in showdown is uncontested if all but one player mucks face down without tabling. The last player with live cards wins and is not required to table the cards.
|
||||
|
||||
### 18: Asking to See a Hand 🔴
|
||||
- ~~A: Players not still in possession of cards at showdown, or who have mucked their cards face down without tabling, lose any rights or privileges to ask to see any hand.~~
|
||||
|
||||
- ~~B: If there was a river bet, any caller has an inalienable right to have the last aggressor’s hand tabled on request (“the hand they paid to see”) provided the caller tabled or retains his or her cards. TDs discretion governs all other requests such as to see the hand of another caller, or if there was no river bet. See Illustration Addendum [adopted 2013].~~
|
||||
|
||||
### 19: Playing the Board at Showdown 🔴
|
||||
~~To play the board, players must table all hole cards to get part of the pot (See Rule 13-A).~~
|
||||
|
||||
### 20: Awarding Odd Chips 🔴
|
||||
~~First, odd chips will be broken into the smallest denomination in play. A) Board games with 2 or more high or low hands: the odd chip goes to the first seat left of the button. B) Stud, razz, and if 2 or more high or low hands in stud/8: the odd chip goes to the high card by suit in the player’s 5-card winning hand. C) H/L split: the odd chip in the total pot goes to the high side. D) Deleted 2022.~~
|
||||
|
||||
### 21: Side Pots 🟡
|
||||
Each side pot will be split separately.
|
||||
|
||||
### 22: Disputed Hands and Pots 🔴
|
||||
~~The reading of a tabled hand may be disputed until the next hand begins (see Rule 23). Accounting errors in calculating and awarding the pot may be disputed until substantial action occurs on the next hand. If a hand finishes during a break, the right to any dispute ends 1 minute after the pot is awarded.~~
|
||||
|
||||
## General Procedures
|
||||
|
||||
### 23: New Hand and New Limits 🟢
|
||||
A new level starts on announcement by the floor or audio signal by the clocking system. The new level applies to the next hand. Hands begin on the first riffle, push of the shuffler button, or on the dealer push. If a hand starts at the prior level by mistake, the hand will continue at the prior level after substantial action occurs (Rule 36). If a new level starts during the dealer push, the incoming dealer will deal one hand at the prior level.
|
||||
|
||||
### 24: Chip Race, Scheduled Color Ups 🟡
|
||||
- A: At scheduled color-ups, chips will be raced off starting in seat 1, with a maximum of one chip awarded to a player. Players can’t be raced out of play: a player losing their last chip(s) in a race will get 1 chip of the lowest denomination still in play.
|
||||
|
||||
- B: Players must have their chips fully visible and are encouraged to witness the chip race.
|
||||
|
||||
- C: If after the race, a player still has chips of a removed denomination, they will be exchanged for current denominations only at equal value. Chips of removed denominations that do not fully total at least the smallest denomination still in play will be removed without compensation.
|
||||
|
||||
### 25: Cards and Chips Kept Visible, Countable, and Manageable. Discretionary Color-Ups 🔴
|
||||
- ~~A: Players, dealers, and the floor are entitled to a reasonable estimation of chip counts; thus, chips should be kept in countable stacks. The TDA recommends clean vertical stacks of 20 same denomination chips each as a standard. Higher denomination chips must be visible and identifiable at all times. If a floor person can’t look at a chip stack and quickly estimate its value, players likely can’t either.~~
|
||||
|
||||
- ~~B: TDs control the number and denominations of chips in play and may color up one or more players at their discretion at any time. Discretionary color ups are to be announced.~~
|
||||
|
||||
- ~~C: Players must keep live hands in plain view at all times.~~
|
||||
|
||||
### 26: Deck Changes 🔴
|
||||
~~Deck changes will be on the dealer push or level changes or as prescribed by the house. Players may not ask for deck changes.~~
|
||||
|
||||
### 27: Re-buys 🔴
|
||||
~~Players may not miss a hand. Players declaring intent to rebuy before a hand are playing chips behind and must make the re-buy.~~
|
||||
|
||||
### 28: Rabbit Hunting 🔴
|
||||
~~Rabbit hunting (revealing cards that would have come if the hand had not ended) is not allowed.~~
|
||||
|
||||
### 29: Calling for a Clock 🟡
|
||||
Players should act in a timely manner to maintain a reasonable pace of the game. If in TD’s judgement reasonable time has passed, they may call the clock or approve a clock request by any player in the event. Players must be at their seats to call for a clock (Rule 30). A player on the clock has up to 25 seconds plus a 5 second countdown to act. If the player faces a bet and time expires, the hand is dead; if not facing a bet, the hand is checked. A tie goes to the player. TDs may adjust the time allowed and take other steps to fit the game and stop persistent delays. See also Rules 2 and 70.
|
||||
|
||||
## Player Present / Eligible for Hand
|
||||
|
||||
### 30: At Your Seat and Live Hands 🟢
|
||||
To have a live hand, players must be at their seats when the last card is dealt to all players on the initial deal. Players not then at their seats may not look at their cards which are killed immediately. Their posted blinds and antes forfeit to the pot and an absent player dealt the stud bring-in card posts the bring-in. “At your seat” means in reach of your chair. This rule is not intended to encourage players to be out of their seats while in a hand.
|
||||
|
||||
### 31: At the Table with Action Pending 🔴
|
||||
~~Players with live hands (including players all-in or otherwise finished betting) must remain at the table for all betting rounds and showdown. Leaving the table is incompatible with protecting your hand and following the action and is subject to penalty.~~
|
||||
|
||||
## Button / Blinds
|
||||
|
||||
### 32: Dead Button 🟡
|
||||
Tournament play will use a dead button.
|
||||
|
||||
### 33: Dodging Blinds 🔴
|
||||
~~Players who intentionally dodge any blind will incur a penalty. See Rule 71-B.~~
|
||||
|
||||
### 34: Button Placement and Movement 🟢
|
||||
- A: If incorrect button movement is discovered before SA occurs, the error will be corrected. However, if SA has occurred, play will continue. Ex: If the button is moved twice and SA occurs the error will stand, the button will not be backed-up on the next hand. All players have a responsibility to monitor button placement and speak up if they see a mistake (Rule 2)
|
||||
|
||||
- B: Heads-up, the small blind is the button, is dealt the last card, and acts first pre-flop and last on all other betting rounds. Starting heads-up play, the button may need to be adjusted to ensure no player has the big blind twice in a row.
|
||||
|
||||
## Dealing Rules
|
||||
|
||||
### 35: Misdeals and Fouled Decks 🔴
|
||||
- ~~A: Misdeals include but are not necessarily limited to: 1) 2 or more boxed cards on the initial deal; 2) first card dealt to the wrong seat; 3) cards dealt to a seat not entitled to a hand; 4) a seat entitled to a hand is dealt out; 5) the wrong number of cards is dealt to any player (except Rule 37); 6) Before SA, a non-standard card for the game type is found (example: jokers, 2-3-4-5 in short deck); 7) In flop games, if 1 of the first 2 cards dealt off the deck or any other 2 downcards are exposed by dealer error. House rules apply for draw games (ex: lowball).~~
|
||||
|
||||
- ~~B: Players may be dealt 2 consecutive cards on the button (see also Rule 37).~~
|
||||
|
||||
- ~~C: In misdeals, the re-deal is an exact re-play: the button doesn’t move, no new players are seated, limits stay the same. Cards are dealt to players who were dealt-in but not at their seats for the original deal and they can play the re-deal (Rule 30). Players on penalty who were originally dealt-in will receive cards then their hands are killed. The original deal and re-deal count as 1 hand for a player on penalty, not 2.~~
|
||||
|
||||
- ~~D: Once substantial action occurs (see Rule 36) a misdeal cannot be declared; the hand must proceed unless the deck is fouled. Non-standard cards found after SA are treated as scraps of paper (exception: fouled decks).~~
|
||||
|
||||
- ~~E: Fouled decks.If 2 or more cards of the same suit and rank are found, the deck is fouled. Other fouled deck conditions may be defined by local gaming regulations and house policy. If a fouled deck is discovered, regardless of SA, play will stop and all bets will be returned. Once a hand concludes, the right to dispute based on a fouled deck ends according to Rule 22.~~
|
||||
|
||||
### 36: Substantial Action (SA) 🟡
|
||||
Substantial Action is either A) any 2 actions in turn, at least one of which puts chips in the pot (i.e. any 2 actions except 2 checks or 2 folds) or B) any combination of 3 actions in turn (check, bet, raise, call, fold). Posted blinds do not count towards SA. See Rules 35-D and 53-B.
|
||||
|
||||
### 37: Button with Too Few Cards 🔴
|
||||
~~A player on the button dealt too few cards should announce it immediately. Missing button cards may be replaced even after substantial action if permitted for the game type. However, if the button acts on a hand with too few cards (by check or bet), the button’s hand is dead.~~
|
||||
|
||||
### 38: Burns After Substantial Action 🔴
|
||||
~~The burn card is to protect the stub, not “preserve card order”. If SA occurs and a hand is killed due to the wrong number of cards, all cards of the killed hand are mucked and randomness applies to further dealing (See also RP-14 Randomness). The stub is treated as a normal stub and one and only one card is burned off the stub for each subsequent street. The burn is always one card per street, never more. See Illustration Addendum.~~
|
||||
|
||||
### 39: Irregular Flops and Premature-Dealt Cards 🔴
|
||||
- ~~A: 4-Card Flops. If the flop has 4 rather than 3 cards, exposed or not, and regardless of whether the door card is presumed known, the floor will be called. The dealer then scrambles the 4 cards face down, the floor randomly selects 1 as the next burn card and the other 3 are the flop (See also RP-14 Randomness).~~
|
||||
|
||||
- ~~B: If there was no burn on a 3-card flop, exposed or not and regardless of whether the door card is presumed known, if no action has occurred, the 3 cards are scrambled face down, one chosen as the burn. The flop will be the other 2 cards plus the next card off the stub. If any action (even one check) has occurred, play proceeds with the initial 3 cards. Only one card is burned for the turn.
|
||||
|
||||
- ~~C: For prematurely dealt cards, see Recommended Procedure 5.~~
|
||||
|
||||
- ~~D: Reshuffling During a Hand. To protect game integrity, anytime the stub must be re-shuffled during the play of a hand, the cards must be shuffled face-down and unexposed. Examples include premature cards (Rule 39 and RP-5), disordered stub (RP-4), extra draw or stud cards (RP-10-H), etc.~~
|
||||
|
||||
## Play: Bets and Raises
|
||||
|
||||
### 40: Methods of Betting: Verbal and Chips 🟢
|
||||
- A: Bets are by verbal declaration and/or pushing out chips. If a player does both, whichever is first defines the bet. If simultaneous, a clear and reasonable verbal declaration takes precedence, otherwise the chips play. In unclear situations or where verbal and chips are contradictory, the TD will determine the bet based on the circumstances and Rule 1. See Illustration Addendum. See also Rule 57.
|
||||
|
||||
- B: Verbal declarations may be general (“call”, “raise”), a specific amount only (“one thousand”) or both (“raise, one thousand”).
|
||||
|
||||
- C: For all betting rules, declaring a specific amount only is the same as silently pushing out an equal amount. Ex: Declaring “two hundred” is the same as silently pushing out 200 in chips.
|
||||
|
||||
### 41: Methods of Calling 🟢
|
||||
Standard and acceptable forms of calling include: A) saying “call”; B) pushing out chips equal to a call; C) silently pushing out an overchip; or D) silently pushing out multiple chips equal to a call under the multi-chip rule (Rule 45). Silently betting chip(s) relatively tiny to the bet (ex: blinds 2k-4k. A bets 50k, B then silently puts out one 1k chip) is non-standard, strongly discouraged, subject to penalty, and will be interpreted at TDs discretion, including being ruled a full call.
|
||||
|
||||
### 42: Methods of Raising 🟢
|
||||
In no-limit or pot-limit, a raise must be made by A) pushing out the full amount in one motion or B) verbally declaring the full amount prior to pushing out chips. It is the responsibility of players to make their intentions clear. Note: 2-motion raises eliminated in 2019.
|
||||
|
||||
### 43: Raise Amounts 🟢
|
||||
- A: A raise must be at least equal to the largest prior full bet or raise of the current betting round. A player who raises 50% or more of the largest prior bet but less than a minimum raise must make a full minimum raise. If less than 50% it is a call unless “raise” is first declared or the player is all-in (Rule 45-B). Declaring an amount or pushing out the same amount of chips is treated the same (Rule 40-C). Ex: NLHE, opening bet is 1000, verbally declaring “Fourteen hundred” or silently pushing out 1400 in chips are both calls unless raise is first declared. See Illustration Addendum.
|
||||
|
||||
- B: Without other clarifying information, declaring raise and an amount is the total bet. Ex: A opens for 2000, B declares “Raise, eight thousand.” The total bet is 8000.
|
||||
|
||||
### 44: Oversized Chip Betting (Overchips) 🔴
|
||||
~~If facing a bet or blind, pushing out a single oversized chip (including your last chip) is a call if raise isn’t first declared. To raise with an overchip you must declare raise before the chip hits the table surface. If raise is declared but no amount is stated, the raise is the maximum allowable for the chip. If not facing a bet, pushing out an overchip silently (no declaration) is a bet of the maximum for the chip.~~
|
||||
|
||||
### 45: Multiple Chip Betting 🔴
|
||||
- ~~A: If facing a bet, unless raise or all-in is declared first, a multiple-chip bet (including a bet of your last chips) is a call if every chip is needed to make the call; i.e. removal of just one of the smallest chips leaves less than the call amount. Ex-1: Player A opens for 400: B raises to 1100 total (a 700 raise), C puts out one 500 and one 1000 chip silently. This is a call because removing the 500 chip leaves less than the 1100 call amount. Ex-2: NLHE 25-50. Post-flop A opens for 1050 and B puts out his last chips (two 1000’s). B calls unless raise or all-in was first declared.~~
|
||||
|
||||
- ~~B: If every chip is not needed to make the call; i.e. removing just one of the smallest chips leaves the call amount or more: 1) if the player has chips remaining, the 50% standard in Rule 43 governs the bet. 2) A bet of a player’s last chip(s) is an all-in bet whether reaching the 50% threshold or not. See Addendum.~~
|
||||
|
||||
### 46: Prior Bet Chips Not Pulled In 🔴
|
||||
- ~~A: To avoid confusion, players with prior-bet chips not yet pulled in who face a raise should verbalize their action before adding chips to the prior bet.~~
|
||||
|
||||
- ~~B: If facing a raise, clearly pulling back a prior bet chip binds a player to call or raise; they may not put the chip(s) back out and fold.~~
|
||||
|
||||
- ~~C: If new chip(s) are added silently and the bet is unclear to the house, the call and raise rules 41-45 apply as follows: 1) If prior chips don’t cover the call AND are either left alone OR fully pulled back, an overchip is a call and multiple new chips are subject to the 50% raise standard (Rule 43). 2) If prior chips are partly pulled back OR if prior chip(s) cover the call, the combined final chip bet is a raise if reaching the 50% standard (Rules 43 and 45), if less it is a call. See Illustration Addendum.~~
|
||||
|
||||
### 47: Re-Opening the Bet. 🟡
|
||||
- A: In no-limit and pot limit, an all-in wager (or cumulative multiple short all-ins) totaling less than a full bet or raise will not reopen betting for players who have already acted and are not facing at least a full bet or raise when the action returns to them. If multiple short all-ins re-open the betting, the minimum raise is always the last full valid bet or raise of the round (See also Rule 43).
|
||||
|
||||
- B: In limit, at least 50% of a full bet or raise is required to re-open betting for players who have already acted. See Illustration Addendum.
|
||||
|
||||
### 48: Number of Allowable Raises 🟡
|
||||
There is no cap on the number of raises in no-limit and pot-limit. In limit play, there is a limit to raises even when heads-up until the event is down to 2 players; the house limit applies.
|
||||
|
||||
### 49: Accepted Action 🟢
|
||||
Poker is a game of alert, continuous observation. It is the caller’s responsibility to determine the correct amount of an opponent’s bet before calling, regardless of what is stated by others. If a caller requests a count but receives incorrect information from a dealer or player, then pushes out that amount or declares call, the caller has accepted the full correct action and is subject to the correct wager or all-in amount. As with all situations, Rule 1 may apply at TD’s discretion. See also RP-12.
|
||||
|
||||
### 50: Acting in Turn 🟢
|
||||
- A: Players must act in turn verbally and/or by pushing out chips. Action in turn is binding and commits chips to the pot that stay in the pot.
|
||||
|
||||
- B: Players must wait for clear bet amounts before acting. Ex: NLHE, A says “raise” (but no amount), and B quickly folds. B should wait to act until A’s raise amount is clear.
|
||||
|
||||
### 51: Binding Declarations / Undercalls in Turn 🟡
|
||||
- A: General verbal declarations in turn (such as “call” or “raise”) commit a player to the full current action. See Illustration Addendum
|
||||
|
||||
- B: A player undercalls by declaring or pushing out less than the call amount without first declaring “call”. An undercall is a mandatory full call if made in turn facing 1) any bet heads-up or 2) the opening bet on any round multi-way. In other situations, TD’s discretion applies. The opening bet is the first chip bet of each betting round (not a check). In blind games the posted BB is the pre-flop opener. All-in buttons reduce undercall frequency (See Recommended Procedure 1). This rule governs when players must make a full call and when, at TDs discretion they may forfeit the amount of the intended undercall and fold (see Illustration Addendum). For underbets and underraises, see Rule 52.
|
||||
|
||||
- C: If two or more undercalls occur in sequence, play backs up to the first undercaller who must correct his or her bet per Rule 51-B. The TD will determine how to treat hands of the remaining bettors based on the circumstances.
|
||||
|
||||
### 52: Incorrect Bets, Underbets and Underraises 🟡
|
||||
- A: In limit and no-limit, opening or raising less than the minimum legal amount is corrected anywhere on the current street (if on the river any time before showdown starts). Ex: NLHE 100-200, post-flop A opens for 600 and B raises to 1000 (a 200 underraise). C and D call, E folds then the error is noticed. Increase the bet to 1200 total for all bettors any time before the turn is dealt. After the turn the error stands. For undercalls, see Rule 51.
|
||||
|
||||
- B: In pot limit, if a player underbets the pot based on an inaccurate count, if the pot count is too high (an illegal bet), it will be corrected for all players anywhere on the current street; if too low, corrected until substantial action occurs after the bet. See Illustration Addendum.
|
||||
|
||||
### 53: Action Out of Turn (OOT) 🟡
|
||||
- A: Any action out of turn (check, call, or raise) will be backed up to the correct player in order. The OOT action is subject to penalty and is binding if action to the OOT player does not change. A check, call or fold by the correct player does not change action. If action changes, the OOT action is not binding; any bet or raise is returned to the OOT player who has all options: call, raise, or fold. An OOT fold is binding. See Illustration Addendum.
|
||||
|
||||
- B: Players skipped by OOT action must defend their right to act. If a skipped player had reasonable time and does not speak up before substantial action (Rule 36) OOT occurs after the player, the OOT action is binding. Action backs up and the floor will rule on how to treat the skipped hand given the circumstances, including ruling the hand dead or limiting the player to non-aggressive action. See Addendum.
|
||||
|
||||
### 54: Pot Size and Pot-Limit Bets 🟡
|
||||
- A: Players are entitled to a pot count in pot-limit only. Dealers will not count the pot in limit and no-limit. See also RP-22 Spreading the Pot
|
||||
|
||||
- B: Pre-flop a dead or short all-in blind will not affect pot calculation. All pre-flop pot and re-pot bets will assume full blinds were posted. Ex 1: PLO, 100-200 blinds, dead SB, BB posts 200. Ex 2: SB posts 100, BB short posts 100. In both examples the pot-limit bet for first player to act is 700.
|
||||
|
||||
- C: Post-flop, bets are based on actual pot size.
|
||||
|
||||
- D: Declaring “I bet the pot” is not a valid bet in no-limit but it does bind the player to making a valid bet (at least a minimum bet) and may be subject to penalty. Players facing a bet must make a valid raise.
|
||||
|
||||
### 55: Invalid Bet Declarations 🔴
|
||||
~~If a player faces no bet and: A) declares “call”, it is a check; B) declares “raise”, the player must make at least a minimum bet. A player declaring “check” when facing a bet may call or fold, but cannot raise.~~
|
||||
|
||||
### 56: String Bets and Raises 🟡
|
||||
String bets and raises are not allowed. Such wagers involve multiple movements whereby a player puts out a bet then returns to their stack for more chips to add to the bet.
|
||||
|
||||
### 57: Non-Standard and Unclear Betting 🟡
|
||||
Players use unofficial betting terms and gestures at their own risk. These may be interpreted to mean other than what the player intended. Also, if a declared bet can legally have multiple meanings, it will be ruled the highest reasonable amount that is less than or equal to the pot size* before the bet. Ex: NLHE 200-400, the pot totals less than 5000, player declares “I bet five.” With no other clarifying information, the bet is 500; if the pot totals 5000 or more, the bet is 5000. *The pot is the total of all prior bets including any bets in front of a player not yet pulled in. See Rules 2, 3, 40 and 42.
|
||||
|
||||
### 58: Non-Standard Folds 🔴
|
||||
~~Any time before the end of the final betting round, folding in turn if there’s no bet to you (ex: facing a check or first to act post-flop) or folding out of turn are binding folds subject to penalty. See also 15-B.~~
|
||||
|
||||
### 59: Conditional and Premature Declarations 🟡
|
||||
- A: Conditional statements of future action are non-standard and strongly discouraged. At TDs discretion they may be binding and/or penalized. Example: “if – then” statements such as “If you bet, I will raise.”
|
||||
|
||||
- B: If Player A declares “bet” or “raise” and B calls before A’s exact bet amount is known, the TD will rule the bet as best fits the situation including possibly obliging B to call any amount.
|
||||
|
||||
### 60: Count of Opponent’s Chip Stack 🟡
|
||||
Players, dealers, and the floor are entitled to a reasonable estimation of opponents’ chip stacks (Rule 25). A player may request a more precise count only if facing an all-in bet and it is his or her turn to act. The all-in player is not required to count; on request the dealer or floor will count it. Accepted action applies (Rule 49). Visible and countable chip stacks (Rule 25) greatly improve counting accuracy.
|
||||
|
||||
### 61: Over-Betting Expecting Change 🔴
|
||||
~~Betting should not be used to obtain change. Pushing out more than the intended bet can confuse everyone at the table. All chips pushed out silently are at risk of being counted in the bet. Ex: the opening bet is 325 to player A who silently puts out 525 (one 500 and one 25), expecting 200 change. This is a raise to 650 under the multiple chip rule (Rule 45).~~
|
||||
|
||||
### 62: All-In with Chips Found Behind Later 🟡
|
||||
If A bets all-in and a hidden chip is found behind after a player calls, the TD will determine if the chip behind is part of accepted action (Rule 49). If not part of the action, A is not paid off for the chip(s) if he or she wins. If A loses, he or she is not saved by the chip(s) and the TD may award the chip(s) to the winning caller.
|
||||
|
||||
## Play: Other
|
||||
|
||||
### 63: Chips Out of View and in Transit 🔴
|
||||
~~Players may not hold or transport chips in a way that takes them out of view. A player who does so will forfeit the chips and may be disqualified. The forfeited chips will be taken out of play. The TDA recommends the house provide racks or bags to transport chips when needed.~~
|
||||
|
||||
### 64: Lost and Found Chips 🔴
|
||||
~~Lost and found chips for which ownership cannot be determined will be taken out of play and returned to tournament inventory.~~
|
||||
|
||||
### 65: Accidentally Killed / Fouled / Exposed Hands 🟢
|
||||
- A: Players must protect their hands at all times, including at showdown while waiting for hands to be read. If the dealer kills a hand by mistake or if in TDs judgement a hand is fouled and cannot be identified to 100% certainty, the player has no redress and is not entitled to a refund of called bets. If the player initiated a bet or raise and hasn’t been called, the uncalled amount will be returned.
|
||||
|
||||
- B: If a hand is fouled but can be identified, it remains in play despite any cards exposed.
|
||||
|
||||
### 66: Dead Hands and Mucking in Stud 🔴
|
||||
~~In stud poker, if a player picks up the upcards while facing action, the hand is dead. Proper mucking in stud is turning down all up cards and pushing them all forward face down.~~
|
||||
|
||||
## Etiquette and Penalties
|
||||
|
||||
### 67: No Disclosure. One Player to a Hand 🟢
|
||||
Players must protect other players in the tournament at all times. Therefore players, whether in the hand or not, must not:
|
||||
1. Discuss contents of live or mucked hands,
|
||||
2. Advise or criticize play at any time,
|
||||
3. Read a hand that hasn’t been tabled.
|
||||
One-player-to-a-hand is in effect. Among other things, this rule prohibits showing a hand to or discussing strategy with another player, advisor, or spectator.
|
||||
|
||||
### 68: Exposing Cards and Proper Folding 🔴
|
||||
~~Exposing cards with action pending, including the current player when last to act, may result in a penalty but not a dead hand. Any penalty begins at the end of the hand. When folding, cards should be pushed forward low to the table, not deliberately exposed or tossed high (“helicoptered”). See Rule 66.~~
|
||||
|
||||
### 69: Ethical Play 🔴
|
||||
~~Poker is an individual game. Soft play will result in penalties, which may include chip forfeiture and/or disqualification. Chip dumping and other forms of collusion will result in disqualification.~~
|
||||
|
||||
### 70: Etiquette Violations 🔴
|
||||
~~Etiquette violations are subject to enforcement actions in Rule 71. Examples include but are not limited to: persistent delay of the game, unnecessarily touching another player’s person, cards or chips, repeatedly acting out of turn, maintaining poor card or chip visibility and countability, betting out of reach of the dealer, abusive conduct, offensive hygiene, and excessive chatter.~~
|
||||
|
||||
### 71: Warnings, Penalties, and Disqualification 🔴
|
||||
- ~~A: Enforcement options include but are not limited to verbal warnings, one or more “missed hand” or “missed round” penalties, and disqualification. For missed rounds, the offender will miss one hand for every player (including him or her) at the table when the penalty is given multiplied by the number of penalty rounds. Repeat infractions are subject to escalating penalties. Players away from the table or on penalty may be anted or blinded out of a tournament.~~
|
||||
|
||||
- ~~B: A penalty may be invoked for etiquette violations (Rule 70), card exposure with action pending, throwing cards, violating one-player-to-a-hand, improper use of devices or strategy tools (Rule 5), or similar incidents. Penalties will be given for soft play, abuse, disruptive behavior, dodging blinds or cheating. Checking the exclusive nuts when last to act on the river is not an automatic soft play violation; TD’s discretion applies based on the situation.~~
|
||||
|
||||
- ~~C: Players on penalty must be away from the table. Cards are dealt to their seats, their blinds and antes posted, their hands are killed after the initial deal, and if dealt the stud bring-in they must post the bring-in.~~
|
||||
|
||||
- ~~D: Chips of a disqualified player shall be removed from play.~~
|
||||
|
||||
## 2024 Recommended Procedures
|
||||
|
||||
> Version 1.0, October, 2024
|
||||
|
||||
TDA Recommended Procedures are policy suggestions to reduce errors and improve event management. They also may apply to situations with too many variations to address in one universal rule. The fairest ruling in these cases may require use of multiple rules, evaluation of all circumstances, and reliance on Rule 1 as a primary guide.
|
||||
|
||||
### RP-1. All-In Buttons 🔴
|
||||
~~All-in buttons clearly indicate a player is “all-in.” The dealer should keep the buttons (not each player). When a player bets all-in, the dealer places an all-in button in front of the player, in full view of the rest of the table.~~
|
||||
|
||||
### RP-2. Bringing in Bets is Discouraged 🔴
|
||||
~~Routinely bringing in chips as betting and raising proceeds around the table is poor dealing practice. Reducing bet stacks can influence action, create confusion and increase errors. Only the player currently facing action may ask the dealer to bring-in bets.~~
|
||||
|
||||
### RP-3. Personal Belongings 🔴
|
||||
~~The table surface is vital for chip stack management, dealing, and betting. The table and nearby spaces (legroom and walkways) must not be cluttered by non-essential personal items. Each cardroom should clearly display its policy on items allowed in the tournament area.~~
|
||||
|
||||
### RP-4. Disordered Stub 🔴
|
||||
~~When cards remain to be dealt on a hand and the stub is accidentally dropped and appears to be disordered: 1) first try to reconstruct the stub in its original order if possible; 2) If not possible, create a new stub using only the stub cards (not the muck and prior burns). These should be scrambled, shuffled, cut, and play proceeds with the new stub; 3) If when dropped the stub is mixed in with the muck and/or burns, then scramble the mixed cards together, shuffle, and cut. Play proceeds with the new stub.~~
|
||||
|
||||
### RP-5. Prematurely Dealt Cards 🔴
|
||||
~~Board and burn cards are sometimes dealt prematurely, before action on the preceding round is finished. The general procedures for these situations are:~~
|
||||
|
||||
- ~~A: Premature flop, leave the flop burn card as the burn. Return the premature board cards to the deck stub and reshuffle the entire stub. Re-deal the flop (without another burn) from the newly shuffled stub.~~
|
||||
|
||||
- ~~B: A premature turn card: leave the turn burn card as the burn. Return the premature turn card to the deck stub and reshuffle the entire stub. Re-deal the turn (without another burn) from the newly shuffled stub~~
|
||||
|
||||
- ~~C: A premature river card: leave the river burn card as the burn. Return the premature river card to the deck stub and reshuffle the entire stub. Re-deal the river (without another burn) from the newly shuffled stub~~
|
||||
|
||||
- ~~D: Premature card in stud: the premature card is returned to the stub, the stub is re-shuffled (See RP-17, reshuffling), and a new street is dealt from the newly shuffled stub without another burn.~~
|
||||
|
||||
### RP-6. Efficient Movement of Players 🔴
|
||||
~~Moving players for breaking and balancing should be expeditious so as not to unduly miss blinds or otherwise delay the game. If possible, players should have racks for chip transport and sufficient color-ups should be done so players do not carry unusually large numbers of chips (see Rules 10, 11 and 63).~~
|
||||
|
||||
### RP-7. Timing of Dealer Pushes 🔴
|
||||
~~The TDA recommends that dealers hold up the push 90 seconds prior to a scheduled break or a level change. This avoids having time expire in crucial stages of the game.~~
|
||||
|
||||
### RP-8: Hand for Hand Procedures 🔴
|
||||
- ~~A: Payoff eligibility starts at the announcement: “finish the current hand you’re on then hold up, we are going hand for hand”. If enough players bust on the current hand to break into the money, the busting players will be eligible for a share of the place(s) paid on the current hand. Example: NLHE tournament paying 50 players. 52 players remain when the announcement is made and during the current hand 3 players bust. All 3 players will share in the 50th place payout.~~
|
||||
|
||||
- ~~B: During H4H play, a maximum of 3 minutes per hand will be deducted from the clock.~~
|
||||
|
||||
- ~~C: So that players can most clearly know the timing of level changes, whenever possible the clock should be reduced by 2-minutes each hand not after “batches” of multiple hands.~~
|
||||
|
||||
- ~~D: Blinds continue to increase as time elapses off the clock at the rate of 2 minutes per hand and new levels are reached.~~
|
||||
|
||||
- ~~E: Players are encouraged but not required to remain seated during H4H play.~~
|
||||
|
||||
- ~~F: In the event of an all-in and call during H4H, the cards of all players in the hand should remain face down. Dealers should not deal additional cards until instructed.~~
|
||||
|
||||
### RP-9: Number of Players at Final Table 🔴
|
||||
~~9 and 8-handed events will combine from two tables of five players each to a 9-handed final table. 7 and 6-handed events will combine from two tables of four players each to a 7-handed final table.~~
|
||||
|
||||
### RP-10: Tournament Stud Dealing Procedures 🔴
|
||||
- ~~A: A downcard exposed on the initial deal will be the player’s upcard and 3rd street will be dealt down to that player. The player can be the bring-in.~~
|
||||
|
||||
- ~~B: A card exposed by the dealer on 7th street will be replaced if betting action remains on the hand. 7th street should be dealt down even if no betting action remains on the hand and in all-in situations the player(s) not at risk expose first.~~
|
||||
|
||||
- ~~C: Cards of a player not at his or her seat (See Rule 30) for the deal will be killed. No cards will be dealt to a hand on 4th street that is not live.~~
|
||||
|
||||
- ~~D: If there are two or more matching high hands showing in Stud (or Stud-8) or low hands in Razz, betting starts on the hand with the high card by suit in both games.~~
|
||||
|
||||
- ~~E: If the player dealt the low card by suit is all-in for the ante, betting starts to his or her left. Players with chips must bet at least the bring-in or fold.~~
|
||||
|
||||
- ~~F: Bets will not be doubled on 4th street for a pair showing.~~
|
||||
|
||||
- ~~G: For premature cards dealt in stud see RP-5-D.~~
|
||||
|
||||
- ~~H: 7th street short stub procedure. If before dealing 7th street the number of cards in the current stub is less than the “required number” (# remaining players + burn card + undealt last card) proceed as follows: A) if the required number can be reached by adding the 3 prior burn cards (for 4th, 5th, and 6th street) the current stub will be scrambled with the prior burns to create a new stub. The new stub will be cut, a card burned, and one card dealt to each player. B) if there are at least 3 cards in the current stub but adding the prior burns would not reach the required number, the dealer will burn the top card of the current stub and deal the next card as a community card in the center of the table. C) if the current stub has less than 3 cards, it will be scrambled with the 3 prior burns for a new stub which will then be cut, a card burned, and the next card dealt as a community card. D) If a community card is in play, the first player who would act on 6th street will be first to act on 7th street.~~
|
||||
|
||||
### RP-11: Ante Formats and No Ante Reduction 🔴
|
||||
~~If a single-payer ante is used, the big blind ante format (BBA) with big-blind-first calculation is recommended. Antes should not be reduced (including at the final table) as play progresses in the event.~~
|
||||
|
||||
### RP-12: Dealers Should Announce Bets and Raises 🔴
|
||||
~~Dealers should routinely announce non-all-in bet values as betting proceeds around the table. All-in bets will be counted only on request of the player currently facing action. Accepted action continues to apply (Rule 49). Scheduled and discretionary color-ups improve bet countability.~~
|
||||
|
||||
### RP-13: Dealers Should Stack Chips in Split-Pot Games 🔴
|
||||
~~Where possible, dealers should periodically stack pot chips in split-pot games. Stacking chips should not obscure players’ view or otherwise disrupt the game.~~
|
||||
|
||||
### RP-14: Randomness May be Applied to Special Situations 🔴
|
||||
~~For error remedies not otherwise covered in the TDA Rules and Procedures, TDs may use the concept of randomness to design a solution.~~
|
||||
|
||||
### RP-15: Proper Tournament Staff Communication 🔴
|
||||
- ~~A: Outgoing dealers should inform incoming dealers of pertinent information regarding the table. Examples include: blind information, players on warning or penalties, disruptive behavior.~~
|
||||
|
||||
- ~~B: The dealer should inform the floor of all existing and potential infractions of Rule 2 (Player Responsibilities) and Rule 70 (Etiquette). Special emphasis on any discriminatory or offensive behavior in general or towards specific players or staff.~~
|
||||
|
||||
### RP-16: Player Absent on a Breaking Table 🔴
|
||||
~~If a player is not present during breaking of a table, their chips should be moved to the new table by a staff member.~~
|
||||
|
||||
### RP-17: Tournament Draw Betting Procedures 🔴
|
||||
~~Limping is allowed in all single-draw games.~~
|
||||
|
||||
### RP-18: Order of Mixed Games 🔴
|
||||
~~In order to reduce errors, in mixed game events (ex HORSE), stud and stud-8 need not be played consecutively.~~
|
||||
|
||||
### RP-19: Reducing Stalling 🔴
|
||||
~~The house should clearly announce intention to reduce stalling so that players understand timely play is expected. It’s recommended that each house establish creative methods for reducing stalling. Some methods successfully used by TDA member houses include:
|
||||
Random table breaks instead of table draws, using fixed # of hands per level, going orbit for orbit, soft hand for hand, and adding a shot clock~~
|
||||
|
||||
### RP-20: Cards Ready for Shuffle 🔴
|
||||
~~At the start of the tournament of ending of a break, within one minute of starting or resuming play, the floor should announce “dealers prepare your decks”. When at least 2 players are at the table, the dealer will wash and square the deck, to be ready for shuffle when the level starts.~~
|
||||
|
||||
### RP-21: Spreading the Pot 🔴
|
||||
~~The pot will only be counted in pot-limit events. On request the pot may be spread to increase chip visibility. See also Rule 54: Pot Size and Pot-Limit Bets.~~
|
||||
|
||||
### RP-22: Betting Non-Denominational Items (Bounty chips, clock tokens etc) 🔴
|
||||
~~Action items with no nominal value (bounty chips, clock tokens etc) should be of different size than standard betting chips. Betting with these items will be interpreted per house policy or Rule 1 and may be ruled a call or all-in at TDs discretion.~~
|
||||
|
||||
## Illustration Addendum 2024 Rules
|
||||
|
||||
> Version 1.0, October, 2024
|
||||
|
||||
The Poker TDA is a voluntary poker industry association founded in 2001. The TDA mission is to increase global uniformity of poker tournament rules. TDA Rules supplement the rules of this house. In case of conflict with a gaming agency, the agency rules apply.
|
||||
|
||||
### Rule 10: Breaking Tables, 2-Step Random Process. 🔴
|
||||
~~A 2-step random or “double-blind” process assures that there is no favoritism in distributing new seat assignments. An example of one such process: 1) show players at the breaking table the new seat cards then scramble the cards face down and form a stack; 2) the dealer then deals one playing card face up to each player. The seat cards are then dealt out with the first seat card going to the player with the highest playing card by suit showing.~~
|
||||
|
||||
### Rule 11-D: Balancing Tables and Halting Play. 🔴
|
||||
|
||||
- ~~Example: NLHE 9-handed, table A has 5 players, table B has the most players with 8. Play halts on table A once the BB hits an open seat.~~
|
||||
|
||||
### Rule 16: Face Up for All-Ins. 🔴
|
||||
~~“All hands will be tabled without delay once a player is all-in and all betting action by all other players in the hand is complete”. This rule means that all downcards of all players will be turned up at once when at least one player is all-in and there is no chance of further betting action by the other player(s). Do not wait for the showdown to turn the cards up; do not wait for side pots to be divided before turning up the all-in who is only in for the main pot; if betting action is finalized on any street prior to the showdown, turn the cards up at that point and then run out the remaining cards.~~
|
||||
|
||||
- ~~Example 1. NLHE. Two players remain. On the turn, Player A (the shorter stack) pushes all-in and is called by B. Turn both A and B’s downcards up at this point, then burn and turn the river and proceed to showdown.~~
|
||||
|
||||
- ~~Example 2. NLHE. Three players remain.
|
||||
Pre-flop, Player A (the shortest stack) pushes all-in and is called by both B and C. Do not turn cards up yet because B and C both have chips so further betting action is possible.~~
|
||||
|
||||
~~On the flop B and C check; betting is still possible so don’t turn the cards up yet.~~
|
||||
|
||||
~~On the turn B pushes all-in and C calls. Turn all hands up now (A, B, and C) because no further betting is possible. Burn and turn the river then proceed to showdown. Award the side pot between B and C first, then award the main pot. Notice: you do not keep A’s cards face down until the side pot between B and C is awarded.~~
|
||||
|
||||
- ~~Example 3. NLHE. Three players remain.
|
||||
Pre-flop, Player A (the shortest stack) pushes all-in for 700 and is called by both B and C who have several thousand each left. Do not turn cards up yet because B and C both have chips so further betting action is possible.~~
|
||||
|
||||
~~On the flop B and C check; betting is still possible so don’t turn the cards up yet.
|
||||
On the turn B bets 1000 and C calls. Since both B and C still have chips and the river remains to be dealt, betting is still possible so don’t turn the cards up yet.~~
|
||||
|
||||
~~On the river both B and C check. Turn all hands up now (A, B, and C) because betting is over and the hand is moving to showdown. Award the 2000 side pot between B and C first, then award the main pot. Notice: do not keep A’s cards face down until the side pot between B and C is awarded.~~
|
||||
|
||||
### Rule 18: Asking to See a Hand 🔴
|
||||
|
||||
- ~~Example 1: NLHE. 3 players remain in the hand. There is no betting on the river and no player is all-in. At showdown Player A discards face down and the cards are pushed into the muck by the dealer. B tables his hand, showing trips. C pushes his cards forward face-down. B may ask to see C’s hand because B has tabled his cards. However, B’s request is at TDs discretion; B has no inalienable right to see it because there was no bet on the river thus he did not “pay to see C’s hand.” Neither A nor C may ask to see a competitor’s hand because they have neither tabled their cards nor retained them.~~
|
||||
|
||||
- ~~Example 2: NLHE. 4 players remain in the hand. On the river A bets 1000, B calls, C raises to 5000, and D, A and B all call. No player is all-in. B tables his hand, showing trips. D instantly discards face down and the dealer kills his hand into the muck. C begins to push his cards forward face-down. Both A and B have an inalienable right to see C’s hand on request because 1) they paid to see it as C was the last aggressor on the river and 2) both A and B retain their cards. D (who also called C) relinquished his right to see C’s hand when he discarded without tabling. All other requests in this situation are at TD’s discretion, such as B asking to see A’s cards (the cards of another caller).~~
|
||||
|
||||
### Rule 38: Burns After Substantial Action 🔴
|
||||
|
||||
- ~~Example 1-A: THE 50-100. SB / BB in seats 1 and 2. Pre-flop, initial cards dealt to all players. SB / BB in seats 1 and 2. Seat 3 (UTG) folds and Seat 4 calls, completing substantial action with 2 actions with chips. Seat 5 then realizes they have only 1 card and the hand is dead because SA has occurred. The dealer will burn only one card and then put out the flop. The dealer will not burn 2 cards to “return to the original stub order”.~~
|
||||
|
||||
- ~~Example 1-B: Same game and initial deal. Seat 3 (UTG) folds and Seat 4 calls, completing substantial action. Seat 5 then realizes they have 3 cards and the hand is dead because SA has occurred. The dealer will burn one card and then put out the flop. The dealer will not consider Seat 5’s third card as the burn and put out the flop without a burn off the stub.~~
|
||||
|
||||
### Rule 40-A: Methods of Betting, Unclear or Contradictory Bets. 🟢
|
||||
“In unclear situations or where verbal and chips are contradictory, the TD will determine the bet based on the circumstances and Rule 1”.
|
||||
|
||||
- Example 1: THE, heads-up on the river Player A verbally declares “forty-two thousand” but pushes out only a 5k chip. Not everyone at the table heard the declaration. Player B pushes out 5k to call. Both players table and A has the best hand. Ruling criteria is mixed: verbal came first but wasn’t necessarily clear. The chip appeared to be a bet of 5k. In these unclear and contradictory situations, the TD will make the fairest ruling possible using Rule 1.
|
||||
|
||||
### Rule 43: Raise Amounts. “The largest prior full bet or raise of the current betting round”. 🟢
|
||||
|
||||
This line refers to the largest additional action or “last legal increment” by a preceding bettor in the current round. The current round is the “current street”, i.e. pre-flop, flop, turn, river in board games; 3rd – 4th – 5th – 6th – 7th street in 7-stud, etc.
|
||||
|
||||
- Example 1: NLHE, Blinds 100-200. Post-flop, A opens with a bet of 600. B raises 1000 for total of 1600. C re-raises 2000 for total of 3600. If D wants to raise, he must at least raise the “largest bet or raise of the current round”, which is C’s raise of 2000. So, D must re-raise at least 2000 more for a total of 5600. Note that D’s minimum raise is not 3600 (C’s total bet), but only 2000, the additional raise action that C added.
|
||||
|
||||
- Example 2: NLHE, Blinds 50-100. Pre-flop A is under the gun and goes all-in for a total of 150 (an increase in the bet of 50). So, we have a 100 blind bet and an all-in wager that increases the total by 50. Which is larger? The 100 is still the “largest bet or raise of the current round”, so if B wants to re-raise he must raise at least 100 for a total of 250.
|
||||
|
||||
- Example 3: NLHE, Blinds 100-200. On the turn A bets 300. B pushes out two 500 chips making the total 1000 (a 700 raise). It is 1000 to C to call. If C wants to raise, it must be “at least the largest bet or raise of the current round”, which is B’s raise of 700. So, C’s minimum raise would be 700 for a total of 1700. Note his minimum raise is not 1000, B’s total bet.
|
||||
|
||||
- Example 4-A: NLHE, Blinds 25-50. A raises 75 to 125 total. Notice that 125 total = 50 (bet) plus 75 (raise). The next raise on this street must be “at least the size of the largest previous bet or raise”, which is 75. B now raises the minimum (75) to 200 total. C then re-raises 300 for total of 500. We now have a bet of 50, two raises of 75 and a raise of 300 for total of 500. If D wants to re-raise, “the raise must be at least the size of the largest previous bet or raise of the current betting round”, which is now 300. So, D must raise at least 300 more to a total of 800.
|
||||
|
||||
- Example 4-B: Same as 4-A. It’s the same 500 to D, but there’s just been one raise of 450 by A to a total of 500 and B and C have both called. So, there’s a blind bet of 50 and a raise of 450. “A raise must be at least the size of the largest previous bet or raise of the current betting round”, which is A’s raise of 450. So, it’s 500 for D to call, and if D wants to re-raise he must raise at least 450 for a total of 950.
|
||||
|
||||
### Rule 45: Multiple Chip Betting. 🟡
|
||||
“A: If facing a bet, unless raise or all-in is declared first, a multiple-chip bet (including a bet of your last chips) is a call if every chip is needed to make the call; i.e. removal of just one of the smallest chips leaves less than the call amount. B: If every chip is not needed to make the call; i.e. removal of just one of the smallest chips leaves the call amount or more: 1) if the player has chips remaining, the bet is governed by the 50% standard in Rule 43; 2) if the player’s last chips are bet he or she is all-in whether reaching the 50% threshold or not.”
|
||||
|
||||
- Example 1: There is not one chip that can be removed and still leave the call amount.
|
||||
- 1-A: Player A opens post flop for 1200, B silently puts out two 1000’s. This is a call because neither chip can be removed and still leave at least 1200.
|
||||
|
||||
- 1-B: NLHE, blinds 250-500. Preflop the UTG raises 600 to total of 1100. The UTG+1 silently puts out one 500 and one 1000 chip. This is a call because neither the 500 nor the 1000 can be removed and still leave at least 1100.
|
||||
|
||||
- Example 2: Same as 1-B above except the UTG+1 puts out one 1000 and five 100s silently. Four of the 100s could be removed and still leave the 1100 call amount. Therefore, this would be subject to the 50% standard in Rule 43: the minimum raise is 600, 50% of 600 is 300, therefore, if the UTG+1 puts out 1400 or more, he will be held to making a full raise to 1700 total. Since the UTG put out 1500 he must raise in this example.
|
||||
|
||||
- Example 3: Same as 2 above except the UTG+1 puts out one 1000 and three 100s silently. Two of the 100s can be removed and still leave the 1100 call amount therefore this is subject to Rule 43. Since the player did not put out at least 50% of a minimum raise, this bet is ruled a call and 200 is returned to the player.
|
||||
|
||||
- Example 4: Multiple-chip bet of all chips. A) If all chips are needed to make the call, this is treated exactly the same as a player with chips behind (See example 1 above). B) If removing just one of the smallest chips leaves the call amount or more, the player is all-in regardless of whether the bet reaches the 50% raise standard.
|
||||
|
||||
- Example 4-A: A opens for 1400, B (with remaining chips behind in large chip stack) silently pushes out one 1000 and three 500’s. This is a mandatory min-raise to 2800 because the 50% threshold of 2100 (1400+700=2100) is reached.
|
||||
|
||||
- Example 4-B: Same 1400 opener, B (with remaining chips behind in large chip stack) puts out one 1000 and two 500s. This is a call because it is short of the 50% threshold of 2100. NOTE: In both example 4-A and 4-B, Player B would be all-in if putting out his or her last chips.
|
||||
|
||||
### Rule 46: Prior Bet Chips Not Pulled In, situation examples. 🔴
|
||||
|
||||
- ~~Situation 1: If prior chips don’t cover the call AND are left alone. Ex: THE 25-50, the BB posts two 25’s, button raises to 600 total (550 more to BB).
|
||||
1: Adding an overchip is a call (drop a 1k chip onto the two 25’s).
|
||||
2: Adding multiple new chips is a call if all new chips are needed to call a) drop two 500’s onto the two 25’s or b) drop a 100 and 500 chip onto the two 25’s. In these two examples all new chips when combined with the prior chips are needed to make the call.
|
||||
3: Adding multiple new chips is a Rule 45 multiple chip bet if one of the smallest new chips is not needed to make the call (drop a 1k and 500 chip onto the two 25’s is a total bet of 1550). Per Rule 45, a silent multi-chip bet is a raise if it hits the 50% threshold; otherwise it is a call.~~
|
||||
|
||||
- ~~Situation 2: If prior chips don’t cover the call AND are fully pulled back:~~
|
||||
~~1) Removing all prior chips and adding an overchip is a call (pull back the two 25’s, add 1k chip).~~
|
||||
~~2) Removing all prior chips and adding new multiple chips is a Rule 45 bet (pull back two 25’s, add two or more new chips).~~
|
||||
|
||||
- ~~Situation 3: if prior chip(s) are partly pulled back (whether or not they cover the call amount)~~
|
||||
~~1) Partial removal of prior chips (pull back one 25, leave the other 25 out, add any new chip(s), is a Rule 45 multiple-chip bet (a raise if hitting 50%, otherwise a call).~~
|
||||
|
||||
- ~~Situation 4: If prior chip(s) cover the call amount, adding any new chip(s) is a Rule 45 multiple chip bet. Ex: THE 50-100, BB posts one 1k chip. Pre-flop raise to 700 (600 more to BB). The 1k prior chip covers the raise, thus adding any new chip(s) is a Rule 45 bet of all chips. This applies whether or not the initial 1k posted is pulled back or left alone.~~
|
||||
|
||||
- ~~Situation 5: Regardless of the above, the gesture of combining and pushing or tossing all chips forward may be interpreted as intent to bet all chips under Rule 45.~~
|
||||
|
||||
### Rule 47: Re-opening the bet. 🟡
|
||||
|
||||
- Example 1. Multiple short all-in wagers that cumulatively equal a full raise and therefore re-open betting:
|
||||
|
||||
NLHE, Blinds 50-100. Post-flop, A opens betting for the 100 minimum.
|
||||
|
||||
B goes all in for a total of 125. C calls the 125,
|
||||
|
||||
D goes all in for 200 total and E calls 200.
|
||||
|
||||
Action returns to A who is facing a total raise of 100. Since 100 is a full raise, the betting is re-opened for A who can fold, call, or raise here. Note that neither B’s increment of 25 or D’s increment of 75 is by itself a full raise, but when added together they total a full raise and thus re-open the betting to “a player who is facing at least a full raise when the action returns”.
|
||||
|
||||
- Example 1-A: At the end of Example 1 above, A smooth calls the 200 total (another 100 to him). The bet is now on C who only faces a 75 increment. C called 125 previously and now faces 200 total (75 more). C must face at least 225 total to re-open betting. Because 75 is not a full raise, betting for C is not re-opened and C can either call with 75 more or fold, he cannot raise.
|
||||
|
||||
- Example 1-B: At the end of Example 1 above, A raises the minimum (100), and makes it 300 total to C. C already has called 125 so it’s an additional 175 for C to call. 175 is more than a full raise. Since C already acted and is “now facing at least a full raise”, the betting is re-opened to C who can fold, call, or re-raise here.
|
||||
|
||||
- Example 2: Multiple short all-ins, the min-raise is the last full valid bet or raise.
|
||||
NLHE, Blinds 50-100. Post-flop A opens for 300, B pushes all-in for 500 total, C goes all-in for 650 total, D goes all-in for 800 total, E calls 800. What is the min raise for Player F? The opening bet (300) sets the initial min raise. Because no single player was all-in for more than 300, the min raise for F remains 300. F can either smooth call 800 or raise to at least 1100. See also Rule 43, Example 2 in Illustration Addendum.
|
||||
|
||||
- Example 3. Short all-in, 2 scenarios.
|
||||
NLHE, Blinds 2000-4000. Pre-flop A calls the BB for 4000. B folds and C pushes all-in for 7500 total (an increment of 3500 above the 4000 BB). It’s folded around to the SB who also folds.
|
||||
|
||||
- Example 3-A. It’s 3500 more to the BB who has not yet acted on his option. The BB can fold, smooth call the 3500, or raise by at least 4000 for a total of 11,500. The BB smooth calls and it’s 3500 more to A. A has already acted and is facing 3500 which is not a full raise. Therefore, A can only fold or call the 3500, he cannot raise because it is not “at least a full bet when the action returns to him”.
|
||||
|
||||
- Example 3-B. The BB raises the minimum (4000), for a total of 11500. It is now 7500 to A and because 7500 is more than a full minimum raise, betting is now re-opened for A who can fold, call, or re-raise.
|
||||
|
||||
### Rule 51: Binding Declarations / Undercalls in Turn 🟡
|
||||
|
||||
- Example 1: NLHE, blinds 1000-2000. Post-flop, A opens for 2000, B raises to 8000, C pushes out 2000 silently. C has undercalled B’s bet. Per Rule 51-B, because B is not the opener (A is) and the round is still multi-way, at TD’s discretion C may be required to make a full call or allowed to forfeit the 2000 undercall and fold.
|
||||
|
||||
- Example 2: NLHE, blinds 1000-2000. Post-flop 4 players remain. A opens for 8000, B silently puts out 2000. Per Rule 51-B, B undercalled the opening bet and must make a full call of 8000.
|
||||
|
||||
- Example 3: NLHE, blinds 1000-2000. Post-flop, A opens for 2000, B raises to 8000, C declares “call”. Per Rule 51-A, C has made a general verbal declaration (“call”) in turn. C is obligated to call B’s full bet of 8000.
|
||||
|
||||
- Example 4: NLHE, blinds 200-400. Opener bets 400, player A raises to 1200 and Player B puts out one 500 chip silently. Dealer tells B it’s 1200 and B folds. At TD’s discretion B forfeits 400 and 100 is returned.
|
||||
|
||||
### Rule 52-B: Incorrect Bet Amounts, Pot-Limit Games 🟡
|
||||
|
||||
- Example 1: PLO, 500-1000 blinds. Post-flop the pot totals 10,500. Player A wants to bet the pot and asks the dealer for a count. Dealer replies “nine thousand five hundred”. A pushes out 9,500. Player B folds and Player C calls 9,500. Substantial action has occurred after the initial erroneous bet. The dealer then realizes A’s pot bet should have been 10,500. Because the quoted amount was less than the pot and substantial action has occurred, the 9,500 bet is binding and will not be increased to 10,500.
|
||||
|
||||
- Example 2: Same as example 1 above, Player B folds then the dealer realizes A’s pot bet should have been 10,500. Substantial action has not occurred, so A must increase his or her bet to 10,500 total.
|
||||
|
||||
- Example 3: PLO, 500-1000 blinds. Post-flop the pot totals 10,500. Player A wants to bet the pot and asks the dealer for a count. Dealer replies “eleven thousand five hundred”. A pushes out 11,500. Player B folds, Player C and D both call 11,500. Before burning and turning the next card, the dealer realizes the initial bet was an illegal overbet. Despite substantial action occurring, because the bet was illegal it will be reduced to 10,500 for all players calling anywhere on the current street. If the next card is dealt the error will stand.
|
||||
|
||||
### Rule 53-A: Action Out of Turn (OOT) 🟡
|
||||
|
||||
- Example 1: THE 50-100. Post flop Seat 3 opens for 300, Seat 4 folds, action is on Seat 5 when Seat 6 declares “raise to eight hundred”.
|
||||
Step 1: Action backs up to the correct player in order (Seat 5) who is facing a bet of 300.
|
||||
Step 2: If Seat 5 calls or folds then the action (a 300 bet) has not changed and Seat 6’s OOT raise is binding (raise to 800). However, if Seat 5 raises, (say, to 600 total), then the action to Seat 6 has changed from a 300 bet to a 600 bet. If action changes, the 800 chips may be returned to Seat 6 who has all options open: call 600, re-raise to at least 900, or fold.
|
||||
|
||||
- Example 2: THE 50-100. Post flop Seat 3 checks, Seat 4 checks, action is on Seat 5 when Seat 6 declares “check”.
|
||||
Step 1: Action backs up to the correct player in order (Seat 5) who is not facing a bet.
|
||||
Step 2: If Seat 5 checks then the action (a check) has not changed and Seat 6’s OOT check is binding. However, if Seat 5 bets, (say, 300), then the action to Seat 6 has changed from a check to a 300 bet. If action changes, then Seat 6 has all options open: call 300, raise to at least 600, or fold.
|
||||
|
||||
### Rule 53-B: Substantial Action Out of Turn (OOT). 🟡
|
||||
A player skipped by OOT action must defend his right to act. If there is reasonable time and the skipped player has not spoken up by the time substantial action (see Rule 36) OOT occurs to his left, the OOT action is binding. The floor will be called to render a decision on how to treat the skipped hand.
|
||||
|
||||
- Example 1: NLHE, blinds 100-200. UTG (Seat 3) makes it 600. Seat 4 is skipped when Seat 5 calls 600 OOT. Seat 6 thinks for a moment then folds. There are now two players acting with chips involved to the left of Seat 4. Two players with chips qualifies as substantial action (Rule 36). Also, Seat 4 has had reasonable time to speak up and bring it to the dealer’s attention that he has been skipped. The OOT call by Seat 5 is now binding due to substantial action OOT, and the OOT fold by Seat 6 is binding (Rule 58). The floor is called to make a decision on the fate of Seat 4’s hand.
|
||||
|
||||
- Example 2: NLHE, blinds 100-200. Four players remain to see the turn. After the dealer tables the turn card, the UTG (Seat 3) opens betting for 600. Seat 4 is skipped when Seat 5 checks and Seat 6 calls 600 OOT. The floor is called to make a decision on the fate of Seat 4’s hand.
|
||||
@@ -0,0 +1,940 @@
|
||||
# Casono Game Engine
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Architecture Overview](#architecture-overview)
|
||||
* [server/GameController](#servergamecontroller)
|
||||
* [GameController](#gamecontroller)
|
||||
* [addPlayer](#addplayer)
|
||||
* [startGame](#startgame)
|
||||
* [rotateDealer](#rotatedealer)
|
||||
* [getDealer](#getdealer)
|
||||
* [postBlinds](#postblinds)
|
||||
* [dealHoleCards](#dealholecards)
|
||||
* [dealFlop](#dealflop)
|
||||
* [dealTurn](#dealturn)
|
||||
* [dealRiver](#dealriver)
|
||||
* [playerFold](#playerfold)
|
||||
* [playerCall](#playercall)
|
||||
* [playerRaise](#playerraise)
|
||||
* [getState](#getstate)
|
||||
* [getCommunityCards](#getcommunitycards)
|
||||
* [determineWinner](#determinewinner)
|
||||
* [server/action/AbstractAction](#serveractionabstractaction)
|
||||
* [AbstractAction](#abstractaction)
|
||||
* [server/action/AllInAction](#serveractionallinaction)
|
||||
* [AllInAction](#allinaction)
|
||||
* [server/action/BetAction](#serveractionbetaction)
|
||||
* [BetAction](#betaction)
|
||||
* [getAmount](#getamount)
|
||||
* [server/action/BlindAction](#serveractionblindaction)
|
||||
* [BlindAction](#blindaction)
|
||||
* [getAmount](#getamount-1)
|
||||
* [server/action/CallAction](#serveractioncallaction)
|
||||
* [CallAction](#callaction)
|
||||
* [server/action/FoldAction](#serveractionfoldaction)
|
||||
* [FoldAction](#foldaction)
|
||||
* [server/action/RaiseAction](#serveractionraiseaction)
|
||||
* [RaiseAction](#raiseaction)
|
||||
* [getAmount](#getamount-2)
|
||||
* [server/deck/Card](#serverdeckcard)
|
||||
* [Card](#card)
|
||||
* [getSuit](#getsuit)
|
||||
* [getRank](#getrank)
|
||||
* [server/deck/Deck](#serverdeckdeck)
|
||||
* [Deck](#deck)
|
||||
* [shuffle](#shuffle)
|
||||
* [draw](#draw)
|
||||
* [setCards](#setcards)
|
||||
* [server/engine/GameEngine](#serverenginegameengine)
|
||||
* [GameEngine](#gameengine)
|
||||
* [startNewHand](#startnewhand)
|
||||
* [startNewHand](#startnewhand-1)
|
||||
* [processAction](#processaction)
|
||||
* [handleAction](#handleaction)
|
||||
* [getState](#getstate-1)
|
||||
* [server/engine/RoundManager](#serverengineroundmanager)
|
||||
* [startNewHand](#startnewhand-2)
|
||||
* [progressIfNeeded](#progressifneeded)
|
||||
* [isBettingRoundFinished](#isbettingroundfinished)
|
||||
* [advancePhase](#advancephase)
|
||||
* [postBlinds](#postblinds-1)
|
||||
* [server/engine/TurnManager](#serverengineturnmanager)
|
||||
* [nextPlayer](#nextplayer)
|
||||
* [server/evaluator/HandEvaluator](#serverevaluatorhandevaluator)
|
||||
* [evaluate](#evaluate)
|
||||
* [getSortedRanks](#getsortedranks)
|
||||
* [getFlushCards](#getflushcards)
|
||||
* [buildFlush](#buildflush)
|
||||
* [checkStraightFlush](#checkstraightflush)
|
||||
* [checkFourOfAKind](#checkfourofakind)
|
||||
* [checkFullHouse](#checkfullhouse)
|
||||
* [checkThreeOfAKind](#checkthreeofakind)
|
||||
* [checkTwoPair](#checktwopair)
|
||||
* [checkOnePair](#checkonepair)
|
||||
* [getRank](#getrank-1)
|
||||
* [isStraight](#isstraight)
|
||||
* [server/evaluator/HandRank](#serverevaluatorhandrank)
|
||||
* [HandRank](#handrank)
|
||||
* [getType](#gettype)
|
||||
* [getKickers](#getkickers)
|
||||
* [server/player/Player](#serverplayerplayer)
|
||||
* [Player](#player)
|
||||
* [isAllIn](#isallin)
|
||||
* [getId](#getid)
|
||||
* [getName](#getname)
|
||||
* [getChips](#getchips)
|
||||
* [removeChips](#removechips)
|
||||
* [addChips](#addchips)
|
||||
* [setStatus](#setstatus)
|
||||
* [getStatus](#getstatus)
|
||||
* [getHand](#gethand)
|
||||
* [giveCard](#givecard)
|
||||
* [clearHand](#clearhand)
|
||||
* [isFolded](#isfolded)
|
||||
* [setFolded](#setfolded)
|
||||
* [server/player/PlayerId](#serverplayerplayerid)
|
||||
* [PlayerId](#playerid)
|
||||
* [of](#of)
|
||||
* [server/rules/RuleEngine](#serverrulesruleengine)
|
||||
* [RuleEngine](#ruleengine)
|
||||
* [validate](#validate)
|
||||
* [server/rules/RuleViolationException](#serverrulesruleviolationexception)
|
||||
* [RuleViolationException](#ruleviolationexception)
|
||||
* [server/rules/showdown/CardsSpeakRule](#serverrulesshowdowncardsspeakrule)
|
||||
* [determineWinner](#determinewinner-1)
|
||||
* [awardPot](#awardpot)
|
||||
* [server/state/GameState](#serverstategamestate)
|
||||
* [getPlayers](#getplayers)
|
||||
* [getCurrentPlayer](#getcurrentplayer)
|
||||
* [getCurrentPlayerIndex](#getcurrentplayerindex)
|
||||
* [isHandActive](#ishandactive)
|
||||
* [getPhase](#getphase)
|
||||
* [getTableState](#gettablestate)
|
||||
* [getPot](#getpot)
|
||||
* [getDeck](#getdeck)
|
||||
* [getCommunityCards](#getcommunitycards-1)
|
||||
* [getHoleCards](#getholecards)
|
||||
* [getDealerIndex](#getdealerindex)
|
||||
* [getPlayerCount](#getplayercount)
|
||||
* [setCurrentPlayerIndex](#setcurrentplayerindex)
|
||||
* [setHandActive](#sethandactive)
|
||||
* [setPhase](#setphase)
|
||||
* [setDealerIndex](#setdealerindex)
|
||||
* [setDeck](#setdeck)
|
||||
* [addPlayer](#addplayer-1)
|
||||
* [getCurrentBet](#getcurrentbet)
|
||||
* [setCurrentBet](#setcurrentbet)
|
||||
* [resetBets](#resetbets)
|
||||
* [addToPot](#addtopot)
|
||||
* [isAllowOutOfTurn](#isallowoutofturn)
|
||||
* [setAllowOutOfTurn](#setallowoutofturn)
|
||||
* [getPlayer](#getplayer)
|
||||
* [getCurrentBetCommitment](#getcurrentbetcommitment)
|
||||
* [setCurrentBetCommitment](#setcurrentbetcommitment)
|
||||
* [giveHoleCards](#giveholecards)
|
||||
* [addCommunityCard](#addcommunitycard)
|
||||
* [resetCommunityCards](#resetcommunitycards)
|
||||
* [nextPlayer](#nextplayer-1)
|
||||
* [rotateDealer](#rotatedealer-1)
|
||||
* [getDealer](#getdealer-1)
|
||||
* [startNewHand](#startnewhand-3)
|
||||
* [foldPlayer](#foldplayer)
|
||||
* [isFolded](#isfolded-1)
|
||||
* [server/state/Pot](#serverstatepot)
|
||||
* [add](#add)
|
||||
* [getAmount](#getamount-3)
|
||||
* [reset](#reset)
|
||||
* [server/state/TableState](#serverstatetablestate)
|
||||
* [getCurrentBet](#getcurrentbet-1)
|
||||
* [setCurrentBet](#setcurrentbet-1)
|
||||
* [getBigBlind](#getbigblind)
|
||||
* [setBigBlind](#setbigblind)
|
||||
* [getMinRaise](#getminraise)
|
||||
* [setMinRaise](#setminraise)
|
||||
* [isBettingOpen](#isbettingopen)
|
||||
* [setBettingOpen](#setbettingopen)
|
||||
* [canReopenBetting](#canreopenbetting)
|
||||
* [setCanReopenBetting](#setcanreopenbetting)
|
||||
* [getLastAggressorId](#getlastaggressorid)
|
||||
* [setLastAggressorId](#setlastaggressorid)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```txt
|
||||
server/
|
||||
└── domain/
|
||||
└── game/
|
||||
├── GameController.java
|
||||
│
|
||||
├── engine/
|
||||
│ ├── GameEngine.java
|
||||
│ ├── TurnManager.java
|
||||
│ │ → 23: New Hand and New Limits 🟢
|
||||
│ │ → 34: Button Placement and Movement 🟢
|
||||
│ │
|
||||
│ └── RoundManager.java
|
||||
│ → 23: New Hand and New Limits 🟢
|
||||
│ → 34: Button Placement and Movement 🟢
|
||||
│
|
||||
├── state/
|
||||
│ ├── GameState.java
|
||||
│ ├── GamePhase.java
|
||||
│ ├── TableState.java
|
||||
│ ├── BettingState.java
|
||||
│ └── Pot.java
|
||||
│ → 21: Side Pots 🟡
|
||||
│
|
||||
├── player/
|
||||
│ ├── Player.java
|
||||
│ └── PlayerStatus.java
|
||||
│
|
||||
├── deck/
|
||||
│ ├── Deck.java
|
||||
│ │ → Responsible for shuffling and distributing cards
|
||||
│ │
|
||||
│ ├── Card.java
|
||||
│ │ → Definition of a playing card
|
||||
│ │
|
||||
│ ├── Rank.java
|
||||
│ └── Suit.java
|
||||
│
|
||||
├── action/
|
||||
│ ├── Action.java
|
||||
│ ├── AbstractAction.java
|
||||
│ ├── ActionType.java
|
||||
│ │
|
||||
│ ├── BlindAction.java
|
||||
│ ├── BetAction.java
|
||||
│ ├── CallAction.java
|
||||
│ ├── RaiseAction.java
|
||||
│ ├── FoldAction.java
|
||||
│ └── AllInAction.java
|
||||
│
|
||||
├── rules/
|
||||
│ ├── Rule.java
|
||||
│ ├── RuleEngine.java
|
||||
│ └── RuleViolationException.java
|
||||
│
|
||||
│ ├── betting/
|
||||
│ │ ├── AcceptedActionRule.java
|
||||
│ │ │ → 49: Accepted Action 🟢
|
||||
│ │ │
|
||||
│ │ ├── MinimumRaiseRule.java
|
||||
│ │ │ → 43: Raise Amounts 🟢
|
||||
│ │ │
|
||||
│ │ ├── RaiseReopenRule.java
|
||||
│ │ │ → 43: Raise Amounts 🟢
|
||||
│ │ │ → 47: Re-Opening the Bet 🟡
|
||||
│ │ │ → 48: Number of Allowable Raises 🟡
|
||||
│ │ │
|
||||
│ │ ├── MinimumBetRule.java
|
||||
│ │ │ → 52: Incorrect Bets, Underbets and Underraises 🟡
|
||||
│ │ │
|
||||
│ │ ├── BindingDeclarationRule.java
|
||||
│ │ │ → 51: Binding Declarations / Undercalls in Turn 🟡
|
||||
│ │ │ → 56: String Bets and Raises 🟡
|
||||
│ │ │
|
||||
│ │ ├── AllInRule.java
|
||||
│ │ │ → 16: Face Up for All-Ins 🟢
|
||||
│ │ │ → 62: All-In with Chips Found Behind Later 🟡
|
||||
│ │ │
|
||||
│ │ ├── HandActiveRule.java
|
||||
│ │ │ → Ensures only active players can act
|
||||
│ │ │
|
||||
│ │ ├── ActionOrderRule.java
|
||||
│ │ │ → 50: Acting in Turn 🟢
|
||||
│ │ │
|
||||
│ │ └── OutOfTurnRule.java
|
||||
│ │ → 53: Action Out of Turn (OOT) 🟡
|
||||
│ │
|
||||
│ └── showdown/
|
||||
│ └── CardsSpeakRule.java
|
||||
│ → 12: Declarations. Cards Speak at Showdown 🟢
|
||||
│
|
||||
├── evaluator/
|
||||
│ ├── HandEvaluator.java
|
||||
│ └── HandRank.java
|
||||
│
|
||||
└── exception/
|
||||
└── RuleViolationException.java
|
||||
|
||||
```
|
||||
|
||||
## server/GameController
|
||||
|
||||
### GameController
|
||||
|
||||
- **Description**: GameController is responsible for managing the flow of the poker game. It
|
||||
- **Parameter (`engine`)**: The GameEngine instance that manages the game state and logic.
|
||||
|
||||
### addPlayer
|
||||
|
||||
- **Description**: Adds a player to the game with the specified name and initial chip count.
|
||||
- **Parameter (`name`)**: The name of the player to add.
|
||||
- **Parameter (`chips`)**: The initial number of chips the player has.
|
||||
|
||||
### startGame
|
||||
|
||||
- **Description**: Initializes a new hand by preparing the deck, setting the phase to PREFLOP,
|
||||
|
||||
### rotateDealer
|
||||
|
||||
- **Description**: Rotates the dealer position to the next player in the list.
|
||||
|
||||
### getDealer
|
||||
|
||||
- **Description**: Returns the player currently acting as dealer.
|
||||
|
||||
### postBlinds
|
||||
|
||||
- **Description**: Determines the small and big blind players relative to the dealer
|
||||
|
||||
### dealHoleCards
|
||||
|
||||
- **Description**: Deals hole cards to each player from the deck.
|
||||
|
||||
### dealFlop
|
||||
|
||||
- **Description**: Draws three cards from the deck and adds them as community cards (flop).
|
||||
|
||||
### dealTurn
|
||||
|
||||
- **Description**: Deals the turn by drawing one community card from the deck and adding it to
|
||||
|
||||
### dealRiver
|
||||
|
||||
- **Description**: Deals the river by drawing one community card from the deck and adding it to
|
||||
|
||||
### playerFold
|
||||
|
||||
- **Description**: Processes a player's fold action by sending a FoldAction to the GameEngine.
|
||||
- **Parameter (`playerId`)**: The ID of the player who is folding.
|
||||
|
||||
### playerCall
|
||||
|
||||
- **Description**: Processes a player's call action by sending a CallAction to the GameEngine.
|
||||
- **Parameter (`playerId`)**: The ID of the player who is calling.
|
||||
|
||||
### playerRaise
|
||||
|
||||
- **Description**: Processes a player's raise action by sending a RaiseAction to the GameEngine.
|
||||
- **Parameter (`playerId`)**: The ID of the player who is raising.
|
||||
- **Parameter (`amount`)**: The amount the player is raising.
|
||||
|
||||
### getState
|
||||
|
||||
- **Description**: Retrieves the current game state from the GameEngine.
|
||||
|
||||
### getCommunityCards
|
||||
|
||||
- **Description**: Retrieves the list of community cards currently on the table.
|
||||
|
||||
### determineWinner
|
||||
|
||||
- **Description**: 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.
|
||||
|
||||
## server/action/AbstractAction
|
||||
|
||||
### AbstractAction
|
||||
|
||||
- **Description**: AbstractAction serves as a base class for all player actions in the poker
|
||||
- **Parameter (`playerId`)**: The ID of the player performing the action.
|
||||
|
||||
## server/action/AllInAction
|
||||
|
||||
### AllInAction
|
||||
|
||||
- **Description**: AllInAction represents the action of a player going all-in in a poker game.
|
||||
- **Parameter (`playerId`)**: The ID of the player performing the all-in action.
|
||||
|
||||
## server/action/BetAction
|
||||
|
||||
### BetAction
|
||||
|
||||
- **Description**: Represents a bet action where a player contributes a fixed amount of chips.
|
||||
- **Parameter (`playerId`)**: The ID of the player performing the bet action.
|
||||
- **Parameter (`amount`)**: The amount of chips the player is betting.
|
||||
|
||||
### getAmount
|
||||
|
||||
- **Description**: Retrieves the amount of chips being bet in this action.
|
||||
|
||||
## server/action/BlindAction
|
||||
|
||||
### BlindAction
|
||||
|
||||
- **Description**: Executes a blind by deducting chips from the player, adding them to the pot,
|
||||
- **Parameter (`playerId`)**: The ID of the player posting the blind bet.
|
||||
- **Parameter (`amount`)**: The amount of chips the player is posting as a blind bet.
|
||||
|
||||
### getAmount
|
||||
|
||||
- **Description**: Retrieves the type of this action, which is BLIND.
|
||||
- **Parameter (`state`)**: The current game state on which to execute the action.
|
||||
- **Return**: The ActionType corresponding to this action.
|
||||
|
||||
## server/action/CallAction
|
||||
|
||||
### CallAction
|
||||
|
||||
- **Description**: CallAction represents the action of a player calling in a poker game. When a
|
||||
- **Parameter (`playerId`)**: the ID of the player performing the call action
|
||||
|
||||
## server/action/FoldAction
|
||||
|
||||
### FoldAction
|
||||
|
||||
- **Description**: FoldAction represents the action of a player folding in a poker game. When a
|
||||
- **Parameter (`playerId`)**: The ID of the player performing the fold action.
|
||||
|
||||
## server/action/RaiseAction
|
||||
|
||||
### RaiseAction
|
||||
|
||||
- **Description**: Constructs a RaiseAction for the specified player ID and raise amount.
|
||||
- **Parameter (`playerId`)**: The ID of the player performing the raise action.
|
||||
- **Parameter (`raiseAmount`)**: The amount of chips the player is raising.
|
||||
|
||||
### getAmount
|
||||
|
||||
- **Description**: Retrieves the amount of chips being raised in this action.
|
||||
|
||||
## server/deck/Card
|
||||
|
||||
### Card
|
||||
|
||||
- **Description**: The Card class represents a single playing card in a standard deck of cards.
|
||||
- **Parameter (`suit`)**: The suit of the card (Hearts, Diamonds, Clubs, Spades).
|
||||
- **Parameter (`rank`)**: The rank of the card (2-10, Jack, Queen, King, Ace).
|
||||
|
||||
### getSuit
|
||||
|
||||
- **Description**: Retrieves the suit of the card.
|
||||
|
||||
### getRank
|
||||
|
||||
- **Description**: Retrieves the rank of the card.
|
||||
|
||||
## server/deck/Deck
|
||||
|
||||
### Deck
|
||||
|
||||
- **Description**: The Deck class represents a standard deck of playing cards. It provides
|
||||
|
||||
### shuffle
|
||||
|
||||
- **Description**: Shuffles the deck of cards using the Collections.shuffle method, which
|
||||
|
||||
### draw
|
||||
|
||||
- **Description**: Draws and removes the last card in the list, which represents the top of the deck.
|
||||
|
||||
### setCards
|
||||
|
||||
- **Description**: Retrieves the current list of cards in the deck. This method returns a new
|
||||
|
||||
## server/engine/GameEngine
|
||||
|
||||
### GameEngine
|
||||
|
||||
- **Description**: The GameEngine class is responsible for managing the core logic of the poker
|
||||
- **Parameter (`state`)**: The initial game state to be managed by the engine.
|
||||
- **Parameter (`ruleEngine`)**: The RuleEngine instance responsible for validating player actions.
|
||||
- **Parameter (`roundManager`)**: The RoundManager instance responsible for managing the progression of rounds.
|
||||
- **Parameter (`turnManager`)**: The TurnManager instance responsible for managing player turns.
|
||||
|
||||
### startNewHand
|
||||
|
||||
- **Description**: Starts a new hand with the provided game state. This method initializes the
|
||||
- **Parameter (`state`)**: The game state to be used for starting the new hand.
|
||||
|
||||
### startNewHand
|
||||
|
||||
- **Description**: Starts a new hand using the current game state. This method is a convenience
|
||||
|
||||
### processAction
|
||||
|
||||
- **Description**: Processes a player action by validating it against the game rules, executing
|
||||
- **Parameter (`action`)**: The player action to be processed.
|
||||
|
||||
### handleAction
|
||||
|
||||
- **Description**: Handles a player action by performing the following steps:
|
||||
- **Parameter (`state`)**: The current game state on which to execute the action.
|
||||
- **Parameter (`action`)**: The player action to be processed.
|
||||
|
||||
### getState
|
||||
|
||||
- **Description**: Retrieves the current game state managed by the GameEngine.
|
||||
|
||||
## server/engine/RoundManager
|
||||
|
||||
### startNewHand
|
||||
|
||||
- **Description**: RoundManager is responsible for managing the flow of a poker game round. It
|
||||
- **Parameter (`state`)**: The game state to be used for starting the new hand.
|
||||
|
||||
### progressIfNeeded
|
||||
|
||||
- **Description**: Checks if the betting round is finished and advances the game phase if
|
||||
- **Parameter (`state`)**: The current game state to be evaluated for betting round progression.
|
||||
|
||||
### isBettingRoundFinished
|
||||
|
||||
- **Description**: Determines if the betting round is finished by checking if all active players
|
||||
- **Parameter (`state`)**: The current game state to be evaluated for betting round completion.
|
||||
|
||||
### advancePhase
|
||||
|
||||
- **Description**: Advances the game phase to the next stage (flop, turn, river, or showdown)
|
||||
- **Parameter (`state`)**: The current game state to be updated with the new phase.
|
||||
|
||||
### postBlinds
|
||||
|
||||
- **Description**: Handles the posting of blinds at the start of a new hand. This method
|
||||
- **Parameter (`state`)**: The current game state to be updated with the posted blinds.
|
||||
|
||||
## server/engine/TurnManager
|
||||
|
||||
### nextPlayer
|
||||
|
||||
- **Description**: The TurnManager class is responsible for managing the flow of turns in a
|
||||
- **Parameter (`state`)**: The current game state that will be updated to reflect the next player's turn.
|
||||
|
||||
## server/evaluator/HandEvaluator
|
||||
|
||||
### evaluate
|
||||
|
||||
- **Description**: The HandEvaluator class provides functionality to evaluate a poker hand and
|
||||
- **Parameter (`cards`)**: A list of Card objects representing the player's hand.
|
||||
|
||||
### getSortedRanks
|
||||
|
||||
- **Description**: Helper method to extract and sort the ranks of the cards in descending order.
|
||||
- **Parameter (`cards`)**: A list of Card objects representing the player's hand.
|
||||
|
||||
### getFlushCards
|
||||
|
||||
- **Description**: Helper method to count the occurrences of each card rank in the hand.
|
||||
- **Parameter (`ranks`)**: A list of integer ranks representing the cards in the hand.
|
||||
- **Parameter (`cards`)**: A list of Card objects representing the player's hand.
|
||||
- **Parameter (`suits`)**: A map where the key is the Suit and the value is a list of Cards belonging to that suit.
|
||||
- **Return**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
|
||||
### buildFlush
|
||||
|
||||
- **Description**: Helper method to build a HandRank object for a flush hand.
|
||||
- **Parameter (`flushCards`)**: A list of Card objects that form a flush.
|
||||
|
||||
### checkStraightFlush
|
||||
|
||||
- **Description**: Helper method to check for a straight flush or royal flush in the hand.
|
||||
- **Parameter (`flushCards`)**: A list of Card objects that form a flush.
|
||||
|
||||
### checkFourOfAKind
|
||||
|
||||
- **Description**: Helper method to check for a four of a kind hand rank.
|
||||
- **Parameter (`rankCount`)**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
|
||||
### checkFullHouse
|
||||
|
||||
- **Description**: Helper method to check for a full house hand rank.
|
||||
- **Parameter (`rankCount`)**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
|
||||
### checkThreeOfAKind
|
||||
|
||||
- **Description**: Helper method to check for a three of a kind hand rank.
|
||||
- **Parameter (`rankCount`)**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
|
||||
### checkTwoPair
|
||||
|
||||
- **Description**: Helper method to check for a two pair hand rank.
|
||||
- **Parameter (`rankCount`)**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
|
||||
### checkOnePair
|
||||
|
||||
- **Description**: Helper method to check for a one pair hand rank.
|
||||
- **Parameter (`rankCount`)**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
|
||||
### getRank
|
||||
|
||||
- **Description**: Helper method to get the rank of a card based on the count of occurrences in
|
||||
- **Parameter (`map`)**: A map where the key is the card rank and the value is the count of occurrences.
|
||||
- **Parameter (`count`)**: The specific count to look for (e.g., 2 for pairs, 3 for three of a kind).
|
||||
|
||||
### isStraight
|
||||
|
||||
- **Description**: Helper method to determine if a list of card ranks forms a straight.
|
||||
- **Parameter (`ranks`)**: A list of integer ranks representing the cards in the hand.
|
||||
|
||||
## server/evaluator/HandRank
|
||||
|
||||
### HandRank
|
||||
|
||||
- **Description**: HandRank represents the rank of a poker hand, including its type (e.g.,
|
||||
- **Parameter (`type`)**: The type of the hand (e.g., flush, straight).
|
||||
- **Parameter (`kickers`)**: A list of integers representing the kickers for tie-breaking.
|
||||
|
||||
### getType
|
||||
|
||||
- **Description**: Retrieves the type of the hand.
|
||||
|
||||
### getKickers
|
||||
|
||||
- **Description**: Retrieves the list of kickers for tie-breaking.
|
||||
|
||||
## server/player/Player
|
||||
|
||||
### Player
|
||||
|
||||
- **Description**: The Player class represents a participant in the poker game. It holds
|
||||
- **Parameter (`id`)**: The unique identifier for the player.
|
||||
- **Parameter (`chips`)**: The initial number of chips the player has.
|
||||
|
||||
### isAllIn
|
||||
|
||||
- **Description**: Checks if the player is all-in, meaning they have no chips left to bet.
|
||||
|
||||
### getId
|
||||
|
||||
- **Description**: Retrieves the unique identifier of the player.
|
||||
|
||||
### getName
|
||||
|
||||
- **Description**: Returns the display name of the player.
|
||||
|
||||
### getChips
|
||||
|
||||
- **Description**: Retrieves the current chip count of the player.
|
||||
|
||||
### removeChips
|
||||
|
||||
- **Description**: Removes a specified amount of chips from the player's total. If the amount
|
||||
- **Parameter (`amount`)**: The number of chips to remove from the player.
|
||||
|
||||
### addChips
|
||||
|
||||
- **Description**: Adds a specified amount of chips to the player's total.
|
||||
- **Parameter (`amount`)**: The number of chips to add to the player.
|
||||
|
||||
### setStatus
|
||||
|
||||
- **Description**: Sets the player's status to the specified value.
|
||||
- **Parameter (`status`)**: The new status for the player.
|
||||
|
||||
### getStatus
|
||||
|
||||
- **Description**: Retrieves the current status of the player.
|
||||
|
||||
### getHand
|
||||
|
||||
- **Description**: Retrieves the player's current hand of cards.
|
||||
|
||||
### giveCard
|
||||
|
||||
- **Description**: Adds a card to the player's hand.
|
||||
- **Parameter (`card`)**: The Card object to be added to the player's hand.
|
||||
|
||||
### clearHand
|
||||
|
||||
- **Description**: Clears the player's hand of cards, removing all cards from the hand.
|
||||
|
||||
### isFolded
|
||||
|
||||
- **Description**: Checks if the player has folded in the current round.
|
||||
|
||||
### setFolded
|
||||
|
||||
- **Description**: Sets the player's folded status to the specified value.
|
||||
- **Parameter (`folded`)**: The new folded status for the player.
|
||||
|
||||
## server/player/PlayerId
|
||||
|
||||
### PlayerId
|
||||
|
||||
- **Description**: PlayerId is a value object that represents the unique identifier of a player
|
||||
|
||||
### of
|
||||
|
||||
- **Description**: Constructs a PlayerId with the specified value. The constructor validates
|
||||
- **Parameter (`value`)**: the string value representing the player's unique identifier
|
||||
- **Parameter (`value`)**: the string value representing the player's unique identifier
|
||||
|
||||
## server/rules/RuleEngine
|
||||
|
||||
### RuleEngine
|
||||
|
||||
- **Description**: The RuleEngine class is responsible for managing and validating a list of
|
||||
- **Parameter (`rules`)**: The list of rules to be managed by the RuleEngine.
|
||||
|
||||
### validate
|
||||
|
||||
- **Description**: Validates the given action against all the rules in the RuleEngine. If any
|
||||
- **Parameter (`state`)**: The current state of the game.
|
||||
- **Parameter (`action`)**: The action to be validated against the rules.
|
||||
|
||||
## server/rules/RuleViolationException
|
||||
|
||||
### RuleViolationException
|
||||
|
||||
- **Description**: RuleViolationException is a custom exception that is thrown when a player
|
||||
- **Parameter (`message`)**: The detail message explaining the reason for the rule violation.
|
||||
|
||||
## server/rules/showdown/CardsSpeakRule
|
||||
|
||||
### determineWinner
|
||||
|
||||
- **Description**: The CardsSpeakRule class implements the Rule interface and defines the logic
|
||||
- **Parameter (`state`)**: The current state of the game.
|
||||
- **Parameter (`action`)**: The action to be validated.
|
||||
- **Parameter (`state`)**: The current state of the game, which includes player information, hole cards, and community cards.
|
||||
|
||||
### awardPot
|
||||
|
||||
- **Description**: Awards the pot to the winner of the poker hand. It determines the winner(s)
|
||||
- **Parameter (`state`)**: The current state of the game, which includes player information and pot details.
|
||||
|
||||
## server/state/GameState
|
||||
|
||||
### getPlayers
|
||||
|
||||
- **Description**: The GameState class encapsulates the entire state of a poker game at any
|
||||
|
||||
### getCurrentPlayer
|
||||
|
||||
- **Description**: Returns the current player whose turn it is to act.
|
||||
|
||||
### getCurrentPlayerIndex
|
||||
|
||||
- **Description**: Returns the index of the current player in the players list.
|
||||
|
||||
### isHandActive
|
||||
|
||||
- **Description**: Indicates whether a hand is currently active in the game.
|
||||
|
||||
### getPhase
|
||||
|
||||
- **Description**: Returns the current phase of the game (e.g., PREFLOP, FLOP, TURN, RIVER).
|
||||
|
||||
### getTableState
|
||||
|
||||
- **Description**: Returns the current state of the table, including player statuses and
|
||||
|
||||
### getPot
|
||||
|
||||
- **Description**: Returns the current pot, which contains the total amount of chips bet by
|
||||
|
||||
### getDeck
|
||||
|
||||
- **Description**: Returns the current deck of cards being used in the game.
|
||||
|
||||
### getCommunityCards
|
||||
|
||||
- **Description**: Returns the list of community cards currently on the table.
|
||||
|
||||
### getHoleCards
|
||||
|
||||
- **Description**: Returns the hole cards for a specific player based on their ID.
|
||||
- **Parameter (`playerId`)**: The ID of the player whose hole cards are being requested.
|
||||
|
||||
### getDealerIndex
|
||||
|
||||
- **Description**: Returns the index of the dealer in the players list.
|
||||
|
||||
### getPlayerCount
|
||||
|
||||
- **Description**: 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.
|
||||
|
||||
### setCurrentPlayerIndex
|
||||
|
||||
- **Description**: Sets the index of the current player in the players list.
|
||||
- **Parameter (`index`)**: An integer representing the index of the current player.
|
||||
|
||||
### setHandActive
|
||||
|
||||
- **Description**: Sets whether a hand is currently active in the game.
|
||||
- **Parameter (`handActive`)**: A boolean value indicating whether a hand is active.
|
||||
|
||||
### setPhase
|
||||
|
||||
- **Description**: Sets the current phase of the game.
|
||||
- **Parameter (`phase`)**: The GamePhase enum value representing the new phase of the game.
|
||||
|
||||
### setDealerIndex
|
||||
|
||||
- **Description**: Sets the index of the dealer in the players list.
|
||||
- **Parameter (`dealerIndex`)**: An integer representing the index of the dealer.
|
||||
|
||||
### setDeck
|
||||
|
||||
- **Description**: Sets the current deck of cards being used in the game.
|
||||
- **Parameter (`deck`)**: A Deck object representing the new deck of cards to be used in the game.
|
||||
|
||||
### addPlayer
|
||||
|
||||
- **Description**: Adds a player to the game with the specified ID and initial chip count. This
|
||||
- **Parameter (`id`)**: The ID of the player to add.
|
||||
- **Parameter (`chips`)**: The initial number of chips the player has.
|
||||
|
||||
### getCurrentBet
|
||||
|
||||
- **Description**: Retrieves the current bet amount for a specific player based on their ID.
|
||||
- **Parameter (`playerId`)**: The ID of the player whose current bet is being requested.
|
||||
|
||||
### setCurrentBet
|
||||
|
||||
- **Description**: Sets the current bet amount for a specific player based on their ID. This
|
||||
- **Parameter (`playerId`)**: The ID of the player whose current bet is being set.
|
||||
- **Parameter (`amount`)**: The new bet amount to be set for the specified player.
|
||||
|
||||
### resetBets
|
||||
|
||||
- **Description**: Resets the current bets for all players by clearing the currentBets map. This
|
||||
|
||||
### addToPot
|
||||
|
||||
- **Description**: Adds a specified amount to the pot. This method updates the total amount in
|
||||
- **Parameter (`amount`)**: The amount of chips to be added to the pot.
|
||||
|
||||
### isAllowOutOfTurn
|
||||
|
||||
- **Description**: Returns whether out-of-turn actions are allowed in the game. Out-of-turn
|
||||
|
||||
### setAllowOutOfTurn
|
||||
|
||||
- **Description**: Sets whether out-of-turn actions are allowed in the game. This method updates
|
||||
- **Parameter (`allowOutOfTurn`)**: A boolean value indicating whether out-of-turn actions should be allowed in the game.
|
||||
|
||||
### getPlayer
|
||||
|
||||
- **Description**: Retrieves a player from the game based on their ID. This method searches the
|
||||
- **Parameter (`id`)**: The ID of the player to retrieve.
|
||||
- **Return**: The Player object corresponding to the specified ID.
|
||||
|
||||
### getCurrentBetCommitment
|
||||
|
||||
- **Description**: Retrieves the current bet commitment for a specific player based on their ID.
|
||||
- **Parameter (`playerId`)**: The ID of the player whose current bet commitment is being requested.
|
||||
|
||||
### setCurrentBetCommitment
|
||||
|
||||
- **Description**: Sets the current bet commitment for a specific player based on their ID. This
|
||||
- **Parameter (`playerId`)**: The ID of the player whose current bet commitment is being set.
|
||||
- **Parameter (`amount`)**: The new bet commitment amount to be set for the specified player.
|
||||
|
||||
### giveHoleCards
|
||||
|
||||
- **Description**: Gives hole cards to a specific player based on their ID. This method takes
|
||||
- **Parameter (`playerId`)**: The ID of the player to whom the hole cards are being given.
|
||||
- **Parameter (`c1`)**: The first Card object representing one of the player's hole cards.
|
||||
- **Parameter (`c2`)**: The second Card object representing the other hole card for the player.
|
||||
|
||||
### addCommunityCard
|
||||
|
||||
- **Description**: Adds a community card to the game state. This method takes a Card object
|
||||
- **Parameter (`card`)**: The Card object representing the community card to be added to the game state.
|
||||
|
||||
### resetCommunityCards
|
||||
|
||||
- **Description**: Resets the community cards by clearing the list of community cards. This
|
||||
|
||||
### nextPlayer
|
||||
|
||||
- **Description**: Advances the turn to the next player in the players list. This method updates
|
||||
|
||||
### rotateDealer
|
||||
|
||||
- **Description**: Rotates the dealer position to the next player in the players list. This
|
||||
|
||||
### getDealer
|
||||
|
||||
- **Description**: Retrieves the current dealer based on the dealerIndex. This method returns
|
||||
|
||||
### startNewHand
|
||||
|
||||
- **Description**: Starts a new hand by resetting the game state for the next round of poker.
|
||||
|
||||
### foldPlayer
|
||||
|
||||
- **Description**: Folds a player in the current hand. This method adds the specified player's ID
|
||||
- **Parameter (`playerId`)**: The ID of the player who is folding.
|
||||
|
||||
### isFolded
|
||||
|
||||
- **Description**: Checks if a specific player has folded in the current hand. This method
|
||||
- **Parameter (`playerId`)**: The ID of the player to check for folding status.
|
||||
|
||||
## server/state/Pot
|
||||
|
||||
### add
|
||||
|
||||
- **Description**: The Pot class represents the total amount of chips that players have bet in a
|
||||
- **Parameter (`chips`)**: The number of chips to add to the pot.
|
||||
|
||||
### getAmount
|
||||
|
||||
- **Description**: Retrieves the current amount of chips in the pot.
|
||||
|
||||
### reset
|
||||
|
||||
- **Description**: Resets the pot to zero, typically used at the end of a round or when
|
||||
|
||||
## server/state/TableState
|
||||
|
||||
### getCurrentBet
|
||||
|
||||
- **Description**: The TableState class represents the current state of the poker table during a
|
||||
|
||||
### setCurrentBet
|
||||
|
||||
- **Description**: Sets the current bet amount for the table. This method is typically called
|
||||
- **Parameter (`currentBet`)**: The new current bet amount to set for the table.
|
||||
|
||||
### getBigBlind
|
||||
|
||||
- **Description**: Returns the big blind amount. The big blind serves as a baseline
|
||||
|
||||
### setBigBlind
|
||||
|
||||
- **Description**: Sets the big blind amount for the game. The big blind is a forced bet that
|
||||
- **Parameter (`bigBlind`)**: The amount to set as the big blind for the game.
|
||||
|
||||
### getMinRaise
|
||||
|
||||
- **Description**: Retrieves the minimum raise amount for the current betting round. The
|
||||
|
||||
### setMinRaise
|
||||
|
||||
- **Description**: Sets the minimum raise amount for the current betting round. This method is
|
||||
- **Parameter (`minRaise`)**: The new minimum raise amount to set for the current betting round.
|
||||
|
||||
### isBettingOpen
|
||||
|
||||
- **Description**: Checks if betting is currently open at the table. This status indicates
|
||||
|
||||
### setBettingOpen
|
||||
|
||||
- **Description**: Sets the betting status for the table. This method can be used to open or
|
||||
- **Parameter (`bettingOpen`)**: The new betting status to set for the table (true for open, false for closed).
|
||||
|
||||
### canReopenBetting
|
||||
|
||||
- **Description**: Checks if betting can be reopened after being closed. This status allows for
|
||||
|
||||
### setCanReopenBetting
|
||||
|
||||
- **Description**: Sets whether betting can be reopened after being closed. This method is
|
||||
- **Parameter (`canReopenBetting`)**: The new status indicating whether betting can be reopened (true or false).
|
||||
|
||||
### getLastAggressorId
|
||||
|
||||
- **Description**: Retrieves the ID of the last aggressor in the current betting round. The
|
||||
|
||||
### setLastAggressorId
|
||||
|
||||
- **Description**: Sets the ID of the last aggressor in the current betting round. This method
|
||||
- **Parameter (`lastAggressorId`)**: The new ID of the last aggressor to set for the current betting round.
|
||||
@@ -0,0 +1,200 @@
|
||||
# Game Engine Control
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [GameController](#gamecontroller)
|
||||
* [How the Server interacts with the GameController](#how-the-server-interacts-with-the-gamecontroller)
|
||||
* [Starting a Game](#starting-a-game)
|
||||
* [Preflop Actions](#preflop-actions)
|
||||
* [Flop](#flop)
|
||||
* [Turn](#turn)
|
||||
* [River](#river)
|
||||
* [Showdown](#showdown)
|
||||
* [Server Outputs](#server-outputs)
|
||||
* [Get full game state](#get-full-game-state)
|
||||
* [Get community cards](#get-community-cards)
|
||||
* [Get player hole cards](#get-player-hole-cards)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
# GameController
|
||||
|
||||
The GameController acts as the main entry point for controlling the poker game logic from the server.
|
||||
|
||||
The server does not manipulate the game state directly.
|
||||
Instead, it interacts exclusively with the `GameController`, which internally coordinates:
|
||||
|
||||
- the GameEngine
|
||||
- the GameState
|
||||
- the RuleEngine
|
||||
- the RoundManager
|
||||
- the TurnManager
|
||||
|
||||
# How the Server interacts with the GameController
|
||||
|
||||
The server calls methods on the GameController to control the game.
|
||||
|
||||
## Starting a Game
|
||||
|
||||
```java
|
||||
GameController game = new GameController(engine);
|
||||
|
||||
game.addPlayer(PlayerId.of("Julian"), 20000);
|
||||
game.addPlayer(PlayerId.of("Mathis"), 20000);
|
||||
game.addPlayer(PlayerId.of("Jona"), 20000);
|
||||
game.addPlayer(PlayerId.of("Lars"), 20000);
|
||||
|
||||
game.startGame();
|
||||
```
|
||||
|
||||
Steps performed:
|
||||
|
||||
1. Create a GameController instance.
|
||||
2. Add players with their starting chip stacks.
|
||||
3. Start the game.
|
||||
|
||||
When `startGame()` is called:
|
||||
|
||||
- the deck is prepared
|
||||
- hole cards are dealt
|
||||
- the game phase switches to PREFLOP
|
||||
|
||||
## Preflop Actions
|
||||
|
||||
During the preflop phase, players perform actions.
|
||||
|
||||
```java
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
game.playerFold(PlayerId.of("Mathis"));
|
||||
game.playerCall(PlayerId.of("Jona"));
|
||||
game.playerRaise(PlayerId.of("Lars"), 1200);
|
||||
```
|
||||
|
||||
Supported actions include:
|
||||
|
||||
* `playerCall(playerId)`
|
||||
* `playerFold(playerId)`
|
||||
* `playerRaise(playerId, amount)`
|
||||
|
||||
The GameController passes these actions on to the GameEngine, which validates them using the RuleEngine.
|
||||
|
||||
## Flop
|
||||
|
||||
Once the preflop betting round is completed, the server can deal the flop.
|
||||
|
||||
```java
|
||||
game.dealFlop();
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
- draw 3 community cards
|
||||
- add them to the board
|
||||
- update the game phase
|
||||
|
||||
## Turn
|
||||
|
||||
```java
|
||||
game.dealTurn();
|
||||
```
|
||||
|
||||
This deals the fourth community card.
|
||||
|
||||
## River
|
||||
|
||||
```java
|
||||
game.dealRiver();
|
||||
```
|
||||
|
||||
This deals the fifth and final community card and starts the final betting round.
|
||||
|
||||
## Showdown
|
||||
|
||||
After the final betting round, the server can determine the winner.
|
||||
|
||||
```java
|
||||
String winner = game.showdown();
|
||||
````
|
||||
|
||||
This will:
|
||||
|
||||
- evaluate the hands of all remaining players
|
||||
- determine the best poker hand
|
||||
- award the pot to the winner
|
||||
- end the current hand
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Winner: Julian
|
||||
Pot: 3200
|
||||
```
|
||||
|
||||
During the showdown, each remaining player's hand is evaluated using their:
|
||||
|
||||
- two hole cards
|
||||
- five community cards
|
||||
|
||||
The best possible 5-card poker hand wins the pot.
|
||||
|
||||
# Server Outputs
|
||||
|
||||
The server can query the current game state at any time.
|
||||
|
||||
## Get full game state
|
||||
|
||||
```java
|
||||
GameState state = game.getState();
|
||||
````
|
||||
|
||||
Returns the complete game state.
|
||||
|
||||
The state includes information such as:
|
||||
|
||||
- players
|
||||
- chip stacks
|
||||
- pot
|
||||
- current game phase
|
||||
- deck state
|
||||
|
||||
## Get community cards
|
||||
|
||||
```java
|
||||
List<Card> board = game.getCommunityCards();
|
||||
```
|
||||
|
||||
Returns the community cards currently on the board.
|
||||
|
||||
The number of cards depends on the game phase:
|
||||
|
||||
- Flop -> 3 cards
|
||||
- Turn -> 4 cards
|
||||
- River -> 5 cards
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
[NINE of DIAMONDS, TEN of SPADES, JACK of CLUBS]
|
||||
```
|
||||
|
||||
## Get player hole cards
|
||||
|
||||
```java
|
||||
Map<PlayerId, List<Card>> cards = game.getPlayerCards();
|
||||
```
|
||||
|
||||
Returns a mapping of player IDs to their hole cards.
|
||||
|
||||
Each player receives two private cards at the start of the hand.
|
||||
|
||||
```text
|
||||
playerId -> [Card, Card]
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Mathis -> [NINE of DIAMONDS, TEN of SPADES]
|
||||
Lars -> [JACK of SPADES, TWO of HEARTS]
|
||||
Jona -> [SEVEN of DIAMONDS, TWO of DIAMONDS]
|
||||
Julian -> [NINE of HEARTS, FIVE of DIAMONDS]
|
||||
```
|
||||
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 427 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 209 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.domain.game;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
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.deck.Rank;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.deck.Suit;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.GameEngine;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.RoundManager;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.TurnManager;
|
||||
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.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine;
|
||||
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.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* The GameControllerTest class contains unit tests for the GameController class, which manages the
|
||||
* flow of a poker game. These tests simulate various game scenarios, including player actions, card
|
||||
* dealing, and hand evaluation, to ensure that the GameController behaves as expected under
|
||||
* different conditions.
|
||||
*/
|
||||
public class GameControllerTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GameControllerTest.class.getName());
|
||||
|
||||
/**
|
||||
* This test simulates a specific game scenario where Julian is expected to win with a Two Pair
|
||||
* hand. It sets up a fixed deck to ensure that the desired cards are dealt to the players and
|
||||
* verifies that the hand evaluation correctly identifies Julian as the winner.
|
||||
*/
|
||||
@Test
|
||||
public void testFullPokerGameSimulation1() {
|
||||
|
||||
GameState state = new GameState();
|
||||
|
||||
GameEngine engine =
|
||||
new GameEngine(
|
||||
state,
|
||||
new RuleEngine(new ArrayList<>()),
|
||||
new RoundManager(),
|
||||
new TurnManager());
|
||||
|
||||
GameController game = new GameController(engine);
|
||||
|
||||
game.addPlayer(PlayerId.of("Julian"), 20000);
|
||||
game.addPlayer(PlayerId.of("Mathis"), 20000);
|
||||
game.addPlayer(PlayerId.of("Jona"), 20000);
|
||||
game.addPlayer(PlayerId.of("Lars"), 20000);
|
||||
|
||||
assertEquals(4, game.getState().getPlayers().size());
|
||||
|
||||
game.startGame();
|
||||
|
||||
Map<PlayerId, List<Card>> playerCards = game.getPlayerCards();
|
||||
|
||||
assertEquals(4, playerCards.size());
|
||||
|
||||
for (List<Card> cards : playerCards.values()) {
|
||||
assertEquals(2, cards.size());
|
||||
}
|
||||
|
||||
Set<String> seenCards = new HashSet<>();
|
||||
|
||||
for (List<Card> cards : playerCards.values()) {
|
||||
for (Card c : cards) {
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
assertFalse(seenCards.contains(key));
|
||||
seenCards.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
game.playerFold(PlayerId.of("Mathis"));
|
||||
game.playerCall(PlayerId.of("Jona"));
|
||||
game.playerRaise(PlayerId.of("Lars"), 1200);
|
||||
|
||||
game.dealFlop();
|
||||
|
||||
List<Card> board = game.getCommunityCards();
|
||||
assertEquals(3, board.size());
|
||||
|
||||
game.dealTurn();
|
||||
assertEquals(4, game.getCommunityCards().size());
|
||||
|
||||
game.dealRiver();
|
||||
assertEquals(5, game.getCommunityCards().size());
|
||||
|
||||
for (Card c : game.getCommunityCards()) {
|
||||
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
|
||||
assertFalse(seenCards.contains(key));
|
||||
seenCards.add(key);
|
||||
}
|
||||
|
||||
assertTrue(game.getState().getPot().getAmount() > 0);
|
||||
|
||||
Player mathis = game.getState().getPlayer(PlayerId.of("Mathis"));
|
||||
|
||||
assertNotNull(mathis);
|
||||
|
||||
assertEquals(5, game.getCommunityCards().size());
|
||||
|
||||
assertEquals(4, game.getState().getPlayers().size());
|
||||
|
||||
LOGGER.info("Test 1 was successfully completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test simulates a full poker game, including player actions, card dealing, and hand
|
||||
* evaluation. It verifies that the game state is updated correctly throughout the game and that
|
||||
* the expected outcomes are achieved.
|
||||
*/
|
||||
@Test
|
||||
public void testFullPokerGameSimulation2() {
|
||||
|
||||
GameState state = new GameState();
|
||||
|
||||
GameEngine engine =
|
||||
new GameEngine(
|
||||
state,
|
||||
new RuleEngine(new ArrayList<>()),
|
||||
new RoundManager(),
|
||||
new TurnManager());
|
||||
|
||||
GameController game = new GameController(engine);
|
||||
|
||||
game.addPlayer(PlayerId.of("Julian"), 20000);
|
||||
game.addPlayer(PlayerId.of("Mathis"), 20000);
|
||||
game.addPlayer(PlayerId.of("Jona"), 20000);
|
||||
game.addPlayer(PlayerId.of("Lars"), 20000);
|
||||
|
||||
assertEquals(4, game.getState().getPlayers().size(), "There should be exactly 4 players");
|
||||
|
||||
game.startGame();
|
||||
|
||||
assertEquals(GamePhase.PREFLOP, game.getState().getPhase(), "The hand must start preflop");
|
||||
|
||||
Map<PlayerId, List<Card>> playerCards = game.getPlayerCards();
|
||||
|
||||
assertEquals(4, playerCards.size(), "All players must receive cards");
|
||||
|
||||
for (Map.Entry<PlayerId, List<Card>> entry : playerCards.entrySet()) {
|
||||
|
||||
assertEquals(
|
||||
2,
|
||||
entry.getValue().size(),
|
||||
"Player " + entry.getKey() + " must have exactly 2 cards");
|
||||
}
|
||||
|
||||
Set<String> seenCards = new HashSet<>();
|
||||
|
||||
for (List<Card> cards : playerCards.values()) {
|
||||
|
||||
for (Card c : cards) {
|
||||
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
|
||||
assertFalse(seenCards.contains(key), "Duplicate card found: " + key);
|
||||
|
||||
seenCards.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
int potBefore = game.getState().getPot().getAmount();
|
||||
|
||||
game.playerRaise(PlayerId.of("Lars"), 1200);
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
game.playerFold(PlayerId.of("Mathis"));
|
||||
game.playerCall(PlayerId.of("Jona"));
|
||||
|
||||
System.out.println("Pot after actions: " + game.getState().getPot().getAmount());
|
||||
|
||||
int potAfter = game.getState().getPot().getAmount();
|
||||
|
||||
assertTrue(potAfter >= potBefore, "The pot must be larger after actions");
|
||||
|
||||
game.dealFlop();
|
||||
|
||||
assertEquals(3, game.getCommunityCards().size(), "The flop must have 3 cards");
|
||||
|
||||
game.dealTurn();
|
||||
|
||||
assertEquals(4, game.getCommunityCards().size(), "A turn must result in 4 cards");
|
||||
|
||||
game.dealRiver();
|
||||
|
||||
assertEquals(5, game.getCommunityCards().size(), "The river must consist of 5 cards");
|
||||
|
||||
for (Card c : game.getCommunityCards()) {
|
||||
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
|
||||
assertFalse(seenCards.contains(key), "Duplicate board card detected: " + key);
|
||||
|
||||
seenCards.add(key);
|
||||
}
|
||||
|
||||
assertTrue(seenCards.size() <= 52, "There can be a maximum of 52 cards");
|
||||
|
||||
assertTrue(game.getState().getPot().getAmount() > 0, "The pot must contain chips");
|
||||
|
||||
for (Player p : game.getState().getPlayers()) {
|
||||
|
||||
assertTrue(p.getChips() >= 0, "A player may not have any negative chips: " + p.getId());
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
4, game.getState().getPlayers().size(), "The number of players must not change");
|
||||
|
||||
for (Player p : game.getState().getPlayers()) {
|
||||
|
||||
LOGGER.info(p.getId() + " | Chips: " + p.getChips());
|
||||
}
|
||||
|
||||
LOGGER.info("Pot: " + game.getState().getPot().getAmount());
|
||||
LOGGER.info("Board: " + game.getCommunityCards());
|
||||
|
||||
LOGGER.info("Test 2 was successfully completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test simulates a specific game scenario where Julian is expected to win with a Two Pair
|
||||
* hand. It sets up a fixed deck to ensure that the desired cards are dealt to the players and
|
||||
* verifies that the hand evaluation correctly identifies Julian as the winner.
|
||||
*/
|
||||
@Test
|
||||
public void testFullPokerGameSimulation3() {
|
||||
|
||||
GameState state = new GameState();
|
||||
|
||||
GameEngine engine =
|
||||
new GameEngine(
|
||||
state,
|
||||
new RuleEngine(new ArrayList<>()),
|
||||
new RoundManager(),
|
||||
new TurnManager());
|
||||
|
||||
GameController game = new GameController(engine);
|
||||
|
||||
game.addPlayer(PlayerId.of("Julian"), 20000);
|
||||
game.addPlayer(PlayerId.of("Mathis"), 20000);
|
||||
game.addPlayer(PlayerId.of("Jona"), 20000);
|
||||
game.addPlayer(PlayerId.of("Lars"), 20000);
|
||||
|
||||
Deck deck = new Deck();
|
||||
List<Card> cards = new ArrayList<>();
|
||||
|
||||
cards.add(new Card(Suit.CLUBS, Rank.SEVEN));
|
||||
cards.add(new Card(Suit.HEARTS, Rank.TWO));
|
||||
cards.add(new Card(Suit.CLUBS, Rank.FIVE));
|
||||
cards.add(new Card(Suit.SPADES, Rank.FIVE));
|
||||
cards.add(new Card(Suit.HEARTS, Rank.KING));
|
||||
|
||||
cards.add(new Card(Suit.DIAMONDS, Rank.ACE));
|
||||
cards.add(new Card(Suit.SPADES, Rank.ACE));
|
||||
|
||||
cards.add(new Card(Suit.CLUBS, Rank.NINE));
|
||||
cards.add(new Card(Suit.DIAMONDS, Rank.TEN));
|
||||
|
||||
cards.add(new Card(Suit.CLUBS, Rank.SEVEN));
|
||||
cards.add(new Card(Suit.SPADES, Rank.TWO));
|
||||
|
||||
cards.add(new Card(Suit.CLUBS, Rank.KING));
|
||||
cards.add(new Card(Suit.HEARTS, Rank.KING));
|
||||
|
||||
deck.setCards(cards);
|
||||
state.setDeck(deck);
|
||||
|
||||
game.startGame();
|
||||
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
game.playerFold(PlayerId.of("Mathis"));
|
||||
game.playerCall(PlayerId.of("Jona"));
|
||||
game.playerRaise(PlayerId.of("Lars"), 1200);
|
||||
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
game.playerCall(PlayerId.of("Jona"));
|
||||
|
||||
game.dealFlop();
|
||||
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
game.playerFold(PlayerId.of("Jona"));
|
||||
|
||||
game.dealTurn();
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
|
||||
game.dealRiver();
|
||||
game.playerCall(PlayerId.of("Julian"));
|
||||
|
||||
List<Card> board = game.getCommunityCards();
|
||||
|
||||
List<Card> julian = state.getHoleCards(PlayerId.of("Julian"));
|
||||
List<Card> lars = state.getHoleCards(PlayerId.of("Lars"));
|
||||
|
||||
List<Card> julianCards = new ArrayList<>(board);
|
||||
julianCards.addAll(julian);
|
||||
|
||||
List<Card> larsCards = new ArrayList<>(board);
|
||||
larsCards.addAll(lars);
|
||||
|
||||
HandRank julianRank = HandEvaluator.evaluate(julianCards);
|
||||
HandRank larsRank = HandEvaluator.evaluate(larsCards);
|
||||
|
||||
assertTrue(julianRank.compareTo(larsRank) > 0);
|
||||
|
||||
LOGGER.info("Correct scenario: Julian wins with two pair.");
|
||||
LOGGER.info("Test 3 was successfully completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test simulates a full poker game, including player actions, card dealing, and hand
|
||||
* evaluation. It verifies that the game state is updated correctly throughout the game and that
|
||||
* the expected outcomes are achieved.
|
||||
*/
|
||||
@Test
|
||||
public void testFullPokerGameSimulation4() {
|
||||
|
||||
GameState state = new GameState();
|
||||
|
||||
GameEngine engine =
|
||||
new GameEngine(
|
||||
state,
|
||||
new RuleEngine(new ArrayList<>()),
|
||||
new RoundManager(),
|
||||
new TurnManager());
|
||||
|
||||
GameController game = new GameController(engine);
|
||||
|
||||
game.addPlayer(PlayerId.of("Julian"), 5000);
|
||||
game.addPlayer(PlayerId.of("Mathis"), 5000);
|
||||
game.addPlayer(PlayerId.of("Jona"), 5000);
|
||||
game.addPlayer(PlayerId.of("Lars"), 5000);
|
||||
|
||||
assertEquals(4, game.getState().getPlayers().size());
|
||||
|
||||
game.startGame();
|
||||
|
||||
Map<PlayerId, List<Card>> cards = game.getPlayerCards();
|
||||
|
||||
assertEquals(4, cards.size());
|
||||
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (List<Card> list : cards.values()) {
|
||||
assertEquals(2, list.size());
|
||||
|
||||
for (Card c : list) {
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
assertFalse(seen.contains(key), "Duplicate hole card detected: " + key);
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
int potBefore = game.getState().getPot().getAmount();
|
||||
|
||||
game.playerRaise(PlayerId.of("Julian"), 300);
|
||||
game.playerCall(PlayerId.of("Mathis"));
|
||||
game.playerFold(PlayerId.of("Jona"));
|
||||
game.playerCall(PlayerId.of("Lars"));
|
||||
|
||||
for (Player p : game.getState().getPlayers()) {
|
||||
assertTrue(p.getChips() >= 0, "Negative chips detected: " + p.getId());
|
||||
}
|
||||
|
||||
assertTrue(game.getState().getPot().getAmount() >= potBefore);
|
||||
|
||||
game.dealFlop();
|
||||
assertEquals(3, game.getCommunityCards().size());
|
||||
|
||||
game.dealTurn();
|
||||
assertEquals(4, game.getCommunityCards().size());
|
||||
|
||||
game.dealRiver();
|
||||
assertEquals(5, game.getCommunityCards().size());
|
||||
|
||||
Set<String> boardSeen = new HashSet<>();
|
||||
|
||||
for (Card c : game.getCommunityCards()) {
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
|
||||
assertFalse(boardSeen.contains(key), "Duplicate board card: " + key);
|
||||
assertFalse(seen.contains(key), "Board card already used in hole cards: " + key);
|
||||
|
||||
boardSeen.add(key);
|
||||
}
|
||||
|
||||
assertTrue(game.getState().getPot().getAmount() > 0, "Pot must not be zero");
|
||||
|
||||
assertEquals(4, game.getState().getPlayers().size(), "Player count must remain stable");
|
||||
|
||||
long activePlayers =
|
||||
game.getState().getPlayers().stream().filter(p -> p.getChips() > 0).count();
|
||||
|
||||
assertTrue(activePlayers >= 1, "At least one player must still have chips");
|
||||
|
||||
assertNotNull(game.getState().getPhase());
|
||||
|
||||
LOGGER.info("Test 4 was successfully completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test simulates a full poker game, including player actions, card dealing, and hand
|
||||
* evaluation. It verifies that the game state is updated correctly throughout the game and that
|
||||
* the expected outcomes are achieved.
|
||||
*/
|
||||
@Test
|
||||
public void testFullPokerGameSimulation5() {
|
||||
|
||||
GameState state = new GameState();
|
||||
|
||||
GameEngine engine =
|
||||
new GameEngine(
|
||||
state,
|
||||
new RuleEngine(new ArrayList<>()),
|
||||
new RoundManager(),
|
||||
new TurnManager());
|
||||
|
||||
GameController game = new GameController(engine);
|
||||
|
||||
game.addPlayer(PlayerId.of("ShortStack"), 500);
|
||||
game.addPlayer(PlayerId.of("MidStack"), 2000);
|
||||
game.addPlayer(PlayerId.of("BigStack"), 5000);
|
||||
game.addPlayer(PlayerId.of("Caller"), 2000);
|
||||
|
||||
game.startGame();
|
||||
|
||||
game.playerRaise(PlayerId.of("BigStack"), 1000);
|
||||
game.playerCall(PlayerId.of("Caller"));
|
||||
game.playerCall(PlayerId.of("MidStack"));
|
||||
game.playerCall(PlayerId.of("ShortStack"));
|
||||
|
||||
for (Player p : game.getState().getPlayers()) {
|
||||
assertTrue(p.getChips() >= 0, "Negative chips detected for " + p.getId());
|
||||
}
|
||||
|
||||
assertTrue(game.getState().getPot().getAmount() > 0);
|
||||
|
||||
game.playerRaise(PlayerId.of("BigStack"), 100);
|
||||
|
||||
int pot = game.getState().getPot().getAmount();
|
||||
assertTrue(pot > 0, "Pot must remain valid after re-raises");
|
||||
|
||||
game.playerFold(PlayerId.of("Caller"));
|
||||
game.playerFold(PlayerId.of("MidStack"));
|
||||
|
||||
long activePlayers =
|
||||
game.getState().getPlayers().stream().filter(p -> p.getChips() > 0).count();
|
||||
|
||||
assertTrue(activePlayers >= 1, "At least one player must remain");
|
||||
|
||||
game.dealFlop();
|
||||
assertEquals(3, game.getCommunityCards().size());
|
||||
|
||||
game.dealTurn();
|
||||
assertEquals(4, game.getCommunityCards().size());
|
||||
|
||||
game.dealRiver();
|
||||
assertEquals(5, game.getCommunityCards().size());
|
||||
|
||||
Set<String> allCards = new HashSet<>();
|
||||
|
||||
for (List<Card> hand : game.getPlayerCards().values()) {
|
||||
for (Card c : hand) {
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
assertFalse(allCards.contains(key), "Duplicate hole card: " + key);
|
||||
allCards.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (Card c : game.getCommunityCards()) {
|
||||
String key = c.getRank() + "-" + c.getSuit();
|
||||
assertFalse(allCards.contains(key), "Card reused from hole cards: " + key);
|
||||
allCards.add(key);
|
||||
}
|
||||
|
||||
assertEquals(4, game.getState().getPlayers().size());
|
||||
assertNotNull(game.getState().getPhase());
|
||||
assertTrue(game.getState().getPot().getAmount() > 0);
|
||||
|
||||
PlayerId winner = game.determineWinner();
|
||||
|
||||
assertNotNull(winner, "Winner should not be null");
|
||||
|
||||
assertTrue(game.getPlayerCards().containsKey(winner), "Winner must be a valid player");
|
||||
|
||||
LOGGER.info("Winner determined: " + winner);
|
||||
|
||||
LOGGER.info("Test 5 was successfully completed");
|
||||
}
|
||||
}
|
||||