feat: error reporting

This commit is contained in:
hypercross 2026-04-05 00:30:21 +08:00
parent c678974489
commit 2f0fb2bca8
4 changed files with 23 additions and 60 deletions

View File

@ -30,7 +30,7 @@ async function place(game: BoopGame, row: number, col: number, player: PlayerTyp
const part = findPartInRegion(game, player, type); const part = findPartInRegion(game, player, type);
if (!part) { if (!part) {
throw new Error(`No ${type} available in ${player}'s supply`); throw new Error(`${player} 的 supply 中没有可用的 ${type}`);
} }
const partId = part.id; const partId = part.id;
@ -180,15 +180,15 @@ async function checkFullBoard(game: BoopGame, turnPlayer: PlayerType){
(command: Command) => { (command: Command) => {
const [player, row, col] = command.params as [PlayerType, number, number]; const [player, row, col] = command.params as [PlayerType, number, number];
if (player !== turnPlayer) { if (player !== turnPlayer) {
throw `Invalid player: ${player}. Expected ${turnPlayer}.`; throw `无效的玩家: ${player},期望的是 ${turnPlayer}`;
} }
if (!isInBounds(row, col)) { 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); const part = findPartAtPosition(game, row, col);
if (!part || part.player !== turnPlayer) { if (!part || part.player !== turnPlayer) {
throw `No ${player} piece at (${row}, ${col}).`; throw `(${row}, ${col}) 位置没有 ${player} 的棋子。`;
} }
return part.id; return part.id;
@ -211,18 +211,18 @@ async function turn(game: BoopGame, turnPlayer: PlayerType) {
const pieceType = type === 'cat' ? 'cat' : 'kitten'; const pieceType = type === 'cat' ? 'cat' : 'kitten';
if (player !== turnPlayer) { if (player !== turnPlayer) {
throw `Invalid player: ${player}. Expected ${turnPlayer}.`; throw `无效的玩家: ${player},期望的是 ${turnPlayer}`;
} }
if (!isInBounds(row, col)) { 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)) { if (isCellOccupied(game, row, col)) {
throw `Cell (${row}, ${col}) is already occupied.`; throw `单元格 (${row}, ${col}) 已被占用。`;
} }
const found = findPartInRegion(game, player, pieceType); const found = findPartInRegion(game, player, pieceType);
if (!found) { if (!found) {
throw `No ${pieceType}s left in ${player}'s supply.`; throw `${player} 的 supply 中没有 ${pieceType === 'cat' ? '大猫' : '小猫'} 了。`;
} }
return {player, row,col,type}; return {player, row,col,type};
}, },

View File

@ -32,7 +32,7 @@ export class BoardRenderer {
this.infoText = this.scene.add.text( this.infoText = this.scene.add.text(
BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2, BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2,
BOARD_OFFSET.y + BOARD_SIZE * CELL_SIZE + 60, BOARD_OFFSET.y + BOARD_SIZE * CELL_SIZE + 60,
'Click to place kitten. Cats win with 3 in a row!', '点击空白处放置小猫,三个大猫连线获胜!',
{ {
fontSize: '16px', fontSize: '16px',
fontFamily: 'Arial', fontFamily: 'Arial',
@ -91,31 +91,6 @@ export class BoardRenderer {
setupInput( setupInput(
getState: () => BoopState, getState: () => BoopState,
onCellClick: (row: number, col: number) => void, 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, onPieceClick: (row: number, col: number) => void,
checkWinner: () => boolean checkWinner: () => boolean
): void { ): void {
@ -129,22 +104,18 @@ export class BoardRenderer {
zone.on('pointerdown', () => { zone.on('pointerdown', () => {
const state = getState(); const state = getState();
const isOccupied = !!state.regions.board.partMap[`${row},${col}`]; const isOccupied = !!state.regions.board.partMap[`${row},${col}`];
if (isOccupied && !checkWinner()) { if (checkWinner()) return;
if (isOccupied) {
onPieceClick(row, col); onPieceClick(row, col);
} else {
onCellClick(row, col);
} }
}); });
} }
} }
} }
/**
*
*/
updatePieceInteraction(enabled: boolean): void {
// 可以通过此方法启用/禁用棋子点击
// 暂时通过重新设置 zone 的 interactive 状态来实现
}
destroy(): void { destroy(): void {
this.container.destroy(); this.container.destroy();
this.gridGraphics.destroy(); this.gridGraphics.destroy();

View File

@ -36,30 +36,28 @@ export class ErrorOverlay {
this.overlay.add(bg); this.overlay.add(bg);
// 错误提示框 // 错误提示框(居中显示)
const errorBoxY = BOARD_OFFSET.y + (BOARD_SIZE * CELL_SIZE) / 2 - 60; const centerX = BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2;
const errorBox = this.scene.add.container( const centerY = BOARD_OFFSET.y + (BOARD_SIZE * CELL_SIZE) / 2;
BOARD_OFFSET.x + (BOARD_SIZE * CELL_SIZE) / 2, const errorBox = this.scene.add.container(centerX, centerY);
errorBoxY
);
// 错误框背景 // 错误框背景
const boxBg = this.scene.add.rectangle(0, 0, 450, 100, 0xef4444, 0.95) const boxBg = this.scene.add.rectangle(0, 0, 450, 100, 0xef4444, 0.95)
.setStrokeStyle(4, 0xdc2626); .setStrokeStyle(4, 0xdc2626);
// 错误图标 // 错误图标
const iconText = this.scene.add.text(-180, 0, '❌', { const iconText = this.scene.add.text(-140, 0, '❌', {
fontSize: '36px', fontSize: '36px',
}).setOrigin(0.5); }).setOrigin(0.5);
// 错误文本 // 错误文本
const errorText = this.scene.add.text(30, 0, message, { const errorText = this.scene.add.text(60, 0, message, {
fontSize: '22px', fontSize: '22px',
fontFamily: 'Arial', fontFamily: 'Arial',
color: '#ffffff', color: '#ffffff',
align: 'center', align: 'center',
wordWrap: { width: 380 }, wordWrap: { width: 300 },
}).setOrigin(0, 0.5); }).setOrigin(0.5);
errorBox.add([boxBg, iconText, errorText]); errorBox.add([boxBg, iconText, errorText]);
this.overlay.add(errorBox); this.overlay.add(errorBox);

View File

@ -35,16 +35,10 @@ export class GameScene extends GameHostScene<BoopState> {
// 设置棋子生成器 // 设置棋子生成器
this.disposables.add(createPieceSpawner(this)); this.disposables.add(createPieceSpawner(this));
// 设置输入处理 // 设置输入处理(空单元格和已有棋子统一处理)
this.boardRenderer.setupInput( this.boardRenderer.setupInput(
() => this.state, () => this.state,
(row, col) => this.handleCellClick(row, col), (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), (row, col) => this.handlePieceClick(row, col),
() => this.gameHost.status.value !== 'running' || !!this.state.winner () => this.gameHost.status.value !== 'running' || !!this.state.winner
); );