games: the poker table opens, and the bots go back to school
Phase 4. Hold'em, and it's the only table in the casino that is a session rather than a game: you buy in, play as many hands as you like, and leave with what's in front of you. So the live row spans hands and chips cross the border exactly twice. Everything in between is inside the engine. The bots move inside ApplyMove, as UNO's do, which is what keeps poker off a socket: shove all-in and the flop, turn, river, showdown and payout all come back in one response, as a script the felt plays back. The CFR policy the plan called "the single highest-value asset in either repo" was never read. Not once, in the whole life of the game: the trainer wrote its info-set keys under IP/OOP and the runtime looked them up under BTN/SB/BB, so every lookup missed and fell silently through to a pot-odds heuristic. Nothing looked broken, because a policy miss is not an error. And it was the wrong policy anyway — ten big blinds deep, trained on a tree where a call always ends the street, which is not poker. So the trainer is rewritten to play the real engine through the real reducer, at every stack depth the table deals, and the trainer and the table now build the key with the same function so they cannot drift apart again. A test fails if the bots stop finding themselves in it. Three money bugs, and the tests earned their keep. Chip conservation across a hundred sessions caught an uncalled bet that minted chips. A var-init ordering trap meant every card was identical, every showdown tied and every bot believed it held exactly 50% equity. And the browser caught the rake being silently zero — the tier said 5 meaning percent, the casino handed it 0.05 meaning a fraction, and integer division took the house's cut down to nothing. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"pete/internal/games/blackjack"
|
||||
"pete/internal/games/hangman"
|
||||
"pete/internal/games/holdem"
|
||||
"pete/internal/games/klondike"
|
||||
"pete/internal/games/trivia"
|
||||
"pete/internal/games/uno"
|
||||
@@ -28,9 +29,10 @@ type gameTeaser struct {
|
||||
Blurb string
|
||||
}
|
||||
|
||||
var comingSoon = []gameTeaser{
|
||||
{Name: "Hold'em", Emoji: "♠️", Blurb: "Six seats, and the house bots know how to play."},
|
||||
}
|
||||
// comingSoon is empty, and that is the point: every game the plan named is now on
|
||||
// the felt. Leave it here — the lobby renders nothing for an empty list, and the
|
||||
// next game to be dreamed up goes in it.
|
||||
var comingSoon = []gameTeaser{}
|
||||
|
||||
// betDenominations are the chips you build a bet out of.
|
||||
var betDenominations = []int64{5, 25, 100, 500}
|
||||
@@ -78,6 +80,8 @@ type gamesPage struct {
|
||||
Quizzes []trivia.Tier // trivia's three difficulties
|
||||
Rungs int // how long the trivia ladder is
|
||||
Tables []uno.Tier // uno's three tables, and how many bots sit at each
|
||||
Stakes []holdem.Tier // hold'em's three tables, by blinds
|
||||
MaxBots int // how many seats hold'em will fill with bots
|
||||
}
|
||||
|
||||
// casinoRoutes hangs every table off the mux.
|
||||
@@ -93,6 +97,7 @@ func (s *Server) casinoRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /games/solitaire", s.handleSolitaire)
|
||||
mux.HandleFunc("GET /games/trivia", s.handleTrivia)
|
||||
mux.HandleFunc("GET /games/uno", s.handleUno)
|
||||
mux.HandleFunc("GET /games/holdem", s.handleHoldem)
|
||||
|
||||
mux.HandleFunc("GET /api/games/table", s.handleTable)
|
||||
mux.HandleFunc("POST /api/games/buyin", s.handleBuyIn)
|
||||
@@ -112,6 +117,9 @@ func (s *Server) casinoRoutes(mux *http.ServeMux) {
|
||||
|
||||
mux.HandleFunc("POST /api/games/uno/start", s.handleUnoStart)
|
||||
mux.HandleFunc("POST /api/games/uno/move", s.handleUnoMove)
|
||||
|
||||
mux.HandleFunc("POST /api/games/holdem/sit", s.handleHoldemSit)
|
||||
mux.HandleFunc("POST /api/games/holdem/move", s.handleHoldemMove)
|
||||
}
|
||||
|
||||
// requirePlayer sends an anonymous visitor to sign in and comes back here after.
|
||||
@@ -145,6 +153,8 @@ func (s *Server) gamesPage(r *http.Request) gamesPage {
|
||||
Quizzes: trivia.Tiers,
|
||||
Rungs: trivia.Rungs,
|
||||
Tables: uno.Tiers,
|
||||
Stakes: holdem.Tiers,
|
||||
MaxBots: holdem.MaxBots,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,3 +199,10 @@ func (s *Server) handleUno(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
s.render(w, "uno", s.gamesPage(r))
|
||||
}
|
||||
|
||||
func (s *Server) handleHoldem(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requirePlayer(w, r) {
|
||||
return
|
||||
}
|
||||
s.render(w, "holdem", s.gamesPage(r))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user