Aufdecken: Ansage bleibt stehen, ueberzaehlige Wuerfel kommen gruen dazu
Bisher wurde die Ansage in der Tischmitte beim Aufdecken komplett umgefaerbt: gruen was da war, rot was fehlte. Damit sah eine getroffene Ansage genauso bunt aus wie eine daneben liegende. Jetzt bleibt die Ansage so stehen, wie sie gemacht wurde. Fehlende Wuerfel werden rot, die Anzahl aendert sich nicht. Gab es mehr als angesagt, haengen die ueberzaehligen gruen hinten dran. Ausserdem: bei einem frueheren Umbau des README sind die Abschnitte zu Bildaufteilung, Physik, Regeln und Boostern verloren gegangen. Sie sind wieder da. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
101
README.md
101
README.md
@@ -67,6 +67,107 @@ im `Dockerfile`), importiert das Projekt einmal vor und startet den Server.
|
||||
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. **Boost wählen** – jeder bekommt 3 zufällige Booster angeboten und wählt
|
||||
einen; er wirkt nur für die laufende Runde.
|
||||
3. **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.
|
||||
4. **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 Boosterwahl und Ansage hat man jeweils **30 Sekunden**; die Restzeit steht
|
||||
im Booster-Fenster beziehungsweise neben „Deine Ansage" und wird unter zehn
|
||||
Sekunden rot. Läuft die Zeit ab, entscheidet der Host automatisch – beim Booster
|
||||
zufällig, beim Gebot 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.
|
||||
|
||||
## Booster
|
||||
|
||||
| Booster | 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 |
|
||||
| Zwilling | Buff | Ein anderer eigener Würfel nimmt den Wert des gewählten an |
|
||||
| Spähen | Buff | Von jedem Gegner wird ein zufälliger Würfel aufgedeckt |
|
||||
| Rütteln | Debuff | Jeder Gegner wirft einen zufälligen Würfel neu |
|
||||
| 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 | Der nächste Spieler muss bieten und darf nicht anfechten |
|
||||
|
||||
## Aufbau
|
||||
|
||||
| Datei | Inhalt |
|
||||
|
||||
@@ -13,7 +13,7 @@ const DIM := Color(0.62, 0.64, 0.70)
|
||||
## Wette werden so gross gewählt, dass sie dort hineinpassen.
|
||||
const BID_BOX := Vector2(200.0, 150.0)
|
||||
const TIMER_W := 52.0 ## Breite der Zeitanzeige in der Ansage-Leiste
|
||||
const GRUEN := Color(0.55, 0.84, 0.55) ## vorhandener Würfel beim Aufdecken
|
||||
const GRUEN := Color(0.55, 0.84, 0.55) ## überzähliger Würfel beim Aufdecken
|
||||
const ROT := Color(0.94, 0.55, 0.52) ## fehlender Würfel beim Aufdecken
|
||||
|
||||
var root: Control
|
||||
@@ -466,14 +466,20 @@ func update_state(st: Dictionary, my_seat: int) -> void:
|
||||
else:
|
||||
bid_name.text = String(st.seats[st.bid.seat].name)
|
||||
bid_name.add_theme_color_override("font_color", ACCENT)
|
||||
var px := _fit_dice(int(st.bid.count))
|
||||
# Beim Aufdecken zeigen die Farben, wie viele der angesagten Würfel es
|
||||
# wirklich gab: grün die vorhandenen, rot die fehlenden.
|
||||
# Beim Aufdecken bleibt die Ansage stehen, wie sie war: die angesagten
|
||||
# Würfel normal, die fehlenden davon rot. Gab es mehr als angesagt,
|
||||
# kommen die überzähligen grün dahinter.
|
||||
var faerben: bool = st.phase == "reveal"
|
||||
for i in int(st.bid.count):
|
||||
var angesagt := int(st.bid.count)
|
||||
var gezeigt := maxi(angesagt, _actual) if faerben else angesagt
|
||||
var px := _fit_dice(gezeigt)
|
||||
for i in gezeigt:
|
||||
var bg := Color.TRANSPARENT
|
||||
if faerben:
|
||||
bg = GRUEN if i < _actual else ROT
|
||||
if i >= angesagt:
|
||||
bg = GRUEN
|
||||
elif i >= _actual:
|
||||
bg = ROT
|
||||
bid_row.add_child(DieIcon.new(int(st.bid.face), px, bg))
|
||||
|
||||
_clear(boosts_box)
|
||||
|
||||
Reference in New Issue
Block a user