team3 3f4532ad59 Booster-Phase raus, Karten auf die Hand
Die Boosterwahl kostete jede Runde eine eigene Phase mit 30 Sekunden Frist.
Zwischen Wurf und Ansage lag damit eine Wartezeit, die den Ablauf zerriss.

Jetzt gibt es Karten statt einer Phase:

* Wer einen Wuerfel abgibt, zieht eine Karte. Das ist der einzige Nachschub,
  also bekommt der etwas zurueck, der gerade hinten liegt.
* Gespielt wird jederzeit waehrend der Ansagen, nicht nur am eigenen Zug.
* Hoechstens drei Karten auf der Hand.
* Die Hand liegt offen unten links, ein Klick spielt. Nur Feinschliff und
  Gluecksgriff fragen noch nach dem Wuerfel.

Der Katalog ist angepasst, weil jede Karte jetzt sofort wirken muss:
neu sind Erhoehen (Ansage +1), Widerruf (hebt Nebel, Stoerfeuer und Maulkorb
auf dir auf) und Nachschub (ein Wuerfel dazu). Zwilling und Ruetteln sind
raus - beide brauchten den festen Zeitpunkt vor dem Wurf.

Bots spielen ihre Karten am eigenen Zug, bei voller Hand immer.

Zwei Karten gleichzeitig wuerden sich beim Wuerfeln in die Quere kommen,
deshalb wird immer nur eine aufgeloest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 16:11:07 +02:00
2026-09-05 23:19:46 +02:00
2026-09-05 23:19:46 +02:00
2026-09-07 18:07:53 +02:00
2026-09-05 23:19:46 +02:00
2026-09-07 18:07:53 +02:00
2026-09-07 18:39:02 +02:00
2026-09-05 23:19:46 +02:00

Liar's Dice 3D

Würfel-Bluffspiel in Godot 4.7 mit echter 3D-Physik, Mehrspieler über Raum-Codes, KI-Bots und einem Booster-System aus Buffs und Debuffs.

Starten

godot --path .          # oder das Projekt im Godot-Editor öffnen

Das Startmenü hat drei Punkte:

  • Einzelspieler – nur die Bot-Anzahl (1–7) einstellen und starten. Es wird kein Netzwerk geöffnet.
  • Mehrspieler – nur den Namen eintragen, dann Hosten oder Beitreten. Hosten führt direkt in die Lobby; dort steht der Code. Beitreten fragt zuerst nach dem Code. Niemand braucht eine IP oder eine Portfreigabe.
  • Lobby – zeigt den Code, wer schon da ist und die Bot-Anzahl. Nur der Host stellt die Bots ein und startet, sobald mindestens 2 Teilnehmer zusammen sind (Menschen und Bots zusammengezählt, maximal 8).
  • Beenden – schließt das Spiel.

Mehrspieler: wie es zusammenhängt

Die Partie läuft nicht beim Host, sondern auf dice.marha.de:

Spieler ──wss://443──> ein Serverprozess ──> Raum MQG4 (SubViewport + World3D)
                                        └──> Raum 4YPV (SubViewport + World3D)
  • Ein Prozess, ein Port. Der Server trägt alle Räume gleichzeitig und spricht WebSocket. Traefik reicht das auf 443 durch, es muss kein Port freigegeben werden.
  • Ein Raum ist ein Knoten unter /root/Main/Raeume/<CODE>. Darin ein SubViewport mit own_world_3d – nur so hat jede Partie ihren eigenen Physikraum, sonst lägen alle Würfel im selben.
  • Die Knotennamen sind auf Server und Client gleich. Daran hängt das Ziel jedes RPC, deshalb dürfen sie nicht auseinanderlaufen.
  • Broadcasts (rpc) würden alle Räume treffen. Deshalb geht alles gezielt per rpc_id an die Peers des eigenen Raums.
  • Der Server sitzt nicht mit am Tisch: local_seat bleibt −1, kein HUD.
  • Der Host ist der erste Spieler im Raum. Geht er, rückt der nächste nach. Nur er stellt die Bots ein und startet.
  • Der letzte macht das Licht aus: verlässt der letzte Spieler den Raum, wird er samt Code sofort abgeräumt.

Gemessen mit sechs gleichzeitigen Partien zu acht Plätzen: 3,7 MB je Raum, im Leerlauf unter 1 % eines Kerns, kurze Spitzen beim Würfelwurf.

Lokal entwickeln

make server         # Spielserver auf :8000, trägt alle Räume
make dev            # Spiel, zeigt auf den lokalen Server

