import Phaser from 'phaser'; import type { BoopState, PlayerType, BoopPart } from '@/game'; 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); } private countPieces(state: BoopState, player: PlayerType) { const pieces = Object.values(state.pieces); const playerPieces = pieces.filter((p: BoopPart) => p.player === player); const kittensInSupply = playerPieces.filter((p: BoopPart) => p.type === 'kitten' && p.regionId === player).length; const catsInSupply = playerPieces.filter((p: BoopPart) => p.type === 'cat' && p.regionId === player).length; const piecesOnBoard = playerPieces.filter((p: BoopPart) => p.regionId === 'board').length; return { kittensInSupply, catsInSupply, piecesOnBoard }; } updateTurnText(player: PlayerType, state: BoopState): void { const { kittensInSupply, catsInSupply } = this.countPieces(state, player); this.turnText.setText( `${player.toUpperCase()}'s turn | Kittens: ${kittensInSupply} | Cats: ${catsInSupply}` ); } setupInput( getState: () => BoopState, onCellClick: (row: number, col: number) => void, checkWinner: () => boolean ): 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 state = getState(); const isOccupied = !!state.regions.board.partMap[`${row},${col}`]; if (!isOccupied && !checkWinner()) { onCellClick(row, col); } }); } } } /** * 设置棋盘上棋子的点击处理(用于选择要升级的棋子) */ setupPieceInput( getState: () => BoopState, onPieceClick: (row: number, col: number) => void, checkWinner: () => boolean ): 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 state = getState(); const isOccupied = !!state.regions.board.partMap[`${row},${col}`]; if (isOccupied && !checkWinner()) { onPieceClick(row, col); } }); } } } /** * 更新棋盘上棋子的交互状态 */ updatePieceInteraction(enabled: boolean): void { // 可以通过此方法启用/禁用棋子点击 // 暂时通过重新设置 zone 的 interactive 状态来实现 } destroy(): void { this.container.destroy(); this.gridGraphics.destroy(); this.turnText.destroy(); this.infoText.destroy(); } }