| 1 | import { STACKS_PER_SIDE, WALL_SIZE, WALL_STACKS } from '../game/tiles'; |
| 2 | import 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 | */ |
| 11 | export 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 | } |