Files
DFJ/01_文档/打飞机小程序需求说明书-已经过期失效.md
2025-09-11 14:08:02 +08:00

88 KiB
Raw Permalink Blame History

打飞机小程序完整需求说明书

文档信息

项目 详情
项目名称 打飞机对战小程序
文档版本 v1.0
创建日期 2025年9月
目标平台 微信小程序 / 原生APP
开发语言 TypeScript + JavaScript

此文档已经过期失效 参考价值可以忽略

此文档已经过期失效 参考价值可以忽略

1. 项目概述

1.1 项目背景

打飞机是一款经典的双人对战策略游戏,玩家需要在棋盘上布置飞机并猜测对手飞机位置。本项目旨在开发一个现代化的小程序版本,支持人机对战和在线对战功能。

1.2 项目目标

  • 提供流畅的游戏体验和直观的用户界面
  • 实现智能AI对战系统
  • 支持在线实时对战功能
  • 建立完整的用户系统和数据统计
  • 确保跨平台兼容性和高性能表现

1.3 目标用户群体

  • 主要用户: 8-60岁喜欢益智游戏的用户
  • 使用场景: 休闲娱乐、朋友对战、碎片时间游戏
  • 用户特征: 追求简单易上手但有一定策略性的游戏

2. 功能需求规格

2.1 核心功能模块

2.1.1 用户系统

interface User {
  userId: string
  nickname: string
  avatar: string
  level: number
  experience: number
  winRate: number
  totalGames: number
  ranking: number
  achievements: Achievement[]
  createdAt: Date
  lastLoginAt: Date
}

interface Achievement {
  id: string
  name: string
  description: string
  icon: string
  unlockedAt: Date
  progress: number
  maxProgress: number
}

功能列表:

  • 微信登录/游客登录
  • 用户资料管理(头像、昵称)
  • 成就系统(解锁条件和奖励)
  • 好友系统(添加、删除、邀请对战)
  • 排行榜(全服排名、好友排名)

2.1.2 游戏核心系统

A. 棋盘系统
interface GameBoard {
  size: { width: number, height: number } // 10x10
  cells: Cell[][]
  coordinateSystem: 'LETTER_NUMBER' // A1-J10
}

interface Cell {
  position: Position
  state: CellState
  isRevealed: boolean
  hasPlane: boolean
  planePartType?: PlanePartType
  attackResult?: AttackResult
}

enum CellState {
  EMPTY = 0,
  PLANE_PART = 1,
  ATTACKED_MISS = 2,
  ATTACKED_HIT = 3,
  ATTACKED_DESTROYED = 4
}
B. 飞机系统
interface Plane {
  id: string
  center: Position
  direction: Direction
  positions: Position[] // 11个位置
  isDestroyed: boolean
  headPosition: Position
  wingPositions: Position[]
  bodyPositions: Position[]
  tailPositions: Position[]
}

enum Direction {
  UP = 'UP',
  DOWN = 'DOWN', 
  LEFT = 'LEFT',
  RIGHT = 'RIGHT'
}

interface Position {
  x: number // 1-10
  y: number // 1-10
  coordinate: string // "A_1" 格式
}
C. 游戏状态管理
interface GameState {
  gameId: string
  gameType: GameType
  currentPhase: GamePhase
  players: Player[]
  currentPlayer: string
  gameBoard: {
    player1: GameBoard
    player2: GameBoard
  }
  moveHistory: Move[]
  startTime: Date
  endTime?: Date
  winner?: string
  gameSettings: GameSettings
}

enum GameType {
  AI_BATTLE = 'AI_BATTLE',
  ONLINE_BATTLE = 'ONLINE_BATTLE',
  LOCAL_BATTLE = 'LOCAL_BATTLE'
}

enum GamePhase {
  WAITING = 'WAITING',
  PLACING_PLANES = 'PLACING_PLANES', 
  BATTLING = 'BATTLING',
  GAME_OVER = 'GAME_OVER'
}

2.1.3 AI对战系统

A. AI难度等级
interface AIConfig {
  level: AILevel
  reactionTime: number // ms
  mistakeProbability: number // 0-1
  strategicDepth: number // 1-5
  personalityType: AIPersonality
}

enum AILevel {
  BEGINNER = 'BEGINNER',     // 初级
  INTERMEDIATE = 'INTERMEDIATE', // 中级  
  ADVANCED = 'ADVANCED',     // 高级
  EXPERT = 'EXPERT',         // 专家
  MASTER = 'MASTER'          // 大师
}

enum AIPersonality {
  AGGRESSIVE = 'AGGRESSIVE',   // 激进型
  DEFENSIVE = 'DEFENSIVE',     // 防守型
  ANALYTICAL = 'ANALYTICAL',   // 分析型
  UNPREDICTABLE = 'UNPREDICTABLE' // 随机型
}
B. AI决策算法接口
interface AIDecisionEngine {
  calculatePlacementStrategy(board: GameBoard): PlacementStrategy
  selectAttackPosition(gameState: GameState): Position
  analyzeOpponentPattern(attackHistory: Move[]): OpponentAnalysis
  adaptStrategy(gameResult: GameResult): void
}

interface PlacementStrategy {
  planes: PlaneConfiguration[]
  confidence: number
  reasoning: string[]
}

interface OpponentAnalysis {
  predictedPattern: string
  riskAreas: Position[]
  nextMovePredict: Position[]
  confidence: number
}

2.1.4 在线对战系统

A. 房间系统
interface GameRoom {
  roomId: string
  roomCode: string // 6位数字邀请码
  hostUserId: string
  guestUserId?: string
  gameState: GameState
  roomSettings: RoomSettings
  createdAt: Date
  status: RoomStatus
}

enum RoomStatus {
  WAITING = 'WAITING',
  IN_GAME = 'IN_GAME', 
  FINISHED = 'FINISHED',
  ABANDONED = 'ABANDONED'
}

interface RoomSettings {
  isPrivate: boolean
  allowSpectators: boolean
  timeLimit: number // 每步时间限制(秒)
  gameMode: GameMode
}
B. 实时通信协议
interface GameMessage {
  type: MessageType
  from: string
  to?: string
  data: any
  timestamp: number
  gameId: string
}

enum MessageType {
  // 房间管理
  JOIN_ROOM = 'JOIN_ROOM',
  LEAVE_ROOM = 'LEAVE_ROOM',
  ROOM_STATUS_UPDATE = 'ROOM_STATUS_UPDATE',
  
  // 游戏操作
  PLACE_PLANE = 'PLACE_PLANE',
  PREPARE_ATTACK = 'PREPARE_ATTACK', // 玩家选择但未确认打击的位置
  ATTACK_POSITION = 'ATTACK_POSITION',
  GAME_STATE_SYNC = 'GAME_STATE_SYNC',
  
  // 系统消息
  PLAYER_RECONNECT = 'PLAYER_RECONNECT',
  PLAYER_TIMEOUT = 'PLAYER_TIMEOUT',
  GAME_END = 'GAME_END'
}

2.2 用户界面需求

2.2.1 界面结构设计

主界面
├── 头部导航
│   ├── 用户头像+昵称
│   └── 设置按钮
├── 游戏模式选择
│   ├── AI对战
│   ├── 在线对战
│   └── 本地对战
├── 功能区域
│   ├── 排行榜
│   ├── 成就系统
│   ├── 游戏记录
│   └── 教程帮助
└── 底部导航
    ├── 首页
    ├── 对战
    ├── 排行
    └── 我的

2.2.2 游戏界面布局

interface GameUILayout {
  // 棋盘区域 - 占屏幕60%
  gameBoard: {
    playerBoard: BoardComponent    // 己方棋盘
    opponentBoard: BoardComponent  // 对方棋盘
    switchButton: SwitchComponent  // 切换视角
  }
  
  // 信息面板 - 占屏幕25%
  infoPanel: {
    playerInfo: PlayerInfoComponent
    gameStatus: GameStatusComponent
    timer: TimerComponent
    moveCounter: MoveCounterComponent
  }
  
  // 控制面板 - 占屏幕15% 
  controlPanel: {
    actionButtons: ActionButtonComponent[]
    chatBox?: ChatComponent
    settingsMenu: SettingsComponent
  }
}

2.2.3 交互设计规范

A. 飞机布置交互
  1. 拖拽模式: 从侧边栏拖拽飞机到棋盘
  2. 点击模式: 点击棋盘位置,选择飞机方向
  3. 辅助功能:
    • 半透明预览显示
    • 红色提示无效位置
    • 绿色确认有效位置
    • 自动布置功能
B. 攻击操作交互
  1. 点击攻击: 直接点击对方棋盘位置
  2. 确认机制: 重要攻击需二次确认
  3. 视觉反馈:
    • 实时瞄准提示: 在对手回合,棋盘上应实时显示对手正在瞄准(已选择但未确认)的格子。
    • 攻击动画效果
    • 结果显示动画
    • 音效配合

2.2.4 响应式设计要求

/* 屏幕适配断点 */
@media (max-width: 375px) {
  /* 小屏手机 */
}

@media (min-width: 376px) and (max-width: 414px) {
  /* 中屏手机 */
}

@media (min-width: 415px) {
  /* 大屏手机/平板 */
}

2.3 性能需求

2.3.1 响应时间要求

功能模块 响应时间要求 备注
小程序启动 < 3秒 首次启动
页面切换 < 300ms 页面路由
AI决策 < 1秒 普通难度
AI决策 < 3秒 最高难度
网络同步 < 500ms 局域网环境
动画渲染 60fps 流畅动画

2.3.2 内存占用限制

  • 小程序运行内存: < 10MB
  • 图片资源缓存: < 5MB
  • 游戏数据缓存: < 2MB
  • 总内存使用: < 20MB

2.3.3 网络要求

  • 支持弱网络环境(2G/3G)
  • 断线重连机制
  • 离线模式支持
  • 数据压缩传输

3. 技术选型方案

3.1 前端技术栈

3.1.1 框架选择

主框架: Taro 3.x + React 18

// 原因:
// 1. 一套代码多端运行(小程序+H5+APP)
// 2. React生态成熟组件丰富
// 3. TypeScript支持完善
// 4. 性能优化方案成熟

// 项目结构
src/
├── components/     # 通用组件
├── pages/         # 页面组件  
├── hooks/         # 自定义Hooks
├── store/         # 状态管理
├── services/      # API服务
├── utils/         # 工具函数
├── types/         # TypeScript类型
├── assets/        # 静态资源
└── constants/     # 常量配置

3.1.2 状态管理

选择: Zustand + Immer

// 游戏状态Store
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

