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