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:
45
internal/web/templates/_chipbar.html
Normal file
45
internal/web/templates/_chipbar.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{{define "_chipbar"}}
|
||||
<section data-chipbar
|
||||
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 class="min-w-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Chips in front of you</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span data-chips class="font-display text-3xl font-bold tabular-nums">—</span>
|
||||
<span data-pending class="hidden text-sm font-semibold text-[color:var(--ink)]/60 animate-pulse"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-[color:var(--ink)]/50">Your wallet</div>
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span data-euros class="font-display text-2xl font-bold tabular-nums text-[color:var(--ink)]/70">—</span>
|
||||
<span class="text-sm text-[color:var(--ink)]/40" title="Pete reads this off the game box every couple of minutes, so it lags a little.">€, give or take</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex flex-wrap items-center gap-2">
|
||||
<div class="flex items-center gap-1.5 rounded-full bg-[color:var(--ink)]/5 p-1.5">
|
||||
<label for="buyin-amount" class="sr-only">How many chips?</label>
|
||||
<input id="buyin-amount" data-buyin-amount type="number" min="1" step="1" value="100"
|
||||
inputmode="numeric" enterkeyhint="go"
|
||||
class="w-24 rounded-full bg-[color:var(--card)] px-3 py-1.5 text-sm font-semibold tabular-nums
|
||||
border-2 border-[color:var(--ink)]/10 focus:outline-none focus:border-[color:var(--accent)]">
|
||||
<button type="button" data-buyin
|
||||
class="rounded-full bg-[color:var(--accent)] px-4 py-1.5 text-sm font-bold text-white shadow-pete
|
||||
hover:brightness-105 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none transition">
|
||||
Buy chips
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" data-cashout
|
||||
class="rounded-full bg-[color:var(--card)] px-4 py-2 text-sm 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">
|
||||
Cash out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p data-chipbar-msg class="hidden mt-3 rounded-2xl bg-[color:var(--ink)]/5 px-4 py-2 text-sm font-semibold"></p>
|
||||
</section>
|
||||
{{end}}
|
||||
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}}
|
||||
72
internal/web/templates/games.html
Normal file
72
internal/web/templates/games.html
Normal file
@@ -0,0 +1,72 @@
|
||||
{{define "title"}}Pete's Casino · {{.SiteTitle}}{{end}}
|
||||
|
||||
{{define "main"}}
|
||||
<div class="space-y-8">
|
||||
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 sm:p-8 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<h1 class="font-display text-3xl sm:text-4xl font-bold">Pete's Casino 🎲</h1>
|
||||
<p class="mt-2 text-[color:var(--ink)]/70">
|
||||
Real euros, from the same wallet as everything else. Chips are one euro each,
|
||||
and whatever you don't spend goes straight back when you cash out.
|
||||
</p>
|
||||
</div>
|
||||
<img src="/static/img/pete.avif" alt="" width="72" height="72"
|
||||
class="hidden sm:block h-18 w-18 shrink-0 rounded-2xl object-cover shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{template "_chipbar" .}}
|
||||
|
||||
<section>
|
||||
<h2 class="font-display text-2xl font-bold mb-4">The tables</h2>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
|
||||
<a href="/games/blackjack"
|
||||
class="group rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10 hover:-translate-y-0.5 hover:shadow-pete-lg transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--accent)]/25 text-2xl">🃏</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold">Blackjack</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60">Six decks, blackjack pays 3:2.</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full bg-theme-gaming px-3 py-1 text-xs font-bold uppercase tracking-wider text-white">Open</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-[color:var(--ink)]/70">
|
||||
The dealer stands on 17 and hits a soft one. House takes {{.RakePct}}% of what you win,
|
||||
and nothing at all when you lose or push.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
{{range .Soon}}
|
||||
<div class="rounded-3xl bg-[color:var(--card)]/60 p-6 shadow-pete border-2 border-dashed border-[color:var(--ink)]/15">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-[color:var(--ink)]/5 text-2xl grayscale">{{.Emoji}}</span>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-display text-xl font-bold text-[color:var(--ink)]/50">{{.Name}}</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/40">{{.Blurb}}</p>
|
||||
</div>
|
||||
<span class="ml-auto shrink-0 rounded-full border-2 border-[color:var(--ink)]/10 px-3 py-1 text-xs font-bold uppercase tracking-wider text-[color:var(--ink)]/40">Soon</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-3xl bg-[color:var(--card)] p-6 shadow-pete border-2 border-[color:var(--ink)]/10">
|
||||
<h2 class="font-display text-xl font-bold mb-2">House rules</h2>
|
||||
<ul class="space-y-1.5 text-sm text-[color:var(--ink)]/70">
|
||||
<li>· A chip is a euro. Nothing is worth more here than it is out there.</li>
|
||||
<li>· You can have {{.Cap}} chips in front of you at most. That's the limit for one sitting.</li>
|
||||
<li>· The house takes {{.RakePct}}% of your winnings. Never your stake: a push hands your bet straight back.</li>
|
||||
<li>· Walk away for half an hour and Pete cashes you out on his own, so your euros never sit in limbo.</li>
|
||||
<li>· Every hand is logged with the seed it was dealt from, so any hand can be dealt again exactly as it fell.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}<script src="/static/js/games.js" defer></script>{{end}}
|
||||
@@ -322,5 +322,6 @@
|
||||
<script src="/static/js/settings.js" defer></script>
|
||||
<script src="/static/js/reader.js" defer></script>
|
||||
<script src="/static/js/pwa.js" defer></script>
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>{{end}}
|
||||
|
||||
Reference in New Issue
Block a user