interface GameStore {
  gameState: GameState | null
  setGameState: (state: GameState) => void
  updatePlayerBoard: (playerId: string, board: GameBoard) => void
  addMove: (move: Move) => void
  resetGame: () => void
}

export const useGameStore = create<GameStore>()(
  immer((set) => ({
    gameState: null,
    
    setGameState: (state) => set((draft) => {
      draft.gameState = state
    }),
    
    updatePlayerBoard: (playerId, board) => set((draft) => {
      if (draft.gameState) {
        draft.gameState.gameBoard[playerId as keyof typeof draft.gameState.gameBoard] = board
      }
    }),
    
    addMove: (move) => set((draft) => {
      draft.gameState?.moveHistory.push(move)
    }),
    
    resetGame: () => set((draft) => {
      draft.gameState = null
    })
  }))
)

3.1.3 UI组件库

选择: Taro UI + 自定义组件

// 自定义游戏棋盘组件
import React from 'react'
import { View } from '@tarojs/components'
import './GameBoard.scss'

interface GameBoardProps {
  board: GameBoard
  isInteractive: boolean
  onCellClick?: (position: Position) => void
  showPlanes?: boolean
}

export const GameBoard: React.FC<GameBoardProps> = ({
  board,
  isInteractive,
  onCellClick,
  showPlanes = false
}) => {
  const renderCell = (cell: Cell) => (
    <View 
      key={`${cell.position.x}-${cell.position.y}`}
      className={`cell ${cell.state}`}
      onClick={() => isInteractive && onCellClick?.(cell.position)}
    >
      {renderCellContent(cell)}
    </View>
  )

  return (
    <View className="game-board">
      <View className="board-grid">
        {board.cells.flat().map(renderCell)}
      </View>
    </View>
  )
}

3.2 后端技术架构

3.2.1 服务端框架

选择: Node.js + Express + TypeScript

// 服务器架构
src/
├── controllers/    # 控制器层
├── services/      # 业务逻辑层
├── models/        # 数据模型
├── middleware/    # 中间件
├── routes/        # 路由配置
├── websocket/     # WebSocket处理
├── utils/         # 工具函数
├── config/        # 配置文件
└── types/         # TypeScript类型

// 游戏服务示例
import { GameEngine } from '../services/GameEngine'
import { GameRoom } from '../models/GameRoom'

export class GameController {
  private gameEngine: GameEngine

  constructor() {
    this.gameEngine = new GameEngine()
  }

  async createRoom(req: Request, res: Response) {
    try {
      const { userId, settings } = req.body
      const room = await this.gameEngine.createRoom(userId, settings)
      
      res.json({
        success: true,
        data: room
      })
    } catch (error) {
      res.status(500).json({
        success: false,
        error: error.message
      })
    }
  }

  async joinRoom(req: Request, res: Response) {
    // 房间加入逻辑
  }

  async makeMove(req: Request, res: Response) {
    // 游戏操作逻辑
  }
}

3.2.2 实时通信

选择: Socket.IO

import { Server as SocketServer } from 'socket.io'
import { GameRoomManager } from '../services/GameRoomManager'

export class GameSocketHandler {
  private roomManager: GameRoomManager

  constructor(io: SocketServer) {
    this.roomManager = new GameRoomManager()
    this.setupSocketHandlers(io)
  }

  private setupSocketHandlers(io: SocketServer) {
    io.on('connection', (socket) => {
      console.log('Client connected:', socket.id)

      // 加入房间
      socket.on('join-room', async (data) => {
        const { roomId, userId } = data
        try {
          await this.roomManager.joinRoom(roomId, userId)
          socket.join(roomId)
          
          // 通知房间其他玩家
          socket.to(roomId).emit('player-joined', { userId })
        } catch (error) {
          socket.emit('error', { message: error.message })
        }
      })

      // 游戏操作
      socket.on('game-move', async (data) => {
        const { roomId, move } = data
        try {
          const result = await this.roomManager.processMove(roomId, move)
          
          // 广播游戏状态更新
          io.to(roomId).emit('game-state-update', result)
        } catch (error) {
          socket.emit('error', { message: error.message })
        }
      })

      // 断线处理
      socket.on('disconnect', () => {
        console.log('Client disconnected:', socket.id)
        this.roomManager.handlePlayerDisconnect(socket.id)
      })
    })
  }
}

3.2.3 数据库设计

选择: MongoDB + Redis

// MongoDB 数据模型
import mongoose from 'mongoose'

// 用户模型
const UserSchema = new mongoose.Schema({
  userId: { type: String, required: true, unique: true },
  nickname: { type: String, required: true },
  avatar: String,
  level: { type: Number, default: 1 },
  experience: { type: Number, default: 0 },
  statistics: {
    totalGames: { type: Number, default: 0 },
    wins: { type: Number, default: 0 },
    winRate: { type: Number, default: 0 }
  },
  achievements: [{
    achievementId: String,
    unlockedAt: Date
  }],
  createdAt: { type: Date, default: Date.now },
  lastLoginAt: { type: Date, default: Date.now }
})

// 游戏记录模型
const GameRecordSchema = new mongoose.Schema({
  gameId: { type: String, required: true, unique: true },
  gameType: { type: String, enum: ['AI', 'ONLINE', 'LOCAL'] },
  players: [{
    userId: String,
    nickname: String,
    result: { type: String, enum: ['WIN', 'LOSE', 'DRAW'] }
  }],
  gameData: {
    moves: [{}],
    duration: Number,
    placements: {}
  },
  createdAt: { type: Date, default: Date.now }
})

// Redis 缓存结构
interface RedisGameSession {
  gameId: string
  roomId: string
  gameState: GameState
  players: string[]
  lastActivity: number
  ttl: number // 生存时间
}

3.3 AI算法技术方案

3.3.1 核心算法框架

// AI决策引擎架构
export class AIDecisionEngine {
  private difficultyConfig: AIConfig
  private probabilityMap: ProbabilityHeatmap
  private patternAnalyzer: PatternAnalyzer
  private strategySelector: StrategySelector

  constructor(difficulty: AILevel) {
    this.difficultyConfig = this.loadDifficultyConfig(difficulty)
    this.probabilityMap = new ProbabilityHeatmap()
    this.patternAnalyzer = new PatternAnalyzer()
    this.strategySelector = new StrategySelector()
  }

  // 飞机布置策略
  async generatePlacementStrategy(): Promise<PlacementStrategy> {
    const strategies = [
      this.generateScatteredPlacement(),
      this.generateClusteredPlacement(),
      this.generateEdgeAvoidingPlacement(),
      this.generateCornerFocusedPlacement()
    ]

    const selectedStrategy = this.strategySelector.selectBestStrategy(
      strategies,
      this.difficultyConfig
    )

    return selectedStrategy
  }

  // 攻击位置选择
  async selectAttackPosition(gameState: GameState): Promise<Position> {
    // 更新概率地图
    this.updateProbabilityMap(gameState)

    // 生成候选位置
    const candidates = this.generateCandidatePositions(gameState)

    // 评估每个位置的价值
    const evaluatedPositions = candidates.map(pos => ({
      position: pos,
      score: this.evaluatePosition(pos, gameState)
    }))

    // 根据难度选择最终位置
    return this.selectFinalPosition(evaluatedPositions)
  }

  private updateProbabilityMap(gameState: GameState): void {
    // 基于贝叶斯推理更新概率分布
    for (const move of gameState.moveHistory) {
      this.probabilityMap.updateAfterAttack(move.position, move.result)
    }

    // 模式识别更新
    const patterns = this.patternAnalyzer.analyzePatterns(gameState.moveHistory)
    this.probabilityMap.applyPatternAdjustments(patterns)
  }
}

3.3.2 概率计算算法

// 概率热力图实现
export class ProbabilityHeatmap {
  private probabilities: number[][]
  private readonly BOARD_SIZE = 10

  constructor() {
    this.initializeHeatmap()
  }

  private initializeHeatmap(): void {
    this.probabilities = Array(this.BOARD_SIZE + 1).fill(null)
      .map(() => Array(this.BOARD_SIZE + 1).fill(0))

    // 计算初始概率分布
    for (let x = 1; x <= this.BOARD_SIZE; x++) {
      for (let y = 1; y <= this.BOARD_SIZE; y++) {
        this.probabilities[x][y] = this.calculateInitialProbability(x, y)
      }
    }
  }

  private calculateInitialProbability(x: number, y: number): number {
    let totalPlacements = 0
    let validPlacements = 0

    // 计算该位置可能的飞机布置数量
    const directions: Direction[] = ['UP', 'DOWN', 'LEFT', 'RIGHT']
    const parts: PlanePartType[] = ['HEAD', 'WING', 'BODY', 'TAIL']

    for (const direction of directions) {
      for (const partType of parts) {
        // 计算以当前位置为特定部件的飞机中心位置
        const centerPositions = this.getPossibleCenters(x, y, direction, partType)
        
        for (const center of centerPositions) {
          totalPlacements++
          if (this.isValidPlanePosition(center, direction)) {
            validPlacements++
          }
        }
      }
    }

    return totalPlacements > 0 ? validPlacements / totalPlacements : 0
  }

  updateAfterAttack(position: Position, result: AttackResult): void {
    if (result.type === 'MISS') {
      // 该位置及相关联的飞机配置概率置零
      this.probabilities[position.x][position.y] = 0
      this.propagateMissInformation(position)
    } else if (result.type === 'HIT') {
      // 提升相邻位置的概率
      this.boostAdjacentProbabilities(position)
      // 排除不可能的飞机配置
      this.eliminateInvalidConfigurations(position, result)
    } else if (result.type === 'DESTROYED') {
      // 移除已摧毁飞机的所有位置
      this.removeDestroyedPlane(position, result.destroyedPlane)
    }

    // 重新归一化概率分布
    this.normalizeProbabilities()
  }

  getBestAttackPositions(count: number = 5): Position[] {
    const candidates: { position: Position, probability: number }[] = []

    for (let x = 1; x <= this.BOARD_SIZE; x++) {
      for (let y = 1; y <= this.BOARD_SIZE; y++) {
        if (this.probabilities[x][y] > 0) {
          candidates.push({
            position: { x, y, coordinate: `${String.fromCharCode(64 + x)}_${y}` },
            probability: this.probabilities[x][y]
          })
        }
      }
    }

    return candidates
      .sort((a, b) => b.probability - a.probability)
      .slice(0, count)
      .map(c => c.position)
  }
}

3.3.3 模式识别算法

// 模式分析器
export class PatternAnalyzer {
  private knownPatterns: Map<string, PatternSignature> = new Map()

  constructor() {
    this.initializeKnownPatterns()
  }

