games: a blackjack table you can actually sit down at
The engine, the escrow and the wire were all in place; nothing had a browser on the end of it. This is that end: a lobby, a table, and the five endpoints between them. The browser holds no game. It sends intents and gets back a view — the cards it is entitled to see, and the script of how they arrived, one event per card off the shoe. The dealer's hole card is not in the payload at all until the reveal, because a field the client is told to ignore is a field somebody reads in devtools. The shoe lives in game_live_hands, which also means a redeploy mid-hand no longer costs a player their stake: the hand is still there when they come back. The money is ordered so nothing can be spent twice. The stake leaves the stack in the same statement that checks it exists, before a card is dealt. Every new hand is seated with a plain INSERT, so a double-clicked Deal is decided by the primary key rather than by a read that raced — it loses, gets its chips back, and the hand in progress is untouched. A double takes its raise up front and hands it straight back if the engine refuses the move. Cards are dealt rather than swapped in — they fly out of the shoe and turn over, which was a requirement and not a flourish. The faces and the chips are still plain; that's next.
This commit is contained in:
84
internal/web/games_pages.go
Normal file
84
internal/web/games_pages.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"pete/internal/games/blackjack"
|
||||
"pete/internal/storage"
|
||||
)
|
||||
|
||||
// The casino's two pages. Both require a signed-in visitor — there is money in
|
||||
// here, and a player has to be somebody gogobee's ledger can name.
|
||||
//
|
||||
// Neither page renders any game state server-side. The felt is drawn by the
|
||||
// browser from /api/games/table, because a hand is a thing that *happens*: cards
|
||||
// are dealt one at a time and the table plays that back. A server-rendered hand
|
||||
// would arrive fully formed, which is the one thing a card table must never do.
|
||||
|
||||
// gameTeaser is a table that isn't open yet. They're on the lobby because an
|
||||
// empty casino with one game reads as broken, and this reads as early.
|
||||
type gameTeaser struct {
|
||||
Name string
|
||||
Emoji string
|
||||
Blurb string
|
||||
}
|
||||
|
||||
var comingSoon = []gameTeaser{
|
||||
{Name: "Hold'em", Emoji: "♠️", Blurb: "Six seats, and Pete's bots know how to play."},
|
||||
{Name: "UNO", Emoji: "🎴", Blurb: "Normal rules, or no mercy."},
|
||||
{Name: "Trivia", Emoji: "🧠", Blurb: "Faster answers score higher."},
|
||||
{Name: "Hangman", Emoji: "🪢", Blurb: "Guess the phrase before Pete finishes drawing."},
|
||||
}
|
||||
|
||||
// betDenominations are the chips you build a bet out of.
|
||||
var betDenominations = []int64{5, 25, 100, 500}
|
||||
|
||||
type gamesPage struct {
|
||||
pageData
|
||||
Cap int64
|
||||
RakePct int
|
||||
Soon []gameTeaser
|
||||
Denominations []int64
|
||||
}
|
||||
|
||||
// requirePlayer sends an anonymous visitor to sign in and comes back here after.
|
||||
// Anyone who is signed in but carries a session from before the casino existed
|
||||
// has no username in it, so they get sent through sign-in too — which mints one.
|
||||
func (s *Server) requirePlayer(w http.ResponseWriter, r *http.Request) bool {
|
||||
if !s.gamesReady() {
|
||||
http.NotFound(w, r)
|
||||
return false
|
||||
}
|
||||
u := s.auth.userFromRequest(r)
|
||||
if u != nil && u.MatrixUser(s.cfg.Games.MatrixServer) != "" {
|
||||
return true
|
||||
}
|
||||
http.Redirect(w, r, "/auth/login?next="+r.URL.Path, http.StatusFound)
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Server) gamesPage(r *http.Request) gamesPage {
|
||||
base := s.base(r)
|
||||
base.NoIndex = true // the casino is for players, not for search engines
|
||||
return gamesPage{
|
||||
pageData: base,
|
||||
Cap: storage.MaxChipsOnTable,
|
||||
RakePct: int(blackjack.DefaultRules().RakePct * 100),
|
||||
Soon: comingSoon,
|
||||
Denominations: betDenominations,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleLobby(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requirePlayer(w, r) {
|
||||
return
|
||||
}
|
||||
s.render(w, "games", s.gamesPage(r))
|
||||
}
|
||||
|
||||
func (s *Server) handleBlackjack(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requirePlayer(w, r) {
|
||||
return
|
||||
}
|
||||
s.render(w, "blackjack", s.gamesPage(r))
|
||||
}
|
||||
Reference in New Issue
Block a user