anvilsign in

collin/mahjong

1import { STACKS_PER_SIDE } from '../game/tiles';
2import { WALL_SIDES, wallStacks } from '../game/wall';
3import type { GameState } from '../game/engine';
4
5/**
6 * The wall as it sits on a real table: a square of four staggered sides, 18
7 * stacks of two to a side. Position 0 is the break point — draws eat the square
8 * from there, while kong and flower replacements come off the far end (the 底牌
9 * tail), so it is consumed from both directions at once. Spent stacks stay as
10 * faint ghosts so the square keeps its shape all game.
11 *
12 * What is left where is `game/wall.ts`, because the physics needs the same
13 * answer: a stack still standing is what a thrown tile has to clear.
14 *
15 * Every stack carries its index, and that is load-bearing — `table/geometry.ts`
16 * measures these elements to build the colliders, rather than restating the CSS
17 * that sizes the square.
18 */
19export function WallRing({ state }: { state: GameState }) {
20 const stacks = wallStacks(state);
21
22 return (
23 <div className="wall-ring" aria-label="牌牆">
24 {WALL_SIDES.map((cls, side) => {
25 const from = side * STACKS_PER_SIDE;
26 return (
27 <div className={`wall-side ${cls}`} key={cls}>
28 {stacks.slice(from, from + STACKS_PER_SIDE).map((st, i) => (
29 <div
30 key={i}
31 data-stack={from + i}
32 className={`wall-stack s${st.count}${st.dead ? ' dead' : ''}${st.next ? ' next' : ''}`}
33 >
34 <i />
35 </div>
36 ))}
37 </div>
38 );
39 })}
40 </div>
41 );
42}