  analyzePlayerBehavior(gameHistory: GameRecord[]): PlayerBehaviorProfile {
    const profile: PlayerBehaviorProfile = {
      preferredPlacements: this.analyzePlacementPatterns(gameHistory),
      attackStrategies: this.analyzeAttackPatterns(gameHistory),
      timingPatterns: this.analyzeTimingPatterns(gameHistory),
      riskPreference: this.calculateRiskPreference(gameHistory)
    }

    return profile
  }

  private analyzePlacementPatterns(games: GameRecord[]): PlacementPattern[] {
    const patterns: PlacementPattern[] = []

    for (const game of games) {
      const placements = game.gameData.placements
      
      // 分析飞机分布
      const distribution = this.calculateDistribution(placements)
      
      // 分析边缘使用
      const edgeUsage = this.calculateEdgeUsage(placements)
      
      // 分析对称性
      const symmetry = this.calculateSymmetry(placements)

      patterns.push({
        gameId: game.gameId,
        distribution,
        edgeUsage,
        symmetry,
        difficulty: this.classifyDifficulty(distribution, edgeUsage)
      })
    }

    return patterns
  }

  predictNextMove(moveHistory: Move[], playerProfile: PlayerBehaviorProfile): Position[] {
    const predictions: Position[] = []

    // 基于历史模式预测
    if (playerProfile.attackStrategies.includes('SYSTEMATIC_GRID')) {
      predictions.push(...this.predictGridSearchNext(moveHistory))
    }

    if (playerProfile.attackStrategies.includes('HUNT_AND_TARGET')) {
      predictions.push(...this.predictHuntTargetNext(moveHistory))
    }

    if (playerProfile.attackStrategies.includes('RANDOM_SEARCH')) {
      predictions.push(...this.predictRandomNext(moveHistory))
    }

    return predictions.slice(0, 3) // 返回前3个预测
  }
}

4. 核心算法实现详解

4.1 游戏核心逻辑算法

4.1.1 飞机位置生成算法

// 飞机几何模型定义
const PLANE_GEOMETRY: Record<Direction, number[][]> = {
  UP: [
    [0, -2],  // 机头
    [-2, -1], [-1, -1], [0, -1], [1, -1], [2, -1], // 机翼
    [0, 0], [0, 1],  // 机身
    [-1, 2], [0, 2], [1, 2]  // 机尾
  ],
  DOWN: [
    [0, 2],   // 机头
    [-2, 1], [-1, 1], [0, 1], [1, 1], [2, 1],  // 机翼
    [0, 0], [0, -1], // 机身
    [-1, -2], [0, -2], [1, -2] // 机尾
  ],
  LEFT: [
    [-2, 0],  // 机头
    [-1, -2], [-1, -1], [-1, 0], [-1, 1], [-1, 2], // 机翼
    [0, 0], [1, 0],   // 机身
    [2, -1], [2, 0], [2, 1]   // 机尾
  ],
  RIGHT: [
    [2, 0],   // 机头
    [1, -2], [1, -1], [1, 0], [1, 1], [1, 2],   // 机翼
    [0, 0], [-1, 0],  // 机身
    [-2, -1], [-2, 0], [-2, 1] // 机尾
  ]
}

export class PlaneGeometry {
  static generatePlanePositions(center: Position, direction: Direction): Position[] {
    const offsets =

 PLANE_GEOMETRY[direction]
    
    return offsets.map(([dx, dy]) => ({
      x: center.x + dx,
      y: center.y + dy,
      coordinate: `${String.fromCharCode(64 + center.x + dx)}_${center.y + dy}`
    }))
  }

  static validatePlanePosition(center: Position, direction: Direction, boardSize: number): boolean {
    const positions = this.generatePlanePositions(center, direction)
    
    // 边界检查
    return positions.every(pos => 
      pos.x >= 1 && pos.x <= boardSize && 
      pos.y >= 1 && pos.y <= boardSize
    )
  }

  static getPlanePartType(position: Position, center: Position, direction: Direction): PlanePartType {
    const positions = this.generatePlanePositions(center, direction)
    const offsets = PLANE_GEOMETRY[direction]
    
    const index = positions.findIndex(pos => 
      pos.x === position.x && pos.y === position.y
    )
    
    if (index === 0) return 'HEAD'
    if (index >= 1 && index <= 5) return 'WING'  
    if (index >= 6 && index <= 7) return 'BODY'
    if (index >= 8 && index <= 10) return 'TAIL'
    
    throw new Error('Position not part of plane')
  }
}

4.1.2 碰撞检测算法

export class CollisionDetector {
  private occupancyMap: Set<string> = new Set()

  addPlane(plane: Plane): void {
    for (const position of plane.positions) {
      this.occupancyMap.add(this.positionToKey(position))
    }
  }

  removePlane(plane: Plane): void {
    for (const position of plane.positions) {
      this.occupancyMap.delete(this.positionToKey(position))
    }
  }

  checkCollision(newPlane: Plane): boolean {
    return newPlane.positions.some(pos => 
      this.occupancyMap.has(this.positionToKey(pos))
    )
  }

  isPositionOccupied(position: Position): boolean {
    return this.occupancyMap.has(this.positionToKey(position))
  }

  private positionToKey(position: Position): string {
    return `${position.x},${position.y}`
  }

  // 优化的批量检测
  checkMultipleCollisions(planes: Plane[]): boolean {
    const tempMap = new Set(this.occupancyMap)
    
    for (const plane of planes) {
      for (const position of plane.positions) {
        const key = this.positionToKey(position)
        if (tempMap.has(key)) {
          return true // 发现碰撞
        }
        tempMap.add(key)
      }
    }
    
    return false
  }
}

4.1.3 自动布置算法

export class AutoPlacementEngine {
  private collisionDetector: CollisionDetector
  private readonly MAX_ATTEMPTS = 1000
  
  constructor() {
    this.collisionDetector = new CollisionDetector()
  }

  generateRandomPlacement(boardSize: number): Plane[] {
    const planes: Plane[] = []
    this.collisionDetector = new CollisionDetector()

    for (let i = 0; i < 3; i++) {
      const plane = this.placeSinglePlane(boardSize, planes)
      if (plane) {
        planes.push(plane)
        this.collisionDetector.addPlane(plane)
      } else {
        throw new Error(`无法布置第${i + 1}架飞机`)
      }
    }

    return planes
  }

  private placeSinglePlane(boardSize: number, existingPlanes: Plane[]): Plane | null {
    const directions: Direction[] = ['UP', 'DOWN', 'LEFT', 'RIGHT']
    
    for (let attempt = 0; attempt < this.MAX_ATTEMPTS; attempt++) {
      const center: Position = {
        x: Math.floor(Math.random() * boardSize) + 1,
        y: Math.floor(Math.random() * boardSize) + 1,
        coordinate: ''
      }
      center.coordinate = `${String.fromCharCode(64 + center.x)}_${center.y}`

      const direction = directions[Math.floor(Math.random() * directions.length)]
      
      // 验证位置有效性
      if (PlaneGeometry.validatePlanePosition(center, direction, boardSize)) {
        const plane = this.createPlane(center, direction)
        
        if (!this.collisionDetector.checkCollision(plane)) {
          return plane
        }
      }
    }

    return null
  }

  // 智能布置策略
  generateSmartPlacement(boardSize: number, strategy: PlacementStrategy = 'BALANCED'): Plane[] {
    switch (strategy) {
      case 'DEFENSIVE':
        return this.generateDefensivePlacement(boardSize)
      case 'AGGRESSIVE': 
        return this.generateAggressivePlacement(boardSize)
      case 'SCATTERED':
        return this.generateScatteredPlacement(boardSize)
      default:
        return this.generateBalancedPlacement(boardSize)
    }
  }

  private generateDefensivePlacement(boardSize: number): Plane[] {
    const planes: Plane[] = []
    const preferredPositions = this.getDefensivePositions(boardSize)
    
    // 优先选择边角和边缘位置
    for (const position of preferredPositions) {
      const directions: Direction[] = ['UP', 'DOWN', 'LEFT', 'RIGHT']
      
      for (const direction of directions) {
        if (PlaneGeometry.validatePlanePosition(position, direction, boardSize)) {
          const plane = this.createPlane(position, direction)
          
          if (!this.collisionDetector.checkCollision(plane)) {
            planes.push(plane)
            this.collisionDetector.addPlane(plane)
            break
          }
        }
      }
      
      if (planes.length === 3) break
    }

    return planes
  }

  private getDefensivePositions(boardSize: number): Position[] {
    const positions: Position[] = []
    
    // 边角位置 (优先级最高)
    const corners = [
      { x: 3, y: 3 }, { x: 8, y: 3 }, 
      { x: 3, y: 8 }, { x: 8, y: 8 }
    ]
    
    // 边缘位置
    for (let i = 3; i <= 8; i++) {
      positions.push(
        { x: i, y: 3, coordinate: '' }, // 上边缘
        { x: i, y: 8, coordinate: '' }, // 下边缘
        { x: 3, y: i, coordinate: '' }, // 左边缘
        { x: 8, y: i, coordinate: '' }  // 右边缘
      )
    }

    return [...corners, ...positions].map(pos => ({
      ...pos,
      coordinate: `${String.fromCharCode(64 + pos.x)}_${pos.y}`
    }))
  }

  private createPlane(center: Position, direction: Direction): Plane {
    const positions = PlaneGeometry.generatePlanePositions(center, direction)
    const headPosition = positions[0] // 机头始终是第一个位置
    
    return {
      id: this.generatePlaneId(),
      center,
      direction,
      positions,
      isDestroyed: false,
      headPosition,
      wingPositions: positions.slice(1, 6),
      bodyPositions: positions.slice(6, 8),
      tailPositions: positions.slice(8, 11)
    }
  }

