Compare commits
233 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfd13b43fc | |||
| 5916c26b86 | |||
| d734f815e8 | |||
| c5518d64a3 | |||
| f1e09cb328 | |||
| b06cd571b0 | |||
| 3ee1e63577 | |||
| 8f4ce1db80 | |||
| 066d4ee8c3 | |||
| 5ed455c20d | |||
| 938ef71c2a | |||
| 09e8822bef | |||
| 35297775dc | |||
| 77dad54c84 | |||
| 983d31c963 | |||
| 4071be3341 | |||
| 74497a1e8e | |||
| afef96ce21 | |||
| bb1e026a1e | |||
| a96c050c55 | |||
| a164f87379 | |||
| f59f5c5930 | |||
| 627c38da6f | |||
| 239dfa714e | |||
| 40d623fbb7 | |||
| 1d500e6973 | |||
| c42dc09c0e | |||
| 5e15ad359d | |||
| 2350ddf1c4 | |||
| 62bc75b46b | |||
| 8bcd4dd033 | |||
| 0afa1d65e9 | |||
| 259173a809 | |||
| 7ca432fae6 | |||
| a99cf16c7e | |||
| e5f814b853 | |||
| 5abb46b915 | |||
| 61e77be35c | |||
| 01934b76f3 | |||
| 0ddb65e870 | |||
| 795830cf02 | |||
| 3de20e3d6c | |||
| e23a9817e2 | |||
| 1b3ba0000a | |||
| 6ee2c4f901 | |||
| eb2a679868 | |||
| 15062df711 | |||
| ffca8e5e29 | |||
| 77d9ded3d3 | |||
| ebb2e267bf | |||
| a62da1235d | |||
| cf09a70fb8 | |||
| a747c2de56 | |||
| b60374f7e5 | |||
| 160ad8f2bb | |||
| 06c5e5ab83 | |||
| d4722989d9 | |||
| 8a2d0607f8 | |||
| 7c9cf3e00d | |||
| 5ab29a3337 | |||
| 4789273d8f | |||
| 4c10bc7ab0 | |||
| dfbaaac4f0 | |||
| 78e0599182 | |||
| 822dfe778d | |||
| ee694b8168 | |||
| 36f07880e5 | |||
| 22dc2c7e54 | |||
| 9a7b1c4dea | |||
| 8376f681b1 | |||
| fa0ac684f1 | |||
| 4ff28932b4 | |||
| fee71a8a2c | |||
| 708f6c5773 | |||
| be173f2847 | |||
| 7eda64b5c3 | |||
| 8580a2803c | |||
| 68a69b865e | |||
| 60e15e12fa | |||
| d757c2a317 | |||
| 2152560dea | |||
| 101e21d568 | |||
| 6693d57688 | |||
| aca8924f15 | |||
| d3009a422c | |||
| 5304128094 | |||
| 828e2f3131 | |||
| bf7ac4fc34 | |||
| a9bf0635a2 | |||
| ae3db3f829 | |||
| 25837fc869 | |||
| d4e69d26d7 | |||
| 6456e86f07 | |||
| b043127ced | |||
| f7dc3d1a8e | |||
| 585a78fbcc | |||
| 769105b87d | |||
| a20ba7cef5 | |||
| fb71c6de7b | |||
| 041a5f135a | |||
| 700e8de39d | |||
| a0319af3b3 | |||
| b8a0c224cf | |||
| b99facab3b | |||
| 6d19b89351 | |||
| b9a8448b53 | |||
| 59c47267ad | |||
| f971e6ad5b | |||
| 4317b63a87 | |||
| 413999a226 | |||
| ee1e474a9d | |||
| 8c0dd1a481 | |||
| 3640c1117d | |||
| e86de25b34 | |||
| 5568c8a073 | |||
| 93dba00a4d | |||
| de11d673dc | |||
| 72e3d257ff | |||
| 5ef2fa5df1 | |||
| 5fa478c440 | |||
| 9087f009d3 | |||
| bfbf4b2015 | |||
| ea26e371c2 | |||
| cf64f11912 | |||
| 415ad754df | |||
| a9878da3f2 | |||
| f91fa698f3 | |||
| 5b70fb89ef | |||
| 7a9114045f | |||
| 95e3474c8c | |||
| 528a40b394 | |||
| f95d9174f1 | |||
| 07587340d4 | |||
| 3b44e58505 | |||
| 35c8590c57 | |||
| f2df06d21a | |||
| 10347b3ed4 | |||
| 87b2dcfc2c | |||
| 707477bd73 | |||
| dafa95af41 | |||
| b445b019a1 | |||
| 92a2ffe054 | |||
| 948484853c | |||
| 521982f7fc | |||
| 0e9cf6ae74 | |||
| 120b55f5cc | |||
| 5dd620116a | |||
| f4c20e1725 | |||
| 0c6595a58b | |||
| 936463c8f1 | |||
| 4c203b279c | |||
| 983f2cca5e | |||
| 56ba5b81ae | |||
| c600b6422d | |||
| 20e8491cff | |||
| 01a72a6719 | |||
| 3b7b53d973 | |||
| df0f8ca44c | |||
| ae98334c56 | |||
| 4deeace547 | |||
| 68e8bc76b2 | |||
| cb65ad2ed0 | |||
| 1d322f5cd8 | |||
| 40ea45460e | |||
| 60f326c4d5 | |||
| 2906c59f25 | |||
| 6736345cbe | |||
| 307c234c91 | |||
| 2ad2981724 | |||
| 945418798b | |||
| 34d782e252 | |||
| 665e006b89 | |||
| da23a30ca5 | |||
| d762f7bb2b | |||
| 605260f0d0 | |||
| 27913854c5 | |||
| 56667bfb12 | |||
| 3a0248e0c3 | |||
| ecd66a4ee9 | |||
| ae8eccb158 | |||
| 7a29ce0b3b | |||
| 865d5bb0dc | |||
| 8ee5114d37 | |||
| c3bf2d4bb0 | |||
| 874df242ab | |||
| 06e49a756c | |||
| b88fdceb9f | |||
| 71d417c6da | |||
| 595800a469 | |||
| 701fa9b6ed | |||
| b0b8e3b1d0 | |||
| 3af362a9e5 | |||
| 65db63d3a1 | |||
| 9a81c6f1af | |||
| 972e46f4d0 | |||
| 4e9082f106 | |||
| e210bd1d49 | |||
| 238cf937f4 | |||
| e07f107384 | |||
| 600d10286f | |||
| 2cd4324c68 | |||
| 45a6b87303 | |||
| c6ac3d4aff | |||
| 73a80deb12 | |||
| 2203f80490 | |||
| 059b706485 | |||
| b588f38cd5 | |||
| 0930fbe990 | |||
| 86b4a6c191 | |||
| 6e446b665a | |||
| 442a846c71 | |||
| e0b791589c | |||
| cf0cd2666c | |||
| ba6ff36ee2 | |||
| ed24ba7214 | |||
| 52daf4f2e0 | |||
| 050b349638 | |||
| d4b5010158 | |||
| f9519a6eb5 | |||
| 8b8ac9ccc0 | |||
| 4dcade3daf | |||
| f78e9dfa38 | |||
| af37e33f07 | |||
| 3d8bad2427 | |||
| c88c809a2e | |||
| ab851113e0 | |||
| 4dd9c56e02 | |||
| 0e7507f2c4 | |||
| 52bbbd6181 | |||
| 1727aec42c | |||
| 8639fa420f | |||
| 85f89a91f1 | |||
| 08b85af147 |
@@ -48,6 +48,9 @@ gradle-app.setting
|
||||
# JDT-specific (Eclipse Java Development Tools)
|
||||
.classpath
|
||||
|
||||
# Gradle properties
|
||||
gradle.properties
|
||||
|
||||
## MacOS
|
||||
# General
|
||||
.DS_Store
|
||||
@@ -122,4 +125,28 @@ $RECYCLE.BIN/
|
||||
|
||||
## bin
|
||||
bin/
|
||||
gradle.properties
|
||||
|
||||
# LaTeX (TeX)
|
||||
## Core latex/pdflatex auxiliary files:
|
||||
*.aux
|
||||
*.lof
|
||||
*.log
|
||||
*.lot
|
||||
*.fls
|
||||
*.out
|
||||
*.toc
|
||||
*.fmt
|
||||
*.fot
|
||||
*.cb
|
||||
*.cb2
|
||||
.*.lb
|
||||
|
||||
## Build tool auxiliary files:
|
||||
*.fdb_latexmk
|
||||
*.synctex
|
||||
*.synctex(busy)
|
||||
*.synctex.gz
|
||||
*.synctex.gz(busy)
|
||||
*.pdfsync
|
||||
*.rubbercache
|
||||
rubber.cache
|
||||
|
||||
@@ -92,6 +92,14 @@ compile-check:
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
allow_failure: false
|
||||
|
||||
javadoc-check:
|
||||
<<: [*gradle-cache, *on-mr]
|
||||
stage: build
|
||||
image: gradle:9.3.1-jdk25
|
||||
script:
|
||||
- gradle javaDoc --configuration-cache --configuration-cache-problems=warn
|
||||
needs: []
|
||||
|
||||
test:
|
||||
<<: *gradle-cache
|
||||
stage: test
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
### Checklist
|
||||
- [ ] I reproduced the problem using the steps above
|
||||
- [ ] I searched documentation docs for relevant information
|
||||
- [ ] I searched documentation for relevant information
|
||||
- [ ] I added relevant labels
|
||||
|
||||
/label ~bug
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<!-- Detailed description of the desired behavior -->
|
||||
|
||||
### Checklist
|
||||
- [ ] I reproduced the problem using the steps above
|
||||
- [ ] I searched documentation docs for relevant information
|
||||
- [ ] I have described the function in detail
|
||||
- [ ] I searched docs for alternative implementations matching my needs
|
||||
- [ ] I added relevant labels
|
||||
|
||||
/label ~enhancement
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## Milestone Achievement
|
||||
<!-- The recommended type is: Task -->
|
||||
|
||||
### Category
|
||||
<!-- Category of the milestone (Process, Product, Presentation) -->
|
||||
|
||||
### Title
|
||||
<!-- Title of the milestone (equal to the title in the milestone catalog (https://p9.dmi.unibas.ch/cs108/2026) -->
|
||||
|
||||
### Rewarded points on completion
|
||||
<!-- Number of points rewarded on completion of the milestone -->
|
||||
|
||||
### Description of milestone
|
||||
<!-- Description of the milestone (equal to the description in the milestone catalog (https://p9.dmi.unibas.ch/cs108/2026) -->
|
||||
|
||||
/label ~achievement
|
||||
@@ -8,9 +8,15 @@
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Export puml to SVG",
|
||||
"label": "Export PlantUML to SVG",
|
||||
"type": "shell",
|
||||
"command": "./scripts/export-plantuml.sh",
|
||||
"command": "./scripts/export-plantuml-to-svg.sh",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Export PlantUML to PNG",
|
||||
"type": "shell",
|
||||
"command": "./scripts/export-plantuml-to-png.sh",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
|
||||
@@ -12,6 +12,7 @@ Since this project is part of a course at the University of Basel, the [Code of
|
||||
- [Creating an issue](#creating-an-issue)
|
||||
- [During implementation](#during-implementation)
|
||||
- [Collaborative work](#collaborative-work)
|
||||
- [Milestone Achievements](#milestone-achievements)
|
||||
- [Git Workflow](#git-workflow)
|
||||
- [Creating a branch](#creating-a-branch)
|
||||
- [Working on a branch](#working-on-a-branch)
|
||||
@@ -49,6 +50,20 @@ Issue or Task in GitLab **before** any implementation begins.
|
||||
centrally visible and searchable.
|
||||
- Before starting work that overlaps with an existing issue, check its comment thread first to avoid duplicating effort.
|
||||
|
||||
### Milestone Achievements
|
||||
For every process and product-related milestone achievement, there is a dedicated milestone task.
|
||||
|
||||
- **Do not create a branch directly from a milestone task.**
|
||||
- Instead, create a normal implementation task (using the regular task template) and reference the milestone task there.
|
||||
- In the merge request, reference the milestone task again.
|
||||
- If the milestone condition is fully met, you may use `Closing #<id>` to close the milestone task.
|
||||
- If it is only partially addressed, use `Relates to #<id>` or `Contributes to #<id>` so the milestone task stays open.
|
||||
|
||||
Milestone tasks may, but do not have to, be assigned to a specific person.
|
||||
|
||||
- If multiple people are involved, contribution is tracked through linked tasks that reference the milestone task.
|
||||
- For small topics, a Milestone Achievement may be assigned to one person. Others should only contribute on request and should not modify components introduced under that achievement without coordination.
|
||||
|
||||
|
||||
## Git Workflow
|
||||
We use a **feature branch -> main** strategy. The `main` branch is always in a releasable state.
|
||||
|
||||
@@ -108,3 +108,17 @@ tasks.register('fatJar', Jar) {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
})
|
||||
}
|
||||
|
||||
tasks.register('javadocJar', Jar) {
|
||||
group = 'build'
|
||||
description = 'Assembles a Javadoc JAR.'
|
||||
dependsOn tasks.named('javadoc')
|
||||
archiveClassifier = 'javadoc'
|
||||
from(tasks.javadoc.destinationDir)
|
||||
}
|
||||
|
||||
tasks.register('build-cs108') {
|
||||
group = 'build'
|
||||
description = 'Produces executable JAR and Javadoc JAR for CS108.'
|
||||
dependsOn tasks.named('fatJar'), tasks.named('javadocJar')
|
||||
}
|
||||
|
||||
@@ -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,35 @@
|
||||
# Commands
|
||||
Commands are the primary extension point of the server.
|
||||
Every client-facing operation e.g. checking a username, sending a chat message, joining a lobby is implemented as a command.
|
||||
Each command consists of four classes: a **Parser**, a **Request**, a **Handler**, and a **Response**.
|
||||
|
||||
The infrastructure for routing and dispatching commands lives in the `network/` layer and is intentionally kept generic.
|
||||
The concrete implementations for each command live in `app/commands/` and are wired together at startup in `ServerApp`.
|
||||
|
||||

|
||||
|
||||
## Contents
|
||||
### Guides
|
||||
- [Implementing a Command](./guide-on-implementing-a-command.md) -
|
||||
Step-by-step walkthrough for adding a new command to the server, including registration and common pitfalls.
|
||||
|
||||
### Reference
|
||||
- [Command Infrastructure](./commands-deep-dive.md) -
|
||||
Technical deep-dive into the `CommandParser`, `CommandParserDispatcher`, `CommandHandler`, `CommandRouter`, `Request`, and the response hierarchy.
|
||||
|
||||
### Protocol document
|
||||
- [Protocol Document](./protocol-document.md) -
|
||||
List of all supported commands by the server, along with descriptions, required pre-execution checks, and an example for both request and response.
|
||||
|
||||
## Key Concepts
|
||||
**Each command is self-contained.**
|
||||
A command's Parser, Request, Handler, and Response all live in the same package under `app/commands/<name>/`.
|
||||
This keeps related code co-located and makes it easy to reason about a single command without navigating across multiple directories.
|
||||
|
||||
**The `network/` layer knows nothing about specific commands.**
|
||||
`CommandParser` and `CommandHandler` are generic interfaces. The `CommandParserDispatcher` and `CommandRouter` operate on those interfaces.
|
||||
Adding a new command **never** requires modifying infrastructure code.
|
||||
|
||||
**Registration happens at the composition root.**
|
||||
All commands are wired in `ServerApp` by calling `parserDispatcher.register(...)` and `commandRouter.register(...)`.
|
||||
This keeps the wiring explicit and compiler-checked.
|
||||
@@ -0,0 +1,128 @@
|
||||
# Command Infrastructure
|
||||
This document describes the generic command infrastructure that lives in the `network/` layer.
|
||||
It covers the parsing pipeline, the routing pipeline, and the base types that every command builds on.
|
||||
|
||||
The infrastructure is intentionally project-agnostic: It has no knowledge of specific commands and is never modified when a new command is added.
|
||||
|
||||
## Overview
|
||||
An incoming request travels through two sequential pipelines: **parsing** and **execution**.
|
||||
|
||||
The parsing pipeline converts a stringly-typed `PrimitiveRequest` into a strongly-typed `Request` subclass.
|
||||
The execution pipeline routes that typed request to the correct handler, which produces a `Response`.
|
||||
|
||||
<img src="../../../images/docs/networking/commands/sequence_diagram.png" alt="Sequence diagram of all involved components to process and respond to an incoming request" />
|
||||
|
||||
## Parsing Pipeline
|
||||
### `CommandParser<T extends Request>`
|
||||
<img src="../../../images/docs/networking/commands/command_parser.png" alt="Class diagram of the CommandParser" />
|
||||
|
||||
The `CommandParser` is a single-method interface responsible for converting a `PrimitiveRequest` into a concrete, typed `Request` subclass.
|
||||
Implementations live in `app/commands/<name>/` and are registered by name in `CommandParserDispatcher`.
|
||||
|
||||
The parser is the correct place to validate and extract parameters.
|
||||
If a required parameter is absent, `RequestParameterAccessor.require(...)` throws an `MissingParameterException`, which the `SessionReader` catches and converts into a `MISSING_PARAMETER` error response for the client.
|
||||
|
||||
Parsers **do not** perform any domain logic. Their only job is extraction and type conversion.
|
||||
|
||||
### `CommandParserDispatcher`
|
||||
<img src="../../../images/docs/networking/commands/command_parser_dispatcher.png" alt="Class diagram of CommandParserDispatcher" />
|
||||
|
||||
The `CommandParserDispatcher` holds a map from command name strings (e.g. `"PING"`) to their corresponding `CommandParser`.
|
||||
When the `SessionReader` receives a `PrimitiveRequest`, it calls `dispatcher.parse(...)`, which looks up the parser by the request's command string and delegates parsing.
|
||||
|
||||
If no parser is registered for the command name, `parse(...)` throws an `UnknownCommandException`, which `SessionReader` catches and converts into an `UNKNOWN_COMMAND` error response for the client.
|
||||
|
||||
### `RequestParameterAccessor`
|
||||
<img src="../../../images/docs/networking/commands/request_parameter_accessor.png" alt="Class diagram of RequestParameterAccessor" />
|
||||
|
||||
The `RequestParameterAccessor` is a helper provided to parsers for reading typed parameter values from a `PrimitiveRequest`.
|
||||
It indexes the parameter list by key on construction for O(1) lookups.
|
||||
|
||||
```java
|
||||
// Require a parameter — throws MissingParameterException if absent
|
||||
String username = accessor.require("USERNAME");
|
||||
|
||||
// Require and parse — throws ParameterParseException if conversion fails
|
||||
int count = accessor.require("COUNT", Integer::parseInt);
|
||||
|
||||
// Optional with a default
|
||||
String mode = accessor.optional("MODE", "default");
|
||||
```
|
||||
|
||||
The `ThrowingParser<T>` functional interface accepted by the typed overloads allows any checked or unchecked exception to propagate from the conversion function.
|
||||
The `RequestParameterAccessor` wraps it in a `ParameterParseException`.
|
||||
|
||||
## Execution Pipeline
|
||||
### `Request`
|
||||
<img src="../../../images/docs/networking/commands/request.png" alt="Class diagram of the Request" />
|
||||
|
||||
The `Request` is the abstract base class for all typed command requests. It carries a `RequestContext`, an immutable record containing the originating `SessionId` and the numeric `requestId`.
|
||||
Both of which are later used by the handler to direct the response to the correct session.
|
||||
|
||||
Concrete subclasses add command-specific fields, all set via constructor. Requests are immutable value objects. They carry data, not behaviour.
|
||||
|
||||
### `CommandHandler<T extends Request>`
|
||||
<img src="../../../images/docs/networking/commands/command_handler.png" alt="Class diagram of the CommandHandler" />
|
||||
|
||||
The `CommandHandler` is an abstract base class responsible for executing a typed request.
|
||||
The handler contains the domain logic: reading from registries and managers, modifying state, and dispatching a response via the `ResponseDispatcher`.
|
||||
|
||||
Handlers receive their dependencies (the `ResponseDispatcher`, registries and managers etc.) through constructor injection.
|
||||
They can also register reusable pre-execution checks via `addCheck(...)`. These checks are stored on the handler and are evaluated before `execute(...)` runs.
|
||||
|
||||
When a piece of trivial validation is shared by multiple handlers, it should be extracted into a dedicated `HandlerCheck` instead of being duplicated in each handler.
|
||||
|
||||
### `HandlerCheck`
|
||||
<img src="../../../images/docs/networking/commands/handler_check.png" alt="Class diagram of HandlerCheck and CommandHandlerExecutor" />
|
||||
|
||||
The `HandlerCheck` is a functional interface for reusable pre-execution validation.
|
||||
Its `check(...)` method receives the incoming `Request` and returns an empty `Optional` if the request may continue.
|
||||
If the check fails, it returns an `ErrorResponse` wrapped in the `Optional`, which will be dispatched to the client instead of calling the handler.
|
||||
|
||||
This is the right place for small shared checks such as "is the user logged in?" or other simple preconditions that multiple handlers need.
|
||||
|
||||
### `CommandHandlerExecutor`
|
||||
<img src="../../../images/docs/networking/commands/handler_check_executor.png" alt="Class diagram of HandlerCheck and CommandHandlerExecutor" />
|
||||
|
||||
The `CommandHandlerExecutor` runs all checks registered on a handler before invoking `execute(...)`.
|
||||
If any `HandlerCheck` returns a response, the executor dispatches it immediately and aborts execution.
|
||||
Otherwise, the handler is executed normally.
|
||||
|
||||
This keeps precondition handling separate from the actual domain logic inside the handler.
|
||||
|
||||
### `CommandRouter`
|
||||
<img src="../../../images/docs/networking/commands/command_router.png" alt="Class diagram of the CommandRouter" />
|
||||
|
||||
The `CommandRouter` maps `Request` subclasses to their handlers using the request's runtime class as the key.
|
||||
The type safety of `register(...)` ensures that a handler can only be registered for the exact type it is parameterised on.
|
||||
The unchecked cast in `execute(...)` is therefore safe by construction and is documented with a `@SuppressWarnings` comment in the source.
|
||||
|
||||
If no handler is registered for the given request type, `execute(...)` throws `UnknownRequestException`.
|
||||
Unlike `UnknownCommandException` (which covers unknown command strings), this exception indicates a programming error i.e. a parser was registered without a corresponding handler.
|
||||
|
||||
## Response Types
|
||||
<img src="../../../images/docs/networking/commands/response_types.png" alt="Class diagram of the response interface and built-in implementations" />
|
||||
|
||||
The `Response` is the abstract base for all server responses. Its two concrete branches are `SuccessResponse` (prefix `+OK`) and `ErrorResponse` (prefix `-ERR`).
|
||||
Command-specific responses extend `SuccessResponse` and populate the body using the `ResponseBodyBuilder`.
|
||||
|
||||
`OkResponse` is a pre-built convenience subclass of `SuccessResponse` with an empty body, used for commands that need only acknowledge success without returning data (e.g. `PING`).
|
||||
|
||||
The body is built with a fluent `ResponseBodyBuilder`:
|
||||
|
||||
```java
|
||||
// Simple key/value parameters
|
||||
new ResponseBodyBuilder()
|
||||
.param("STATUS", UsernameAvailability.FREE)
|
||||
.build();
|
||||
|
||||
// Nested block
|
||||
new ResponseBodyBuilder()
|
||||
.block("USER", b -> b
|
||||
.param("ID", user.getId().value())
|
||||
.param("NAME", user.getName()))
|
||||
.build();
|
||||
```
|
||||
|
||||
`ResponseEncoder` serialises the body into the wire format (tab-indented, `END`-terminated blocks) and wraps it in a `PrimitiveResponse`.
|
||||
`ResponseDispatcher` then enqueues this into the target session's bounded response queue.
|
||||
@@ -0,0 +1,218 @@
|
||||
# Implementing a Command
|
||||
This guide walks through the full process of adding a new command to the server. By the end you
|
||||
will have a working command with a Parser, Request, Handler, and Response, all correctly wired
|
||||
into the server.
|
||||
|
||||
For background on how these components interact at a technical level, see [Command Infrastructure](../reference/command-infrastructure.md).
|
||||
|
||||
## Before You Start
|
||||
A command consists of exactly four classes, all placed in the same package:
|
||||
|
||||
```
|
||||
app/commands/<your_command>/
|
||||
YourCommandParser.java
|
||||
YourCommandRequest.java
|
||||
YourCommandHandler.java
|
||||
YourCommandResponse.java ← omit this if OkResponse is sufficient
|
||||
```
|
||||
|
||||
Name the package after the command in `snake_case`, matching the wire-protocol name (e.g. `join_lobby` for the `JOIN_LOBBY` command).
|
||||
Name the classes in `UpperCamelCase` with the command name as prefix.
|
||||
|
||||
## Step 1 — Define the Request
|
||||
|
||||
The `Request` subclass is a typed, immutable value object holding everything the handler needs.
|
||||
Define it first, because both the Parser and the Handler depend on it.
|
||||
|
||||
```java
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.greet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
public class GreetRequest extends Request {
|
||||
private final String name;
|
||||
|
||||
public GreetRequest(RequestContext context, String name) {
|
||||
super(context);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rules for the Request class:**
|
||||
|
||||
- Always pass `context` directly to `super(context)`. Never store it in a separate field.
|
||||
- Fields must be `private final`. Set them only through the constructor.
|
||||
- Provide a getter for every field. No setters.
|
||||
- No logic. The request is data, not behaviour.
|
||||
|
||||
|
||||
|
||||
## Step 2 — Implement the Parser
|
||||
|
||||
The Parser extracts parameters from the `PrimitiveRequest` and constructs the typed Request.
|
||||
|
||||
```java
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.greet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
|
||||
public class GreetParser implements CommandParser<GreetRequest> {
|
||||
@Override
|
||||
public GreetRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
String name = accessor.require("NAME");
|
||||
return new GreetRequest(primitiveRequest.context(), name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rules for the Parser class:**
|
||||
|
||||
- Always create a `RequestParameterAccessor` from `primitiveRequest.parameters()`.
|
||||
- Use `accessor.require(key)` for mandatory parameters. It throws `MissingParameterException`
|
||||
automatically — do not write your own null checks.
|
||||
- Use `accessor.optional(key, defaultValue)` for optional parameters.
|
||||
- Use the typed overloads (e.g. `accessor.require("COUNT", Integer::parseInt)`) for non-string
|
||||
parameters. The resulting `ParameterParseException` is handled by `SessionReader`.
|
||||
- Always pass `primitiveRequest.context()` as the first argument to the Request constructor.
|
||||
- No domain logic.
|
||||
|
||||
### Choosing between `require` and `optional`
|
||||
|
||||
| Use | When |
|
||||
|-|-|
|
||||
| `accessor.require(key)` | The command cannot function without this parameter |
|
||||
| `accessor.require(key, parser)` | Same, but the value must be converted to a specific type |
|
||||
| `accessor.optional(key, default)` | The parameter has a sensible default when omitted |
|
||||
| `accessor.optional(key, default, parser)` | Optional + type conversion |
|
||||
|
||||
|
||||
|
||||
## Step 3 — Implement the Response
|
||||
|
||||
If your command returns data, create a dedicated Response class. If it only needs to signal
|
||||
success, use `OkResponse` directly in the handler and skip this step.
|
||||
|
||||
```java
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.greet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
|
||||
|
||||
public class GreetResponse extends SuccessResponse {
|
||||
public GreetResponse(RequestContext context, String greeting) {
|
||||
super(context, new ResponseBodyBuilder()
|
||||
.param("GREETING", greeting)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rules for the Response class:**
|
||||
|
||||
- Extend `SuccessResponse` for successful outcomes, not `Response` directly.
|
||||
- Build the body inline in the `super(...)` call using `ResponseBodyBuilder`. Do not store
|
||||
the builder or body separately.
|
||||
- Use `ResponseBodyBuilder.block(tag, consumer)` to add nested structures when the response
|
||||
carries a list or a complex sub-object.
|
||||
- Parameter keys must be `UPPER_SNAKE_CASE` to match the wire protocol convention.
|
||||
- If you need to return an error (e.g. lobby not found), do not throw — dispatch an
|
||||
`ErrorResponse` from the handler instead (see Step 4).
|
||||
|
||||
|
||||
|
||||
## Step 4 — Implement the Handler
|
||||
|
||||
The Handler contains the domain logic. It reads from registries, modifies state if needed, and
|
||||
always dispatches exactly one response.
|
||||
|
||||
```java
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.greet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
public class GreetHandler implements CommandHandler<GreetRequest> {
|
||||
public final ResponseDispatcher responseDispatcher;
|
||||
|
||||
public GreetHandler(ResponseDispatcher responseDispatcher) {
|
||||
this.responseDispatcher = responseDispatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GreetRequest request) {
|
||||
GreetResponse response = new GreetResponse(request.getContext(), "Hello " + request.getName() + ", nice to meet you");
|
||||
responseDispatcher.dispatch(response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rules for the Handler class:**
|
||||
|
||||
- Declare all dependencies as `private final` fields, injected through the constructor.
|
||||
- Use `addCheck(...)` to attach pre-execution checks that should run before `execute(...)`.
|
||||
- If several handlers share the same trivial logic, such as verifying that the user is logged in,
|
||||
extract that logic into a separate `HandlerCheck` and reuse it instead of duplicating the code.
|
||||
- Always dispatch exactly one response per execution path. Every branch must end with a
|
||||
`responseDispatcher.dispatch(...)` call.
|
||||
- Use `ErrorResponse` for domain-level failures (e.g. entity not found, precondition not met).
|
||||
Do not throw exceptions for expected failure cases.
|
||||
- Use `request.getContext()` when constructing any Response — never construct a `RequestContext`
|
||||
yourself.
|
||||
- Do not call `responseDispatcher.dispatch(...)` more than once in a single `execute` invocation.
|
||||
|
||||
|
||||
|
||||
## Step 5 — Register the Command
|
||||
|
||||
Open `ServerApp.registerCommands(...)` and add two lines: one to register the parser and one to
|
||||
register the handler.
|
||||
|
||||
```java
|
||||
private static void registerCommands(
|
||||
CommandParserDispatcher parserDispatcher,
|
||||
CommandRouter commandRouter,
|
||||
ResponseDispatcher responseDispatcher /* add new dependencies here */) {
|
||||
// ... existing commands ...
|
||||
|
||||
parserDispatcher.register("GREET", new GreetParser());
|
||||
commandRouter.register(GreetRequest.class,
|
||||
new GreetHandler(responseDispatcher));
|
||||
}
|
||||
```
|
||||
|
||||
The string passed to `parserDispatcher.register(...)` must exactly match the command name as
|
||||
sent by the client on the wire, in `UPPER_SNAKE_CASE`.
|
||||
|
||||
> **Important:** Always register both the parser **and** the handler. Registering a parser
|
||||
> without a handler will result in an `UnknownRequestException` at runtime when the command is
|
||||
> received — the parser will succeed, but the router will find no handler for the resulting
|
||||
> request type.
|
||||
|
||||
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Forgetting to pass `context` through to the Response.** The `context` is how the response
|
||||
finds its way back to the right client. Dropping it means the response is dispatched to the
|
||||
wrong session or causes a NullPointerException.
|
||||
|
||||
**Putting domain logic in the Parser.** Parsers run before the request is validated as
|
||||
meaningful. A parser that calls a registry or modifies state creates hidden coupling between the
|
||||
parsing and execution phases and makes the parser difficult to test.
|
||||
|
||||
**Dispatching a response before an early return.** A common mistake is to dispatch an error
|
||||
and then fall through to dispatch a success response as well. Always `return` immediately after
|
||||
dispatching an error.
|
||||
|
||||
**Using a raw string for the error code.** Error codes should be `UPPER_SNAKE_CASE` constant
|
||||
strings that the client can match against programmatically. Avoid spaces or punctuation.
|
||||
@@ -0,0 +1,344 @@
|
||||
# Protocol Document
|
||||
This document describes the protocol for client-server communication in our application. It defines the structure of requests and responses, the supported commands along with their request parameters, response formats, and possible errors.
|
||||
|
||||
|
||||
# Table of Contents
|
||||
- [Protocol Document](#protocol-document)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [General structure of requests](#general-structure-of-requests)
|
||||
- [Preconditions](#preconditions)
|
||||
- [Parsing](#parsing)
|
||||
- [Error Response](#error-response)
|
||||
- [Command dispatching](#command-dispatching)
|
||||
- [Error Response](#error-response-1)
|
||||
- [Command parsing](#command-parsing)
|
||||
- [Error Response](#error-response-2)
|
||||
- [Pre-execution checks](#pre-execution-checks)
|
||||
- [UserLoggedInCheck](#userloggedincheck)
|
||||
- [Error Response](#error-response-3)
|
||||
- [Commands](#commands)
|
||||
- [PING command](#ping-command)
|
||||
- [Required pre-execution checks](#required-pre-execution-checks)
|
||||
- [Request Parameters](#request-parameters)
|
||||
- [Success Response](#success-response)
|
||||
- [Example Request](#example-request)
|
||||
- [Example Response](#example-response)
|
||||
- [CHECK\_USERNAME command](#check_username-command)
|
||||
- [Required pre-execution checks](#required-pre-execution-checks-1)
|
||||
- [Request Parameters](#request-parameters-1)
|
||||
- [Success Response](#success-response-1)
|
||||
- [Example Request](#example-request-1)
|
||||
- [Example Response](#example-response-1)
|
||||
- [LOGIN command](#login-command)
|
||||
- [Required pre-execution checks](#required-pre-execution-checks-2)
|
||||
- [Request Parameters](#request-parameters-2)
|
||||
- [Success Response](#success-response-2)
|
||||
- [Error Response](#error-response-4)
|
||||
- [Example Request](#example-request-2)
|
||||
- [Example Response](#example-response-2)
|
||||
- [LOGOUT command](#logout-command)
|
||||
- [Required pre-execution checks](#required-pre-execution-checks-3)
|
||||
- [Request Parameters](#request-parameters-3)
|
||||
- [Success Response](#success-response-3)
|
||||
- [Error Response](#error-response-5)
|
||||
- [Example Request](#example-request-3)
|
||||
- [Example Response](#example-response-3)
|
||||
- [LIST\_USERS command](#list_users-command)
|
||||
- [Required pre-execution checks](#required-pre-execution-checks-4)
|
||||
- [Request Parameters](#request-parameters-4)
|
||||
- [Success Response](#success-response-4)
|
||||
- [Error Response](#error-response-6)
|
||||
- [Example Request](#example-request-4)
|
||||
- [Example Response](#example-response-4)
|
||||
|
||||
<!-- Please see the comments for copy ‚ n' paste ready examples -->
|
||||
|
||||
# General structure of requests
|
||||
As mentioned before, our protocol is based on POP3.
|
||||
Each command is represented as a single line of text, starting with the command name followed by parameters. The server responds with a status line indicating success or failure, followed by the body.
|
||||
|
||||
Requests can have parameters that provide additional information for the command. Parameters are key-value pairs separated by an equal sign (`=`).
|
||||
|
||||
Responses are collections of key-value pairs, containing either a value or another collection, allowing for nested structures.
|
||||
Each collection is ended with the `END` keyword.
|
||||
|
||||
|
||||
|
||||
# Preconditions
|
||||
The serverside pipeline to process incoming requests consists of multiple stages.
|
||||
Each of these stages can yield an error response if the request does not meet the requirements of that stage.
|
||||
|
||||
## Parsing
|
||||
One of these stages is the parsing. It is responsible for parsing the raw request into a structured format that can be easily processed by the command handlers. It validates the syntax of the request as well.
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :-------------- | :---------------------------------------------------------------------------------------------- |
|
||||
| `PARSING_ERROR` | The body of the request contains syntax errors (see message field of response for more details) |
|
||||
|
||||
|
||||
## Command dispatching
|
||||
After the request has been successfully parsed, the next stage is to dispatch the `PrimitiveRequest` to the appropriate `CommandParser`. This is done by the `CommandDispatcherDispatcher`, which uses the command name to determine which parser to use.
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :---------------- | :---------------------------------------------------------------- |
|
||||
| `UNKNOWN_COMMAND` | The command is unknown to this server. No parser has been defined |
|
||||
|
||||
|
||||
## Command parsing
|
||||
Once the `PrimitiveRequest` has been dispatched to the appropriate `CommandParser`, the parser is responsible for parsing the parameters of the request and creating a `Request` that can be executed by the responsible `CommandHandler`.
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :------------------ | :------------------------------------------------------------------------------------------------ |
|
||||
| `MISSING_PARAMETER` | A required parameter is missing from the request (see message field of response for more details) |
|
||||
|
||||
|
||||
|
||||
# Pre-execution checks
|
||||
Pre-execution checks are reusable validation steps that can be registered on command handlers.
|
||||
They are implemented as `HandlerCheck` instances and are executed by the `CommandHandlerExecutor` before the handler's main logic is invoked.
|
||||
|
||||
<!--
|
||||
## Name of the check
|
||||
Description of the check, what it does and when it should be used.
|
||||
|
||||
### Response if not met
|
||||
| Code | Description |
|
||||
| :----------- | :----------------------- |
|
||||
| `ERROR_CODE` | Description of the error |
|
||||
-->
|
||||
|
||||
## UserLoggedInCheck
|
||||
The `UserLoggedInCheck` is a common pre-execution check that verifies whether the user is logged in (i.e. has a user associated with his session).
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :------------------- | :------------------------ |
|
||||
| `USER_NOT_LOGGED_IN` | The user is not logged in |
|
||||
|
||||
|
||||
|
||||
# Commands
|
||||
Commands are the core of our protocol, representing the various actions that clients can request from the server. Each command has a unique name and may require specific parameters in addition to pre-execution checks.
|
||||
The server processes these commands and responds accordingly.
|
||||
|
||||
<!--
|
||||
## Name of the command
|
||||
Description of the command, what it does, and when it should be used.
|
||||
|
||||
### Required pre-execution checks
|
||||
- [`Check1`](#check1)
|
||||
|
||||
### Request Parameters
|
||||
| Parameter Name | Type | Optional | Description |
|
||||
| :------------- | :----- | :---------------------------------- | :-------------------- |
|
||||
| `param1` | `type` | If the parameter is optional or not | Description of param1 |
|
||||
|
||||
### Success Response
|
||||
| Field | Type | Description |
|
||||
| :------- | :------------------ | :-------------------- |
|
||||
| `field1` | `type` | Description of field1 |
|
||||
| `field2` | `Collection<type2>` | |
|
||||
| `field3` | `Enum<type3>` | Description of field3 |
|
||||
|
||||
| Fields of `type2` | Type | Description |
|
||||
| :---------------- | :----- | :-------------------- |
|
||||
| `field1` | `type` | Description of field1 |
|
||||
|
||||
| Members of `type` | Description |
|
||||
| :---------------- | :--------------------- |
|
||||
| `MEMBER1` | Description of member1 |
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :----------- | :----------------------- |
|
||||
| `ERROR_CODE` | Description of the error |
|
||||
|
||||
### Example Request
|
||||
```
|
||||
COMMAND_NAME PARAM1='value1' PARAM2='value2'
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```
|
||||
+OK
|
||||
KEY1=VALUE1
|
||||
FIELDS
|
||||
FIELD
|
||||
NESTED_KEY=NESTED_VALUE
|
||||
END
|
||||
KEY2=VALUE2
|
||||
END
|
||||
```
|
||||
-->
|
||||
|
||||
## PING command
|
||||
The `PING` command is a simple command that can be used to check if the server is responsive.
|
||||
|
||||
### Required pre-execution checks
|
||||
None.
|
||||
|
||||
### Request Parameters
|
||||
No parameters.
|
||||
|
||||
### Success Response
|
||||
No response fields.
|
||||
|
||||
### Example Request
|
||||
```
|
||||
PING
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```
|
||||
+OK
|
||||
END
|
||||
```
|
||||
|
||||
|
||||
## CHECK_USERNAME command
|
||||
The `CHECK_USERNAME` command is used to check if a username is already taken by another user. Additional users can still log in with the same username, but their name will be substituted with a suffix.
|
||||
|
||||
### Required pre-execution checks
|
||||
None.
|
||||
|
||||
### Request Parameters
|
||||
| Parameter Name | Type | Optional | Description |
|
||||
| :------------- | :------- | :------- | :------------------------------------- |
|
||||
| `USERNAME` | `String` | no | The username to check for availability |
|
||||
|
||||
### Success Response
|
||||
| Field | Type | Description |
|
||||
| :------- | :--------------------------- | :---------------------------------------------------------------------- |
|
||||
| `STATUS` | `Enum<UsernameAvailability>` | Member of enum indicating if the username is available or already taken |
|
||||
|
||||
| Members of `UsernameAvailability` | Description |
|
||||
| :-------------------------------- | :------------------------- |
|
||||
| `FREE` | Username is available |
|
||||
| `TAKEN` | Username is already in use |
|
||||
|
||||
### Example Request
|
||||
```
|
||||
CHECK_USERNAME USERNAME='Lars'
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```
|
||||
+OK
|
||||
STATUS=FREE
|
||||
END
|
||||
```
|
||||
|
||||
## LOGIN command
|
||||
The `LOGIN` command is used to log in a user with a specified username. If the username is already taken by another user, the server will append a suffix to the username to make it unique.
|
||||
|
||||
### Required pre-execution checks
|
||||
None.
|
||||
|
||||
### Request Parameters
|
||||
| Parameter Name | Type | Optional | Description |
|
||||
| :------------- | :------- | :------- | :----------------------------------- |
|
||||
| `USERNAME` | `String` | no | The username to create the user with |
|
||||
|
||||
### Success Response
|
||||
| Field | Type | Description |
|
||||
| :--------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
|
||||
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :------------------ | :----------------------------------------------------------------------------- |
|
||||
| `ALREADY_LOGGED_IN` | The session is already associated with a user, logging in again is prohibited. |
|
||||
|
||||
### Example Request
|
||||
```
|
||||
LOGIN USERNAME='Lars'
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```
|
||||
+OK
|
||||
USERNAME='Lars_1234'
|
||||
ID=e47a671e-2b2a-42df-bb82-953fe2ebd307
|
||||
END
|
||||
```
|
||||
|
||||
|
||||
## LOGOUT command
|
||||
Description of the command, what it does, and when it should be used.
|
||||
|
||||
### Required pre-execution checks
|
||||
None.
|
||||
|
||||
### Request Parameters
|
||||
No parameters.
|
||||
|
||||
### Success Response
|
||||
No response fields.
|
||||
|
||||
### Error Response
|
||||
| Code | Description |
|
||||
| :------------------- | :--------------------------------------------------------------- |
|
||||
| `NO_USER_ASSOCIATED` | The session has no user associated, logging out is not possible. |
|
||||
|
||||
### Example Request
|
||||
```
|
||||
LOGOUT
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```
|
||||
+OK
|
||||
END
|
||||
```
|
||||
|
||||
## LIST_USERS command
|
||||
The `LIST_USERS` command is used to retrieve a list of all currently logged-in users.
|
||||
|
||||
### Required pre-execution checks
|
||||
None.
|
||||
|
||||
### Request Parameters
|
||||
No parameters.
|
||||
|
||||
### Success Response
|
||||
| Field | Type | Description |
|
||||
| :------ | :----------------- | :--------------------------------------- |
|
||||
| `USERS` | `Collection<User>` | Collection of all users currently online |
|
||||
|
||||
| Fields of `User` | Type | Description |
|
||||
| :--------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
|
||||
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
|
||||
|
||||
|
||||
### Error Response
|
||||
None.
|
||||
|
||||
### Example Request
|
||||
```
|
||||
LIST_USERS
|
||||
```
|
||||
|
||||
### Example Response
|
||||
```
|
||||
+OK
|
||||
USERS
|
||||
USER
|
||||
USERNAME=Lars_001
|
||||
ID=56765d0f-8cd3-4eec-91b2-7e36265c1a5d
|
||||
END
|
||||
USER
|
||||
USERNAME=Lars_002
|
||||
ID=b7bbd9b3-0d49-4c92-8306-b1c8506d2ff0
|
||||
END
|
||||
USER
|
||||
USERNAME=Lars
|
||||
ID=982bc78e-547f-495a-a821-433a3603f92c
|
||||
END
|
||||
END
|
||||
END
|
||||
```
|
||||
@@ -26,8 +26,7 @@ Responses start with `+OK` on success or `-ERR` when something goes wrong. Comma
|
||||
We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text stream, and a clearly defined set of commands.
|
||||
|
||||
## Core Components & Their Roles
|
||||
|
||||

|
||||
<img src="../../images/docs/networking/server-architecure/networking_components.png" alt="PlantUML diagram of all components outlined in this document" />
|
||||
|
||||
Here's a quick rundown of the main building blocks:
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,8 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
interface CommandHandler<T extends Request> {
|
||||
+ execute(request: T): void
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,8 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
interface CommandParser<T extends Request> {
|
||||
+ parse(primitiveRequest: PrimitiveRequest): T
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
@@ -0,0 +1,11 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
class CommandParserDispatcher {
|
||||
- parsers: Map<String, CommandParser>
|
||||
|
||||
+ register(command: String, parser: CommandParser): void
|
||||
+ parse(primitiveRequest: PrimitiveRequest): Request
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,10 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
class CommandRouter {
|
||||
- handlers: Map<Class<? extends Request>, CommandHandler<?>>
|
||||
+ register(requestClass: Class<T>, handler: CommandHandler<T>): void
|
||||
+ execute(request: Request): void
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,8 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
interface HandlerCheck {
|
||||
+ check(request: Request): Optional<Response>
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,31 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
class CommandHandlerExecutor {
|
||||
- responseDispatcher: ResponseDispatcher
|
||||
+ CommandHandlerExecutor(responseDispatcher: ResponseDispatcher)
|
||||
+ execute(handler: CommandHandler<Request>, request: Request): void
|
||||
}
|
||||
|
||||
interface HandlerCheck {
|
||||
+ check(request: Request): Optional<Response>
|
||||
}
|
||||
|
||||
abstract class CommandHandler<T extends Request> {
|
||||
- checks: List<HandlerCheck>
|
||||
# addCheck(check: HandlerCheck): void
|
||||
+ getChecks(): List<HandlerCheck>
|
||||
+ execute(request: T): void
|
||||
}
|
||||
|
||||
interface ResponseDispatcher
|
||||
class Request
|
||||
class Response
|
||||
|
||||
CommandHandlerExecutor --> CommandHandler : executes
|
||||
CommandHandler "1" o-- "0..*" HandlerCheck : registered checks
|
||||
CommandHandlerExecutor --> HandlerCheck : evaluates
|
||||
HandlerCheck ..> Request : inspects
|
||||
HandlerCheck ..> Response : returns failure response
|
||||
CommandHandlerExecutor --> ResponseDispatcher : dispatches failures
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 61 KiB |
@@ -0,0 +1,33 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
package "network/ (infrastructure)" {
|
||||
class PrimitiveRequest <<record>>
|
||||
interface CommandParser<T extends Request>
|
||||
interface CommandHandler<T extends Request>
|
||||
class CommandParserDispatcher
|
||||
class CommandRouter
|
||||
abstract class Request
|
||||
abstract class Response
|
||||
|
||||
PrimitiveRequest --> CommandParserDispatcher : routes
|
||||
PrimitiveRequest ..> CommandParser : parsed by
|
||||
CommandParserDispatcher --> CommandParser : routes to
|
||||
CommandParser --> Request : parses to
|
||||
Request --> CommandRouter : routes
|
||||
CommandRouter --> CommandHandler : routes to
|
||||
Request ..> CommandHandler : executed by
|
||||
CommandHandler --> Response : creates
|
||||
}
|
||||
|
||||
package "app/commands/<name>/ (per command)" {
|
||||
class ExampleParser implements CommandParser
|
||||
class ExampleRequest extends Request
|
||||
class ExampleHandler implements CommandHandler
|
||||
class ExampleResponse extends Response
|
||||
|
||||
ExampleParser --> ExampleRequest : parses to
|
||||
ExampleRequest --> ExampleHandler : executed by
|
||||
ExampleHandler --> ExampleResponse : produces
|
||||
}
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,11 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
abstract class Request {
|
||||
# context: RequestContext
|
||||
+ getContext(): RequestContext
|
||||
+ getSessionId(): SessionId
|
||||
+ getRequestId(): int
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,14 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
class RequestParameterAccessor {
|
||||
- index: Map<String, String>
|
||||
|
||||
+ RequestParameterAccessor(parameters: List<RequestParameters>)
|
||||
+ require(key: String): String
|
||||
+ require(key: String, parser: ThrowingParser<T>): T
|
||||
+ optional(key: String, defaultValue: String): String
|
||||
+ optional(key: String, defaultValue: T, parser: ThrowingParser<T>): T
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,23 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
abstract class Response {
|
||||
# context: RequestContext
|
||||
# body: ResponseBody
|
||||
+ prefix(): String
|
||||
+ getSessionId(): SessionId
|
||||
+ getRequestId(): int
|
||||
+ getBody(): ResponseBody
|
||||
}
|
||||
|
||||
abstract class SuccessResponse extends Response {
|
||||
+ prefix(): String (+OK)
|
||||
}
|
||||
|
||||
class OkResponse extends SuccessResponse
|
||||
|
||||
class ErrorResponse extends Response {
|
||||
+ prefix(): String (-ERR)
|
||||
}
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,20 @@
|
||||
@startuml
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
participant SessionReader
|
||||
participant CommandParserDispatcher as Dispatcher
|
||||
participant "CommandParser<T>" as Parser
|
||||
participant CommandRouter as Router
|
||||
participant "CommandHandler<T>" as Handler
|
||||
participant ResponseDispatcher
|
||||
|
||||
SessionReader -> Dispatcher : parse(primitiveRequest)
|
||||
Dispatcher -> Parser : parse(primitiveRequest)
|
||||
Parser --> Dispatcher : ExampleRequest
|
||||
Dispatcher --> SessionReader : Request
|
||||
|
||||
SessionReader -> Router : execute(request)
|
||||
Router -> Handler : execute(ExampleRequest)
|
||||
Handler -> ResponseDispatcher : dispatch(ExampleRequest)
|
||||
|
||||
@enduml
|
||||
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for plantuml installation
|
||||
PLANTUML_PATH=""
|
||||
check_plantuml_installed() {
|
||||
if command -v plantuml &>/dev/null; then
|
||||
PLANTUML_PATH=$(command -v plantuml)
|
||||
return 0 # plantuml via PATH
|
||||
else
|
||||
return 1 # not found
|
||||
fi
|
||||
}
|
||||
|
||||
if ! check_plantuml_installed; then
|
||||
echo "Error: 'plantuml' was not found. Make sure, that plantuml is installed."
|
||||
echo "https://plantuml.com/starting"
|
||||
exit 1
|
||||
else
|
||||
echo "PlantUML installation found at: $PLANTUML_PATH"
|
||||
fi
|
||||
|
||||
# Compare source (.puml document) and exported file png on mtime
|
||||
needs_export() {
|
||||
local puml_file="$1"
|
||||
local png_file="$2"
|
||||
|
||||
# If no file found, export
|
||||
if [[ ! -f "$png_file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$puml_file" -nt "$png_file" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# If (re-)export is necessary export with plantuml cli
|
||||
export_file() {
|
||||
local puml_file="$1"
|
||||
local png_file="${puml_file%.puml}.png"
|
||||
|
||||
if needs_export "$puml_file" "$png_file"; then
|
||||
echo "↻ Exporting: $puml_file"
|
||||
"$PLANTUML_PATH" -tpng "$puml_file"
|
||||
else
|
||||
echo "✓ Up-to-date: $puml_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Iterate over all .puml files
|
||||
main() {
|
||||
# Source - https://stackoverflow.com/a/9612232
|
||||
# Posted by Kevin, modified by community. See post 'Timeline' for change history
|
||||
# Retrieved 2026-03-09, License - CC BY-SA 4.0
|
||||
find . -name '*.puml' -print0 |
|
||||
while IFS= read -r -d '' line; do
|
||||
export_file "$line"
|
||||
done
|
||||
|
||||
echo "✓ Exported all files"
|
||||
}
|
||||
|
||||
# Start script at entry point
|
||||
main
|
||||
@@ -10,15 +10,15 @@ import org.apache.logging.log4j.Logger;
|
||||
/**
|
||||
* Entry point for the Casono client application. Handles client startup and connection parameters.
|
||||
*
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
* <p>Default constructor for the application.
|
||||
*/
|
||||
public class ClientApp {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ClientApp.class);
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
/** Default constructor. */
|
||||
public ClientApp() {
|
||||
// Standardkonstruktor
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,13 +6,13 @@ import javafx.application.Application;
|
||||
/**
|
||||
* Launcher for the Casono main UI.
|
||||
*
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
* <p>Default constructor for the application.
|
||||
*/
|
||||
public class Launcher {
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
/** Default constructor. */
|
||||
public Launcher() {
|
||||
// Standardkonstruktor
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,11 @@ public class ChatController {
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
|
||||
/** Standardkonstruktor. Wird von FXML verwendet. */
|
||||
public ChatController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode wird vom Senden-Button oder Enter ausgelöst. Sie gibt die eigene Nachricht an
|
||||
* das Netzwerkprotokoll weiter.
|
||||
|
||||
@@ -5,21 +5,32 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Controller für die Casino-Spielfläche.
|
||||
* Controller for the casino gaming area.
|
||||
*
|
||||
* <p>Verantwortlich für: - die Darstellung des Pokertisches und der Spieleroberfläche, - die
|
||||
* Verarbeitung von Benutzereingaben, - die Schnittstelle zur GameEngine und zum Netzwerkprotokoll.
|
||||
* <p>Responsible for: - the display of the poker table and the player interface, - the processing
|
||||
* of user input, - the interface to the game engine and the network protocol.
|
||||
*
|
||||
* <p>Hinweise: - Die Methode `onTableClick()` dient aktuell nur als Test-Logik. Sie ist ggf. nicht
|
||||
* mehr funktionsfähig und wird zukünftig durch die finale Spielinteraktion ersetzt.
|
||||
* <p>Notes: - The `onTableClick()` method currently serves only as test logic. It may no longer be
|
||||
* functional and will be replaced by the final game interaction in the future.
|
||||
*/
|
||||
public class CasinoGameController {
|
||||
|
||||
/** Standard constructor. Used by FXML. */
|
||||
public CasinoGameController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
@FXML private Label welcomeText;
|
||||
@FXML private VBox casinoTable;
|
||||
|
||||
// TODO: Test-Logik: wird durch echte Spielinteraktionen ersetzt,
|
||||
// sobald die GameEngine fertig ist
|
||||
// TODO: Test logic: will be replaced by real game interactions,
|
||||
// once the game engine is finished
|
||||
|
||||
/**
|
||||
* Temporary test method that performs a placeholder action when the table is clicked.
|
||||
*
|
||||
* <p>In the final implementation, this will be replaced by the game logic.
|
||||
*/
|
||||
@FXML
|
||||
public void onTableClick() {
|
||||
welcomeText.setText("Einsatz akzeptiert!");
|
||||
|
||||
@@ -7,29 +7,34 @@ import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* Hauptklasse für das Casino-Spiel-UI.
|
||||
* Main class for the Casono Game UI.
|
||||
*
|
||||
* <p>Startet die JavaFX-Anwendung, lädt die grafische Oberfläche aus der FXML-Datei und
|
||||
* initialisiert die Haupt-Stage für das Spiel.
|
||||
* <p>Starts the JavaFX application, loads the graphical user interface from the FXML file, and
|
||||
* initializes the main stage for the game.
|
||||
*
|
||||
* <p>Aufgaben: - Lädt die FXML-Oberfläche "/ui-structure/Casinogameui.fxml". - Lädt das
|
||||
* Anwendungs-Icon aus "/images/logoinverted.png". - Startet die Anwendung im Vollbildmodus.
|
||||
* <p>Tasks: - Loads the FXML interface "/ui-structure/Casinogameui.fxml". - Loads the application
|
||||
* icon from "/images/logoinverted.png". - Starts the application in full-screen mode.
|
||||
*/
|
||||
public class CasinoGameUI extends Application {
|
||||
|
||||
/** default constructor */
|
||||
public CasinoGameUI() {
|
||||
// default no-arg constructor
|
||||
}
|
||||
|
||||
private static final int DEFAULT_WIDTH = 1200;
|
||||
private static final int DEFAULT_HEIGHT = 800;
|
||||
|
||||
/**
|
||||
* Startet die Haupt-Stage der Anwendung.
|
||||
* Starts the main stage of the application.
|
||||
*
|
||||
* @param stage Die vom System bereitgestellte Haupt-Stage.
|
||||
* @throws IOException Wenn die FXML-Datei oder Ressourcen nicht geladen werden können.
|
||||
* @param stage The main stage provided by the system.
|
||||
* @throws IOException If the FXML file or resources cannot be loaded.
|
||||
*/
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader =
|
||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/casinogameui.fxml"));
|
||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
stage.setTitle("Casono (GAME)");
|
||||
|
||||
@@ -41,9 +46,9 @@ public class CasinoGameUI extends Application {
|
||||
}
|
||||
|
||||
/**
|
||||
* Startpunkt der Anwendung.
|
||||
* Starting point of the application.
|
||||
*
|
||||
* @param args Befehlszeilenargumente.
|
||||
* @param args Command line arguments.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
|
||||
@@ -27,36 +27,41 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Experimenteller integrierter Browser für Casono.
|
||||
* Experimental embedded browser for Casono.
|
||||
*
|
||||
* <p>Diese Klasse implementiert einen einfachen eingebetteten Webbrowser auf Basis von {@link
|
||||
* javafx.scene.web.WebView}. Der Browser dient primär als Hilfswerkzeug innerhalb des Spiels, um
|
||||
* externe Inhalte wie Webseiten oder Videos anzuzeigen.
|
||||
* <p>This class implements a simple embedded web browser based on {@link javafx.scene.web.WebView}.
|
||||
* The browser primarily serves as a utility within the game to display external content such as web
|
||||
* pages or videos.
|
||||
*
|
||||
* <p>Status Der Browser befindet sich derzeit in einer experimentellen Phase. Einige
|
||||
* Sicherheitsmechanismen basieren auf experimentellen KI-gestützten Empfehlungen und können sich in
|
||||
* zukünftigen Versionen noch ändern.
|
||||
* <p>Status The browser is currently in an experimental phase. Some security mechanisms are based
|
||||
* on experimental AI-driven recommendations and may change in future versions.
|
||||
*
|
||||
* <p>Zweck Der Browser wird aktuell experimentell genutzt, um: Pokerregeln direkt im Spiel zu
|
||||
* erklären Hilfeseiten oder Dokumentationen anzuzeigen Videos (z.B. Tutorials oder Erklärungen)
|
||||
* über Plattformen wie YouTube abzuspielen
|
||||
* <p>Purpose The browser is currently being used experimentally to: Explain poker rules directly
|
||||
* within the game Display help pages or documentation Play videos (e.g., tutorials or explanations)
|
||||
* via platforms such as YouTube
|
||||
*
|
||||
* <p>Sicherheitsmechanismen Da externe Webseiten geladen werden können, wurden einige grundlegende
|
||||
* Schutzmaßnahmen integriert: - HTTPS-Zwang für Webseiten - Whitelist für bekannte Domains -
|
||||
* Warnung bei unbekannten Webseiten - JavaScript standardmäßig deaktiviert (man kann es jedoch für
|
||||
* Google etc. einschalten) - Popup-Blocker - Automatische Cookie-Löschung beim Schließen
|
||||
* <p>Security Mechanisms Since external websites can be loaded, some basic protective measures have
|
||||
* been integrated: - Mandatory HTTPS for websites - Whitelist for known domains - Warning for
|
||||
* unknown websites - JavaScript disabled by default (but can be enabled for Google, etc.) - Pop-up
|
||||
* blocker - Automatic cookie deletion upon closing
|
||||
*/
|
||||
public class CasinoBrowserController {
|
||||
|
||||
/** Default constructor. Initializes the CasinoBrowserController. */
|
||||
public CasinoBrowserController() {
|
||||
// Intentionally left blank; controller initialization is FXML-driven.
|
||||
}
|
||||
|
||||
private static final Set<String> TRUSTED_DOMAINS = new HashSet<>();
|
||||
|
||||
private static final CookieManager COOKIE_MANAGER =
|
||||
new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
|
||||
private static final org.apache.logging.log4j.Logger LOGGER =
|
||||
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
private static final int LOGO_HEIGHT = 40;
|
||||
private static final int CORNER_RADIUS = 40;
|
||||
@@ -95,7 +100,7 @@ public class CasinoBrowserController {
|
||||
|
||||
private static boolean javascriptEnabled = false;
|
||||
|
||||
/** Löscht alle gespeicherten Cookies der aktuellen Browser-Sitzung. */
|
||||
/** Deletes all cookies stored during the current browser session. */
|
||||
private static void clearCookies() {
|
||||
try {
|
||||
COOKIE_MANAGER.getCookieStore().removeAll();
|
||||
@@ -104,12 +109,12 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet ein neues Browserfenster und lädt eine angegebene Webseite.
|
||||
* Opens a new browser window and loads a specified webpage.
|
||||
*
|
||||
* <p>Falls die Webseite nicht zur Liste vertrauenswürdiger Domains gehört, wird der Benutzer
|
||||
* gefragt, ob die Seite dennoch geladen werden soll.
|
||||
* <p>If the webpage is not on the list of trusted domains, the user will be asked whether the
|
||||
* page should be loaded anyway.
|
||||
*
|
||||
* @param url die Startadresse der Webseite, die geladen werden soll
|
||||
* @param url the URL of the webpage to be loaded
|
||||
*/
|
||||
public static void open(String url) {
|
||||
Platform.runLater(
|
||||
@@ -176,10 +181,10 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* WebView Container
|
||||
* WebView container
|
||||
*
|
||||
* @param webView WebView Inhalt
|
||||
* @return StackPane Container
|
||||
* @param webView WebView content
|
||||
* @return StackPane container
|
||||
*/
|
||||
public static StackPane createWebContainer(WebView webView) {
|
||||
StackPane webContainer = new StackPane(webView);
|
||||
@@ -192,7 +197,7 @@ public class CasinoBrowserController {
|
||||
/**
|
||||
* Popup Blocker
|
||||
*
|
||||
* @param engine WebEngine nutzen
|
||||
* @param engine Use WebEngine
|
||||
*/
|
||||
public static void configurePopupBlocker(WebEngine engine) {
|
||||
engine.setCreatePopupHandler(
|
||||
@@ -206,8 +211,8 @@ public class CasinoBrowserController {
|
||||
var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
|
||||
Image logo = new Image(stream);
|
||||
|
||||
// Variable 'streamM' abgekürzt, um das 100-Zeichen-Limit (LineLength)
|
||||
// einzuhalten
|
||||
// Variable ‘streamM’ abbreviated to comply with the 100-character
|
||||
// limit (LineLength)
|
||||
var streamM =
|
||||
CasinoBrowserController.class.getResourceAsStream(LOGO_PATH_MAIN);
|
||||
Image logomain = new Image(streamM);
|
||||
@@ -226,7 +231,7 @@ public class CasinoBrowserController {
|
||||
alert.setGraphic(logoView);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Logo konnte nicht geladen werden");
|
||||
LOGGER.error("The logo could not be loaded");
|
||||
}
|
||||
|
||||
alert.showAndWait();
|
||||
@@ -235,9 +240,9 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Logos laden
|
||||
* Load logos
|
||||
*
|
||||
* @param stage Fenster Stage
|
||||
* @param stage Window Stage
|
||||
* @return ImageView Logo
|
||||
*/
|
||||
private static ImageView loadLogos(Stage stage) {
|
||||
@@ -247,8 +252,7 @@ public class CasinoBrowserController {
|
||||
var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
|
||||
Image logo = new Image(stream);
|
||||
|
||||
// Variable 'streamM' abgekürzt, um das 100-Zeichen-Limit (LineLength)
|
||||
// einzuhalten
|
||||
// Variable ‘streamM’ abbreviated to comply with the 100-character limit (LineLength)
|
||||
var streamM = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH_MAIN);
|
||||
Image logomain = new Image(streamM);
|
||||
|
||||
@@ -262,17 +266,17 @@ public class CasinoBrowserController {
|
||||
browserLogo.setPreserveRatio(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Logo konnte nicht geladen werden");
|
||||
LOGGER.error("The logo could not be loaded");
|
||||
}
|
||||
|
||||
return browserLogo;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL Feld
|
||||
* URL field
|
||||
*
|
||||
* @param url Start URL
|
||||
* @return TextField Eingabe
|
||||
* @return TextField input
|
||||
*/
|
||||
public static TextField createUrlField(String url) {
|
||||
TextField urlField = new TextField(url);
|
||||
@@ -283,12 +287,12 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sicherheits Label
|
||||
* Security Label
|
||||
*
|
||||
* @return Label Anzeige
|
||||
* @return Label display
|
||||
*/
|
||||
public static Label createSecurityLabel() {
|
||||
Label securityLabel = new Label("SICHER");
|
||||
Label securityLabel = new Label("SAFE");
|
||||
securityLabel.getStyleClass().add("security-label");
|
||||
|
||||
return securityLabel;
|
||||
@@ -297,8 +301,8 @@ public class CasinoBrowserController {
|
||||
/**
|
||||
* JS Toggle
|
||||
*
|
||||
* @param engine WebEngine nutzen
|
||||
* @return Button Umschalten
|
||||
* @param engine Use WebEngine
|
||||
* @return Toggle button
|
||||
*/
|
||||
public static Button createJsToggle(WebEngine engine) {
|
||||
Button jsToggle = new Button("JS EINSCHALTEN");
|
||||
@@ -310,7 +314,7 @@ public class CasinoBrowserController {
|
||||
engine.setJavaScriptEnabled(javascriptEnabled);
|
||||
|
||||
if (javascriptEnabled) {
|
||||
jsToggle.setText("JS AUSSCHALTEN");
|
||||
jsToggle.setText("JS TURN OFF");
|
||||
jsToggle.getStyleClass().removeAll("red-button");
|
||||
jsToggle.getStyleClass().add("yellow-button");
|
||||
|
||||
@@ -325,10 +329,10 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zurück Button
|
||||
* Back Button
|
||||
*
|
||||
* @param engine WebEngine nutzen
|
||||
* @return Button Zurück
|
||||
* @param engine Use WebEngine
|
||||
* @return Back button
|
||||
*/
|
||||
public static Button createBackButton(WebEngine engine) {
|
||||
Button backBtn = new Button("<");
|
||||
@@ -344,18 +348,19 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Vorwärts Button
|
||||
* Forward Button
|
||||
*
|
||||
* @param engine WebEngine nutzen
|
||||
* @return Button Vorwärts
|
||||
* @param engine Use WebEngine
|
||||
* @return Forward button
|
||||
*/
|
||||
public static Button createForwardButton(WebEngine engine) {
|
||||
Button fwdBtn = new Button(">");
|
||||
fwdBtn.getStyleClass().add("gray-button");
|
||||
fwdBtn.setOnAction(
|
||||
e -> {
|
||||
if (engine.getHistory().getCurrentIndex()
|
||||
< engine.getHistory().getEntries().size() - 1) {
|
||||
int currentIndex = engine.getHistory().getCurrentIndex();
|
||||
int lastIndex = engine.getHistory().getEntries().size() - 1;
|
||||
if (currentIndex < lastIndex) {
|
||||
engine.getHistory().go(1);
|
||||
}
|
||||
});
|
||||
@@ -366,8 +371,8 @@ public class CasinoBrowserController {
|
||||
/**
|
||||
* Reload Button
|
||||
*
|
||||
* @param engine WebEngine nutzen
|
||||
* @return Button Reload
|
||||
* @param engine Use WebEngine
|
||||
* @return Reload button
|
||||
*/
|
||||
public static Button createReloadButton(WebEngine engine) {
|
||||
Button reloadBtn = new Button("⟳");
|
||||
@@ -379,9 +384,9 @@ public class CasinoBrowserController {
|
||||
/**
|
||||
* Close Button
|
||||
*
|
||||
* @param stage Fenster Stage
|
||||
* @param webView WebView Inhalt
|
||||
* @return Button Schließen
|
||||
* @param stage Window stage
|
||||
* @param webView WebView content
|
||||
* @return Close button
|
||||
*/
|
||||
public static Button createCloseButton(Stage stage, WebView webView) {
|
||||
Button closeBtn = new Button("X");
|
||||
@@ -399,9 +404,9 @@ public class CasinoBrowserController {
|
||||
/**
|
||||
* URL Events
|
||||
*
|
||||
* @param engine WebEngine nutzen
|
||||
* @param urlField URL Textfeld
|
||||
* @param securityLabel Sicherheits Label
|
||||
* @param engine Use WebEngine
|
||||
* @param urlField URL text field
|
||||
* @param securityLabel Security label
|
||||
*/
|
||||
public static void configureUrlEvents(
|
||||
WebEngine engine, TextField urlField, Label securityLabel) {
|
||||
@@ -410,11 +415,11 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene erstellen
|
||||
* Create a scene
|
||||
*
|
||||
* @param taskbar Taskbar HBox
|
||||
* @param webContainer Web Container
|
||||
* @return Scene Fenster
|
||||
* @param webContainer Web container
|
||||
* @return Scene window
|
||||
*/
|
||||
public static Scene createScene(HBox taskbar, StackPane webContainer) {
|
||||
VBox root = new VBox(VBOX_SPACING, taskbar, webContainer);
|
||||
@@ -423,7 +428,7 @@ public class CasinoBrowserController {
|
||||
|
||||
Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/casinogameui.css");
|
||||
var css = CasinoBrowserController.class.getResource("/ui-structure/Casinogameui.css");
|
||||
|
||||
if (css != null) {
|
||||
scene.getStylesheets().add(css.toExternalForm());
|
||||
@@ -435,8 +440,8 @@ public class CasinoBrowserController {
|
||||
/**
|
||||
* Key Events
|
||||
*
|
||||
* @param scene Scene Fenster
|
||||
* @param engine WebEngine nutzen
|
||||
* @param scene Scene window
|
||||
* @param engine Use WebEngine
|
||||
*/
|
||||
public static void configureKeyEvents(Scene scene, WebEngine engine) {
|
||||
scene.setOnKeyPressed(
|
||||
@@ -448,18 +453,18 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt eine URL in den Browser, nachdem grundlegende Sicherheitsprüfungen durchgeführt wurden.
|
||||
* Loads a URL in the browser after performing basic security checks.
|
||||
*
|
||||
* <p>Vor dem Laden einer Seite werden folgende Prüfungen durchgeführt: - Überprüfung des
|
||||
* Protokolls (nur HTTPS erlaubt) - Überprüfung der Domain gegen eine Whitelist - Schutz vor
|
||||
* Domain-Spoofing (Domain-Vortäuschung)
|
||||
* <p>Before loading a page, the following checks are performed: - Verification of the protocol
|
||||
* (only HTTPS allowed) - Verification of the domain against a whitelist - Protection against
|
||||
* domain spoofing
|
||||
*
|
||||
* <p>Falls eine Domain nicht als vertrauenswürdig eingestuft wird, muss der Benutzer
|
||||
* bestätigen, dass die Seite dennoch geöffnet werden darf.
|
||||
* <p>If a domain is not classified as trustworthy, the user must confirm that the page may
|
||||
* still be opened.
|
||||
*
|
||||
* @param engine der WebEngine-Renderer des Browsers
|
||||
* @param url die zu ladende Webadresse
|
||||
* @param securityLabel Label zur Anzeige des aktuellen Sicherheitsstatus
|
||||
* @param engine the browser's WebEngine renderer
|
||||
* @param url the web address to be loaded
|
||||
* @param securityLabel label for displaying the current security status
|
||||
*/
|
||||
private static void loadUrlSafely(WebEngine engine, String url, Label securityLabel) {
|
||||
try {
|
||||
@@ -469,9 +474,9 @@ public class CasinoBrowserController {
|
||||
|
||||
URI uri = new URI(url);
|
||||
|
||||
// HTTPS Pflicht
|
||||
// HTTPS required
|
||||
if (!"https".equalsIgnoreCase(uri.getScheme())) {
|
||||
securityLabel.setText("BLOCKIERT");
|
||||
securityLabel.setText("BLOCKED");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -481,19 +486,19 @@ public class CasinoBrowserController {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sicherer Domain Check
|
||||
// Secure Domain Check
|
||||
boolean trusted =
|
||||
TRUSTED_DOMAINS.stream()
|
||||
.anyMatch(domain -> host.equals(domain) || host.endsWith("." + domain));
|
||||
|
||||
if (!trusted) {
|
||||
if (!showUnknownWebsiteAlert(host)) {
|
||||
securityLabel.setText("BLOCKIERT");
|
||||
securityLabel.setText("BLOCKED");
|
||||
return;
|
||||
}
|
||||
securityLabel.setText("UNBEKANNT");
|
||||
} else {
|
||||
securityLabel.setText("SICHER");
|
||||
securityLabel.setText("SAFE");
|
||||
}
|
||||
engine.load(uri.toString());
|
||||
} catch (Exception e) {
|
||||
@@ -502,20 +507,21 @@ public class CasinoBrowserController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt Warnung an.
|
||||
* Displays a warning.
|
||||
*
|
||||
* @param host Website Host
|
||||
* @return OK gedrückt
|
||||
* @param host Website host
|
||||
* @return OK pressed
|
||||
*/
|
||||
private static boolean showUnknownWebsiteAlert(String host) {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
alert.setTitle("Unbekannte Website");
|
||||
alert.setHeaderText("Diese Website ist nicht bekannt");
|
||||
alert.setContentText(
|
||||
alert.setTitle("Unknown website");
|
||||
alert.setHeaderText("This website is unknown");
|
||||
String content =
|
||||
host
|
||||
+ "\n\nDiese Seite ist nicht vom "
|
||||
+ "Casono Browser verifiziert.\nMöchten Sie sie trotzdem öffnen?");
|
||||
+ "\n\nThis site has not been verified by the Casono browser.\n"
|
||||
+ "Do you still want to open it?";
|
||||
alert.setContentText(content);
|
||||
|
||||
var stream = CasinoBrowserController.class.getResourceAsStream(LOGO_PATH);
|
||||
Image logo = new Image(stream);
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Controller für die interaktive Taskleiste innerhalb der Poker-UI.
|
||||
* Controller for the interactive taskbar within the poker UI.
|
||||
*
|
||||
* <p>Verantwortlich für: - Drag-and-Drop-Verschieben der Taskleiste, - Eingabe und Verwaltung von
|
||||
* Spieleinsätzen, - Steuerung allgemeiner Menüfunktionen wie Exit.
|
||||
* <p>Responsible for: - Drag-and-drop movement of the taskbar, - Input and management of game
|
||||
* stakes, - Control of general menu functions such as Exit.
|
||||
*/
|
||||
public class TaskbarController {
|
||||
|
||||
private static final org.apache.logging.log4j.Logger LOGGER =
|
||||
org.apache.logging.log4j.LogManager.getLogger(CasinoBrowserController.class);
|
||||
/** Standard constructor. Used by FXML. */
|
||||
public TaskbarController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
@FXML private HBox taskbar;
|
||||
@FXML private TextField taskbarInput;
|
||||
@@ -30,10 +36,10 @@ public class TaskbarController {
|
||||
private static final int CREDIT_STEP = 5;
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn die Taskleiste mit der Maus gedrückt wird. Speichert die relative
|
||||
* Position, um später korrekt zu verschieben.
|
||||
* Called when the taskbar is clicked with the mouse. Saves the relative position for later,
|
||||
* correct repositioning.
|
||||
*
|
||||
* @param event Das Mausereignis
|
||||
* @param event The mouse event
|
||||
*/
|
||||
@FXML
|
||||
private void onTaskbarPressed(MouseEvent event) {
|
||||
@@ -42,11 +48,10 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, während die Taskleiste mit der Maus gezogen wird. Aktualisiert die Position
|
||||
* und skaliert die Taskleiste leicht zur visuellen Rückmeldung.
|
||||
* Called while dragging the taskbar with the mouse. Updates the position and slightly scales
|
||||
* the taskbar for visual feedback.
|
||||
*
|
||||
* <p>TODO: Es muss noch gefixt werden, dass die Taskleiste nicht aus dem Fenster verschwinden
|
||||
* kann.
|
||||
* <p>TODO: It still needs to be fixed that the taskbar cannot disappear out of the window.
|
||||
*
|
||||
* @param event Das Mausereignis
|
||||
*/
|
||||
@@ -60,10 +65,10 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn die Maus über der Taskleiste losgelassen wird. Setzt die Skalierung der
|
||||
* Taskleiste wieder auf Normalgröße.
|
||||
* Called when the mouse cursor is released over the taskbar. Resets the taskbar scaling to
|
||||
* normal size.
|
||||
*
|
||||
* @param event Das Mausereignis
|
||||
* @param event The mouse event
|
||||
*/
|
||||
@FXML
|
||||
private void onTaskbarReleased(MouseEvent event) {
|
||||
@@ -72,9 +77,9 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn im Textfeld die Enter-Taste gedrückt wird.
|
||||
* Called up when the Enter key is pressed in the text field.
|
||||
*
|
||||
* @param event Das Tastaturereignis
|
||||
* @param event The keyboard event
|
||||
*/
|
||||
@FXML
|
||||
private void onInputSubmitted(KeyEvent event) {
|
||||
@@ -84,29 +89,35 @@ public class TaskbarController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn der Submit-Button in der Taskleiste gedrückt wird. Löst die
|
||||
* Verarbeitung des Einsatzes aus.
|
||||
* Called when the submit button in the taskbar is pressed. Triggers the processing of the
|
||||
* deployment.
|
||||
*/
|
||||
@FXML
|
||||
private void onInputSubmittedAction() {
|
||||
processBet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wird aufgerufen, wenn der Exit-Button in der Taskleiste gedrückt wird.
|
||||
*
|
||||
* <p>TODO: Logik implementieren, um zur Lobby zurückzukehren, ohne die gesamte Anwendung zu
|
||||
* schließen (kein System.exit/Platform.exit).
|
||||
*/
|
||||
@FXML
|
||||
private void onExitButtonClick() {
|
||||
Platform.exit();
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
// Close game stage
|
||||
javafx.stage.Stage currentStage =
|
||||
(javafx.stage.Stage) taskbar.getScene().getWindow();
|
||||
currentStage.close();
|
||||
// Start lobby UI
|
||||
try {
|
||||
new Casinomainui().start(new javafx.stage.Stage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Starten der Lobby-UI: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet den im Textfeld eingegebenen Einsatz. Es werden ausschließlich ganzzahlige Werte
|
||||
* im Bereich von 5 bis 100.000 Credits akzeptiert, die einem Vielfachen von 5 entsprechen
|
||||
* (5er-Schritte). Der Einsatz wird aktuell nur auf der Konsole ausgegeben.
|
||||
* Processes the stake entered in the text field. Only integer values between 5 and 100,000
|
||||
* credits are accepted, in multiples of 5 (in increments of 5). The stake is currently only
|
||||
* displayed on the console.
|
||||
*/
|
||||
private void processBet() {
|
||||
String input = taskbarInput.getText();
|
||||
@@ -114,22 +125,22 @@ public class TaskbarController {
|
||||
int credits = Integer.parseInt(input.trim());
|
||||
|
||||
if (credits >= MIN_CREDITS && credits <= MAX_CREDITS && credits % CREDIT_STEP == 0) {
|
||||
// TODO: Credits müssen an die GameEngine gesendet werden
|
||||
LOGGER.info("Einsatz gesetzt: {} Casono Credits", credits);
|
||||
// TODO: Credits must be sent to the GameEngine
|
||||
LOGGER.info("Bet set: {} Casono Credits", credits);
|
||||
taskbarInput.clear();
|
||||
} else {
|
||||
LOGGER.info("Fehler: Nur 5er-Schritte (5, 10, ... 100.000) erlaubt!");
|
||||
LOGGER.error("Error: Only increments of 5 (5, 10, ... 100,000) are allowed!");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
LOGGER.info("Fehler: Bitte nur eine Zahl eingeben!");
|
||||
LOGGER.error("Error: Please enter only one number!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet den integrierten Casono Webbrowser.
|
||||
* Opens the integrated Casono web browser.
|
||||
*
|
||||
* <p>TODO: Ersetze die Start-URL durch die offizielle Projekt-Website (z.B. Tipps & Tricks
|
||||
* Seite), sobald die Inhalte für Strategien und Support bereitstehen.
|
||||
* <p>TODO: Replace the start URL with the official project website (e.g., Tips & Tricks page)
|
||||
* once the content for strategies and support is available.
|
||||
*/
|
||||
@FXML
|
||||
private void onBrowserButtonClick() {
|
||||
|
||||
@@ -10,13 +10,13 @@ import javafx.stage.Stage;
|
||||
/**
|
||||
* JavaFX Application class for the Casono main UI.
|
||||
*
|
||||
* <p>Standardkonstruktor für die Anwendung.
|
||||
* <p>Default constructor for the application.
|
||||
*/
|
||||
public class Casinomainui extends Application {
|
||||
|
||||
/** Standardkonstruktor. */
|
||||
/** Default constructor. */
|
||||
public Casinomainui() {
|
||||
// Standardkonstruktor
|
||||
// Default constructor
|
||||
}
|
||||
|
||||
private static final int SCENE_WIDTH = 1200;
|
||||
|
||||
@@ -28,6 +28,7 @@ public class CasinomainuiController {
|
||||
private LobbyButtonGridManager gridManager;
|
||||
private int nextButtonId = 1;
|
||||
|
||||
/** Default constructor for dependency injection by FXMLLoader. */
|
||||
public CasinomainuiController() {
|
||||
// Default constructor
|
||||
}
|
||||
@@ -39,7 +40,7 @@ public class CasinomainuiController {
|
||||
subtitleLabel.setText("Texas Hold'em Poker");
|
||||
logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm()));
|
||||
|
||||
translationManager = new LobbyButtonTranslationManager();
|
||||
translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
gridManager =
|
||||
new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager);
|
||||
casinoTable.getChildren().clear();
|
||||
@@ -57,7 +58,7 @@ public class CasinomainuiController {
|
||||
@FXML
|
||||
public void handleCreateLobbyButton() {
|
||||
if (translationManager.isFull()) {
|
||||
LOGGER.warn("Grid voll! Keine weiteren Lobbys moeglich.");
|
||||
LOGGER.warn("Grid is full! No more lobbies available.");
|
||||
return;
|
||||
}
|
||||
int buttonId = nextButtonId++;
|
||||
@@ -67,7 +68,7 @@ public class CasinomainuiController {
|
||||
LOGGER.info("ButtonID: {}, LobbyID: {}", buttonId, lobbyId);
|
||||
gridManager.renderLobbyButtons();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Hinzufügen: {}", e.getMessage());
|
||||
LOGGER.error("Error while adding lobby button: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import org.apache.logging.log4j.Logger;
|
||||
* ButtonID to LobbyID.
|
||||
*/
|
||||
public class LobbyButtonGridManager {
|
||||
private static final double BUTTON_WIDTH_MARGIN = 20.0;
|
||||
private static final double BUTTON_MIN_SIZE = 10.0;
|
||||
private static final Logger LOGGER = LogManager.getLogger(LobbyButtonGridManager.class);
|
||||
|
||||
/** GridPane for the button grid. */
|
||||
@@ -46,7 +48,8 @@ public class LobbyButtonGridManager {
|
||||
public LobbyButtonGridManager(
|
||||
GridPane gridPane, LobbyButtonTranslationManager translationManager) {
|
||||
this.gridPane = gridPane;
|
||||
this.translationManager = translationManager;
|
||||
// Singleton immer verwenden
|
||||
this.translationManager = LobbyButtonTranslationManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,8 +68,21 @@ public class LobbyButtonGridManager {
|
||||
int buttonId = entry.getKey();
|
||||
Button btn = new Button();
|
||||
btn.setId("lobbyBtn-" + buttonId);
|
||||
btn.setGraphic(
|
||||
new ImageView(new Image(getClass().getResourceAsStream(BUTTON_IMAGE_PATH))));
|
||||
ImageView imageView =
|
||||
new ImageView(new Image(getClass().getResourceAsStream(BUTTON_IMAGE_PATH)));
|
||||
imageView.setPreserveRatio(true);
|
||||
// Dynamische Breite: Bindung an die Zellengröße
|
||||
imageView
|
||||
.fitWidthProperty()
|
||||
.bind(gridPane.widthProperty().divide(COLS).subtract(BUTTON_WIDTH_MARGIN));
|
||||
imageView.setSmooth(true);
|
||||
btn.setGraphic(imageView);
|
||||
btn.setMaxWidth(Double.MAX_VALUE);
|
||||
btn.setMaxHeight(Double.MAX_VALUE);
|
||||
btn.setMinWidth(BUTTON_MIN_SIZE);
|
||||
btn.setMinHeight(BUTTON_MIN_SIZE);
|
||||
GridPane.setHgrow(btn, javafx.scene.layout.Priority.ALWAYS);
|
||||
GridPane.setVgrow(btn, javafx.scene.layout.Priority.ALWAYS);
|
||||
btn.setOnAction(
|
||||
e -> {
|
||||
Integer lobbyId = translationManager.getLobbyIdForButton(buttonId);
|
||||
@@ -99,8 +115,22 @@ public class LobbyButtonGridManager {
|
||||
* @param lobbyId The lobbyId to join
|
||||
*/
|
||||
public void joinLobby(int lobbyId) {
|
||||
// TODO: Replace with actual join logic
|
||||
// Game-UI starten und Lobby-UI schließen
|
||||
LOGGER.info("Joining lobby: {}", lobbyId);
|
||||
javafx.application.Platform.runLater(
|
||||
() -> {
|
||||
// Lobby-Stage schließen
|
||||
javafx.stage.Stage currentStage =
|
||||
(javafx.stage.Stage) gridPane.getScene().getWindow();
|
||||
currentStage.close();
|
||||
// Game-UI starten
|
||||
try {
|
||||
new ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI()
|
||||
.start(new javafx.stage.Stage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Starten der Game-UI: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,36 +4,53 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher. Keine Dateioperationen,
|
||||
* nur Laufzeitdatenstruktur.
|
||||
* Manages the mapping between Button IDs and Lobby IDs in memory only. No file operations,
|
||||
* runtime-only data structure.
|
||||
*/
|
||||
public class LobbyButtonTranslationManager {
|
||||
/** Maximale Anzahl an Buttons/Lobbys */
|
||||
|
||||
// Singleton instance
|
||||
private static LobbyButtonTranslationManager instance;
|
||||
|
||||
// Singleton access
|
||||
/**
|
||||
* Returns the singleton instance of the manager.
|
||||
*
|
||||
* @return the single instance of {@code LobbyButtonTranslationManager}
|
||||
*/
|
||||
public static LobbyButtonTranslationManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new LobbyButtonTranslationManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** Maximum number of buttons/lobbies */
|
||||
private static final int MAX_BUTTONS = 8;
|
||||
|
||||
/** Zuordnung ButtonID → LobbyID */
|
||||
/** Mapping ButtonID → LobbyID */
|
||||
private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>();
|
||||
|
||||
/** Konstruktor: initialisiert die Zuordnung leer. */
|
||||
public LobbyButtonTranslationManager() {
|
||||
// Zuordnung bleibt leer beim Start
|
||||
/** Private constructor for the singleton pattern */
|
||||
private LobbyButtonTranslationManager() {
|
||||
// Mapping is empty at startup
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht).
|
||||
* Checks whether the grid is full (MAX_BUTTONS reached).
|
||||
*
|
||||
* @return true, wenn Grid voll; sonst false
|
||||
* @return true if the grid is full; otherwise false
|
||||
*/
|
||||
public boolean isFull() {
|
||||
return buttonIdToLobbyId.size() >= MAX_BUTTONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt eine Zuordnung ButtonID → LobbyID hinzu.
|
||||
* Adds a mapping ButtonID → LobbyID.
|
||||
*
|
||||
* @param buttonId Die ID des Buttons
|
||||
* @param lobbyId Die ID der Lobby
|
||||
* @throws Exception wenn das Grid voll ist
|
||||
* @param buttonId the ID of the button
|
||||
* @param lobbyId the ID of the lobby
|
||||
* @throws Exception when the grid is full
|
||||
*/
|
||||
public void addLobbyButton(int buttonId, int lobbyId) throws Exception {
|
||||
if (isFull()) {
|
||||
@@ -43,28 +60,28 @@ public class LobbyButtonTranslationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt eine Zuordnung für die gegebene ButtonID.
|
||||
* Removes the mapping for the given ButtonID.
|
||||
*
|
||||
* @param buttonId Die ID des zu entfernenden Buttons
|
||||
* @param buttonId the ID of the button to remove
|
||||
*/
|
||||
public void removeLobbyButton(int buttonId) {
|
||||
buttonIdToLobbyId.remove(buttonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die LobbyID für eine gegebene ButtonID zurück.
|
||||
* Returns the LobbyID for a given ButtonID.
|
||||
*
|
||||
* @param buttonId Die ButtonID
|
||||
* @return Die zugehoerige LobbyID oder null, falls nicht vorhanden
|
||||
* @param buttonId the ButtonID
|
||||
* @return the associated LobbyID or null if not present
|
||||
*/
|
||||
public Integer getLobbyIdForButton(int buttonId) {
|
||||
return buttonIdToLobbyId.get(buttonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die gesamte Zuordnung ButtonID → LobbyID zurück.
|
||||
* Returns the full mapping ButtonID → LobbyID.
|
||||
*
|
||||
* @return Map aller Zuordnungen
|
||||
* @return Map of all mappings
|
||||
*/
|
||||
public Map<Integer, Integer> getButtonIdToLobbyId() {
|
||||
return buttonIdToLobbyId;
|
||||
|
||||
@@ -1,12 +1,41 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.add_player.AddPlayerHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.add_player.AddPlayerParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.add_player.AddPlayerRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet.PlayerBetHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet.PlayerBetParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet.PlayerBetRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call.PlayerCallHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call.PlayerCallParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call.PlayerCallRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users.ListUsersHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users.ListUsersParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users.ListUsersRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout.LogoutHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout.LogoutParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout.LogoutRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise.PlayerRaiseHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise.PlayerRaiseParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise.PlayerRaiseRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserCleanupJob;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandlerExecutor;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandRouter;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.CommandParserDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionDisconnectJob;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||
import java.time.Duration;
|
||||
@@ -18,46 +47,106 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
/** Application class for starting the server. */
|
||||
public class ServerApp {
|
||||
private static final int USER_CLEANUP_JOB_DELAY = 0;
|
||||
private static final int USER_CLEANUP_JOB_PERIOD = 10;
|
||||
private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10;
|
||||
private static final int SESSION_DISCONNECT_JOB_DELAY = 0;
|
||||
private static final int SESSION_DISCONNECT_JOB_PERIOD = 2;
|
||||
private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5;
|
||||
private static final int USER_CLEANUP_JOB_DELAY = 0;
|
||||
private static final int USER_CLEANUP_JOB_PERIOD = 10;
|
||||
private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10;
|
||||
private static final int SESSION_DISCONNECT_JOB_DELAY = 0;
|
||||
private static final int SESSION_DISCONNECT_JOB_PERIOD = 2;
|
||||
private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5;
|
||||
|
||||
public static void start(String arg) {
|
||||
int port = Integer.parseInt(arg);
|
||||
public static void start(String arg) {
|
||||
int port = Integer.parseInt(arg);
|
||||
|
||||
Logger logger = LogManager.getLogger(ServerApp.class);
|
||||
logger.info("Starting server at port {}", port);
|
||||
Logger logger = LogManager.getLogger(ServerApp.class);
|
||||
logger.info("Starting server at port {}", port);
|
||||
|
||||
EventBus eventBus = new EventBus();
|
||||
CommandParserDispatcher dispatcher = new CommandParserDispatcher();
|
||||
CommandRouter router = new CommandRouter();
|
||||
EventBus eventBus = new EventBus();
|
||||
CommandParserDispatcher dispatcher = new CommandParserDispatcher();
|
||||
SessionManager sessionManager = new SessionManager(eventBus, dispatcher);
|
||||
ResponseDispatcher responseDispatcher = new ResponseDispatcher(sessionManager);
|
||||
CommandHandlerExecutor handlerExecutor = new CommandHandlerExecutor(responseDispatcher);
|
||||
CommandRouter router = new CommandRouter(handlerExecutor);
|
||||
|
||||
SessionManager sessionManager = new SessionManager(eventBus, dispatcher, router);
|
||||
eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event));
|
||||
NetworkManager networkManager = new NetworkManager(port, sessionManager);
|
||||
eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.onDisconnect(event));
|
||||
|
||||
UserRegistry userRegistry = new UserRegistry();
|
||||
eventBus.subscribe(
|
||||
DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId()));
|
||||
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
scheduler.scheduleAtFixedRate(
|
||||
new UserCleanupJob(
|
||||
userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)),
|
||||
USER_CLEANUP_JOB_DELAY,
|
||||
USER_CLEANUP_JOB_PERIOD,
|
||||
TimeUnit.SECONDS);
|
||||
scheduler.scheduleAtFixedRate(
|
||||
new SessionDisconnectJob(
|
||||
sessionManager,
|
||||
eventBus,
|
||||
Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)),
|
||||
SESSION_DISCONNECT_JOB_DELAY,
|
||||
SESSION_DISCONNECT_JOB_PERIOD,
|
||||
TimeUnit.SECONDS);
|
||||
UserRegistry userRegistry = new UserRegistry();
|
||||
eventBus.subscribe(
|
||||
DisconnectEvent.class, event -> userRegistry.onDisconnect(event.sessionId()));
|
||||
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
scheduler.scheduleAtFixedRate(
|
||||
new UserCleanupJob(
|
||||
userRegistry, Duration.ofSeconds(USER_CLEANUP_JOB_RECONNECT_THRESHOLD)),
|
||||
USER_CLEANUP_JOB_DELAY,
|
||||
USER_CLEANUP_JOB_PERIOD,
|
||||
TimeUnit.SECONDS);
|
||||
scheduler.scheduleAtFixedRate(
|
||||
new SessionDisconnectJob(
|
||||
sessionManager,
|
||||
eventBus,
|
||||
Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)),
|
||||
SESSION_DISCONNECT_JOB_DELAY,
|
||||
SESSION_DISCONNECT_JOB_PERIOD,
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
networkManager.start();
|
||||
}
|
||||
registerCommands(dispatcher, router, responseDispatcher, userRegistry);
|
||||
|
||||
NetworkManager networkManager = new NetworkManager(port, sessionManager, router);
|
||||
networkManager.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers command parsers and handlers.
|
||||
*
|
||||
* @param parserDispatcher the dispatcher responsible for parsing incoming
|
||||
* commands
|
||||
* @param commandRouter the router that dispatches parsed commands to
|
||||
* appropriate handlers
|
||||
* @param responseDispatcher the dispatcher responsible for sending responses
|
||||
* back to clients
|
||||
*/
|
||||
private static void registerCommands(
|
||||
CommandParserDispatcher parserDispatcher,
|
||||
CommandRouter commandRouter,
|
||||
ResponseDispatcher responseDispatcher,
|
||||
UserRegistry userRegistry) {
|
||||
parserDispatcher.register("PING", new PingParser());
|
||||
commandRouter.register(PingRequest.class, new PingHandler(responseDispatcher));
|
||||
|
||||
parserDispatcher.register("CHECK_USERNAME", new CheckUsernameParser());
|
||||
commandRouter.register(
|
||||
CheckUsernameRequest.class,
|
||||
new CheckUsernameHandler(responseDispatcher, userRegistry));
|
||||
|
||||
parserDispatcher.register("LOGIN", new LoginParser());
|
||||
commandRouter.register(
|
||||
LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry));
|
||||
|
||||
parserDispatcher.register("LOGOUT", new LogoutParser());
|
||||
commandRouter.register(
|
||||
LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry));
|
||||
|
||||
parserDispatcher.register("LIST_USERS", new ListUsersParser());
|
||||
commandRouter.register(
|
||||
ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry));
|
||||
|
||||
// ADD_PLAYER
|
||||
parserDispatcher.register("ADD_PLAYER", new AddPlayerParser());
|
||||
commandRouter.register(AddPlayerRequest.class,
|
||||
new AddPlayerHandler(userRegistry, responseDispatcher));
|
||||
|
||||
// BET
|
||||
parserDispatcher.register("BET", new PlayerBetParser());
|
||||
commandRouter.register(PlayerBetRequest.class,
|
||||
new PlayerBetHandler(responseDispatcher));
|
||||
|
||||
// CALL
|
||||
parserDispatcher.register("CALL", new PlayerCallParser());
|
||||
commandRouter.register(PlayerCallRequest.class,
|
||||
new PlayerCallHandler(responseDispatcher));
|
||||
|
||||
// RAISE
|
||||
parserDispatcher.register("RAISE", new PlayerRaiseParser());
|
||||
commandRouter.register(PlayerRaiseRequest.class,
|
||||
new PlayerRaiseHandler(responseDispatcher));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.checks;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.checks.HandlerCheck;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.Response;
|
||||
import java.util.Optional;
|
||||
|
||||
public class UserLoggedInCheck implements HandlerCheck {
|
||||
private final UserRegistry userRegistry;
|
||||
|
||||
public UserLoggedInCheck(UserRegistry userRegistry) {
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> check(Request request) {
|
||||
Optional<User> user = userRegistry.getBySessionId(request.getSessionId());
|
||||
if (user.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
new ErrorResponse(
|
||||
request.getContext(),
|
||||
"USER_NOT_LOGGED_IN",
|
||||
"The execution of this command requires the user to be logged in"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Handles {@link CheckUsernameRequest}s to check whether a username is available. */
|
||||
public class CheckUsernameHandler extends CommandHandler<CheckUsernameRequest> {
|
||||
private final UserRegistry userRegistry;
|
||||
|
||||
/**
|
||||
* Creates a new handler for checking username availability.
|
||||
*
|
||||
* @param responseDispatcher the dispatcher used to send the response
|
||||
* @param userRegistry the registry used to look up existing users
|
||||
*/
|
||||
public CheckUsernameHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
|
||||
super(responseDispatcher);
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the username availability check for the given request.
|
||||
*
|
||||
* <p>If no user exists for the requested username, the username is reported as {@link
|
||||
* UsernameAvailability#FREE}; otherwise, it is reported as {@link UsernameAvailability#TAKEN}.
|
||||
*
|
||||
* @param request the request to execute
|
||||
*/
|
||||
@Override
|
||||
public void execute(CheckUsernameRequest request) {
|
||||
Optional<User> user = userRegistry.getByUsername(request.getUsername());
|
||||
UsernameAvailability availability;
|
||||
if (user.isEmpty()) {
|
||||
availability = UsernameAvailability.FREE;
|
||||
} else {
|
||||
availability = UsernameAvailability.TAKEN;
|
||||
}
|
||||
responseDispatcher.dispatch(new CheckUsernameResponse(request.getContext(), availability));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
|
||||
/** Parses a primitive request into a {@link CheckUsernameRequest}. */
|
||||
public class CheckUsernameParser implements CommandParser<CheckUsernameRequest> {
|
||||
/**
|
||||
* Extracts the required {@code USERNAME} parameter from the incoming request.
|
||||
*
|
||||
* @param primitiveRequest the request to parse
|
||||
* @return {@link CheckUsernameRequest} containing the username
|
||||
*/
|
||||
@Override
|
||||
public CheckUsernameRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor =
|
||||
new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
return new CheckUsernameRequest(primitiveRequest.context(), accessor.require("USERNAME"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request implementation used to check whether a username is available or already taken */
|
||||
public class CheckUsernameRequest extends Request {
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Constructs a new CheckUsernameRequest with the given context and username to check
|
||||
*
|
||||
* @param context the {@link RequestContext} containing information for responding to the
|
||||
* request
|
||||
* @param username the username to check for availability
|
||||
*/
|
||||
public CheckUsernameRequest(RequestContext context, String username) {
|
||||
super(context);
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provided username in the request
|
||||
*
|
||||
* @return username to check
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
|
||||
|
||||
/** Response indicating the availability status of a username check. */
|
||||
public class CheckUsernameResponse extends SuccessResponse {
|
||||
/**
|
||||
* Creates a new response to respond to the username availability check to
|
||||
*
|
||||
* @param context the {@link RequestContext} associated with the request
|
||||
* @param availability the availability status of the requested username
|
||||
*/
|
||||
public CheckUsernameResponse(RequestContext context, UsernameAvailability availability) {
|
||||
super(context, new ResponseBodyBuilder().param("STATUS", availability).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick;
|
||||
|
||||
/** Represents the availability status of a username */
|
||||
enum UsernameAvailability {
|
||||
/** Username is available */
|
||||
FREE,
|
||||
|
||||
/** Username is already in use */
|
||||
TAKEN
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.add_player;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Handler for the ADD_PLAYER command.
|
||||
*/
|
||||
public class AddPlayerHandler extends CommandHandler<AddPlayerRequest> {
|
||||
private final UserRegistry userRegistry;
|
||||
|
||||
public AddPlayerHandler(UserRegistry userRegistry, ResponseDispatcher responseDispatcher) {
|
||||
super(responseDispatcher);
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(AddPlayerRequest request) {
|
||||
Optional<User> created = userRegistry.registerIfAvailable(request.getName(), request.getContext().sessionId());
|
||||
if (created.isEmpty()) {
|
||||
responseDispatcher.dispatch(new ErrorResponse(request.getContext(), "NAME_TAKEN", "Name already taken"));
|
||||
return;
|
||||
}
|
||||
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.add_player;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.ParameterParseException;
|
||||
|
||||
/** Parser for the ADD_PLAYER command. */
|
||||
public class AddPlayerParser implements CommandParser<AddPlayerRequest> {
|
||||
@Override
|
||||
public AddPlayerRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
|
||||
String name = accessor.require("NAME");
|
||||
|
||||
int chips = accessor.require("CHIPS", Integer::parseInt);
|
||||
|
||||
return new AddPlayerRequest(primitiveRequest.context(), name, chips);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.add_player;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request data for the ADD_PLAYER command. */
|
||||
public class AddPlayerRequest extends Request {
|
||||
private final String name;
|
||||
private final int chips;
|
||||
|
||||
public AddPlayerRequest(RequestContext context, String name, int chips) {
|
||||
super(context);
|
||||
this.name = name;
|
||||
this.chips = chips;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getChips() {
|
||||
return chips;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
/**
|
||||
* Handler for the BET command.
|
||||
*/
|
||||
public class PlayerBetHandler extends CommandHandler<PlayerBetRequest> {
|
||||
|
||||
public PlayerBetHandler(ResponseDispatcher responseDispatcher) {
|
||||
super(responseDispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(PlayerBetRequest request) {
|
||||
if (request.getAmount() < 0) {
|
||||
responseDispatcher
|
||||
.dispatch(new ErrorResponse(request.getContext(), "INVALID_AMOUNT", "Amount must be non-negative"));
|
||||
return;
|
||||
}
|
||||
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
|
||||
/** Parser for the BET command. */
|
||||
public class PlayerBetParser implements CommandParser<PlayerBetRequest> {
|
||||
@Override
|
||||
public PlayerBetRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
|
||||
int amount = accessor.require("AMOUNT", Integer::parseInt);
|
||||
|
||||
return new PlayerBetRequest(primitiveRequest.context(), amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.bet;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request for BET command. */
|
||||
public class PlayerBetRequest extends Request {
|
||||
private final int amount;
|
||||
|
||||
public PlayerBetRequest(RequestContext context, int amount) {
|
||||
super(context);
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
/**
|
||||
* Minimal handler for CALL: acknowledges the command.
|
||||
*/
|
||||
public class PlayerCallHandler extends CommandHandler<PlayerCallRequest> {
|
||||
|
||||
public PlayerCallHandler(ResponseDispatcher responseDispatcher) {
|
||||
super(responseDispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(PlayerCallRequest request) {
|
||||
// TODO: integrate with game engine to process call.
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
|
||||
/** Parser for the CALL command (no parameters). */
|
||||
public class PlayerCallParser implements CommandParser<PlayerCallRequest> {
|
||||
@Override
|
||||
public PlayerCallRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
return new PlayerCallRequest(primitiveRequest.context());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.call;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request for CALL command. */
|
||||
public class PlayerCallRequest extends Request {
|
||||
public PlayerCallRequest(RequestContext context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
/** Minimal handler for FOLD: acknowledges the command. */
|
||||
public class PlayerFoldHandler extends CommandHandler<PlayerFoldRequest> {
|
||||
|
||||
public PlayerFoldHandler(ResponseDispatcher responseDispatcher) {
|
||||
super(responseDispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(PlayerFoldRequest request) {
|
||||
// TODO: integrate with game engine to process call.
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
|
||||
/** Parser for the FOLD command (no parameters). */
|
||||
public class PlayerFoldParser implements CommandParser<PlayerFoldRequest> {
|
||||
@Override
|
||||
public PlayerFoldRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
return new PlayerFoldRequest(primitiveRequest.context());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.fold;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request for FOLD command. */
|
||||
public class PlayerFoldRequest extends Request {
|
||||
public PlayerFoldRequest(RequestContext context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
/**
|
||||
* Minimal handler for RAISE: validates and acknowledges the command.
|
||||
*/
|
||||
public class PlayerRaiseHandler extends CommandHandler<PlayerRaiseRequest> {
|
||||
|
||||
public PlayerRaiseHandler(ResponseDispatcher responseDispatcher) {
|
||||
super(responseDispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(PlayerRaiseRequest request) {
|
||||
int amount = request.getAmount();
|
||||
|
||||
if (amount < 0) {
|
||||
responseDispatcher
|
||||
.dispatch(new ErrorResponse(request.getContext(), "INVALID_AMOUNT", "Amount must be non-negative"));
|
||||
return;
|
||||
}
|
||||
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
|
||||
/** Parser for the RAISE command. */
|
||||
public class PlayerRaiseParser implements CommandParser<PlayerRaiseRequest> {
|
||||
@Override
|
||||
public PlayerRaiseRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor = new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
|
||||
int amount = accessor.require("AMOUNT", Integer::parseInt);
|
||||
|
||||
return new PlayerRaiseRequest(primitiveRequest.context(), amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.game.raise;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request for RAISE command. */
|
||||
public class PlayerRaiseRequest extends Request {
|
||||
private final int amount;
|
||||
|
||||
public PlayerRaiseRequest(RequestContext context, int amount) {
|
||||
super(context);
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Handles {@link ListUsersRequest}s by retrieving all users from the {@link UserRegistry} and
|
||||
* dispatching a {@link ListUsersResponse} containing the list of users.
|
||||
*/
|
||||
public class ListUsersHandler extends CommandHandler<ListUsersRequest> {
|
||||
private final UserRegistry userRegistry;
|
||||
|
||||
/**
|
||||
* Creates a new handler for listing all users
|
||||
*
|
||||
* @param responseDispatcher the dispatcher used to send the response
|
||||
* @param userRegistry the registry used to look up existing users
|
||||
*/
|
||||
public ListUsersHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
|
||||
super(responseDispatcher);
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the list users request
|
||||
*
|
||||
* <p>All users are retrieved from the {@link UserRegistry} and returned in a {@link
|
||||
* ListUsersResponse}.
|
||||
*
|
||||
* @param request the request to execute
|
||||
*/
|
||||
@Override
|
||||
public void execute(ListUsersRequest request) {
|
||||
Collection<User> users = userRegistry.getAllUsers();
|
||||
responseDispatcher.dispatch(new ListUsersResponse(request.getContext(), users));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
|
||||
/** Parses a primitive request into a {@link ListUsersRequest}. */
|
||||
public class ListUsersParser implements CommandParser<ListUsersRequest> {
|
||||
/**
|
||||
* Parses a primitive request into a ListUsersRequest.
|
||||
*
|
||||
* @param primitiveRequest the request to parse
|
||||
* @return the created {@link ListUsersRequest}
|
||||
*/
|
||||
@Override
|
||||
public ListUsersRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
return new ListUsersRequest(primitiveRequest.context());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request implementation used to retrieve all existing users from the server. */
|
||||
public class ListUsersRequest extends Request {
|
||||
/**
|
||||
* Constructs a new ListUsersRequest with the given context
|
||||
*
|
||||
* @param context the {@link RequestContext} containing information for responding to the
|
||||
* request
|
||||
*/
|
||||
public ListUsersRequest(RequestContext context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBody;
|
||||
import java.util.Collection;
|
||||
|
||||
/** Response containing a list of all active users on the server */
|
||||
public class ListUsersResponse extends SuccessResponse {
|
||||
/**
|
||||
* Creates a new ListUsersResponse containing the given list of users
|
||||
*
|
||||
* @param context the {@link RequestContext} associated with the request
|
||||
* @param users the collection of users currently active on the server
|
||||
*/
|
||||
public ListUsersResponse(RequestContext context, Collection<User> users) {
|
||||
super(
|
||||
context,
|
||||
ResponseBody.builder()
|
||||
.block(
|
||||
"USERS",
|
||||
users_block -> {
|
||||
for (User user : users) {
|
||||
users_block.block(
|
||||
"USER",
|
||||
user_block -> {
|
||||
user_block.param("USERNAME", user.getName());
|
||||
user_block.param("ID", user.getId().value());
|
||||
});
|
||||
}
|
||||
})
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserFactory;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Handles {@link LoginRequest}s to create a user for a session, if the session has not assigned one
|
||||
* already
|
||||
*/
|
||||
public class LoginHandler extends CommandHandler<LoginRequest> {
|
||||
private final UserRegistry userRegistry;
|
||||
private final UserFactory userFactory;
|
||||
|
||||
/**
|
||||
* Creates a new handler for checking for existing user and creating a new one
|
||||
*
|
||||
* @param responseDispatcher the dispatcher used to send the response
|
||||
* @param userRegistry the registry used to look up existing users and create the new one
|
||||
*/
|
||||
public LoginHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
|
||||
super(responseDispatcher);
|
||||
this.userRegistry = userRegistry;
|
||||
this.userFactory = new UserFactory(userRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the login request
|
||||
*
|
||||
* <p>If no user is already assigned to the session, a new user is created and its name and
|
||||
* {@link UserId} returned in the response. If the session has already a user assigned, a {@code
|
||||
* ALREADY_LOGGED_IN} error is responded with.
|
||||
*
|
||||
* @param request the request to execute
|
||||
*/
|
||||
@Override
|
||||
public void execute(LoginRequest request) {
|
||||
Optional<User> existingUser = userRegistry.getBySessionId(request.getSessionId());
|
||||
if (existingUser.isPresent()) {
|
||||
responseDispatcher.dispatch(
|
||||
new ErrorResponse(
|
||||
request.getContext(),
|
||||
"ALREADY_LOGGED_IN",
|
||||
"This session is already associated with an active user."));
|
||||
return;
|
||||
}
|
||||
|
||||
User user = userFactory.create(request.getUsername(), request.getSessionId());
|
||||
responseDispatcher.dispatch(
|
||||
new LoginResponse(request.getContext(), user.getName(), user.getId()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor;
|
||||
|
||||
/** Parses a primitive request into a {@link LoginRequest}. */
|
||||
public class LoginParser implements CommandParser<LoginRequest> {
|
||||
/**
|
||||
* Extracts the required {@code USERNAME} parameter from the incoming request.
|
||||
*
|
||||
* @param primitiveRequest the request to parse
|
||||
* @return {@link LoginRequest} containing the username
|
||||
*/
|
||||
@Override
|
||||
public LoginRequest parse(PrimitiveRequest primitiveRequest) {
|
||||
RequestParameterAccessor accessor =
|
||||
new RequestParameterAccessor(primitiveRequest.parameters());
|
||||
return new LoginRequest(primitiveRequest.context(), accessor.require("USERNAME"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
|
||||
/** Request implementation used to create server-side user instance based on provided username */
|
||||
public class LoginRequest extends Request {
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* Constructs a new LoginRequest with the given context and desired username
|
||||
*
|
||||
* @param context the {@link RequestContext} containing information for responding to the
|
||||
* request
|
||||
* @param username the desired username when creating the user
|
||||
*/
|
||||
public LoginRequest(RequestContext context, String username) {
|
||||
super(context);
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the desired username requested for login
|
||||
*
|
||||
* @return the desired username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder;
|
||||
|
||||
/** Response containing the assigned username and id of said user */
|
||||
public class LoginResponse extends SuccessResponse {
|
||||
/**
|
||||
* @param context the {@link RequestContext} associated with the request
|
||||
* @param assignedUsername the assigned username to this user
|
||||
* @param id of the created user
|
||||
*/
|
||||
public LoginResponse(RequestContext context, String assignedUsername, UserId id) {
|
||||
super(
|
||||
context,
|
||||
new ResponseBodyBuilder()
|
||||
.param("USERNAME", assignedUsername)
|
||||
.param("ID", id.value())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher;
|
||||
|
||||
/** Handles {@link LogoutRequest} to logout connected user. */
|
||||
public class LogoutHandler extends CommandHandler<LogoutRequest> {
|
||||
private final UserRegistry userRegistry;
|
||||
|
||||
/**
|
||||
* Creates a new logout handler to logout user.
|
||||
*
|
||||
* @param responseDispatcher dispatcher used to send the logout result back to the client
|
||||
* @param userRegistry registry responsible for tracking connected user sessions
|
||||
*/
|
||||
public LogoutHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
|
||||
super(responseDispatcher);
|
||||
this.userRegistry = userRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the logout request for the given request.
|
||||
*
|
||||
* <p>The user is removed from the {@link UserRegistry}.
|
||||
*
|
||||
* @param request the request to execute
|
||||
*/
|
||||
@Override
|
||||
public void execute(LogoutRequest request) {
|
||||
boolean wasRemoved = userRegistry.removeBySessionId(request.getSessionId());
|
||||
if (wasRemoved) {
|
||||
responseDispatcher.dispatch(new OkResponse(request.getContext()));
|
||||
} else {
|
||||
responseDispatcher.dispatch(
|
||||
new ErrorResponse(
|
||||
request.getContext(),
|
||||
"NO_USER_ASSOCIATED",
|
||||
"No user is associated with your session. Did you login before?"));
|
||||
}
|
||||
}
|
||||
}
|
||||