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:
@@ -1894,3 +1894,257 @@ html[data-room] .pete-felt {
|
||||
.cs-stack[data-chip="25"] { --chip: #4caf7d; }
|
||||
.cs-stack[data-chip="100"] { --chip: #2b2118; }
|
||||
.cs-stack[data-chip="500"] { --chip: #b079d6; }
|
||||
|
||||
/* ── Hold'em ────────────────────────────────────────────────────────────────
|
||||
The one table with other people at it. Everything else in the casino has a
|
||||
single bet spot, because there was only ever one player; here every seat has
|
||||
its own, chips travel between them and the pot rather than between you and the
|
||||
house, and the pot in the middle is the thing they all move toward.
|
||||
|
||||
Seats are a wrapping row rather than an ellipse. An oval of six seats is what a
|
||||
poker table looks like, and it is also what stops fitting the moment a phone is
|
||||
held up — this keeps the geometry honest at 390px and still reads as a table
|
||||
because the board and the pot sit in the middle of it, which is where the eye
|
||||
goes anyway. */
|
||||
|
||||
.pete-poker { --seat-w: 7.5rem; }
|
||||
|
||||
.pete-poker-seats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.pete-seat {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
width: var(--seat-w);
|
||||
transition: opacity 0.3s ease, filter 0.3s ease;
|
||||
}
|
||||
/* A folded seat is still there — you can see they're in the game and out of the
|
||||
hand, which is information you're entitled to. It just stops competing. */
|
||||
.pete-seat[data-state="folded"] { opacity: 0.38; filter: grayscale(0.7); }
|
||||
.pete-seat[data-state="out"] { opacity: 0.2; }
|
||||
|
||||
/* Whose turn it is. The ring is the only thing on the felt that moves on its own,
|
||||
because it is the only thing you are waiting for. */
|
||||
.pete-seat[data-turn="1"] .pete-seat-plate {
|
||||
box-shadow: 0 0 0 2px var(--accent), 0 0 1.6rem -0.2rem var(--accent);
|
||||
animation: pete-seat-wait 1.6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pete-seat-wait {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-2px); }
|
||||
}
|
||||
|
||||
.pete-seat-cards {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.15rem;
|
||||
min-height: 4.4rem;
|
||||
--card-h: 4.4rem;
|
||||
--card-w: 3.15rem;
|
||||
}
|
||||
/* A seat that folded throws its cards in. */
|
||||
.pete-seat-cards[data-mucked="1"] { opacity: 0; transform: translateY(-0.6rem) scale(0.9); transition: all 0.35s ease; }
|
||||
|
||||
.pete-seat-plate {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0.3rem 0.4rem;
|
||||
border-radius: 0.85rem;
|
||||
background: rgba(0,0,0,0.32);
|
||||
border: 1px solid rgba(255,255,255,0.12);
|
||||
transition: box-shadow 0.25s ease;
|
||||
}
|
||||
.pete-seat-name {
|
||||
font-family: var(--font-display, inherit);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.1;
|
||||
color: rgba(255,255,255,0.92);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pete-seat-stack {
|
||||
font-weight: 700;
|
||||
font-size: 0.78rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: rgba(255,255,255,0.58);
|
||||
}
|
||||
.pete-seat[data-state="allin"] .pete-seat-stack::after {
|
||||
content: " all in";
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* The dealer button, and the blinds. Small, and worth having: position is most of
|
||||
what makes one hand different from the next, and a player who cannot see where
|
||||
the button is cannot see the game. */
|
||||
.pete-seat-pos {
|
||||
position: absolute;
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-width: 1.45rem;
|
||||
height: 1.45rem;
|
||||
padding: 0 0.3rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.58rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
background: rgba(255,255,255,0.9);
|
||||
color: #2b2118;
|
||||
box-shadow: 0 2px 0 rgba(0,0,0,0.3);
|
||||
}
|
||||
.pete-seat-pos[data-pos="BTN"] { background: #f5d76e; }
|
||||
|
||||
/* Every seat's bet, sitting between them and the pot.
|
||||
A .pete-spot is 7rem across, because blackjack has exactly one of them. Six of
|
||||
those is most of a felt, so a seat's is less than half the size — and so are the
|
||||
chips in it, which are otherwise bigger than the ring they land in. */
|
||||
.pete-seat-spot {
|
||||
height: 3.6rem;
|
||||
width: 3.6rem;
|
||||
margin-bottom: 0.5rem; /* the bet total hangs below the ring; leave it somewhere to hang */
|
||||
}
|
||||
.pete-seat-spot .pete-disc {
|
||||
height: 1.6rem;
|
||||
width: 1.6rem;
|
||||
margin: -0.8rem 0 0 -0.8rem;
|
||||
}
|
||||
.pete-seat-spot .pete-spot-total {
|
||||
font-size: 0.66rem;
|
||||
padding: 0.05rem 0.45rem;
|
||||
}
|
||||
|
||||
/* The middle of the table: the board, and the pot under it. */
|
||||
.pete-poker-middle {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
/* The pot's chips stack upwards, so they need room under the board or they climb
|
||||
into the river card. */
|
||||
.pete-poker-pot { margin-top: 0.9rem; }
|
||||
/* The board keeps its height when it is empty, so the felt does not jump a hundred
|
||||
pixels the moment the flop lands. */
|
||||
.pete-poker-board {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
min-height: 6rem;
|
||||
--card-h: 6rem;
|
||||
--card-w: 4.3rem;
|
||||
}
|
||||
.pete-poker-pot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
/* The pot's chips need a box to themselves. .pete-stack is position:absolute with
|
||||
inset:0, so a pile that shares a box with the number under it lands on top of
|
||||
the number — which is exactly what it did: the pot showed a chip and no total. */
|
||||
.pete-poker-pot-pile {
|
||||
position: relative;
|
||||
width: 7rem;
|
||||
height: 3rem;
|
||||
}
|
||||
.pete-poker-pot-label {
|
||||
font-size: 0.62rem;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: rgba(255,255,255,0.42);
|
||||
}
|
||||
.pete-poker-pot-total {
|
||||
font-family: var(--font-display, inherit);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 0 rgba(0,0,0,0.28);
|
||||
}
|
||||
.pete-poker-side {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
/* Your seat. Bigger cards, because they are the two you are actually reading. */
|
||||
.pete-poker-you .pete-seat { width: auto; }
|
||||
.pete-poker-you .pete-seat-cards {
|
||||
--card-h: 6.8rem;
|
||||
--card-w: 4.85rem;
|
||||
gap: 0.3rem;
|
||||
min-height: 6.8rem;
|
||||
}
|
||||
.pete-poker-you .pete-seat-plate { padding: 0.4rem 1.1rem; }
|
||||
.pete-poker-you .pete-seat-name { font-size: 0.95rem; }
|
||||
.pete-poker-you .pete-seat-stack { font-size: 0.95rem; }
|
||||
|
||||
/* What the hand did to you, said once, in the middle of the felt. */
|
||||
.pete-poker-verdict {
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.95);
|
||||
padding: 0.5rem 1.25rem;
|
||||
font-family: var(--font-display, inherit);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
color: #2b2118;
|
||||
box-shadow: 0 4px 0 rgba(0,0,0,0.18);
|
||||
}
|
||||
.pete-poker-verdict[data-tone="win"] { background: #4caf7d; color: #fff; }
|
||||
.pete-poker-verdict[data-tone="lose"] { background: rgba(255,255,255,0.9); color: #7a5c50; }
|
||||
|
||||
/* The action bar. Raise is a slider, because a raise is a *size* and a text box
|
||||
makes you type a number you have not thought about. */
|
||||
.pete-raise {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
flex: 1 1 14rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.pete-raise input[type="range"] {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.pete-raise-to {
|
||||
font-family: var(--font-display, inherit);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 800;
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 3.5rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.pete-poker-log {
|
||||
max-height: 7rem;
|
||||
overflow-y: auto;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: rgba(255,255,255,0.55);
|
||||
}
|
||||
.pete-poker-log b { color: rgba(255,255,255,0.85); font-weight: 700; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pete-poker { --seat-w: 5.6rem; }
|
||||
.pete-seat-cards { --card-h: 3.5rem; --card-w: 2.5rem; min-height: 3.5rem; }
|
||||
.pete-poker-board { --card-h: 5rem; --card-w: 3.55rem; min-height: 5rem; gap: 0.25rem; }
|
||||
.pete-poker-you .pete-seat-cards { --card-h: 6rem; --card-w: 4.3rem; min-height: 6rem; }
|
||||
.pete-poker-pot-total { font-size: 1.25rem; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user