  private generatePlaneId(): string {
    return `plane_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
  }
}

4.2 AI决策算法

4.2.1 蒙特卡洛树搜索(MCTS)

export class MCTSEngine {
  private readonly EXPLORATION_CONSTANT = Math.sqrt(2)
  private readonly MAX_ITERATIONS = 1000
  private readonly MAX_SIMULATION_DEPTH = 50

  selectBestMove(gameState: GameState): Position {
    const rootNode = new MCTSNode(gameState, null, null)
    
    for (let i = 0; i < this.MAX_ITERATIONS; i++) {
      // 1. 选择
      const leafNode = this.selectLeaf(rootNode)
      
      // 2. 扩展 
      const newNode = this.expand(leafNode)
      
      // 3. 模拟
      const result = this.simulate(newNode || leafNode)
      
      // 4. 回传
      this.backpropagate(newNode || leafNode, result)
    }

    // 选择最佳子节点
    return this.getBestChild(rootNode).move!
  }

  private selectLeaf(node: MCTSNode): MCTSNode {
    let current = node
    
    while (!current.isLeaf() && !current.gameState.isTerminal) {
      current = this.getBestUCTChild(current)
    }
    
    return current
  }

  private getBestUCTChild(node: MCTSNode): MCTSNode {
    let bestChild: MCTSNode | null = null
    let bestValue = -Infinity

    for (const child of node.children) {
      const uctValue = this.calculateUCT(child, node.visits)
      
      if (uctValue > bestValue) {
        bestValue = uctValue
        bestChild = child
      }
    }

    return bestChild!
  }

  private calculateUCT(node: MCTSNode, parentVisits: number): number {
    if (node.visits === 0) return Infinity
    
    const exploitation = node.wins / node.visits
    const exploration = this.EXPLORATION_CONSTANT * 
      Math.sqrt(Math.log(parentVisits) / node.visits)
    
    return exploitation + exploration
  }

  private expand(node: MCTSNode): MCTSNode | null {
    if (node.gameState.isTerminal) return null

    const availableMoves = this.getAvailableMoves(node.gameState)
    const untriedMoves = availableMoves.filter(move => 
      !node.children.some(child => this.movesEqual(child.move!, move))
    )

    if (untriedMoves.length === 0) return null

    // 选择随机未尝试的移动
    const randomMove = untriedMoves[Math.floor(Math.random() * untriedMoves.length)]
    const newGameState = this.applyMove(node.gameState, randomMove)
    const newNode = new MCTSNode(newGameState, node, randomMove)
    
    node.addChild(newNode)
    return newNode
  }

  private simulate(node: MCTSNode): GameResult {
    let currentState = { ...node.gameState }
    let depth = 0

    while (!currentState.isTerminal && depth < this.MAX_SIMULATION_DEPTH) {
      const availableMoves = this.getAvailableMoves(currentState)
      const randomMove = availableMoves[Math.floor(Math.random() * availableMoves.length)]
      
      currentState = this.applyMove(currentState, randomMove)
      depth++
    }

    return this.evaluateGameState(currentState)
  }

  private backpropagate(node: MCTSNode, result: GameResult): void {
    let current: MCTSNode | null = node

    while (current !== null) {
      current.visits++
      
      if (result.winner === current.gameState.currentPlayer) {
        current.wins++
      }
      
      current = current.parent
    }
  }
}

class MCTSNode {
  public visits: number = 0
  public wins: number = 0
  public children: MCTSNode[] = []

  constructor(
    public gameState: GameState,
    public parent: MCTSNode | null,
    public move: Position | null
  ) {}

  isLeaf(): boolean {
    return this.children.length === 0
  }

  addChild(child: MCTSNode): void {
    this.children.push(child)
  }
}

4.2.2 神经网络评估函数

export class NeuralNetworkEvaluator {
  private model: any // 实际使用TensorFlow.js或其他ML库
  private featureExtractor: FeatureExtractor

  constructor() {
    this.featureExtractor = new FeatureExtractor()
    this.loadModel()
  }

  async evaluatePosition(gameState: GameState, position: Position): Promise<number> {
    const features = this.featureExtractor.extractFeatures(gameState, position)
    const normalized = this.normalizeFeatures(features)
    
    const prediction = await this.model.predict(normalized)
    return prediction[0] // 返回概率值
  }

  async evaluatePlacement(planes: Plane[]): Promise<number> {
    const features = this.featureExtractor.extractPlacementFeatures(planes)
    const normalized = this.normalizeFeatures(features)
    
    const prediction = await this.model.predict(normalized)
    return prediction[0] // 返回布置质量评分
  }

  private loadModel(): void {
    // 加载预训练的神经网络模型
    // 实际实现中需要使用TensorFlow.js等库
  }
}

export class FeatureExtractor {
  extractFeatures(gameState: GameState, position: Position): number[] {
    const features: number[] = []
    
    // 位置特征
    features.push(
      position.x / 10,  // 归一化x坐标
      position.y / 10,  // 归一化y坐标
      this.getDistanceToCenter(position), // 到中心距离
      this.getDistanceToEdge(position)    // 到边缘距离
    )

    // 邻域特征
    const neighbors = this.getNeighborStates(gameState, position)
    features.push(...neighbors)

    // 历史攻击特征
    const attackDensity = this.calculateAttackDensity(gameState, position)
    features.push(attackDensity)

    // 模式特征
    const patternFeatures = this.extractPatternFeatures(gameState, position)
    features.push(...patternFeatures)

    return features
  }

  extractPlacementFeatures(planes: Plane[]): number[] {
    const features: number[] = []

    // 分散度特征
    const dispersion = this.calculateDispersion(planes)
    features.push(dispersion)

    // 边缘使用特征
    const edgeUsage = this.calculateEdgeUsage(planes)
    features.push(edgeUsage)

    // 对称性特征
    const symmetry = this.calculateSymmetry(planes)
    features.push(symmetry)

    // 覆盖度特征
    const coverage = this.calculateCoverage(planes)
    features.push(coverage)

    return features
  }

  private getDistanceToCenter(position: Position): number {
    const center = { x: 5.5, y: 5.5 }
    const dx = position.x - center.x
    const dy = position.y - center.y
    return Math.sqrt(dx * dx + dy * dy) / 7.07 // 归一化到[0,1]
  }

  private getDistanceToEdge(position: Position): number {
    const distances = [
      position.x - 1,      // 左边缘
      10 - position.x,     // 右边缘
      position.y - 1,      // 上边缘
      10 - position.y      // 下边缘
    ]
    return Math.min(...distances) / 10 // 归一化
  }
}

4.3 网络同步算法

4.3.1 状态同步协议

export interface GameSyncProtocol {
  // 状态同步消息
  syncGameState(gameState: GameState): void
  requestStateSync(): void
  
  // 增量更新
  sendDelta(delta: GameStateDelta): void
  applyDelta(delta: GameStateDelta): void
  
  // 冲突解决
  resolveConflict(localState: GameState, remoteState: GameState): GameState
}

export class GameStateSynchronizer implements GameSyncProtocol {
  private localState: GameState
  private lastSyncTimestamp: number = 0
  private pendingDeltas: GameStateDelta[] = []

  constructor(initialState: GameState) {
    this.localState = initialState
  }

  syncGameState(gameState: GameState): void {
    // 检查时间戳,防止过期状态
    if (gameState.timestamp <= this.lastSyncTimestamp) {
      return
    }

    // 应用远程状态
    this.localState = this.mergeStates(this.localState, gameState)
    this.lastSyncTimestamp = gameState.timestamp
    
    // 清除已应用的增量更新
    this.clearAppliedDeltas(gameState.timestamp)
  }

  sendDelta(delta: GameStateDelta): void {
    // 记录本地更改
    this.pendingDeltas.push(delta)
    
    // 应用到本地状态
    this.applyDelta(delta)
    
    // 发送给其他玩家
    this.broadcastDelta(delta)
  }

  applyDelta(delta: GameStateDelta): void {
    switch (delta.type) {
      case 'ATTACK':
        this.applyAttackDelta(delta)
        break
      case 'PLACE_PLANE':
        this.applyPlacementDelta(delta)
        break
      case 'GAME_PHASE_CHANGE':
        this.applyPhaseChangeDelta(delta)
        break
    }
  }

  resolveConflict(localState: GameState, remoteState: GameState): GameState {
    // 基于时间戳的简单冲突解决
    if (remoteState.timestamp > localState.timestamp) {
      return remoteState
    }

    // 如果时间戳相同,使用更详细的冲突解决策略
    if (remoteState.timestamp === localState.timestamp) {
      return this.mergeConflictingStates(localState, remoteState)
    }

    return localState
  }

  private mergeStates(local: GameState, remote: GameState): GameState {
    // 合并两个游戏状态,优先使用更新的数据
    return {
      ...local,
      ...remote,
      timestamp: Math.max(local.timestamp, remote.timestamp),
      moveHistory: this.mergeMoveHistory(local.moveHistory, remote.moveHistory)
    }
  }

  private mergeMoveHistory(localHistory: Move[], remoteHistory: Move[]): Move[] {
    const combined = [...localHistory, ...remoteHistory]
    
    // 去重并按时间戳排序
    const unique = combined.filter((move, index, arr) => 
      arr.findIndex(m => m.id === move.id) === index
    )
    
    return unique.sort((a, b) => a.timestamp - b.timestamp)
  }
}

export interface GameStateDelta {
  id: string
  type: DeltaType
  timestamp: number
  playerId: string
  data: any
  checksum: string
}

enum DeltaType {
  ATTACK = 'ATTACK',
  PLACE_PLANE = 'PLACE_PLANE', 
  GAME_PHASE_CHANGE = 'GAME_PHASE_CHANGE',
  PLAYER_JOIN = 'PLAYER_JOIN',
  PLAYER_LEAVE = 'PLAYER_LEAVE'
}

4.3.2 断线重连机制

export class ReconnectionManager {
  private connectionState: ConnectionState = ConnectionState.CONNECTED
  private reconnectAttempts: number = 0
  private maxReconnectAttempts: number = 5
  private baseDelay: number = 1000
  private maxDelay: number = 30000
  
  private gameStateSyncQueue: GameStateDelta[] = []
  private heartbeatInterval: NodeJS.Timeout | null = null

  async handleDisconnection(): Promise<void> {
    this.connectionState = ConnectionState.DISCONNECTED
    this.stopHeartbeat()
    
    // 开始重连流程
    await this.attemptReconnection()
  }

  private async attemptReconnection(): Promise<void> {
    while (
      this.reconnectAttempts < this.maxReconnectAttempts && 
      this.connectionState !== ConnectionState.CONNECTED
    ) {
      this.connectionState = ConnectionState.RECONNECTING
      
      const delay = Math.min(
        this.baseDelay * Math.pow(2, this.reconnectAttempts),
        this.maxDelay
      )
      
      await this.delay(delay)
      
      try {
        await this.connect()
        await this.syncAfterReconnection()
        
        this.connectionState = ConnectionState.CONNECTED
        this.reconnectAttempts = 0
        this.startHeartbeat()
        
      } catch (error) {
        this.reconnectAttempts++
        console.warn(`重连失败 (${this.reconnectAttempts}/${this.maxReconnectAttempts}):`, error)
      }
    }

    if (this.connectionState !== ConnectionState.CONNECTED) {
      this.connectionState = ConnectionState.FAILED
      throw new Error('重连失败,超过最大重试次数')
    }
  }

  private async syncAfterReconnection(): Promise<void> {
    // 请求完整的游戏状态同步
    const currentGameState = await this.requestFullGameState()
    
    // 应用断线期间可能错过的更新
    await this.applyMissedUpdates(currentGameState)
    
    // 重新发送断线期间的本地操作
    await this.resendPendingOperations()
  }

  private startHeartbeat(): void {
    this.heartbeatInterval = setInterval(() => {
      this.sendHeartbeat().catch(() => {
        this.handleDisconnection()
      })
    }, 10000) // 10秒心跳
  }

  private stopHeartbeat(): void {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval)
      this.heartbeatInterval = null
    }
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
}

enum ConnectionState {
  CONNECTED = 'CONNECTED',
  DISCONNECTED = 'DISCONNECTED',
  RECONNECTING = 'RECONNECTING',
  FAILED = 'FAILED'
}

5. 数据存储设计

5.1 数据库表结构

5.1.1 用户相关表

-- 用户基本信息表
CREATE TABLE users (
    user_id VARCHAR(50) PRIMARY KEY,
    nickname VARCHAR(100) NOT NULL,
    avatar_url VARCHAR(500),
    level INT DEFAULT 1,
    experience INT DEFAULT 0,
    total_games INT DEFAULT 0,
    total_wins INT DEFAULT 0,
    win_rate DECIMAL(5,4) DEFAULT 0.0000,
    ranking INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    last_login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    INDEX idx_ranking (ranking),
    INDEX idx_level (level),
    INDEX idx_last_login (last_login_at)
);

-- 用户成就表
CREATE TABLE user_achievements (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id VARCHAR(50) NOT NULL,
    achievement_id VARCHAR(100) NOT NULL,
    progress INT DEFAULT 0,
    max_progress INT NOT NULL,
    is_unlocked BOOLEAN DEFAULT FALSE,
    unlocked_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    UNIQUE KEY uk_user_achievement (user_id, achievement_id),
    INDEX idx_user_unlocked (user_id, is_unlocked)
);

-- 好友关系表
CREATE TABLE friendships (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    requester_id VARCHAR(50) NOT NULL,
    addressee_id VARCHAR(50) NOT NULL,
    status ENUM('PENDING', 'ACCEPTED', 'BLOCKED') NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    FOREIGN KEY (requester_id) REFERENCES users(user_id),
    FOREIGN KEY (addressee_id) REFERENCES users(user_id),
    UNIQUE KEY uk_friendship (requester_id, addressee_id),
    INDEX idx_status (status)
);

5.1.2 游戏相关表

-- 游戏记录表
CREATE TABLE game_records (
    game_id VARCHAR(50) PRIMARY KEY,
    game_type ENUM('AI', 'ONLINE', 'LOCAL') NOT NULL,
    game_mode VARCHAR(50) DEFAULT 'STANDARD',
    status ENUM('IN_PROGRESS', 'COMPLETED', 'ABANDONED') NOT NULL,
    winner_id VARCHAR(50),
    total_moves INT DEFAULT 0,
    game_duration INT DEFAULT 0, -- 秒
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    completed_at TIMESTAMP NULL,
    
    INDEX idx_game_type (game_type),
    INDEX idx_status (status),
    INDEX idx_created_at (created_at),
    FOREIGN KEY (winner_id) REFERENCES users(user_id)
);

-- 游戏参与者表
CREATE TABLE game_players (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    game_id VARCHAR(50) NOT NULL,
    user_id VARCHAR(50),
    player_type ENUM('HUMAN', 'AI') NOT NULL,
    ai_difficulty ENUM('BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'EXPERT', 'MASTER') NULL,
    player_index TINYINT NOT NULL, -- 1 or 2
    result ENUM('WIN', 'LOSE', 'DRAW') NULL,
    moves_made INT DEFAULT 0,
    planes_destroyed INT DEFAULT 0,
    accuracy_rate DECIMAL(5,4) DEFAULT 0.0000,
    
    FOREIGN KEY (game_id) REFERENCES game_records(game_id),
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    UNIQUE KEY uk_game_player (game_id, player_index)
);

-- 游戏操作记录表
CREATE TABLE game_moves (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    game_id VARCHAR(50) NOT NULL,
    player_id VARCHAR(50) NOT NULL,
    move_sequence INT NOT NULL,
    move_type ENUM('PLACE_PLANE', 'ATTACK') NOT NULL,
    position_x TINYINT NOT NULL,
    position_y TINYINT NOT NULL,
    result_type ENUM('MISS', 'HIT', 'DESTROYED') NULL,
    plane_id VARCHAR(50) NULL,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    FOREIGN KEY (game_id) REFERENCES game_records(game_id),
    INDEX idx_game_sequence (game_id, move_sequence),
    INDEX idx_timestamp (timestamp)
);

-- 飞机布置记录表
CREATE TABLE plane_placements (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    game_id VARCHAR(50) NOT NULL,
    player_id VARCHAR(50) NOT NULL,
    plane_id VARCHAR(50) NOT NULL,
    center_x TINYINT NOT NULL,
    center_y TINYINT NOT NULL,
    direction ENUM('UP', 'DOWN', 'LEFT', 'RIGHT') NOT NULL,
    is_destroyed BOOLEAN DEFAULT FALSE,
    destroyed_at_move INT NULL,
    
    FOREIGN KEY (game_id) REFERENCES game_records(game_id),
    UNIQUE KEY uk_game_player_plane (game_id, player_id, plane_id)
);

5.2 缓存设计

5.2.1 Redis 缓存结构

// 游戏会话缓存
interface GameSessionCache {
  key: string // `game_session:${gameId}`
  data: {
    gameState: GameState
    playerIds: string[]
    lastActivity: number
    roomCode?: string
  }
  ttl: number // 3600 seconds (1 hour)
}

// 用户在线状态缓存
interface UserOnlineCache {
  key: string // `user_online:${userId}`
  data: {
    isOnline: boolean
    lastSeen: number
    currentGameId?: string
    socketId?: string
  }
  ttl: number // 1800 seconds (30 minutes)
}

// 房间匹配缓存
interface RoomMatchmakingCache {
  key: string // `room_queue:${difficulty}`
  data: {
    waitingPlayers: Array<{
      userId: string
      joinedAt: number
      preferences: MatchmakingPreferences
    }>
  }
  ttl: number // 300 seconds (5 minutes)
}

// 排行榜缓存
interface LeaderboardCache {
  key: string // `leaderboard:${type}:${timeframe}`
  data: Array<{
    userId: string
    nickname: string
    score: number
    rank: number
  }>
  ttl: number // 3600 seconds (1 hour)
}

5.2.2 缓存操作类

export class GameCacheManager {
  private redis: Redis

  constructor(redisClient: Redis) {
    this.redis = redisClient
  }

  async cacheGameSession(gameId: string, gameState: GameState): Promise<void> {
    const key = `game_session:${gameId}`
    const data = {
      gameState,
      playerIds: gameState.players.map(p => p.userId),
      lastActivity: Date.now(),
      roomCode: gameState.roomCode
    }

    await this.redis.

setex(key, 3600, JSON.stringify(data))
  }

  async getGameSession(gameId: string): Promise<GameState | null> {
    const key = `game_session:${gameId}`
    const cached = await this.redis.get(key)
    
    if (cached) {
      const data = JSON.parse(cached)
      return data.gameState
    }
    
    return null
  }

  async updateUserOnlineStatus(userId: string, isOnline: boolean): Promise<void> {
    const key = `user_online:${userId}`
    const data = {
      isOnline,
      lastSeen: Date.now(),
      socketId: isOnline ? this.getCurrentSocketId(userId) : undefined
    }
    
    await this.redis.setex(key, 1800, JSON.stringify(data))
  }

  async cacheLeaderboard(type: string, timeframe: string, data: any[]): Promise<void> {
    const key = `leaderboard:${type}:${timeframe}`
    await this.redis.setex(key, 3600, JSON.stringify(data))
  }

  async getCachedLeaderboard(type: string, timeframe: string): Promise<any[] | null> {
    const key = `leaderboard:${type}:${timeframe}`
    const cached = await this.redis.get(key)
    return cached ? JSON.parse(cached) : null
  }

  private getCurrentSocketId(userId: string): string | undefined {
    // 获取用户当前的Socket连接ID
    return undefined // 实现细节
  }
}

6. 安全性设计

6.1 数据安全

6.1.1 输入验证

export class InputValidator {
  static validatePosition(position: Position): ValidationResult {
    const errors: string[] = []
    
    if (position.x < 1 || position.x > 10) {
      errors.push('X坐标必须在1-10范围内')
    }
    
    if (position.y < 1 || position.y > 10) {
      errors.push('Y坐标必须在1-10范围内')
    }
    
    const coordinatePattern = /^[A-J]_([1-9]|10)$/
    if (!coordinatePattern.test(position.coordinate)) {
      errors.push('坐标格式不正确')
    }
    
    return {
      isValid: errors.length === 0,
      errors
    }
  }

  static validateGameMove(move: GameMove, gameState: GameState): ValidationResult {
    const errors: string[] = []
    
    // 验证玩家权限
    if (move.playerId !== gameState.currentPlayer) {
      errors.push('不是当前玩家的回合')
    }
    
    // 验证游戏阶段
    if (gameState.currentPhase !== GamePhase.BATTLING) {
      errors.push('当前游戏阶段不允许攻击')
    }
    
    // 验证位置是否已被攻击
    const isAlreadyAttacked = gameState.moveHistory.some(
      historyMove => 
        historyMove.position.x === move.position.x && 
        historyMove.position.y === move.position.y
    )
    
    if (isAlreadyAttacked) {
      errors.push('该位置已被攻击过')
    }
    
    // 验证位置
    const positionValidation = this.validatePosition(move.position)
    errors.push(...positionValidation.errors)
    
    return {
      isValid: errors.length === 0,
      errors
    }
  }

  static sanitizeUserInput(input: string): string {
    return input
      .trim()
      .replace(/[<>\"'&]/g, '') // 移除潜在的XSS字符
      .substring(0, 100) // 限制长度
  }
}

interface ValidationResult {
  isValid: boolean
  errors: string[]
}

6.1.2 反作弊系统

export class AntiCheatSystem {
  private suspiciousActivityDetector: SuspiciousActivityDetector
  private gameIntegrityChecker: GameIntegrityChecker
  
  constructor() {
    this.suspiciousActivityDetector = new SuspiciousActivityDetector()
    this.gameIntegrityChecker = new GameIntegrityChecker()
  }

  validateGameAction(action: GameAction, context: GameContext): AntiCheatResult {
    const checks: AntiCheatCheck[] = [
      this.checkActionTiming(action, context),
      this.checkActionSequence(action, context),
      this.checkPlayerBehavior(action, context),
      this.checkClientIntegrity(action, context)
    ]

    const failures = checks.filter(check => !check.passed)
    
    return {
      isValid: failures.length === 0,
      riskScore: this.calculateRiskScore(failures),
      violations: failures.map(f => f.violation),
      action: failures.length > 0 ? this.determineAction(failures) : 'ALLOW'
    }
  }

  private checkActionTiming(action: GameAction, context: GameContext): AntiCheatCheck {
    const timeSinceLastAction = action.timestamp - context.lastActionTimestamp
    const minHumanReactionTime = 200 // 毫秒
    const maxReasonableThinkTime = 300000 // 5分钟

    if (timeSinceLastAction < minHumanReactionTime) {
      return {
        passed: false,
        violation: 'INHUMAN_REACTION_TIME',
        severity: 'HIGH',
        details: `动作间隔过短: ${timeSinceLastAction}ms`
      }
    }

    if (timeSinceLastAction > maxReasonableThinkTime) {
      return {
        passed: false,
        violation: 'SUSPICIOUS_DELAY',
        severity: 'LOW',
        details: `动作间隔过长: ${timeSinceLastAction}ms`
      }
    }

    return { passed: true, violation: 'NONE', severity: 'NONE' }
  }

  private checkActionSequence(action: GameAction, context: GameContext): AntiCheatCheck {
    // 检查动作序列的合理性
    const recentActions = context.actionHistory.slice(-10)
    
    // 检查是否有不自然的攻击模式
    if (this.detectUnhumanAttackPattern(recentActions)) {
      return {
        passed: false,
        violation: 'PATTERN_ANALYSIS_FAIL',
        severity: 'MEDIUM',
        details: '检测到非人类攻击模式'
      }
    }

    return { passed: true, violation: 'NONE', severity: 'NONE' }
  }

  private detectUnhumanAttackPattern(actions: GameAction[]): boolean {
    if (actions.length < 5) return false

    // 检查过度规律的攻击模式
    const positions = actions.map(a => a.position)
    const intervals = this.calculateIntervals(positions)
    
    // 如果攻击位置间隔过于规律,可能是脚本行为
    const variance = this.calculateVariance(intervals)
    return variance < 0.1 // 方差过小表示过于规律
  }

  private calculateRiskScore(failures: AntiCheatCheck[]): number {
    let score = 0
    for (const failure of failures) {
      switch (failure.severity) {
        case 'LOW': score += 1; break
        case 'MEDIUM': score += 3; break
        case 'HIGH': score += 5; break
      }
    }
    return Math.min(score, 10) // 最高10分
  }
}

interface AntiCheatCheck {
  passed: boolean
  violation: string
  severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'NONE'
  details?: string
}

interface AntiCheatResult {
  isValid: boolean
  riskScore: number
  violations: string[]
  action: 'ALLOW' | 'WARNING' | 'RESTRICT' | 'BAN'
}

6.2 网络安全

6.2.1 API 安全

export class APISecurityMiddleware {
  private rateLimiter: RateLimiter
  private tokenValidator: TokenValidator
  
  constructor() {
    this.rateLimiter = new RateLimiter({
      windowMs: 15 * 60 * 1000, // 15分钟
      max: 100 // 每个IP最多100次请求
    })
    this.tokenValidator = new TokenValidator()
  }

  async validateRequest(req: Request): Promise<SecurityValidationResult> {
    // 1. 速率限制检查
    const rateLimitResult = await this.rateLimiter.checkLimit(req.ip)
    if (!rateLimitResult.allowed) {
      return {
        isValid: false,
        reason: 'RATE_LIMIT_EXCEEDED',
        statusCode: 429
      }
    }

    // 2. Token验证
    const token = this.extractToken(req)
    if (!token) {
      return {
        isValid: false,
        reason: 'MISSING_TOKEN',
        statusCode: 401
      }
    }

    const tokenValidation = await this.tokenValidator.validate(token)
    if (!tokenValidation.isValid) {
      return {
        isValid: false,
        reason: 'INVALID_TOKEN',
        statusCode: 401
      }
    }

    // 3. 请求签名验证
    const signatureValidation = this.validateSignature(req)
    if (!signatureValidation.isValid) {
      return {
        isValid: false,
        reason: 'INVALID_SIGNATURE',
        statusCode: 400
      }
    }

    return {
      isValid: true,
      userId: tokenValidation.userId
    }
  }

  private extractToken(req: Request): string | null {
    const authHeader = req.headers.authorization
    if (authHeader && authHeader.startsWith('Bearer ')) {
      return authHeader.substring(7)
    }
    return null
  }

  private validateSignature(req: Request): { isValid: boolean } {
    const signature = req.headers['x-signature'] as string
    const timestamp = req.headers['x-timestamp'] as string
    const body = JSON.stringify(req.body)
    
    if (!signature || !timestamp) {
      return { isValid: false }
    }

    // 时间戳检查(防重放攻击)
    const now = Date.now()
    const requestTime = parseInt(timestamp)
    if (Math.abs(now - requestTime) > 300000) { // 5分钟有效期
      return { isValid: false }
    }

    // 签名验证
    const expectedSignature = this.generateSignature(body, timestamp)
    return { isValid: signature === expectedSignature }
  }

  private generateSignature(body: string, timestamp: string): string {
    const crypto = require('crypto')
    const secretKey = process.env.API_SECRET_KEY
    const data = `${timestamp}.${body}`
    
    return crypto
      .createHmac('sha256', secretKey)
      .update(data)
      .digest('hex')
  }
}

interface SecurityValidationResult {
  isValid: boolean
  reason?: string
  statusCode?: number
  userId?: string
}

6.2.2 WebSocket 安全

export class WebSocketSecurityManager {
  private connectionLimiter: Map<string, number> = new Map()
  private blacklistedIPs: Set<string> = new Set()
  
  validateConnection(socket: any, request: any): ConnectionValidationResult {
    const ip = this.getClientIP(request)
    
    // IP黑名单检查
    if (this.blacklistedIPs.has(ip)) {
      return {
        allowed: false,
        reason: 'IP_BLACKLISTED'
      }
    }

    // 连接数限制
    const currentConnections = this.connectionLimiter.get(ip) || 0
    if (currentConnections >= 5) { // 每个IP最多5个连接
      return {
        allowed: false,
        reason: 'TOO_MANY_CONNECTIONS'
      }
    }

    // Token验证
    const token = this.extractTokenFromSocket(socket)
    if (!token) {
      return {
        allowed: false,
        reason: 'MISSING_AUTH_TOKEN'
      }
    }

    return {
      allowed: true,
      ip,
      token
    }
  }

  onConnection(socket: any, ip: string): void {
    // 增加连接计数
    const current = this.connectionLimiter.get(ip) || 0
    this.connectionLimiter.set(ip, current + 1)

    // 设置连接超时
    socket.setTimeout(60000) // 60秒无活动则断开

    // 监听消息频率
    let messageCount = 0
    const messageWindow = setInterval(() => {
      if (messageCount > 100) { // 每秒超过100条消息
        socket.close(1008, 'Message rate too high')
      }
      messageCount = 0
    }, 1000)

    socket.on('message', () => {
      messageCount++
    })

    socket.on('close', () => {
      this.onDisconnection(ip)
      clearInterval(messageWindow)
    })
  }

  private onDisconnection(ip: string): void {
    const current = this.connectionLimiter.get(ip) || 0
    if (current <= 1) {
      this.connectionLimiter.delete(ip)
    } else {
      this.connectionLimiter.set(ip, current - 1)
    }
  }

  private getClientIP(request: any): string {
    return request.headers['x-forwarded-for'] || 
           request.connection.remoteAddress || 
           request.socket.remoteAddress
  }

  private extractTokenFromSocket(socket: any): string | null {
    // 从WebSocket握手中提取认证token
    const url = new URL(socket.url, 'http://localhost')
    return url.searchParams.get('token')
  }
}

interface ConnectionValidationResult {
  allowed: boolean
  reason?: string
  ip?: string
  token?: string
}

7. 部署与运维

7.1 系统架构图

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   小程序客户端    │    │    H5客户端     │    │   原生APP客户端  │
└─────────┬───────┘    └─────────┬───────┘    └─────────┬───────┘
          │                      │                      │
          └──────────────────────┼──────────────────────┘
                                 │
                    ┌─────────────▼──────────────┐
                    │      负载均衡器 (Nginx)     │
                    └─────────────┬──────────────┘
                                 │
                    ┌─────────────▼──────────────┐
                    │      API网关服务          │
                    │   - 认证鉴权              │
                    │   - 请求路由              │
                    │   - 限流熔断              │
                    └─────────────┬──────────────┘
                                 │
            ┌────────────────────┼────────────────────┐
            │                    │                    │
  ┌─────────▼─────────┐ ┌────────▼────────┐ ┌────────▼────────┐
  │   游戏服务集群    │ │   用户服务      │ │   匹配服务      │
  │ - 游戏逻辑处理    │ │ - 用户管理      │ │ - 房间匹配      │
  │ - 实时通信        │ │ - 好友系统      │ │ - 排行榜        │
  │ - AI决策          │ │ - 成就系统      │ │ - 数据统计      │
  └─────────┬─────────┘ └────────┬────────┘ └────────┬────────┘
            │                    │                    │
            └────────────────────┼────────────────────┘
                                 │
                    ┌─────────────▼──────────────┐
                    │      数据存储层            │
                    │ ┌─────────┐ ┌─────────────┐│
                    │ │ MongoDB │ │   Redis     ││
                    │ │ 主数据库 │ │   缓存      ││
                    │ └─────────┘ └─────────────┘│
                    └────────────────────────────┘

7.2 部署配置

7.2.1 Docker 配置

# Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:18-alpine AS production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .

USER nextjs

EXPOSE 3000
ENV NODE_ENV production

CMD ["node", "dist/server.js"]
# docker-compose.yml
version: '3.8'

services:
  game-api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=mongodb
      - REDIS_HOST=redis
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - mongodb
      - redis
    restart: unless-stopped

  mongodb:
    image: mongo:6.0
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
    volumes:
      - mongo_data:/data/db
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/ssl
    depends_on:
      - game-api
    restart: unless-stopped

volumes:
  mongo_data:
  redis_data:

7.2.2 Nginx 配置

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream api_servers {
        least_conn;
        server game-api:3000 max_fails=3 fail_timeout=30s;
        # 可以添加更多服务器实例
        # server game-api-2:3000 max_fails=3 fail_timeout=30s;
    }

    # 限流配置
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=ws:10m rate=5r/s;

    server {
        listen 80;
        server_name your-domain.com;
        
        # HTTP重定向到HTTPS
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name your-domain.com;

        ssl_certificate /etc/ssl/cert.pem;
        ssl_certificate_key /etc/ssl/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;

        # API请求
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            
            proxy_pass http://api_servers;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }

        # WebSocket连接
        location /socket.io/ {
            limit_req zone=ws burst=10 nodelay;
            
            proxy_pass http://api_servers;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # WebSocket超时设置
            proxy_read_timeout 86400s;
            proxy_send_timeout 86400s;
        }

        # 静态资源
        location /static/ {
            expires 30d;
            add_header Cache-Control "public, immutable";
            add_header X-Content-Type-Options nosniff;
        }

        # 健康检查
        location /health {
            access_log off;
            proxy_pass http://api_servers;
        }
    }
}

7.3 监控与日志

7.3.1 应用监控

// 监控指标收集
export class MetricsCollector {
  private metrics: Map<string, number> = new Map()
  
  incrementCounter(name: string, tags?: Record<string, string>): void {
    const key = this.buildMetricKey(name, tags)
    const current = this.metrics.get(key) || 0
    this.metrics.set(key, current + 1)
  }

  recordHistogram(name: string, value: number, tags?: Record<string, string>): void {
    const key = this.buildMetricKey(name, tags)
    // 实际实现中可以使用更复杂的直方图数据结构
    this.metrics.set(`${key}.sum`, (this.metrics.get(`${key}.sum`) || 0) + value)
    this.metrics.set(`${key}.count`, (this.metrics.get(`${key}.count`) || 0) + 1)
  }

  getMetrics(): Record<string, number> {
    return Object.fromEntries(this.metrics)
  }

  // 游戏特定指标
  recordGameStarted(gameType: string): void {
    this.incrementCounter('games.started', { type: gameType })
  }

  recordGameCompleted(gameType: string, duration: number): void {
    this.incrementCounter('games.completed', { type: gameType })
    this.recordHistogram('game.duration', duration, { type: gameType })
  }

  recordAIDecisionTime(difficulty: string, decisionTime: number): void {
    this.recordHistogram('ai.decision_time', decisionTime, { difficulty })
  }

  recordPlayerAction(action: string): void {
    this.incrementCounter('player.actions', { action })
  }

  private buildMetricKey(name: string, tags?: Record<string, string>): string {
    if (!tags || Object.keys(tags).length === 0) {
      return name
    }
    
    const tagString = Object.entries(tags)
      .map(([key, value]) => `${key}:${value}`)
      .sort()
      .join(',')
    
    return `${name}{${tagString}}`
  }
}

7.3.2 日志系统

// 结构化日志记录
export class GameLogger {
  private logger: any // 实际使用winston或其他日志库

  constructor() {
    this.logger = this.createLogger()
  }

  logGameStart(gameId: string, players: string[], gameType: string): void {
    this.logger.info('Game started', {
      event: 'GAME_START',
      gameId,
      players,
      gameType,
      timestamp: new Date().toISOString()
    })
  }

  logGameMove(gameId: string, playerId: string, move: GameMove): void {
    this.logger.info('Game move', {
      event: 'GAME_MOVE',
      gameId,
      playerId,
      position: move.position,
      result: move.result,
      timestamp: new Date().toISOString()
    })
  }

  logGameEnd(gameId: string, winner: string, duration: number): void {
    this.logger.info('Game ended', {
      event: 'GAME_END',
      gameId,
      winner,
      duration,
      timestamp: new Date().toISOString()
    })
  }

  logError(error: Error, context?: any): void {
    this.logger.error('Application error', {
      event: 'ERROR',
      message: error.message,
      stack: error.stack,
      context,
      timestamp: new Date().toISOString()
    })
  }

  logSecurityEvent(event: string, details: any): void {
    this.logger.warn('Security event', {
      event: 'SECURITY',
      type: event,
      details,
      timestamp: new Date().toISOString()
    })
  }

  private createLogger(): any {
    // 实际实现中创建winston logger或其他日志实例
    return {
      info: (message: string, meta: any) => console.log(JSON.stringify({ level: 'info', message, ...meta })),
      warn: (message: string, meta: any) => console.warn(JSON.stringify({ level: 'warn', message, ...meta })),
      error: (message: string, meta: any) => console.error(JSON.stringify({ level: 'error', message, ...meta }))
    }
  }
}

8. 测试策略

8.1 测试分层策略

8.1.1 单元测试

// 游戏逻辑单元测试示例
describe('PlaneGeometry', () => {
  describe('generatePlanePositions', () => {
    test('should generate correct positions for UP direction', () => {
      const center = { x: 5, y: 5, coordinate: 'E_5' }
      const positions = PlaneGeometry.generatePlanePositions(center, 'UP')
      
      expect(positions).toHaveLength(11)
      expect(positions[0]).toEqual({ x: 5, y: 3, coordinate: 'E_3' }) // 机头
      expect(positions).toContainEqual({ x: 3, y: 4, coordinate: 'C_4' }) // 左翼尖
      expect(positions).toContainEqual({ x: 7, y: 4, coordinate: 'G_4' }) // 右翼尖
    })

    test('should throw error for invalid center position', () => {
      const center = { x: 1, y: 1, coordinate: 'A_1' }
      
      expect(() => {
        PlaneGeometry.generatePlanePositions(center, 'UP')
      }).toThrow('Position would exceed board boundaries')
    })
  })

  describe('validatePlanePosition', () => {
    test('should validate position within board boundaries', () => {
      const center = { x: 5, y: 5, coordinate: 'E_5' }
      const isValid = PlaneGeometry.validatePlanePosition(center, 'UP', 10)
      
      expect(isValid).toBe(true)
    })

    test('should reject position near edges', () => {
      const center = { x: 2, y: 2, coordinate: 'B_2' }
      const isValid = PlaneGeometry.validatePlanePosition(center, 'UP', 10)
      
      expect(isValid).toBe(false)
    })
  })
})

// AI决策测试
describe('AIDecisionEngine', () => {
  let engine: AIDecisionEngine
  let mockGameState: GameState

  beforeEach(() => {
    engine = new AIDecisionEngine('INTERMEDIATE')
    mockGameState = createMockGameState()
  })

  test('should select valid attack position', async () => {
    const position = await engine.selectAttackPosition(mockGameState)
    
    expect(position.x).toBeGreaterThanOrEqual(1)
    expect(position.x).toBeLessThanOrEqual(10)
    expect(position.y).toBeGreaterThanOrEqual(1)
    expect(position.y).toBeLessThanOrEqual(10)
  })

  test('should not attack same position twice', async () => {
    // 模拟已攻击的位置
    mockGameState.moveHistory.push({
      id: 'move1',
      position: { x: 5, y: 5, coordinate: 'E_5' },
      playerId: 'ai',
      timestamp: Date.now(),
      result: { type: 'MISS', value: 0 }
    })

    const position = await engine.selectAttackPosition(mockGameState)
    
    expect(position).not.toEqual({ x: 5, y: 5, coordinate: 'E_5' })
  })
})

8.1.2 集成测试

// 游戏流程集成测试
describe('Game Integration Tests', () => {
  let gameEngine: GameEngine
  let player1Id: string
  let player2Id: string
  let gameId: string

  beforeEach(async () => {
    gameEngine = new GameEngine()
    player1Id = 'player1'
    player2Id = 'player2'
  })

  test('complete game flow', async () => {
    // 1. 创建游戏
    const game = await gameEngine.createGame({
      gameType: 'ONLINE',
      players: [player1Id, player2Id]
    })
    gameId = game.gameId

    expect(game.status).toBe('WAITING_FOR_PLANES')
    
    // 2. 玩家布置飞机
    const player1Planes = generateTestPlanes()
    const player2Planes = generateTestPlanes()

    await gameEngine.placePlanes(gameId, player1Id, player1Planes)
    await gameEngine.placePlanes(gameId, player2Id, player2Planes)

    const updatedGame = await gameEngine.getGame(gameId)
    expect(updatedGame.status).toBe('IN_PROGRESS')

    // 3. 进行攻击
    let gameResult = await gameEngine.processAttack(gameId, player1Id, { x: 5, y: 5, coordinate: 'E_5' })
    expect(gameResult.isValid).toBe(true)

    // 4. 验证游戏状态更新
    const finalGame = await gameEngine.getGame(gameId)
    expect(finalGame.moveHistory).toHaveLength(1)
    expect(finalGame.currentPlayer).toBe(player2Id)
  })

  test('should handle game completion', async () => {
    // 创建接近结束的游戏状态
    const game = await createNearEndGame()
    
    // 执行最后一击
    const result = await gameEngine.processAttack(
      game.gameId, 
      game.currentPlayer, 
      getWinningMove(game)
    )

    expect(result.gameEnded).toBe(true)
    expect(result.winner).toBe(game.currentPlayer)

    // 验证游戏记录已保存
    const gameRecord = await gameEngine.getGameRecord(game.gameId)
    expect(gameRecord.status).toBe('COMPLETED')
    expect(gameRecord.winner).toBe(game.currentPlayer)
  })
})

8.1.3 端到端测试

// E2E测试 - 使用Playwright或Selenium
describe('Game E2E Tests', () => {
  let page: Page
  let gameUrl: string

  beforeEach(async () => {
    page = await browser.newPage()
    gameUrl = await setupTestGame()
  })

  test('complete multiplayer game session', async () => {
    // 1. 第一个玩家加入游戏
    await page.goto(gameUrl)
    await page.waitForSelector('[data-testid="game-board"]')

    // 2. 布置飞机
    await placePlanesViaUI(page)
    await page.click('[data-testid="confirm-placement"]')

    // 3. 等待对手加入(模拟第二个玩家)
    await simulateSecondPlayer()

    // 4. 进行攻击
    await page.click('[data-testid="cell-5-5"]')
    await page.waitForSelector('[data-testid="attack-result"]')

    // 5. 验证UI更新
    const resultText = await page.textContent('[data-testid="attack-result"]')
    expect(resultText).toMatch(/命中|未命中|击毁/)

    // 6. 验证游戏状态
    const gameStatus = await page.textContent('[data-testid="game-status"]')
    expect(gameStatus).toContain('对手回合')
  })

  test('AI game session', async () => {
    await page.goto(`${gameUrl}?mode=ai`)
    
    // 选择AI难度
    await page.click('[data-testid="ai-difficulty-intermediate"]')
    
    // 布置飞机
    await page.click('[data-testid="auto-place-planes"]')
    await page.click('[data-testid="start-game"]')

    // 进行攻击
    await page.click('[data-testid="cell-3-3"]')
    
    // 等待AI响应
    await page.waitForSelector('[data-testid="ai-thinking"]', { state: 'hidden' })
    
    // 验证AI已做出攻击
    const aiMoveIndicator = await page.locator('[data-testid="ai-move-indicator"]')
    await expect(aiMoveIndicator).toBeVisible()
  })

  async function placePlanesViaUI(page: Page): Promise<void> {
    // 模拟拖拽布置飞机
    const plane1 = page.locator('[data-testid="plane-template"]').first()
    const targetCell = page.locator('[data-testid="cell-3-3"]')
    
    await plane1.dragTo(targetCell)
    
    // 验证飞机已正确放置
    await expect(page.locator('[data-testid="placed-plane-1"]')).toBeVisible()
  }
})

8.2 性能测试

8.2.1 负载测试

// 负载测试脚本
import { check, sleep } from 'k6'
import http from 'k6/http'
import ws from 'k6/ws'

export let options = {
  stages: [
    { duration: '2m', target: 100 }, // 逐步增加到100个用户
    { duration: '5m', target: 100 }, // 保持100个用户5分钟
    { duration: '2m', target: 200 }, // 增加到200个用户
    { duration: '5m', target: 200 }, // 保持200个用户
    { duration: '2m', target: 0 },   // 逐步减少到0
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95%的请求在500ms内完成
    http_req_failed: ['rate<0.1'],    // 错误率小于10%
  },
}

export default function () {
  // 测试API性能
  testAPIPerformance()
  
  // 测试WebSocket性能
  testWebSocketPerformance()
  
  sleep(1)
}

function testAPIPerformance() {
  // 用户登录
  let loginResponse = http.post(`${__ENV.API_BASE_URL}/api/auth/login`, {
    username: `user_${__VU}_${__ITER}`,
    password: 'testpass123'
  })
  
  check(loginResponse, {
    'login successful': (r) => r.status === 200,
    'login response time OK': (r) => r.timings.duration < 200,
  })

  let authToken = loginResponse.json('token')
  let headers = { 'Authorization': `Bearer ${authToken}` }

  // 创建游戏
  let createGameResponse = http.post(`${__ENV.API_BASE_URL}/api/games`, {
    gameType: 'AI',
    difficulty: 'INTERMEDIATE'
  }, { headers })

  check(createGameResponse, {
    'create game successful': (r) => r.status === 201,
    'create game response time OK': (r) => r.timings.duration < 300,
  })

  // 获取游戏状态
  let gameId = createGameResponse.json('gameId')
  let getGameResponse = http.get(`${__ENV.API_BASE_URL}/api/games/${gameId}`, { headers })

  check(getGameResponse, {
    'get game successful': (r) => r.status === 200,
    'get game response time OK': (r) => r.timings.duration < 100,
  })
}

function testWebSocketPerformance() {
  let url = `ws://${__ENV.WS_HOST}/socket.io/?token=${__ENV.TEST_TOKEN}`
  
  let response = ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      console.log('WebSocket connected')
      
      // 发送游戏操作
      socket.send(JSON.stringify({
        type: 'ATTACK',
        position: { x: 5, y: 5 },
        gameId: 'test-game-id'
      }))
    })

    socket.on('message', function (message) {
      let data = JSON.parse(message)
      check(data, {
        'message received': (data) => data !== null,
        'message has type': (data) => 'type' in data,
      })
    })

    sleep(10) // 保持连接10秒
  })

  check(response, {
    'WebSocket connection successful': (r) => r && r.status === 101,
  })
}

