boardgame-phaser/packages/boop-game/src/scenes/BoardRenderer.ts

155 lines
4.7 KiB
TypeScript
Raw Normal View History

2026-04-04 16:25:36 +08:00
import Phaser from 'phaser';
2026-04-04 23:25:43 +08:00
import type { BoopState, PlayerType, BoopPart } from '@/game';
2026-04-04 16:25:36 +08:00
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);
}
2026-04-04 23:25:43 +08:00
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 };
}
2026-04-04 16:25:36 +08:00
updateTurnText(player: PlayerType, state: BoopState): void {
2026-04-04 23:25:43 +08:00
const { kittensInSupply, catsInSupply } = this.countPieces(state, player);
2026-04-04 16:25:36 +08:00
this.turnText.setText(
2026-04-04 23:25:43 +08:00
`${player.toUpperCase()}'s turn | Kittens: ${kittensInSupply} | Cats: ${catsInSupply}`
2026-04-04 16:25:36 +08:00
);
}
setupInput(
2026-04-04 23:25:43 +08:00
getState: () => BoopState,
onCellClick: (row: number, col: number) => void,
checkWinner: () => boolean
2026-04-04 16:25:36 +08:00
): 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', () => {
2026-04-04 23:25:43 +08:00
const state = getState();
const isOccupied = !!state.regions.board.partMap[`${row},${col}`];
if (!isOccupied && !checkWinner()) {
2026-04-04 16:25:36 +08:00
onCellClick(row, col);
}
});
}
}
}
2026-04-05 00:24:06 +08:00
/**
*
*/
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 状态来实现
}
2026-04-04 16:25:36 +08:00
destroy(): void {
this.container.destroy();
this.gridGraphics.destroy();
this.turnText.destroy();
this.infoText.destroy();
}
}