Merge branch 'feat/game-ui' into 'main'
Add Game UI logic See merge request cs108-fs26/Gruppe-13!99
@@ -0,0 +1,9 @@
|
||||
# Besprechung über Veränderung der Aufgabenteilung
|
||||
|
||||
## Verschieben von Aufgaben
|
||||
|
||||
Da die Implementation der client server Kommunikation länger dauerte als geplant, wurden achivements erfüllt, die erst bei späteren Meilensteinen relevant sein werden.
|
||||
|
||||
## Aufgabeverteilung
|
||||
|
||||
Um zu vermeiden, dass sich Personen erst lange Zeit in den Code anderer einarbeiten müssen um zu helfen, wurden Dokumentation und das Erstellen von Materialien für MS4 auf ein Teammitgleid (Jona) ausgelagert um das Projekt nicht zu verlangsamen
|
||||
@@ -0,0 +1,537 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<title>casono-rules-easy-description</title>
|
||||
<style>
|
||||
html {
|
||||
color: #1a1a1a;
|
||||
background-color: #fdfdfd;
|
||||
}
|
||||
body {
|
||||
margin: 0 auto;
|
||||
max-width: 36em;
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
padding-top: 50px;
|
||||
padding-bottom: 50px;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-kerning: normal;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
font-size: 0.9em;
|
||||
padding: 12px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
html {
|
||||
background-color: white;
|
||||
}
|
||||
body {
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
font-size: 12pt;
|
||||
}
|
||||
p, h2, h3 {
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
h2, h3, h4 {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
a {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
a:visited {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 1.4em;
|
||||
}
|
||||
h5, h6 {
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
h6 {
|
||||
font-weight: normal;
|
||||
}
|
||||
ol, ul {
|
||||
padding-left: 1.7em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
li > ol, li > ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
blockquote {
|
||||
margin: 1em 0 1em 1.7em;
|
||||
padding-left: 1em;
|
||||
border-left: 2px solid #e6e6e6;
|
||||
color: #606060;
|
||||
}
|
||||
code {
|
||||
font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;
|
||||
font-size: 85%;
|
||||
margin: 0;
|
||||
hyphens: manual;
|
||||
}
|
||||
pre {
|
||||
margin: 1em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
pre code {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
.sourceCode {
|
||||
background-color: transparent;
|
||||
overflow: visible;
|
||||
}
|
||||
hr {
|
||||
background-color: #1a1a1a;
|
||||
border: none;
|
||||
height: 1px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
table {
|
||||
margin: 1em 0;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
font-variant-numeric: lining-nums tabular-nums;
|
||||
}
|
||||
table caption {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
tbody {
|
||||
margin-top: 0.5em;
|
||||
border-top: 1px solid #1a1a1a;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
}
|
||||
th {
|
||||
border-top: 1px solid #1a1a1a;
|
||||
padding: 0.25em 0.5em 0.25em 0.5em;
|
||||
}
|
||||
td {
|
||||
padding: 0.125em 0.5em 0.25em 0.5em;
|
||||
}
|
||||
header {
|
||||
margin-bottom: 4em;
|
||||
text-align: center;
|
||||
}
|
||||
#TOC li {
|
||||
list-style: none;
|
||||
}
|
||||
#TOC ul {
|
||||
padding-left: 1.3em;
|
||||
}
|
||||
#TOC > ul {
|
||||
padding-left: 0;
|
||||
}
|
||||
#TOC a:not(:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||||
div.column{flex: auto; overflow-x: auto;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
/* The extra [class] is a hack that increases specificity enough to
|
||||
override a similar rule in reveal.js */
|
||||
ul.task-list[class]{list-style: none;}
|
||||
ul.task-list li input[type="checkbox"] {
|
||||
font-size: inherit;
|
||||
width: 0.8em;
|
||||
margin: 0 0.8em 0.2em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="casono-rules-easy-description">Casono Rules Easy
|
||||
Description</h1>
|
||||
<blockquote>
|
||||
<p>Source:
|
||||
https://www.youtube.com/watch?v=h-1WyU5Wqsw&pp=ygUZbGVybmVuIHBva2VyIHRleGFzIGhvbGRlbQ%3D%3D
|
||||
<!-- vim-markdown-toc GFM --></p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li><a href="#deustch">Deustch</a>
|
||||
<ul>
|
||||
<li><a href="#blinds-small-blind--big-blind">Blinds (Small Blind &
|
||||
Big Blind)</a></li>
|
||||
<li><a href="#erste-setzrunde-preflop">Erste Setzrunde
|
||||
(Preflop)</a></li>
|
||||
<li><a href="#beispiel-preflop">Beispiel Preflop</a></li>
|
||||
<li><a href="#flop-3-gemeinschaftskarten">Flop (3
|
||||
Gemeinschaftskarten)</a></li>
|
||||
<li><a href="#beispiel-flop">Beispiel Flop</a></li>
|
||||
<li><a href="#turn-4-karte">Turn (4. Karte)</a></li>
|
||||
<li><a href="#river-5-karte">River (5. Karte)</a></li>
|
||||
<li><a href="#showdown-gewinnentscheidung">Showdown
|
||||
(Gewinnentscheidung)</a></li>
|
||||
<li><a href="#poker-hand-rankings-gewichtung">Poker Hand Rankings
|
||||
(Gewichtung)</a></li>
|
||||
<li><a href="#fazit">Fazit</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#english">English</a>
|
||||
<ul>
|
||||
<li><a href="#blinds-small-blind--big-blind-1">Blinds (Small Blind &
|
||||
Big Blind)</a></li>
|
||||
<li><a href="#first-betting-round-preflop">First Betting Round
|
||||
(Preflop)</a></li>
|
||||
<li><a href="#preflop-example">Preflop Example</a></li>
|
||||
<li><a href="#flop-3-community-cards">Flop (3 Community Cards)</a></li>
|
||||
<li><a href="#flop-example">Flop Example</a></li>
|
||||
<li><a href="#turn-4th-card">Turn (4th Card)</a></li>
|
||||
<li><a href="#river-5th-card">River (5th Card)</a></li>
|
||||
<li><a href="#showdown-winning-decision">Showdown (Winning
|
||||
Decision)</a></li>
|
||||
<li><a href="#poker-hand-rankings-hierarchy">Poker Hand Rankings
|
||||
(Hierarchy)</a></li>
|
||||
<li><a href="#conclusion">Conclusion</a></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
<!-- vim-markdown-toc -->
|
||||
<h2 id="deutsch">Deutsch</h2>
|
||||
<p>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.</p>
|
||||
<p>Damit Du in diesem Erlebnisraum starten kannst, ist es – wie bei
|
||||
jedem Spiel – notwendig, zuerst die Grundregeln und den Spielablauf zu
|
||||
verstehen.</p>
|
||||
<p>Also los geht es:</p>
|
||||
<p>Wir haben am Tisch <strong>4 Spieler</strong>: Julian, Mathis, Jona
|
||||
und Lars. Jeder Spieler startet mit <strong>20.000 Chips</strong>. Jeder
|
||||
bekommt <strong>2 Karten auf die Hand</strong> und es gibt zusätzlich
|
||||
<strong>5 Gemeinschaftskarten</strong>, die später in der Mitte
|
||||
aufgedeckt werden.</p>
|
||||
<figure>
|
||||
<img src="./images/1.png" alt="1. image" />
|
||||
<figcaption aria-hidden="true">1. image</figcaption>
|
||||
</figure>
|
||||
<p>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.</p>
|
||||
<p>Regel: <em>34 Button Placement and Movement 🟢</em></p>
|
||||
<h3 id="blinds-small-blind-big-blind">Blinds (Small Blind & Big
|
||||
Blind)</h3>
|
||||
<p>Bevor die Karten verteilt werden, gibt es zwei Pflicht-Einsätze:</p>
|
||||
<p>Der Small Blind und der Big Blind. Der Big Blind ist immer doppelt so
|
||||
hoch wie der Small Blind.</p>
|
||||
<figure>
|
||||
<img src="./images/2.png" alt="2. image" />
|
||||
<figcaption aria-hidden="true">2. image</figcaption>
|
||||
</figure>
|
||||
<p>Regel: <em>32 Dead Button 🟡</em></p>
|
||||
<p>Diese Einsätze sorgen dafür, dass sofort ein Pot entsteht und das
|
||||
Spiel überhaupt beginnt, weil jeder schon “im Spiel” ist.</p>
|
||||
<p>Danach werden die Karten verteilt: zuerst Small Blind, dann Big Blind
|
||||
und dann im Uhrzeigersinn alle anderen Spieler.</p>
|
||||
<h3 id="erste-setzrunde-preflop">Erste Setzrunde (Preflop)</h3>
|
||||
<p>Die erste Setzrunde beginnt immer bei dem Spieler links vom Big
|
||||
Blind.</p>
|
||||
<p>Jetzt muss jeder Spieler entscheiden:</p>
|
||||
<ul>
|
||||
<li>Fold (aussteigen)</li>
|
||||
<li>Call (mitgehen)</li>
|
||||
<li>Raise (erhöhen)</li>
|
||||
</ul>
|
||||
<p>Regel: <em>40 Methods of Betting 🟢</em> Regel: <em>41 Methods of
|
||||
Calling 🟢</em> Regel: <em>42 Methods of Raising 🟢</em> Regel: <em>50
|
||||
Acting in Turn 🟢</em></p>
|
||||
<h3 id="beispiel-preflop">Beispiel Preflop</h3>
|
||||
<p>Julian schaut seine Karten an und entscheidet sich direkt für einen
|
||||
Call von <strong>600 Chips</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/3.png" alt="3. image" />
|
||||
<figcaption aria-hidden="true">3. image</figcaption>
|
||||
</figure>
|
||||
<p>Mathis sieht seine Karten an und merkt, dass sie nicht gut sind, also
|
||||
foldet er und steigt aus.</p>
|
||||
<figure>
|
||||
<img src="./images/4.png" alt="4. image" />
|
||||
<figcaption aria-hidden="true">4. image</figcaption>
|
||||
</figure>
|
||||
<p>Jona ist nun dran und entscheidet sich ebenfalls für einen Call, weil
|
||||
seine Hand spielbar ist.</p>
|
||||
<figure>
|
||||
<img src="./images/5.png" alt="5. image" />
|
||||
<figcaption aria-hidden="true">5. image</figcaption>
|
||||
</figure>
|
||||
<p>Lars schaut seine Karten an, erkennt eine starke Hand und erhöht auf
|
||||
<strong>1200 Chips</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/6.png" alt="6. image" />
|
||||
<figcaption aria-hidden="true">6. image</figcaption>
|
||||
</figure>
|
||||
<p>Damit verändert sich sofort die Situation: Julian und Jona müssen
|
||||
entscheiden, ob sie diesen Raise bezahlen, selbst erhöhen oder
|
||||
aussteigen.</p>
|
||||
<h3 id="flop-3-gemeinschaftskarten">Flop (3 Gemeinschaftskarten)</h3>
|
||||
<p>Jetzt werden <strong>3 Gemeinschaftskarten</strong> in die Mitte
|
||||
gelegt. Ab hier verändert sich das Spiel komplett, weil alle Spieler
|
||||
zusätzliche Informationen bekommen.</p>
|
||||
<figure>
|
||||
<img src="./images/7.png" alt="7. image" />
|
||||
<figcaption aria-hidden="true">7. image</figcaption>
|
||||
</figure>
|
||||
<p>Es beginnt eine neue Setzrunde.</p>
|
||||
<p>Regel: <em>49 Accepted Action 🟢</em></p>
|
||||
<h3 id="beispiel-flop">Beispiel Flop</h3>
|
||||
<p>Lars setzt <strong>1000 Chips</strong> als Erstes. Julian entscheidet
|
||||
sich mitzugehen (Call), weil seine Karten durch die Gemeinschaftskarten
|
||||
stärker geworden sind.</p>
|
||||
<figure>
|
||||
<img src="./images/8.png" alt="8. image" />
|
||||
<figcaption aria-hidden="true">8. image</figcaption>
|
||||
</figure>
|
||||
<p>Jona steigt aus, weil er keine gute Verbindung mehr sieht. Mathis ist
|
||||
bereits raus.</p>
|
||||
<h3 id="turn-4.-karte">Turn (4. Karte)</h3>
|
||||
<p>Jetzt kommt die <strong>4. Gemeinschaftskarte</strong>.</p>
|
||||
<p>Wieder beginnt eine neue Setzrunde.</p>
|
||||
<p>Lars setzt diesmal <strong>3000 Chips</strong>. Julian bezahlt erneut
|
||||
(Call), weil seine Hand weiterhin gut spielbar ist.</p>
|
||||
<figure>
|
||||
<img src="./images/9.png" alt="9. image" />
|
||||
<figcaption aria-hidden="true">9. image</figcaption>
|
||||
</figure>
|
||||
<p>Regel: <em>53 Action Out of Turn 🟡</em></p>
|
||||
<h3 id="river-5.-karte">River (5. Karte)</h3>
|
||||
<p>Jetzt wird die letzte Gemeinschaftskarte aufgedeckt.</p>
|
||||
<p>Dies ist die letzte Entscheidung im Spiel.</p>
|
||||
<p>Lars setzt <strong>5000 Chips</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/10.png" alt="10. image" />
|
||||
<figcaption aria-hidden="true">10. image</figcaption>
|
||||
</figure>
|
||||
<p>Julian muss jetzt entscheiden: Fold, Call oder Raise auf
|
||||
<strong>10000 Chips</strong>.</p>
|
||||
<p>Regel: <em>54 Pot Size Bets 🟡</em></p>
|
||||
<h3 id="showdown-gewinnentscheidung">Showdown (Gewinnentscheidung)</h3>
|
||||
<p>Wenn nach der letzten Setzrunde noch zwei Spieler übrig sind, kommt
|
||||
es zum Showdown.</p>
|
||||
<p>Beide Spieler zeigen ihre Karten offen. Gewonnen hat die
|
||||
<strong>beste 5-Karten-Kombination aus Handkarten und
|
||||
Gemeinschaftskarten</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/11.png" alt="11. image" />
|
||||
<figcaption aria-hidden="true">11. image</figcaption>
|
||||
</figure>
|
||||
<p>Regel: <em>12 Cards Speak at Showdown 🟢</em> Regel: <em>16 Face Up
|
||||
for All-Ins 🟢</em> Regel: <em>17 Non All-In Showdowns 🟢</em></p>
|
||||
<p>Wenn Julian den letzten Einsatz bezahlt, werden die Hände verglichen.
|
||||
Wenn er foldet, gewinnt Lars automatisch den gesamten Pot.</p>
|
||||
<h3 id="poker-hand-rankings-gewichtung">Poker Hand Rankings
|
||||
(Gewichtung)</h3>
|
||||
<p>Die Kartenkombinationen sind klar geordnet – von schwach bis extrem
|
||||
stark:</p>
|
||||
<figure>
|
||||
<img src="./images/12.png" alt="12. image" />
|
||||
<figcaption aria-hidden="true">12. image</figcaption>
|
||||
</figure>
|
||||
<p>Je höher die Kombination, desto stärker die Hand und desto
|
||||
wahrscheinlicher der Gewinn.</p>
|
||||
<p>Julian: 2. Paar:</p>
|
||||
<figure>
|
||||
<img src="./images/13.png" alt="13. image" />
|
||||
<figcaption aria-hidden="true">13. image</figcaption>
|
||||
</figure>
|
||||
<p>Lars: 1. Paar:</p>
|
||||
<figure>
|
||||
<img src="./images/14.png" alt="14. image" />
|
||||
<figcaption aria-hidden="true">14. image</figcaption>
|
||||
</figure>
|
||||
<p>Da zwei Paare in der Rangfolge über einem einzelnen Paar stehen,
|
||||
gewinnt Julian diese Runde.</p>
|
||||
<h3 id="fazit">Fazit</h3>
|
||||
<p>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.</p>
|
||||
<p>Regel: <em>67 One Player One Hand 🟢</em> Regel: <em>52 Incorrect
|
||||
Bets 🟡</em> Regel: <em>57 Non-Standard Betting 🟡</em></p>
|
||||
<h2 id="english">English</h2>
|
||||
<p>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?</p>
|
||||
<p>To enter this experiential space, it’s necessary—like with any
|
||||
game—to first understand the basic rules and game flow.</p>
|
||||
<p>So let’s begin:</p>
|
||||
<p>We have <strong>4 players</strong> at the table: Julian, Mathis,
|
||||
Jona, and Lars. Each player starts with <strong>20,000 chips</strong>.
|
||||
Each receives <strong>2 hole cards</strong>, and there are <strong>5
|
||||
community cards</strong> that will later be revealed in the center.</p>
|
||||
<figure>
|
||||
<img src="./images/1.png" alt="1. image" />
|
||||
<figcaption aria-hidden="true">1. image</figcaption>
|
||||
</figure>
|
||||
<p>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.</p>
|
||||
<p>Rule: <em>34 Button Placement and Movement 🟢</em></p>
|
||||
<h3 id="blinds-small-blind-big-blind-1">Blinds (Small Blind & Big
|
||||
Blind)</h3>
|
||||
<p>Before cards are dealt, there are two mandatory bets:</p>
|
||||
<p>The Small Blind and the Big Blind. The Big Blind is always double the
|
||||
amount of the Small Blind.</p>
|
||||
<figure>
|
||||
<img src="./images/2.png" alt="2. image" />
|
||||
<figcaption aria-hidden="true">2. image</figcaption>
|
||||
</figure>
|
||||
<p>Rule: <em>32 Dead Button 🟡</em></p>
|
||||
<p>These forced bets ensure that a pot exists from the start, making the
|
||||
game begin with everyone already involved.</p>
|
||||
<p>After this, the cards are dealt: first to the Small Blind, then the
|
||||
Big Blind, and then clockwise to all other players.</p>
|
||||
<h3 id="first-betting-round-preflop">First Betting Round (Preflop)</h3>
|
||||
<p>The first betting round always begins with the player to the left of
|
||||
the Big Blind.</p>
|
||||
<p>Now each player must decide:</p>
|
||||
<ul>
|
||||
<li>Fold (exit the hand)</li>
|
||||
<li>Call (match the current bet)</li>
|
||||
<li>Raise (increase the bet)</li>
|
||||
</ul>
|
||||
<p>Rule: <em>40 Methods of Betting 🟢</em><br />
|
||||
Rule: <em>41 Methods of Calling 🟢</em><br />
|
||||
Rule: <em>42 Methods of Raising 🟢</em><br />
|
||||
Rule: <em>50 Acting in Turn 🟢</em></p>
|
||||
<h3 id="preflop-example">Preflop Example</h3>
|
||||
<p>Julian looks at his cards and decides to Call for <strong>600
|
||||
chips</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/3.png" alt="3. image" />
|
||||
<figcaption aria-hidden="true">3. image</figcaption>
|
||||
</figure>
|
||||
<p>Mathis sees his cards and realizes they aren’t strong, so he Folds
|
||||
and exits the hand.</p>
|
||||
<figure>
|
||||
<img src="./images/4.png" alt="4. image" />
|
||||
<figcaption aria-hidden="true">4. image</figcaption>
|
||||
</figure>
|
||||
<p>Jona is next and decides to Call as well, because his hand is
|
||||
playable.</p>
|
||||
<figure>
|
||||
<img src="./images/5.png" alt="5. image" />
|
||||
<figcaption aria-hidden="true">5. image</figcaption>
|
||||
</figure>
|
||||
<p>Lars looks at his cards, recognizes a strong hand, and Raises to
|
||||
<strong>1200 chips</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/6.png" alt="6. image" />
|
||||
<figcaption aria-hidden="true">6. image</figcaption>
|
||||
</figure>
|
||||
<p>This immediately changes the situation: Julian and Jona must now
|
||||
decide whether to call this raise, re-raise, or fold.</p>
|
||||
<h3 id="flop-3-community-cards">Flop (3 Community Cards)</h3>
|
||||
<p>Now <strong>3 community cards</strong> are placed face-up in the
|
||||
center. From this point, the game changes completely, as all players
|
||||
gain additional information.</p>
|
||||
<figure>
|
||||
<img src="./images/7.png" alt="7. image" />
|
||||
<figcaption aria-hidden="true">7. image</figcaption>
|
||||
</figure>
|
||||
<p>A new betting round begins.</p>
|
||||
<p>Rule: <em>49 Accepted Action 🟢</em></p>
|
||||
<h3 id="flop-example">Flop Example</h3>
|
||||
<p>Lars bets <strong>1,000 chips</strong> first. Julian decides to Call
|
||||
because his hand has improved with the community cards.</p>
|
||||
<figure>
|
||||
<img src="./images/8.png" alt="8. image" />
|
||||
<figcaption aria-hidden="true">8. image</figcaption>
|
||||
</figure>
|
||||
<p>Jona folds, as he no longer sees a strong connection. Mathis is
|
||||
already out.</p>
|
||||
<h3 id="turn-4th-card">Turn (4th Card)</h3>
|
||||
<p>Now the <strong>4th community card</strong> is revealed.</p>
|
||||
<p>Another betting round begins.</p>
|
||||
<p>Lars bets <strong>3,000 chips</strong> this time. Julian calls again,
|
||||
as his hand remains strong.</p>
|
||||
<figure>
|
||||
<img src="./images/9.png" alt="9. image" />
|
||||
<figcaption aria-hidden="true">9. image</figcaption>
|
||||
</figure>
|
||||
<p>Rule: <em>53 Action Out of Turn 🟡</em></p>
|
||||
<h3 id="river-5th-card">River (5th Card)</h3>
|
||||
<p>Now the final community card is revealed.</p>
|
||||
<p>This is the last decision point in the hand.</p>
|
||||
<p>Lars bets <strong>5,000 chips</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/10.png" alt="10. image" />
|
||||
<figcaption aria-hidden="true">10. image</figcaption>
|
||||
</figure>
|
||||
<p>Julian must now decide: Fold, Call, or even Raise to <strong>10,000
|
||||
chips</strong>.</p>
|
||||
<p>Rule: <em>54 Pot Size Bets 🟡</em></p>
|
||||
<h3 id="showdown-winning-decision">Showdown (Winning Decision)</h3>
|
||||
<p>If two or more players remain after the final betting round, a
|
||||
showdown occurs.</p>
|
||||
<p>All active players reveal their cards. The winner is the one with the
|
||||
<strong>best 5-card combination using any combination of their hole
|
||||
cards and the community cards</strong>.</p>
|
||||
<figure>
|
||||
<img src="./images/11.png" alt="11. image" />
|
||||
<figcaption aria-hidden="true">11. image</figcaption>
|
||||
</figure>
|
||||
<p>Rule: <em>12 Cards Speak at Showdown 🟢</em><br />
|
||||
Rule: <em>16 Face Up for All-Ins 🟢</em><br />
|
||||
Rule: <em>17 Non All-In Showdowns 🟢</em></p>
|
||||
<p>If Julian calls the final bet, the hands are compared. If he folds,
|
||||
Lars wins the entire pot automatically.</p>
|
||||
<h3 id="poker-hand-rankings-hierarchy">Poker Hand Rankings
|
||||
(Hierarchy)</h3>
|
||||
<p>The card combinations are clearly ranked—from weakest to
|
||||
strongest:</p>
|
||||
<figure>
|
||||
<img src="./images/12.png" alt="12. image" />
|
||||
<figcaption aria-hidden="true">12. image</figcaption>
|
||||
</figure>
|
||||
<p>The higher the combination, the stronger the hand, and the greater
|
||||
the chance of winning.</p>
|
||||
<p>Julian: Two Pair:</p>
|
||||
<figure>
|
||||
<img src="./images/13.png" alt="13. image" />
|
||||
<figcaption aria-hidden="true">13. image</figcaption>
|
||||
</figure>
|
||||
<p>Lars: One Pair:</p>
|
||||
<figure>
|
||||
<img src="./images/14.png" alt="14. image" />
|
||||
<figcaption aria-hidden="true">14. image</figcaption>
|
||||
</figure>
|
||||
<p>Since two pair ranks higher than one pair, Julian wins this
|
||||
round.</p>
|
||||
<h3 id="conclusion">Conclusion</h3>
|
||||
<p>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.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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.
|
||||
@@ -2,6 +2,53 @@ package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
/** Represents a playing card with a value and suit. */
|
||||
public class Card {
|
||||
public String value;
|
||||
public String suit;
|
||||
private String value;
|
||||
private String suit;
|
||||
|
||||
/**
|
||||
* Constructs a Card with the specified value and suit.
|
||||
*
|
||||
* @param value The value of the card (e.g., 2, 3, ..., 10, J, Q, K, A).
|
||||
* @param suit The suit of the card (e.g., Hearts, Diamonds, Clubs, Spades).
|
||||
*/
|
||||
public Card(String value, String suit) {
|
||||
this.value = value;
|
||||
this.suit = suit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the card.
|
||||
*
|
||||
* @return The value of the card.
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the suit of the card.
|
||||
*
|
||||
* @return The suit of the card.
|
||||
*/
|
||||
public String getSuit() {
|
||||
return suit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the card.
|
||||
*
|
||||
* @param value The value to set for the card.
|
||||
*/
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the suit of the card.
|
||||
*
|
||||
* @param suit The suit to set for the card.
|
||||
*/
|
||||
public void setSuit(String suit) {
|
||||
this.suit = suit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.GameClient;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service class responsible for managing the game state and providing methods to interact with the
|
||||
* game.
|
||||
*/
|
||||
public class GameService {
|
||||
|
||||
private final GameClient client;
|
||||
private GameState state;
|
||||
|
||||
/**
|
||||
* Constructs a GameService with the given GameClient for communication with the server.
|
||||
*
|
||||
* @param client The GameClient instance used to send commands and receive responses from the
|
||||
* server.
|
||||
*/
|
||||
public GameService(GameClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the game state by fetching the latest state from the server using the GameClient.
|
||||
*
|
||||
* @return The updated GameState object representing the current state of the game after
|
||||
* refreshing.
|
||||
*/
|
||||
public GameState refresh() {
|
||||
state = client.fetchGameState();
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current game state.
|
||||
*
|
||||
* @return The current GameState object representing the state of the game.
|
||||
*/
|
||||
public int getPot() {
|
||||
return state.pot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current game state.
|
||||
*
|
||||
* @return The current GameState object representing the state of the game.
|
||||
*/
|
||||
public List<Card> getCommunityCards() {
|
||||
return state.communityCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of players in the current game state.
|
||||
*
|
||||
* @return A list of Player objects representing the players in the current game state.
|
||||
*/
|
||||
public List<Player> getPlayers() {
|
||||
return state.players;
|
||||
}
|
||||
|
||||
/** Send a CALL command to the server to indicate that the player wants to call. */
|
||||
public void call() {
|
||||
client.sendCall();
|
||||
}
|
||||
|
||||
/** Send a FOLD command to the server to indicate that the player wants to fold. */
|
||||
public void fold() {
|
||||
client.sendFold();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a BET command to the server to indicate that the player wants to bet with the specified
|
||||
* amount.
|
||||
*
|
||||
* @param amount The amount the player wants to bet.
|
||||
*/
|
||||
public void bet(int amount) {
|
||||
client.sendBet(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a RAISE command to the server to indicate that the player wants to raise to the
|
||||
* specified amount.
|
||||
*
|
||||
* @param amount The amount the player wants to raise to.
|
||||
*/
|
||||
public void raise(int amount) {
|
||||
client.sendRaise(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the winner of the game if there is one. If the game is still ongoing or if there is no
|
||||
* winner, this method returns null.
|
||||
*
|
||||
* @return The Player object representing the winner of the game, or null if there is no winner
|
||||
* yet.
|
||||
*/
|
||||
public Player getWinner() {
|
||||
if (state.winnerIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.players.get(state.winnerIndex);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public class GameState {
|
||||
public int currentBet;
|
||||
public int dealer;
|
||||
public int activePlayer;
|
||||
public int winnerIndex = -1;
|
||||
|
||||
public List<Card> communityCards = new ArrayList<>();
|
||||
public List<Player> players = new ArrayList<>();
|
||||
|
||||
@@ -8,10 +8,186 @@ import java.util.List;
|
||||
* (e.g., "active", "folded"), and their hole cards.
|
||||
*/
|
||||
public class Player {
|
||||
public String name;
|
||||
public int chips;
|
||||
public int bet;
|
||||
public String state;
|
||||
|
||||
public List<Card> cards = new ArrayList<>();
|
||||
private PlayerId id;
|
||||
private int chips;
|
||||
private int bet;
|
||||
private PlayerState state;
|
||||
|
||||
private List<Card> cards = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs a new Player with default values. The player's state is set to ACTIVE by default.
|
||||
*/
|
||||
public Player() {
|
||||
this.state = PlayerState.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Player with the specified ID and initial chip count. The player's state is
|
||||
* set to ACTIVE by default.
|
||||
*
|
||||
* @param id The unique identifier for the player.
|
||||
* @param chips The initial number of chips the player has.
|
||||
*/
|
||||
public Player(PlayerId id, int chips) {
|
||||
this.id = id;
|
||||
this.chips = chips;
|
||||
this.bet = 0;
|
||||
this.state = PlayerState.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the unique identifier of the player.
|
||||
*
|
||||
* @return The player's ID.
|
||||
*/
|
||||
public PlayerId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the unique identifier of the player.
|
||||
*
|
||||
* @param id The player's ID to set.
|
||||
*/
|
||||
public void setId(PlayerId id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name of the player. If the player's ID is not set, it returns an empty
|
||||
* string.
|
||||
*
|
||||
* @return The player's name.
|
||||
*/
|
||||
public String getName() {
|
||||
return id != null ? id.value() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current chip count of the player.
|
||||
*
|
||||
* @return The number of chips the player has.
|
||||
*/
|
||||
public int getChips() {
|
||||
return chips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current chip count of the player.
|
||||
*
|
||||
* @param chips The number of chips to set for the player.
|
||||
*/
|
||||
public void setChips(int chips) {
|
||||
this.chips = chips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current bet amount of the player.
|
||||
*
|
||||
* @return The amount the player has currently bet in the ongoing hand.
|
||||
*/
|
||||
public int getBet() {
|
||||
return bet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current bet amount of the player.
|
||||
*
|
||||
* @param bet The amount the player has currently bet in the ongoing hand to set.
|
||||
*/
|
||||
public void setBet(int bet) {
|
||||
this.bet = bet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current state of the player (e.g., ACTIVE, FOLDED, ALL_IN).
|
||||
*
|
||||
* @return The player's current state in the game.
|
||||
*/
|
||||
public PlayerState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current state of the player (e.g., ACTIVE, FOLDED, ALL_IN).
|
||||
*
|
||||
* @param state The player's current state in the game to set.
|
||||
*/
|
||||
public void setState(PlayerState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player is currently the dealer.
|
||||
*
|
||||
* @return True if the player's state is DEALER, false otherwise.
|
||||
*/
|
||||
public boolean isDealer() {
|
||||
return state == PlayerState.DEALER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of hole cards currently held by the player.
|
||||
*
|
||||
* @return A list of Card objects representing the player's hole cards.
|
||||
*/
|
||||
public List<Card> getCards() {
|
||||
return cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of hole cards currently held by the player.
|
||||
*
|
||||
* @param cards A list of Card objects representing the player's hole cards to set.
|
||||
*/
|
||||
public void setCards(List<Card> cards) {
|
||||
this.cards = cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a card to the player's list of hole cards.
|
||||
*
|
||||
* @param card The Card object to be added to the player's hole cards.
|
||||
*/
|
||||
public void addCard(Card card) {
|
||||
this.cards.add(card);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified amount of chips to the player's current chip count.
|
||||
*
|
||||
* @param amount The number of chips to add to the player's current chip count.
|
||||
*/
|
||||
public void addChips(int amount) {
|
||||
if (amount < 0) {
|
||||
throw new IllegalArgumentException("Amount cannot be negative");
|
||||
}
|
||||
this.chips += amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified amount of chips from the player's current chip count.
|
||||
*
|
||||
* @param amount The number of chips to remove from the player's current chip count.
|
||||
*/
|
||||
public void removeChips(int amount) {
|
||||
if (amount < 0) {
|
||||
throw new IllegalArgumentException("Amount cannot be negative");
|
||||
}
|
||||
if (amount > this.chips) {
|
||||
throw new IllegalArgumentException(
|
||||
"Not enough chips. Available: " + this.chips + ", Requested: " + amount);
|
||||
}
|
||||
this.chips -= amount;
|
||||
}
|
||||
|
||||
/** Sets the player's state to FOLDED, indicating that they have folded in the current hand. */
|
||||
public void fall() {
|
||||
this.state = PlayerState.FOLDED;
|
||||
if (this.id != null) {
|
||||
this.id = PlayerId.of(this.id.value() + " (Fall)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** Represents a unique identifier for a player in the client domain. */
|
||||
public record PlayerId(String value) {
|
||||
|
||||
/**
|
||||
* Constructs a PlayerId with the given string value, ensuring it is not null or empty.
|
||||
*
|
||||
* @param value the string value representing the player's unique identifier.
|
||||
*/
|
||||
public PlayerId {
|
||||
Objects.requireNonNull(value, "PlayerId cannot be null");
|
||||
value = value.trim();
|
||||
|
||||
if (value.isEmpty()) {
|
||||
throw new IllegalArgumentException("PlayerId cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory method to create a PlayerId. */
|
||||
public static PlayerId of(String value) {
|
||||
return new PlayerId(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the PlayerId, which is the encapsulated value.
|
||||
*
|
||||
* @return the string value of the PlayerId.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this PlayerId with another object for equality.
|
||||
*
|
||||
* @param o the object to compare to.
|
||||
* @return true if this PlayerId is equal to the given object, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerId playerId = (PlayerId) o;
|
||||
return Objects.equals(value, playerId.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code for this PlayerId.
|
||||
*
|
||||
* @return the hash code of the PlayerId.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.game;
|
||||
|
||||
/** Enumeration representing the possible states of a player in the poker game. */
|
||||
public enum PlayerState {
|
||||
ACTIVE,
|
||||
FOLDED,
|
||||
DEALER
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Card;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -13,6 +15,7 @@ import java.util.List;
|
||||
public class GameClient {
|
||||
|
||||
private final ClientService client;
|
||||
private final int gameId;
|
||||
|
||||
/**
|
||||
* Constructs a GameClient with the given ClientService for communication.
|
||||
@@ -20,80 +23,290 @@ public class GameClient {
|
||||
* @param client The ClientService instance used to send commands and receive responses from the
|
||||
* server.
|
||||
*/
|
||||
public GameClient(ClientService client) {
|
||||
public GameClient(ClientService client, int gameId) {
|
||||
this.client = client;
|
||||
this.gameId = gameId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current game state from the server by sending a command and parsing the
|
||||
* response.
|
||||
* Fetch the current game state from the server by sending a "GET_GAME_STATE"
|
||||
*
|
||||
* @return A GameState object representing the current state of the game.
|
||||
* @return A GameState object representing the current state of the game, as parsed
|
||||
*/
|
||||
public GameState getGameState() {
|
||||
List<String> response = client.processCommand("GET_GAME_STATE");
|
||||
return parseGameState(response);
|
||||
public GameState fetchGameState() {
|
||||
List<String> responseLines = client.processCommand("GET_GAME_STATE\nGAME_ID=" + gameId);
|
||||
|
||||
if (responseLines == null || responseLines.isEmpty()) {
|
||||
throw new RuntimeException("Empty server response");
|
||||
}
|
||||
|
||||
String fullResponse = String.join("\n", responseLines);
|
||||
|
||||
return parse(fullResponse);
|
||||
}
|
||||
|
||||
/** Send a CALL command to the server to indicate that the player wants to call */
|
||||
public void sendCall() {
|
||||
client.processCommand("CALL\nGAME_ID=" + gameId);
|
||||
}
|
||||
|
||||
/** Send a FOLD command to the server to indicate that the player wants to fold */
|
||||
public void sendFold() {
|
||||
client.processCommand("FOLD\nGAME_ID=" + gameId);
|
||||
}
|
||||
|
||||
/** Send a BET command to the server to indicate that the player wants to bet */
|
||||
public void sendBet(int amount) {
|
||||
client.processCommand("BET\nGAME_ID=" + gameId + "\nAMOUNT=" + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw response from the server into a structured GameState object.
|
||||
* Send a RAISE command to the server to indicate that the player wants to raise
|
||||
*
|
||||
* @param input The raw response string from the server.
|
||||
* @param amount The amount to raise to
|
||||
*/
|
||||
public void sendRaise(int amount) {
|
||||
client.processCommand("RAISE\nGAME_ID=" + gameId + "\nAMOUNT=" + amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw server response string into a structured GameState object.
|
||||
*
|
||||
* @param input The raw server response string containing the game state information.
|
||||
* @return A GameState object representing the current state of the game.
|
||||
*/
|
||||
private GameState parseGameState(List<String> input) {
|
||||
|
||||
private GameState parse(String input) {
|
||||
GameState state = new GameState();
|
||||
|
||||
// String[] lines = input.split("\n");
|
||||
ParserContext ctx = new ParserContext(state);
|
||||
|
||||
Player currentPlayer = null;
|
||||
Card currentCard = null;
|
||||
for (String raw : input.split("\n")) {
|
||||
String line = raw.trim();
|
||||
|
||||
for (String rawLine : input) {
|
||||
|
||||
String line = rawLine.trim();
|
||||
|
||||
if (line.startsWith("+OK") || line.equals("END")) {
|
||||
if (shouldSkip(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("PHASE=")) {
|
||||
state.phase = line.split("=")[1];
|
||||
} else if (line.startsWith("POT=")) {
|
||||
state.pot = Integer.parseInt(line.split("=")[1]);
|
||||
} else if (line.startsWith("CURRENT_BET=")) {
|
||||
state.currentBet = Integer.parseInt(line.split("=")[1]);
|
||||
} else if (line.startsWith("DEALER=")) {
|
||||
state.dealer = Integer.parseInt(line.split("=")[1]);
|
||||
} else if (line.startsWith("ACTIVE_PLAYER=")) {
|
||||
state.activePlayer = Integer.parseInt(line.split("=")[1]);
|
||||
} else if (line.startsWith("PLAYER")) {
|
||||
currentPlayer = new Player();
|
||||
state.players.add(currentPlayer);
|
||||
} else if (line.startsWith("NAME=") && currentPlayer != null) {
|
||||
currentPlayer.name = line.split("=")[1];
|
||||
} else if (line.startsWith("CHIPS=") && currentPlayer != null) {
|
||||
currentPlayer.chips = Integer.parseInt(line.split("=")[1]);
|
||||
} else if (line.startsWith("BET=") && currentPlayer != null) {
|
||||
currentPlayer.bet = Integer.parseInt(line.split("=")[1]);
|
||||
} else if (line.startsWith("STATE=") && currentPlayer != null) {
|
||||
currentPlayer.state = line.split("=")[1];
|
||||
} else if (line.startsWith("CARD")) {
|
||||
currentCard = new Card();
|
||||
|
||||
if (currentPlayer != null) {
|
||||
currentPlayer.cards.add(currentCard);
|
||||
} else {
|
||||
state.communityCards.add(currentCard);
|
||||
if (handleGlobal(line, ctx)) {
|
||||
continue;
|
||||
}
|
||||
} else if (line.startsWith("VALUE=") && currentCard != null) {
|
||||
currentCard.value = line.split("=")[1];
|
||||
} else if (line.startsWith("SUIT=") && currentCard != null) {
|
||||
currentCard.suit = line.split("=")[1];
|
||||
|
||||
if (handleSections(line, ctx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handlePlayer(line, ctx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handleCards(line, ctx)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Holds the mutable parsing state while processing the server response. */
|
||||
private static class ParserContext {
|
||||
GameState state;
|
||||
Player currentPlayer;
|
||||
Card currentCard;
|
||||
|
||||
boolean inPlayers;
|
||||
boolean inPlayerCards;
|
||||
boolean inCommunityCards;
|
||||
|
||||
ParserContext(GameState state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a line should be ignored.
|
||||
*
|
||||
* @param line the current input line
|
||||
* @return true if the line is empty or a status message, false otherwise
|
||||
*/
|
||||
private boolean shouldSkip(String line) {
|
||||
return line.isEmpty() || line.startsWith("+OK");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses global game state properties (e.g., phase, pot, current bet).
|
||||
*
|
||||
* @param line the current input line
|
||||
* @param ctx the parser context containing the game state
|
||||
* @return true if the line was handled, false otherwise
|
||||
*/
|
||||
private boolean handleGlobal(String line, ParserContext ctx) {
|
||||
GameState state = ctx.state;
|
||||
|
||||
if (line.startsWith("PHASE=")) {
|
||||
state.phase = value(line);
|
||||
} else if (line.startsWith("POT=")) {
|
||||
state.pot = intVal(line);
|
||||
} else if (line.startsWith("CURRENT_BET=")) {
|
||||
state.currentBet = intVal(line);
|
||||
} else if (line.startsWith("DEALER=")) {
|
||||
state.dealer = intVal(line);
|
||||
} else if (line.startsWith("ACTIVE_PLAYER=")) {
|
||||
state.activePlayer = intVal(line);
|
||||
} else if (line.startsWith("WINNER=")) {
|
||||
state.winnerIndex = intVal(line);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes section markers such as PLAYERS, PLAYER, CARDS, and END. Updates the parser context
|
||||
* accordingly.
|
||||
*
|
||||
* @param line the current input line
|
||||
* @param ctx the parser context
|
||||
* @return true if the line was handled, false otherwise
|
||||
*/
|
||||
private boolean handleSections(String line, ParserContext ctx) {
|
||||
|
||||
if (line.equals("END")) {
|
||||
ctx.inPlayers = false;
|
||||
ctx.inPlayerCards = false;
|
||||
ctx.inCommunityCards = false;
|
||||
ctx.currentPlayer = null;
|
||||
ctx.currentCard = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.equals("PLAYERS")) {
|
||||
ctx.inPlayers = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.equals("PLAYER")) {
|
||||
ctx.currentPlayer = new Player();
|
||||
ctx.state.players.add(ctx.currentPlayer);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.equals("CARDS")) {
|
||||
if (ctx.inPlayers && ctx.currentPlayer != null) {
|
||||
ctx.inPlayerCards = true;
|
||||
ctx.inCommunityCards = false;
|
||||
} else {
|
||||
ctx.inCommunityCards = true;
|
||||
ctx.inPlayerCards = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses properties of the current player (e.g., name, chips, bet, state).
|
||||
*
|
||||
* @param line the current input line
|
||||
* @param ctx the parser context
|
||||
* @return true if the line was handled, false otherwise
|
||||
*/
|
||||
private boolean handlePlayer(String line, ParserContext ctx) {
|
||||
Player p = ctx.currentPlayer;
|
||||
|
||||
if (p == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (line.startsWith("NAME=")) {
|
||||
p.setId(PlayerId.of(value(line)));
|
||||
} else if (line.startsWith("CHIPS=")) {
|
||||
p.setChips(intVal(line));
|
||||
} else if (line.startsWith("BET=")) {
|
||||
p.setBet(intVal(line));
|
||||
} else if (line.startsWith("STATE=")) {
|
||||
p.setState(parseState(value(line)));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses card-related lines and assigns cards to the current player or the community cards.
|
||||
*
|
||||
* @param line the current input line
|
||||
* @param ctx the parser context
|
||||
* @return true if the line was handled, false otherwise
|
||||
*/
|
||||
private boolean handleCards(String line, ParserContext ctx) {
|
||||
|
||||
if (line.startsWith("CARD")) {
|
||||
ctx.currentCard = new Card("", "");
|
||||
|
||||
if (ctx.inPlayerCards && ctx.currentPlayer != null) {
|
||||
ctx.currentPlayer.addCard(ctx.currentCard);
|
||||
} else if (ctx.inCommunityCards) {
|
||||
ctx.state.communityCards.add(ctx.currentCard);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.startsWith("VALUE=") && ctx.currentCard != null) {
|
||||
ctx.currentCard.setValue(value(line));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line.startsWith("SUIT=") && ctx.currentCard != null) {
|
||||
ctx.currentCard.setSuit(value(line));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract the value from a line in the format KEY=VALUE.
|
||||
*
|
||||
* @param line The input line from which to extract the value, expected to be in the format
|
||||
* KEY=VALUE.
|
||||
* @return The extracted value part of the input line, which is the substring after the first
|
||||
* '=' character.
|
||||
*/
|
||||
private String value(String line) {
|
||||
return line.split("=", 2)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract an integer value from a line in the format KEY=VALUE.
|
||||
*
|
||||
* @param line The input line from which to extract the integer value, expected to be in the
|
||||
* format KEY=VALUE where VALUE is an integer.
|
||||
* @return The extracted integer value from the input line, parsed from the substring after the
|
||||
* first '=' character.
|
||||
*/
|
||||
private int intVal(String line) {
|
||||
return Integer.parseInt(value(line));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to parse a string representation of a player's state into the corresponding
|
||||
* PlayerState enum value.
|
||||
*
|
||||
* @param s The input string representing the player's state, expected to be one of ACTIVE,
|
||||
* FOLDED, or DEALER (case-insensitive).
|
||||
* @return The corresponding PlayerState enum value based on the input string. If the input does
|
||||
* not match any known state, it defaults to PlayerState.ACTIVE.
|
||||
*/
|
||||
private PlayerState parseState(String s) {
|
||||
return switch (s.toUpperCase()) {
|
||||
case "ACTIVE" -> PlayerState.ACTIVE;
|
||||
case "FOLDED" -> PlayerState.FOLDED;
|
||||
case "DEALER" -> PlayerState.DEALER;
|
||||
default -> PlayerState.ACTIVE;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Card;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
@@ -15,24 +27,853 @@ import javafx.scene.layout.VBox;
|
||||
*/
|
||||
public class CasinoGameController {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CasinoGameController.class.getName());
|
||||
|
||||
@FXML private Label tableText;
|
||||
@FXML private VBox casinoTable;
|
||||
@FXML private HBox communityCardsBox;
|
||||
@FXML private VBox casinoTableInnerBox;
|
||||
@FXML private HBox playerCardsBox;
|
||||
@FXML private HBox potBox;
|
||||
@FXML private PlayerStatusController playerStatusController;
|
||||
@FXML private PlayerStatusController player1Controller;
|
||||
@FXML private PlayerStatusController player2Controller;
|
||||
@FXML private PlayerStatusController player3Controller;
|
||||
@FXML private VBox player1;
|
||||
@FXML private VBox player2;
|
||||
@FXML private VBox player3;
|
||||
@FXML private ImageView myDealerIcon;
|
||||
|
||||
private GameService gameService;
|
||||
private PlayerId myPlayerId;
|
||||
private TaskbarController taskbarController;
|
||||
private Image dealerImage;
|
||||
|
||||
private static final int TOTAL_SLOTS = 5;
|
||||
private static final int PLAYER_SLOTS = 2;
|
||||
private int pot = 0;
|
||||
private static final String BACKSIDE = "/images/card-background-3.png";
|
||||
private static final String DEALER_IMAGE_PATH = "/images/chip-dealer-blue-3.png";
|
||||
private static final double TABLE_OFFSET_Y_FACTOR = 0.09;
|
||||
private static final double PLAYER_CARDS_OFFSET_Y_FACTOR = 0.35;
|
||||
private static final double MOVE_FROM_X = -40;
|
||||
private static final double MOVE_FROM_Y = -10;
|
||||
private static final double MOVE_TO_X = 0;
|
||||
private static final double MOVE_TO_Y = 0;
|
||||
private static final double FADE_FROM = 0;
|
||||
private static final double FADE_TO = 1;
|
||||
private static final double SCALE_FROM = 0.95;
|
||||
private static final double SCALE_TO = 1;
|
||||
private static final double SETTLE_FROM_Y = 0;
|
||||
private static final double SETTLE_TO_Y = 10;
|
||||
private static final boolean SETTLE_AUTO_REVERSE = true;
|
||||
private static final int SETTLE_CYCLE_COUNT = 2;
|
||||
private static final long ANIMATION_DURATION_MS = 350;
|
||||
private static final long SETTLE_DURATION_MS = 150;
|
||||
private static final long STAGGER_DELAY_MS = 120;
|
||||
private static final double HOVER_SCALE_ON = 1.08;
|
||||
private static final double HOVER_SCALE_OFF = 1.0;
|
||||
private static final double HOVER_LIFT_ON = -8;
|
||||
private static final double HOVER_LIFT_OFF = 0;
|
||||
private static final long HOVER_DURATION_MS = 180;
|
||||
private static final long UI_UPDATE_INTERVAL_SECONDS = 1;
|
||||
private static final int PLAYER_INDEX_0 = 0;
|
||||
private static final int PLAYER_INDEX_1 = 1;
|
||||
private static final int PLAYER_INDEX_2 = 2;
|
||||
private static final int DEALER_INDEX_PLAYER_1 = 0;
|
||||
private static final int DEALER_INDEX_PLAYER_2 = 1;
|
||||
private static final int DEALER_INDEX_PLAYER_3 = 2;
|
||||
private static final int DEALER_PLAYER_1 = 0;
|
||||
private static final int DEALER_PLAYER_2 = 1;
|
||||
private static final int DEALER_PLAYER_3 = 2;
|
||||
private static final int DEALER_MYSELF = 3;
|
||||
private static final double CARD_START_OPACITY = 0.0;
|
||||
private static final double CARD_START_SCALE = 0.85;
|
||||
private static final double COMMUNITY_CARD_HEIGHT_RATIO = 0.22;
|
||||
private static final double COMMUNITY_CARD_WIDTH_RATIO = 0.11;
|
||||
private static final double PLAYER_CARD_HEIGHT_RATIO = 0.30;
|
||||
private static final double PLAYER_CARD_WIDTH_RATIO = 0.15;
|
||||
private static final double PLAYER_CARDS_Y_OFFSET_FACTOR = 0.10;
|
||||
private static final int[] CHIP_VALUES = {
|
||||
100000, 50000, 20000, 10000,
|
||||
5000, 2000, 1000, 500,
|
||||
200, 100, 50, 20,
|
||||
10, 5, 2, 1
|
||||
};
|
||||
private static final double CHIP_HEIGHT_RATIO = 0.08;
|
||||
private static final double CHIP_WIDTH_RATIO = 0.04;
|
||||
private static final double CHIP_OPACITY_START = 0.0;
|
||||
private static final double CHIP_SCALE_START = 0.6;
|
||||
private static final double CHIP_SCALE_END = 1.0;
|
||||
private static final double CHIP_RANDOM_X_FACTOR = 20.0;
|
||||
private static final double CHIP_RANDOM_X_CENTER = 0.5;
|
||||
private static final double CHIP_RANDOM_Y_BASE = -30.0;
|
||||
private static final double CHIP_RANDOM_Y_VARIATION = 20.0;
|
||||
private static final double CHIP_TRANSLATE_RESET_X = 0.0;
|
||||
private static final double CHIP_TRANSLATE_RESET_Y = 0.0;
|
||||
private static final double CHIP_FADE_TO = 1.0;
|
||||
private static final double CHIP_SCALE_TO = 1.0;
|
||||
private static final long CHIP_FADE_DURATION_MS = 200;
|
||||
private static final long CHIP_SCALE_DURATION_MS = 220;
|
||||
private static final long CHIP_DROP_DURATION_MS = 250;
|
||||
private static final long CHIP_STAGGER_DELAY_MULTIPLIER = 35L;
|
||||
|
||||
/** Standard constructor. Used by FXML. */
|
||||
public CasinoGameController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
@FXML private Label welcomeText;
|
||||
@FXML private VBox casinoTable;
|
||||
/**
|
||||
* Set the GameService for this controller. This method must be called before starting the UI to
|
||||
* ensure that the controller has access to the game logic and can update the interface
|
||||
* accordingly.
|
||||
*
|
||||
* @param gameService The GameService instance that provides access to the game state and logic.
|
||||
*/
|
||||
public void setGameService(GameService gameService) {
|
||||
this.gameService = gameService;
|
||||
}
|
||||
|
||||
// TODO: Test logic: will be replaced by real game interactions,
|
||||
// once the game engine is finished
|
||||
/** Set the PlayerId of the current player. */
|
||||
@FXML
|
||||
public void initialize() {
|
||||
LOGGER.info("INIT UI");
|
||||
|
||||
if (communityCardsBox == null) {
|
||||
LOGGER.warning("communityCardsBox is NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
var url = getClass().getResource(DEALER_IMAGE_PATH);
|
||||
|
||||
if (url != null) {
|
||||
dealerImage = new Image(url.toExternalForm(), true);
|
||||
myDealerIcon.setImage(dealerImage);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.warning("Dealer icon load failed");
|
||||
}
|
||||
|
||||
myDealerIcon.setVisible(false);
|
||||
|
||||
javafx.stage.Screen screen = javafx.stage.Screen.getPrimary();
|
||||
double screenHeight = screen.getBounds().getHeight();
|
||||
casinoTableInnerBox.setTranslateY(-screenHeight * TABLE_OFFSET_Y_FACTOR);
|
||||
playerCardsBox.setTranslateY(screenHeight * PLAYER_CARDS_OFFSET_Y_FACTOR);
|
||||
|
||||
// uiTest();
|
||||
|
||||
// empty display only (optional)
|
||||
renderCommunityCards(List.of());
|
||||
renderPlayerCards(List.of());
|
||||
}
|
||||
|
||||
// Test method to demonstrate the UI functionality with sample data.
|
||||
// @FXML
|
||||
// public void uiTest() {
|
||||
// if (communityCardsBox == null) {
|
||||
// LOGGER.info("communityCardsBox is NULL");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// renderCommunityCards(
|
||||
// List.of(
|
||||
// new Card("ace", "hearts"),
|
||||
// new Card("king", "spades"),
|
||||
// new Card("10", "diamonds")));
|
||||
//
|
||||
// renderPlayerCards(List.of(new Card("10", "spades"), new Card("king", "spades")));
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// Player mathis = new Player(PlayerId.of("Mathis"), 20000);
|
||||
// mathis.setState(PlayerState.ACTIVE);
|
||||
//
|
||||
// Player jona = new Player(PlayerId.of("Jona"), 20000);
|
||||
// jona.setState(PlayerState.FOLDED);
|
||||
//
|
||||
// Player lars = new Player(PlayerId.of("Lars"), 20000);
|
||||
// lars.setState(PlayerState.ACTIVE);
|
||||
//
|
||||
// player1Controller.setPlayer(mathis);
|
||||
// player2Controller.setPlayer(jona);
|
||||
// player3Controller.setPlayer(lars);
|
||||
//
|
||||
// LOGGER.info(
|
||||
// "Player 1: "
|
||||
// + mathis.getName()
|
||||
// + " | $"
|
||||
// + mathis.getChips()
|
||||
// + " | "
|
||||
// + mathis.getState());
|
||||
// LOGGER.info(
|
||||
// "Player 2: "
|
||||
// + jona.getName()
|
||||
// + " | $"
|
||||
// + jona.getChips()
|
||||
// + " | "
|
||||
// + jona.getState());
|
||||
// LOGGER.info(
|
||||
// "Player 3: "
|
||||
// + lars.getName()
|
||||
// + " | $"
|
||||
// + lars.getChips()
|
||||
// + " | "
|
||||
// + lars.getState());
|
||||
//
|
||||
// if (playerStatusController != null) {
|
||||
// playerStatusController.setPlayer(mathis);
|
||||
// } else {
|
||||
// LOGGER.warning("PlayerStatusController is NULL");
|
||||
// }
|
||||
//
|
||||
// javafx.stage.Screen screen = javafx.stage.Screen.getPrimary();
|
||||
// double screenHeight = screen.getBounds().getHeight();
|
||||
//
|
||||
// casinoTableInnerBox.setTranslateY(-screenHeight * 0.08);
|
||||
// playerCardsBox.setTranslateY(screenHeight * 0.35);
|
||||
//
|
||||
// renderPot(500);
|
||||
//
|
||||
// lars.removeChips(500);
|
||||
// LOGGER.info("Lars: $" + lars.getChips());
|
||||
//
|
||||
// player3Controller.refresh();
|
||||
//
|
||||
// mathis.fall();
|
||||
// player1Controller.refresh();
|
||||
//
|
||||
// GameState s = new GameState();
|
||||
// s.dealer = 3;
|
||||
// highlightDealer(s);
|
||||
//
|
||||
// s.phase = "FLOP";
|
||||
//
|
||||
// updateGameInfo(s);
|
||||
//
|
||||
// s.winnerIndex = 1;
|
||||
//
|
||||
// updateGameInfo(s);
|
||||
// }
|
||||
|
||||
/** Start the UI update loop. */
|
||||
public void start() {
|
||||
|
||||
if (gameService == null) {
|
||||
throw new IllegalStateException("GameService not set!");
|
||||
}
|
||||
|
||||
startLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Start a loop that periodically updates the UI by fetching the latest game state from the
|
||||
* GameService.
|
||||
*/
|
||||
@FXML
|
||||
public void onTableClick() {
|
||||
welcomeText.setText("Einsatz akzeptiert!");
|
||||
private void startLoop() {
|
||||
|
||||
javafx.animation.Timeline t =
|
||||
new javafx.animation.Timeline(
|
||||
new javafx.animation.KeyFrame(
|
||||
javafx.util.Duration.seconds(UI_UPDATE_INTERVAL_SECONDS),
|
||||
e -> updateUI()));
|
||||
|
||||
t.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
||||
t.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the UI by fetching the latest game state from the GameService and updating all
|
||||
* relevant components.
|
||||
*/
|
||||
private void updateUI() {
|
||||
|
||||
try {
|
||||
GameState s = gameService.refresh();
|
||||
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderCommunityCards(s.communityCards);
|
||||
renderPot(s.pot);
|
||||
|
||||
updatePlayers(s.players);
|
||||
updatePlayerCards(s);
|
||||
|
||||
updateGameInfo(s);
|
||||
highlightDealer(s);
|
||||
|
||||
updateTaskbar(s);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Error: UI Update failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the player's hole cards based on the active player in the game state.
|
||||
*
|
||||
* @param s The current game state.
|
||||
*/
|
||||
private void updatePlayerCards(GameState s) {
|
||||
|
||||
int active = s.activePlayer;
|
||||
|
||||
if (active >= 0 && active < s.players.size()) {
|
||||
Player p = s.players.get(active);
|
||||
renderPlayerCards(p.getCards());
|
||||
} else {
|
||||
renderPlayerCards(List.of());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the game information display, such as the current phase and the winner if the hand has
|
||||
* ended.
|
||||
*
|
||||
* @param s The current game state.
|
||||
*/
|
||||
private void updateGameInfo(GameState s) {
|
||||
|
||||
String text = "Phase: " + s.phase;
|
||||
|
||||
if (s.winnerIndex >= 0 && s.winnerIndex < s.players.size()) {
|
||||
Player winner = s.players.get(s.winnerIndex);
|
||||
text = "Winner: " + winner.getName();
|
||||
}
|
||||
|
||||
tableText.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the taskbar with the current game state and the player's ID.
|
||||
*
|
||||
* @param s The current game state.
|
||||
*/
|
||||
private void updateTaskbar(GameState s) {
|
||||
|
||||
taskbarController.update(s, myPlayerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the player status components with the latest player information from the game state.
|
||||
*
|
||||
* @param p The list of players in the current game state.
|
||||
*/
|
||||
private void updatePlayers(List<Player> p) {
|
||||
|
||||
if (p == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p.size() > PLAYER_INDEX_0) {
|
||||
player1Controller.setPlayer(p.get(PLAYER_INDEX_0));
|
||||
}
|
||||
|
||||
if (p.size() > PLAYER_INDEX_1) {
|
||||
player2Controller.setPlayer(p.get(PLAYER_INDEX_1));
|
||||
}
|
||||
|
||||
if (p.size() > PLAYER_INDEX_2) {
|
||||
player3Controller.setPlayer(p.get(PLAYER_INDEX_2));
|
||||
}
|
||||
|
||||
player1Controller.refresh();
|
||||
player2Controller.refresh();
|
||||
player3Controller.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the dealer in the UI based on the dealer index in the game state.
|
||||
*
|
||||
* @param s The current game state containing the dealer index.
|
||||
*/
|
||||
private void highlightDealer(GameState s) {
|
||||
|
||||
player1Controller.setDealer(false);
|
||||
player2Controller.setDealer(false);
|
||||
player3Controller.setDealer(false);
|
||||
|
||||
myDealerIcon.setVisible(false);
|
||||
|
||||
int dealer = s.dealer;
|
||||
|
||||
if (dealer == DEALER_PLAYER_1) {
|
||||
player1Controller.setDealer(true);
|
||||
}
|
||||
|
||||
if (dealer == DEALER_PLAYER_2) {
|
||||
player2Controller.setDealer(true);
|
||||
}
|
||||
|
||||
if (dealer == DEALER_PLAYER_3) {
|
||||
player3Controller.setDealer(true);
|
||||
}
|
||||
|
||||
if (dealer == DEALER_MYSELF) {
|
||||
myDealerIcon.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the community cards on the table based on the list of cards provided.
|
||||
*
|
||||
* @param cards The list of community cards to display.
|
||||
*/
|
||||
private void renderCommunityCards(List<Card> cards) {
|
||||
|
||||
communityCardsBox.getChildren().clear();
|
||||
|
||||
if (cards == null) {
|
||||
cards = List.of();
|
||||
}
|
||||
|
||||
for (int i = 0; i < TOTAL_SLOTS; i++) {
|
||||
|
||||
ImageView view = new ImageView();
|
||||
styleCommunityCard(view);
|
||||
|
||||
Image image;
|
||||
|
||||
if (i < cards.size() && cards.get(i) != null) {
|
||||
image = loadImageSafe(mapCardToImage(cards.get(i)));
|
||||
} else {
|
||||
image = loadImageSafe(BACKSIDE);
|
||||
}
|
||||
|
||||
view.setImage(image);
|
||||
|
||||
view.setOnMouseEntered(e -> animateCardHover(view, true));
|
||||
view.setOnMouseExited(e -> animateCardHover(view, false));
|
||||
|
||||
view.setOpacity(CARD_START_OPACITY);
|
||||
|
||||
view.setScaleX(CARD_START_SCALE);
|
||||
view.setScaleY(CARD_START_SCALE);
|
||||
|
||||
communityCardsBox.getChildren().add(view);
|
||||
|
||||
animateCardAppear(view, i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the player's hole cards based on the list of cards provided.
|
||||
*
|
||||
* @param cards The list of hole cards to display for the player.
|
||||
*/
|
||||
private void renderPlayerCards(List<Card> cards) {
|
||||
|
||||
playerCardsBox.getChildren().clear();
|
||||
|
||||
playerCardsBox.getChildren().add(myDealerIcon);
|
||||
|
||||
if (cards == null) {
|
||||
cards = List.of();
|
||||
}
|
||||
|
||||
for (int i = 0; i < PLAYER_SLOTS; i++) {
|
||||
|
||||
ImageView view = new ImageView();
|
||||
stylePlayerCard(view);
|
||||
|
||||
Image image;
|
||||
|
||||
if (i < cards.size() && cards.get(i) != null) {
|
||||
image = loadImageSafe(mapCardToImage(cards.get(i)));
|
||||
} else {
|
||||
image = loadImageSafe(BACKSIDE);
|
||||
}
|
||||
|
||||
view.setImage(image);
|
||||
|
||||
view.setOnMouseEntered(e -> animateCardHover(view, true));
|
||||
view.setOnMouseExited(e -> animateCardHover(view, false));
|
||||
|
||||
view.setOpacity(CARD_START_OPACITY);
|
||||
|
||||
view.setScaleX(CARD_START_SCALE);
|
||||
view.setScaleY(CARD_START_SCALE);
|
||||
|
||||
playerCardsBox.getChildren().add(view);
|
||||
|
||||
animateCardAppear(view, i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an image from the given path safely, falling back to a default backside image if the
|
||||
* specified image cannot be found.
|
||||
*
|
||||
* @param path The path to the image resource to load.
|
||||
* @return The loaded Image object.
|
||||
*/
|
||||
private Image loadImageSafe(String path) {
|
||||
|
||||
var stream = getClass().getResourceAsStream(path);
|
||||
|
||||
if (stream == null) {
|
||||
LOGGER.warning("IMAGE NOT FOUND: " + path);
|
||||
stream = getClass().getResourceAsStream(BACKSIDE);
|
||||
}
|
||||
|
||||
return new Image(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply styling to the given ImageView for community cards, including CSS classes and size
|
||||
* bindings.
|
||||
*
|
||||
* @param view The ImageView to style as a community card.
|
||||
*/
|
||||
private void styleCommunityCard(ImageView view) {
|
||||
|
||||
view.getStyleClass().add("community-card");
|
||||
|
||||
view.setPreserveRatio(true);
|
||||
view.setSmooth(true);
|
||||
|
||||
javafx.scene.Scene scene = communityCardsBox.getScene();
|
||||
|
||||
if (scene != null) {
|
||||
bindCommunityCardSize(view, scene);
|
||||
} else {
|
||||
communityCardsBox
|
||||
.sceneProperty()
|
||||
.addListener(
|
||||
(obs, oldScene, newScene) -> {
|
||||
if (newScene != null) {
|
||||
bindCommunityCardSize(view, newScene);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply styling to the given ImageView for player hole cards, including CSS classes and size
|
||||
* bindings.
|
||||
*
|
||||
* @param view The ImageView to style as a player hole card.
|
||||
*/
|
||||
private void stylePlayerCard(ImageView view) {
|
||||
|
||||
view.getStyleClass().add("player-card");
|
||||
|
||||
view.setPreserveRatio(true);
|
||||
view.setSmooth(true);
|
||||
|
||||
javafx.scene.Scene scene = playerCardsBox.getScene();
|
||||
|
||||
if (scene != null) {
|
||||
bindPlayerCardSize(view, scene);
|
||||
} else {
|
||||
playerCardsBox
|
||||
.sceneProperty()
|
||||
.addListener(
|
||||
(obs, oldScene, newScene) -> {
|
||||
if (newScene != null) {
|
||||
bindPlayerCardSize(view, newScene);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the size of the given ImageView to the scene dimensions for community cards.
|
||||
*
|
||||
* @param view The ImageView representing a community card whose size should be bound to the
|
||||
* scene dimensions.
|
||||
* @param scene The JavaFX Scene to which the ImageView belongs, used for binding the size
|
||||
* properties.
|
||||
*/
|
||||
private void bindCommunityCardSize(ImageView view, javafx.scene.Scene scene) {
|
||||
|
||||
view.fitHeightProperty().bind(scene.heightProperty().multiply(COMMUNITY_CARD_HEIGHT_RATIO));
|
||||
|
||||
view.fitWidthProperty().bind(scene.widthProperty().multiply(COMMUNITY_CARD_WIDTH_RATIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the size of the given ImageView to the scene dimensions for player hole cards.
|
||||
*
|
||||
* @param view The ImageView representing a player hole card whose size should be bound to the
|
||||
* scene dimensions.
|
||||
* @param scene The JavaFX Scene to which the ImageView belongs, used for binding the size
|
||||
* properties.
|
||||
*/
|
||||
private void bindPlayerCardSize(ImageView view, javafx.scene.Scene scene) {
|
||||
|
||||
view.fitHeightProperty().bind(scene.heightProperty().multiply(PLAYER_CARD_HEIGHT_RATIO));
|
||||
|
||||
view.fitWidthProperty().bind(scene.widthProperty().multiply(PLAYER_CARD_WIDTH_RATIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the appearance of a card by applying a sequence of transitions, including movement,
|
||||
* fading, scaling, and settling.
|
||||
*
|
||||
* @param view The ImageView representing the card to animate.
|
||||
* @param index The index of the card in the display order.
|
||||
*/
|
||||
private void animateCardAppear(ImageView view, int index) {
|
||||
|
||||
javafx.animation.TranslateTransition move =
|
||||
new javafx.animation.TranslateTransition(
|
||||
javafx.util.Duration.millis(ANIMATION_DURATION_MS), view);
|
||||
|
||||
move.setFromX(MOVE_FROM_X);
|
||||
move.setToX(MOVE_TO_X);
|
||||
|
||||
move.setFromY(MOVE_FROM_Y);
|
||||
move.setToY(MOVE_TO_Y);
|
||||
|
||||
move.setInterpolator(javafx.animation.Interpolator.EASE_OUT);
|
||||
|
||||
javafx.animation.FadeTransition fade =
|
||||
new javafx.animation.FadeTransition(
|
||||
javafx.util.Duration.millis(ANIMATION_DURATION_MS), view);
|
||||
|
||||
fade.setFromValue(FADE_FROM);
|
||||
fade.setToValue(FADE_TO);
|
||||
|
||||
javafx.animation.ScaleTransition scale =
|
||||
new javafx.animation.ScaleTransition(
|
||||
javafx.util.Duration.millis(ANIMATION_DURATION_MS), view);
|
||||
|
||||
scale.setFromX(SCALE_FROM);
|
||||
scale.setFromY(SCALE_FROM);
|
||||
scale.setToX(SCALE_TO);
|
||||
scale.setToY(SCALE_TO);
|
||||
scale.setInterpolator(javafx.animation.Interpolator.EASE_OUT);
|
||||
|
||||
javafx.animation.TranslateTransition settle =
|
||||
new javafx.animation.TranslateTransition(
|
||||
javafx.util.Duration.millis(SETTLE_DURATION_MS), view);
|
||||
|
||||
settle.setFromY(SETTLE_FROM_Y);
|
||||
settle.setToY(SETTLE_TO_Y);
|
||||
settle.setAutoReverse(SETTLE_AUTO_REVERSE);
|
||||
settle.setCycleCount(SETTLE_CYCLE_COUNT);
|
||||
|
||||
javafx.animation.SequentialTransition st =
|
||||
new javafx.animation.SequentialTransition(
|
||||
new javafx.animation.ParallelTransition(move, fade, scale), settle);
|
||||
|
||||
st.setDelay(javafx.util.Duration.millis(index * STAGGER_DELAY_MS));
|
||||
|
||||
st.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate a card hover effect by scaling and lifting the card when hovered and resetting it
|
||||
* when not hovered.
|
||||
*
|
||||
* @param view The ImageView representing the card to animate on hover.
|
||||
* @param hover A boolean indicating whether the card is being hovered (true) or not (false).
|
||||
*/
|
||||
private void animateCardHover(ImageView view, boolean hover) {
|
||||
|
||||
double scale = hover ? HOVER_SCALE_ON : HOVER_SCALE_OFF;
|
||||
double lift = hover ? HOVER_LIFT_ON : HOVER_LIFT_OFF;
|
||||
|
||||
javafx.animation.ScaleTransition st =
|
||||
new javafx.animation.ScaleTransition(
|
||||
javafx.util.Duration.millis(HOVER_DURATION_MS), view);
|
||||
st.setToX(scale);
|
||||
st.setToY(scale);
|
||||
|
||||
javafx.animation.TranslateTransition tt =
|
||||
new javafx.animation.TranslateTransition(
|
||||
javafx.util.Duration.millis(HOVER_DURATION_MS), view);
|
||||
tt.setToY(lift);
|
||||
|
||||
st.play();
|
||||
tt.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the player's hole cards box on the screen based on a predefined offset factor
|
||||
* relative to the screen height.
|
||||
*/
|
||||
private void positionPlayerCards() {
|
||||
|
||||
javafx.stage.Screen screen = javafx.stage.Screen.getPrimary();
|
||||
double screenHeight = screen.getBounds().getHeight();
|
||||
|
||||
playerCardsBox.setTranslateY(screenHeight * PLAYER_CARDS_Y_OFFSET_FACTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a Card object to the corresponding image path based on its suit and value.
|
||||
*
|
||||
* @param card The Card object to be mapped to an image path.
|
||||
* @return The string path to the image representing the given card.
|
||||
*/
|
||||
private String mapCardToImage(Card card) {
|
||||
|
||||
if (card == null) {
|
||||
return BACKSIDE;
|
||||
}
|
||||
|
||||
String suit =
|
||||
switch (card.getSuit().toLowerCase()) {
|
||||
case "hearts" -> "heart";
|
||||
case "diamonds" -> "diamond";
|
||||
case "clubs", "cross" -> "cross";
|
||||
case "spades", "pik" -> "pik";
|
||||
default -> card.getSuit().toLowerCase();
|
||||
};
|
||||
|
||||
String value =
|
||||
switch (card.getValue().toLowerCase()) {
|
||||
case "a", "ace" -> "ace";
|
||||
case "k", "king" -> "king";
|
||||
case "q", "queen" -> "queen";
|
||||
case "j", "jack" -> "jack";
|
||||
case "10", "t" -> "10";
|
||||
default -> card.getValue().toLowerCase();
|
||||
};
|
||||
|
||||
String path = "/images/card-" + suit + "-" + value + "-3.png";
|
||||
|
||||
// LOGGER.info("CARD PATH: " + path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the pot by calculating the required chips based on the pot amount and displaying them
|
||||
* with animations.
|
||||
*
|
||||
* @param pot The total amount in the pot that needs to be represented with chips on the UI.
|
||||
*/
|
||||
private void renderPot(int pot) {
|
||||
|
||||
potBox.getChildren().clear();
|
||||
|
||||
int remaining = pot;
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (int chipValue : CHIP_VALUES) {
|
||||
|
||||
while (remaining >= chipValue) {
|
||||
|
||||
ImageView chip =
|
||||
new ImageView(loadImageSafe("/images/chip-" + chipValue + "-5.png"));
|
||||
|
||||
if (chip.getImage() == null) {
|
||||
LOGGER.warning("Missing chip image: " + chipValue);
|
||||
break;
|
||||
}
|
||||
|
||||
styleChip(chip);
|
||||
|
||||
potBox.getChildren().add(chip);
|
||||
|
||||
animateChipAppear(chip, index);
|
||||
|
||||
remaining -= chipValue;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// LOGGER.info("POT RENDERED: " + pot + " -> remaining: " + remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply styling to the given ImageView for chips, including CSS classes and size bindings.
|
||||
*
|
||||
* @param view The ImageView to style as a chip in the pot display.
|
||||
*/
|
||||
private void styleChip(ImageView view) {
|
||||
|
||||
view.getStyleClass().add("chips-icon");
|
||||
|
||||
view.setPreserveRatio(true);
|
||||
view.setSmooth(true);
|
||||
|
||||
javafx.scene.Scene scene = potBox.getScene();
|
||||
|
||||
if (scene != null) {
|
||||
bindChipSize(view, scene);
|
||||
} else {
|
||||
potBox.sceneProperty()
|
||||
.addListener(
|
||||
(obs, oldScene, newScene) -> {
|
||||
if (newScene != null) {
|
||||
bindChipSize(view, newScene);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the size of the given ImageView to the scene dimensions for chips in the pot display.
|
||||
*
|
||||
* @param view The ImageView representing a chip whose size should be bound to the scene
|
||||
* dimensions.
|
||||
* @param scene The JavaFX Scene to which the ImageView belongs, used for binding the size
|
||||
* properties.
|
||||
*/
|
||||
private void bindChipSize(ImageView view, javafx.scene.Scene scene) {
|
||||
|
||||
view.fitHeightProperty()
|
||||
.bind(
|
||||
scene.heightProperty().multiply(CHIP_HEIGHT_RATIO) // kleiner als Karten
|
||||
);
|
||||
|
||||
view.fitWidthProperty().bind(scene.widthProperty().multiply(CHIP_WIDTH_RATIO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the appearance of a chip by applying a sequence of transitions, including random
|
||||
* initial positioning, fading, scaling, and dropping into place.
|
||||
*
|
||||
* @param view The ImageView representing the chip to animate.
|
||||
* @param index The index of the chip in the display order, used to stagger the animation timing
|
||||
* for multiple chips.
|
||||
*/
|
||||
private void animateChipAppear(ImageView view, int index) {
|
||||
|
||||
view.setOpacity(CHIP_OPACITY_START);
|
||||
view.setScaleX(CHIP_SCALE_START);
|
||||
view.setScaleY(CHIP_SCALE_START);
|
||||
|
||||
double randomX = (Math.random() - CHIP_RANDOM_X_CENTER) * CHIP_RANDOM_X_FACTOR;
|
||||
double randomY = CHIP_RANDOM_Y_BASE - Math.random() * CHIP_RANDOM_Y_VARIATION;
|
||||
|
||||
view.setTranslateX(randomX);
|
||||
view.setTranslateY(randomY);
|
||||
|
||||
javafx.animation.FadeTransition fade =
|
||||
new javafx.animation.FadeTransition(
|
||||
javafx.util.Duration.millis(CHIP_FADE_DURATION_MS), view);
|
||||
fade.setToValue(CHIP_FADE_TO);
|
||||
|
||||
javafx.animation.ScaleTransition scale =
|
||||
new javafx.animation.ScaleTransition(
|
||||
javafx.util.Duration.millis(CHIP_SCALE_DURATION_MS), view);
|
||||
scale.setToX(CHIP_SCALE_TO);
|
||||
scale.setToY(CHIP_SCALE_TO);
|
||||
|
||||
javafx.animation.TranslateTransition drop =
|
||||
new javafx.animation.TranslateTransition(
|
||||
javafx.util.Duration.millis(CHIP_DROP_DURATION_MS), view);
|
||||
drop.setToX(CHIP_TRANSLATE_RESET_X);
|
||||
drop.setToY(CHIP_TRANSLATE_RESET_Y);
|
||||
|
||||
javafx.animation.SequentialTransition st =
|
||||
new javafx.animation.SequentialTransition(fade, scale, drop);
|
||||
|
||||
st.setDelay(javafx.util.Duration.millis(index * CHIP_STAGGER_DELAY_MULTIPLIER));
|
||||
|
||||
st.play();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,30 @@ import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* Main class for the Casono Game UI.
|
||||
*
|
||||
* <p>Starts the JavaFX application, loads the graphical user interface from the FXML file, and
|
||||
* initializes the main stage for the game.
|
||||
*
|
||||
* <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 {
|
||||
|
||||
// Static field for ClientService (workaround for JavaFX Application launch)
|
||||
private static ClientService staticClientService;
|
||||
private static ClientService clientService;
|
||||
|
||||
public static void setClientService(ClientService clientService) {
|
||||
staticClientService = clientService;
|
||||
}
|
||||
private static final int DEFAULT_WIDTH = 1200;
|
||||
private static final int DEFAULT_HEIGHT = 800;
|
||||
|
||||
public static ClientService getClientService() {
|
||||
return staticClientService;
|
||||
}
|
||||
|
||||
/** Default no-arg constructor. */
|
||||
/** default constructor */
|
||||
public CasinoGameUI() {
|
||||
// default no-arg constructor
|
||||
}
|
||||
|
||||
private static final int DEFAULT_WIDTH = 1200;
|
||||
private static final int DEFAULT_HEIGHT = 800;
|
||||
public static void setClientService(ClientService clientService) {
|
||||
CasinoGameUI.clientService = clientService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the main stage of the application.
|
||||
@@ -39,7 +43,7 @@ public class CasinoGameUI extends Application {
|
||||
FXMLLoader fxmlLoader =
|
||||
new FXMLLoader(CasinoGameUI.class.getResource("/ui-structure/Casinogameui.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||
stage.setTitle("Casono (GAME)");
|
||||
stage.setTitle("Casono");
|
||||
|
||||
String iconPath = getClass().getResource("/images/logoinverted.png").toExternalForm();
|
||||
stage.getIcons().add(new javafx.scene.image.Image(iconPath));
|
||||
@@ -49,7 +53,7 @@ public class CasinoGameUI extends Application {
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point of the application.
|
||||
* Starting point of the application.
|
||||
*
|
||||
* @param args Command line arguments.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
|
||||
/**
|
||||
* Controller for displaying a player's status in the poker game, including their name, chip count
|
||||
* and whether they are the dealer.
|
||||
*/
|
||||
public class PlayerStatusController {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PlayerStatusController.class.getName());
|
||||
|
||||
@FXML private Label playerName;
|
||||
@FXML private Label playerMoney;
|
||||
@FXML private ImageView dealerIcon;
|
||||
@FXML private Pane parent;
|
||||
|
||||
private Image dealerImage;
|
||||
private Player player;
|
||||
private static final String DEALER_IMAGE_PATH = "/images/chip-dealer-blue-3.png";
|
||||
private static final double DEALER_ICON_X_FACTOR = 0.8;
|
||||
|
||||
/** Initialize the controller, load dealer image, and set up bindings. */
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
try {
|
||||
var url = getClass().getResource(DEALER_IMAGE_PATH);
|
||||
|
||||
if (url != null) {
|
||||
dealerImage = new Image(url.toExternalForm(), true);
|
||||
dealerIcon.setImage(dealerImage);
|
||||
} else {
|
||||
LOGGER.warning("Dealer image not found in resources!");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Error: loading dealer image:");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
dealerIcon.layoutXProperty().bind(parent.widthProperty().multiply(DEALER_ICON_X_FACTOR));
|
||||
dealerIcon.setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind player to UI
|
||||
*
|
||||
* @param player The player whose status is to be displayed.
|
||||
*/
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/** Refresh UI safely */
|
||||
public void refresh() {
|
||||
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NAME (safe)
|
||||
String name = player.getName();
|
||||
playerName.setText(name != null ? name : "-");
|
||||
|
||||
// MONEY
|
||||
playerMoney.setText(player.getChips() + " $");
|
||||
|
||||
// DEALER
|
||||
boolean isDealer = player.getState() == PlayerState.DEALER;
|
||||
|
||||
dealerIcon.setVisible(isDealer);
|
||||
|
||||
if (isDealer && dealerImage != null) {
|
||||
dealerIcon.setImage(dealerImage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* External quick update
|
||||
*
|
||||
* @param name The player's name to display.
|
||||
* @param chips The player's chip count to display.
|
||||
*/
|
||||
public void updatePlayer(String name, int chips) {
|
||||
playerName.setText(name != null ? name : "-");
|
||||
playerMoney.setText(chips + " $");
|
||||
}
|
||||
|
||||
/**
|
||||
* Force dealer state
|
||||
*
|
||||
* @param isDealer Whether the player is the dealer or not.
|
||||
*/
|
||||
public void setDealer(boolean isDealer) {
|
||||
dealerIcon.setVisible(isDealer);
|
||||
|
||||
if (isDealer && dealerImage != null) {
|
||||
dealerIcon.setImage(dealerImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.Player;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerState;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
@@ -18,22 +25,225 @@ import org.apache.logging.log4j.Logger;
|
||||
*/
|
||||
public class TaskbarController {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
|
||||
@FXML private HBox taskbar;
|
||||
@FXML private TextField taskbarInput;
|
||||
@FXML private Button betButton;
|
||||
@FXML private Button callButton;
|
||||
@FXML private Button foldButton;
|
||||
@FXML private Button raiseButton;
|
||||
@FXML private Label moneyLabel;
|
||||
|
||||
private GameService gameService;
|
||||
private PlayerId myPlayerId;
|
||||
private double xOffset = 0;
|
||||
private double yOffset = 0;
|
||||
private static final double TASKBAR_SCALE = 0.95;
|
||||
private static final int MIN_CREDITS = 5;
|
||||
private static final int MAX_CREDITS = 100000;
|
||||
private static final int CREDIT_STEP = 5;
|
||||
private static final String STYLE_YELLOW_BUTTON = "yellow-button";
|
||||
private static final String STYLE_GRAY_BUTTON = "gray-button";
|
||||
private static final String STYLE_RED_BUTTON = "red-button";
|
||||
private static final String STYLE_YELLOW_INPUT = "yellow-input-field";
|
||||
private static final String STYLE_GRAY_INPUT = "gray-input-field";
|
||||
private static final String STYLE_RED_INPUT = "red-input-field";
|
||||
private static final double SCALE_NORMAL = 1.0;
|
||||
|
||||
/** Standard constructor. Used by FXML. */
|
||||
public TaskbarController() {
|
||||
// default constructor for FXML
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CasinoBrowserController.class);
|
||||
/**
|
||||
* Updates the state of the basic action buttons (Bet, Call, Fold, Raise) and the input field
|
||||
*
|
||||
* @param isMyTurn Indicates whether it is currently the player's turn.
|
||||
* @param isOut Indicates whether the player is currently out of the game (folded or all-in).
|
||||
*/
|
||||
private void updateBasicButtons(boolean isMyTurn, boolean isOut) {
|
||||
|
||||
@FXML private HBox taskbar;
|
||||
@FXML private TextField taskbarInput;
|
||||
if (isOut) {
|
||||
setRedButton(betButton);
|
||||
setRedButton(callButton);
|
||||
setRedButton(foldButton);
|
||||
setRedButton(raiseButton);
|
||||
setRedInputField(taskbarInput);
|
||||
return;
|
||||
}
|
||||
|
||||
private double xOffset = 0;
|
||||
private double yOffset = 0;
|
||||
private static final double TASKBAR_SCALE = 0.9;
|
||||
private static final int MIN_CREDITS = 5;
|
||||
private static final int MAX_CREDITS = 100000;
|
||||
private static final int CREDIT_STEP = 5;
|
||||
if (isMyTurn) {
|
||||
activateButton(betButton);
|
||||
activateButton(callButton);
|
||||
activateButton(foldButton);
|
||||
activateButton(raiseButton);
|
||||
activateInputField(taskbarInput);
|
||||
} else {
|
||||
deactivateButton(betButton);
|
||||
deactivateButton(callButton);
|
||||
deactivateButton(foldButton);
|
||||
deactivateButton(raiseButton);
|
||||
deactivateInputField(taskbarInput);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given button to a disabled state with red styling, indicating that the player is out
|
||||
*
|
||||
* @param b The button to be styled as red and disabled
|
||||
*/
|
||||
private void setRedButton(Button b) {
|
||||
b.setDisable(true);
|
||||
|
||||
b.getStyleClass().remove(STYLE_YELLOW_BUTTON);
|
||||
b.getStyleClass().remove(STYLE_GRAY_BUTTON);
|
||||
|
||||
if (!b.getStyleClass().contains(STYLE_RED_BUTTON)) {
|
||||
b.getStyleClass().add(STYLE_RED_BUTTON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given input field to a disabled state with red styling, indicating that the player
|
||||
* is out
|
||||
*
|
||||
* @param t The text field to be styled as red and disabled
|
||||
*/
|
||||
private void setRedInputField(TextField t) {
|
||||
t.setDisable(true);
|
||||
|
||||
t.getStyleClass().remove(STYLE_YELLOW_INPUT);
|
||||
t.getStyleClass().remove(STYLE_GRAY_INPUT);
|
||||
|
||||
if (!t.getStyleClass().contains(STYLE_RED_INPUT)) {
|
||||
t.getStyleClass().add(STYLE_RED_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the given button by enabling it and applying yellow styling, indicating that it is
|
||||
* the player's turn
|
||||
*
|
||||
* @param b The button to be activated and styled for the player's turn
|
||||
*/
|
||||
private void activateButton(Button b) {
|
||||
b.setDisable(false);
|
||||
|
||||
b.getStyleClass().remove(STYLE_GRAY_BUTTON);
|
||||
|
||||
if (!b.getStyleClass().contains(STYLE_YELLOW_BUTTON)) {
|
||||
b.getStyleClass().add(STYLE_YELLOW_BUTTON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the given input field by enabling it and applying yellow styling, indicating that
|
||||
* it is the player's turn
|
||||
*
|
||||
* @param t The text field to be activated and styled for the player's turn
|
||||
*/
|
||||
private void activateInputField(TextField t) {
|
||||
t.setDisable(false);
|
||||
|
||||
t.getStyleClass().remove(STYLE_GRAY_INPUT);
|
||||
|
||||
if (!t.getStyleClass().contains(STYLE_YELLOW_INPUT)) {
|
||||
t.getStyleClass().add(STYLE_YELLOW_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the given button by disabling it and applying gray styling, indicating that it is
|
||||
* not the player's turn
|
||||
*
|
||||
* @param b The button to be deactivated and styled for non-active state
|
||||
*/
|
||||
private void deactivateButton(Button b) {
|
||||
b.setDisable(true);
|
||||
|
||||
b.getStyleClass().remove(STYLE_YELLOW_BUTTON);
|
||||
b.getStyleClass().remove(STYLE_RED_BUTTON);
|
||||
|
||||
if (!b.getStyleClass().contains(STYLE_GRAY_BUTTON)) {
|
||||
b.getStyleClass().add(STYLE_GRAY_BUTTON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the given input field by disabling it and applying gray styling, indicating that
|
||||
* it is not the player's turn
|
||||
*
|
||||
* @param t The text field to be deactivated and styled for non-active state
|
||||
*/
|
||||
private void deactivateInputField(TextField t) {
|
||||
t.setDisable(true);
|
||||
|
||||
t.getStyleClass().remove(STYLE_YELLOW_INPUT);
|
||||
t.getStyleClass().remove(STYLE_RED_INPUT);
|
||||
|
||||
if (!t.getStyleClass().contains(STYLE_GRAY_INPUT)) {
|
||||
t.getStyleClass().add(STYLE_GRAY_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the displayed amount of money the player has. The amount is shown in the format "X$".
|
||||
*
|
||||
* @param amount The amount of money to display for the player.
|
||||
*/
|
||||
public void setMoney(int amount) {
|
||||
moneyLabel.setText(amount + "$");
|
||||
}
|
||||
|
||||
public void setGameService(GameService gameService, PlayerId myPlayerId) {
|
||||
this.gameService = gameService;
|
||||
this.myPlayerId = myPlayerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the taskbar controller. This method is called automatically after the FXML
|
||||
* components have been loaded.
|
||||
*/
|
||||
@FXML
|
||||
public void initialize() {
|
||||
updateBasicButtons(false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the taskbar based on the current game state and the player's status. It checks if
|
||||
* it's the player's turn and whether they are out of the game (folded or all-in) to adjust the
|
||||
* button states and displayed money accordingly.
|
||||
*
|
||||
* @param state
|
||||
* @param myPlayerId
|
||||
*/
|
||||
public void update(GameState state, PlayerId myPlayerId) {
|
||||
|
||||
if (state == null || state.players == null || myPlayerId == null) {
|
||||
LOGGER.warn("Cannot update taskbar: invalid input");
|
||||
return;
|
||||
}
|
||||
|
||||
Player me =
|
||||
state.players.stream()
|
||||
.filter(p -> myPlayerId.equals(p.getId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (me == null) {
|
||||
LOGGER.error("Player not found in GameState!");
|
||||
return;
|
||||
}
|
||||
|
||||
int myIndex = state.players.indexOf(me);
|
||||
|
||||
boolean isMyTurn = state.activePlayer == myIndex;
|
||||
boolean isOut = me.getState() == PlayerState.FOLDED;
|
||||
|
||||
updateBasicButtons(isMyTurn, isOut);
|
||||
setMoney(me.getChips());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the taskbar is clicked with the mouse. Saves the relative position for later,
|
||||
@@ -72,8 +282,8 @@ public class TaskbarController {
|
||||
*/
|
||||
@FXML
|
||||
private void onTaskbarReleased(MouseEvent event) {
|
||||
taskbar.setScaleX(1.0);
|
||||
taskbar.setScaleY(1.0);
|
||||
taskbar.setScaleX(SCALE_NORMAL);
|
||||
taskbar.setScaleY(SCALE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +307,76 @@ public class TaskbarController {
|
||||
processBet();
|
||||
}
|
||||
|
||||
/** Called when the Call button is clicked. */
|
||||
@FXML
|
||||
private void onInputPlayerCall() {
|
||||
|
||||
if (gameService == null) {
|
||||
LOGGER.error("GameService not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
gameService.call();
|
||||
LOGGER.info("Player CALL");
|
||||
|
||||
refreshGame();
|
||||
}
|
||||
|
||||
/** Called when the Fold button is clicked. */
|
||||
@FXML
|
||||
private void onInputPlayerFold() {
|
||||
|
||||
if (gameService == null) {
|
||||
LOGGER.error("GameService not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
gameService.fold();
|
||||
LOGGER.info("Player FOLD");
|
||||
|
||||
refreshGame();
|
||||
}
|
||||
|
||||
/** Called when the Raise button is clicked. */
|
||||
@FXML
|
||||
private void onInputPlayerRaise() {
|
||||
|
||||
String input = taskbarInput.getText();
|
||||
|
||||
try {
|
||||
int amount = Integer.parseInt(input.trim());
|
||||
|
||||
gameService.raise(amount);
|
||||
|
||||
LOGGER.info("Player RAISE {}", amount);
|
||||
|
||||
taskbarInput.clear();
|
||||
|
||||
refreshGame();
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
LOGGER.error("Invalid raise amount");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the game state by fetching the latest state from the GameService and updating the
|
||||
* taskbar accordingly.
|
||||
*/
|
||||
private void refreshGame() {
|
||||
|
||||
try {
|
||||
GameState newState = gameService.refresh();
|
||||
update(newState, myPlayerId);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to refresh game state: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Exit button is clicked. Closes the current game stage and opens the lobby UI.
|
||||
*/
|
||||
@FXML
|
||||
private void onExitButtonClick() {
|
||||
javafx.application.Platform.runLater(
|
||||
@@ -109,7 +389,7 @@ public class TaskbarController {
|
||||
try {
|
||||
new Casinomainui().start(new javafx.stage.Stage());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Starten der Lobby-UI: {}", e.getMessage());
|
||||
LOGGER.error("Error: starting the lobby UI: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -120,19 +400,34 @@ public class TaskbarController {
|
||||
* displayed on the console.
|
||||
*/
|
||||
private void processBet() {
|
||||
|
||||
if (gameService == null) {
|
||||
LOGGER.error("Error: GameService not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
String input = taskbarInput.getText();
|
||||
|
||||
try {
|
||||
|
||||
int credits = Integer.parseInt(input.trim());
|
||||
|
||||
if (credits >= MIN_CREDITS && credits <= MAX_CREDITS && credits % CREDIT_STEP == 0) {
|
||||
// TODO: Credits must be sent to the GameEngine
|
||||
LOGGER.info("Bet set: {} Casono Credits", credits);
|
||||
|
||||
gameService.bet(credits);
|
||||
|
||||
LOGGER.info("Bet placed: {}", credits);
|
||||
|
||||
taskbarInput.clear();
|
||||
|
||||
refreshGame();
|
||||
|
||||
} else {
|
||||
LOGGER.error("Error: Only increments of 5 (5, 10, ... 100,000) are allowed!");
|
||||
LOGGER.error("Error: Bet must be between 5 and 100000 and multiple of 5");
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
LOGGER.error("Error: Please enter only one number!");
|
||||
LOGGER.error("Error: Invalid number entered");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 645 KiB |
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 461 KiB |
|
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 659 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 657 KiB After Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 659 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 663 KiB After Width: | Height: | Size: 482 KiB |
|
Before Width: | Height: | Size: 662 KiB After Width: | Height: | Size: 477 KiB |
|
Before Width: | Height: | Size: 663 KiB After Width: | Height: | Size: 472 KiB |
|
Before Width: | Height: | Size: 657 KiB After Width: | Height: | Size: 463 KiB |
|
Before Width: | Height: | Size: 664 KiB After Width: | Height: | Size: 477 KiB |
|
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 481 KiB |
|
Before Width: | Height: | Size: 648 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 655 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 477 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 481 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 478 KiB |
|
Before Width: | Height: | Size: 659 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 480 KiB |
|
Before Width: | Height: | Size: 644 KiB After Width: | Height: | Size: 455 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 479 KiB |
|
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 468 KiB |
|
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 468 KiB |
|
Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 464 KiB |
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 464 KiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 655 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 465 KiB |
|
Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 657 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 647 KiB After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 473 KiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 473 KiB |
|
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 472 KiB |
|
Before Width: | Height: | Size: 657 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 480 KiB |
|
Before Width: | Height: | Size: 659 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 461 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 475 KiB |
|
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 479 KiB |
|
Before Width: | Height: | Size: 719 KiB After Width: | Height: | Size: 675 KiB |
|
Before Width: | Height: | Size: 736 KiB After Width: | Height: | Size: 708 KiB |
|
Before Width: | Height: | Size: 744 KiB After Width: | Height: | Size: 721 KiB |
|
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 754 KiB After Width: | Height: | Size: 733 KiB |
|
Before Width: | Height: | Size: 755 KiB After Width: | Height: | Size: 732 KiB |
|
Before Width: | Height: | Size: 732 KiB After Width: | Height: | Size: 697 KiB |
|
Before Width: | Height: | Size: 745 KiB After Width: | Height: | Size: 721 KiB |
|
Before Width: | Height: | Size: 752 KiB After Width: | Height: | Size: 731 KiB |
|
Before Width: | Height: | Size: 757 KiB After Width: | Height: | Size: 737 KiB |
|
Before Width: | Height: | Size: 761 KiB After Width: | Height: | Size: 740 KiB |
|
Before Width: | Height: | Size: 732 KiB After Width: | Height: | Size: 697 KiB |
|
Before Width: | Height: | Size: 725 KiB After Width: | Height: | Size: 686 KiB |
|
Before Width: | Height: | Size: 731 KiB After Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 745 KiB After Width: | Height: | Size: 719 KiB |
|
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 757 KiB After Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 760 KiB After Width: | Height: | Size: 739 KiB |
|
Before Width: | Height: | Size: 733 KiB After Width: | Height: | Size: 702 KiB |
|
Before Width: | Height: | Size: 727 KiB After Width: | Height: | Size: 684 KiB |
|
Before Width: | Height: | Size: 737 KiB After Width: | Height: | Size: 708 KiB |
|
After Width: | Height: | Size: 751 KiB |
|
After Width: | Height: | Size: 760 KiB |
|
Before Width: | Height: | Size: 902 KiB After Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 701 KiB After Width: | Height: | Size: 701 KiB |
|
Before Width: | Height: | Size: 693 KiB After Width: | Height: | Size: 693 KiB |
|
Before Width: | Height: | Size: 694 KiB After Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 921 KiB After Width: | Height: | Size: 921 KiB |