9. 项目管理

9.1 开发计划

9.1.1 项目里程碑

gantt
    title 打飞机小程序开发计划
    dateFormat  YYYY-MM-DD
    section 第一阶段
    需求分析     :done, req, 2025-01-01, 1w
    技术选型     :done, tech, after req, 3d
    架构设计     :done, arch, after tech, 1w
    
    section 第二阶段
    基础框架搭建 :frame, after arch, 1w
    用户系统开发 :user, after frame, 2w
    游戏核心逻辑 :core, after frame, 3w
    
    section 第三阶段
    AI系统开发   :ai, after core, 2w
    在线对战功能 :online, after user, 2w
    UI界面开发   :ui, after core, 2w
    
    section 第四阶段
    功能测试     :test, after ai, 1w
    性能优化     :perf, after test, 1w
    部署上线     :deploy, after perf, 3d

9.1.2 任务分解

interface ProjectTask {
  id: string
  name: string
  description: string
  assignee: string
  status: TaskStatus
  priority: Priority
  estimatedHours: number
  actualHours?: number
  dependencies: string[]
  startDate: Date
  endDate: Date
}

enum TaskStatus {
  TODO = 'TODO',
  IN_PROGRESS = 'IN_PROGRESS',
  IN_REVIEW = 'IN_REVIEW', 
  DONE = 'DONE',
  BLOCKED = 'BLOCKED'
}

