games: a blackjack table you can actually sit down at
The engine, the escrow and the wire were all in place; nothing had a browser on the end of it. This is that end: a lobby, a table, and the five endpoints between them. The browser holds no game. It sends intents and gets back a view — the cards it is entitled to see, and the script of how they arrived, one event per card off the shoe. The dealer's hole card is not in the payload at all until the reveal, because a field the client is told to ignore is a field somebody reads in devtools. The shoe lives in game_live_hands, which also means a redeploy mid-hand no longer costs a player their stake: the hand is still there when they come back. The money is ordered so nothing can be spent twice. The stake leaves the stack in the same statement that checks it exists, before a card is dealt. Every new hand is seated with a plain INSERT, so a double-clicked Deal is decided by the primary key rather than by a read that raced — it loses, gets its chips back, and the hand in progress is untouched. A double takes its raise up front and hands it straight back if the engine refuses the move. Cards are dealt rather than swapped in — they fly out of the shoe and turn over, which was a requirement and not a flourish. The faces and the chips are still plain; that's next.
This commit is contained in:
113
internal/web/templates/blackjack.html
Normal file
113
internal/web/templates/blackjack.html
Normal file
@@ -0,0 +1,113 @@
|
||||
{{define "title"}}Blackjack · Pete's Casino{{end}}
|
||||
|
||||
{{define "main"}}
|
||||
<div class="space-y-6" data-blackjack>
|
||||
|
||||
<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">Blackjack</h1>
|
||||
</div>
|
||||
<p class="text-sm text-[color:var(--ink)]/50">Six decks · pays 3:2 · dealer hits soft 17</p>
|
||||
</div>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
|
||||
<!-- The felt. -->
|
||||
<section class="pete-felt relative overflow-hidden rounded-3xl p-6 sm:p-10 shadow-pete-lg border-2 border-[color:var(--ink)]/10">
|
||||
|
||||
<!-- The shoe every card flies out of. -->
|
||||
<div class="pete-shoe" aria-hidden="true"></div>
|
||||
|
||||
<div class="relative space-y-8">
|
||||
|
||||
<!-- Dealer -->
|
||||
<div>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<span class="text-xs font-bold uppercase tracking-wider text-white/60">Dealer</span>
|
||||
<span data-dealer-total class="hidden rounded-full bg-black/25 px-2.5 py-0.5 text-xs font-bold tabular-nums text-white"></span>
|
||||
</div>
|
||||
<div data-dealer class="pete-hand" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
<!-- What just happened. -->
|
||||
<div class="flex min-h-[2.75rem] items-center justify-center">
|
||||
<p data-verdict class="hidden rounded-full bg-white/95 px-5 py-2 font-display text-lg font-bold shadow-pete"></p>
|
||||
</div>
|
||||
|
||||
<!-- Player -->
|
||||
<div>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<span class="text-xs font-bold uppercase tracking-wider text-white/60">You</span>
|
||||
<span data-player-total class="hidden rounded-full bg-black/25 px-2.5 py-0.5 text-xs font-bold tabular-nums text-white"></span>
|
||||
<span data-bet class="hidden ml-auto rounded-full bg-[color:var(--accent)] px-3 py-0.5 text-xs font-bold tabular-nums text-white"></span>
|
||||
</div>
|
||||
<div data-player class="pete-hand" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Betting: shown between hands. -->
|
||||
<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="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>25</span></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
{{range .Denominations}}
|
||||
<button type="button" data-chip="{{.}}"
|
||||
class="pete-chip h-12 w-12 rounded-full font-display text-sm font-bold text-white shadow-pete
|
||||
hover:-translate-y-0.5 active:translate-y-0 transition">{{.}}</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-deal
|
||||
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">
|
||||
Deal
|
||||
</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>
|
||||
|
||||
<!-- Playing: shown while a hand is live. -->
|
||||
<section data-actions class="hidden rounded-3xl bg-[color:var(--card)] p-5 sm:p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<div class="flex flex-wrap items-center justify-center gap-3">
|
||||
<button type="button" data-move="hit"
|
||||
class="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">
|
||||
Hit
|
||||
</button>
|
||||
<button type="button" data-move="stand"
|
||||
class="rounded-full bg-[color:var(--card)] px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Stand
|
||||
</button>
|
||||
<button type="button" data-move="double"
|
||||
class="rounded-full bg-[color:var(--card)] px-8 py-3 font-display text-lg font-bold shadow-pete border-2 border-[color:var(--ink)]/10
|
||||
hover:bg-[color:var(--ink)]/5 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Double
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-center text-xs text-[color:var(--ink)]/40">
|
||||
<kbd>h</kbd> hit · <kbd>s</kbd> stand · <kbd>d</kbd> double
|
||||
</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/games.js" defer></script>
|
||||
<script src="/static/js/blackjack.js" defer></script>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user