games: the card the dealer never turned over, and the bet that came back doubled
Bust every hand and the dealer doesn't draw, which is right, but it was also not turning over: reveal is only emitted by dealerPlay, and busting out skips the dealer entirely. The browser kept the hole card face down while the settled state printed the dealer's whole total under it. Emit the reveal on that path. And standing your bet back up after a reload read the hand's bet straight off the settled state, which a double has already doubled. Reload, double 200, and the next hand starts with 400 on the spot. Plus: the share card was hand-writing a Content-Length that ServeContent overwrites anyway, and serving a zero-byte 200 for a room with no card. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"sync"
|
||||
@@ -85,20 +86,25 @@ func (s *Server) handleGamesOG(w http.ResponseWriter, r *http.Request) {
|
||||
// No requirePlayer here, and that is the whole point: the thing fetching this
|
||||
// is a chat server's link preview, which has never signed in and never will.
|
||||
ogOnce.Do(func() { ogCards, ogErr = drawShareCards() })
|
||||
if ogErr != nil {
|
||||
room := roomAt(time.Now().Hour())
|
||||
card := ogCards[room.Slug]
|
||||
if ogErr != nil || len(card) == 0 {
|
||||
// A room with no card is a room somebody added to roomAt and not to
|
||||
// ogPalettes. Say so rather than serving a zero-byte PNG, which an unfurler
|
||||
// will happily cache as the casino's face.
|
||||
slog.Error("games: no share card", "room", room.Slug, "err", ogErr)
|
||||
http.Error(w, "share card unavailable", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
room := roomAt(time.Now().Hour())
|
||||
png := ogCards[room.Slug]
|
||||
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
// Long enough that a busy channel isn't redrawing it, short enough that the
|
||||
// room actually turns over: the lights come on at six and the card follows
|
||||
// within the hour.
|
||||
w.Header().Set("Cache-Control", "public, max-age=900")
|
||||
w.Header().Set("Content-Length", fmt.Sprint(len(png)))
|
||||
http.ServeContent(w, r, "og.png", time.Time{}, bytes.NewReader(png))
|
||||
// ServeContent sets Content-Length itself, and gets it right for a range
|
||||
// request, which a hand-written one would not.
|
||||
http.ServeContent(w, r, "og.png", time.Time{}, bytes.NewReader(card))
|
||||
}
|
||||
|
||||
// drawShareCards renders every room's card up front. If the font is broken every
|
||||
@@ -128,16 +134,16 @@ func drawShareCard(f *opentype.Font, rm room, p ogPalette) (image.Image, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer title.Close()
|
||||
line, err := face(f, 33)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer line.Close()
|
||||
small, err := face(f, 25)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer title.Close()
|
||||
defer line.Close()
|
||||
defer small.Close()
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, ogWidth, ogHeight))
|
||||
|
||||
@@ -173,10 +173,12 @@ func (s *Server) casinoURL(r *http.Request, route string) string {
|
||||
|
||||
func (s *Server) gamesPage(r *http.Request) gamesPage {
|
||||
return gamesPage{
|
||||
Room: roomAt(time.Now().Hour()),
|
||||
URL: s.casinoURL(r, r.URL.Path),
|
||||
OGImage: s.casinoURL(r, "/games/og.png"),
|
||||
User: s.auth.userFromRequest(r), // requirePlayer ran first, so this is non-nil
|
||||
Room: roomAt(time.Now().Hour()),
|
||||
URL: s.casinoURL(r, r.URL.Path),
|
||||
OGImage: s.casinoURL(r, "/games/og.png"),
|
||||
// Nil on the front door, and only there — every other page ran requirePlayer
|
||||
// first. A template that reads .User has to guard it.
|
||||
User: s.auth.userFromRequest(r),
|
||||
Cap: storage.MaxChipsOnTable,
|
||||
RakePct: int(blackjack.DefaultRules().RakePct * 100),
|
||||
Soon: comingSoon,
|
||||
|
||||
@@ -436,7 +436,7 @@
|
||||
// has to refuse.
|
||||
return settleChips(final)
|
||||
.then(money)
|
||||
.then(function () { return standing(base || (final.hands[0] && final.hands[0].bet)); })
|
||||
.then(function () { return standing(base || unitBet(final.hands[0])); })
|
||||
.then(function () { setPhase(final); });
|
||||
});
|
||||
}
|
||||
@@ -450,6 +450,16 @@
|
||||
// It's one hand's worth, not the whole deal's: a split cost you four hundred,
|
||||
// and standing four hundred back up on one spot would be betting a stranger's
|
||||
// money on your behalf.
|
||||
// unitBet is what one hand of a deal actually cost, read back off a settled
|
||||
// hand. It exists for the reload: `base` only knows what you pressed Deal with,
|
||||
// and if you reloaded mid-hand nobody pressed Deal. A doubled hand carries twice
|
||||
// the stake it was dealt with, so standing its bet back up would quietly put you
|
||||
// on double the number you thought you were playing.
|
||||
function unitBet(h) {
|
||||
if (!h) return 0;
|
||||
return h.doubled ? h.bet / 2 : h.bet;
|
||||
}
|
||||
|
||||
function standing(amount) {
|
||||
var money = window.PeteGames.view();
|
||||
if (!amount || !money || money.chips < amount) {
|
||||
|
||||
Reference in New Issue
Block a user