Files
Pete/internal/web/templates/hangman.html
prosolis fe2195e85f games: a gallows you can bet on
Hangman, and it plays for chips — which the plan had down as a free game, on
the grounds that trivia has no euro coupling in gogobee. But a free game in a
casino reads as a demo, so it stakes like everything else.

The idea that makes it a casino game rather than hangman with a wager stapled
on: the gallows is the payout meter. A wrong guess draws a limb *and* takes a
tenth off what a win is worth, because those are the same event and showing
them as one is the entire reason to bet on this. Short phrases pay 2.6x (fewer
letters, less to go on), long ones 1.6x — the floor is 1x, so a win never hands
back less than the stake, and the rake still comes out of winnings only.

State.Pays() is the number the felt quotes and the number settle() lands on.
They were briefly two sums, and the table spent an afternoon advertising a
pre-rake payout it didn't honour.

Two things the storage layer had already decided for us, and one it hadn't:
game_live_hands is keyed on the player, so "one game at a time" holds across
games for free (a live hangman 409s a blackjack deal, stake intact). But
table() unmarshalled every live row as a blackjack hand, which does not fail on
a hangman row — it quietly yields an empty hand. It dispatches on the game now.

commit() is the settle path both games share, and casinoRoutes() the one route
list, since the dev rig wires its own mux and a second copy is a copy that stops
including the newest game.

Driven in a browser, win and loss: 200 at 2.34x paid 455 and the bar landed on
it; six wrong took the stake and no more; a reload mid-phrase brought back the
board, the limbs, the multiple, the spent keys and the chips on the spot. The
browser found the two bugs a Go test can't — a lives counter under the house
rack, and a word wrapping early because the rack's clearance was on the whole
column instead of the one row beside it.
2026-07-14 01:19:05 -07:00

