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

3178 lines
88 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 打飞机小程序完整需求说明书
## 文档信息
| 项目 | 详情 |
|------|------|
| 项目名称 | 打飞机对战小程序 |
| 文档版本 | v1.0 |
| 创建日期 | 2025年9月 |
| 目标平台 | 微信小程序 / 原生APP |
| 开发语言 | TypeScript + JavaScript |
## 此文档已经过期失效 **参考价值可以忽略**
> 此文档已经过期失效 **参考价值可以忽略**
## 1. 项目概述
### 1.1 项目背景
打飞机是一款经典的双人对战策略游戏,玩家需要在棋盘上布置飞机并猜测对手飞机位置。本项目旨在开发一个现代化的小程序版本,支持人机对战和在线对战功能。
### 1.2 项目目标
- 提供流畅的游戏体验和直观的用户界面
- 实现智能AI对战系统
- 支持在线实时对战功能
- 建立完整的用户系统和数据统计
- 确保跨平台兼容性和高性能表现
### 1.3 目标用户群体
- **主要用户**: 8-60岁喜欢益智游戏的用户
- **使用场景**: 休闲娱乐、朋友对战、碎片时间游戏
- **用户特征**: 追求简单易上手但有一定策略性的游戏
## 2. 功能需求规格
### 2.1 核心功能模块
#### 2.1.1 用户系统
```typescript
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. 棋盘系统
```typescript
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. 飞机系统
```typescript
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. 游戏状态管理
```typescript
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难度等级
```typescript
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决策算法接口
```typescript
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. 房间系统
```typescript
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. 实时通信协议
```typescript
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 游戏界面布局
```typescript
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 响应式设计要求
```css
/* 屏幕适配断点 */
@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
```typescript
// 原因:
// 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
```typescript
// 游戏状态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 + 自定义组件
```typescript
// 自定义游戏棋盘组件
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
```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
```typescript
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
```typescript
// 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 核心算法框架
```typescript
// 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 概率计算算法
```typescript
// 概率热力图实现
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 模式识别算法
```typescript
// 模式分析器
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 飞机位置生成算法
```typescript
// 飞机几何模型定义
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 碰撞检测算法
```typescript
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 自动布置算法
```typescript
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)
```typescript
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 神经网络评估函数
```typescript
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 状态同步协议
```typescript
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 断线重连机制
```typescript
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 用户相关表
```sql
-- 用户基本信息表
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 游戏相关表
```sql
-- 游戏记录表
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 缓存结构
```typescript
// 游戏会话缓存
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 缓存操作类
```typescript
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 输入验证
```typescript
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 反作弊系统
```typescript
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 安全
```typescript
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 安全
```typescript
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
# 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"]
```
```yaml
# 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
# 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 应用监控
```typescript
// 监控指标收集
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 日志系统
```typescript
// 结构化日志记录
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 单元测试
```typescript
// 游戏逻辑单元测试示例
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 集成测试
```typescript
// 游戏流程集成测试
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 端到端测试
```typescript
// 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 负载测试
```typescript
// 负载测试脚本
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 项目里程碑
```mermaid
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 任务分解
```typescript
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 代码规范
```typescript
// 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 代码审查流程
```markdown
# 代码审查清单
## 功能性检查
- [ ] 功能是否按需求正确实现
- [ ] 边界条件是否正确处理
- [ ] 错误处理是否完善
- [ ] 单元测试是否覆盖主要场景
## 性能检查
- [ ] 算法复杂度是否合理
- [ ] 是否存在内存泄漏风险
- [ ] 数据库查询是否优化
- [ ] 缓存策略是否得当
## 安全性检查
- [ ] 输入验证是否充分
- [ ] 权限控制是否正确
- [ ] 敏感信息是否泄露
- [ ] 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月
**文档状态**: 完成
本需求说明书为打飞机小程序的完整开发提供了详尽的技术指导,涵盖了从架构设计到具体实现的所有关键环节,可作为独立的开发指南使用。