summaryrefslogtreecommitdiffstats
path: root/web/play/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/play/src/blackjack.ts14
-rw-r--r--web/play/src/crash.ts14
-rw-r--r--web/play/src/main.ts6
-rw-r--r--web/play/src/roulette.ts15
-rw-r--r--web/play/src/slots.ts18
5 files changed, 59 insertions, 8 deletions
diff --git a/web/play/src/blackjack.ts b/web/play/src/blackjack.ts
index 3537f45..49d46fa 100644
--- a/web/play/src/blackjack.ts
+++ b/web/play/src/blackjack.ts
@@ -1,6 +1,14 @@
1import type { View } from "./types"; 1import type { View } from "./types";
2import { cardFace, clear, money, node } from "./dom"; 2import { cardFace, clear, money, node } from "./dom";
3 3
4let lastAction = 0;
5
6function actionAllowed(): boolean {
7 if (Date.now() - lastAction < 400) return false;
8 lastAction = Date.now();
9 return true;
10}
11
4function cardElement(card: string): HTMLElement { 12function cardElement(card: string): HTMLElement {
5 if (card === "?") { 13 if (card === "?") {
6 const back = node("div", "card back"); 14 const back = node("div", "card back");
@@ -124,6 +132,7 @@ export function renderBlackjack(
124 ? node("button", "btn primary", label) 132 ? node("button", "btn primary", label)
125 : node("button", "btn", label); 133 : node("button", "btn", label);
126 button.addEventListener("click", () => { 134 button.addEventListener("click", () => {
135 if (!actionAllowed()) return;
127 if (action === "hit") send("Hit"); 136 if (action === "hit") send("Hit");
128 else if (action === "stand") send("Stand"); 137 else if (action === "stand") send("Stand");
129 else send("Double"); 138 else send("Double");
@@ -133,12 +142,15 @@ export function renderBlackjack(
133 actions.append(actionsRow); 142 actions.append(actionsRow);
134 const forfeit = node("button", "btn danger wide", "Forfeit hand"); 143 const forfeit = node("button", "btn danger wide", "Forfeit hand");
135 forfeit.addEventListener("click", () => { 144 forfeit.addEventListener("click", () => {
145 if (!actionAllowed()) return;
136 if (window.confirm("Forfeit this hand? The reserved bet is lost.")) send("Forfeit"); 146 if (window.confirm("Forfeit this hand? The reserved bet is lost.")) send("Forfeit");
137 }); 147 });
138 actions.append(forfeit); 148 actions.append(forfeit);
139 } else if (canDeal) { 149 } else if (canDeal) {
140 const deal = node("button", "btn primary wide big", "DEAL"); 150 const deal = node("button", "btn primary wide big", "DEAL");
141 deal.addEventListener("click", () => send("Deal")); 151 deal.addEventListener("click", () => {
152 if (actionAllowed()) send("Deal");
153 });
142 actions.append(deal); 154 actions.append(deal);
143 } 155 }
144 156
diff --git a/web/play/src/crash.ts b/web/play/src/crash.ts
index 6f3c8dd..a683dcd 100644
--- a/web/play/src/crash.ts
+++ b/web/play/src/crash.ts
@@ -31,6 +31,7 @@ let sendCommand: ((command: unknown) => void) | null = null;
31let frameHandle: number | undefined; 31let frameHandle: number | undefined;
32let verifyToken = 0; 32let verifyToken = 0;
33let lastStatus = ""; 33let lastStatus = "";
34let lastAction = 0;
34 35
35function build( 36function build(
36 board: HTMLElement, 37 board: HTMLElement,
@@ -88,6 +89,8 @@ function build(
88 const primary = node("button", "btn primary wide big", "START") as HTMLButtonElement; 89 const primary = node("button", "btn primary wide big", "START") as HTMLButtonElement;
89 const leave = node("button", "btn ghost wide", "Leave table") as HTMLButtonElement; 90 const leave = node("button", "btn ghost wide", "Leave table") as HTMLButtonElement;
90 primary.addEventListener("click", () => { 91 primary.addEventListener("click", () => {
92 if (Date.now() - lastAction < 500) return;
93 lastAction = Date.now();
91 if (current?.status === "running") sendCommand?.("CashOut"); 94 if (current?.status === "running") sendCommand?.("CashOut");
92 else sendCommand?.("Start"); 95 else sendCommand?.("Start");
93 }); 96 });
@@ -118,6 +121,15 @@ function build(
118 }; 121 };
119} 122}
120 123
124/// Stop the chart loop when another screen takes over.
125export function stopCrashAnimation(): void {
126 if (frameHandle !== undefined) {
127 cancelAnimationFrame(frameHandle);
128 frameHandle = undefined;
129 }
130 current = null;
131}
132
121export function renderCrash( 133export function renderCrash(
122 board: HTMLElement, 134 board: HTMLElement,
123 side: HTMLElement, 135 side: HTMLElement,
@@ -125,7 +137,7 @@ export function renderCrash(
125 send: (command: unknown) => void, 137 send: (command: unknown) => void,
126 now: Clock, 138 now: Clock,
127): void { 139): void {
128 if (!parts || parts.board !== board) { 140 if (!parts || parts.board !== board || !parts.canvas.isConnected) {
129 parts = build(board, side, send, now); 141 parts = build(board, side, send, now);
130 lastStatus = ""; 142 lastStatus = "";
131 } 143 }
diff --git a/web/play/src/main.ts b/web/play/src/main.ts
index fd55250..f051429 100644
--- a/web/play/src/main.ts
+++ b/web/play/src/main.ts
@@ -1,10 +1,10 @@
1import { byId, money, node, sha256Hex, toast, toneFor } from "./dom"; 1import { byId, money, node, sha256Hex, toast, toneFor } from "./dom";
2import { Gateway } from "./net"; 2import { Gateway } from "./net";
3import { renderBlackjack } from "./blackjack"; 3import { renderBlackjack } from "./blackjack";
4import { renderCrash } from "./crash"; 4import { renderCrash, stopCrashAnimation } from "./crash";
5import { renderHistory } from "./history"; 5import { renderHistory } from "./history";
6import { renderRoulette } from "./roulette"; 6import { renderRoulette } from "./roulette";
7import { renderSlots } from "./slots"; 7import { renderSlots, stopSlotsAnimation } from "./slots";
8import type { View } from "./types"; 8import type { View } from "./types";
9 9
10const authScreen = byId("auth-screen"); 10const authScreen = byId("auth-screen");
@@ -72,6 +72,8 @@ const gateway = new Gateway(wsUrl, {
72 }, 72 },
73 onView(next) { 73 onView(next) {
74 view = next; 74 view = next;
75 if (next.screen !== "slots") stopSlotsAnimation();
76 if (next.screen !== "crash") stopCrashAnimation();
75 // Game and history views carry no account field; the lobby is the source 77 // Game and history views carry no account field; the lobby is the source
76 // of truth for who is signed in. A null lobby user is a logout or an 78 // of truth for who is signed in. A null lobby user is a logout or an
77 // expired session. 79 // expired session.
diff --git a/web/play/src/roulette.ts b/web/play/src/roulette.ts
index cbb7c6e..a42686a 100644
--- a/web/play/src/roulette.ts
+++ b/web/play/src/roulette.ts
@@ -20,6 +20,9 @@ type Hotspot = {
20 rank: number; 20 rank: number;
21}; 21};
22 22
23let observer: ResizeObserver | null = null;
24let lastSpin = 0;
25
23type Layout = { 26type Layout = {
24 width: number; 27 width: number;
25 height: number; 28 height: number;
@@ -413,9 +416,11 @@ export function renderRoulette(
413 const zone = hit(event.clientX, event.clientY); 416 const zone = hit(event.clientX, event.clientY);
414 canvas.style.cursor = zone && !spinning ? "pointer" : "default"; 417 canvas.style.cursor = zone && !spinning ? "pointer" : "default";
415 }); 418 });
416 new ResizeObserver(() => { 419 observer?.disconnect();
420 observer = new ResizeObserver(() => {
417 current = draw(canvas, view); 421 current = draw(canvas, view);
418 }).observe(canvas); 422 });
423 observer.observe(canvas);
419 424
420 // Sidebar 425 // Sidebar
421 side.append(node("div", "label", "Roulette")); 426 side.append(node("div", "label", "Roulette"));
@@ -459,7 +464,11 @@ export function renderRoulette(
459 const actions = node("div", "actions"); 464 const actions = node("div", "actions");
460 const spin = node("button", "btn primary wide big", spinning ? "SPINNING…" : "SPIN WHEEL"); 465 const spin = node("button", "btn primary wide big", spinning ? "SPINNING…" : "SPIN WHEEL");
461 spin.disabled = spinning || total === 0; 466 spin.disabled = spinning || total === 0;
462 spin.addEventListener("click", () => send("Spin")); 467 spin.addEventListener("click", () => {
468 if (Date.now() - lastSpin < 600) return;
469 lastSpin = Date.now();
470 send("Spin");
471 });
463 const clearBets = node("button", "btn wide", "Clear bets"); 472 const clearBets = node("button", "btn wide", "Clear bets");
464 clearBets.disabled = spinning || total === 0; 473 clearBets.disabled = spinning || total === 0;
465 clearBets.addEventListener("click", () => send("ClearBets")); 474 clearBets.addEventListener("click", () => send("ClearBets"));
diff --git a/web/play/src/slots.ts b/web/play/src/slots.ts
index dba1e25..7c4677d 100644
--- a/web/play/src/slots.ts
+++ b/web/play/src/slots.ts
@@ -7,6 +7,7 @@ const SYMBOLS = "CLOGDS";
7const LINE_NAMES = ["top", "middle", "bottom", "V", "inverted V"]; 7const LINE_NAMES = ["top", "middle", "bottom", "V", "inverted V"];
8 8
9let spinUntil = 0; 9let spinUntil = 0;
10let lastSpin = 0;
10let animation: number | undefined; 11let animation: number | undefined;
11let latest: View | null = null; 12let latest: View | null = null;
12let latestBoard: HTMLElement | null = null; 13let latestBoard: HTMLElement | null = null;
@@ -15,6 +16,17 @@ export function requestSlotsSpin(): void {
15 spinUntil = Date.now() + 900; 16 spinUntil = Date.now() + 900;
16} 17}
17 18
19/// Stop the reel animation when another screen takes over.
20export function stopSlotsAnimation(): void {
21 if (animation !== undefined) {
22 window.clearInterval(animation);
23 animation = undefined;
24 }
25 latest = null;
26 latestBoard = null;
27 spinUntil = 0;
28}
29
18function spinning(view: View): boolean { 30function spinning(view: View): boolean {
19 return view.phase === "spinning" || Date.now() < spinUntil; 31 return view.phase === "spinning" || Date.now() < spinUntil;
20} 32}
@@ -42,7 +54,9 @@ export function renderSlots(
42 54
43 const reels = node("div", "reels"); 55 const reels = node("div", "reels");
44 const tiles: HTMLElement[] = []; 56 const tiles: HTMLElement[] = [];
45 const flat = String(view.reels ?? ""); 57 const flat = Array.isArray(view.reels)
58 ? view.reels.join("")
59 : String(view.reels ?? "");
46 for (let column = 0; column < REELS; column += 1) { 60 for (let column = 0; column < REELS; column += 1) {
47 const reel = node("div", "reel"); 61 const reel = node("div", "reel");
48 for (let row = 0; row < ROWS; row += 1) { 62 for (let row = 0; row < ROWS; row += 1) {
@@ -133,6 +147,8 @@ export function renderSlots(
133 const spin = node("button", "btn primary wide big", active ? "SPINNING…" : "SPIN"); 147 const spin = node("button", "btn primary wide big", active ? "SPINNING…" : "SPIN");
134 spin.disabled = active; 148 spin.disabled = active;
135 spin.addEventListener("click", () => { 149 spin.addEventListener("click", () => {
150 if (Date.now() - lastSpin < 900) return;
151 lastSpin = Date.now();
136 requestSlotsSpin(); 152 requestSlotsSpin();
137 send("Spin"); 153 send("Spin");
138 }); 154 });