games: the poker table others can walk up to, and the one that empties when they leave
Phase C's handler cutover: hold'em now runs on the shared-table runtime instead of the solo game_live_hands blob. Solo is just a table nobody else has joined. - holdem implements tableGame (name/timeout/stacks). timeout auto-checks-or-folds a walked-away seat and marks it away; the audit is per-hand, with each pot's rake on the winner's row alone so HouseTake cannot 4x itself. - New endpoints: sit opens a table (or joins an open bot seat), leave gets you up (LeaveTable + CloseTable behind the last human), plus tables (lobby), stream (SSE), chat and say. The move path loads the player's table, applies at their seat, commits under the version guard, and fans an SSE nudge. - Engine grows Vacate/Occupy (a human leaving/joining between hands) and TableSeats (a named human + bots). The view carries your_seat, since a shared table has no seat-zero-is-you convention. - Storage grows OpenSoloTable (stake+claim+create+seat in one tx), PlayerSeat, and the abandoned-table reaper (AbandonedTables/ReapTable) — the seated-player counterpart to the session reaper, since a walked-away stack is inside a blob the session reaper cannot see. upsertSeat preserves last_seen so an auto-fold never refreshes an away player's clock. Not deployed, and the felt is not rewired yet: the frontend still assumes seat zero is you, so this is browser-unverified. Solo sit/deal/play/leave and two-human join/leave/reaper are covered by tests; the whole suite is green. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -297,11 +297,30 @@ func (s *Server) table(user string) (tableView, error) {
|
||||
uv := viewUno(g)
|
||||
v.Uno = &uv
|
||||
case gameHoldem:
|
||||
// A seated hold'em player's cards are in game_tables, not here — this row is
|
||||
// only their occupancy claim, so its state is empty. Load the table and render
|
||||
// it as their own seat sees it.
|
||||
if live.TableID == "" {
|
||||
return s.dropUnreadable(user, v, fmt.Errorf("holdem row with no table"))
|
||||
}
|
||||
t, _, err := storage.LoadTable(live.TableID)
|
||||
if errors.Is(err, storage.ErrNoSuchTable) {
|
||||
// The table closed under them (reaped, or the last hand cashed them out).
|
||||
// Their claim is stale; clear it so they can sit down again.
|
||||
return s.dropUnreadable(user, v, fmt.Errorf("holdem table %s gone", live.TableID))
|
||||
}
|
||||
if err != nil {
|
||||
return tableView{}, err
|
||||
}
|
||||
_, seat, err := storage.PlayerSeat(user)
|
||||
if err != nil {
|
||||
return tableView{}, err
|
||||
}
|
||||
var g holdem.State
|
||||
if err := json.Unmarshal(live.State, &g); err != nil {
|
||||
if err := json.Unmarshal(t.State, &g); err != nil {
|
||||
return s.dropUnreadable(user, v, err)
|
||||
}
|
||||
hv := viewHoldem(g, soloViewer)
|
||||
hv := viewHoldem(g, seat)
|
||||
v.Holdem = &hv
|
||||
default:
|
||||
return s.dropUnreadable(user, v, fmt.Errorf("unknown game %q", live.Game))
|
||||
|
||||
Reference in New Issue
Block a user