177 lines
8.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "title"}}Hangman · {{.Room.Name}}{{end}}
{{define "main"}}
<div class="space-y-6" data-hangman>
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-3 min-w-0">
<a href="/games" class="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-[color:var(--card)] shadow-pete border-2 border-[color:var(--ink)]/10 hover:bg-[color:var(--ink)]/5 transition" title="Back to the casino">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-5 w-5" aria-hidden="true">
<path d="M19 12H5"></path><polyline points="12 19 5 12 12 5"></polyline>
</svg>
<span class="sr-only">Back to the casino</span>
</a>
<h1 class="font-display text-3xl font-bold">Hangman</h1>
</div>
<p class="text-sm text-[color:var(--ink)]/50">{{.MaxWrong}} lives · every wrong guess costs you a tenth of the win</p>
</div>
{{template "_chipbar" .}}
<!-- The felt. The gallows is on it because the gallows is the meter: it counts
down your lives and your winnings at the same time. -->
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
<div class="pete-rack" data-house aria-hidden="true">
<span data-chip="500" style="--stack: 5"></span>
<span data-chip="100" style="--stack: 7"></span>
<span data-chip="25" style="--stack: 4"></span>
<span data-chip="5" style="--stack: 6"></span>
</div>
<!-- The gallows and the stake stack up on the left; the phrase and what it's
worth take the rest. The right column keeps clear of the rack in the
corner, which is why nothing in it is pushed to the far edge. -->
<div class="relative grid gap-x-10 gap-y-8 lg:grid-cols-[auto,1fr] lg:items-start">
<div class="flex flex-col items-center gap-6 lg:items-start">
<svg data-gallows viewBox="0 0 130 150" class="pete-gallows h-48 w-auto sm:h-56" role="img"
aria-labelledby="gallows-title">
<title id="gallows-title">The gallows</title>
<g class="pete-gallows-frame">
<path d="M8 145 H72" />
<path d="M28 145 V8" />
<path d="M28 8 H92" />
<path d="M92 8 V26" />
</g>
<g class="pete-gallows-body">
<circle data-part="0" cx="92" cy="38" r="12" />
<path data-part="1" d="M92 50 V88" />
<path data-part="2" d="M92 58 L74 76" />
<path data-part="3" d="M92 58 L110 76" />
<path data-part="4" d="M92 88 L76 114" />
<path data-part="5" d="M92 88 L108 114" />
</g>
</svg>
<!-- The stake sits under the gallows it's riding on, the same spot the
blackjack table puts your bet on. -->
<div class="flex items-center gap-4">
<div class="pete-spot" data-spot>
<span class="pete-spot-label">Bet</span>
<div class="pete-stack" data-stack></div>
<span data-spot-total class="pete-spot-total hidden"></span>
</div>
<p data-lives class="text-xs font-bold uppercase tracking-wider text-white/50"></p>
</div>
</div>
<div class="min-w-0 space-y-6">
<!-- What a win is worth right now. It falls as the figure fills in.
This row is the only one level with the house rack in the corner, so
it is the only one that has to keep clear of it — the board below
gets the full width, and needs it. -->
<div class="flex flex-wrap items-center gap-3 pr-24 sm:pr-28">
<div class="pete-meter" data-meter>
<span class="pete-meter-label">Pays</span>
<span data-multiple class="pete-meter-value"></span>
</div>
<p class="text-sm text-white/60">
<span data-stands class="font-bold tabular-nums text-white/90"></span>
<span data-stands-label>if you get it</span>
</p>
</div>
<!-- The phrase. -->
<div data-board class="pete-board" aria-live="polite" aria-label="The phrase"></div>
<!-- Letters that missed. -->
<div class="flex min-h-[1.75rem] flex-wrap items-center gap-1.5">
<span data-wrong-label class="hidden text-xs font-bold uppercase tracking-wider text-white/40">Missed</span>
<div data-wrong class="flex flex-wrap gap-1.5"></div>
</div>
<!-- What just happened. -->
<div class="flex min-h-[2.75rem] items-center">
<p data-verdict class="hidden rounded-full bg-white/95 px-5 py-2 font-display text-lg font-bold text-[#2b2118] shadow-pete"></p>
</div>
</div>
</div>
</section>
<!-- Guessing: shown while a game is live. -->
<section data-guessing class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
<div data-keyboard class="pete-keys"></div>
<div class="mt-4 flex flex-wrap items-center gap-2">
<label for="solve" class="sr-only">Guess the whole phrase</label>
<input id="solve" data-solve-input type="text" autocomplete="off" enterkeyhint="go"
placeholder="Or just say what it is…"
class="min-w-0 flex-1 rounded-full bg-[color:var(--ink)]/5 px-4 py-2.5 text-sm font-semibold
border-2 border-[color:var(--ink)]/10 focus:outline-none focus:border-[color:var(--accent)]">
<button type="button" data-solve
class="rounded-full bg-[color:var(--accent)] px-6 py-2.5 font-display font-bold text-white shadow-pete
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
Solve
</button>
</div>
<p class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
Type a letter to guess it. A wrong solve costs a life, same as a wrong letter.
</p>
<p data-game-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
</section>
<!-- Betting: shown between games. -->
<section data-betting class="rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">How long a phrase?</div>
<div class="mt-2 grid gap-2 sm:grid-cols-3">
{{range .Tiers}}
<button type="button" data-tier="{{.Slug}}"
class="pete-tier rounded-2xl border-2 p-3 text-left transition">
<div class="flex items-baseline justify-between gap-2">
<span class="font-display text-lg font-bold">{{.Name}}</span>
<span class="font-display text-lg font-bold tabular-nums text-[color:var(--accent)]">{{printf "%.1f" .Base}}×</span>
</div>
<p class="mt-0.5 text-xs text-[color:var(--ink)]/50">{{.Blurb}}</p>
</button>
{{end}}
</div>
<div class="mt-5 flex flex-wrap items-center gap-x-6 gap-y-4">
<div>
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Your bet</div>
<div class="font-display text-3xl font-bold tabular-nums"><span data-bet-amount>0</span></div>
</div>
<div class="flex flex-wrap items-center gap-2">
{{range .Denominations}}
<button type="button" data-chip="{{.}}" aria-label="Bet {{.}} more"
class="pete-chip pete-disc grid h-12 w-12 place-items-center font-display text-sm font-bold text-white">
<span>{{.}}</span>
</button>
{{end}}
<button type="button" data-bet-clear
class="rounded-full px-3 py-2 text-sm font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)] transition">Clear</button>
</div>
<button type="button" data-start
class="ml-auto rounded-full bg-[color:var(--accent)] px-8 py-3 font-display text-lg font-bold text-white shadow-pete
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
Play
</button>
</div>
<p data-table-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
</section>
</div>
{{end}}
{{define "scripts"}}
<script src="/static/js/casino-fx.js" defer></script>
<script src="/static/js/games.js" defer></script>
<script src="/static/js/hangman.js" defer></script>
{{end}}