Ohne PIRATE_DICE_SERVER zeigt das Spiel auf wss://dice.marha.de.

Auf dem Server

git pull
make prod           # baut und startet, holt Godot selbst
make logs
make down

Mehr ist nicht nötig. Der Docker-Build lädt Godot 4.7.2 herunter (Version steht im Dockerfile), importiert das Projekt einmal vor und startet den Server. restart: unless-stopped bringt ihn nach einem Neustart von selbst wieder hoch.

Es muss kein Port freigegeben werden: alles läuft als WebSocket über Traefik auf 443.

Das Spiel startet im Vollbild, F11 schaltet um. Esc öffnet ein Menü mit Fortsetzen und Verlassen; im Einzelspieler hält es das Spiel samt Zeitlimit wirklich an, im Mehrspieler läuft die Runde weiter – sonst würde ein geöffnetes Menü alle anderen am Tisch blockieren.

Die Bildaufteilung: Tisch mittig, darauf die letzte Wette als so viele Würfel wie angesagt; aktive Booster oben links, Würfel aller Spieler oben rechts (Name mit den Würfeln darunter) – beide Panels nur so breit wie ihr Inhalt –, und die eigene Ansage als schmale Leiste unten in der Mitte.

Augenzahlen werden überall als gezeichnete Würfel dargestellt (DieIcon in hud.gd), unbekannte Werte als Würfel mit Fragezeichen. Beim Aufdecken bleibt die Wette in der Tischmitte stehen, wie sie angesagt war: fehlende Würfel werden rot. Gab es mehr als angesagt, kommen die überzähligen grün dahinter. Am Tisch trägt jeder Becher ein Schild mit Namen und der letzten Ansage dieses Spielers; Unterlage und Würfelbilder dafür werden beim Start einmal als Textur gezeichnet. Im Panel oben rechts stehen deshalb deine eigenen Würfel offen, gegnerische verdeckt – bis auf die per Spähen aufgedeckten und beim Aufdecken am Rundenende.

Jeder Platz hat eine runde Würfelzone mit umlaufender Bande. Die runde Form ist nicht nur Optik: in den Ecken der früheren rechteckigen Zone verkanteten sich Würfel, im Kreis liegt kein Wurf mehr schief (Messung: 2,0 % schiefe Würfel und 1,3 % Nachhelfer vorher, 0,4 % und 0 % nachher). Beim Aufdecken legen sich die Würfel als Bogen auf einen Kreis um die Zonenmitte, damit sie sich nicht überdecken und das Namensschild frei bleibt.

Der Tisch wächst mit der Spielerzahl, damit die Würfelzonen auch bei acht Plätzen nicht aneinanderstoßen. Die Kamera schaut mit 15° Neigung fast senkrecht von oben darauf; der eigene Platz liegt unten im Bild. _frame_camera in table.gd rechnet den Abstand so aus, dass der Tisch genau das freie Band zwischen den HUD-Feldern ausfüllt (HUD_TOP/HUD_BOTTOM) – bei jeder Spielerzahl und jedem Fensterformat, auch nach dem Ändern der Fenstergröße.

Physik und Bildrate

Zwei Engine-Vorgaben sind bewusst gesetzt, beide in project.godot:

  • 3d/physics_engine="Jolt Physics" – GodotPhysics3D hat Kontakt-Toleranzen, die auf meterhohe Objekte ausgelegt sind (contact_max_separation 0.05 bei 0.075 Würfel-Halbkante). Würfel dieser Größe sanken dadurch in die Tischplatte ein und rollten endlos weiter, sodass jeder Wurf in den Settle-Timeout lief. Mit Jolt liegt die Wurfphase bei rund 0,9 s statt 7 s.
  • renderer/rendering_method="mobile" – auf integrierten GPUs kostet Forward+ hier mehr als das Doppelte: 22 statt 47 fps bei 30 Würfeln.

Ein liegender Würfel wird mit FREEZE_MODE_STATIC eingefroren, nicht kinematisch. Ein kinematischer Körper meldet seine Lage weiter an den Knoten zurück: setzt ein Booster wie Glücksgriff die Augenzahl über snap_to_value(), überschreibt der Physikserver die neue Lage im nächsten Schritt wieder mit der alten, und der Würfel zeigt den falschen Wert. Die.place() zieht die Lage zusätzlich sofort im Physikserver nach.

