179 lines
6.1 KiB
Go
179 lines
6.1 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"pete/internal/games/blackjack"
|
|
"pete/internal/games/hangman"
|
|
"pete/internal/games/klondike"
|
|
"pete/internal/games/trivia"
|
|
"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 the house bots know how to play."},
|
|
{Name: "UNO", Emoji: "🎴", Blurb: "Normal rules, or no mercy."},
|
|
}
|
|
|
|
// betDenominations are the chips you build a bet out of.
|
|
var betDenominations = []int64{5, 25, 100, 500}
|
|
|
|
// The casino is not called Pete — the news app is Pete's, and this is somewhere
|
|
// you go. It has two names, and which one is over the door depends on the hour:
|
|
// the lights come on at six and the place turns into Casino Night Zone until
|
|
// dawn. Same tables, different room.
|
|
type room struct {
|
|
Slug string // drives the palette: html[data-room="…"]
|
|
Name string // what's on the sign
|
|
}
|
|
|
|
var (
|
|
roomDay = room{Slug: "casinopolis", Name: "Casinopolis"}
|
|
roomNight = room{Slug: "casino-night", Name: "Casino Night Zone"}
|
|
)
|
|
|
|
// roomAt picks the room for an hour of the day. Daylight is 6am to 6pm; the rest
|
|
// belongs to the neon. The browser re-runs this same rule against its own clock
|
|
// (games_layout.html), so a player in another timezone sees their own evening —
|
|
// this server-side pick only exists so the first paint isn't the wrong room.
|
|
func roomAt(hour int) room {
|
|
if hour >= 6 && hour < 18 {
|
|
return roomDay
|
|
}
|
|
return roomNight
|
|
}
|
|
|
|
// gamesPage is deliberately *not* pageData. The casino shares Pete's design
|
|
// language and nothing else — no channels, no weather, no sources, no push.
|
|
// Giving it its own page struct is what stops the news app's furniture drifting
|
|
// back in one convenient field at a time.
|
|
type gamesPage struct {
|
|
Room room
|
|
User *SessionUser
|
|
Cap int64
|
|
RakePct int
|
|
Soon []gameTeaser
|
|
Denominations []int64
|
|
Tiers []hangman.Tier // hangman's three lengths, and what each pays
|
|
MaxWrong int
|
|
Deals []klondike.Tier // solitaire's three deals
|
|
FullDeck int
|
|
Quizzes []trivia.Tier // trivia's three difficulties
|
|
Rungs int // how long the trivia ladder is
|
|
}
|
|
|
|
// casinoRoutes hangs every table off the mux.
|
|
//
|
|
// It exists so there is exactly one list of them. The dev rig (devcasino_test.go)
|
|
// has to wire its own mux — New() decides whether the casino exists before the
|
|
// rig has signed anybody in — and a second copy of this list is a list that
|
|
// silently stops including the newest game.
|
|
func (s *Server) casinoRoutes(mux *http.ServeMux) {
|
|
mux.HandleFunc("GET /games", s.handleLobby)
|
|
mux.HandleFunc("GET /games/blackjack", s.handleBlackjack)
|
|
mux.HandleFunc("GET /games/hangman", s.handleHangman)
|
|
mux.HandleFunc("GET /games/solitaire", s.handleSolitaire)
|
|
mux.HandleFunc("GET /games/trivia", s.handleTrivia)
|
|
|
|
mux.HandleFunc("GET /api/games/table", s.handleTable)
|
|
mux.HandleFunc("POST /api/games/buyin", s.handleBuyIn)
|
|
mux.HandleFunc("POST /api/games/cashout", s.handleCashOut)
|
|
|
|
mux.HandleFunc("POST /api/games/blackjack/deal", s.handleDeal)
|
|
mux.HandleFunc("POST /api/games/blackjack/move", s.handleMove)
|
|
|
|
mux.HandleFunc("POST /api/games/hangman/start", s.handleHangmanStart)
|
|
mux.HandleFunc("POST /api/games/hangman/guess", s.handleHangmanGuess)
|
|
|
|
mux.HandleFunc("POST /api/games/solitaire/start", s.handleSolitaireStart)
|
|
mux.HandleFunc("POST /api/games/solitaire/move", s.handleSolitaireMove)
|
|
|
|
mux.HandleFunc("POST /api/games/trivia/start", s.handleTriviaStart)
|
|
mux.HandleFunc("POST /api/games/trivia/answer", s.handleTriviaAnswer)
|
|
}
|
|
|
|
// 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 {
|
|
return gamesPage{
|
|
Room: roomAt(time.Now().Hour()),
|
|
User: s.auth.userFromRequest(r), // requirePlayer ran first, so this is non-nil
|
|
Cap: storage.MaxChipsOnTable,
|
|
RakePct: int(blackjack.DefaultRules().RakePct * 100),
|
|
Soon: comingSoon,
|
|
Denominations: betDenominations,
|
|
Tiers: hangman.Tiers,
|
|
MaxWrong: hangman.MaxWrong,
|
|
Deals: klondike.Tiers,
|
|
FullDeck: klondike.FullDeck,
|
|
Quizzes: trivia.Tiers,
|
|
Rungs: trivia.Rungs,
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
func (s *Server) handleHangman(w http.ResponseWriter, r *http.Request) {
|
|
if !s.requirePlayer(w, r) {
|
|
return
|
|
}
|
|
s.render(w, "hangman", s.gamesPage(r))
|
|
}
|
|
|
|
func (s *Server) handleSolitaire(w http.ResponseWriter, r *http.Request) {
|
|
if !s.requirePlayer(w, r) {
|
|
return
|
|
}
|
|
s.render(w, "solitaire", s.gamesPage(r))
|
|
}
|
|
|
|
func (s *Server) handleTrivia(w http.ResponseWriter, r *http.Request) {
|
|
if !s.requirePlayer(w, r) {
|
|
return
|
|
}
|
|
s.render(w, "trivia", s.gamesPage(r))
|
|
}
|