import { BOARD_SIZE, BoopState, PieceType, PlayerType, WinnerType, WIN_LENGTH, MAX_PIECES_PER_PLAYER, BoopGame } from "./data"; import {createGameCommandRegistry} from "@/core/game"; import {moveToRegion} from "@/core/region"; import { findPartAtPosition, findPartInRegion, getLineCandidates, getNeighborPositions, isCellOccupied, isInBounds } from "./utils"; export const registry = createGameCommandRegistry(); /** * 放置棋子到棋盘 */ async function place(game: BoopGame, row: number, col: number, player: PlayerType, type: PieceType) { const value = game.value; // 从玩家supply中找到对应类型的棋子 const part = findPartInRegion(game, player, type); if (!part) { throw new Error(`No ${type} available in ${player}'s supply`); } const partId = part.id; await game.produceAsync(state => { // 将棋子从supply移动到棋盘 const part = state.pieces[partId]; moveToRegion(part, state.regions[player], state.regions.board, [row, col]); }); return { row, col, player, type, partId }; } const placeCommand = registry.register( 'place ', place); /** * 执行boop - 推动周围棋子 */ async function boop(game: BoopGame, row: number, col: number, type: PieceType) { const booped: string[] = []; await game.produceAsync(state => { // 按照远离放置位置的方向推动 for (const [dr, dc] of getNeighborPositions()) { const nr = row + dr; const nc = col + dc; if (!isInBounds(nr, nc)) continue; // 从 state 中查找,而不是 game const part = findPartAtPosition(state, nr, nc); if (!part) continue; // 小猫不能推动猫 if (type === 'kitten' && part.type === 'cat') continue; // 计算推动后的位置 const newRow = nr + dr; const newCol = nc + dc; // 检查新位置是否为空或在棋盘外 if (!isInBounds(newRow, newCol)) { // 棋子被推出棋盘,返回玩家supply booped.push(part.id); moveToRegion(part, state.regions.board, state.regions[part.player]); } else if (!isCellOccupied(state, newRow, newCol)) { // 新位置为空,移动过去 booped.push(part.id); moveToRegion(part, state.regions.board, state.regions.board, [newRow, newCol]); } // 如果新位置被占用,则不移动(两个棋子都保持原位) } }); return { booped }; } const boopCommand = registry.register('boop ', boop); /** * 检查是否有玩家获胜(三个猫连线) */ async function checkWin(game: BoopGame) { for(const line of getLineCandidates()){ let whites = 0; let blacks = 0; for(const [row, col] of line){ const part = findPartAtPosition(game, row, col); if(part?.type !== 'cat') continue; if (part.player === 'white') whites++; else blacks++; } if(whites >= WIN_LENGTH) { return 'white'; } if(blacks >= WIN_LENGTH) { return 'black'; } } return null; } const checkWinCommand = registry.register('check-win', checkWin); /** * 检查并执行小猫升级(三个小猫连线变成猫) */ async function checkGraduates(game: BoopGame){ const toUpgrade = new Set(); for(const line of getLineCandidates()){ let whites = 0; let blacks = 0; for(const [row, col] of line){ const part = findPartAtPosition(game, row, col); if (part?.player === 'white') whites++; else if(part?.player === 'black') blacks++; } const player = whites >= WIN_LENGTH ? 'white' : blacks >= WIN_LENGTH ? 'black' : null; if(!player) continue; for(const [row, col] of line){ const part = findPartAtPosition(game, row, col); part && toUpgrade.add(part.id); } } await game.produceAsync(state => { for(const partId of toUpgrade){ const part = state.pieces[partId]; const [row, col] = part.position; const player = part.player; moveToRegion(part, state.regions.board, null); const newPart = findPartInRegion(state, '', 'cat', player); moveToRegion(newPart || part, null, state.regions[player], [row, col]); } }); } const checkGraduatesCommand = registry.register('check-graduates', checkGraduates); async function setup(game: BoopGame) { while (true) { const currentPlayer = game.value.currentPlayer; const turnOutput = await turnCommand(game, currentPlayer); if (!turnOutput.success) throw new Error(turnOutput.error); await game.produceAsync(state => { state.winner = turnOutput.result.winner; if (!state.winner) { state.currentPlayer = state.currentPlayer === 'white' ? 'black' : 'white'; } }); if (game.value.winner) break; } return game.value; } registry.register('setup', setup); async function checkFullBoard(game: BoopGame, turnPlayer: PlayerType){ // 检查8-piece规则: 如果玩家所有8个棋子都在棋盘上且没有获胜,强制升级一个小猫 const playerPieces = Object.values(game.value.pieces).filter( p => p.player === turnPlayer && p.regionId === 'board' ); if(playerPieces.length < MAX_PIECES_PER_PLAYER || game.value.winner !== null){ return; } const partId = await game.prompt( 'choose ', (command) => { const [player, row, col] = command.params as [PlayerType, number, number]; if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } if (!isInBounds(row, col)) { throw `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`; } const part = findPartAtPosition(game, row, col); if (!part || part.player !== turnPlayer) { throw `No ${player} piece at (${row}, ${col}).`; } return part.id; } ); await game.produceAsync(state => { const part = state.pieces[partId]; moveToRegion(part, state.regions.board, null); const cat = findPartInRegion(state, '', 'cat'); moveToRegion(cat || part, null, state.regions[turnPlayer]); }); } async function turn(game: BoopGame, turnPlayer: PlayerType) { const {row, col, type} = await game.prompt( 'play [type:string]', (command) => { const [player, row, col, type] = command.params as [PlayerType, number, number, PieceType?]; const pieceType = type === 'cat' ? 'cat' : 'kitten'; if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } if (!isInBounds(row, col)) { throw `Invalid position: (${row}, ${col}). Must be between 0 and ${BOARD_SIZE - 1}.`; } if (isCellOccupied(game, row, col)) { throw `Cell (${row}, ${col}) is already occupied.`; } const found = findPartInRegion(game, player, pieceType); if (!found) { throw `No ${pieceType}s left in ${player}'s supply.`; } return {player, row,col,type}; }, game.value.currentPlayer ); const pieceType = type === 'cat' ? 'cat' : 'kitten'; await placeCommand(game, row, col, turnPlayer, pieceType); await boopCommand(game, row, col, pieceType); const winner = await checkWinCommand(game); if(winner.success) return { winner: winner.result as WinnerType }; await checkGraduatesCommand(game); await checkFullBoard(game, turnPlayer); return { winner: null }; } const turnCommand = registry.register('turn ', turn);