enum Priority {
  LOW = 'LOW',
  MEDIUM = 'MEDIUM',
  HIGH = 'HIGH',
  CRITICAL = 'CRITICAL'
}

// 任务列表示例
const projectTasks: ProjectTask[] = [
  {
    id: 'CORE-001',
    name: '飞机几何模型实现',
    description: '实现飞机位置生成、碰撞检测等核心几何算法',
    assignee: '后端开发工程师',
    status: TaskStatus.TODO,
    priority: Priority.HIGH,
    estimatedHours: 16,
    dependencies: [],
    startDate: new Date('2025-01-15'),
    endDate: new Date('2025-01-17')
  },
  {
    id: 'AI-001',
    name: '概率热图算法',
    description: '实现基于贝叶斯推理的攻击概率计算',
    assignee: 'AI工程师',
    status: TaskStatus.TODO,
    priority: Priority.HIGH,
    estimatedHours: 24,
    dependencies: ['CORE-001'],
    startDate: new Date('2025-01-18'),
    endDate: new Date('2025-01-21')
  },
  {
    id: 'UI-001',
    name: '游戏棋盘组件',
    description: '开发可交互的游戏棋盘UI组件',
    assignee: '前端开发工程师',
    status: TaskStatus.TODO,
    priority: Priority.MEDIUM,
    estimatedHours: 20,
    dependencies: ['CORE-001'],
    startDate: new Date('2025-01-18'),
    endDate: new Date('2025-01-22')
  }
]

