# 游戏核心逻辑详设文档 > **文档版本**: v1.0 > **撰写人**: 游戏逻辑架构师 > **创建日期**: 2024年9月11日 ## 1. 游戏逻辑总览 ### 1.1 核心游戏机制 基于经典"打飞机"游戏规则,实现回合制战略对战: ```typescript // 游戏核心参数 const GAME_CONFIG = { BOARD_SIZE: 10, // 10x10棋盘 PLANE_COUNT: 3, // 每位玩家3架飞机 PLANE_SIZE: 11, // 每架飞机占11个格子 TURN_TIME_LIMIT: 30, // 每回合30秒限时 GAME_TIME_LIMIT: 1800 // 游戏总时长30分钟 } // 游戏阶段枚举 enum GamePhase { WAITING = 'waiting', // 等待玩家 PLACING = 'placing', // 飞机布置阶段 BATTLING = 'battling', // 对战阶段 FINISHED = 'finished' // 游戏结束 } // 攻击结果类型 enum AttackResult { MISS = 'miss', // 未命中 HIT = 'hit', // 命中 DESTROY = 'destroy' // 击毁飞机 } ``` ### 1.2 飞机几何模型 ```typescript // 飞机形状定义 interface PlaneShape { id: string center: Position // 飞机头部位置(中心点) direction: Direction // 飞机朝向 positions: Position[] // 飞机占据的所有位置 parts: { head: Position // 头部(1个格子) wings: Position[] // 翅膀(5个格子) body: Position[] // 机身(2个格子) tail: Position[] // 尾翼(3个格子) } } // 方向枚举 enum Direction { UP = 'up', DOWN = 'down', LEFT = 'left', RIGHT = 'right' } // 位置坐标 interface Position { x: number // 行坐标 (0-9) y: number // 列坐标 (0-9) } // 飞机几何生成器 export class PlaneGeometry { static generatePlane(center: Position, direction: Direction): PlaneShape { const plane: PlaneShape = { id: generateId(), center, direction, positions: [], parts: { head: center, wings: [], body: [], tail: [] } } switch (direction) { case Direction.UP: plane.parts = { head: center, wings: [ { x: center.x + 1, y: center.y - 2 }, { x: center.x + 1, y: center.y - 1 }, { x: center.x + 1, y: center.y }, { x: center.x + 1, y: center.y + 1 }, { x: center.x + 1, y: center.y + 2 } ], body: [ { x: center.x + 2, y: center.y }, { x: center.x + 3, y: center.y } ], tail: [ { x: center.x + 4, y: center.y - 1 }, { x: center.x + 4, y: center.y }, { x: center.x + 4, y: center.y + 1 } ] } break case Direction.DOWN: plane.parts = { head: center, wings: [ { x: center.x - 1, y: center.y - 2 }, { x: center.x - 1, y: center.y - 1 }, { x: center.x - 1, y: center.y }, { x: center.x - 1, y: center.y + 1 }, { x: center.x - 1, y: center.y + 2 } ], body: [ { x: center.x - 2, y: center.y }, { x: center.x - 3, y: center.y } ], tail: [ { x: center.x - 4, y: center.y - 1 }, { x: center.x - 4, y: center.y }, { x: center.x - 4, y: center.y + 1 } ] } break case Direction.LEFT: plane.parts = { head: center, wings: [ { x: center.x - 2, y: center.y + 1 }, { x: center.x - 1, y: center.y + 1 }, { x: center.x, y: center.y + 1 }, { x: center.x + 1, y: center.y + 1 }, { x: center.x + 2, y: center.y + 1 } ], body: [ { x: center.x, y: center.y + 2 }, { x: center.x, y: center.y + 3 } ], tail: [ { x: center.x - 1, y: center.y + 4 }, { x: center.x, y: center.y + 4 }, { x: center.x + 1, y: center.y + 4 } ] } break case Direction.RIGHT: plane.parts = { head: center, wings: [ { x: center.x - 2, y: center.y - 1 }, { x: center.x - 1, y: center.y - 1 }, { x: center.x, y: center.y - 1 }, { x: center.x + 1, y: center.y - 1 }, { x: center.x + 2, y: center.y - 1 } ], body: [ { x: center.x, y: center.y - 2 }, { x: center.x, y: center.y - 3 } ], tail: [ { x: center.x - 1, y: center.y - 4 }, { x: center.x, y: center.y - 4 }, { x: center.x + 1, y: center.y - 4 } ] } break } // 合并所有位置 plane.positions = [ plane.parts.head, ...plane.parts.wings, ...plane.parts.body, ...plane.parts.tail ] return plane } // 验证飞机位置是否合法 static validatePlanePosition(plane: PlaneShape, boardSize: number = 10): boolean { return plane.positions.every(pos => pos.x >= 0 && pos.x < boardSize && pos.y >= 0 && pos.y < boardSize ) } // 检查两架飞机是否重叠 static checkPlanesOverlap(plane1: PlaneShape, plane2: PlaneShape): boolean { return plane1.positions.some(pos1 => plane2.positions.some(pos2 => pos1.x === pos2.x && pos1.y === pos2.y ) ) } } ``` ## 2. 棋盘状态管理 ### 2.1 棋盘数据结构 ```typescript // 单元格状态 enum CellState { EMPTY = 'empty', // 空格 PLANE_PART = 'plane_part', // 飞机部件 ATTACKED_MISS = 'attacked_miss', // 攻击未命中 ATTACKED_HIT = 'attacked_hit' // 攻击命中 } // 棋盘单元格 interface BoardCell { position: Position state: CellState planeId?: string // 所属飞机ID partType?: 'head' | 'wing' | 'body' | 'tail' // 部件类型 isDestroyed?: boolean // 是否已被击毁 attackedAt?: Date // 攻击时间 } // 游戏棋盘 interface GameBoard { size: number // 棋盘大小 (10x10) cells: BoardCell[][] // 二维单元格数组 planes: PlaneShape[] // 放置的飞机 attackHistory: AttackRecord[] // 攻击历史 remainingPlanes: number // 剩余飞机数量 } // 攻击记录 interface AttackRecord { position: Position result: AttackResult timestamp: Date targetPlaneId?: string } ``` ### 2.2 棋盘操作类 ```typescript export class BoardManager { // 创建空棋盘 static createEmptyBoard(size: number = 10): GameBoard { const cells: BoardCell[][] = [] for (let x = 0; x < size; x++) { cells[x] = [] for (let y = 0; y < size; y++) { cells[x][y] = { position: { x, y }, state: CellState.EMPTY } } } return { size, cells, planes: [], attackHistory: [], remainingPlanes: 0 } } // 在棋盘上放置飞机 static placePlane(board: GameBoard, plane: PlaneShape): boolean { // 验证飞机位置合法性 if (!PlaneGeometry.validatePlanePosition(plane, board.size)) { return false } // 检查是否与现有飞机重叠 for (const existingPlane of board.planes) { if (PlaneGeometry.checkPlanesOverlap(plane, existingPlane)) { return false } } // 在棋盘上标记飞机位置 plane.positions.forEach(pos => { const cell = board.cells[pos.x][pos.y] cell.state = CellState.PLANE_PART cell.planeId = plane.id // 标记部件类型 if (pos.x === plane.parts.head.x && pos.y === plane.parts.head.y) { cell.partType = 'head' } else if (plane.parts.wings.some(w => w.x === pos.x && w.y === pos.y)) { cell.partType = 'wing' } else if (plane.parts.body.some(b => b.x === pos.x && b.y === pos.y)) { cell.partType = 'body' } else { cell.partType = 'tail' } }) // 添加飞机到棋盘 board.planes.push(plane) board.remainingPlanes++ return true } // 批量放置飞机 static placePlanes(board: GameBoard, planes: PlaneShape[]): boolean { if (planes.length !== 3) { throw new Error('必须放置3架飞机') } // 创建临时棋盘进行验证 const tempBoard = this.createEmptyBoard(board.size) // 逐个放置验证 for (const plane of planes) { if (!this.placePlane(tempBoard, plane)) { return false } } // 验证通过,应用到实际棋盘 board.cells = tempBoard.cells board.planes = tempBoard.planes board.remainingPlanes = tempBoard.remainingPlanes return true } // 执行攻击 static executeAttack(board: GameBoard, position: Position): AttackResult { const cell = board.cells[position.x][position.y] // 检查是否已经攻击过该位置 if (cell.state === CellState.ATTACKED_MISS || cell.state === CellState.ATTACKED_HIT) { throw new Error('该位置已被攻击过') } let result: AttackResult let targetPlaneId: string | undefined if (cell.state === CellState.PLANE_PART) { // 命中飞机 cell.state = CellState.ATTACKED_HIT cell.isDestroyed = true targetPlaneId = cell.planeId // 检查飞机是否完全被击毁 const plane = board.planes.find(p => p.id === targetPlaneId)! const allPartsDestroyed = plane.positions.every(pos => { const targetCell = board.cells[pos.x][pos.y] return targetCell.isDestroyed }) if (allPartsDestroyed) { result = AttackResult.DESTROY board.remainingPlanes-- // 标记整架飞机为已击毁 plane.positions.forEach(pos => { board.cells[pos.x][pos.y].isDestroyed = true }) } else { result = AttackResult.HIT } } else { // 未命中 cell.state = CellState.ATTACKED_MISS result = AttackResult.MISS } // 记录攻击历史 const attackRecord: AttackRecord = { position, result, timestamp: new Date(), targetPlaneId } board.attackHistory.push(attackRecord) return result } // 检查游戏是否结束 static isGameOver(board: GameBoard): boolean { return board.remainingPlanes === 0 } // 获取对手视图的棋盘(隐藏未被攻击的飞机位置) static getOpponentView(board: GameBoard): GameBoard { const opponentBoard = JSON.parse(JSON.stringify(board)) as GameBoard // 隐藏未被攻击的飞机位置 for (let x = 0; x < board.size; x++) { for (let y = 0; y < board.size; y++) { const cell = opponentBoard.cells[x][y] if (cell.state === CellState.PLANE_PART && !cell.isDestroyed) { cell.state = CellState.EMPTY delete cell.planeId delete cell.partType } } } return opponentBoard } // 获取棋盘统计信息 static getBoardStats(board: GameBoard): BoardStats { const totalCells = board.size * board.size const attackedCells = board.attackHistory.length const hitCells = board.attackHistory.filter(a => a.result !== AttackResult.MISS).length const accuracy = attackedCells > 0 ? (hitCells / attackedCells * 100) : 0 return { totalCells, attackedCells, hitCells, accuracy: Math.round(accuracy * 100) / 100, remainingPlanes: board.remainingPlanes, destroyedPlanes: 3 - board.remainingPlanes } } } interface BoardStats { totalCells: number attackedCells: number hitCells: number accuracy: number remainingPlanes: number destroyedPlanes: number } ``` ## 3. 游戏状态机 ### 3.1 游戏状态管理 ```typescript // 游戏状态 interface GameState { gameId: string roomCode: string phase: GamePhase players: GamePlayer[] currentPlayer: string boards: { [playerId: string]: GameBoard } gameConfig: GameConfig timeState: TimeState events: GameEvent[] result?: GameResult } // 游戏玩家 interface GamePlayer { id: string nickname: string avatar?: string isReady: boolean isOnline: boolean stats: PlayerGameStats } // 玩家游戏内统计 interface PlayerGameStats { attacksCount: number hitsCount: number planesDestroyed: number accuracy: number timeUsed: number } // 时间状态 interface TimeState { gameStartTime?: Date gameEndTime?: Date currentTurnStartTime?: Date turnTimeLimit: number totalTimeLimit: number turnTimeRemaining: number gameTimeRemaining: number } // 游戏事件 interface GameEvent { id: string type: GameEventType playerId: string timestamp: Date data: any } enum GameEventType { GAME_STARTED = 'game_started', PLANE_PLACED = 'plane_placed', PLACEMENT_COMPLETED = 'placement_completed', TURN_STARTED = 'turn_started', ATTACK_EXECUTED = 'attack_executed', PLANE_DESTROYED = 'plane_destroyed', TURN_TIMEOUT = 'turn_timeout', PLAYER_DISCONNECTED = 'player_disconnected', PLAYER_RECONNECTED = 'player_reconnected', GAME_ENDED = 'game_ended' } ``` ### 3.2 游戏状态机实现 ```typescript export class GameStateMachine { private state: GameState private timers: Map = new Map() constructor(gameState: GameState) { this.state = gameState } // 开始游戏 startGame(): void { if (this.state.phase !== GamePhase.WAITING) { throw new Error('游戏状态错误,无法开始游戏') } this.state.phase = GamePhase.PLACING this.state.timeState.gameStartTime = new Date() // 设置游戏总时长定时器 this.setGameTimeLimit() this.addEvent({ type: GameEventType.GAME_STARTED, playerId: '', data: { startTime: this.state.timeState.gameStartTime } }) } // 玩家放置飞机 placePlanes(playerId: string, planes: PlaneShape[]): void { if (this.state.phase !== GamePhase.PLACING) { throw new Error('当前不是飞机放置阶段') } const player = this.getPlayer(playerId) if (player.isReady) { throw new Error('玩家已经完成飞机放置') } // 放置飞机到棋盘 const board = this.state.boards[playerId] const success = BoardManager.placePlanes(board, planes) if (!success) { throw new Error('飞机放置失败') } // 标记玩家已准备 player.isReady = true this.addEvent({ type: GameEventType.PLACEMENT_COMPLETED, playerId, data: { planes: planes.length } }) // 检查是否所有玩家都已准备 if (this.allPlayersReady()) { this.startBattle() } } // 开始对战阶段 private startBattle(): void { this.state.phase = GamePhase.BATTLING // 随机选择先手玩家 const firstPlayer = this.state.players[Math.floor(Math.random() * this.state.players.length)] this.state.currentPlayer = firstPlayer.id this.startTurn() } // 开始新回合 private startTurn(): void { this.state.timeState.currentTurnStartTime = new Date() this.state.timeState.turnTimeRemaining = this.state.timeState.turnTimeLimit // 设置回合时间限制 this.setTurnTimeLimit() this.addEvent({ type: GameEventType.TURN_STARTED, playerId: this.state.currentPlayer, data: { timeLimit: this.state.timeState.turnTimeLimit } }) } // 执行攻击 executeAttack(playerId: string, position: Position): AttackResult { if (this.state.phase !== GamePhase.BATTLING) { throw new Error('当前不是对战阶段') } if (this.state.currentPlayer !== playerId) { throw new Error('不是你的回合') } // 获取对手棋盘 const opponentId = this.getOpponent(playerId).id const opponentBoard = this.state.boards[opponentId] // 执行攻击 const result = BoardManager.executeAttack(opponentBoard, position) // 更新玩家统计 const player = this.getPlayer(playerId) player.stats.attacksCount++ if (result !== AttackResult.MISS) { player.stats.hitsCount++ player.stats.accuracy = (player.stats.hitsCount / player.stats.attacksCount) * 100 } if (result === AttackResult.DESTROY) { player.stats.planesDestroyed++ } this.addEvent({ type: GameEventType.ATTACK_EXECUTED, playerId, data: { position, result, opponentId } }) if (result === AttackResult.DESTROY) { this.addEvent({ type: GameEventType.PLANE_DESTROYED, playerId: opponentId, data: { attackerId: playerId, position } }) } // 检查游戏是否结束 if (BoardManager.isGameOver(opponentBoard)) { this.endGame(playerId) } else { // 切换回合 this.switchTurn() } return result } // 切换回合 private switchTurn(): void { this.clearTurnTimer() const currentPlayerIndex = this.state.players.findIndex(p => p.id === this.state.currentPlayer) const nextPlayerIndex = (currentPlayerIndex + 1) % this.state.players.length this.state.currentPlayer = this.state.players[nextPlayerIndex].id this.startTurn() } // 回合超时处理 private handleTurnTimeout(): void { this.addEvent({ type: GameEventType.TURN_TIMEOUT, playerId: this.state.currentPlayer, data: { timeUsed: this.state.timeState.turnTimeLimit } }) // 自动跳过回合 this.switchTurn() } // 结束游戏 private endGame(winnerId: string): void { this.state.phase = GamePhase.FINISHED this.state.timeState.gameEndTime = new Date() const winner = this.getPlayer(winnerId) const loser = this.getOpponent(winnerId) this.state.result = { winnerId, loserId: loser.id, winReason: 'ALL_PLANES_DESTROYED', gameStats: { duration: this.getGameDuration(), totalMoves: this.state.events.filter(e => e.type === GameEventType.ATTACK_EXECUTED).length, winnerStats: winner.stats, loserStats: loser.stats } } // 清除所有定时器 this.clearAllTimers() this.addEvent({ type: GameEventType.GAME_ENDED, playerId: winnerId, data: this.state.result }) } // 玩家断线处理 handlePlayerDisconnection(playerId: string): void { const player = this.getPlayer(playerId) player.isOnline = false this.addEvent({ type: GameEventType.PLAYER_DISCONNECTED, playerId, data: { timestamp: new Date() } }) // 如果是对战阶段且是当前玩家断线,暂停计时 if (this.state.phase === GamePhase.BATTLING && this.state.currentPlayer === playerId) { this.pauseTurnTimer() } } // 玩家重连处理 handlePlayerReconnection(playerId: string): void { const player = this.getPlayer(playerId) player.isOnline = true this.addEvent({ type: GameEventType.PLAYER_RECONNECTED, playerId, data: { timestamp: new Date() } }) // 如果是对战阶段且是当前玩家重连,恢复计时 if (this.state.phase === GamePhase.BATTLING && this.state.currentPlayer === playerId) { this.resumeTurnTimer() } } // 定时器管理方法 private setGameTimeLimit(): void { const timer = setTimeout(() => { this.endGameByTimeout() }, this.state.timeState.totalTimeLimit * 1000) this.timers.set('gameTime', timer) } private setTurnTimeLimit(): void { const timer = setTimeout(() => { this.handleTurnTimeout() }, this.state.timeState.turnTimeLimit * 1000) this.timers.set('turnTime', timer) } private clearTurnTimer(): void { const timer = this.timers.get('turnTime') if (timer) { clearTimeout(timer) this.timers.delete('turnTime') } } private clearAllTimers(): void { this.timers.forEach(timer => clearTimeout(timer)) this.timers.clear() } private pauseTurnTimer(): void { // 实现回合计时器暂停逻辑 this.clearTurnTimer() } private resumeTurnTimer(): void { // 实现回合计时器恢复逻辑 this.setTurnTimeLimit() } // 辅助方法 private getPlayer(playerId: string): GamePlayer { const player = this.state.players.find(p => p.id === playerId) if (!player) { throw new Error('玩家不存在') } return player } private getOpponent(playerId: string): GamePlayer { const opponent = this.state.players.find(p => p.id !== playerId) if (!opponent) { throw new Error('对手不存在') } return opponent } private allPlayersReady(): boolean { return this.state.players.every(p => p.isReady) } private addEvent(event: Omit): void { const gameEvent: GameEvent = { id: generateId(), timestamp: new Date(), ...event } this.state.events.push(gameEvent) } private getGameDuration(): number { if (!this.state.timeState.gameStartTime || !this.state.timeState.gameEndTime) { return 0 } return this.state.timeState.gameEndTime.getTime() - this.state.timeState.gameStartTime.getTime() } private endGameByTimeout(): void { // 根据当前分数决定胜负 const player1 = this.state.players[0] const player2 = this.state.players[1] const player1Score = player1.stats.planesDestroyed const player2Score = player2.stats.planesDestroyed let winnerId: string if (player1Score > player2Score) { winnerId = player1.id } else if (player2Score > player1Score) { winnerId = player2.id } else { // 平局,根据命中率决定 winnerId = player1.stats.accuracy >= player2.stats.accuracy ? player1.id : player2.id } this.endGame(winnerId) } // 获取当前游戏状态 getState(): GameState { return { ...this.state } } // 获取玩家视图的游戏状态 getPlayerView(playerId: string): any { const state = this.getState() // 隐藏对手棋盘上未被攻击的飞机 const opponentId = this.getOpponent(playerId).id state.boards[opponentId] = BoardManager.getOpponentView(state.boards[opponentId]) return state } } ``` ## 4. 游戏规则验证 ### 4.1 输入验证器 ```typescript export class GameValidator { // 验证飞机放置是否合法 static validatePlanesPlacement(planes: PlaneShape[]): ValidationResult { const errors: string[] = [] // 检查飞机数量 if (planes.length !== 3) { errors.push('必须放置3架飞机') } // 检查每架飞机的合法性 planes.forEach((plane, index) => { // 检查飞机形状是否正确 if (plane.positions.length !== 11) { errors.push(`第${index + 1}架飞机形状不正确`) } // 检查飞机是否在棋盘范围内 if (!PlaneGeometry.validatePlanePosition(plane)) { errors.push(`第${index + 1}架飞机位置超出棋盘范围`) } }) // 检查飞机之间是否重叠 for (let i = 0; i < planes.length; i++) { for (let j = i + 1; j < planes.length; j++) { if (PlaneGeometry.checkPlanesOverlap(planes[i], planes[j])) { errors.push(`第${i + 1}架和第${j + 1}架飞机位置重叠`) } } } return { isValid: errors.length === 0, errors } } } interface ValidationResult { isValid: boolean errors: string[] }