import { BOARD_SIZE, BoopState, BoopPart, PieceType, PlayerType, WinnerType, WIN_LENGTH, MAX_PIECES_PER_PLAYER, BoopGame, prompts, } from "./data"; import {createGameCommandRegistry, Command, moveToRegion} from "boardgame-core"; import { findPartAtPosition, findPartInRegion, getLineCandidates, getNeighborPositions, isCellOccupied, isInBounds } from "./utils"; export const registry = createGameCommandRegistry(); /** * 放置棋子到棋盘 */ async function handlePlace(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(`${player} 的 supply 中没有可用的 ${type}`); } const partId = part.id; await game.produceAsync((state: BoopState) => { // 将棋子从supply移动到棋盘 const part = state.pieces[partId]; moveToRegion(part, state.regions[player], state.regions.board, [row, col]); }); return { row, col, player, type, partId }; } const place = registry.register( 'place ', handlePlace); /** * 执行boop - 推动周围棋子 */ async function handleBoop(game: BoopGame, row: number, col: number, type: PieceType) { const booped: string[] = []; const toRemove = new Set(); await game.produceAsync((state: BoopState) => { // 按照远离放置位置的方向推动 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 toRemove.add(part.id); booped.push(part.id); moveToRegion(part, state.regions.board, state.regions.board, [newRow, newCol]); } else if (!isCellOccupied(state, newRow, newCol)) { // 新位置为空,移动过去 booped.push(part.id); moveToRegion(part, state.regions.board, state.regions.board, [newRow, newCol]); } // 如果新位置被占用,则不移动(两个棋子都保持原位) } }); await game.produceAsync((state: BoopState) => { // 移除被吃掉的棋子 for (const partId of toRemove) { const part = state.pieces[partId]; moveToRegion(part, state.regions.board, state.regions[part.player]); } }); return { booped }; } const boop = registry.register('boop ', handleBoop); /** * 检查是否有玩家获胜(三个猫连线) */ async function handleCheckWin(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 checkWin = registry.register('check-win', handleCheckWin); /** * 检查并执行小猫升级(三个小猫连线变成猫) */ async function handleCheckGraduates(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: BoopState) => { 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 checkGraduates = registry.register('check-graduates', handleCheckGraduates); export async function start(game: BoopGame) { while (true) { const currentPlayer = game.value.currentPlayer; const { winner } = await turn(game, currentPlayer); await game.produceAsync((state: BoopState) => { state.winner = winner; if (!state.winner) { state.currentPlayer = state.currentPlayer === 'white' ? 'black' : 'white'; } }); if (game.value.winner) break; } return game.value; } async function handleCheckFullBoard(game: BoopGame, turnPlayer: PlayerType){ // 检查8-piece规则: 如果玩家所有8个棋子都在棋盘上且没有获胜,强制升级一个小猫 const playerPieces = Object.values(game.value.pieces).filter( (p: BoopPart) => p.player === turnPlayer && p.regionId === 'board' ); if(playerPieces.length < MAX_PIECES_PER_PLAYER || game.value.winner !== null){ return; } const partId = await game.prompt( prompts.graduate, (player, row, col) => { if (player !== turnPlayer) { throw `无效的玩家: ${player},期望的是 ${turnPlayer}。`; } if (!isInBounds(row, col)) { throw `无效的位置: (${row}, ${col}),必须在 0 到 ${BOARD_SIZE - 1} 之间。`; } const part = findPartAtPosition(game, row, col); if (!part || part.player !== turnPlayer) { throw `(${row}, ${col}) 位置没有 ${player} 的棋子。`; } return part.id; } ); await game.produceAsync((state: BoopState) => { const part = state.pieces[partId]; moveToRegion(part, state.regions.board, null); const cat = findPartInRegion(state, '', 'cat'); moveToRegion(cat || part, null, state.regions[turnPlayer]); }); } const checkFullBoard = registry.register('check-full-board', handleCheckFullBoard); async function handleTurn(game: BoopGame, turnPlayer: PlayerType) { const {row, col, type} = await game.prompt( prompts.play, (player, row, col, type?) => { const pieceType = type === 'cat' ? 'cat' : 'kitten'; if (player !== turnPlayer) { throw `无效的玩家: ${player},期望的是 ${turnPlayer}。`; } if (!isInBounds(row, col)) { throw `无效的位置: (${row}, ${col}),必须在 0 到 ${BOARD_SIZE - 1} 之间。`; } if (isCellOccupied(game, row, col)) { throw `单元格 (${row}, ${col}) 已被占用。`; } const found = findPartInRegion(game, player, pieceType); if (!found) { throw `${player} 的 supply 中没有 ${pieceType === 'cat' ? '大猫' : '小猫'} 了。`; } return {player, row,col,type}; }, game.value.currentPlayer ); const pieceType = type === 'cat' ? 'cat' : 'kitten'; await place(game, row, col, turnPlayer, pieceType); await boop(game, row, col, pieceType); const winner = await checkWin(game); if(winner) return { winner: winner as WinnerType }; await checkGraduates(game); await handleCheckFullBoard(game, turnPlayer); return { winner: null }; } const turn = registry.register('turn ', handleTurn);