import Phaser from 'phaser'; import type { BoopState, PlayerType } from '@/game/boop'; import type { ReadonlySignal } from '@preact/signals-core'; const BOARD_SIZE = 6; const CELL_SIZE = 80; const BOARD_OFFSET = { x: 80, y: 100 }; export { BOARD_SIZE, CELL_SIZE, BOARD_OFFSET }; export class BoardRenderer { private container: Phaser.GameObjects.Container; private gridGraphics: Phaser.GameObjects.Graphics; private turnText: Phaser.GameObjects.Text; private infoText: Phaser.GameObjects.Text; constructor(private scene: Phaser.Scene) { this.container = this.scene.add.container(0, 0); this.gridGraphics = this.scene.add.graphics(); this.drawGrid(); this.turnText = this.scene.add.text( BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2, BOARD_OFFSET.y + BOARD_SIZE * CELL_SIZE + 30, '', { fontSize: '22px', fontFamily: 'Arial', color: '#4b5563', } ).setOrigin(0.5); this.infoText = this.scene.add.text( BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2, BOARD_OFFSET.y + BOARD_SIZE * CELL_SIZE + 60, 'Click to place kitten. Cats win with 3 in a row!', { fontSize: '16px', fontFamily: 'Arial', color: '#6b7280', } ).setOrigin(0.5); } private drawGrid(): void { const g = this.gridGraphics; g.lineStyle(2, 0x6b7280); for (let i = 1; i < BOARD_SIZE; i++) { g.lineBetween( BOARD_OFFSET.x + i * CELL_SIZE, BOARD_OFFSET.y, BOARD_OFFSET.x + i * CELL_SIZE, BOARD_OFFSET.y + BOARD_SIZE * CELL_SIZE, ); g.lineBetween( BOARD_OFFSET.x, BOARD_OFFSET.y + i * CELL_SIZE, BOARD_OFFSET.x + BOARD_SIZE * CELL_SIZE, BOARD_OFFSET.y + i * CELL_SIZE, ); } g.strokePath(); this.scene.add.text(BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2, BOARD_OFFSET.y - 50, 'Boop Game', { fontSize: '32px', fontFamily: 'Arial', color: '#1f2937', }).setOrigin(0.5); } updateTurnText(player: PlayerType, state: BoopState): void { const current = player === 'white' ? state.players.white : state.players.black; const catsAvailable = current.catPool.remaining() + current.graduatedCount; this.turnText.setText( `${player.toUpperCase()}'s turn | Kittens: ${current.kittenPool.remaining()} | Cats: ${catsAvailable}` ); } setupInput( state: ReadonlySignal, onCellClick: (row: number, col: number) => void ): void { for (let row = 0; row < BOARD_SIZE; row++) { for (let col = 0; col < BOARD_SIZE; col++) { const x = BOARD_OFFSET.x + col * CELL_SIZE + CELL_SIZE / 2; const y = BOARD_OFFSET.y + row * CELL_SIZE + CELL_SIZE / 2; const zone = this.scene.add.zone(x, y, CELL_SIZE, CELL_SIZE).setInteractive(); zone.on('pointerdown', () => { const isOccupied = !!state.value.board.partMap[`${row},${col}`]; if (!isOccupied && !state.value.winner) { onCellClick(row, col); } }); } } } destroy(): void { this.container.destroy(); this.gridGraphics.destroy(); this.turnText.destroy(); this.infoText.destroy(); } }