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({ game }: { game: TableCtl }) {
23 const t = useT();
24 const s = game.state;
25 if (!game.reroll) return null;
26
27 const total = s.dice.reduce((n, d) => n + d, 0);
28 // Who the count landed on, and how far into their wall — the two things the
29 // total decides, said out loud so the square can be checked against them.
30 const whose = ((s.dealer + total - 1) % 4) as SeatId;
31 const into = total % STACKS_PER_SIDE;
32 const step = (to: number) => game.reroll?.(to - 2, 1, 1);
33
34 return (
35 <div className="dice-debug">
36 <div className="dice-faces">
37 {s.dice.map((d, i) => (
38 <b key={i}>{d}</b>
39 ))}
40 <em>{total}</em>
41 </div>
42 <div className="dice-row">
43 <button className="btn ghost" onClick={() => step(Math.max(3, total - 1))} disabled={total <= 3}>
44 −
45 </button>
46 <button className="btn ghost" onClick={() => game.reroll?.()}>
47 {t('擲骰', 'Throw')}
48 </button>
49 <button
50 className="btn ghost"
51 onClick={() => step(Math.min(18, total + 1))}
52 disabled={total >= 18}
53 >
54 +
55 </button>
56 </div>
57 <div className="dice-says">
58 {s.players[whose].name} · {WALL_SIDES[SEAT_WALL_SIDE[whose]]} ·{' '}
59 {t(`第 ${into || STACKS_PER_SIDE} 疊`, `${into || STACKS_PER_SIDE} in`)} ·{' '}
60 {t('斷點', 'break')} {breakAt(s.dealer, s.dice)}
61 </div>
62 </div>
63 );
64}