From 2f0fb2bca844350edbae3b0477935aee3d47198d Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 5 Apr 2026 00:30:21 +0800 Subject: [PATCH] feat: error reporting --- packages/boop-game/src/game/commands.ts | 16 ++++---- .../boop-game/src/scenes/BoardRenderer.ts | 41 +++---------------- packages/boop-game/src/scenes/ErrorOverlay.ts | 18 ++++---- packages/boop-game/src/scenes/GameScene.ts | 8 +--- 4 files changed, 23 insertions(+), 60 deletions(-) diff --git a/packages/boop-game/src/game/commands.ts b/packages/boop-game/src/game/commands.ts index 89d4e02..7a5df4b 100644 --- a/packages/boop-game/src/game/commands.ts +++ b/packages/boop-game/src/game/commands.ts @@ -30,7 +30,7 @@ async function place(game: BoopGame, row: number, col: number, player: PlayerTyp const part = findPartInRegion(game, player, type); if (!part) { - throw new Error(`No ${type} available in ${player}'s supply`); + throw new Error(`${player} 的 supply 中没有可用的 ${type}`); } const partId = part.id; @@ -180,15 +180,15 @@ async function checkFullBoard(game: BoopGame, turnPlayer: PlayerType){ (command: Command) => { const [player, row, col] = command.params as [PlayerType, number, number]; if (player !== turnPlayer) { - throw `Invalid player: ${player}. Expected ${turnPlayer}.`; + throw `无效的玩家: ${player},期望的是 ${turnPlayer}。`; } if (!isInBounds(row, col)) { - throw `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`; + throw `无效的位置: (${row}, ${col}),必须在 0 到 ${BOARD_SIZE - 1} 之间。`; } const part = findPartAtPosition(game, row, col); if (!part || part.player !== turnPlayer) { - throw `No ${player} piece at (${row}, ${col}).`; + throw `(${row}, ${col}) 位置没有 ${player} 的棋子。`; } return part.id; @@ -211,18 +211,18 @@ async function turn(game: BoopGame, turnPlayer: PlayerType) { const pieceType = type === 'cat' ? 'cat' : 'kitten'; if (player !== turnPlayer) { - throw `Invalid player: ${player}. Expected ${turnPlayer}.`; + throw `无效的玩家: ${player},期望的是 ${turnPlayer}。`; } if (!isInBounds(row, col)) { - throw `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`; + throw `无效的位置: (${row}, ${col}),必须在 0 到 ${BOARD_SIZE - 1} 之间。`; } if (isCellOccupied(game, row, col)) { - throw `Cell (${row}, ${col}) is already occupied.`; + throw `单元格 (${row}, ${col}) 已被占用。`; } const found = findPartInRegion(game, player, pieceType); if (!found) { - throw `No ${pieceType}s left in ${player}'s supply.`; + throw `${player} 的 supply 中没有 ${pieceType === 'cat' ? '大猫' : '小猫'} 了。`; } return {player, row,col,type}; }, diff --git a/packages/boop-game/src/scenes/BoardRenderer.ts b/packages/boop-game/src/scenes/BoardRenderer.ts index ba06587..6efb793 100644 --- a/packages/boop-game/src/scenes/BoardRenderer.ts +++ b/packages/boop-game/src/scenes/BoardRenderer.ts @@ -32,7 +32,7 @@ export class BoardRenderer { 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', @@ -91,31 +91,6 @@ export class BoardRenderer { 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 { @@ -129,22 +104,18 @@ export class BoardRenderer { zone.on('pointerdown', () => { const state = getState(); const isOccupied = !!state.regions.board.partMap[`${row},${col}`]; - if (isOccupied && !checkWinner()) { + if (checkWinner()) return; + + if (isOccupied) { onPieceClick(row, col); + } else { + onCellClick(row, col); } }); } } } - /** - * 更新棋盘上棋子的交互状态 - */ - updatePieceInteraction(enabled: boolean): void { - // 可以通过此方法启用/禁用棋子点击 - // 暂时通过重新设置 zone 的 interactive 状态来实现 - } - destroy(): void { this.container.destroy(); this.gridGraphics.destroy(); diff --git a/packages/boop-game/src/scenes/ErrorOverlay.ts b/packages/boop-game/src/scenes/ErrorOverlay.ts index 064fca6..ea62e3e 100644 --- a/packages/boop-game/src/scenes/ErrorOverlay.ts +++ b/packages/boop-game/src/scenes/ErrorOverlay.ts @@ -36,30 +36,28 @@ export class ErrorOverlay { this.overlay.add(bg); - // 错误提示框 - const errorBoxY = BOARD_OFFSET.y + (BOARD_SIZE * CELL_SIZE) / 2 - 60; - const errorBox = this.scene.add.container( - BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2, - errorBoxY - ); + // 错误提示框(居中显示) + const centerX = BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2; + const centerY = BOARD_OFFSET.y + (BOARD_SIZE * CELL_SIZE) / 2; + const errorBox = this.scene.add.container(centerX, centerY); // 错误框背景 const boxBg = this.scene.add.rectangle(0, 0, 450, 100, 0xef4444, 0.95) .setStrokeStyle(4, 0xdc2626); // 错误图标 - const iconText = this.scene.add.text(-180, 0, '❌', { + const iconText = this.scene.add.text(-140, 0, '❌', { fontSize: '36px', }).setOrigin(0.5); // 错误文本 - const errorText = this.scene.add.text(30, 0, message, { + const errorText = this.scene.add.text(60, 0, message, { fontSize: '22px', fontFamily: 'Arial', color: '#ffffff', align: 'center', - wordWrap: { width: 380 }, - }).setOrigin(0, 0.5); + wordWrap: { width: 300 }, + }).setOrigin(0.5); errorBox.add([boxBg, iconText, errorText]); this.overlay.add(errorBox); diff --git a/packages/boop-game/src/scenes/GameScene.ts b/packages/boop-game/src/scenes/GameScene.ts index 71b9f95..58a9b1f 100644 --- a/packages/boop-game/src/scenes/GameScene.ts +++ b/packages/boop-game/src/scenes/GameScene.ts @@ -35,16 +35,10 @@ export class GameScene extends GameHostScene { // 设置棋子生成器 this.disposables.add(createPieceSpawner(this)); - // 设置输入处理 + // 设置输入处理(空单元格和已有棋子统一处理) this.boardRenderer.setupInput( () => this.state, (row, col) => this.handleCellClick(row, col), - () => this.gameHost.status.value !== 'running' || !!this.state.winner - ); - - // 设置棋子点击处理(用于棋盘满时选择要升级的棋子) - this.boardRenderer.setupPieceInput( - () => this.state, (row, col) => this.handlePieceClick(row, col), () => this.gameHost.status.value !== 'running' || !!this.state.winner );