Ein Würfel ist ein einziges Renderobjekt: Körper und Augen stecken in einem gemeinsamen Mesh mit Vertexfarben, statt 21 Augen als eigene Meshes zu hängen.

Weil Kamera und Deckenlicht beide fast senkrecht stehen, liegt der Glanzpunkt genau auf der Würfeloberseite. Mit glänzendem Material und starkem Spot klippte die weiße Fläche bei 255 und riss die schwarzen Augen mit hoch – gemessen lag kein einziger Punkt-Pixel unter Luminanz 150. Deshalb ist das Würfelmaterial matt (roughness 0.9, metallic_specular 0.0) und der Spot schwächer zugunsten von mehr Umgebungslicht; der Kontrast Auge zu Körper liegt damit bei rund 11:1.

Regeln

Klassisches Liar's Dice ohne Joker. Jeder startet mit 5 Würfeln und sieht nur die eigenen.

  1. Würfeln – alle Würfel fallen physikalisch in den eigenen Becher.
  2. Ansagen – reihum wird ein Gebot "Anzahl x Augenzahl" über alle Würfel am Tisch gemacht. Jedes Gebot muss höher sein: mehr Würfel, oder gleich viele bei höherer Augenzahl.
  3. Anfechten – statt zu erhöhen kann man das Vorgebot anzweifeln. Alle Becher werden gehoben und gezählt: Stimmt das Gebot, verliert der Anfechter einen Würfel, sonst der Bieter.

Für die Ansage hat man 30 Sekunden; die Restzeit steht neben „Deine Ansage" und wird unter zehn Sekunden rot. Läuft die Zeit ab, entscheidet der Host mit der Bot-Logik. Ohne dieses Limit würde ein abwesender oder getrennter Mitspieler den ganzen Tisch dauerhaft blockieren.

Wer keine Würfel mehr hat, scheidet aus. Der Letzte gewinnt.

Karten

Karten ersetzen die frühere Booster-Phase. Die kostete jede Runde eine eigene Wartezeit und riss den Ablauf auseinander.

  • Wer einen Würfel abgibt, zieht eine Karte. Das ist der einzige Nachschub, also bekommt der etwas zurück, der gerade hinten liegt.
  • Gespielt wird jederzeit während der Ansagen, nicht nur am eigenen Zug.
  • Mehr als drei Karten passen nicht auf die Hand.
  • Die Hand liegt offen unten links; ein Klick spielt die Karte sofort. Nur Feinschliff und Glücksgriff fragen noch, welcher Würfel gemeint ist.
Karte Art Wirkung
Neuwurf Buff Alle eigenen Würfel neu werfen
Feinschliff Buff Einen gewählten eigenen Würfel neu werfen
Glücksgriff Buff Ein gewählter Würfel wird zur 6
Nachschub Buff Du bekommst einen Würfel dazu
Widerruf Buff Hebt Nebel, Störfeuer und Maulkorb auf, die auf dir liegen
Spähen Buff Von jedem Gegner wird ein zufälliger Würfel aufgedeckt
Erhöhen Debuff Die laufende Ansage steigt um 1
Absenken Debuff Bei jedem Gegner sinkt ein zufälliger Würfel um 1
Nebel Debuff Jeder Gegner sieht einen eigenen Würfel nicht mehr
Störfeuer Debuff Gegner müssen diese Runde um mindestens 2 erhöhen
Maulkorb Debuff Wer gerade dran ist, muss bieten und darf nicht anfechten

Aufbau

Datei Inhalt
scripts/net.gd Autoload: WebSocket-Verbindung, Räume, Lobby, Spielstart
scripts/boosters.gd Autoload: Kartenkatalog
scripts/game.gd Spiellogik (host-autoritativ), RPCs, Zustandsabgleich
scripts/table.gd Aufbau der 3D-Szene: Tisch, Würfelzonen, Becher, Kamera
scripts/die.gd Physik-Würfel, Ablesen der Augenzahl aus der Ruhelage
scripts/ai.gd Bot-Entscheidungen (Binomialabschätzung für Gebot/Anfechtung)
scripts/hud.gd Spiel-UI
scripts/menu.gd Startmenü und Lobby

Die gesamte Spiellogik und die Würfelphysik laufen beim Host. Clients erhalten nur ihre eigenen Würfel als Transform-Stream, gegnerische Werte erst beim Aufdecken – Mitlesen fremder Würfel ist damit nicht möglich.

Description
No description provided
Readme 1.5 MiB
Languages
GDScript 97.2%
Shell 1.6%
Makefile 0.7%
Dockerfile 0.5%