games: the hand that becomes two, and the bet that has to follow it

Blackjack has a split. It was the last rule missing from a game that has been
live for a week, and it is the only move in blackjack that takes chips out of
your stack *after* the cards are out — which is most of what there is to get
wrong about it.

So the state stops pretending. State.Player is gone; there is a slice of Hands,
each with its own cards, its own bet, its own outcome and its own payout, and an
Active index the player works left to right. Settle runs per hand and rakes per
hand: netting them against each other first would mean a player who won one and
lost one paid no rake at all, which is not a rake, it's a discount for
splitting. The web layer takes the second bet before the move and hands it
straight back if the engine refuses — the same shape double already used, except
double was staking st.Bet, the whole table's stake, which was the same number as
the hand's until today and is now emphatically not. DoubleCost/SplitCost are the
active hand's, and the felt would have found this by charging you 300 to double
the third hand of a split.

The rules that cost money if you guess them: split aces get one card each and no
say (a pair of aces is otherwise the best hand in the game, forever), 21 on a
split hand is twenty-one and not a natural (it does not pay 3:2 — the test that
pins this is the most expensive one in the file), same rank rather than same
value (a king and a queen are not a pair), four hands maximum, double after
split allowed, and if every hand busts the dealer does not turn over.

A live hand outlives a deploy, so State.UnmarshalJSON still reads the old blobs:
"player" with no "hands" becomes one hand holding the whole stake. Without it, a
player mid-hand at restart is a player whose cards vanished — which is not a
decode error, and would not have looked like one.

On the felt a hand is now a box with its own spot, and a split is a card lifting
out of one hand into a new one with a second stack of chips flying after it from
your pile. Verified in a browser against a real pair: chips 4738 -> 4638 on the
split, two hands played out, one push and one loss, "Down on the deal. -100",
4738 back. Three hands stack without collision at 390px. Settled hands come back
to full brightness — dimming means "not your turn", and when the deal is over
they are the thing you are reading.

Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
prosolis
2026-07-14 13:54:55 -07:00
parent 7ca1f7a030
commit 6f34a89622
9 changed files with 1116 additions and 225 deletions

View File

@@ -767,6 +767,55 @@ html[data-phase="night"] {
}
.pete-spot[data-live="1"] .pete-spot-label { opacity: 0; }
/* The player's hands at blackjack. There is one of them, until you split.
Then there are up to four, side by side, each with its own chips on its own
spot — because after a split they are not one bet in two piles, they are two
bets that win and lose separately. */
.bj-hands {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 0.75rem 1rem;
}
.bj-hand {
display: flex;
align-items: center;
gap: 0.85rem;
border-radius: 1.5rem;
padding: 0.4rem 0.7rem 0.4rem 0.4rem;
transition: opacity 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
}
/* Which hand you are actually playing. The rest are still on the table; they
are simply not your problem this second. */
.bj-hand[data-live="0"] { opacity: 0.5; }
.bj-hand[data-live="1"] {
background: rgba(0,0,0,0.16);
box-shadow: 0 0 0 2px rgba(var(--glow, 242,181,61), 0.5);
}
/* Four hands do not fit at one hand's worth of card, and the fix is to tell the
row what a card *is* rather than to squash the box a card is in: everything
about a card is sized off these two vars. The spot shrinks with them, or the
chips end up bigger than the cards they're riding on. */
.bj-hands[data-count="2"] { --card-h: 6.6rem; --card-w: 4.7rem; }
.bj-hands[data-count="3"],
.bj-hands[data-count="4"] { --card-h: 5rem; --card-w: 3.6rem; }
.bj-hands[data-count="2"] .pete-hand { min-height: 6.8rem; }
.bj-hands[data-count="3"] .pete-hand,
.bj-hands[data-count="4"] .pete-hand { min-height: 5.2rem; gap: 0.35rem; }
.bj-hands[data-count="2"] .pete-spot { height: 5.5rem; width: 5.5rem; }
.bj-hands[data-count="3"] .pete-spot,
.bj-hands[data-count="4"] .pete-spot { height: 4.25rem; width: 4.25rem; }
.bj-hands[data-count="3"] .pete-spot-label,
.bj-hands[data-count="4"] .pete-spot-label { display: none; }
/* The card a split is about to take away. It lifts, and then it's gone —
otherwise a card simply teleports into a hand that didn't exist yet. */
.bj-splitting { animation: bj-split-lift 0.32s ease forwards; }
@keyframes bj-split-lift {
from { transform: translateY(0) rotate(0); }
to { transform: translateY(-1.1rem) rotate(6deg); }
}
/* The stack in the spot. Each chip is offset up the pile by its index, so ten
chips look like ten chips rather than one chip with a number on it. */
.pete-stack {

File diff suppressed because one or more lines are too long