| 1 | import { STACKS_PER_SIDE } from '../game/tiles'; |
| 2 | import { WALL_SIDES, wallStacks } from '../game/wall'; |
| 3 | import 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 | */ |
| 19 | export 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 | } |