9.2 质量保证

9.2.1 代码规范

// ESLint 配置示例
module.exports = {
  extends: [
    '@typescript-eslint/recommended',
    'eslint:recommended'
  ],
  rules: {
    // 代码风格
    'indent': ['error', 2],
    'quotes': ['error', 'single'],
    'semi': ['error', 'never'],
    
    // TypeScript规则
    '@typescript-eslint/explicit-function-return-type': 'error',
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn',
    
    // 游戏特定规则
    'prefer-const': 'error',
    'no-magic-numbers': ['warn', { ignore: [-1, 0, 1, 2] }],
    
    // 性能相关
    'no-console': 'warn',
    'no-debugger': 'error'
  }
}

// Prettier 配置
module.exports = {
  semi: false,
  singleQuote: true,
  tabWidth: 2,
  trailingComma: 'es5',
  printWidth: 100,
  bracketSpacing: true,
  arrowParens: 'avoid'
}

9.2.2 代码审查流程

# 代码审查清单

## 功能性检查
- [ ] 功能是否按需求正确实现
- [ ] 边界条件是否正确处理
- [ ] 错误处理是否完善
- [ ] 单元测试是否覆盖主要场景

## 性能检查
- [ ] 算法复杂度是否合理
- [ ] 是否存在内存泄漏风险
- [ ] 数据库查询是否优化
- [ ] 缓存策略是否得当

