import { OnitamaGame, OnitamaState, PlayerType, prompts } from "./types"; import {createGameCommandRegistry} from "@/core/game"; import {moveToRegion} from "@/core/region"; export const registry = createGameCommandRegistry(); /** * 检查位置是否在棋盘范围内 */ function isInBounds(x: number, y: number): boolean { return x >= 0 && x < 5 && y >= 0 && y < 5; } /** * 检查位置是否有棋子 */ function getPawnAtPosition(state: OnitamaState, x: number, y: number) { const key = `${x},${y}`; const pawnId = state.regions.board.partMap[key]; return pawnId ? state.pawns[pawnId] : null; } /** * 检查玩家是否拥有某张卡牌 */ function playerHasCard(state: OnitamaState, player: PlayerType, cardName: string): boolean { const cardList = player === 'red' ? state.redCards : state.blackCards; return cardList.includes(cardName); } /** * 检查移动是否合法 */ function isValidMove(state: OnitamaState, cardName: string, fromX: number, fromY: number, toX: number, toY: number, player: PlayerType): string | null { // 检查玩家是否拥有该卡牌 if (!playerHasCard(state, player, cardName)) { return `玩家 ${player} 不拥有卡牌 ${cardName}`; } // 检查起始位置是否有玩家的棋子 const fromPawn = getPawnAtPosition(state, fromX, fromY); if (!fromPawn) { return `位置 (${fromX}, ${fromY}) 没有棋子`; } if (fromPawn.owner !== player) { return `位置 (${fromX}, ${fromY}) 的棋子不属于玩家 ${player}`; } // 检查卡牌是否存在 const card = state.cards[cardName]; if (!card) { return `卡牌 ${cardName} 不存在`; } // 计算移动偏移量 const dx = toX - fromX; const dy = toY - fromY; // 检查移动是否在卡牌的移动候选项中 const isValid = card.moveCandidates.some(m => m.dx === dx && m.dy === dy); if (!isValid) { return `卡牌 ${cardName} 不支持移动 (${dx}, ${dy})`; } // 检查目标位置是否在棋盘内 if (!isInBounds(toX, toY)) { return `目标位置 (${toX}, ${toY}) 超出棋盘范围`; } // 检查目标位置是否有己方棋子 const toPawn = getPawnAtPosition(state, toX, toY); if (toPawn && toPawn.owner === player) { return `目标位置 (${toX}, ${toY}) 已有己方棋子`; } return null; } /** * 执行移动 */ async function handleMove(game: OnitamaGame, player: PlayerType, cardName: string, fromX: number, fromY: number, toX: number, toY: number) { const state = game.value; // 验证移动 const error = isValidMove(state, cardName, fromX, fromY, toX, toY, player); if (error) { throw new Error(error); } const capturedPawnId = getPawnAtPosition(state, toX, toY)?.id || null; await game.produceAsync(state => { const pawn = state.pawns[getPawnAtPosition(state, fromX, fromY)!.id]; // 如果目标位置有敌方棋子,将其移除(吃掉) if (capturedPawnId) { const capturedPawn = state.pawns[capturedPawnId]; moveToRegion(capturedPawn, state.regions.board, null); } // 移动棋子到目标位置 moveToRegion(pawn, state.regions.board, state.regions.board, [toX, toY]); }); // 交换卡牌 await handleSwapCard(game, player, cardName); return { from: { x: fromX, y: fromY }, to: { x: toX, y: toY }, card: cardName, captured: capturedPawnId }; } const move = registry.register({ schema: 'move ', run: handleMove }); /** * 交换卡牌:将使用的卡牌与备用卡牌交换 */ async function handleSwapCard(game: OnitamaGame, player: PlayerType, usedCard: string) { await game.produceAsync(state => { const spareCard = state.spareCard; const usedCardData = state.cards[usedCard]; const spareCardData = state.cards[spareCard]; // 从玩家手牌中移除使用的卡牌 if (player === 'red') { state.redCards = state.redCards.filter(c => c !== usedCard); state.redCards.push(spareCard); } else { state.blackCards = state.blackCards.filter(c => c !== usedCard); state.blackCards.push(spareCard); } // 更新卡牌区域 usedCardData.regionId = 'spare'; spareCardData.regionId = player; // 更新备用卡牌 state.spareCard = usedCard; }); } const swapCard = registry.register({ schema: 'swap-card ', run: handleSwapCard }); /** * 检查占领胜利条件:玩家的师父棋子到达对手的初始位置 */ async function handleCheckConquestWin(game: OnitamaGame): Promise { const state = game.value; // 红色师父到达 y=4(黑色初始位置) const redMaster = state.pawns['red-master']; if (redMaster && redMaster.regionId === 'board' && redMaster.position[1] === 4) { return 'red'; } // 黑色师父到达 y=0(红色初始位置) const blackMaster = state.pawns['black-master']; if (blackMaster && blackMaster.regionId === 'board' && blackMaster.position[1] === 0) { return 'black'; } return null; } const checkConquestWin = registry.register({ schema: 'check-conquest-win', run: handleCheckConquestWin }); /** * 检查吃掉胜利条件:对手的师父棋子被吃掉(不在棋盘上) */ async function handleCheckCaptureWin(game: OnitamaGame): Promise { const state = game.value; // 红色师父不在棋盘上,黑色获胜 const redMaster = state.pawns['red-master']; if (!redMaster || redMaster.regionId !== 'board') { return 'black'; } // 黑色师父不在棋盘上,红色获胜 const blackMaster = state.pawns['black-master']; if (!blackMaster || blackMaster.regionId !== 'board') { return 'red'; } return null; } const checkCaptureWin = registry.register({ schema: 'check-capture-win', run: handleCheckCaptureWin }); /** * 综合胜利检测 */ async function handleCheckWin(game: OnitamaGame): Promise { const conquestWinner = await handleCheckConquestWin(game); if (conquestWinner) { return conquestWinner; } const captureWinner = await handleCheckCaptureWin(game); if (captureWinner) { return captureWinner; } return null; } const checkWin = registry.register({ schema: 'check-win', run: handleCheckWin }); /** * 获取玩家可用的移动 */ function getAvailableMoves(state: OnitamaState, player: PlayerType): Array<{card: string, fromX: number, fromY: number, toX: number, toY: number}> { const moves: Array<{card: string, fromX: number, fromY: number, toX: number, toY: number}> = []; // 获取玩家的所有卡牌 const cardNames = player === 'red' ? state.redCards : state.blackCards; // 获取玩家的所有棋子 const playerPawns = Object.values(state.pawns).filter(p => p.owner === player && p.regionId === 'board'); // 对于每张卡牌 for (const cardName of cardNames) { const card = state.cards[cardName]; // 对于每个棋子 for (const pawn of playerPawns) { const [fromX, fromY] = pawn.position; // 对于卡牌的每个移动 for (const move of card.moveCandidates) { const toX = fromX + move.dx; const toY = fromY + move.dy; // 检查移动是否合法 if (isInBounds(toX, toY)) { const targetPawn = getPawnAtPosition(state, toX, toY); // 目标位置为空或有敌方棋子 if (!targetPawn || targetPawn.owner !== player) { moves.push({ card: cardName, fromX, fromY, toX, toY }); } } } } } return moves; } /** * 处理回合 */ async function handleTurn(game: OnitamaGame, turnPlayer: PlayerType) { const state = game.value; const availableMoves = getAvailableMoves(state, turnPlayer); let moveOutput; if (availableMoves.length === 0) { // 没有可用移动,玩家必须交换一张卡牌 const cardToSwap = await game.prompt( prompts.move, (player, card, _fromX, _fromY, _toX, _toY) => { if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } if (!playerHasCard(state, player, card)) { throw `Player ${player} does not have card ${card}.`; } return card; }, turnPlayer ); await swapCard(game, turnPlayer, cardToSwap); moveOutput = { swappedCard: cardToSwap, noMoves: true }; } else { // 有可用移动,提示玩家选择 moveOutput = await game.prompt( prompts.move, (player, card, fromX, fromY, toX, toY) => { if (player !== turnPlayer) { throw `Invalid player: ${player}. Expected ${turnPlayer}.`; } const error = isValidMove(state, card, fromX, fromY, toX, toY, player); if (error) { throw error; } return { player, card, fromX, fromY, toX, toY }; }, turnPlayer ); await move(game, moveOutput.player, moveOutput.card, moveOutput.fromX, moveOutput.fromY, moveOutput.toX, moveOutput.toY); } // 检查胜利 const winner = await checkWin(game); await game.produceAsync(state => { state.winner = winner; if (!winner) { state.currentPlayer = state.currentPlayer === 'red' ? 'black' : 'red'; state.turn++; } }); return { winner, move: moveOutput }; } const turn = registry.register({ schema: 'turn ', run: handleTurn }); /** * 开始游戏主循环 */ export async function start(game: OnitamaGame) { while (true) { const currentPlayer = game.value.currentPlayer; const turnOutput = await turn(game, currentPlayer); if (turnOutput.winner) { break; } } return game.value; }