anvilsign in

collin/mahjong

1import { STACKS_PER_SIDE, WALL_SIZE, WALL_STACKS } from '../game/tiles';
2import type { GameState } from '../game/engine';
3
4/**
5 * The wall as it sits on a real table: a square of four staggered sides, 18
6 * stacks of two to a side. Position 0 is the break point — draws eat the square
7 * from there, while kong and flower replacements come off the far end (the 底牌
8 * tail), so it is consumed from both directions at once. Spent stacks stay as
9 * faint ghosts so the square keeps its shape all game.
10 */
11export function WallRing({ state }: { state: GameState }) {
12 const front = state.drawnFront;
13 const back = WALL_SIZE - state.drawnBack;
14 const deadFrom = back - state.rules.wallReserve;
15
16 const stacks = Array.from({ length: WALL_STACKS }, (_, i) => {
17 const a = i * 2;
18 const b = a + 1;
19 const live = (p: number) => p >= front && p < back;
20 const count = (live(a) ? 1 : 0) + (live(b) ? 1 : 0);
21 return { count, dead: b >= deadFrom, next: front === a || front === b };
22 });
23
24 const side = (from: number, cls: string) => (
25 <div className={`wall-side ${cls}`} key={cls}>
26 {stacks.slice(from, from + STACKS_PER_SIDE).map((st, i) => (
27 <div
28 key={i}
29 className={`wall-stack s${st.count}${st.dead ? ' dead' : ''}${st.next ? ' next' : ''}`}
30 >
31 <i />
32 </div>
33 ))}
34 </div>
35 );
36
37 return (
38 <div className="wall-ring" aria-label="牌牆">
39 {side(0, 'top')}
40 {side(STACKS_PER_SIDE, 'right')}
41 {side(STACKS_PER_SIDE * 2, 'bottom')}
42 {side(STACKS_PER_SIDE * 3, 'left')}
43 </div>
44 );
45}