anvilsign in

collin/mahjong

1import type { TableCtl } from '../game/ctl';
2import type { SeatId } from '../game/types';
3import { breakAt, SEAT_WALL_SIDE, WALL_SIDES } from '../game/wall';
4import { STACKS_PER_SIDE } from '../game/tiles';
5import { useT } from './lang';
6
7/**
8 * The dealer's dice, with a hand on them. Dev builds only.
9 *
10 * Where the wall is broken is thrown for once at the start of a hand and then
11 * lived with, which is right at a table and useless for looking at: half the
12 * question about the square is what it looks like opened at each of the
13 * seventy-two places it can be opened at, and waiting for the dice to walk you
14 * round is not a way to find out. So the total is a thing you can step through
15 * here — every one three dice can make, 3 to 18 — and the square is redrawn on
16 * each with the gap somewhere else.
17 *
18 * It moves the gap and nothing else: the tiles were shuffled before the wall
19 * was built, so nobody's hand changes and the draw order is the draw order.
20 * See `reroll` in game/engine.ts and `breakAt` in game/wall.ts.
21 */
22export function DiceDebug({
23 game,
24 whole,
25 onWhole,
26}: {
27 game: TableCtl;
28 /** Drawing the wall as if nothing had been taken off it — see `WallRing`. */
29 whole: boolean;
30 onWhole: (whole: boolean) => void;
31}) {
32 const t = useT();
33 const s = game.state;
34 if (!game.reroll) return null;
35
36 const total = s.dice.reduce((n, d) => n + d, 0);
37 // Who the count landed on, and how far into their wall — the two things the
38 // total decides, said out loud so the square can be checked against them.
39 const whose = ((s.dealer + total - 1) % 4) as SeatId;
40 const into = total % STACKS_PER_SIDE;
41 const step = (to: number) => game.reroll?.(to - 2, 1, 1);
42
43 return (
44 <div className="dice-debug">
45 <div className="dice-faces">
46 {s.dice.map((d, i) => (
47 <b key={i}>{d}</b>
48 ))}
49 <em>{total}</em>
50 </div>
51 <div className="dice-row">
52 <button className="btn ghost" onClick={() => step(Math.max(3, total - 1))} disabled={total <= 3}>
53 −
54 </button>
55 <button className="btn ghost" onClick={() => game.reroll?.()}>
56 {t('擲骰', 'Throw')}
57 </button>
58 <button
59 className="btn ghost"
60 onClick={() => step(Math.min(18, total + 1))}
61 disabled={total >= 18}
62 >
63 +
64 </button>
65 </div>
66 <div className="dice-row">
67 {/* The wall is only whole for the moment before the deal, and that is
68 the shape the square was built to. Puts all 144 back to look at,
69 without touching a tile anybody is holding. */}
70 <button
71 className={`btn ghost ${whole ? 'on' : ''}`}
72 onClick={() => onWhole(!whole)}
73 aria-pressed={whole}
74 >
75 {t('整牆 144', 'Whole wall')}
76 </button>
77 </div>
78 <div className="dice-says">
79 {s.players[whose].name} · {WALL_SIDES[SEAT_WALL_SIDE[whose]]} ·{' '}
80 {t(`第 ${into || STACKS_PER_SIDE} 疊`, `${into || STACKS_PER_SIDE} in`)} ·{' '}
81 {t('斷點', 'break')} {breakAt(s.dealer, s.dice)}
82 </div>
83 </div>
84 );
85}