## 安全性检查
- [ ] 输入验证是否充分
- [ ] 权限控制是否正确
- [ ] 敏感信息是否泄露
- [ ] SQL注入等安全风险

## 代码质量
- [ ] 命名是否清晰易懂
- [ ] 代码结构是否合理
- [ ] 注释是否充分
- [ ] 是否遵循团队规范

10. 风险评估与应对

10.1 技术风险

10.1.1 性能风险

风险描述: 在高并发场景下系统性能下降

风险等级: 中等

影响分析:

  • 用户体验下降
  • 服务器资源消耗过高
  • 运营成本增加

应对策略:

  1. 预防措施:

    • 实施负载测试
    • 优化关键算法
    • 采用缓存策略
    • 数据库索引优化
  2. 应急处理:

    • 自动扩容机制
    • 熔断降级策略
    • 优雅降级功能

10.1.2 AI算法风险

风险描述: AI决策质量不佳或响应时间过长

风险等级: 中等

应对策略:

  1. 多级AI难度系统
  2. 决策时间限制机制
  3. 备用简单策略算法
  4. 持续的模型训练和优化

10.2 业务风险

10.2.1 用户流失风险

风险描述: 游戏平衡性问题导致用户流失

应对策略:

  1. 数据驱动的平衡性调整
  2. A/B测试验证
  3. 用户反馈收集机制
  4. 快速迭代能力

10.2.2 竞品风险

风险描述: 市场出现更优秀的同类产品

应对策略:

  1. 持续创新和功能迭代
  2. 建立用户社区
  3. 差异化竞争策略
  4. 快速响应市场变化

11. 总结

11.1 项目特色

本需求说明书为打飞机小程序项目提供了全面详尽的技术指导,具有以下特色:

  1. 完整的技术栈: 从前端到后端从数据库到缓存覆盖了现代Web应用的所有技术层面

  2. 先进的AI算法: 集成了蒙特卡洛树搜索、神经网络评估、概率推理等前沿AI技术

  3. 企业级架构: 采用微服务架构、容器化部署、监控体系等企业级最佳实践

  4. 全面的安全考虑: 从输入验证到反作弊,从网络安全到数据保护的完整安全体系

  5. 可扩展设计: 支持多平台部署、水平扩展、功能模块化的灵活架构

11.2 实施建议

  1. 分阶段开发: 按照MVP->完整功能->优化增强的顺序逐步实施

  2. 技术选型灵活性: 根据团队技术栈和项目预算灵活调整技术选择

  3. 持续集成: 建立CI/CD流水线确保代码质量和部署效率

  4. 数据驱动: 建立完善的数据收集和分析体系,指导产品优化

  5. 用户体验优先: 在所有技术决策中优先考虑用户体验

11.3 预期成果

通过本需求说明书的指导实施,预期能够开发出一个:

  • 技术先进: 采用最新技术栈和算法
  • 性能优秀: 支持高并发、低延迟的游戏体验
  • 功能完善: 涵盖单机AI、在线对战、社交系统等完整功能
  • 安全可靠: 具备企业级安全防护能力
  • 可扩展: 支持后续功能扩展和技术升级

的高质量小程序产品。


文档版本: v1.0
最后更新: 2025年9月
文档状态: 完成

本需求说明书为打飞机小程序的完整开发提供了详尽的技术指导,涵盖了从架构设计到具体实现的所有关键环节,可作为独立的开发指南使用。