games: no mercy on the felt, and the bill that went to the wrong window
The engine has been able to play No Mercy since aca523e. Now a browser can.
The switch is a switch, not a fourth table: the tier is still the table size,
because that is what you are paid for, and the deck is the other dial. Six faces
the normal box does not print, sized by the card's own vars and never by the box
they sit in. The stack says what the bill is on the felt, in the turn line and on
the button, and under it the deck is dead — you cannot draw your way out of a
bill somebody has run up and pointed at you.
The wild draws glow. That started as decoration and turned out to be doing work:
No Mercy prints a coloured +4 right beside the wild one, and in a hand of twenty
the glow is what tells them apart.
A buried seat is not an empty one, which is the whole trap here — a seat killed
at twenty-five holds no cards, and neither does a seat that just went out and
won. The view asks the engine which it is instead of counting to zero, so the
winner is never the corpse.
Two bugs, both found in a browser and neither findable anywhere else:
The felt's stack bill was writing into the chip bar. It was [data-pending], and
so is the bar's "your chips are still coming" readout — and the bar lives inside
the table's own root and comes first in the document. A stack quietly overwrote
the escrow message and never appeared on the felt at all. A table's attributes
are not a private namespace.
And hold'em, re-driven on the 20M-hand policy (six hands, got up 61 ahead of a
100 buy-in, money conserved to the chip — Phase 4 closed), let you click a button
that did nothing: Deal, Leave and Top up stayed alive through the whole deal
animation, where send() drops the click on purpose. The lock is on the buttons
now, not only in the variable.
Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -49,6 +49,7 @@ type unoSeatView struct {
|
||||
Cards int `json:"cards"`
|
||||
You bool `json:"you"`
|
||||
Uno bool `json:"uno"` // down to one card
|
||||
Out bool `json:"out"` // No Mercy: buried at twenty-five, and not coming back
|
||||
}
|
||||
|
||||
// unoView is a game as its player may see it.
|
||||
@@ -65,6 +66,11 @@ type unoView struct {
|
||||
Turn int `json:"turn"`
|
||||
Dir int `json:"dir"`
|
||||
|
||||
// No Mercy: the bill a stack of draw cards has run up. Whoever stops stacking
|
||||
// pays it, and while it stands it is the only thing on the table that matters —
|
||||
// so the felt has to be able to say what it is.
|
||||
Pending int `json:"pending"`
|
||||
|
||||
Bet int64 `json:"bet"`
|
||||
Pays int64 `json:"pays"` // what going out right now would actually pay
|
||||
Phase string `json:"phase"`
|
||||
@@ -83,6 +89,7 @@ func viewUno(g uno.State) unoView {
|
||||
Deck: g.Left(),
|
||||
Turn: g.Turn,
|
||||
Dir: g.Dir,
|
||||
Pending: g.Pending,
|
||||
Bet: g.Bet,
|
||||
Pays: g.Pays(),
|
||||
Phase: string(g.Phase),
|
||||
@@ -92,18 +99,27 @@ func viewUno(g uno.State) unoView {
|
||||
Rake: g.Rake,
|
||||
Net: g.Net(),
|
||||
}
|
||||
// An empty hand is a seat that went out — *unless* the mercy rule took it, in
|
||||
// which case an empty hand is a grave. Those two look identical from the count
|
||||
// alone, which is why a buried seat is asked about rather than inferred.
|
||||
for i, n := range g.Counts() {
|
||||
seat := unoSeatView{Cards: n, You: i == uno.You, Uno: n == 1}
|
||||
live := g.Live(i)
|
||||
seat := unoSeatView{Cards: n, You: i == uno.You, Uno: live && n == 1, Out: !live}
|
||||
if i == uno.You {
|
||||
seat.Name = "You"
|
||||
} else if i-1 < len(g.Bots) {
|
||||
seat.Name = g.Bots[i-1]
|
||||
}
|
||||
v.Seats = append(v.Seats, seat)
|
||||
if n == 0 {
|
||||
if live && n == 0 {
|
||||
v.Winner = i
|
||||
}
|
||||
}
|
||||
// And you can win a No Mercy table without ever going out: outlive it, and the
|
||||
// last seat standing is you, with a hand still in it.
|
||||
if v.Winner < 0 && g.Outcome.Won() {
|
||||
v.Winner = uno.You
|
||||
}
|
||||
for _, c := range g.Hands[uno.You] {
|
||||
v.Hand = append(v.Hand, viewUnoCard(c))
|
||||
}
|
||||
@@ -248,6 +264,10 @@ func (s *Server) handleUnoMove(w http.ResponseWriter, r *http.Request) {
|
||||
msg = "play the card you drew, or pass"
|
||||
case errors.Is(err, uno.ErrCantPass):
|
||||
msg = "draw first, then you can pass"
|
||||
case errors.Is(err, uno.ErrMustStack):
|
||||
msg = "answer the stack with a draw card, or take it"
|
||||
case errors.Is(err, uno.ErrNoStack):
|
||||
msg = "there's nothing pointed at you to take"
|
||||
}
|
||||
writeJSONStatus(w, http.StatusBadRequest, map[string]string{"error": msg})
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user