import type { Rule, RuleResult, RuleContext } from '../../rules/Rule'; import { createValidationRule, createEffectRule, createTriggerRule } from '../../rules/Rule'; import type { Player, TicTacToeMetadata, MoveRecord } from './TicTacToeState'; import { getWinningCombinations, parseCellId } from './TicTacToeState'; /** * 井字棋游戏规则集合 */ /** * 获取当前玩家 */ function getCurrentPlayer(gameState: any): Player { const metadata = gameState.data.value.metadata as Record | undefined; const ticTacToeMetadata = metadata?.ticTacToe as TicTacToeMetadata | undefined; return ticTacToeMetadata?.currentPlayer || 'X'; } /** * 检查游戏是否结束 */ function isGameEnded(gameState: any): boolean { const metadata = gameState.data.value.metadata as Record | undefined; const ticTacToeMetadata = metadata?.ticTacToe as TicTacToeMetadata | undefined; return ticTacToeMetadata?.gameEnded || false; } /** * 更新游戏 metadata */ function updateGameMetadata(gameState: any, updates: Partial): void { const metadata = gameState.data.value.metadata as Record | undefined; const currentTicTacToe = (metadata?.ticTacToe as TicTacToeMetadata) || { currentPlayer: 'X', gameEnded: false, winner: null, moveHistory: [], totalMoves: 0, }; gameState.data.value = { ...gameState.data.value, metadata: { ...metadata, ticTacToe: { ...currentTicTacToe, ...updates }, }, }; } /** * 获取单元格的玩家标记 */ function getCellPlayer( context: RuleContext, cellId: string ): Player | null { const placement = context.gameState.placements.value.get(cellId); if (!placement?.metadata?.player) { return null; } return placement.metadata.player as Player; } /** * 检查是否有玩家获胜 */ function checkWin( context: RuleContext, size: number = 3 ): { winner: Player; combination: string[] } | null { const combinations = getWinningCombinations(size); for (const combination of combinations) { const players = combination.map((cellId) => getCellPlayer(context, cellId)); const firstPlayer = players[0]; if (firstPlayer && players.every((p) => p === firstPlayer)) { return { winner: firstPlayer, combination }; } } return null; } /** * 检查是否平局(所有单元格都被填充且无获胜者) */ function isDraw(context: RuleContext, size: number = 3): boolean { const totalCells = size * size; const filledCells = Array.from(context.gameState.placements.value.values()).filter( (p) => p.metadata?.player ).length; return filledCells === totalCells; } /** * 规则 1:验证轮到当前玩家 * 在玩家尝试下子时检查是否是他们的回合 */ export const validateTurnRule = createValidationRule({ id: 'tictactoe-validate-turn', name: 'Validate Turn', description: 'Check if it is the current player turn', priority: 1, gameType: 'tictactoe', applicableCommands: ['markCell'], validate: async (context: RuleContext): Promise => { const cellId = context.command.steps[0]?.params?.cell as string; const expectedPlayer = context.command.steps[0]?.params?.player as Player; const currentPlayer = getCurrentPlayer(context.gameState); if (!cellId) { return { success: false, error: 'Cell ID is required', }; } if (expectedPlayer && expectedPlayer !== currentPlayer) { return { success: false, error: `It is ${currentPlayer}'s turn, not ${expectedPlayer}'s`, }; } return { success: true }; }, }); /** * 规则 2:验证单元格为空 * 检查目标单元格是否已经被占用 */ export const validateCellEmptyRule = createValidationRule({ id: 'tictactoe-validate-cell-empty', name: 'Validate Cell Empty', description: 'Check if the target cell is empty', priority: 2, gameType: 'tictactoe', applicableCommands: ['markCell'], validate: async (context: RuleContext): Promise => { const cellId = context.command.steps[0]?.params?.cell as string; if (!cellId) { return { success: false, error: 'Cell ID is required', }; } const cellPlayer = getCellPlayer(context, cellId); if (cellPlayer !== null) { return { success: false, error: `Cell ${cellId} is already occupied by ${cellPlayer}`, }; } return { success: true }; }, }); /** * 规则 3:验证游戏未结束 * 游戏结束后不允许继续下子 */ export const validateGameNotEndedRule = createValidationRule({ id: 'tictactoe-validate-game-not-ended', name: 'Validate Game Not Ended', description: 'Check if the game has already ended', priority: 0, gameType: 'tictactoe', applicableCommands: ['markCell'], validate: async (context: RuleContext): Promise => { const gameEnded = isGameEnded(context.gameState); if (gameEnded) { return { success: false, error: 'Game has already ended', blockCommand: true, }; } return { success: true }; }, }); /** * 效果规则:切换玩家 * 在玩家下子后自动切换到下一个玩家 */ export const switchTurnRule = createEffectRule({ id: 'tictactoe-switch-turn', name: 'Switch Turn', description: 'Switch to the next player after a move', priority: 10, gameType: 'tictactoe', applicableCommands: ['markCell'], apply: async (context: RuleContext): Promise => { const currentPlayer = getCurrentPlayer(context.gameState); const nextPlayer: Player = currentPlayer === 'X' ? 'O' : 'X'; // 直接更新 metadata updateGameMetadata(context.gameState, { currentPlayer: nextPlayer }); return { success: true, }; }, }); /** * 效果规则:记录移动历史 * 记录玩家的每一步移动 */ export const recordMoveHistoryRule = createEffectRule({ id: 'tictactoe-record-history', name: 'Record Move History', description: 'Record the move in game history', priority: 9, gameType: 'tictactoe', applicableCommands: ['markCell'], apply: async (context: RuleContext): Promise => { const cellId = context.command.steps[0]?.params?.cell as string; const player = context.command.steps[0]?.params?.player as Player; const metadata = context.gameState.data.value.metadata || {}; const ticTacToeMetadata = (metadata?.ticTacToe as TicTacToeMetadata) || { currentPlayer: 'X', gameEnded: false, winner: null, moveHistory: [], totalMoves: 0, }; const moveRecord: MoveRecord = { player, cellId, timestamp: Date.now(), }; const moveHistory = ticTacToeMetadata.moveHistory || []; moveHistory.push(moveRecord); // 直接更新 metadata updateGameMetadata(context.gameState, { moveHistory, totalMoves: (ticTacToeMetadata.totalMoves || 0) + 1, }); return { success: true, }; }, }); /** * 触发规则:检查获胜条件 * 当有玩家连成一线时触发 */ export const checkWinConditionRule = createTriggerRule({ id: 'tictactoe-check-win', name: 'Check Win Condition', description: 'Check if a player has won the game', priority: 100, gameType: 'tictactoe', condition: async (context: RuleContext): Promise => { const winResult = checkWin(context); return winResult !== null; }, action: async (context: RuleContext): Promise => { const winResult = checkWin(context); if (!winResult) { return { success: false, error: 'No winner detected' }; } const { winner, combination } = winResult; // 直接更新 metadata updateGameMetadata(context.gameState, { gameEnded: true, winner, winningCombination: combination, }); return { success: true, }; }, }); /** * 触发规则:检查平局条件 * 当所有单元格都被填充且无获胜者时触发 */ export const checkDrawConditionRule = createTriggerRule({ id: 'tictactoe-check-draw', name: 'Check Draw Condition', description: 'Check if the game is a draw', priority: 101, gameType: 'tictactoe', condition: async (context: RuleContext): Promise => { const winResult = checkWin(context); if (winResult !== null) { return false; // 有获胜者,不是平局 } return isDraw(context); }, action: async (context: RuleContext): Promise => { // 直接更新 metadata updateGameMetadata(context.gameState, { gameEnded: true, winner: null, // null 表示平局 }); return { success: true, }; }, }); /** * 所有井字棋游戏规则 */ export const ticTacToeRules: Rule[] = [ validateTurnRule, validateCellEmptyRule, validateGameNotEndedRule, switchTurnRule, recordMoveHistoryRule, checkWinConditionRule, checkDrawConditionRule, ]; /** * 获取井字棋验证规则 */ export function getTicTacToeValidationRules(): Rule[] { return [validateTurnRule, validateCellEmptyRule, validateGameNotEndedRule]; } /** * 获取井字棋效果规则 */ export function getTicTacToeEffectRules(): Rule[] { return [switchTurnRule, recordMoveHistoryRule]; } /** * 获取井字棋触发规则 */ export function getTicTacToeTriggerRules(): Rule[] { return [checkWinConditionRule, checkDrawConditionRule]; }