Compare commits

...

5 Commits

Author SHA1 Message Date
史悦
dafb706202 任务 2025-12-16 08:57:19 +08:00
史悦
b8d081bd8a feat: 添加完整的项目设计文档,替换旧的原型需求文档 2025-09-11 15:13:38 +08:00
史悦
e922bacfb8 已经过期失效 2025-09-11 14:08:02 +08:00
史悦
29b7844909 原型设计完成 2025-09-11 13:34:29 +08:00
史悦
adbd205b7e 统一样式 2025-09-11 13:17:27 +08:00
64 changed files with 8549 additions and 503 deletions

View File

@@ -0,0 +1,53 @@
---
description: Guidelines for creating and maintaining Cursor rules to ensure consistency and effectiveness.
globs: .cursor/rules/*.mdc
alwaysApply: true
---
- **Required Rule Structure:**
```markdown
---
description: Clear, one-line description of what the rule enforces
globs: path/to/files/*.ext, other/path/**/*
alwaysApply: boolean
---
- **Main Points in Bold**
- Sub-points with details
- Examples and explanations
```
- **File References:**
- Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files
- Example: [prisma.mdc](mdc:.cursor/rules/prisma.mdc) for rule references
- Example: [schema.prisma](mdc:prisma/schema.prisma) for code references
- **Code Examples:**
- Use language-specific code blocks
```typescript
// ✅ DO: Show good examples
const goodExample = true;
// ❌ DON'T: Show anti-patterns
const badExample = false;
```
- **Rule Content Guidelines:**
- Start with high-level overview
- Include specific, actionable requirements
- Show examples of correct implementation
- Reference existing code when possible
- Keep rules DRY by referencing other rules
- **Rule Maintenance:**
- Update rules when new patterns emerge
- Add examples from actual codebase
- Remove outdated patterns
- Cross-reference related rules
- **Best Practices:**
- Use bullet points for clarity
- Keep descriptions concise
- Include both DO and DON'T examples
- Reference actual code over theoretical examples
- Use consistent formatting across rules

View File

@@ -0,0 +1,335 @@
---
description:
globs:
alwaysApply: false
---
- **Global CLI Commands**
- Task Master now provides a global CLI through the `task-manager` command
- All functionality from `scripts/dev.js` is available through this interface
- Install globally with `npm install -g claude-task-manager` or use locally via `npx`
- Use `task-manager <command>` instead of `node scripts/dev.js <command>`
- Examples:
- `task-manager list` instead of `node scripts/dev.js list`
- `task-manager next` instead of `node scripts/dev.js next`
- `task-manager expand --id=3` instead of `node scripts/dev.js expand --id=3`
- All commands accept the same options as their script equivalents
- The CLI provides additional commands like `task-manager init` for project setup
- **Development Workflow Process**
- Start new projects by running `task-manager init` or `node scripts/dev.js parse-prd --input=<prd-file.txt>` to generate initial tasks.json
- Begin coding sessions with `task-manager list` to see current tasks, status, and IDs
- Analyze task complexity with `task-manager analyze-complexity --research` before breaking down tasks
- Select tasks based on dependencies (all marked 'done'), priority level, and ID order
- Clarify tasks by checking task files in tasks/ directory or asking for user input
- View specific task details using `task-manager show <id>` to understand implementation requirements
- Break down complex tasks using `task-manager expand --id=<id>` with appropriate flags
- Clear existing subtasks if needed using `task-manager clear-subtasks --id=<id>` before regenerating
- Implement code following task details, dependencies, and project standards
- Verify tasks according to test strategies before marking as complete
- Mark completed tasks with `task-manager set-status --id=<id> --status=done`
- Update dependent tasks when implementation differs from original plan
- Generate task files with `task-manager generate` after updating tasks.json
- Maintain valid dependency structure with `task-manager fix-dependencies` when needed
- Respect dependency chains and task priorities when selecting work
- Report progress regularly using the list command
- **Task Complexity Analysis**
- Run `node scripts/dev.js analyze-complexity --research` for comprehensive analysis
- Review complexity report in scripts/task-complexity-report.json
- Or use `node scripts/dev.js complexity-report` for a formatted, readable version of the report
- Focus on tasks with highest complexity scores (8-10) for detailed breakdown
- Use analysis results to determine appropriate subtask allocation
- Note that reports are automatically used by the expand command
- **Task Breakdown Process**
- For tasks with complexity analysis, use `node scripts/dev.js expand --id=<id>`
- Otherwise use `node scripts/dev.js expand --id=<id> --subtasks=<number>`
- Add `--research` flag to leverage Perplexity AI for research-backed expansion
- Use `--prompt="<context>"` to provide additional context when needed
- Review and adjust generated subtasks as necessary
- Use `--all` flag to expand multiple pending tasks at once
- If subtasks need regeneration, clear them first with `clear-subtasks` command
- **Implementation Drift Handling**
- When implementation differs significantly from planned approach
- When future tasks need modification due to current implementation choices
- When new dependencies or requirements emerge
- Call `node scripts/dev.js update --from=<futureTaskId> --prompt="<explanation>"` to update tasks.json
- **Task Status Management**
- Use 'pending' for tasks ready to be worked on
- Use 'done' for completed and verified tasks
- Use 'deferred' for postponed tasks
- Add custom status values as needed for project-specific workflows
- **Task File Format Reference**
```
# Task ID: <id>
# Title: <title>
# Status: <status>
# Dependencies: <comma-separated list of dependency IDs>
# Priority: <priority>
# Description: <brief description>
# Details:
<detailed implementation notes>
# Test Strategy:
<verification approach>
```
- **Command Reference: parse-prd**
- Legacy Syntax: `node scripts/dev.js parse-prd --input=<prd-file.txt>`
- CLI Syntax: `task-manager parse-prd --input=<prd-file.txt>`
- Description: Parses a PRD document and generates a tasks.json file with structured tasks
- Parameters:
- `--input=<file>`: Path to the PRD text file (default: sample-prd.txt)
- `--num-tasks=<number>`: Number of tasks to generate (if not specified, AI will determine based on complexity)
- `--knowledge-base=<path>, -k`: Path to business knowledge documents to use as context
- Example: `task-manager parse-prd --input=requirements.txt`
- Notes: Will overwrite existing tasks.json file. Use with caution.
- **Command Reference: update**
- Legacy Syntax: `node scripts/dev.js update --from=<id> --prompt="<prompt>"`
- CLI Syntax: `task-manager update --from=<id> --prompt="<prompt>"`
- Description: Updates tasks with ID >= specified ID based on the provided prompt
- Parameters:
- `--from=<id>`: Task ID from which to start updating (required)
- `--prompt="<text>"`: Explanation of changes or new context (required)
- Example: `task-manager update --from=4 --prompt="Now we are using Express instead of Fastify."`
- Notes: Only updates tasks not marked as 'done'. Completed tasks remain unchanged.
- **Command Reference: generate**
- Legacy Syntax: `node scripts/dev.js generate`
- CLI Syntax: `task-manager generate`
- Description: Generates individual task files in tasks/ directory based on tasks.json
- Parameters:
- `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json')
- `--output=<dir>, -o`: Output directory (default: 'tasks')
- Example: `task-manager generate`
- Notes: Overwrites existing task files. Creates tasks/ directory if needed.
- **Command Reference: set-status**
- Legacy Syntax: `node scripts/dev.js set-status --id=<id> --status=<status>`
- CLI Syntax: `task-manager set-status --id=<id> --status=<status>`
- Description: Updates the status of a specific task in tasks.json
- Parameters:
- `--id=<id>`: ID of the task to update (required)
- `--status=<status>`: New status value (required)
- Example: `task-manager set-status --id=3 --status=done`
- Notes: Common values are 'done', 'pending', and 'deferred', but any string is accepted.
- **Command Reference: list**
- Legacy Syntax: `node scripts/dev.js list`
- CLI Syntax: `task-manager list`
- Description: Lists all tasks in tasks.json with IDs, titles, and status
- Parameters:
- `--status=<status>, -s`: Filter by status
- `--with-subtasks`: Show subtasks for each task
- `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json')
- Example: `task-manager list`
- Notes: Provides quick overview of project progress. Use at start of sessions.
- **Command Reference: expand**
- Legacy Syntax: `node scripts/dev.js expand --id=<id> [--num=<number>] [--research] [--prompt="<context>"]`
- CLI Syntax: `task-manager expand --id=<id> [--num=<number>] [--research] [--prompt="<context>"]`
- Description: Expands a task with subtasks for detailed implementation
- Parameters:
- `--id=<id>`: ID of task to expand (required unless using --all)
- `--all`: Expand all pending tasks, prioritized by complexity
- `--num=<number>`: Number of subtasks to generate (if not specified, AI will determine based on task complexity)
- `--research`: Use Perplexity AI for research-backed generation
- `--prompt="<text>"`: Additional context for subtask generation
- `--force`: Regenerate subtasks even for tasks that already have them
- `--knowledge-base=<path>, -k`: Path to business knowledge documents to use as context
- Example: `task-manager expand --id=3 --num=5 --research --prompt="Focus on security aspects"`
- Notes: Uses complexity report recommendations if available. If no subtask count is specified, AI will analyze task complexity and determine the optimal number of subtasks.
- **Command Reference: analyze-complexity**
- Legacy Syntax: `node scripts/dev.js analyze-complexity [options]`
- CLI Syntax: `task-manager analyze-complexity [options]`
- Description: Analyzes task complexity and generates expansion recommendations
- Parameters:
- `--output=<file>, -o`: Output file path (default: scripts/task-complexity-report.json)
- `--model=<model>, -m`: Override LLM model to use
- `--threshold=<number>, -t`: Minimum score for expansion recommendation (default: 5)
- `--file=<path>, -f`: Use alternative tasks.json file
- `--research, -r`: Use Perplexity AI for research-backed analysis
- Example: `task-manager analyze-complexity --research`
- Notes: Report includes complexity scores, recommended subtasks, and tailored prompts.
- **Command Reference: clear-subtasks**
- Legacy Syntax: `node scripts/dev.js clear-subtasks --id=<id>`
- CLI Syntax: `task-manager clear-subtasks --id=<id>`
- Description: Removes subtasks from specified tasks to allow regeneration
- Parameters:
- `--id=<id>`: ID or comma-separated IDs of tasks to clear subtasks from
- `--all`: Clear subtasks from all tasks
- Examples:
- `task-manager clear-subtasks --id=3`
- `task-manager clear-subtasks --id=1,2,3`
- `task-manager clear-subtasks --all`
- Notes:
- Task files are automatically regenerated after clearing subtasks
- Can be combined with expand command to immediately generate new subtasks
- Works with both parent tasks and individual subtasks
- **Task Structure Fields**
- **id**: Unique identifier for the task (Example: `1`)
- **title**: Brief, descriptive title (Example: `"Initialize Repo"`)
- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`)
- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`)
- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2]`)
- Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending)
- This helps quickly identify which prerequisite tasks are blocking work
- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`)
- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`)
- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`)
- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`)
- **Environment Variables Configuration**
- **ANTHROPIC_API_KEY** (Required): Your Anthropic API key for Claude (Example: `ANTHROPIC_API_KEY=sk-ant-api03-...`)
- **MODEL** (Default: `"claude-3-7-sonnet-20250219"`): Claude model to use (Example: `MODEL=claude-3-opus-20240229`)
- **MAX_TOKENS** (Default: `"4000"`): Maximum tokens for responses (Example: `MAX_TOKENS=8000`)
- **TEMPERATURE** (Default: `"0.7"`): Temperature for model responses (Example: `TEMPERATURE=0.5`)
- **DEBUG** (Default: `"false"`): Enable debug logging (Example: `DEBUG=true`)
- **LOG_LEVEL** (Default: `"info"`): Console output level (Example: `LOG_LEVEL=debug`)
- **DEFAULT_SUBTASKS** (Default: `"3"`): Default subtask count (Example: `DEFAULT_SUBTASKS=5`)
- **DEFAULT_PRIORITY** (Default: `"medium"`): Default priority (Example: `DEFAULT_PRIORITY=high`)
- **PROJECT_NAME** (Default: `"MCP SaaS MVP"`): Project name in metadata (Example: `PROJECT_NAME=My Awesome Project`)
- **PROJECT_VERSION** (Default: `"1.0.0"`): Version in metadata (Example: `PROJECT_VERSION=2.1.0`)
- **PERPLEXITY_API_KEY**: For research-backed features (Example: `PERPLEXITY_API_KEY=pplx-...`)
- **PERPLEXITY_MODEL** (Default: `"sonar-medium-online"`): Perplexity model (Example: `PERPLEXITY_MODEL=sonar-large-online`)
- **Determining the Next Task**
- Run `task-manager next` to show the next task to work on
- The next command identifies tasks with all dependencies satisfied
- Tasks are prioritized by priority level, dependency count, and ID
- The command shows comprehensive task information including:
- Basic task details and description
- Implementation details
- Subtasks (if they exist)
- Contextual suggested actions
- Recommended before starting any new development work
- Respects your project's dependency structure
- Ensures tasks are completed in the appropriate sequence
- Provides ready-to-use commands for common task actions
- **Viewing Specific Task Details**
- Run `task-manager show <id>` or `task-manager show --id=<id>` to view a specific task
- Use dot notation for subtasks: `task-manager show 1.2` (shows subtask 2 of task 1)
- Displays comprehensive information similar to the next command, but for a specific task
- For parent tasks, shows all subtasks and their current status
- For subtasks, shows parent task information and relationship
- Provides contextual suggested actions appropriate for the specific task
- Useful for examining task details before implementation or checking status
- **Managing Task Dependencies**
- Use `task-manager add-dependency --id=<id> --depends-on=<id>` to add a dependency
- Use `task-manager remove-dependency --id=<id> --depends-on=<id>` to remove a dependency
- The system prevents circular dependencies and duplicate dependency entries
- Dependencies are checked for existence before being added or removed
- Task files are automatically regenerated after dependency changes
- Dependencies are visualized with status indicators in task listings and files
- **Command Reference: add-dependency**
- Legacy Syntax: `node scripts/dev.js add-dependency --id=<id> --depends-on=<id>`
- CLI Syntax: `task-manager add-dependency --id=<id> --depends-on=<id>`
- Description: Adds a dependency relationship between two tasks
- Parameters:
- `--id=<id>`: ID of task that will depend on another task (required)
- `--depends-on=<id>`: ID of task that will become a dependency (required)
- Example: `task-manager add-dependency --id=22 --depends-on=21`
- Notes: Prevents circular dependencies and duplicates; updates task files automatically
- **Command Reference: remove-dependency**
- Legacy Syntax: `node scripts/dev.js remove-dependency --id=<id> --depends-on=<id>`
- CLI Syntax: `task-manager remove-dependency --id=<id> --depends-on=<id>`
- Description: Removes a dependency relationship between two tasks
- Parameters:
- `--id=<id>`: ID of task to remove dependency from (required)
- `--depends-on=<id>`: ID of task to remove as a dependency (required)
- Example: `task-manager remove-dependency --id=22 --depends-on=21`
- Notes: Checks if dependency actually exists; updates task files automatically
- **Command Reference: validate-dependencies**
- Legacy Syntax: `node scripts/dev.js validate-dependencies [options]`
- CLI Syntax: `task-manager validate-dependencies [options]`
- Description: Checks for and identifies invalid dependencies in tasks.json and task files
- Parameters:
- `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json')
- Example: `task-manager validate-dependencies`
- Notes:
- Reports all non-existent dependencies and self-dependencies without modifying files
- Provides detailed statistics on task dependency state
- Use before fix-dependencies to audit your task structure
- **Command Reference: fix-dependencies**
- Legacy Syntax: `node scripts/dev.js fix-dependencies [options]`
- CLI Syntax: `task-manager fix-dependencies [options]`
- Description: Finds and fixes all invalid dependencies in tasks.json and task files
- Parameters:
- `--file=<path>, -f`: Use alternative tasks.json file (default: 'tasks/tasks.json')
- Example: `task-manager fix-dependencies`
- Notes:
- Removes references to non-existent tasks and subtasks
- Eliminates self-dependencies (tasks depending on themselves)
- Regenerates task files with corrected dependencies
- Provides detailed report of all fixes made
- **Command Reference: complexity-report**
- Legacy Syntax: `node scripts/dev.js complexity-report [options]`
- CLI Syntax: `task-manager complexity-report [options]`
- Description: Displays the task complexity analysis report in a formatted, easy-to-read way
- Parameters:
- `--file=<path>, -f`: Path to the complexity report file (default: 'scripts/task-complexity-report.json')
- Example: `task-manager complexity-report`
- Notes:
- Shows tasks organized by complexity score with recommended actions
- Provides complexity distribution statistics
- Displays ready-to-use expansion commands for complex tasks
- If no report exists, offers to generate one interactively
- **Command Reference: add-task**
- CLI Syntax: `task-manager add-task [options]`
- Description: Add a new task to tasks.json using AI
- Parameters:
- `--file=<path>, -f`: Path to the tasks file (default: 'tasks/tasks.json')
- `--prompt=<text>, -p`: Description of the task to add (required)
- `--dependencies=<ids>, -d`: Comma-separated list of task IDs this task depends on
- `--priority=<priority>`: Task priority (high, medium, low) (default: 'medium')
- Example: `task-manager add-task --prompt="Create user authentication using Auth0"`
- Notes: Uses AI to convert description into structured task with appropriate details
- **Command Reference: init**
- CLI Syntax: `task-manager init`
- Description: Initialize a new project with Task Master structure
- Parameters: None
- Example: `task-manager init`
- Notes:
- Creates initial project structure with required files
- Prompts for project settings if not provided
- Merges with existing files when appropriate
- Can be used to bootstrap a new Task Master project quickly
- **Code Analysis & Refactoring Techniques**
- **Top-Level Function Search**
- Use grep pattern matching to find all exported functions across the codebase
- Command: `grep -E "export (function|const) \w+|function \w+\(|const \w+ = \(|module\.exports" --include="*.js" -r ./`
- Benefits:
- Quickly identify all public API functions without reading implementation details
- Compare functions between files during refactoring (e.g., monolithic to modular structure)
- Verify all expected functions exist in refactored modules
- Identify duplicate functionality or naming conflicts
- Usage examples:
- When migrating from `scripts/dev.js` to modular structure: `grep -E "function \w+\(" scripts/dev.js`
- Check function exports in a directory: `grep -E "export (function|const)" scripts/modules/`
- Find potential naming conflicts: `grep -E "function (get|set|create|update)\w+\(" -r ./`
- Variations:
- Add `-n` flag to include line numbers
- Add `--include="*.ts"` to filter by file extension
- Use with `| sort` to alphabetize results
- Integration with refactoring workflow:
- Start by mapping all functions in the source file
- Create target module files based on function grouping
- Verify all functions were properly migrated
- Check for any unintentional duplications or omissions

View File

@@ -0,0 +1,73 @@
---
description: Guidelines for continuously improving Cursor rules based on emerging code patterns and best practices.
globs: **/*
alwaysApply: true
---
- **Rule Improvement Triggers:**
- New code patterns not covered by existing rules
- Repeated similar implementations across files
- Common error patterns that could be prevented
- New libraries or tools being used consistently
- Emerging best practices in the codebase
- **Analysis Process:**
- Compare new code with existing rules
- Identify patterns that should be standardized
- Look for references to external documentation
- Check for consistent error handling patterns
- Monitor test patterns and coverage
- **Rule Updates:**
- **Add New Rules When:**
- A new technology/pattern is used in 3+ files
- Common bugs could be prevented by a rule
- Code reviews repeatedly mention the same feedback
- New security or performance patterns emerge
- **Modify Existing Rules When:**
- Better examples exist in the codebase
- Additional edge cases are discovered
- Related rules have been updated
- Implementation details have changed
- **Example Pattern Recognition:**
```typescript
// If you see repeated patterns like:
const data = await prisma.user.findMany({
select: { id: true, email: true },
where: { status: 'ACTIVE' }
});
// Consider adding to [prisma.mdc](mdc:.cursor/rules/prisma.mdc):
// - Standard select fields
// - Common where conditions
// - Performance optimization patterns
```
- **Rule Quality Checks:**
- Rules should be actionable and specific
- Examples should come from actual code
- References should be up to date
- Patterns should be consistently enforced
- **Continuous Improvement:**
- Monitor code review comments
- Track common development questions
- Update rules after major refactors
- Add links to relevant documentation
- Cross-reference related rules
- **Rule Deprecation:**
- Mark outdated patterns as deprecated
- Remove rules that no longer apply
- Update references to deprecated rules
- Document migration paths for old patterns
- **Documentation Updates:**
- Keep examples synchronized with code
- Update references to external docs
- Maintain links between related rules
- Document breaking changes
Follow [cursor_rules.mdc](mdc:.cursor/rules/cursor_rules.mdc) for proper rule formatting and structure.

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Skill(task-manager-skill)",
"Bash(task-manager next:*)",
"Bash(task-manager show:*)"
]
}
}

3
.env Normal file
View File

@@ -0,0 +1,3 @@
GOOGLE_API_KEY=sk-QodfEMFLud0D6mtKQwtgcOUA6Vh2djaSPZSKuu9r4bopnKLF
GEMINI_BASE_URL=https://newapi.shizhuoran.top
GEMINI_MODEL=gemini-2.5-pro

View File

@@ -1,155 +0,0 @@
# “打飞机”小程序App原型设计需求文档
## 1. 概述
本文档旨在为“打飞机”小程序的核心界面提供清晰的设计需求主要面向UI/UX设计人员用于指导原型设计。文档充分考虑了手机触摸屏的操作特性。核心界面包括“准备页面”和“打击页面”。
---
## 2. 核心页面设计需求
### 2.1 准备页面
**页面目标:** 玩家在此页面中通过触控操作完成飞机的布局,并确认进入“准备就绪”状态。
#### 2.1.1 界面核心元素
1. **游戏棋盘 (10x10 网格):**
* **功能:** 用于放置飞机的主要区域。
* **设计要求:**
* 应为一个 10x10 的网格布局,每个格子大小一致,适合触控点击。
* 格子有清晰的边框。
* **触控反馈:** 点击格子时,应有视觉反馈(如背景色瞬时变化或涟漪效果)。
* 坐标标识在棋盘的上方和左侧应有坐标轴上方为字母A-J左侧为数字1-10
2. **飞机状态与选择区:**
* **功能:** 展示可用的飞机,并允许玩家选择要操作的飞机。
* **设计要求:**
* 提供三个按钮分别代表“飞机1”、“飞机2”、“飞机3”。
* 当一架飞机被成功放置到棋盘上后,对应的按钮应变为“选中”或“已放置”状态(如改变颜色或样式)。
* 当玩家在棋盘上**点击**已放置的飞机时,此区域对应的飞机按钮也应同步显示为“当前选中”状态。
* 如果所有飞机都已放置,此区域的按钮都应处于“已放置”状态。
3. **布局操作按钮组 (针对触控优化):**
* **功能:** 控制飞机的放置、移动和旋转。
* **设计要求:**
* **方向控制:** “上、下、左、右”四个方向按钮,用于在添加飞机前,预设飞机的机头朝向。**此为第一种放置方式的辅助按钮。**
* **移动控制:** 提供四个方向箭头按钮(上、下、左、右),用于在棋盘上移动当前选中的飞机。
* **旋转控制:** 提供一个旋转按钮,用于顺时针旋转当前选中的飞机。
* **删除按钮:** 用于删除当前在棋盘上选中的飞机。
4. **准备确认按钮组:**
* **功能:** 玩家完成布局后,进行最终确认。
* **设计要求:**
* **完成按钮:**
* 当棋盘上没有满3架飞机时此按钮应为“禁用”状态。
* 当3架飞机都已放置好后此按钮变为“可用”状态。
* **删除按钮:** (此处有重复,建议整合)一个“清空”或“重置”按钮,用于一键清除所有已放置的飞机,方便重新布局。
5. **状态提示区:**
* **功能:** 向玩家反馈当前的游戏状态。
* **设计要求:**
* 在页面底部或一个显著位置,显示文本信息,如“请放置三架飞机”、“已准备,等待对手中...”、“对手已准备就绪”。
#### 2.1.2 交互流程
1. **进入页面:** 默认显示一个空的10x10棋盘。
2. **放置飞机:**
* **直接点击机身**
* 玩家直接点击棋盘上的一个**空白**格子。
* 系统以此格为飞机的中心点(或机身的一部分),自动生成一架默认朝向(例如,机头朝上)的飞机。
* 如果空间不足以放置飞机应给出提示如格子闪烁红色或toast提示且不放置飞机。
* **通用规则:**
* 飞机成功放置后“飞机x”按钮状态改变表示一架飞机已被使用。
* 当3架飞机都放置完毕后放置功能自动禁用。
3. **选择与操作飞机:**
* **单击**棋盘上已放置的飞机,该飞机进入“选中”状态(如高亮或边框变化),此时可对它进行移动或旋转。
* 使用移动和旋转按钮调整“选中”飞机的位置和形态。
* 点击“删除”按钮,移除选中的飞机。
4. **完成准备:**
* 当3架飞机全部放置后“完成”按钮激活。
* 玩家点击“完成”,布局操作按钮全部变为“禁用”状态,页面提示“已准备,等待对手中...”。
---
### 2.2 对战页面
**页面目标:** 玩家在此页面进行回合制对战,包括攻击对手和观察我方被攻击情况。页面分为“我的回合”和“对手回合”两种状态。
---
#### 2.2.1 我的回合 (攻击对手)
**核心目标:** 攻击对手棋盘,并可使用模拟功能辅助决策。
**界面核心元素:**
1. **攻击棋盘 (10x10 网格):**
* **功能:** 攻击对手的核心区域,同时内置飞机布局模拟功能。棋盘上会叠加显示历史攻击结果和当前模拟的飞机。
* **设计要求:**
* 样式与准备页面一致,带坐标轴。
* **攻击结果状态:**
* **打偏 (未命中):** 显示“X”或特定图标。
* **命中:** 显示火焰等命中效果。
* **摧毁:** 显示强烈的摧毁效果。
* **模拟飞机样式:** 模拟放置的飞机应有独特的视觉样式(如半透明、虚线边框),与真实的攻击结果标记明确区分。
2. **飞机模拟区:**
* **功能:** 提供可用于模拟的飞机模型。
* **设计要求:**
* 此区域的设计应参考**准备页面**的“飞机状态与选择区”和“布局操作按钮组”。
* 玩家可从此区域选择并操作飞机,将其放置到上方的攻击棋盘上进行模拟。
3. **操作与状态区:**
* **功能:** 执行打击操作和显示信息。
* **设计要求:**
* **打击按钮:** 玩家在棋盘上**点击**一个未攻击过的格子作为目标后,此按钮激活。
* **状态提示:** 清晰显示“我的回合”。
* **攻击结果反馈:** 每次攻击后,应有弹窗或醒目提示。
**交互流程:**
1. **进入回合:** 自动切换至此页面,显示对手的棋盘(包含我方历史攻击记录)。
2. **决策与操作 (可随时进行):**
* **模拟布局:** 从“飞机模拟区”选择飞机,在攻击棋盘上进行拖拽、旋转、删除等操作,以推演对手可能的布局。这些模拟飞机仅为临时辅助,不影响攻击。
* **选择攻击目标:** **单击**棋盘上任一未攻击过的格子,该格子高亮,表示“待打击”,同时“打击”按钮激活。
3. **确认打击:** 点击“打击”按钮。发起攻击后,棋盘上所有模拟的飞机将自动清除。
4. **等待并展示结果:**
* 棋盘上对应的格子根据结果(命中/打偏/摧毁)更新其视觉状态。
* 弹出醒目的结果提示。
5. **回合结束:** 自动切换到“对手回合”页面。
---
#### 2.2.2 对手回合 (观察我方)
**核心目标:** 清晰地看到我方棋盘的被攻击情况,并能实时观察到对手的瞄准意图。
**界面核心元素:**
1. **我方棋盘 (10x10 网格):**
* **功能:** 显示我方飞机布局以及对手的所有攻击记录。
* **设计要求:**
* 清晰展示我方三架飞机的完整布局。
* **格子状态:**
* **被攻击 (打偏):** 对手攻击但我方该位置无飞机部分。
* **被攻击 (命中):** 对手攻击命中我方飞机部件。
* **对手正在瞄准:** 实时显示对手当前选择的、但还未确认攻击的格子(例如,用闪烁的准星或特殊的边框高亮),此状态会随对手的选择而实时变化。
* **机头被摧毁:** 当机头被击中时,整架飞机需要有特殊且醒目的“被摧毁”样式(例如,整架飞机变灰并显示爆炸/损坏图标),以明确表示该飞机已失效。
2. **操作与状态区:**
* **功能:** 显示状态并提供视图切换功能。
* **设计要求:**
* **状态提示:** 清晰显示“对手回合”。
* **切换至攻击视图按钮:** 提供一个明确的按钮(如“切换到攻击页”或“战术模拟”),允许玩家在对手回合时,手动切换回“我的回合”的攻击与模拟棋盘,以便利用对手的思考时间进行我方的策略规划。
**交互流程:**
1. **进入回合:** 自动切换至此页面,显示我方棋盘和被攻击情况。
2. **观察对手:** 实时看到对手正在瞄准的格子位置变化。
3. **结果展示:** 当对手确认攻击后,棋盘上对应格子更新状态(命中/打偏),如果是机头,则整架飞机更新为“被摧毁”状态。
4. **可选操作:** 玩家可随时点击“切换至攻击视图”按钮,跳转回自己的攻击与模拟棋盘进行思考。
5. **回合结束:** 对手攻击完成后,系统自动切换回“我的回合”页面。

View File

@@ -0,0 +1,668 @@
# 打飞机小程序技术选型与功能架构设计文档
> **撰写人**: 移动端产品架构专家 | 苹果设计奖获得者
> **文档版本**: v1.0
> **创建日期**: 2024年9月11日
> **项目**: 打飞机对战小程序
## 1. 项目概述与原型分析
### 1.1 核心功能模块
基于原型设计分析,项目包含以下核心功能:
**页面结构**
- 入口页面:用户登录、游戏模式选择
- 房间管理:创建房间、加入房间、房间等待
- 游戏核心:飞机布置、实时对战
- 用户系统:个人信息、战绩统计
**设计特色**
- 深色科技主题UI设计
- 移动优先的交互体验
- 高度优化的触控操作
- 实时对战能力
### 1.2 MVP原则遵循
严格按照原型设计实现,不增加额外功能:
- 基础双人对战游戏
- 房间制匹配模式
- 简洁用户界面
- 核心游戏机制
## 2. 技术选型方案
### 2.1 前端技术栈
#### 2.1.1 小程序框架选择
**推荐Taro 4.x + React 18**
```typescript
// 技术栈配置
{
"framework": "Taro 4.x",
"ui": "React 18 + TypeScript",
"css": "Sass + CSS Modules",
"state": "Zustand",
"http": "Taro.request + axios适配",
"websocket": "Taro WebSocket API"
}
```
**选择理由**
- 一码多端可快速扩展到H5/APP
- React生态成熟组件复用度高
- TypeScript保证代码质量
- 与原型设计的现代化风格匹配
#### 2.1.2 状态管理
**Zustand** - 轻量级状态管理
```typescript
// 游戏状态Store结构
interface GameStore {
// 用户状态
user: UserInfo | null
// 房间状态
room: RoomInfo | null
// 游戏状态
gameState: GameState | null
// 连接状态
connectionState: 'connected' | 'connecting' | 'disconnected'
}
```
#### 2.1.3 UI组件设计
基于原型的深色科技主题:
```scss
// 设计Token
$primary-color: #6366f1;
$secondary-color: #40e0d0;
$bg-primary: #0f1419;
$gradient-bg: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
```
### 2.2 后端技术架构
#### 2.2.1 服务端选择
**Node.js + TypeScript + Fastify**
```typescript
// 服务端架构
{
"runtime": "Node.js 18+",
"framework": "Fastify",
"language": "TypeScript",
"websocket": "ws + socket.io",
"database": "MongoDB + Redis",
"deployment": "Docker + PM2"
}
```
**架构优势**
- 高性能异步处理
- WebSocket原生支持
- TypeScript类型安全
- 快速开发迭代
#### 2.2.2 数据存储设计
```typescript
// MongoDB数据模型
interface User {
_id: ObjectId
openid: string
nickname: string
avatar: string
stats: {
totalGames: number
wins: number
winRate: number
}
createdAt: Date
}
interface Room {
_id: ObjectId
roomCode: string
hostId: string
guestId?: string
status: 'waiting' | 'playing' | 'finished'
gameData?: GameData
createdAt: Date
}
interface GameSession {
_id: ObjectId
roomId: string
players: [string, string]
gameState: GameStateData
moves: Move[]
result?: GameResult
}
```
```redis
// Redis缓存策略
ROOM:{roomCode} -> RoomData (TTL: 1小时)
USER_ONLINE:{userId} -> ConnectionInfo (TTL: 30分钟)
GAME_SESSION:{sessionId} -> GameState (TTL: 2小时)
```
### 2.3 实时通信方案
#### 2.3.1 WebSocket架构
基于原型的实时对战需求:
```typescript
// WebSocket消息协议
interface GameMessage {
type: 'JOIN_ROOM' | 'PLACE_PLANES' | 'ATTACK' | 'GAME_STATE_SYNC'
roomCode: string
userId: string
data: any
timestamp: number
}
// 实时功能支持
- 房间状态同步
- 对手攻击实时显示(原型中的实时瞄准提示)
- 游戏状态广播
- 断线重连机制
```
## 3. 核心功能架构设计
### 3.1 游戏核心逻辑
#### 3.1.1 飞机模型设计
基于原型设计需求:
```typescript
// 飞机几何模型
interface Plane {
id: string
center: Position
direction: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
positions: Position[] // 11个位置
isDestroyed: boolean
parts: {
head: Position
wings: Position[] // 5个位置
body: Position[] // 2个位置
tail: Position[] // 3个位置
}
}
// 10x10棋盘表示
interface GameBoard {
cells: Cell[][] // 10x10网格
planes: Plane[] // 3架飞机
}
interface Cell {
position: Position
state: 'EMPTY' | 'PLANE_PART' | 'ATTACKED_MISS' | 'ATTACKED_HIT'
planeId?: string
}
```
#### 3.1.2 攻击逻辑
```typescript
// 攻击结果类型
interface AttackResult {
type: 'MISS' | 'HIT' | 'DESTROY'
position: Position
planeId?: string
gameEnded?: boolean
winner?: string
}
// 攻击处理逻辑
class GameEngine {
processAttack(
board: GameBoard,
position: Position
): AttackResult {
// 1. 检查位置有效性
// 2. 判断攻击结果miss/hit/destroy
// 3. 更新游戏状态
// 4. 检查游戏结束条件
}
}
```
### 3.2 页面功能模块
#### 3.2.1 入口页面模块
基于 [`01_入口页面.html`](01_文档/原型设计/01_入口页面.html) 设计:
```typescript
// 入口页面功能
interface EntryPageFeatures {
// 用户信息显示
userProfile: {
avatar: string
nickname: string
level?: number
}
// 游戏模式选择
gameMode: {
quickStart: () => void // AI对战
onlineMatch: () => void // 自动匹配
createRoom: () => void // 创建房间
joinGame: () => void // 加入游戏
}
// 游戏规则显示
rulesDisplay: boolean
}
```
#### 3.2.2 房间管理模块
基于原型设计的房间功能:
```typescript
// 房间管理功能
interface RoomManager {
// 创建房间
createRoom(): Promise<{roomCode: string}>
// 加入房间
joinRoom(roomCode: string): Promise<boolean>
// 房间状态同步
syncRoomState(): void
// 玩家准备状态
setPlayerReady(ready: boolean): void
}
```
#### 3.2.3 游戏对战模块
基于原型的对战页面设计:
```typescript
// 对战功能模块
interface BattleModule {
// 飞机布置阶段
placementPhase: {
placePlane(center: Position, direction: Direction): boolean
autoPlace(): void
confirmPlacement(): void
}
// 攻击阶段
attackPhase: {
selectTarget(position: Position): void
confirmAttack(): void
showAttackResult(result: AttackResult): void
}
// 实时功能(原型中的实时瞄准显示)
realTimeFeatures: {
showOpponentAiming(position: Position): void
hideOpponentAiming(): void
syncGameState(): void
}
}
```
### 3.3 数据流架构
#### 3.3.1 状态管理流程
```
用户操作 -> Action派发 -> Store更新 -> 组件重渲染
|
WebSocket同步
|
服务端处理 -> 状态广播 -> 对手端更新
```
#### 3.3.2 游戏流程设计
基于原型页面流程:
```typescript
// 游戏状态机
enum GamePhase {
WAITING = 'waiting', // 等待对手
PLACING = 'placing', // 布置飞机
BATTLING = 'battling', // 对战中
FINISHED = 'finished' // 游戏结束
}
// 回合控制
interface TurnManager {
currentPlayer: string
phase: GamePhase
timeLimit: number
switchTurn(): void
checkGameEnd(): boolean
}
```
## 4. 技术实现细节
### 4.1 小程序适配策略
#### 4.1.1 页面路由设计
```typescript
// 页面路由配置
const pages = [
'pages/entry/index', // 入口页面
'pages/room/create', // 创建房间
'pages/room/join', // 加入房间
'pages/room/waiting', // 等待房间
'pages/game/prepare', // 准备页面
'pages/game/battle' // 对战页面
]
// 路由管理
class Navigator {
static toRoomCreate() {
wx.navigateTo({ url: '/pages/room/create' })
}
static toGameBattle(roomCode: string) {
wx.navigateTo({
url: `/pages/game/battle?roomCode=${roomCode}`
})
}
}
```
#### 4.1.2 原生API集成
```typescript
// 微信小程序API封装
class WxAPI {
// 用户授权
static async getUserInfo(): Promise<UserInfo> {
const {userInfo} = await wx.getUserProfile({
desc: '用于显示用户信息'
})
return userInfo
}
// 震动反馈(基于原型的触觉反馈)
static vibrate(): void {
wx.vibrateShort({ type: 'light' })
}
// 分享功能
static onShareAppMessage() {
return {
title: '打飞机对战',
path: '/pages/entry/index'
}
}
}
```
### 4.2 性能优化方案
#### 4.2.1 渲染优化
基于原型的复杂UI需求
```typescript
// 游戏棋盘优化渲染
class BoardRenderer {
private shouldUpdate = false
private renderQueue: Position[] = []
// 批量更新棋盘状态
batchUpdateCells(updates: CellUpdate[]): void {
updates.forEach(update => {
this.renderQueue.push(update.position)
})
if (!this.shouldUpdate) {
this.shouldUpdate = true
this.nextTick(() => this.flushUpdates())
}
}
private flushUpdates(): void {
// 批量DOM更新
this.renderQueue.forEach(pos => this.updateCell(pos))
this.renderQueue = []
this.shouldUpdate = false
}
}
```
#### 4.2.2 网络优化
```typescript
// 请求优化和缓存
class NetworkManager {
private cache = new Map<string, any>()
private pending = new Map<string, Promise<any>>()
async request<T>(
url: string,
options?: RequestOptions
): Promise<T> {
const cacheKey = `${url}:${JSON.stringify(options)}`
// 检查缓存
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)
}
// 检查pending请求
if (this.pending.has(cacheKey)) {
return this.pending.get(cacheKey)
}
// 发起新请求
const promise = this.makeRequest<T>(url, options)
this.pending.set(cacheKey, promise)
try {
const result = await promise
this.cache.set(cacheKey, result)
return result
} finally {
this.pending.delete(cacheKey)
}
}
}
```
### 4.3 错误处理和监控
#### 4.3.1 错误边界
```typescript
// React错误边界组件
class GameErrorBoundary extends React.Component {
state = { hasError: false, error: null }
static getDerivedStateFromError(error: Error) {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// 错误上报
this.reportError(error, errorInfo)
}
private reportError(error: Error, errorInfo: ErrorInfo) {
// 上报到监控系统
wx.reportMonitor('game_error', {
error: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack
})
}
render() {
if (this.state.hasError) {
return <ErrorFallback onRetry={() => this.setState({ hasError: false })} />
}
return this.props.children
}
}
```
## 5. 部署和运维方案
### 5.1 开发环境配置
```json
{
"scripts": {
"dev": "taro build --type weapp --watch",
"build": "taro build --type weapp",
"build:h5": "taro build --type h5",
"deploy": "npm run build && npm run upload"
},
"dependencies": {
"@tarojs/taro": "^4.0.0",
"@tarojs/plugin-react": "^4.0.0",
"react": "^18.2.0",
"zustand": "^4.4.0"
}
}
```
### 5.2 服务端部署
```dockerfile
# 服务端Docker配置
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
```yaml
# docker-compose.yml
version: '3.8'
services:
game-server:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- MONGODB_URI=${MONGODB_URI}
- REDIS_URI=${REDIS_URI}
depends_on:
- mongodb
- redis
mongodb:
image: mongo:6.0
volumes:
- mongo_data:/data/db
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
```
### 5.3 监控和日志
```typescript
// 游戏性能监控
class PerformanceMonitor {
// 游戏关键指标监控
trackGameMetrics() {
// 游戏启动时间
const startTime = Date.now()
// 页面加载性能
wx.reportPerformance(1001, {
category: 'page_load',
name: 'entry_page',
value: Date.now() - startTime
})
// 游戏操作响应时间
this.trackUserInteraction()
}
// WebSocket连接质量监控
trackConnectionQuality() {
let pingStart = Date.now()
this.websocket.ping()
this.websocket.onPong(() => {
const latency = Date.now() - pingStart
wx.reportPerformance(1002, {
category: 'network',
name: 'websocket_latency',
value: latency
})
})
}
}
```
## 6. 开发计划和里程碑
### 6.1 开发阶段规划
```
第一阶段 (1周)
- 项目搭建 (2天)
- 基础UI开发 (5天)
第二阶段 (2周)
- 用户系统 (3天)
- 房间功能 (4天)
- 游戏核心逻辑 (6天)
- 实时通信 (3天)
第三阶段 (1周)
- 功能测试 (3天)
- 性能优化 (2天)
- 发布准备 (1天)
```
### 6.2 关键里程碑
- **Week 1**: 完成基础框架搭建和UI实现
- **Week 2**: 实现房间管理和游戏核心逻辑
- **Week 3**: 完成实时对战功能和测试优化
- **Week 4**: 发布上线和后续迭代
## 7. 风险评估和应对策略
### 7.1 技术风险
| 风险项 | 影响程度 | 应对策略 |
|-------|---------|----------|
| WebSocket连接稳定性 | 高 | 实现自动重连+离线缓存 |
| 小程序包体积限制 | 中 | 代码分包+资源CDN |
| 游戏状态同步复杂度 | 高 | 状态机设计+冲突解决 |
### 7.2 用户体验风险
| 风险项 | 影响程度 | 应对策略 |
|-------|---------|----------|
| 网络延迟影响体验 | 中 | 乐观更新+加载动画 |
| 不同设备适配问题 | 中 | 响应式设计+设备测试 |
| 游戏规则理解困难 | 低 | 新手引导+操作提示 |
## 8. 总结
本设计文档基于提供的原型设计严格遵循MVP原则提出了一套完整的微信小程序技术解决方案
**核心优势**
- 与原型设计高度一致的技术选型
- 现代化的前端架构和深色科技主题UI
- 高性能的实时对战体验
- 完善的错误处理和监控机制
- 清晰的开发计划和风险控制
**实现要点**
- 使用Taro + React实现跨端能力
- Node.js + WebSocket保证实时性能
- MongoDB + Redis提供数据支撑
- 完整的状态管理和错误处理
本方案将为您的打飞机小程序提供坚实的技术基础,确保项目按时高质量交付。
---
**文档状态**: ✅ 完成
**技术评审**: 待进行
**下一步**: 开始技术选型实施

View File

@@ -8,6 +8,32 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- 引入统一样式文件 -->
<link rel="stylesheet" href="common-styles.css">
<style>
/* 页面特有的样式补充 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: var(--safe-area-padding);
justify-content: center;
align-items: center;
text-align: center;
position: relative;
}
.game-modes {
width: 100%;
max-width: 350px;
display: flex;
flex-direction: column;
gap: 16px;
}
</style>
<!-- 保留原有的所有样式定义 -->
<style>
/* 设计变量 */
:root {
@@ -687,6 +713,9 @@
</main>
</div>
<!-- 引入导航功能 -->
<script src="navigation.js"></script>
<script>
// 创建动态星空背景
function createStarField() {
@@ -716,37 +745,25 @@
});
}
// 游戏模式按钮点击事件
// 游戏模式按钮点击事件 - 使用导航功能
document.getElementById('quickStart').addEventListener('click', () => {
console.log('快速开始 - 和AI对战');
if ('vibrate' in navigator) {
navigator.vibrate(15);
}
// 这里可以添加跳转到AI对战页面的逻辑
handleGameModeSelection('quickStart');
});
document.getElementById('onlineMatch').addEventListener('click', () => {
console.log('自动匹配 - 在线匹配对战');
if ('vibrate' in navigator) {
navigator.vibrate(15);
}
// 这里可以添加跳转到匹配页面的逻辑
handleGameModeSelection('onlineMatch');
});
document.getElementById('createRoom').addEventListener('click', () => {
console.log('创建房间');
if ('vibrate' in navigator) {
navigator.vibrate(10);
}
// 这里可以添加跳转到创建房间页面的逻辑
handleGameModeSelection('createRoom');
});
document.getElementById('joinGame').addEventListener('click', () => {
console.log('加入游戏');
if ('vibrate' in navigator) {
navigator.vibrate(10);
}
// 这里可以添加跳转到加入游戏页面的逻辑
handleGameModeSelection('joinGame');
});
// 设置按钮点击事件

View File

@@ -4,6 +4,13 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>房间等待 - 打飞机对战</title>
<!-- 引入通用样式 -->
<link rel="stylesheet" href="common-styles.css">
<!-- 引入导航功能 -->
<script src="navigation.js"></script>
<style>
/* 基础CSS变量 */
:root {
@@ -434,7 +441,12 @@
console.log('返回上一页');
// 添加触觉反馈
vibrate();
// 实际项目中这里应该调用路由返回或页面跳转
// 使用导航功能返回入口页面
if (typeof navigateTo === 'function') {
navigateTo('entry');
} else {
window.location.href = '01_入口页面.html';
}
}
@@ -456,8 +468,15 @@
vibrate();
// 检查是否所有玩家都准备
alert('游戏即将开始...');
// 实际项目中这里应该跳转到游戏页面
showToast('游戏即将开始...');
// 跳转到准备页面
setTimeout(() => {
if (typeof navigateTo === 'function') {
navigateTo('preparation');
} else {
window.location.href = '04_准备页面.html';
}
}, 1000);
}
// 分享房间码

View File

@@ -4,77 +4,12 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>加入房间 - 打飞机对战</title>
<!-- 引入通用样式 -->
<link rel="stylesheet" href="common-styles.css">
<style>
/* 基础CSS变量 */
:root {
/* 主色调 - 深空科技蓝 */
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--primary-dark: #4f46e5;
/* 辅助色 - 科技青色 */
--secondary-color: #40e0d0;
--secondary-light: #26d0ce;
/* 强调色 - 橙色 */
--accent-color: #f59e0b;
--accent-light: #fbbf24;
/* 危险色 - 红色 */
--danger-color: #ff4757;
--danger-light: #ff6b6b;
/* 成功色 - 绿色 */
--success-color: #4ade80;
--success-light: #6ee7b7;
/* 背景色系 - 深色渐变 */
--bg-primary: #0f1419;
--bg-secondary: #1a1d29;
--bg-tertiary: #252837;
--bg-elevated: #2d3142;
/* 渐变背景 */
--gradient-bg: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
/* 边框色系 */
--border-primary: #3d4159;
--border-secondary: #4a5073;
--border-highlight: #6366f1;
--border-accent: rgba(64, 224, 208, 0.3);
/* 文字色系 */
--text-primary: #ffffff;
--text-secondary: #b4b7c9;
--text-tertiary: #9ca3af;
--text-disabled: #6b7280;
/* 触摸目标尺寸 */
--touch-target-min: 44px;
--safe-area-padding: 20px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Microsoft YaHei UI", sans-serif;
background: var(--gradient-bg);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
}
/* 加入房间列表页面特定样式 */
.safe-area {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
@@ -82,51 +17,6 @@
padding-bottom: env(safe-area-inset-bottom);
}
.app-container {
flex: 1;
display: flex;
flex-direction: column;
padding: var(--safe-area-padding);
max-width: 400px;
margin: 0 auto;
width: 100%;
}
/* 头部导航 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
margin-bottom: 20px;
}
.back-btn {
width: 40px;
height: 40px;
border: none;
background: rgba(99, 102, 241, 0.15);
border-radius: 12px;
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
font-size: 18px;
}
.back-btn:active {
transform: scale(0.95);
background: rgba(99, 102, 241, 0.25);
}
.page-title {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
}
.placeholder {
width: 40px;
}
@@ -478,6 +368,9 @@
</section>
</div>
<!-- 引入导航功能 -->
<script src="navigation.js"></script>
<script>
// 房间数据模拟
const mockRooms = [
@@ -506,8 +399,17 @@
// 返回上一页
function goBack() {
console.log('返回上一页');
vibrate();
// 实际项目中这里应该调用路由返回
if (typeof window.vibrate === 'function') {
window.vibrate();
} else {
vibrate();
}
// 使用导航功能返回入口页面
if (typeof navigateTo === 'function') {
navigateTo('entry');
} else {
window.location.href = '01_入口页面.html';
}
}
// 搜索房间
@@ -525,7 +427,11 @@
}
console.log('搜索房间:', roomNumber);
vibrate();
if (typeof window.vibrate === 'function') {
window.vibrate();
} else {
vibrate();
}
showLoading();
// 模拟搜索延迟
@@ -543,16 +449,23 @@
// 加入房间
function joinRoom(roomId) {
console.log('加入房间:', roomId);
vibrate();
if (confirm('确定要加入房间 ' + roomId + ' 吗?')) {
showToast('正在加入房间...');
// 实际项目中这里应该调用加入房间的API
setTimeout(() => {
showToast('加入成功!');
// 跳转到房间等待页面
}, 1500);
if (typeof window.vibrate === 'function') {
window.vibrate();
} else {
vibrate();
}
showToast('正在加入房间...');
// 实际项目中这里应该调用加入房间的API
setTimeout(() => {
showToast('加入成功!');
// 跳转到房间等待页面
if (typeof navigateTo === 'function') {
navigateTo('roomWaiting');
} else {
window.location.href = '02_房间等待[房主].html';
}
}, 1500);
}
// 观战房间

View File

@@ -8,12 +8,19 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- 引入通用样式 -->
<link rel="stylesheet" href="common-styles.css">
<!-- 引入导航功能 -->
<script src="navigation.js"></script>
<style>
/* 页面特定样式 - 覆盖通用样式中的部分设置 */
:root {
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--accent-color: #f59e0b;
--danger-color: #ef4444;
--danger-color: #ff4757;
--success-color: #10b981;
--bg-primary: #0f1419;
--bg-secondary: #1a1d29;
@@ -33,7 +40,7 @@
html, body { height: 100%; overflow: hidden; -webkit-user-select: none; user-select: none; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Microsoft YaHei UI", sans-serif;
background: var(--bg-primary);
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
touch-action: none;
@@ -47,8 +54,8 @@
border-bottom: 1px solid var(--border-primary);
text-align: center;
font-weight: 600;
background: rgba(15, 20, 25, 0.8);
backdrop-filter: blur(10px);
background: rgba(26, 29, 41, 0.95);
backdrop-filter: blur(20px);
}
.main-content {
@@ -66,20 +73,42 @@
grid-template-columns: repeat(10, var(--cell-size));
grid-template-rows: repeat(10, var(--cell-size));
gap: 1px;
background: var(--border-primary);
border: 1px solid var(--border-primary);
background: rgba(64, 224, 208, 0.1);
border: 2px solid rgba(64, 224, 208, 0.5);
border-radius: 12px;
padding: 6px;
backdrop-filter: blur(8px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.board-cell {
background: var(--bg-secondary);
transition: background-color 0.2s;
background: rgba(15, 52, 96, 0.8);
border: 1px solid rgba(64, 224, 208, 0.15);
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: rgba(255, 255, 255, 0.4);
font-weight: 600;
position: relative;
cursor: pointer;
user-select: none;
}
.board-cell:active {
background: rgba(64, 224, 208, 0.4);
transform: scale(0.95);
border-color: var(--secondary-color);
}
.board-cell:active { background-color: var(--border-highlight); }
.board-cell.plane-part { background-color: var(--primary-color); }
.board-cell.plane-head { background-color: var(--accent-color); }
.board-cell.plane-body { background-color: var(--primary-light); }
.board-cell.plane-wing { background-color: var(--primary-light); }
.board-cell.plane-tail { background-color: var(--primary-color); }
.board-cell.selected { box-shadow: inset 0 0 0 2px var(--success-color); z-index: 1; }
.board-cell.selected {
box-shadow: inset 0 0 0 2px var(--success-color);
z-index: 1;
border-color: var(--success-color);
}
.col-label, .row-label {
position: absolute;
@@ -88,17 +117,27 @@
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
.col-label {
top: -20px;
height: 15px;
width: var(--cell-size);
}
.row-label {
left: -20px;
width: 15px;
height: var(--cell-size);
}
.col-label { top: -20px; height: 15px; width: var(--cell-size); }
.row-label { left: -20px; width: 15px; height: var(--cell-size); }
.controls-area {
display: flex;
flex-direction: column;
gap: 12px;
padding: var(--safe-area-padding);
background: var(--bg-secondary);
background: rgba(22, 33, 62, 0.8);
border-top: 1px solid var(--border-primary);
backdrop-filter: blur(10px);
}
.main-controls {
@@ -127,19 +166,113 @@
height: 44px;
}
/* 通用按钮样式 */
.btn {
padding: 10px;
border-radius: 8px;
border: 1px solid var(--border-primary);
background: var(--bg-tertiary);
color: var(--text-primary);
min-height: var(--touch-target-min);
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
min-width: 44px;
min-height: 44px;
padding: 14px 24px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
user-select: none;
-webkit-tap-highlight-color: transparent;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.3s, height 0.3s;
}
.btn:active::before {
width: 100%;
height: 100%;
}
/* 主要按钮样式 */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
color: #ffffff;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);
}
.btn-primary:hover, .btn-primary:focus {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.4);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.4);
}
/* 成功按钮样式 */
.btn-success {
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light));
color: #1a1a2e;
box-shadow: 0 4px 16px rgba(64, 224, 208, 0.3);
}
.btn-success:hover, .btn-success:focus {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(64, 224, 208, 0.4);
}
.btn-success:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(64, 224, 208, 0.4);
}
/* 次要按钮样式 */
.btn-secondary {
background: rgba(99, 102, 241, 0.15);
color: var(--primary-color);
border: 1px solid var(--primary-color);
backdrop-filter: blur(10px);
}
.btn-secondary:hover, .btn-secondary:focus {
background: rgba(99, 102, 241, 0.25);
transform: translateY(-1px);
}
.btn-secondary:active {
transform: translateY(0);
}
/* 危险按钮样式 */
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), var(--danger-light));
color: white;
box-shadow: 0 4px 16px rgba(255, 71, 87, 0.3);
}
.btn:disabled {
background: var(--bg-tertiary) !important;
color: var(--text-disabled) !important;
border: 1px solid var(--border-primary) !important;
opacity: 0.6;
cursor: not-allowed;
box-shadow: none !important;
}
.btn:active:not(:disabled) { transform: scale(0.95); }
.plane-selection {
display: flex;
flex-direction: column;
@@ -148,17 +281,59 @@
margin-right: 20px;
}
.confirmation-group {
.plane-btn {
min-height: var(--touch-target-min);
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
padding: 14px 24px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
justify-content: space-around;
gap: 10px;
align-items: center;
justify-content: center;
gap: 8px;
user-select: none;
-webkit-tap-highlight-color: transparent;
position: relative;
overflow: hidden;
background: rgba(99, 102, 241, 0.15);
color: var(--primary-color);
border: 1px solid var(--primary-color);
backdrop-filter: blur(10px);
}
.plane-btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(99, 102, 241, 0.2);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.3s, height 0.3s;
}
.plane-btn:active::before {
width: 100%;
height: 100%;
}
.plane-btn.placed {
background: linear-gradient(135deg, var(--primary-color), var(--primary-light)) !important;
color: white !important;
border-color: var(--primary-light) !important;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);
}
.plane-btn.selected {
background-color: var(--success-color) !important;
border-color: var(--success-color) !important;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
transform: scale(1.05);
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light)) !important;
color: #1a1a2e !important;
border-color: var(--secondary-color) !important;
box-shadow: 0 4px 16px rgba(64, 224, 208, 0.3);
}
.status-display {
@@ -167,35 +342,21 @@
font-size: 14px;
color: var(--text-secondary);
align-self: center;
}
.btn {
padding: 10px;
padding: 12px;
background: rgba(15, 52, 96, 0.4);
border-radius: 8px;
border: 1px solid var(--border-primary);
background: var(--bg-tertiary);
color: var(--text-primary);
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
min-width: 44px;
min-height: 44px;
}
.btn:disabled {
background: var(--bg-tertiary);
color: var(--text-disabled);
border-color: var(--border-primary);
opacity: 0.6;
cursor: not-allowed;
.confirmation-group {
display: flex;
justify-content: space-around;
gap: 10px;
}
.btn:active:not(:disabled) { transform: scale(0.95); }
.plane-btn.placed { background-color: var(--primary-color); border-color: var(--primary-light); }
.plane-btn.selected { background-color: var(--success-color); border-color: var(--success-color); }
.confirmation-group .btn-primary { background-color: var(--primary-color); }
.confirmation-group .btn-danger { background-color: var(--danger-color); }
.confirmation-group .btn {
flex: 1;
max-width: 120px;
}
</style>
</head>
<body>
@@ -215,15 +376,15 @@
<div class="direction-control">
<div class="direction-grid">
<div></div>
<button class="btn" id="moveUpBtn"></button>
<button class="btn" id="deleteBtn">🗑️</button>
<button class="btn btn-secondary" id="moveUpBtn"></button>
<button class="btn btn-danger" id="deleteBtn">🗑️</button>
<button class="btn" id="moveLeftBtn"></button>
<button class="btn" id="rotateBtn"></button>
<button class="btn" id="moveRightBtn"></button>
<button class="btn btn-secondary" id="moveLeftBtn"></button>
<button class="btn btn-secondary" id="rotateBtn"></button>
<button class="btn btn-secondary" id="moveRightBtn"></button>
<div></div>
<button class="btn" id="moveDownBtn"></button>
<button class="btn btn-secondary" id="moveDownBtn"></button>
<div></div>
</div>
</div>
@@ -236,7 +397,7 @@
</div>
<div class="confirmation-group">
<button class="btn" id="randomBtn">🎲 随机</button>
<button class="btn btn-secondary" id="randomBtn">🎲 随机</button>
<button class="btn btn-danger" id="resetBtn">🔄 重置</button>
<button class="btn btn-primary" id="doneBtn">✓ 完成</button>
</div>
@@ -495,12 +656,19 @@
completePlacement() {
if(this.planes.every(p => p.isPlaced)) {
this.updateStatus("准备就绪,等待对手...");
this.updateStatus("准备就绪,正在进入对战...");
localStorage.setItem('playerPlanes', JSON.stringify(this.planes));
// 禁用所有按钮
Object.values(this.dom).flat().forEach(el => {
if(el.tagName === 'BUTTON') el.disabled = true;
});
// 延迟1秒后跳转到对战页面
setTimeout(() => {
if (typeof window.completePlacementAndNavigate === 'function') {
window.completePlacementAndNavigate();
} else if (typeof navigateTo === 'function') {
navigateTo('battle');
} else {
window.location.href = '05_对战页面.html';
}
}, 1000);
} else {
this.updateStatus("请先放置所有飞机。");
}

View File

@@ -8,73 +8,17 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- 引入通用样式 -->
<link rel="stylesheet" href="common-styles.css">
<style>
/* 页面特定CSS变量 */
:root {
/* 色彩系统 */
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--primary-dark: #4f46e5;
--secondary-color: #40e0d0;
--secondary-light: #26d0ce;
--accent-color: #f59e0b;
--accent-light: #fbbf24;
--danger-color: #ff4757;
--danger-light: #ff6b6b;
--success-color: #10b981;
/* 背景色系 */
--bg-primary: #0f1419;
--bg-secondary: #1a1d29;
--bg-tertiary: #252837;
--bg-elevated: #2d3142;
--gradient-bg: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
/* 边框色系 */
--border-primary: #3d4159;
--border-secondary: #4a5073;
--border-highlight: #6366f1;
--border-accent: rgba(64, 224, 208, 0.3);
/* 文字色系 */
--text-primary: #ffffff;
--text-secondary: #b4b7c9;
--text-tertiary: #9ca3af;
--text-disabled: #6b7280;
/* 移动端适配 */
--cell-size: min(8.5vw, 36px);
--safe-area-padding: 16px;
--touch-target-min: 44px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
html, body {
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Microsoft YaHei UI", sans-serif;
}
body {
background: var(--gradient-bg);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
touch-action: manipulation;
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
.app-container {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
}
/* 顶部状态栏 */
@@ -304,6 +248,9 @@
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
overflow: hidden;
height: 320px;
display: flex;
flex-direction: column;
}
/* 页签头部 */
@@ -342,10 +289,13 @@
/* 页签内容 */
.tab-content {
padding: 16px;
flex: 1;
overflow-y: auto;
}
.tab-pane {
display: none;
height: 100%;
}
.tab-pane.active {
@@ -365,6 +315,7 @@
align-items: flex-start;
gap: 16px;
margin-bottom: 16px;
flex: 1;
}
.plane-selection {
@@ -380,87 +331,28 @@
gap: 4px;
}
/* 按钮样式 */
.btn {
min-height: var(--touch-target-min);
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
padding: 10px 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
user-select: none;
position: relative;
overflow: hidden;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
color: #ffffff;
/* 页面特定按钮样式覆盖 */
.plane-btn.placed {
background: linear-gradient(135deg, var(--primary-color), var(--primary-light)) !important;
color: white !important;
border-color: var(--primary-light) !important;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);
}
.btn-primary:active {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.4);
}
.btn-success {
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light));
color: #1a1a2e;
box-shadow: 0 4px 16px rgba(64, 224, 208, 0.3);
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), var(--danger-light));
color: white;
box-shadow: 0 4px 16px rgba(255, 71, 87, 0.3);
}
.btn-secondary {
background: rgba(99, 102, 241, 0.15);
color: var(--primary-color);
border: 1px solid var(--primary-color);
backdrop-filter: blur(10px);
}
.btn-secondary:active {
background: rgba(99, 102, 241, 0.25);
transform: translateY(1px);
}
.btn:disabled {
background: var(--bg-tertiary) !important;
color: var(--text-disabled) !important;
border: 1px solid var(--border-primary) !important;
opacity: 0.6;
cursor: not-allowed;
box-shadow: none !important;
}
.btn:disabled:active {
transform: none !important;
}
.plane-btn.placed {
background: var(--primary-color) !important;
color: white;
}
.plane-btn.selected {
background: var(--success-color) !important;
color: white;
box-shadow: 0 0 12px rgba(16, 185, 129, 0.5);
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light)) !important;
color: #1a1a2e !important;
border-color: var(--secondary-color) !important;
box-shadow: 0 4px 16px rgba(64, 224, 208, 0.3);
}
/* 攻击控制区域 */
.attack-controls {
text-align: center;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.attack-target-info {
@@ -476,12 +368,14 @@
display: flex;
gap: 12px;
justify-content: center;
margin-top: 16px;
}
/* 历史记录 */
.history-container {
max-height: 200px;
flex: 1;
overflow-y: auto;
margin-bottom: 16px;
}
.history-item {
@@ -517,7 +411,7 @@
padding: 12px;
background: rgba(15, 52, 96, 0.4);
border-radius: 8px;
margin-top: 12px;
margin-top: auto;
color: var(--text-secondary);
font-size: 14px;
}

View File

@@ -0,0 +1,755 @@
/* 打飞机对战小程序 - 统一样式表 */
/* 从所有原型页面提取的通用样式 */
/* ========== CSS变量定义 ========== */
:root {
/* 主色调 - 深空科技蓝 */
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--primary-dark: #4f46e5;
/* 辅助色 - 科技青色 */
--secondary-color: #40e0d0;
--secondary-light: #26d0ce;
/* 强调色 - 橙色 */
--accent-color: #f59e0b;
--accent-light: #fbbf24;
/* 危险色 - 红色 */
--danger-color: #ff4757;
--danger-light: #ff6b6b;
/* 成功色 - 绿色 */
--success-color: #10b981;
--success-light: #4ade80;
/* 背景色系 - 深色渐变 */
--bg-primary: #0f1419;
--bg-secondary: #1a1d29;
--bg-tertiary: #252837;
--bg-elevated: #2d3142;
/* 渐变背景 */
--gradient-bg: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 30%, #16213e 70%, #0f3460 100%);
/* 边框色系 */
--border-primary: #3d4159;
--border-secondary: #4a5073;
--border-highlight: #6366f1;
--border-accent: rgba(64, 224, 208, 0.3);
/* 文字色系 */
--text-primary: #ffffff;
--text-secondary: #b4b7c9;
--text-tertiary: #9ca3af;
--text-disabled: #6b7280;
/* 移动端触摸区域尺寸 */
--touch-target-min: 44px;
--safe-area-padding: 20px;
--cell-size: min(8.5vw, 38px);
}
/* ========== 基础重置样式 ========== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
html, body {
height: 100%;
overflow-x: hidden;
-webkit-text-size-adjust: 100%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Microsoft YaHei UI", sans-serif;
background: var(--gradient-bg);
color: var(--text-primary);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
touch-action: manipulation;
user-select: none;
}
/* ========== 安全区域适配 ========== */
.safe-area {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
/* ========== 动态星空背景 ========== */
.star-field {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
overflow: hidden;
}
.star {
position: absolute;
background: rgba(64, 224, 208, 0.8);
border-radius: 50%;
animation: twinkle 3s infinite;
}
@keyframes twinkle {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
}
/* ========== 主容器布局 ========== */
.app-container {
display: flex;
flex-direction: column;
min-height: 100vh;
position: relative;
z-index: 1;
}
/* ========== 顶部导航栏 ========== */
.user-header, .status-header, .header {
background: rgba(26, 29, 41, 0.95);
backdrop-filter: blur(20px);
padding: 15px var(--safe-area-padding);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-primary);
position: sticky;
top: 0;
z-index: 100;
}
.top-nav {
padding: 12px var(--safe-area-padding);
border-bottom: 1px solid var(--border-primary);
text-align: center;
font-weight: 600;
background: rgba(15, 20, 25, 0.8);
backdrop-filter: blur(10px);
}
/* ========== 用户信息组件 ========== */
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.user-avatar, .player-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 600;
color: white;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}
.user-details h3 {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 2px;
}
.user-level {
font-size: 12px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 4px;
}
.level-badge {
background: linear-gradient(135deg, var(--accent-color) 0%, var(--accent-light) 100%);
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
}
/* ========== 通用按钮样式 ========== */
.btn {
min-height: var(--touch-target-min);
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
padding: 14px 24px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
user-select: none;
-webkit-tap-highlight-color: transparent;
position: relative;
overflow: hidden;
}
/* 主要按钮样式 */
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
color: #ffffff;
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);
}
.btn-primary::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.3s, height 0.3s;
}
.btn-primary:hover, .btn-primary:focus {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.4);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.4);
}
.btn-primary:active::before {
width: 100%;
height: 100%;
}
/* 成功按钮样式 */
.btn-success {
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light));
color: #1a1a2e;
box-shadow: 0 4px 16px rgba(64, 224, 208, 0.3);
}
.btn-success::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.3s, height 0.3s;
}
.btn-success:hover, .btn-success:focus {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(64, 224, 208, 0.4);
}
.btn-success:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(64, 224, 208, 0.4);
}
.btn-success:active::before {
width: 100%;
height: 100%;
}
/* 次要按钮样式 */
.btn-secondary {
background: rgba(99, 102, 241, 0.15);
color: var(--primary-color);
border: 1px solid var(--primary-color);
backdrop-filter: blur(10px);
}
.btn-secondary::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(99, 102, 241, 0.2);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.3s, height 0.3s;
}
.btn-secondary:hover, .btn-secondary:focus {
background: rgba(99, 102, 241, 0.25);
transform: translateY(-1px);
}
.btn-secondary:active {
transform: translateY(0);
}
.btn-secondary:active::before {
width: 100%;
height: 100%;
}
/* 危险按钮样式 */
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), var(--danger-light));
color: white;
box-shadow: 0 4px 16px rgba(255, 71, 87, 0.3);
}
/* 按钮禁用状态 */
.btn:disabled {
background: var(--bg-tertiary) !important;
color: var(--text-disabled) !important;
border: 1px solid var(--border-primary) !important;
opacity: 0.6;
cursor: not-allowed;
box-shadow: none !important;
}
.btn:disabled:active {
transform: none !important;
}
/* 按钮图标和副标题 */
.button-icon {
font-size: 18px;
}
.button-subtitle {
font-size: 12px;
opacity: 0.8;
font-weight: 400;
}
/* ========== 设置和操作按钮 ========== */
.settings-btn, .back-btn, .share-btn {
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
font-size: 20px;
}
.settings-btn {
background: rgba(99, 102, 241, 0.15);
border: 1px solid var(--primary-color);
color: var(--primary-color);
}
.back-btn {
background: rgba(99, 102, 241, 0.15);
border-radius: 12px;
color: var(--primary-color);
font-size: 18px;
}
.share-btn {
background: rgba(64, 224, 208, 0.15);
border-radius: 12px;
color: var(--secondary-color);
font-size: 16px;
}
.settings-btn:hover, .back-btn:active, .share-btn:active {
transform: scale(1.05);
}
.settings-btn:active, .back-btn:active, .share-btn:active {
transform: scale(0.95);
}
/* ========== 卡片样式 ========== */
.title-card, .rules-card {
width: 100%;
max-width: 400px;
background: rgba(22, 33, 62, 0.8);
border: 1px solid rgba(64, 224, 208, 0.3);
border-radius: 16px;
padding: 30px 20px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
margin-bottom: 30px;
position: relative;
overflow: hidden;
}
.title-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color));
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { transform: translateX(-100%); }
50% { transform: translateX(100%); }
}
/* 游戏标题 */
.game-title {
font-size: 28px;
font-weight: 700;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 50%, var(--secondary-color) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
position: relative;
}
.game-subtitle {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 20px;
}
/* ========== 飞机装饰 ========== */
.plane-decoration {
position: absolute;
opacity: 0.1;
font-size: 60px;
color: var(--primary-color);
}
.plane-decoration.top-left {
top: 10px;
left: 10px;
transform: rotate(-45deg);
}
.plane-decoration.top-right {
top: 10px;
right: 10px;
transform: rotate(45deg);
}
.plane-decoration.bottom-left {
bottom: 10px;
left: 10px;
transform: rotate(135deg);
}
.plane-decoration.bottom-right {
bottom: 10px;
right: 10px;
transform: rotate(-135deg);
}
/* ========== 游戏规则样式 ========== */
.rules-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.rules-icon {
width: 24px;
height: 24px;
background: linear-gradient(135deg, var(--secondary-color) 0%, var(--secondary-light) 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #1a1a2e;
font-weight: 600;
}
.rules-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
}
.rules-content {
text-align: left;
}
.rule-item {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
color: var(--text-secondary);
font-size: 14px;
line-height: 1.5;
}
.rule-item:last-child {
margin-bottom: 0;
}
.rule-number {
min-width: 20px;
height: 20px;
background: rgba(99, 102, 241, 0.2);
border: 1px solid var(--primary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
color: var(--primary-color);
margin-top: 2px;
}
/* ========== 游戏棋盘样式 ========== */
.board-wrapper {
position: relative;
}
.game-board {
display: grid;
grid-template-columns: repeat(10, var(--cell-size));
grid-template-rows: repeat(10, var(--cell-size));
gap: 1px;
background: var(--border-primary);
border: 1px solid var(--border-primary);
}
.board-cell {
background: var(--bg-secondary);
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: rgba(255, 255, 255, 0.4);
position: relative;
cursor: pointer;
user-select: none;
font-weight: 600;
}
.board-cell:active {
background-color: var(--border-highlight);
transform: scale(0.95);
border-color: var(--secondary-color);
}
/* 棋盘飞机状态 */
.board-cell.plane-part { background-color: var(--primary-color); }
.board-cell.plane-head { background-color: var(--accent-color); }
.board-cell.plane-body { background-color: var(--primary-light); }
.board-cell.plane-wing { background-color: var(--primary-light); }
.board-cell.plane-tail { background-color: var(--primary-color); }
.board-cell.selected { box-shadow: inset 0 0 0 2px var(--success-color); z-index: 1; }
/* 坐标轴标识 */
.col-label, .row-label {
position: absolute;
font-size: 10px;
color: var(--text-tertiary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
}
.col-label {
top: -20px;
height: 15px;
width: var(--cell-size);
}
.row-label {
left: -20px;
width: 15px;
height: var(--cell-size);
}
/* ========== 输入框样式 ========== */
.search-input {
flex: 1;
height: var(--touch-target-min);
padding: 0 16px;
background: rgba(22, 33, 62, 0.8);
border: 1px solid rgba(64, 224, 208, 0.3);
border-radius: 12px;
color: var(--text-primary);
font-size: 16px;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}
.search-input::placeholder {
color: var(--text-tertiary);
}
/* ========== 状态徽章 ========== */
.status-badge {
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
}
.status-ready {
background: rgba(64, 224, 208, 0.2);
color: var(--secondary-color);
}
.status-waiting {
background: rgba(156, 163, 175, 0.2);
color: var(--text-tertiary);
}
/* ========== 网络状态和指示器 ========== */
.network-status {
display: flex;
align-items: center;
gap: 6px;
color: var(--text-tertiary);
}
.connection-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--secondary-color);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(1.3); }
}
.turn-indicator {
padding: 6px 16px;
border-radius: 16px;
font-size: 14px;
font-weight: 600;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.turn-indicator.my-turn {
background: linear-gradient(135deg, var(--secondary-color) 0%, var(--secondary-light) 100%);
color: #1a1a2e;
box-shadow: 0 4px 20px rgba(64, 224, 208, 0.4);
}
.turn-indicator.opponent-turn {
background: linear-gradient(135deg, rgba(255, 71, 87, 0.9) 0%, rgba(255, 56, 56, 0.9) 100%);
color: white;
border: 1px solid rgba(255, 71, 87, 0.6);
box-shadow: 0 4px 20px rgba(255, 71, 87, 0.3);
}
/* ========== 响应式设计 ========== */
@media (max-width: 375px) {
:root {
--cell-size: min(8vw, 32px);
}
.user-header {
padding: 12px var(--safe-area-padding);
}
.user-avatar, .player-avatar {
width: 36px;
height: 36px;
font-size: 16px;
}
.game-title {
font-size: 24px;
}
.btn {
font-size: 16px;
min-height: 42px;
padding: 14px 24px;
}
}
@media (max-height: 640px) {
.main-content {
padding-top: 15px;
padding-bottom: 15px;
}
.title-card, .rules-card {
margin-bottom: 20px;
padding: 20px;
}
.game-title {
margin-bottom: 8px;
}
}
/* ========== 辅助功能支持 ========== */
@media (prefers-contrast: high) {
:root {
--text-primary: #ffffff;
--text-secondary: #e5e5e5;
--border-primary: #ffffff;
--bg-secondary: #000000;
}
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
@media (prefers-color-scheme: dark) {
body {
background: var(--bg-primary);
color: var(--text-primary);
}
}
/* ========== 触觉反馈动画 ========== */
.haptic-feedback {
animation: hapticVibrate 0.1s ease-out;
}
@keyframes hapticVibrate {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-1px); }
75% { transform: translateX(1px); }
}

View File

@@ -0,0 +1,67 @@
/* 打飞机对战小程序 - 页面导航功能 */
// 页面路径映射
const routes = {
'entry': '01_入口页面.html',
'preparation': '04_准备页面.html',
'battle': '05_对战页面.html',
'roomWaiting': '02_房间等待[房主].html',
'joinRoom': '03_加入房间列表.html'
};
// 简单的触觉反馈
function vibrate() {
if ('vibrate' in navigator) {
navigator.vibrate(10);
}
}
// 主要导航函数
function navigateTo(routeKey) {
vibrate();
const pagePath = routes[routeKey];
if (!pagePath) {
console.error('未找到路由:', routeKey);
return;
}
console.log('导航到:', pagePath);
window.location.href = pagePath;
}
// 游戏模式选择处理
function handleGameModeSelection(mode) {
vibrate();
switch(mode) {
case 'quickStart':
case 'onlineMatch':
console.log(`${mode} - 进入准备页面`);
navigateTo('preparation');
break;
case 'createRoom':
console.log('createRoom - 进入房间等待页面');
navigateTo('roomWaiting');
break;
case 'joinGame':
console.log('joinGame - 进入房间列表页面');
navigateTo('joinRoom');
break;
default:
console.warn('未知的游戏模式:', mode);
}
}
// 从准备页面完成后跳转到对战页面
function completePlacementAndNavigate() {
vibrate();
console.log('准备完成,进入对战页面');
navigateTo('battle');
}
// 全局导出,确保函数可用
window.navigateTo = navigateTo;
window.handleGameModeSelection = handleGameModeSelection;
window.completePlacementAndNavigate = completePlacementAndNavigate;
window.vibrate = vibrate;

View File

@@ -11,6 +11,9 @@
| 目标平台 | 微信小程序 / 原生APP |
| 开发语言 | TypeScript + JavaScript |
## 此文档已经过期失效 **参考价值可以忽略**
> 此文档已经过期失效 **参考价值可以忽略**
## 1. 项目概述
### 1.1 项目背景

View File

@@ -0,0 +1,268 @@
# API接口设计详设文档
> **文档版本**: v1.0
> **撰写人**: 后端架构师
> **创建日期**: 2024年9月11日
## 1. 设计原则与规范
- **RESTful风格**: API遵循RESTful设计原则使用标准的HTTP方法 (`GET`, `POST`, `PUT`, `DELETE`)。
- **JSON格式**: 所有请求体和响应体均使用`application/json`格式。
- **URL版本控制**: API版本通过URL前缀进行管理例如 `/api/v1/...`
- **身份认证**: 所有需要认证的接口都通过`Authorization: Bearer <JWT>`头进行身份验证。
- **统一响应格式**: 所有API响应都遵循统一的数据结构便于客户端处理。
- **错误处理**: 使用标准的HTTP状态码表示请求结果并在响应体中提供详细的错误信息。
## 2. 统一响应格式
### 2.1 成功响应 (`2xx`)
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"key1": "value1",
"key2": "value2"
}
}
```
### 2.2 失败响应 (`4xx`, `5xx`)
```json
{
"success": false,
"code": 401,
"message": "身份认证失败",
"error": {
"type": "AuthenticationError",
"details": "JWT token is invalid or expired."
}
}
```
## 3. API接口详解
### 3.1 用户认证模块 (`/api/v1/auth`)
#### 3.1.1 `POST /auth/login`
- **功能**: 微信小程序登录。客户端使用`wx.login()`获取`code`后调用此接口。
- **请求体**:
```json
{
"code": "wx-login-code-from-miniprogram"
}
```
- **成功响应 (`200 OK`)**:
- **描述**: 返回JWT和新/老用户信息。
- **响应体**:
```json
{
"success": true,
"code": 200,
"message": "登录成功",
"data": {
"token": "jwt.string.here",
"isNewUser": false,
"user": {
"id": "user-openid",
"nickname": "张三",
"avatarUrl": "url-to-avatar"
}
}
}
```
- **失败响应**:
- `400 Bad Request`: `code`无效或缺失。
- `500 Internal Server Error`: 微信服务器接口调用失败。
#### 3.1.2 `PUT /auth/profile`
- **功能**: 更新用户信息(昵称、头像)。
- **认证**: 需要JWT。
- **请求体**:
```json
{
"nickname": "新的昵称",
"avatarUrl": "新的头像URL"
}
```
- **成功响应 (`200 OK`)**:
- **描述**: 返回更新后的用户信息。
- **响应体**:
```json
{
"success": true,
"code": 200,
"message": "用户信息更新成功",
"data": {
"id": "user-openid",
"nickname": "新的昵称",
"avatarUrl": "新的头像URL"
// ...
}
}
```
### 3.2 用户信息模块 (`/api/v1/users`)
#### 3.2.1 `GET /users/me`
- **功能**: 获取当前登录用户的详细信息。
- **认证**: 需要JWT。
- **成功响应 (`200 OK`)**:
- **描述**: 返回包含统计数据的完整用户信息。
- **响应体**:
```json
{
"success": true,
"code": 200,
"message": "获取成功",
"data": {
"id": "user-openid",
"nickname": "张三",
"avatarUrl": "url-to-avatar",
"stats": {
"gamesPlayed": 100,
"gamesWon": 60,
"winRate": 60.0,
"eloRating": 1350
},
"createdAt": "2024-09-10T..."
}
}
```
#### 3.2.2 `GET /users/:id`
- **功能**: 获取指定ID用户的公开信息。
- **认证**: 可选。
- **成功响应 (`200 OK`)**:
- **描述**: 返回用户的公开信息(不含敏感数据)。
- **响应体**:
```json
{
"success": true,
"code": 200,
"message": "获取成功",
"data": {
"id": "other-user-id",
"nickname": "李四",
"avatarUrl": "url-to-avatar",
"stats": {
"gamesPlayed": 120,
"winRate": 55.0,
"eloRating": 1300
}
}
}
```
- **失败响应**:
- `404 Not Found`: 用户不存在。
### 3.3 游戏模块 (`/api/v1/games`)
#### 3.3.1 `GET /games/history`
- **功能**: 获取当前用户的历史对局列表(分页)。
- **认证**: 需要JWT。
- **查询参数**:
- `page` (number, default: 1): 页码。
- `limit` (number, default: 10): 每页数量。
- **成功响应 (`200 OK`)**:
- **描述**: 返回分页的游戏历史记录。
- **响应体**:
```json
{
"success": true,
"code": 200,
"message": "获取成功",
"data": {
"games": [
{
"gameId": "game-id-1",
"opponent": { "id": "user-id-2", "nickname": "李四" },
"result": "win", // "win" | "loss"
"finishedAt": "2024-09-11T...",
"duration": 600 // 秒
}
],
"pagination": {
"currentPage": 1,
"totalPages": 10,
"totalGames": 100
}
}
}
```
#### 3.3.2 `GET /games/:id`
- **功能**: 获取指定游戏对局的详细信息(用于复盘)。
- **认证**: 需要JWT且用户必须是该对局的参与者。
- **成功响应 (`200 OK`)**:
- **描述**: 返回完整的游戏会话数据。
- **响应体**: (完整的 `GameSession` 对象)
- **失败响应**:
- `403 Forbidden`: 无权访问该对局。
- `404 Not Found`: 游戏不存在。
### 3.4 排行榜模块 (`/api/v1/leaderboard`)
#### 3.4.1 `GET /leaderboard`
- **功能**: 获取Elo积分排行榜。
- **认证**: 可选。
- **查询参数**:
- `limit` (number, default: 100): 返回的条目数。
- **成功响应 (`200 OK`)**:
- **描述**: 返回排名列表。
- **响应体**:
```json
{
"success": true,
"code": 200,
"message": "获取成功",
"data": {
"ranking": [
{
"rank": 1,
"user": { "id": "user-id-1", "nickname": "高手" },
"eloRating": 2000
},
{
"rank": 2,
"user": { "id": "user-id-2", "nickname": "大师" },
"eloRating": 1950
}
],
"lastUpdatedAt": "2024-09-11T..."
}
}
```
## 4. WebSocket API
WebSocket通信不属于RESTful API但其认证和初始状态获取与HTTP API紧密相关。
- **连接地址**: `wss://your-domain.com/ws`
- **认证流程**:
1. 客户端通过`POST /api/v1/auth/login`获取JWT。
2. 建立WebSocket连接。
3. 连接成功后,发送第一条消息进行认证。
- **认证消息**:
```json
{
"type": "AUTHENTICATE",
"payload": {
"token": "jwt.string.here"
}
}
```
- **认证成功响应**:
```json
{
"type": "AUTHENTICATED",
"payload": {
"userId": "user-id",
// ...
}
}
```
- **后续通信**: 详见《实时通信模块详设文档》。

View File

@@ -0,0 +1,251 @@
# UI组件设计详设文档
> **文档版本**: v1.0
> **撰写人**: UI/UX设计师 & 前端架构师
> **创建日期**: 2024年9月11日
## 1. 设计系统与主题
### 1.1 设计语言
- **主题**: 暗黑科技风 (Dark/Tech Theme),与原型设计稿保持一致。
- **主色调**:
- 背景: `#1A202C` (深灰蓝)
- 主色: `#00BFFF` (深天蓝,用于按钮、高亮、链接)
- 辅助色: `#4A5568` (中灰,用于边框、分割线)
- 强调色: `#FF4500` (橙红,用于警告、错误提示、攻击命中)
- **字体**:
- `sans-serif` 系统默认无衬线字体。
- 使用 `rem``em` 作为字体单位,以支持可访问性。
### 1.2 Design Token
所有颜色、字体大小、间距、圆角等样式参数都将通过Design Token进行管理`Sass`变量形式实现。
```scss
// src/styles/tokens.scss
// Colors
$color-background: #1A202C;
$color-primary: #00BFFF;
$color-secondary: #4A5568;
$color-accent: #FF4500;
$color-text-primary: #FFFFFF;
$color-text-secondary: #A0AEC0;
// Spacing
$spacing-xs: 0.25rem; // 4px
$spacing-sm: 0.5rem; // 8px
$spacing-md: 1rem; // 16px
$spacing-lg: 1.5rem; // 24px
$spacing-xl: 2rem; // 32px
// Border Radius
$border-radius-sm: 4px;
$border-radius-md: 8px;
$border-radius-lg: 16px;
```
## 2. 原子组件 (Atoms)
原子组件是UI构成的最小单元不可再分。
### 2.1 `Button` 组件
- **功能**: 标准按钮,支持不同变体和状态。
- **Props**:
```typescript
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger'; // 样式变体
size?: 'sm' | 'md' | 'lg'; // 尺寸
isLoading?: boolean; // 加载状态
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
```
- **样式**:
- `primary`: 主色背景,白色文字。
- `secondary`: 辅助色边框,主色文字。
- `danger`: 强调色背景,白色文字。
- `disabled`: 降低透明度,禁用鼠标事件。
- `isLoading`: 显示加载动画,禁用按钮。
### 2.2 `Input` 组件
- **功能**: 文本输入框。
- **Props**:
```typescript
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string; // 标签
errorMessage?: string; // 错误信息
leftIcon?: React.ReactNode;
}
```
- **样式**:
- `focus`: 主色边框高亮。
- `error`: 强调色边框和错误信息。
### 2.3 `Spinner` 组件
- **功能**: 加载指示器。
- **Props**:
```typescript
interface SpinnerProps {
size?: 'sm' | 'md' | 'lg'; // 尺寸
color?: string; // 颜色
}
```
### 2.4 `Icon` 组件
- **功能**: 图标库封装,使用`react-icons`。
- **Props**:
```typescript
import { IconType } from 'react-icons';
interface IconProps {
as: IconType; // 图标组件
size?: string | number;
color?: string;
}
```
## 3. 分子组件 (Molecules)
由原子组件组合而成,完成特定功能。
### 3.1 `FormField` 组件
- **构成**: `Input` + `Label` + `ErrorMessage`
- **功能**: 完整的表单字段,包含标签和验证逻辑。
### 3.2 `PlayerAvatar` 组件
- **构成**: `Avatar` + `Text`
- **功能**: 显示玩家头像和昵称。
- **Props**:
```typescript
interface PlayerAvatarProps {
player: {
avatarUrl: string;
nickname: string;
isOwner?: boolean; // 是否是房主
isReady?: boolean; // 是否已准备
};
size?: 'md' | 'lg';
}
```
- **样式**:
- `isReady`: 头像外圈显示主色高亮。
- `isOwner`: 昵称旁显示皇冠图标。
### 3.3 `RoomListItem` 组件
- **构成**: `Text` + `Icon` + `Button`
- **功能**: 在房间列表中显示单个房间的信息。
- **Props**:
```typescript
interface RoomListItemProps {
room: {
roomCode: string;
name: string;
playerCount: number;
isLocked: boolean;
};
onJoin: (roomCode: string) => void;
}
```
## 4. 生物组件 (Organisms)
由分子和原子组件构成的更复杂的UI部分。
### 4.1 `GameBoard` 组件
- **功能**: 核心游戏棋盘UI负责渲染10x10的网格和飞机。
- **构成**: 多个 `GridCell` 组件。
- **Props**:
```typescript
interface GameBoardProps {
boardState: BoardState; // 棋盘状态数据
isMyBoard: boolean; // 是否是自己的棋盘
onCellClick: (position: { x: number, y: number }) => void; // 单元格点击事件
placedPlanes?: Plane[]; // 预放置的飞机
}
```
- **`GridCell` 子组件**:
- 根据单元格状态(`empty`, `plane_part`, `miss`, `hit`, `destroy`)显示不同样式。
- 在对手棋盘上,`plane_part`状态默认不显示,除非被攻击过。
- 鼠标悬停在可攻击的单元格上时显示准星光标。
### 4.2 `PlanePlacementPanel` 组件
- **功能**: 飞机布置阶段的操作面板,允许玩家拖拽或旋转飞机。
- **构成**: `PlanePreview` 组件 + `Button` (旋转/确认)
- **Props**:
```typescript
interface PlanePlacementPanelProps {
onPlanesConfirm: (planes: Plane[]) => void;
}
```
- **交互**:
- 提供3个默认形状的飞机供拖拽到棋盘。
- 支持点击飞机进行90度旋转。
- 棋盘上会实时预览飞机位置,并对非法位置(越界、重叠)进行红色高亮提示。
### 4.3 `Header` 组件
- **功能**: 应用的全局顶部导航栏。
- **构成**: `Logo` + `UserInfo` + `SettingsButton`
## 5. 模板组件 (Templates)
定义页面的整体布局结构。
### 5.1 `MainLayout` 组件
- **功能**: 应用的主布局,包含页头、内容区、页脚。
- **构成**: `Header` + `children` + `Footer`
- **插槽(Slots)**: 通过`children` prop 插入页面具体内容。
## 6. 页面组件 (Pages)
完整的页面,由模板和多个生物/分子组件构成。
### 6.1 `HomePage`
- **路径**: `/`
- **功能**: 游戏入口页面,包含"创建房间"和"加入房间"按钮。
- **组件构成**: `MainLayout`, `Logo`, `Button`。
### 6.2 `RoomListPage`
- **路径**: `/rooms`
- **功能**: 显示公开的房间列表,支持刷新和搜索。
- **组件构成**: `MainLayout`, `RoomListItem`, `Spinner`, `Button`。
### 6.3 `RoomWaitingPage`
- **路径**: `/room/:roomCode`
- **功能**: 玩家等待页面,显示双方玩家信息和准备状态。
- **组件构成**: `MainLayout`, `PlayerAvatar` (x2), `Button` (准备/开始游戏), `ChatBox` (可选)。
- **逻辑**:
- 房主显示"开始游戏"按钮,当所有玩家准备好后激活。
- 其他玩家显示"准备"按钮。
- 实时监听WebSocket的`ROOM_STATE_UPDATE`事件更新UI。
### 6.4 `GamePage`
- **路径**: `/game/:gameId`
- **功能**: 核心游戏对战页面。
- **组件构成**:
- `MainLayout`
- `GameBoard` (我方棋盘)
- `GameBoard` (敌方棋盘)
- `GameStatusPanel`: 显示当前回合、倒计时、游戏日志。
- `PlanePlacementPanel`: 仅在飞机布置阶段显示。
- **逻辑**:
- 页面加载时从Zustand Store获取游戏状态。
- 监听WebSocket的`GAME_STATE_UPDATE`事件更新整个页面。
- 根据游戏阶段(`placing`, `battling`, `finished`)渲染不同UI。
- 在`finished`阶段,显示游戏结果弹窗(`GameResultModal`)。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,887 @@
# 后端技术架构详设文档
> **文档版本**: v1.0
> **撰写人**: 后端架构师
> **创建日期**: 2024年9月11日
## 1. 后端架构总览
### 1.1 技术栈详细说明
```typescript
// 后端技术栈配置
{
"runtime": "Node.js 18+", // 运行环境
"framework": "Fastify 4.x", // Web框架
"language": "TypeScript 5.0+", // 开发语言
"database": "MongoDB 6.0", // 主数据库
"cache": "Redis 7.0", // 缓存数据库
"websocket": "ws + socket.io", // WebSocket库
"orm": "Mongoose 7.x", // ODM工具
"validation": "Joi", // 数据验证
"auth": "JWT + 微信登录", // 认证方案
"logging": "Winston + Morgan", // 日志系统
"testing": "Jest + Supertest", // 测试框架
"process": "PM2", // 进程管理
"containerization": "Docker" // 容器化
}
```
### 1.2 服务架构设计
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Load Balancer │ │ Gateway │ │ Static CDN │
│ (Nginx) │ │ (API Route) │ │ (Assets) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
┌───────────────────────┴───────────────────────┐
│ Application │
│ Layer (Fastify) │
└───────────────────────┬───────────────────────┘
┌────────────────────────────┼────────────────────────────┐
│ │ │
┌───▼────┐ ┌─────────▼─────────┐ ┌─────────▼─────────┐
│ Auth │ │ Game Service │ │ WebSocket │
│Service │ │ (Core Logic) │ │ Service │
└────────┘ └───────────────────┘ └───────────────────┘
│ │ │
└───────────────────┼────────────────────────┘
┌───────────────────┼───────────────────┐
│ │ │
┌───▼─────┐ ┌───────▼──────┐ ┌──────▼─────┐
│MongoDB │ │ Redis │ │ External │
│(主数据) │ │ (缓存) │ │ APIs │
└─────────┘ └──────────────┘ └────────────┘
```
### 1.3 项目目录结构
```
src/
├── app.ts # 应用入口
├── server.ts # 服务器启动
├── config/ # 配置文件
│ ├── database.ts # 数据库配置
│ ├── redis.ts # Redis配置
│ ├── jwt.ts # JWT配置
│ └── environment.ts # 环境变量
├── controllers/ # 控制器层
│ ├── auth.controller.ts # 认证控制器
│ ├── room.controller.ts # 房间控制器
│ ├── game.controller.ts # 游戏控制器
│ └── user.controller.ts # 用户控制器
├── services/ # 业务逻辑层
│ ├── auth.service.ts # 认证服务
│ ├── room.service.ts # 房间服务
│ ├── game.service.ts # 游戏服务
│ ├── user.service.ts # 用户服务
│ └── websocket.service.ts # WebSocket服务
├── models/ # 数据模型
│ ├── User.ts # 用户模型
│ ├── Room.ts # 房间模型
│ ├── Game.ts # 游戏模型
│ └── GameSession.ts # 游戏会话模型
├── middleware/ # 中间件
│ ├── auth.middleware.ts # 认证中间件
│ ├── validation.middleware.ts # 验证中间件
│ ├── error.middleware.ts # 错误处理中间件
│ └── logging.middleware.ts # 日志中间件
├── routes/ # 路由定义
│ ├── auth.routes.ts # 认证路由
│ ├── room.routes.ts # 房间路由
│ ├── game.routes.ts # 游戏路由
│ └── user.routes.ts # 用户路由
├── utils/ # 工具函数
│ ├── gameLogic.ts # 游戏逻辑工具
│ ├── encryption.ts # 加密工具
│ ├── validators.ts # 验证工具
│ └── helpers.ts # 通用工具
├── websocket/ # WebSocket处理
│ ├── handlers/ # 消息处理器
│ ├── events.ts # 事件定义
│ └── connection.ts # 连接管理
└── tests/ # 测试文件
├── unit/ # 单元测试
├── integration/ # 集成测试
└── e2e/ # 端到端测试
```
## 2. 核心服务设计
### 2.1 Fastify应用配置
```typescript
// app.ts - 应用主配置
import Fastify, { FastifyInstance } from 'fastify'
import fastifyJwt from '@fastify/jwt'
import fastifyWebsocket from '@fastify/websocket'
import fastifyCors from '@fastify/cors'
import fastifyRateLimit from '@fastify/rate-limit'
export const createApp = async (): Promise<FastifyInstance> => {
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV === 'development' ? {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss',
ignore: 'pid,hostname'
}
} : undefined
}
})
// 注册插件
await app.register(fastifyJwt, {
secret: process.env.JWT_SECRET!,
sign: { expiresIn: '7d' }
})
await app.register(fastifyWebsocket, {
options: {
maxPayload: 1048576, // 1MB
verifyClient: (info) => {
// WebSocket连接验证
return verifyWebSocketClient(info)
}
}
})
await app.register(fastifyCors, {
origin: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
})
await app.register(fastifyRateLimit, {
max: 100,
timeWindow: '1 minute',
errorResponseBuilder: (request, context) => {
return {
code: 429,
error: 'Too Many Requests',
message: `请求过于频繁,请${Math.round(context.ttl / 1000)}秒后重试`
}
}
})
// 注册路由
await app.register(authRoutes, { prefix: '/api/auth' })
await app.register(userRoutes, { prefix: '/api/users' })
await app.register(roomRoutes, { prefix: '/api/rooms' })
await app.register(gameRoutes, { prefix: '/api/games' })
await app.register(websocketRoutes, { prefix: '/ws' })
// 错误处理
app.setErrorHandler(errorHandler)
app.setNotFoundHandler(notFoundHandler)
return app
}
// 健康检查端点
app.get('/health', async (request, reply) => {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
services: {
database: await checkDatabaseHealth(),
redis: await checkRedisHealth(),
websocket: checkWebSocketHealth()
}
}
reply.send(health)
})
```
### 2.2 认证服务设计
```typescript
// services/auth.service.ts
import jwt from 'jsonwebtoken'
import { User } from '../models/User'
import { redisClient } from '../config/redis'
export class AuthService {
// 微信小程序登录
async wxLogin(code: string): Promise<{ user: User; token: string }> {
try {
// 1. 通过code获取openid
const wxSession = await this.getWxSession(code)
// 2. 查找或创建用户
let user = await User.findOne({ openid: wxSession.openid })
if (!user) {
user = await User.create({
openid: wxSession.openid,
sessionKey: wxSession.session_key,
unionid: wxSession.unionid,
createdAt: new Date()
})
} else {
// 更新session_key
user.sessionKey = wxSession.session_key
await user.save()
}
// 3. 生成JWT token
const token = this.generateToken(user._id.toString())
// 4. 缓存用户会话
await this.cacheUserSession(user._id.toString(), token)
return { user, token }
} catch (error) {
throw new Error('微信登录失败')
}
}
// 获取微信session
private async getWxSession(code: string): Promise<WxSessionResponse> {
const response = await fetch(`https://api.weixin.qq.com/sns/jscode2session`, {
method: 'GET',
params: {
appid: process.env.WX_APPID!,
secret: process.env.WX_APP_SECRET!,
js_code: code,
grant_type: 'authorization_code'
}
})
const data = await response.json()
if (data.errcode) {
throw new Error(`微信API错误: ${data.errmsg}`)
}
return data
}
// 生成JWT token
private generateToken(userId: string): string {
return jwt.sign(
{ userId, type: 'access' },
process.env.JWT_SECRET!,
{ expiresIn: '7d' }
)
}
// 验证token
async verifyToken(token: string): Promise<{ userId: string; valid: boolean }> {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
// 检查会话是否仍然有效
const sessionExists = await redisClient.exists(`session:${decoded.userId}`)
return {
userId: decoded.userId,
valid: sessionExists === 1
}
} catch (error) {
return { userId: '', valid: false }
}
}
// 缓存用户会话
private async cacheUserSession(userId: string, token: string): Promise<void> {
const sessionData = {
token,
createdAt: new Date().toISOString(),
lastActive: new Date().toISOString()
}
await redisClient.setex(
`session:${userId}`,
7 * 24 * 60 * 60, // 7天过期
JSON.stringify(sessionData)
)
}
// 刷新用户活跃时间
async refreshUserActivity(userId: string): Promise<void> {
const sessionKey = `session:${userId}`
const sessionData = await redisClient.get(sessionKey)
if (sessionData) {
const session = JSON.parse(sessionData)
session.lastActive = new Date().toISOString()
await redisClient.setex(
sessionKey,
7 * 24 * 60 * 60,
JSON.stringify(session)
)
}
}
// 用户登出
async logout(userId: string): Promise<void> {
await redisClient.del(`session:${userId}`)
}
}
// 认证中间件
export const authMiddleware = async (request: FastifyRequest, reply: FastifyReply) => {
try {
const authHeader = request.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return reply.code(401).send({ error: '缺少认证令牌' })
}
const token = authHeader.substring(7)
const authService = new AuthService()
const { userId, valid } = await authService.verifyToken(token)
if (!valid) {
return reply.code(401).send({ error: '无效的认证令牌' })
}
// 刷新用户活跃时间
await authService.refreshUserActivity(userId)
// 将用户ID添加到请求上下文
request.user = { id: userId }
} catch (error) {
return reply.code(401).send({ error: '认证失败' })
}
}
```
### 2.3 房间管理服务
```typescript
// services/room.service.ts
import { Room } from '../models/Room'
import { User } from '../models/User'
import { redisClient } from '../config/redis'
import { websocketService } from './websocket.service'
export class RoomService {
// 创建房间
async createRoom(hostId: string): Promise<Room> {
const roomCode = this.generateRoomCode()
const room = await Room.create({
code: roomCode,
hostId,
status: 'waiting',
maxPlayers: 2,
currentPlayers: 1,
createdAt: new Date()
})
// 缓存房间信息
await this.cacheRoomData(room)
// 通知房间列表更新
await websocketService.broadcastRoomListUpdate()
return room
}
// 加入房间
async joinRoom(roomCode: string, playerId: string): Promise<Room> {
const room = await Room.findOne({ code: roomCode })
if (!room) {
throw new Error('房间不存在')
}
if (room.status !== 'waiting') {
throw new Error('房间已开始游戏或已结束')
}
if (room.currentPlayers >= room.maxPlayers) {
throw new Error('房间已满')
}
if (room.hostId === playerId) {
throw new Error('不能加入自己创建的房间')
}
// 更新房间信息
room.guestId = playerId
room.currentPlayers = 2
room.status = 'ready'
await room.save()
// 更新缓存
await this.cacheRoomData(room)
// 通知房间内玩家
await websocketService.notifyRoomUpdate(roomCode, {
type: 'PLAYER_JOINED',
room: room.toObject(),
playerId
})
return room
}
// 离开房间
async leaveRoom(roomCode: string, playerId: string): Promise<void> {
const room = await Room.findOne({ code: roomCode })
if (!room) {
throw new Error('房间不存在')
}
if (room.hostId === playerId) {
// 房主离开,删除房间
await Room.deleteOne({ code: roomCode })
await redisClient.del(`room:${roomCode}`)
// 通知客人
if (room.guestId) {
await websocketService.notifyPlayer(room.guestId, {
type: 'ROOM_CLOSED',
message: '房主已离开,房间关闭'
})
}
} else if (room.guestId === playerId) {
// 客人离开
room.guestId = undefined
room.currentPlayers = 1
room.status = 'waiting'
await room.save()
await this.cacheRoomData(room)
// 通知房主
await websocketService.notifyPlayer(room.hostId, {
type: 'PLAYER_LEFT',
room: room.toObject()
})
}
// 更新房间列表
await websocketService.broadcastRoomListUpdate()
}
// 获取房间列表
async getRoomList(): Promise<Room[]> {
const rooms = await Room.find({
status: 'waiting',
currentPlayers: { $lt: 2 }
}).populate('host', 'nickname avatar').lean()
return rooms
}
// 获取房间详情
async getRoomDetails(roomCode: string): Promise<Room | null> {
// 先从缓存获取
const cachedRoom = await redisClient.get(`room:${roomCode}`)
if (cachedRoom) {
return JSON.parse(cachedRoom)
}
// 从数据库获取
const room = await Room.findOne({ code: roomCode })
.populate('host', 'nickname avatar')
.populate('guest', 'nickname avatar')
.lean()
if (room) {
await this.cacheRoomData(room)
}
return room
}
// 开始游戏
async startGame(roomCode: string, hostId: string): Promise<void> {
const room = await Room.findOne({ code: roomCode })
if (!room) {
throw new Error('房间不存在')
}
if (room.hostId !== hostId) {
throw new Error('只有房主可以开始游戏')
}
if (room.currentPlayers < 2) {
throw new Error('等待其他玩家加入')
}
// 更新房间状态
room.status = 'playing'
await room.save()
// 创建游戏会话
const gameService = new GameService()
const gameSession = await gameService.createGameSession(room)
// 通知玩家游戏开始
await websocketService.notifyRoomUpdate(roomCode, {
type: 'GAME_STARTED',
gameSession: gameSession.toObject()
})
}
// 生成房间码
private generateRoomCode(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let result = ''
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length))
}
return result
}
// 缓存房间数据
private async cacheRoomData(room: any): Promise<void> {
await redisClient.setex(
`room:${room.code}`,
30 * 60, // 30分钟过期
JSON.stringify(room)
)
}
}
```
### 2.4 游戏核心服务
```typescript
// services/game.service.ts
import { GameSession } from '../models/GameSession'
import { Room } from '../models/Room'
import { GameLogic } from '../utils/gameLogic'
import { websocketService } from './websocket.service'
export class GameService {
// 创建游戏会话
async createGameSession(room: Room): Promise<GameSession> {
const gameSession = await GameSession.create({
roomId: room._id,
roomCode: room.code,
players: [room.hostId, room.guestId],
gameState: {
phase: 'PLACING',
currentPlayer: room.hostId, // 房主先开始
boards: {
[room.hostId]: GameLogic.createEmptyBoard(),
[room.guestId!]: GameLogic.createEmptyBoard()
},
moves: [],
startedAt: new Date()
},
createdAt: new Date()
})
return gameSession
}
// 放置飞机
async placePlanes(
gameSessionId: string,
playerId: string,
planes: PlaneData[]
): Promise<void> {
const session = await GameSession.findById(gameSessionId)
if (!session) {
throw new Error('游戏会话不存在')
}
if (session.gameState.phase !== 'PLACING') {
throw new Error('当前不是飞机放置阶段')
}
// 验证飞机放置是否合法
const isValid = GameLogic.validatePlanesPlacement(planes)
if (!isValid) {
throw new Error('飞机放置不合法')
}
// 更新游戏状态
session.gameState.boards[playerId] = GameLogic.placePlanesOnBoard(
session.gameState.boards[playerId],
planes
)
// 标记玩家已准备
session.gameState.playersReady = session.gameState.playersReady || {}
session.gameState.playersReady[playerId] = true
// 检查是否所有玩家都已准备
const allReady = session.players.every(p =>
session.gameState.playersReady[p]
)
if (allReady) {
// 开始对战阶段
session.gameState.phase = 'BATTLING'
session.gameState.battleStartedAt = new Date()
}
await session.save()
// 通知所有玩家状态更新
await websocketService.notifyGameUpdate(session.roomCode, {
type: 'PLACEMENT_UPDATE',
playerId,
ready: true,
allReady,
gameState: session.gameState
})
}
// 执行攻击
async attack(
gameSessionId: string,
attackerId: string,
position: Position
): Promise<AttackResult> {
const session = await GameSession.findById(gameSessionId)
if (!session) {
throw new Error('游戏会话不存在')
}
if (session.gameState.phase !== 'BATTLING') {
throw new Error('当前不是对战阶段')
}
if (session.gameState.currentPlayer !== attackerId) {
throw new Error('不是你的回合')
}
// 确定被攻击的玩家
const defenderId = session.players.find(p => p !== attackerId)!
const defenderBoard = session.gameState.boards[defenderId]
// 执行攻击逻辑
const attackResult = GameLogic.processAttack(defenderBoard, position)
// 更新游戏状态
session.gameState.boards[defenderId] = attackResult.updatedBoard
session.gameState.moves.push({
playerId: attackerId,
type: 'ATTACK',
position,
result: attackResult.type,
timestamp: new Date()
})
// 检查游戏是否结束
if (attackResult.gameEnded) {
session.gameState.phase = 'FINISHED'
session.gameState.winner = attackerId
session.gameState.endedAt = new Date()
// 更新玩家统计
await this.updatePlayerStats(attackerId, defenderId)
} else {
// 切换当前玩家
session.gameState.currentPlayer = defenderId
}
await session.save()
// 通知所有玩家攻击结果
await websocketService.notifyGameUpdate(session.roomCode, {
type: 'ATTACK_RESULT',
attackerId,
defenderId,
position,
result: attackResult,
gameState: session.gameState
})
return attackResult
}
// 获取游戏状态
async getGameState(gameSessionId: string, playerId: string): Promise<any> {
const session = await GameSession.findById(gameSessionId)
if (!session) {
throw new Error('游戏会话不存在')
}
if (!session.players.includes(playerId)) {
throw new Error('你不在这个游戏中')
}
// 返回过滤后的游戏状态(隐藏对手飞机位置)
const filteredState = {
...session.gameState,
boards: {
[playerId]: session.gameState.boards[playerId],
// 对手棋盘只显示攻击结果,不显示飞机位置
opponent: GameLogic.filterOpponentBoard(
session.gameState.boards[session.players.find(p => p !== playerId)!]
)
}
}
return filteredState
}
// 玩家投降
async surrender(gameSessionId: string, playerId: string): Promise<void> {
const session = await GameSession.findById(gameSessionId)
if (!session) {
throw new Error('游戏会话不存在')
}
if (session.gameState.phase === 'FINISHED') {
throw new Error('游戏已结束')
}
const winnerId = session.players.find(p => p !== playerId)!
// 更新游戏状态
session.gameState.phase = 'FINISHED'
session.gameState.winner = winnerId
session.gameState.endedAt = new Date()
session.gameState.surrendered = true
await session.save()
// 更新玩家统计
await this.updatePlayerStats(winnerId, playerId)
// 通知游戏结束
await websocketService.notifyGameUpdate(session.roomCode, {
type: 'GAME_ENDED',
winner: winnerId,
reason: 'SURRENDER',
gameState: session.gameState
})
}
// 更新玩家统计
private async updatePlayerStats(winnerId: string, loserId: string): Promise<void> {
const User = require('../models/User').User
// 更新获胜者统计
await User.updateOne(
{ _id: winnerId },
{
$inc: {
'stats.totalGames': 1,
'stats.wins': 1
}
}
)
// 更新失败者统计
await User.updateOne(
{ _id: loserId },
{
$inc: {
'stats.totalGames': 1
}
}
)
// 重新计算胜率
const users = await User.find({ _id: { $in: [winnerId, loserId] } })
for (const user of users) {
user.stats.winRate = user.stats.totalGames > 0
? (user.stats.wins / user.stats.totalGames * 100).toFixed(2)
: 0
await user.save()
}
}
}
```
## 3. WebSocket服务设计
### 3.1 WebSocket连接管理
```typescript
// websocket/connection.ts
import { WebSocket } from 'ws'
import { EventEmitter } from 'events'
import { redisClient } from '../config/redis'
export class WebSocketManager extends EventEmitter {
private connections = new Map<string, WebSocketConnection>()
private userConnections = new Map<string, string[]>() // userId -> connectionIds[]
private roomConnections = new Map<string, string[]>() // roomCode -> connectionIds[]
// 添加连接
addConnection(connectionId: string, ws: WebSocket, userId: string): void {
const connection = new WebSocketConnection(connectionId, ws, userId)
this.connections.set(connectionId, connection)
// 建立用户映射
if (!this.userConnections.has(userId)) {
this.userConnections.set(userId, [])
}
this.userConnections.get(userId)!.push(connectionId)
// 设置连接事件处理
connection.on('message', (message) => {
this.handleMessage(connectionId, message)
})
connection.on('close', () => {
this.removeConnection(connectionId)
})
connection.on('error', (error) => {
console.error(`WebSocket连接错误 ${connectionId}:`, error)
this.removeConnection(connectionId)
})
console.log(`WebSocket连接已建立: ${connectionId} (用户: ${userId})`)
}
// 移除连接
removeConnection(connectionId: string): void {
const connection = this.connections.get(connectionId)
if (!connection) return
const userId = connection.userId
// 移除连接映射
this.connections.delete(connectionId)
// 移除用户映射
const userConns = this.userConnections.get(userId)
if (userConns) {
const index = userConns.indexOf(connectionId)
if (index > -1) {
userConns.splice(index, 1)
}
if (userConns.length === 0) {
this.userConnections.delete(userId)
}
}
// 移除房间映射
for (const [roomCode, connections] of this.roomConnections.entries()) {
const index = connections.indexOf(connectionId)
if (index > -1) {
connections.splice(index, 1)
if (connections.length === 0) {
this.roomConnections.delete(roomCode)
}
// 通知房间内其他用户
this.notifyRoomUpdate(roomCode, {
type: 'PLAYER_DISCONNECTED',
playerId: userId
})
}
}
console.log(`WebSocket连接已关闭: ${connectionId} (用户: ${userId})`)
}
}

View File

@@ -0,0 +1,280 @@
# 实时通信模块详设文档
> **文档版本**: v1.0
> **撰写人**: 通信架构师
> **创建日期**: 2024年9月11日
## 1. WebSocket通信协议
### 1.1 协议选型
- **WebSocket**: 基于TCP的全双工通信协议提供持久化连接是实现游戏实时对战、状态同步的最佳选择。
- **WSS (WebSocket Secure)**: 在生产环境强制使用WSS协议确保所有通信数据都经过TLS加密保障数据安全。
### 1.2 消息格式
所有客户端与服务端之间的WebSocket消息都采用统一的JSON格式进行封装便于解析和扩展。
```json
{
"type": "MESSAGE_TYPE_ENUM",
"payload": {
"key1": "value1",
"key2": "value2"
},
"timestamp": "2024-09-11T10:00:00.000Z",
"client_message_id": "optional-client-uuid-for-ack"
}
```
- `type`: 消息类型,用于路由到不同的处理逻辑。
- `payload`: 消息体,包含具体业务数据。
- `timestamp`: 消息发送的UTC时间戳。
- `client_message_id`: (可选) 客户端生成的消息ID用于实现消息确认(ACK)机制。
### 1.3 心跳机制
为维持连接活性并检测死链,采用双向心跳机制:
- **客户端**: 每隔25秒向服务端发送一个`PING`消息。
- **服务端**:
- 收到`PING`消息后,立即回复一个`PONG`消息。
- 如果在60秒内未收到任何客户端消息包括`PING`则认为连接已断开主动关闭该WebSocket连接。
```typescript
// 客户端PING消息
{
"type": "PING",
"payload": {},
"timestamp": "..."
}
// 服务端PONG消息
{
"type": "PONG",
"payload": {},
"timestamp": "..."
}
```
## 2. 消息类型与数据结构
### 2.1 消息类型枚举 (`GameMessageType`)
```typescript
// 客户端 -> 服务端 (C2S)
export enum ClientToServerMessageType {
// --- 系统级 ---
PING = 'PING', // 心跳检测
AUTHENTICATE = 'AUTHENTICATE', // 身份认证
// --- 房间管理 ---
CREATE_ROOM = 'CREATE_ROOM', // 创建房间
JOIN_ROOM = 'JOIN_ROOM', // 加入房间
LEAVE_ROOM = 'LEAVE_ROOM', // 离开房间
GET_ROOM_LIST = 'GET_ROOM_LIST', // 获取房间列表
PLAYER_READY = 'PLAYER_READY', // 玩家准备
// --- 游戏逻辑 ---
PLACE_PLANES = 'PLACE_PLANES', // 放置飞机
EXECUTE_ATTACK = 'EXECUTE_ATTACK', // 执行攻击
SURRENDER = 'SURRENDER' // 投降
}
// 服务端 -> 客户端 (S2C)
export enum ServerToClientMessageType {
// --- 系统级 ---
PONG = 'PONG', // 心跳响应
AUTHENTICATED = 'AUTHENTICATED', // 认证成功
ERROR = 'ERROR', // 错误通知
// --- 房间与游戏状态同步 ---
ROOM_LIST_UPDATE = 'ROOM_LIST_UPDATE', // 房间列表更新
ROOM_STATE_UPDATE = 'ROOM_STATE_UPDATE',// 房间状态更新
GAME_STATE_UPDATE = 'GAME_STATE_UPDATE',// 游戏状态更新
// --- 游戏事件通知 ---
GAME_STARTED = 'GAME_STARTED', // 游戏开始
PLACEMENT_PHASE_START = 'PLACEMENT_PHASE_START', // 放置阶段开始
BATTLE_PHASE_START = 'BATTLE_PHASE_START', // 对战阶段开始
TURN_CHANGE = 'TURN_CHANGE', // 回合变更
ATTACK_RESULT = 'ATTACK_RESULT', // 攻击结果
GAME_OVER = 'GAME_OVER', // 游戏结束
PLAYER_RECONNECTED = 'PLAYER_RECONNECTED',// 玩家重连
PLAYER_DISCONNECTED = 'PLAYER_DISCONNECTED'// 玩家断线
}
```
### 2.2 核心消息体 (`Payload`) 详解
#### `AUTHENTICATE` (C2S)
- **描述**: 客户端连接后发送的第一条消息,用于身份认证。
- **Payload**:
```typescript
interface AuthenticatePayload {
token: string; // 从HTTP登录接口获取的JWT
}
```
#### `AUTHENTICATED` (S2C)
- **描述**: 服务端认证成功后返回的消息。
- **Payload**:
```typescript
interface AuthenticatedPayload {
userId: string;
nickname: string;
// ... 其他用户信息
}
```
#### `CREATE_ROOM` (C2S)
- **描述**: 客户端请求创建新房间。
- **Payload**:
```typescript
interface CreateRoomPayload {
roomName: string;
isPublic: boolean;
password?: string; // 如果是私密房间
}
```
#### `ROOM_STATE_UPDATE` (S2C)
- **描述**: 当房间状态(如玩家加入/退出/准备)发生变化时,服务端向房间内所有客户端广播。
- **Payload**: `RoomState` 对象 (详见后端设计文档)
#### `PLACE_PLANES` (C2S)
- **描述**: 玩家在布置阶段提交飞机布局。
- **Payload**:
```typescript
interface PlacePlanesPayload {
planes: {
center: { x: number, y: number };
direction: 'up' | 'down' | 'left' | 'right';
}[];
}
```
#### `EXECUTE_ATTACK` (C2S)
- **描述**: 玩家在对战阶段执行攻击。
- **Payload**:
```typescript
interface ExecuteAttackPayload {
position: { x: number, y: number };
}
```
#### `GAME_STATE_UPDATE` (S2C)
- **描述**: 游戏核心状态发生变化时,服务端向游戏内玩家广播。
- **Payload**: `GameState` 对象 (详见游戏核心逻辑设计文档),但会根据接收玩家进行数据裁剪(如隐藏对手未被攻击的飞机位置)。
#### `ATTACK_RESULT` (S2C)
- **描述**: 服务端通知攻击结果。
- **Payload**:
```typescript
interface AttackResultPayload {
attackerId: string;
position: { x: number, y: number };
result: 'miss' | 'hit' | 'destroy';
targetPlaneId?: string; // 如果击中
isGameOver: boolean;
}
```
#### `ERROR` (S2C)
- **描述**: 服务端向客户端发送错误信息。
- **Payload**:
```typescript
interface ErrorPayload {
code: number; // 错误码
message: string; // 错误信息
requestType?: string; // 导致错误的请求类型
}
```
## 3. 断线重连机制
### 3.1 核心流程
1. **客户端检测断线**:
- WebSocket `onclose` 事件被触发。
- 或,发送`PING`后超过10秒未收到`PONG`。
2. **自动重连**:
- 客户端立即尝试重新建立WebSocket连接。
- 采用**指数退避算法**进行重连尝试例如首次延迟1秒然后2秒, 4秒, 8秒... 直到成功或达到最大重连次数(5次)。
3. **重连后认证**:
- 新连接建立后,客户端必须立即发送`AUTHENTICATE`消息并携带之前的JWT。
4. **服务端处理重连**:
- 服务端通过JWT识别出这是同个玩家的重连请求。
- 服务端查找该玩家当前是否处于某个游戏会话中。
- 如果在游戏中,服务端将最新的`GameState`发送给该玩家,并向房间内所有玩家广播`PLAYER_RECONNECTED`事件。
5. **状态同步**:
- 客户端收到完整的`GameState`后,恢复游戏界面,确保与服务器状态一致。
### 3.2 服务端实现要点
- **会话持久化**: 玩家的`userId`和其`connectionId`的映射关系需要存储在Redis中并设置合理的过期时间如5分钟以便在断线期间保留会话信息。
- **游戏状态恢复**: `GameStateMachine`实例必须在玩家断线时保留在内存中,直到游戏结束或超时。当玩家重连时,可以从该实例获取最新状态。
```typescript
// Redis中存储的重连会话信息
// Key: "reconnect:session:{userId}"
// Value (HASH):
// connectionId: "previous-connection-id"
// gameId: "current-game-id"
// roomCode: "current-room-code"
// expireAt: "timestamp"
```
## 4. 多节点部署与消息同步
### 4.1 挑战
当后端WebSocket服务部署在多个节点上时同一房间的两个玩家可能连接到不同的服务器实例。一个玩家发送的消息需要被广播给连接在另一台服务器上的对手。
### 4.2 解决方案Redis Pub/Sub
使用Redis的发布/订阅Pub/Sub机制作为消息总线实现跨节点通信。
1. **消息流**:
- 客户端A向服务器S1发送消息如`EXECUTE_ATTACK`)。
- S1处理消息更新游戏状态。
- S1将需要广播的消息如`ATTACK_RESULT`, `GAME_STATE_UPDATE`发布到一个特定的Redis频道例如`game-room:{roomCode}`。
- 所有后端服务器实例S1, S2, ...)都订阅了相关的频道。
- S2接收到`game-room:{roomCode}`频道的消息。
2. **消息投递**:
- S2查找连接在本机且属于`roomCode`房间的客户端即客户端B
- S2将消息通过WebSocket连接发送给客户端B。
### 4.3 `socket.io` 与 `socket.io-redis-adapter`
为简化实现,推荐使用`socket.io`库及其官方Redis适配器`socket.io-redis-adapter`。
- **`socket.io`**: 提供了房间(room)、广播(broadcast)、命名空间(namespace)等高级抽象,并内置了心跳和自动重连机制。
- **`socket.io-redis-adapter`**: 自动处理了上述的Redis Pub/Sub逻辑。只需简单配置即可实现多节点间的无缝消息广播。
#### 示例配置
```typescript
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const io = new Server();
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
io.listen(3000);
});
// 使用方法
io.to('some-room-code').emit('event_name', { data: '...' });
```
此方案将所有跨节点通信的复杂性都交由`socket.io-redis-adapter`处理,使业务代码可以专注于游戏逻辑,无需关心底层的消息路由。

View File

@@ -0,0 +1,210 @@
# 数据库设计详设文档
> **文档版本**: v1.0
> **撰写人**: 数据库架构师
> **创建日期**: 2024年9月11日
## 1. 技术选型
- **主数据库**: **MongoDB 6.0+**
- **原因**: 采用面向文档的存储模型非常适合存储游戏会话、用户配置等半结构化数据。其灵活的Schema设计能快速迭代内建的复制和分片功能为高可用和高扩展性提供了有力支持。
- **缓存/消息队列**: **Redis 7.0+**
- **原因**: 基于内存的高性能键值存储用于缓存热点数据如用户信息、排行榜、管理WebSocket会话、实现分布式锁及作为多节点部署时的消息总线Pub/Sub
- **ORM/ODM**: **Mongoose 7.x**
- **原因**: 为MongoDB提供强大的对象数据建模ODM能力支持Schema定义、数据校验、中间件、查询构建等功能能显著提升开发效率和代码健壮性。
## 2. MongoDB 数据模型设计
### 2.1 `users` 集合
存储用户信息。
- **Schema 定义**:
```typescript
import { Schema, model } from 'mongoose';
const userSchema = new Schema({
_id: { type: String, required: true }, // 使用微信的 openid 作为主键
nickname: { type: String, required: true },
avatarUrl: { type: String, required: true },
stats: {
gamesPlayed: { type: Number, default: 0 },
gamesWon: { type: Number, default: 0 },
winRate: { type: Number, default: 0.0 },
totalShots: { type: Number, default: 0 },
totalHits: { type: Number, default: 0 },
accuracy: { type: Number, default: 0.0 },
eloRating: { type: Number, default: 1200 } // Elo积分系统
},
lastLoginAt: { type: Date, default: Date.now },
createdAt: { type: Date, default: Date.now, immutable: true },
updatedAt: { type: Date, default: Date.now }
}, {
timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' }
});
// Mongoose 中间件,用于在胜率和准确率变化时自动更新
userSchema.pre('save', function(next) {
if (this.isModified('stats.gamesPlayed') || this.isModified('stats.gamesWon')) {
this.stats.winRate = this.stats.gamesPlayed > 0 ? (this.stats.gamesWon / this.stats.gamesPlayed) * 100 : 0;
}
if (this.isModified('stats.totalShots') || this.isModified('stats.totalHits')) {
this.stats.accuracy = this.stats.totalShots > 0 ? (this.stats.totalHits / this.stats.totalShots) * 100 : 0;
}
next();
});
export const UserModel = model('User', userSchema);
```
- **索引 (Indexes)**:
- `_id`: (主键) 唯一索引,用于快速查找用户。
- `{ "stats.eloRating": -1 }`: 降序索引,用于实现排行榜。
### 2.2 `game_sessions` 集合
存储完整的游戏对局信息,用于复盘、数据分析和问题排查。
- **Schema 定义**:
```typescript
import { Schema, model } from 'mongoose';
const positionSchema = new Schema({ x: Number, y: Number }, { _id: false });
const planeSchema = new Schema({
center: positionSchema,
direction: String,
positions: [positionSchema]
}, { _id: false });
const playerStateSchema = new Schema({
userId: { type: String, ref: 'User', required: true },
board: {
planes: [planeSchema],
attackHistory: [{
position: positionSchema,
result: String, // 'miss', 'hit', 'destroy'
timestamp: Date
}]
},
stats: {
shots: Number,
hits: Number,
planesDestroyed: Number
}
}, { _id: false });
const gameSessionSchema = new Schema({
_id: { type: String, required: true }, // 游戏会话ID
roomCode: { type: String, required: true, index: true },
status: { type: String, required: true, enum: ['placing', 'battling', 'finished'], default: 'placing' },
players: [{ type: String, ref: 'User' }],
playerStates: [playerStateSchema],
winnerId: { type: String, ref: 'User' },
winReason: { type: String, enum: ['ALL_PLANES_DESTROYED', 'SURRENDER', 'TIMEOUT'] },
gameEvents: [{
type: String,
playerId: String,
data: Schema.Types.Mixed,
timestamp: Date
}],
startedAt: { type: Date, default: Date.now },
finishedAt: { type: Date }
});
export const GameSessionModel = model('GameSession', gameSessionSchema);
```
- **索引 (Indexes)**:
- `_id`: (主键) 唯一索引。
- `{ roomCode: 1 }`: 用于通过房间码快速查找游戏。
- `{ "players": 1 }`: 多键索引,用于查询某玩家参与的所有对局。
- `{ status: 1, startedAt: -1 }`: 复合索引,用于查找特定状态的游戏并按时间排序。
## 3. Redis 数据结构设计
Redis 用于存储高频访问、易失性或需要原子操作的数据。
### 3.1 用户会话 (User Session)
- **用途**: 存储用户登录状态和WebSocket连接信息。
- **数据结构**: `HASH`
- **Key**: `session:{userId}`
- **Value**:
- `token`: `string` (JWT)
- `connectionId`: `string` (当前WebSocket连接ID)
- `status`: `'online' | 'offline' | 'in-game'`
- `gameId`: `string` (如果`status`为`in-game`)
- **TTL**: 24小时 (每次访问刷新)
### 3.2 游戏房间 (Game Rooms)
- **用途**: 管理游戏房间列表和房间内玩家状态。
- **数据结构**: `HASH`
- **Key**: `room:{roomCode}`
- **Value**:
- `name`: `string` (房间名)
- `ownerId`: `string` (房主用户ID)
- `status`: `'waiting' | 'full' | 'in-game'`
- `player1Id`: `string`
- `player2Id`: `string`
- `player1Ready`: `'0' | '1'`
- `player2Ready`: `'0' | '1'`
- **TTL**: 2小时 (从最后一次活动开始计算)
### 3.3 游戏状态 (Live Game State)
- **用途**: 缓存进行中游戏的核心状态减少对MongoDB的读写压力。
- **数据结构**: `STRING` (存储序列化后的`GameState`对象)
- **Key**: `game:state:{gameId}`
- **Value**: `JSON.stringify(GameState)`
- **TTL**: 1小时 (游戏结束后删除)
### 3.4 排行榜 (Leaderboard)
- **用途**: 实时更新和查询玩家排名。
- **数据结构**: `SORTED SET` (ZSET)
- **Key**: `leaderboard:elo`
- **Value**:
- `member`: `userId`
- `score`: `eloRating` (整数)
- **操作**:
- **更新排名**: `ZADD leaderboard:elo <eloRating> <userId>`
- **查询Top 100**: `ZREVRANGE leaderboard:elo 0 99 WITHSCORES`
- **查询玩家排名**: `ZREVRANK leaderboard:elo <userId>`
### 3.5 分布式锁 (Distributed Lock)
- **用途**: 在关键操作(如匹配玩家、创建游戏)中防止并发冲突。
- **数据结构**: `STRING`
- **Key**: `lock:{resource_name}:{resource_id}` (e.g., `lock:room:join:{roomCode}`)
- **Value**: `unique_lock_id` (e.g., a UUID)
- **操作**: 使用`SET key value NX PX milliseconds`命令实现原子性的加锁操作。
- `NX`: 只在键不存在时设置。
- `PX`: 设置过期时间(毫秒),防止死锁。
## 4. 数据一致性策略
- **写操作**:
1. **关键操作** (如`EXECUTE_ATTACK`):
- 开启**分布式锁**。
- 更新Redis中的**实时游戏状态** (`game:state:{gameId}`)。
- 将操作事件**异步写入**一个队列如Redis Stream或RabbitMQ
- **释放锁**。
- 立即向客户端返回成功响应。
2. 一个独立的**后台Worker**消费队列中的事件批量将游戏会话数据持久化到MongoDB (`game_sessions` 集合)。
- **读操作**:
- **进行中的游戏**: 优先从**Redis**读取实时状态。
- **历史游戏/玩家统计**: 从**MongoDB**读取。
- **优势**:
- **低延迟**: 游戏核心逻辑的读写都在内存中完成,响应迅速。
- **高吞吐**: 将对DB的写操作异步化和批量化减轻数据库压力。
- **数据最终一致性**: 即使后台Worker暂时失败数据也保留在队列中保证最终会持久化到MongoDB。

View File

@@ -0,0 +1,908 @@
# 游戏核心逻辑详设文档
> **文档版本**: 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[]
}

View File

@@ -0,0 +1,256 @@
# 部署运维详设文档
> **文档版本**: v1.0
> **撰写人**: DevOps工程师
> **创建日期**: 2024年9月11日
## 1. 架构总览
我们将采用云原生技术栈以容器化为核心利用Kubernetes进行服务编排实现高可用、可扩展、易于维护的部署架构。
- **云服务提供商**: 推荐使用腾讯云、阿里云等主流云厂商,以获得稳定的基础设施和丰富的云产品支持。
- **容器化**: Docker
- **容器编排**: Kubernetes (K8s)
- **CI/CD**: GitHub Actions
- **监控与告警**: Prometheus + Grafana + Alertmanager
- **日志管理**: ELK Stack (Elasticsearch, Logstash, Kibana) 或 Loki
## 2. 容器化 (Docker)
### 2.1 后端服务 Dockerfile
一个优化的、多阶段构建的`Dockerfile`示例:
```dockerfile
# ---- Base Stage ----
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
# ---- Dependencies Stage ----
FROM base AS dependencies
RUN npm install --frozen-lockfile
# ---- Build Stage ----
FROM dependencies AS build
COPY . .
RUN npm run build
# ---- Production Stage ----
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=dependencies /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 8080
CMD ["node", "dist/main.js"]
```
- **多阶段构建**: 减小最终镜像体积,只包含生产运行所需的依赖。
- **使用`alpine`镜像**: 基于轻量级的Alpine Linux进一步减小镜像大小。
- **缓存优化**: 将`package.json`的复制和`npm install`分层利用Docker的层缓存机制只有在依赖变更时才重新安装。
### 2.2 镜像仓库
- **推荐**: 使用云厂商提供的容器镜像服务如腾讯云TCR、阿里云ACR
- **CI/CD集成**: GitHub Actions将在构建成功后自动将Docker镜像推送到指定的镜像仓库并打上版本标签`git commit hash`)。
## 3. Kubernetes (K8s) 部署
### 3.1 部署物清单 (Manifests)
我们将使用YAML文件来定义所有K8s资源。
#### a) `deployment.yaml` (后端服务)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: game-server-deployment
spec:
replicas: 3 # 初始副本数为3可根据负载自动伸缩
selector:
matchLabels:
app: game-server
template:
metadata:
labels:
app: game-server
spec:
containers:
- name: game-server
image: your-registry/game-server:latest # 镜像地址
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
envFrom:
- configMapRef:
name: game-server-config
- secretRef:
name: game-server-secrets
livenessProbe: # 存活探针
httpGet:
path: /api/v1/healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe: # 就绪探针
httpGet:
path: /api/v1/healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
```
#### b) `service.yaml` (服务暴露)
```yaml
apiVersion: v1
kind: Service
metadata:
name: game-server-service
spec:
type: LoadBalancer # 使用云厂商的LB暴露服务
selector:
app: game-server
ports:
- protocol: TCP
port: 80 # LB监听80端口
targetPort: 8080
```
#### c) `hpa.yaml` (水平Pod自动伸缩)
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: game-server-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: game-server-deployment
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 75 # CPU使用率超过75%时扩容
```
### 3.2 配置与密钥管理
- **ConfigMap**: 用于存储非敏感配置如数据库地址、Redis地址、日志级别等。
- **Secret**: 用于存储敏感信息如数据库密码、JWT密钥等。必须进行Base64编码。
## 4. CI/CD (GitHub Actions)
### 4.1 工作流 (`.github/workflows/deploy.yml`)
```yaml
name: Deploy to Production
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install --frozen-lockfile
- name: Run tests
run: npm test
- name: Log in to Docker Registry
uses: docker/login-action@v2
with:
registry: your-registry.com
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: your-registry.com/game-server:${{ github.sha }}
- name: Set up Kubeconfig
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Deploy to Kubernetes
uses: azure/k8s-deploy@v4
with:
action: 'deploy'
manifests: |
k8s/deployment.yaml
k8s/service.yaml
images: |
your-registry.com/game-server:${{ github.sha }}
```
- **触发条件**: 当代码推送到`main`分支时自动触发。
- **流程**: 拉取代码 -> 安装依赖 -> 运行测试 -> 登录镜像仓库 -> 构建并推送镜像 -> 部署到K8s。
- **密钥管理**: 所有敏感信息如密码、Kubeconfig都存储在GitHub Secrets中。
## 5. 监控与告警
- **Prometheus**:
- 通过`kube-prometheus-stack`部署在K8s集群中。
- 自动发现并抓取K8s Pod和Node的指标。
- 后端服务需要暴露一个`/metrics`端点提供自定义业务指标如在线玩家数、活跃游戏数、API响应延迟等
- **Grafana**:
- 提供可视化的监控仪表盘Dashboard
- 预置Dashboard用于监控集群资源、Node.js应用性能等。
- 自定义Dashboard展示核心业务指标。
- **Alertmanager**:
- 根据Prometheus中预设的告警规则如CPU使用率过高、服务Pod重启频繁、API错误率上升通过邮件、钉钉、企业微信等方式发送告警通知。
## 6. 日志管理
- **方案**: **Loki + Promtail**
- **Promtail**: 作为日志代理部署在每个K8s节点上负责收集容器日志并发送给Loki。
- **Loki**: 轻量级的日志聚合系统,对日志进行索引和存储。
- **集成**: 在Grafana中配置Loki作为数据源可以直接在Grafana中查询和分析日志与监控指标在同一平台展示方便问题排查。
## 7. 部署策略 (蓝绿部署)
为实现零停机更新,采用蓝绿部署策略。
1. **当前版本 (Blue)**: `game-server-deployment-blue` 正在运行,并通过`game-server-service`对外提供服务。
2. **部署新版本 (Green)**:
- 创建一个新的Deployment `game-server-deployment-green`,包含新版本的代码。
- 等待`green`环境的所有Pod都进入`Ready`状态。
3. **流量切换**:
- 修改`game-server-service``selector`,将其指向`green`环境的Pod (`app: game-server-green`)。
- K8s会自动将流量无缝切换到新版本。
4. **观察期**:
- 观察新版本运行是否稳定,监控核心指标是否正常。
5. **下线旧版本**:
- 如果一切正常,删除`game-server-deployment-blue`
- 如果出现问题可以快速将Service的`selector`切回`blue`环境,实现秒级回滚。
此流程可通过CI/CD脚本自动化。

1006
scripts/PRD.txt Normal file

File diff suppressed because it is too large Load Diff

47
scripts/example_prd.txt Normal file
View File

@@ -0,0 +1,47 @@
<context>
# Overview
[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.]
# Core Features
[List and describe the main features of your product. For each feature, include:
- What it does
- Why it's important
- How it works at a high level]
# User Experience
[Describe the user journey and experience. Include:
- User personas
- Key user flows
- UI/UX considerations]
</context>
<PRD>
# Technical Architecture
[Outline the technical implementation details:
- System components
- Data models
- APIs and integrations
- Infrastructure requirements]
# Development Roadmap
[Break down the development process into phases:
- MVP requirements
- Future enhancements
- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks]
# Logical Dependency Chain
[Define the logical order of development:
- Which features need to be built first (foundation)
- Getting as quickly as possible to something usable/visible front end that works
- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches]
# Risks and Mitigations
[Identify potential risks and how they'll be addressed:
- Technical challenges
- Figuring out the MVP that we can build upon
- Resource constraints]
# Appendix
[Include any additional information:
- Research findings
- Technical specifications]
</PRD>

11
tasks/task_001.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 1
# Title: Setup Project Infrastructure (Taro, Fastify, TS)
# Status: pending
# Dependencies: None
# Priority: high
# Description: Establish the foundational project structure for both frontend (Taro 4.x + React 18 + TypeScript) and backend (Node.js 18+ + Fastify + TypeScript). This includes setting up the monorepo (if applicable), build tools, and basic configurations for development and production environments.
# Details:
Initialize a Taro project, configure Fastify server, integrate TypeScript for both, set up ESLint and Prettier for code consistency. Ensure Docker setup for local development. Implement basic 'hello world' endpoints for verification.
# Test Strategy:
Verify successful compilation and execution of both frontend and backend. Access a basic frontend page and a backend API endpoint (e.g., /health). Check code formatting with ESLint/Prettier.

11
tasks/task_002.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 2
# Title: Database Setup (MongoDB & Redis)
# Status: pending
# Dependencies: 1
# Priority: high
# Description: Set up MongoDB for persistent data storage and Redis for caching and real-time data. This includes configuring connections, defining initial schemas for core entities (User, Room), and ensuring database access from the backend.
# Details:
Install and configure MongoDB and Redis instances (e.g., via Docker Compose). Implement Mongoose models for User and Room. Create Redis client connection and basic `set`/`get` functions. Ensure secure connection strings and environment variable handling.
# Test Strategy:
Write unit tests for database connection and basic CRUD operations on a dummy collection for MongoDB. Verify Redis `set`/`get` functionality. Confirm database schemas are correctly applied.

11
tasks/task_003.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 3
# Title: Basic UI Component Library Development
# Status: pending
# Dependencies: 1
# Priority: high
# Description: Develop a set of reusable, theme-compliant UI components (buttons, modals, loading spinners, input fields) based on the 'deep tech' design principle. These components will form the building blocks for the entire application's frontend.
# Details:
Implement components using React functional components and styled-components/Sass. Focus on the specified color scheme (#6366f1, #40e0d0, #0f1419 background) and smooth animations. Ensure mobile-first touch optimization (min touch area 44px).
# Test Strategy:
Create a Storybook or similar component showcase. Visually inspect components for design adherence and responsiveness across different screen sizes. Test touch interactions on mobile emulators.

11
tasks/task_004.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 4
# Title: User Authentication (WeChat Login & Guest Mode)
# Status: pending
# Dependencies: 1, 2, 3
# Priority: high
# Description: Implement the user authentication system, including WeChat authorization for quick login and a guest experience mode. This involves both frontend UI for login and backend API for user session management.
# Details:
Frontend: Implement WeChat login button, handle authorization callback. Backend: Create API endpoints for WeChat login (code exchange for openid), user registration/login. Implement JWT token generation for session management. For guest mode, generate a temporary local ID and store basic data in local storage. Add a mechanism for guest accounts to bind to WeChat later.
# Test Strategy:
Test WeChat login flow: successful login, token generation, and user data retrieval. Test guest mode: start as guest, verify local data storage, and conversion to full account. Verify API security (e.g., unauthorized access).

11
tasks/task_005.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 5
# Title: User Profile & Data Models (Server & DB)
# Status: pending
# Dependencies: 2, 4
# Priority: high
# Description: Define and implement the server-side data models and APIs for user profiles, including nickname, avatar, level, experience, and basic game statistics. This task focuses on persistent storage and retrieval of user data.
# Details:
Update MongoDB User model to include `level`, `experience`, `rank`, `stats` (totalGames, wins, winRate, currentStreak, maxStreak), and `achievements`. Implement API endpoints for fetching and updating user profiles. Ensure data consistency and validation.
# Test Strategy:
Create/update user profiles via API. Verify data is correctly stored in MongoDB and retrieved. Test edge cases for stats updates (e.g., first game, win/loss).

11
tasks/task_006.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 6
# Title: Game Board UI & Plane Rendering (Client)
# Status: pending
# Dependencies: 3
# Priority: high
# Description: Develop the client-side game board component (10x10 grid) and the visual representation of planes. This includes the basic rendering of the grid and placeholder plane sprites.
# Details:
Create a `GameBoard` component using Canvas or a grid-based CSS layout. Implement `PlaneSprite` components that can be positioned on the grid. Adhere to the 'deep tech' theme for visual design. The board should support coordinate system (A-J, 1-10).
# Test Strategy:
Render the game board with empty cells. Place dummy plane sprites at various positions and orientations to ensure correct rendering. Verify coordinate display.

11
tasks/task_007.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 7
# Title: Plane Model & Collision Detection Logic
# Status: pending
# Dependencies: 1, 6
# Priority: high
# Description: Implement the plane data structure (head, wings, body, tail) and the logic for checking placement legality, including boundary checks, no overlap, and completeness. This logic will be shared between client-side UI feedback and server-side validation.
# Details:
Define a plane object with its shape (array of relative coordinates from head), orientation, and position. Implement a `checkPlacementLegality(board, plane)` function that verifies all constraints: 10x10 boundary, no overlap with other planes, and all plane parts are within bounds. This logic should be robust and reusable.
# Test Strategy:
Write comprehensive unit tests for `checkPlacementLegality` covering valid placements, invalid placements (out of bounds, overlapping, incomplete), and different plane orientations. Test performance of the algorithm with multiple planes.

11
tasks/task_008.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 8
# Title: Plane Placement Interaction (Client)
# Status: pending
# Dependencies: 6, 7
# Priority: high
# Description: Develop the client-side interactive system for players to drag, drop, and rotate their 3 planes on the 10x10 grid. Provide real-time visual feedback on placement legality.
# Details:
Implement drag-and-drop functionality for planes. Add a rotate button/gesture for changing plane orientation (4 directions). Use the `checkPlacementLegality` logic to provide immediate visual feedback (e.g., green highlight for valid, red for invalid). Include an 'auto-place' button. A 'confirm placement' button should send the final plane configurations to the server.
# Test Strategy:
Manually test dragging planes to all possible valid and invalid positions. Test rotation in all directions. Verify visual feedback (color changes) is accurate. Test 'auto-place' functionality. Confirm the 'confirm placement' button sends correct data.

11
tasks/task_009.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 9
# Title: WebSocket Server & Basic Messaging
# Status: pending
# Dependencies: 1
# Priority: high
# Description: Set up the WebSocket server using Fastify's WebSocket plugin. Define a basic message protocol and implement initial message handling for connection, disconnection, and a simple 'ping-pong' heartbeat mechanism.
# Details:
Integrate `@fastify/websocket` plugin. Design a generic `GameMessage` interface with `type`, `roomCode`, `userId`, `data`, `timestamp`, `sequence`. Implement handlers for `connection` and `disconnection` events. Set up a heartbeat mechanism to detect inactive clients (e.g., every 30 seconds).
# Test Strategy:
Use a WebSocket client (e.g., Postman, browser console) to connect, send messages, and disconnect. Verify server logs for connection/disconnection events. Test heartbeat by letting a client idle and observing server-side timeout.

11
tasks/task_010.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 10
# Title: WebSocket Client Integration & State Management
# Status: pending
# Dependencies: 1, 9
# Priority: high
# Description: Integrate WebSocket client logic into the Taro frontend. Implement global state management (Zustand) to store connection status and received game messages, ensuring real-time UI updates.
# Details:
Create a WebSocket service/hook in the frontend to manage connection lifecycle (connect, disconnect, send, receive). Use Zustand to create a `GameStore` that holds `connectionState`, `user`, `room`, `gameState`. Update UI based on changes in `GameStore`. Implement message parsing and dispatching to relevant parts of the application.
# Test Strategy:
Verify client can connect to WebSocket server. Send test messages from server and confirm client receives and updates state. Test reconnection logic (manual disconnect/reconnect).

11
tasks/task_011.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 11
# Title: Room Management Backend (Create, Join, Match)
# Status: pending
# Dependencies: 2, 4, 9
# Priority: high
# Description: Develop the backend logic for managing game rooms: creating private rooms (with password, config), joining rooms via code, and implementing a basic random matchmaking queue.
# Details:
Implement `Room` model in MongoDB and cache active room states in Redis. Create API endpoints for `createRoom`, `joinRoom`, `leaveRoom`. For matchmaking, use a Redis List or Set as a queue; when two players are in the queue, create a room for them. Implement room code generation (6-digit numeric). Handle host permissions and room configuration (e.g., round time limit, AI difficulty).
# Test Strategy:
Unit test room creation with various configs (password, no password). Test joining valid/invalid rooms. Simulate multiple users joining a matchmaking queue and verify room creation. Test room status updates in Redis.

11
tasks/task_012.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 12
# Title: Room Management Frontend (UI for Create, Join, Match)
# Status: pending
# Dependencies: 3, 10, 11
# Priority: high
# Description: Develop the client-side UI for room management, allowing users to create private rooms, enter room codes to join, and initiate quick matchmaking.
# Details:
Create 'Create Room' modal/page with options for password, AI difficulty, round time. Implement 'Join Room' input field for room code. Implement a 'Quick Match' button. Display room lists/states. Use WebSocket to send room actions and receive updates. Show loading/waiting states during matching.
# Test Strategy:
Test creating a room and verify room code display. Test joining a room with the correct code. Test quick matchmaking flow. Verify error messages for invalid room codes or full rooms. Ensure UI updates correctly with WebSocket messages.

11
tasks/task_013.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 13
# Title: Core Game Engine Logic (Server-side Combat)
# Status: pending
# Dependencies: 2, 7, 9, 11
# Priority: high
# Description: Implement the server-side game engine responsible for managing game state, handling player attacks, determining hit/miss/destroy outcomes, and enforcing game rules (turn-based, win conditions).
# Details:
Create a `GameEngine` module. Store game state (player boards, plane positions, attack history, current turn) in Redis. Implement `processAttack(roomId, userId, targetCoordinate)` function. This function should: 1. Validate turn. 2. Check target on opponent's board. 3. Determine MISS, HIT, or DESTROY (if head is hit). 4. Update game state. 5. Check win condition (all 3 planes destroyed). 6. Switch turn. Use the `Plane` data structure and collision logic from Task 7. Store game sessions in MongoDB for history.
# Test Strategy:
Unit test `processAttack` with various scenarios: miss, hit (body), hit (head/destroy), hitting same spot twice. Test turn switching and win condition detection. Simulate a full game flow via API calls to verify engine logic. Test concurrent attacks (should be prevented by turn validation).

11
tasks/task_014.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 14
# Title: Simple AI Implementation (Random + Basic Exclusion)
# Status: pending
# Dependencies: 13
# Priority: high
# Description: Implement the 'Simple AI' difficulty level. This AI will make random attacks, but with basic exclusion of already attacked cells. It serves as a training partner for new players.
# Details:
Create an `AIService` module. For 'Simple AI', implement a strategy that: 1. Generates a list of all untargeted cells on the opponent's board. 2. Randomly selects one cell from this list. 3. Calls the `processAttack` function with the chosen coordinate. Ensure AI decision-making time is <500ms.
# Test Strategy:
Play multiple games against the Simple AI. Verify AI consistently makes moves, does not attack the same spot twice, and adheres to the turn-based system. Check response time.

11
tasks/task_015.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 15
# Title: Game Play Loop Integration (Client & Server)
# Status: pending
# Dependencies: 8, 10, 12, 13, 14
# Priority: high
# Description: Integrate all core game components (plane placement, attack phase, turn management) into a complete, playable game loop for both player vs. player and player vs. AI modes. This involves extensive WebSocket communication and UI state updates.
# Details:
Frontend: Implement `GameScreen` component. Display both player's (hidden planes) and opponent's boards. Handle 'your turn' UI, attack target selection, and attack confirmation. Display hit/miss/destroy feedback. Show opponent's moves. Implement round countdown. Backend: Coordinate plane placement phase, transition to battle phase, manage turn timers, handle timeout for attacks (random attack). Broadcast game state changes via WebSocket to all participants.
# Test Strategy:
Conduct full end-to-end tests for PV P and PVE (AI) matches. Verify plane placement, attack sequence, turn switching, hit feedback, and game conclusion. Test round countdown and timeout auto-attack. Check for any desynchronization issues between clients.

11
tasks/task_016.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 16
# Title: Game End & Settlement System
# Status: pending
# Dependencies: 5, 15
# Priority: high
# Description: Implement the logic and UI for ending a game, determining the winner, and displaying a settlement screen with results and rewards (e.g., experience points).
# Details:
Backend: When a win condition is met, update player stats (wins, losses, streaks, experience) in MongoDB. Calculate XP based on victory/defeat (win +100XP, loss +20XP). Frontend: Display a 'Game Over' modal/screen showing winner/loser, final board state, and earned XP/rewards. Include 'Play Again' and 'Return to Lobby' buttons.
# Test Strategy:
Play games to completion (win/loss) and verify correct winner determination. Check if XP and stats are updated in the database. Verify settlement screen displays accurate information. Test 'Play Again' and 'Return to Lobby' functionality.

11
tasks/task_017.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 17
# Title: Basic Game Statistics & Display
# Status: pending
# Dependencies: 5, 16
# Priority: medium
# Description: Develop the functionality to track and display basic user game statistics, such as total games played, win rate, and current/max win streaks, on the user's profile.
# Details:
Frontend: Create a 'My Profile' section that fetches user stats from the backend. Display `totalGames`, `wins`, `winRate` (calculated from wins/totalGames), `currentStreak`, `maxStreak`. Backend: Ensure that `Game End & Settlement` (Task 16) correctly updates these fields in the User model.
# Test Strategy:
Play multiple games with varying outcomes. Verify that statistics on the profile page update correctly after each game. Test edge cases like a user's first game, or breaking a win streak.

11
tasks/task_018.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 18
# Title: Disconnect & Reconnect Mechanism (Client & Server)
# Status: pending
# Dependencies: 9, 10, 13
# Priority: high
# Description: Implement a robust mechanism for handling client disconnections during a game, allowing players to automatically reconnect and resume their game session.
# Details:
Frontend: Implement exponential backoff for WebSocket reconnection attempts (1s, 2s, 4s...). Display a 'Reconnecting...' UI. Backend: Store a snapshot of the current game state in Redis. When a client reconnects, verify their identity and send the latest game state snapshot. Implement a 30-minute room retention policy for disconnected players to rejoin.
# Test Strategy:
During a live game, forcibly disconnect the client (e.g., turn off Wi-Fi, close and reopen app quickly). Verify automatic reconnection and game state restoration. Test behavior when reconnection fails after multiple attempts (return to lobby). Test if the room persists for 30 mins.

11
tasks/task_019.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 19
# Title: Initial Performance Optimization & Bug Fixes
# Status: pending
# Dependencies: 15, 18
# Priority: high
# Description: Conduct initial performance profiling and address critical bugs identified during MVP development. Focus on improving client-side rendering, network latency, and overall responsiveness.
# Details:
Frontend: Optimize rendering of `GameBoard` and `PlaneSprite` components (e.g., memoization, use of `requestAnimationFrame`). Implement message compression for WebSocket. Backend: Review database queries for efficiency (indexing). Monitor server CPU/memory usage. Address any reported bugs. Ensure client-side loading times (<2s first screen, <300ms page switch) and stable 60fps.
# Test Strategy:
Use browser/mini-program developer tools to profile rendering performance (FPS, repaint times). Monitor network requests for latency and payload size. Conduct stress tests on the backend. Perform thorough QA testing to identify and verify bug fixes.

11
tasks/task_020.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 20
# Title: User Level & Rank System (Frontend & Backend)
# Status: pending
# Dependencies: 5, 16, 17
# Priority: medium
# Description: Expand the user profile to include a level system (1-100) based on accumulated experience points and a rank system (Bronze-King) based on game performance.
# Details:
Backend: Implement logic for XP thresholds for level progression. Define rules for rank promotion/demotion (e.g., every 10 levels for rank, or based on Elo/Glicko rating). Update `User` model and `Game End & Settlement` to reflect these changes. Frontend: Display user's current level and rank visually on their profile and in game UI. Design appropriate badges/icons for ranks.
# Test Strategy:
Play multiple games to gain XP and level up. Verify level and rank increase correctly. Test edge cases for rank promotion/demotion. Ensure UI accurately reflects the current level and rank.

11
tasks/task_021.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 21
# Title: Friend System (WeChat Import & Invite)
# Status: pending
# Dependencies: 4, 10, 12
# Priority: medium
# Description: Implement a friend system allowing users to import WeChat friends, view online status, and send game invitations. Include a basic blacklist feature.
# Details:
Frontend: Implement UI for 'Friends List'. Use WeChat API for friend relationship chain authorization and import. Display online/offline status (via WebSocket presence updates). Implement 'Invite to Game' button that generates a shareable link/card. Backend: Store friend relationships. Implement APIs for adding/removing friends and managing blacklists. Use Redis to track online users.
# Test Strategy:
Test WeChat friend import. Verify online status updates in real-time. Send game invitations to friends and confirm they can join the invited room. Test blacklist functionality.

11
tasks/task_022.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 22
# Title: In-Room Chat (Text & Emotes)
# Status: pending
# Dependencies: 10, 12
# Priority: medium
# Description: Add a real-time chat feature within game rooms, supporting both text messages and a selection of emotes.
# Details:
Frontend: Implement a chat input field and message display area in the room UI. Include an emote picker. Backend: Implement WebSocket message type for `CHAT_MESSAGE`. Broadcast chat messages to all participants in the room. Implement basic message filtering for offensive content.
# Test Strategy:
Join a room with multiple players. Send text messages and emotes. Verify all players receive messages in real-time. Test message filtering. Check for UI responsiveness with many messages.

11
tasks/task_023.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 23
# Title: Game Replay Functionality
# Status: pending
# Dependencies: 2, 13, 15
# Priority: medium
# Description: Enable users to view replays of their past games, step-by-step, to analyze strategies and share exciting moments.
# Details:
Backend: When a game ends, store a condensed `moves` history (sequence of attacks, results) in the `GameSession` model in MongoDB. Frontend: Create a 'Replay' screen. Fetch `GameSession` data. Reconstruct the game state step-by-step, allowing users to navigate through turns (play, pause, next, previous).
# Test Strategy:
Play several games and verify that replays are saved. Access replay for different games and verify accurate reconstruction of the game. Test playback controls (play/pause, step forward/backward).

11
tasks/task_024.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 24
# Title: Share Game Results (Image Generation)
# Status: pending
# Dependencies: 16, 17
# Priority: medium
# Description: Implement functionality to generate shareable image cards of game results, allowing users to easily share their achievements on WeChat or other platforms.
# Details:
Frontend: After a game, provide a 'Share' button. Use Canvas API to render the game result (winner, score, final board state) into an image. Include user's avatar, nickname, and a QR code for the mini-program. Implement WeChat sharing API to allow sharing the generated image to friends/groups/moments.
# Test Strategy:
Complete a game and trigger the share function. Verify the generated image accurately reflects the game results and looks visually appealing. Test sharing to WeChat (friends, groups, moments) and confirm successful delivery.

11
tasks/task_025.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 25
# Title: Achievement System (Badges, Tasks)
# Status: pending
# Dependencies: 5, 16, 17
# Priority: medium
# Description: Introduce an achievement system with various badges and tasks to incentivize player engagement and provide long-term goals.
# Details:
Backend: Define `Achievement` model (e.g., 'First Win', '5-Win Streak', 'Hit King'). Implement logic to detect when achievements are unlocked based on game events and player stats. Store unlocked achievements in the `User` model. Frontend: Create an 'Achievements' section in the user profile to display earned badges and progress towards unearned ones. Provide visual flair for new achievements.
# Test Strategy:
Test unlocking various achievements (e.g., win first game, achieve a streak). Verify achievements are correctly recorded in the database. Check if the UI updates to show new achievements and progress.

11
tasks/task_026.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 26
# Title: Advanced AI Difficulty Levels (Normal, Hard)
# Status: pending
# Dependencies: 14, 15
# Priority: medium
# Description: Enhance the AI system by implementing 'Normal' and 'Hard' difficulty levels, utilizing more sophisticated strategies like probability heatmaps and pattern recognition.
# Details:
For 'Normal AI', implement a strategy that prioritizes cells with higher probability of containing a plane part (e.g., based on surrounding hits). For 'Hard AI', introduce multi-step prediction and basic pattern recognition (e.g., if a HIT occurs, prioritize adjacent cells). Ensure AI difficulty can be selected in room creation.
# Test Strategy:
Play numerous games against Normal and Hard AI. Observe their attack patterns and verify they are more strategic than Simple AI. Ensure AI still makes decisions within the <500ms time limit. Compare win rates against different AI difficulties.

11
tasks/task_027.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 27
# Title: Leaderboard System
# Status: pending
# Dependencies: 5, 17, 20
# Priority: medium
# Description: Develop a global leaderboard system to display top players based on various metrics (e.g., rank, wins, win rate).
# Details:
Backend: Implement API endpoints to fetch leaderboard data (e.g., top 100 players by rank, by wins). This might involve efficient queries on the `Users` collection or pre-calculated data in Redis. Frontend: Create a 'Leaderboard' screen with tabs for different ranking criteria. Display player's own rank within the leaderboard.
# Test Strategy:
Populate the database with dummy user data and varied stats. Verify leaderboard data is fetched correctly and sorted as expected for different criteria. Check UI rendering of top players and user's own position.

11
tasks/task_028.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 28
# Title: Matchmaking Algorithm Optimization
# Status: pending
# Dependencies: 11, 20, 27
# Priority: medium
# Description: Refine the matchmaking algorithm to improve player experience by considering factors like player skill (rank/level) and reducing wait times. Implement tiered matching.
# Details:
Backend: Modify the matchmaking queue logic. Instead of pure random, prioritize matching players with similar ranks/levels. If no suitable match is found within a short time (e.g., 30s), expand the search criteria (wider rank range, then consider matching with AI or bots). Implement a timeout for matchmaking (e.g., 120s) before offering alternative modes.
# Test Strategy:
Simulate matchmaking with various player populations (e.g., many low-rank, few high-rank). Verify that players are matched with appropriate opponents or that search criteria expand as expected. Measure average matchmaking time.

11
tasks/task_029.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 29
# Title: System Monitoring & Analytics Integration
# Status: pending
# Dependencies: 1, 19
# Priority: medium
# Description: Integrate comprehensive monitoring and analytics tools (Prometheus, Grafana, ELK, Sentry) to track application health, performance, and key business metrics.
# Details:
Set up Prometheus for metric collection and Grafana for dashboard visualization. Configure ELK stack for log aggregation and analysis. Integrate Sentry for real-time error tracking. Implement basic user behavior tracking (e.g., login, room creation, game start/end) using events as described in PRD (8.4.1).
# Test Strategy:
Verify all monitoring tools are collecting data (metrics, logs, errors). Trigger specific events/errors and confirm they appear in the respective dashboards. Check if custom business metrics are being tracked correctly.

11
tasks/task_030.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 30
# Title: Interactive Newbie Guide
# Status: pending
# Dependencies: 3, 8, 15, 14
# Priority: medium
# Description: Create an interactive, step-by-step tutorial for new users to learn the game rules, controls, and core mechanics, including AI accompaniment.
# Details:
Design a series of guided steps (e.g., 'how to place planes', 'how to attack'). Implement overlay UI elements to highlight actions. Use a simplified AI to guide the player through their first game. Allow users to skip or review the guide. Store guide progress in user settings.
# Test Strategy:
On a new account, go through the entire newbie guide. Verify each step is clear and interactive. Test skipping and reviewing sections. Ensure progress is saved if the user exits mid-guide.

11
tasks/task_031.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 31
# Title: Game Sound Effects & Visual Effects
# Status: pending
# Dependencies: 15
# Priority: medium
# Description: Add engaging sound effects for game events (hit, miss, destroy) and UI interactions, along with corresponding visual effects.
# Details:
Source or create sound assets (e.g., 'miss' splash, 'hit' thud, 'destroy' explosion). Integrate an audio manager to play sounds based on game events. Implement visual effects for attacks (e.g., a laser shot, explosion animations for hits/destroys). Ensure performance is not impacted and provide volume controls.
# Test Strategy:
Play a game and verify that all key actions (placing, attacking, hitting, missing, destroying) have appropriate sound and visual effects. Test UI interaction sounds. Check performance during intense effect sequences. Test volume controls.

11
tasks/task_032.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 32
# Title: Activity System (Daily Sign-in, Events)
# Status: pending
# Dependencies: 2, 5
# Priority: low
# Description: Implement a basic activity system to offer daily rewards (e.g., sign-in bonuses) and temporary event-based incentives.
# Details:
Backend: Design a flexible `Activity` data model (e.g., daily sign-in, specific win count event). Implement API endpoints for checking activity status and claiming rewards. Store user activity progress in MongoDB. Frontend: Create an 'Activities' section. Display current activities, progress, and a button to claim rewards. Implement daily sign-in UI with calendar.
# Test Strategy:
Test daily sign-in over several days. Verify rewards are granted correctly and progress is tracked. Test a temporary event (e.g., 'win 3 games for bonus') and ensure rewards are given upon completion.

11
tasks/task_033.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 33
# Title: Basic Shop System (Skins, Effects)
# Status: pending
# Dependencies: 2, 5, 31
# Priority: low
# Description: Implement a basic in-game shop where players can purchase cosmetic items like plane skins and attack effects using virtual currency.
# Details:
Backend: Define `Item` model (ID, type, price, asset path). Implement API for fetching shop items, purchasing items, and managing user's inventory. Users need a virtual currency balance. Frontend: Create a 'Shop' screen displaying available skins and effects. Allow users to preview items and make purchases. Display current virtual currency balance.
# Test Strategy:
Add dummy items to the shop. Test purchasing items with sufficient/insufficient currency. Verify item is added to user's inventory and currency is deducted. Test equipping purchased skins/effects in a game.

11
tasks/task_034.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 34
# Title: Push Notifications (WeChat Template Messages)
# Status: pending
# Dependencies: 4, 11
# Priority: low
# Description: Integrate WeChat template messages to send important notifications to users, such as match invitations, game start reminders, or activity updates.
# Details:
Backend: Implement WeChat template message sending API calls. Obtain user's formId/subscribe message permissions during user interactions (e.g., after login, when creating a room). Define templates for 'Game Invitation', 'Match Found', 'Activity Reminder'. Trigger these messages based on backend events. Frontend: Guide users to authorize template messages.
# Test Strategy:
Send a game invitation to a test user and verify they receive a WeChat template message. Test other notification types (e.g., match found, activity reminder). Ensure proper authorization handling.

11
tasks/task_035.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 35
# Title: Admin Dashboard for Data Analysis
# Status: pending
# Dependencies: 2, 29
# Priority: low
# Description: Develop a basic admin dashboard to view key operational data, such as DAU, retention rates, and game statistics, for product analysis and decision-making.
# Details:
Develop a separate web application (e.g., using React Admin or a simple Fastify API + static HTML) for the admin dashboard. Implement authentication for admin users. Fetch data from MongoDB (user stats, game logs) and Redis (real-time metrics). Display DAU, new users, retention rates, game counts, top players. Integrate with Grafana dashboards if possible.
# Test Strategy:
Access the admin dashboard with admin credentials. Verify data displayed matches direct database queries. Check if DAU, retention, and game stats are calculated and presented correctly. Test dashboard security.

11
tasks/task_036.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 36
# Title: Spectator Mode
# Status: pending
# Dependencies: 10, 13, 21
# Priority: low
# Description: Allow non-playing users to join active game rooms as spectators, viewing the ongoing match in real-time.
# Details:
Backend: Extend WebSocket handling to allow 'spectator' roles. When a spectator joins, send them the current game state snapshot. Broadcast game updates to spectators without allowing them to send game actions. Frontend: Add a 'Spectate' option to rooms or friend profiles. Display the game board and actions without interactive controls. Hide opponent's plane placement until destroyed.
# Test Strategy:
Join an active game as a spectator. Verify real-time updates of game state (attacks, hits, turns). Ensure spectators cannot interact with the game. Test joining/leaving spectator mode. Verify performance with multiple spectators.

11
tasks/task_037.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 37
# Title: H5 Version Adaption
# Status: pending
# Dependencies: 1, 15
# Priority: low
# Description: Adapt the mini-program to run as a web (H5) application, ensuring full functionality and responsive design across different browsers and devices.
# Details:
Leverage Taro's H5 compilation capabilities. Address any platform-specific differences (e.g., WeChat APIs vs. browser APIs for login/sharing). Ensure responsive layout for various screen sizes (desktop, tablet, mobile browser). Test performance on H5.
# Test Strategy:
Compile and deploy the H5 version. Test all core functionalities (login, room, game, chat, stats) on different browsers (Chrome, Firefox, Safari) and devices. Verify responsive layout and performance.

11
tasks/task_038.txt Normal file
View File

@@ -0,0 +1,11 @@
# Task ID: 38
# Title: Internationalization (i18n) Support
# Status: pending
# Dependencies: 1, 3
# Priority: low
# Description: Implement internationalization to support multiple languages, starting with English and Chinese.
# Details:
Integrate an i18n library (e.g., `react-i18next`). Extract all hardcoded strings from the UI and backend messages into translation files (JSON/YAML). Implement a language switcher in settings. Ensure dynamic content (e.g., user-generated names) is handled correctly.
# Test Strategy:
Add English translation files. Switch language in settings and verify all UI text and messages are translated correctly. Test with different locales. Check for text overflow issues with longer translations.

666
tasks/tasks.json Normal file
View File

@@ -0,0 +1,666 @@
{
"tasks": [
{
"id": 1,
"title": "Setup Project Infrastructure (Taro, Fastify, TS)",
"titleTrans": "项目基础设施搭建 (Taro, Fastify, TS)",
"description": "Establish the foundational project structure for both frontend (Taro 4.x + React 18 + TypeScript) and backend (Node.js 18+ + Fastify + TypeScript). This includes setting up the monorepo (if applicable), build tools, and basic configurations for development and production environments.",
"descriptionTrans": "为前端 (Taro 4.x + React 18 + TypeScript) 和后端 (Node.js 18+ + Fastify + TypeScript) 建立基础项目结构。这包括设置 Monorepo如果适用、构建工具以及开发和生产环境的基本配置。",
"status": "pending",
"dependencies": [],
"priority": "high",
"details": "Initialize a Taro project, configure Fastify server, integrate TypeScript for both, set up ESLint and Prettier for code consistency. Ensure Docker setup for local development. Implement basic 'hello world' endpoints for verification.",
"detailsTrans": "初始化 Taro 项目,配置 Fastify 服务器,前后端集成 TypeScript设置 ESLint 和 Prettier 统一代码风格。确保 Docker 设置用于本地开发。实现基本的“hello world”端点进行验证。",
"testStrategy": "Verify successful compilation and execution of both frontend and backend. Access a basic frontend page and a backend API endpoint (e.g., /health). Check code formatting with ESLint/Prettier.",
"testStrategyTrans": "验证前端和后端是否成功编译和执行。访问一个基本的前端页面和一个后端 API 端点(例如,/health。使用 ESLint/Prettier 检查代码格式。"
},
{
"id": 2,
"title": "Database Setup (MongoDB & Redis)",
"titleTrans": "数据库搭建 (MongoDB & Redis)",
"description": "Set up MongoDB for persistent data storage and Redis for caching and real-time data. This includes configuring connections, defining initial schemas for core entities (User, Room), and ensuring database access from the backend.",
"descriptionTrans": "设置 MongoDB 用于持久数据存储Redis 用于缓存和实时数据。这包括配置连接、定义核心实体(用户、房间)的初始模式,并确保后端可以访问数据库。",
"status": "pending",
"dependencies": [
1
],
"priority": "high",
"details": "Install and configure MongoDB and Redis instances (e.g., via Docker Compose). Implement Mongoose models for User and Room. Create Redis client connection and basic `set`/`get` functions. Ensure secure connection strings and environment variable handling.",
"detailsTrans": "安装和配置 MongoDB 和 Redis 实例(例如,通过 Docker Compose。为 User 和 Room 实现 Mongoose 模型。创建 Redis 客户端连接和基本的 `set`/`get` 函数。确保安全的连接字符串和环境变量处理。",
"testStrategy": "Write unit tests for database connection and basic CRUD operations on a dummy collection for MongoDB. Verify Redis `set`/`get` functionality. Confirm database schemas are correctly applied.",
"testStrategyTrans": "为 MongoDB 数据库连接和虚拟集合上的基本 CRUD 操作编写单元测试。验证 Redis 的 `set`/`get` 功能。确认数据库模式已正确应用。"
},
{
"id": 3,
"title": "Basic UI Component Library Development",
"titleTrans": "基础UI组件库开发",
"description": "Develop a set of reusable, theme-compliant UI components (buttons, modals, loading spinners, input fields) based on the 'deep tech' design principle. These components will form the building blocks for the entire application's frontend.",
"descriptionTrans": "根据“深色科技”设计原则,开发一套可复用、符合主题的 UI 组件(按钮、模态框、加载指示器、输入框)。这些组件将构成整个应用程序前端的构建块。",
"status": "pending",
"dependencies": [
1
],
"priority": "high",
"details": "Implement components using React functional components and styled-components/Sass. Focus on the specified color scheme (#6366f1, #40e0d0, #0f1419 background) and smooth animations. Ensure mobile-first touch optimization (min touch area 44px).",
"detailsTrans": "使用 React 函数式组件和 styled-components/Sass 实现组件。专注于指定的配色方案(#6366f1、#40e0d0、#0f1419 背景)和流畅的动画。确保移动优先的触摸优化(最小触摸区域 44px。",
"testStrategy": "Create a Storybook or similar component showcase. Visually inspect components for design adherence and responsiveness across different screen sizes. Test touch interactions on mobile emulators.",
"testStrategyTrans": "创建 Storybook 或类似的组件展示。目视检查组件是否符合设计规范,并在不同屏幕尺寸下具有响应性。在移动模拟器上测试触摸交互。"
},
{
"id": 4,
"title": "User Authentication (WeChat Login & Guest Mode)",
"titleTrans": "用户认证 (微信登录与游客模式)",
"description": "Implement the user authentication system, including WeChat authorization for quick login and a guest experience mode. This involves both frontend UI for login and backend API for user session management.",
"descriptionTrans": "实现用户认证系统,包括微信授权快速登录和游客体验模式。这涉及登录的前端 UI 和用户会话管理的后端 API。",
"status": "pending",
"dependencies": [
1,
2,
3
],
"priority": "high",
"details": "Frontend: Implement WeChat login button, handle authorization callback. Backend: Create API endpoints for WeChat login (code exchange for openid), user registration/login. Implement JWT token generation for session management. For guest mode, generate a temporary local ID and store basic data in local storage. Add a mechanism for guest accounts to bind to WeChat later.",
"detailsTrans": "前端:实现微信登录按钮,处理授权回调。后端:创建微信登录的 API 端点code 交换 openid用户注册/登录。实现 JWT token 生成用于会话管理。游客模式下,生成临时本地 ID 并将基本数据存储在本地存储中。添加游客账号后续绑定微信的机制。",
"testStrategy": "Test WeChat login flow: successful login, token generation, and user data retrieval. Test guest mode: start as guest, verify local data storage, and conversion to full account. Verify API security (e.g., unauthorized access).",
"testStrategyTrans": "测试微信登录流程成功登录、token 生成和用户数据检索。测试游客模式:以游客身份开始,验证本地数据存储,以及转换为完整账号。验证 API 安全性(例如,未经授权的访问)。"
},
{
"id": 5,
"title": "User Profile & Data Models (Server & DB)",
"titleTrans": "用户档案与数据模型 (后端与数据库)",
"description": "Define and implement the server-side data models and APIs for user profiles, including nickname, avatar, level, experience, and basic game statistics. This task focuses on persistent storage and retrieval of user data.",
"descriptionTrans": "定义和实现用户档案的服务器端数据模型和 API包括昵称、头像、等级、经验和基本游戏统计数据。此任务侧重于用户数据的持久存储和检索。",
"status": "pending",
"dependencies": [
2,
4
],
"priority": "high",
"details": "Update MongoDB User model to include `level`, `experience`, `rank`, `stats` (totalGames, wins, winRate, currentStreak, maxStreak), and `achievements`. Implement API endpoints for fetching and updating user profiles. Ensure data consistency and validation.",
"detailsTrans": "更新 MongoDB 用户模型,使其包含 `level`、`experience`、`rank`、`stats` (总局数、胜场、胜率、当前连胜、最高连胜) 和 `achievements`。实现用于获取和更新用户档案的 API 端点。确保数据一致性和验证。",
"testStrategy": "Create/update user profiles via API. Verify data is correctly stored in MongoDB and retrieved. Test edge cases for stats updates (e.g., first game, win/loss).",
"testStrategyTrans": "通过 API 创建/更新用户档案。验证数据是否正确存储在 MongoDB 中并被检索。测试统计数据更新的边缘情况(例如,第一局游戏、胜利/失败)。"
},
{
"id": 6,
"title": "Game Board UI & Plane Rendering (Client)",
"titleTrans": "游戏棋盘UI与飞机渲染 (前端)",
"description": "Develop the client-side game board component (10x10 grid) and the visual representation of planes. This includes the basic rendering of the grid and placeholder plane sprites.",
"descriptionTrans": "开发客户端游戏棋盘组件10x10 网格)和飞机的视觉表示。这包括网格的基本渲染和飞机精灵的占位符。",
"status": "pending",
"dependencies": [
3
],
"priority": "high",
"details": "Create a `GameBoard` component using Canvas or a grid-based CSS layout. Implement `PlaneSprite` components that can be positioned on the grid. Adhere to the 'deep tech' theme for visual design. The board should support coordinate system (A-J, 1-10).",
"detailsTrans": "使用 Canvas 或基于 CSS 网格布局创建 `GameBoard` 组件。实现可以放置在网格上的 `PlaneSprite` 组件。视觉设计需遵循“深色科技”主题。棋盘应支持坐标系统A-J1-10。",
"testStrategy": "Render the game board with empty cells. Place dummy plane sprites at various positions and orientations to ensure correct rendering. Verify coordinate display.",
"testStrategyTrans": "渲染空单元格的游戏棋盘。在不同位置和方向放置虚拟飞机精灵,以确保正确渲染。验证坐标显示。"
},
{
"id": 7,
"title": "Plane Model & Collision Detection Logic",
"titleTrans": "飞机模型与碰撞检测逻辑",
"description": "Implement the plane data structure (head, wings, body, tail) and the logic for checking placement legality, including boundary checks, no overlap, and completeness. This logic will be shared between client-side UI feedback and server-side validation.",
"descriptionTrans": "实现飞机数据结构(机头、机翼、机身、机尾)和检查放置合法性的逻辑,包括边界检查、无重叠和完整性。此逻辑将在客户端 UI 反馈和服务器端验证之间共享。",
"status": "pending",
"dependencies": [
1,
6
],
"priority": "high",
"details": "Define a plane object with its shape (array of relative coordinates from head), orientation, and position. Implement a `checkPlacementLegality(board, plane)` function that verifies all constraints: 10x10 boundary, no overlap with other planes, and all plane parts are within bounds. This logic should be robust and reusable.",
"detailsTrans": "定义一个飞机对象,包含其形状(从机头开始的相对坐标数组)、方向和位置。实现一个 `checkPlacementLegality(board, plane)` 函数用于验证所有约束10x10 边界、不与其他飞机重叠,以及所有飞机部件都在边界内。此逻辑应健壮且可重用。",
"testStrategy": "Write comprehensive unit tests for `checkPlacementLegality` covering valid placements, invalid placements (out of bounds, overlapping, incomplete), and different plane orientations. Test performance of the algorithm with multiple planes.",
"testStrategyTrans": "为 `checkPlacementLegality` 编写全面的单元测试,涵盖有效放置、无效放置(出界、重叠、不完整)和不同飞机方向。测试算法在多架飞机情况下的性能。"
},
{
"id": 8,
"title": "Plane Placement Interaction (Client)",
"titleTrans": "飞机布置交互 (前端)",
"description": "Develop the client-side interactive system for players to drag, drop, and rotate their 3 planes on the 10x10 grid. Provide real-time visual feedback on placement legality.",
"descriptionTrans": "开发客户端交互系统,允许玩家在 10x10 网格上拖拽、放置和旋转 3 架飞机。提供放置合法性的实时视觉反馈。",
"status": "pending",
"dependencies": [
6,
7
],
"priority": "high",
"details": "Implement drag-and-drop functionality for planes. Add a rotate button/gesture for changing plane orientation (4 directions). Use the `checkPlacementLegality` logic to provide immediate visual feedback (e.g., green highlight for valid, red for invalid). Include an 'auto-place' button. A 'confirm placement' button should send the final plane configurations to the server.",
"detailsTrans": "实现飞机的拖放功能。添加旋转按钮/手势以改变飞机方向4 个方向)。使用 `checkPlacementLegality` 逻辑提供即时视觉反馈(例如,有效时绿色高亮,无效时红色)。包括一个“自动布置”按钮。一个“确认布置”按钮应将最终飞机配置发送到服务器。",
"testStrategy": "Manually test dragging planes to all possible valid and invalid positions. Test rotation in all directions. Verify visual feedback (color changes) is accurate. Test 'auto-place' functionality. Confirm the 'confirm placement' button sends correct data.",
"testStrategyTrans": "手动测试将飞机拖动到所有可能的有效和无效位置。测试所有方向的旋转。验证视觉反馈(颜色变化)是否准确。测试“自动布置”功能。确认“确认布置”按钮发送正确数据。"
},
{
"id": 9,
"title": "WebSocket Server & Basic Messaging",
"titleTrans": "WebSocket 服务器与基本消息",
"description": "Set up the WebSocket server using Fastify's WebSocket plugin. Define a basic message protocol and implement initial message handling for connection, disconnection, and a simple 'ping-pong' heartbeat mechanism.",
"descriptionTrans": "使用 Fastify 的 WebSocket 插件设置 WebSocket 服务器。定义基本消息协议并实现连接、断开连接和简单的“ping-pong”心跳机制的初始消息处理。",
"status": "pending",
"dependencies": [
1
],
"priority": "high",
"details": "Integrate `@fastify/websocket` plugin. Design a generic `GameMessage` interface with `type`, `roomCode`, `userId`, `data`, `timestamp`, `sequence`. Implement handlers for `connection` and `disconnection` events. Set up a heartbeat mechanism to detect inactive clients (e.g., every 30 seconds).",
"detailsTrans": "集成 `@fastify/websocket` 插件。设计一个通用的 `GameMessage` 接口,包含 `type`、`roomCode`、`userId`、`data`、`timestamp`、`sequence`。实现 `connection` 和 `disconnection` 事件的处理程序。建立心跳机制以检测不活跃的客户端(例如,每 30 秒)。",
"testStrategy": "Use a WebSocket client (e.g., Postman, browser console) to connect, send messages, and disconnect. Verify server logs for connection/disconnection events. Test heartbeat by letting a client idle and observing server-side timeout.",
"testStrategyTrans": "使用 WebSocket 客户端(例如 Postman、浏览器控制台连接、发送消息和断开连接。验证服务器日志中的连接/断开连接事件。通过让客户端空闲并观察服务器端超时来测试心跳。"
},
{
"id": 10,
"title": "WebSocket Client Integration & State Management",
"titleTrans": "WebSocket 客户端集成与状态管理",
"description": "Integrate WebSocket client logic into the Taro frontend. Implement global state management (Zustand) to store connection status and received game messages, ensuring real-time UI updates.",
"descriptionTrans": "将 WebSocket 客户端逻辑集成到 Taro 前端。实现全局状态管理 (Zustand) 以存储连接状态和接收到的游戏消息,确保实时 UI 更新。",
"status": "pending",
"dependencies": [
1,
9
],
"priority": "high",
"details": "Create a WebSocket service/hook in the frontend to manage connection lifecycle (connect, disconnect, send, receive). Use Zustand to create a `GameStore` that holds `connectionState`, `user`, `room`, `gameState`. Update UI based on changes in `GameStore`. Implement message parsing and dispatching to relevant parts of the application.",
"detailsTrans": "在前端创建一个 WebSocket 服务/Hook 来管理连接生命周期(连接、断开、发送、接收)。使用 Zustand 创建一个 `GameStore`,其中包含 `connectionState`、`user`、`room`、`gameState`。根据 `GameStore` 中的变化更新 UI。实现消息解析和分发到应用程序的相关部分。",
"testStrategy": "Verify client can connect to WebSocket server. Send test messages from server and confirm client receives and updates state. Test reconnection logic (manual disconnect/reconnect).",
"testStrategyTrans": "验证客户端可以连接到 WebSocket 服务器。从服务器发送测试消息并确认客户端接收并更新状态。测试重新连接逻辑(手动断开/重新连接)。"
},
{
"id": 11,
"title": "Room Management Backend (Create, Join, Match)",
"titleTrans": "房间管理后端 (创建、加入、匹配)",
"description": "Develop the backend logic for managing game rooms: creating private rooms (with password, config), joining rooms via code, and implementing a basic random matchmaking queue.",
"descriptionTrans": "开发用于管理游戏房间的后端逻辑:创建私人房间(带密码、配置)、通过代码加入房间,并实现基本的随机匹配队列。",
"status": "pending",
"dependencies": [
2,
4,
9
],
"priority": "high",
"details": "Implement `Room` model in MongoDB and cache active room states in Redis. Create API endpoints for `createRoom`, `joinRoom`, `leaveRoom`. For matchmaking, use a Redis List or Set as a queue; when two players are in the queue, create a room for them. Implement room code generation (6-digit numeric). Handle host permissions and room configuration (e.g., round time limit, AI difficulty).",
"detailsTrans": "在 MongoDB 中实现 `Room` 模型,并将活跃房间状态缓存到 Redis 中。创建 `createRoom`、`joinRoom`、`leaveRoom` 的 API 端点。对于匹配,使用 Redis List 或 Set 作为队列当队列中有两名玩家时为他们创建一个房间。实现房间代码生成6 位数字。处理房主权限和房间配置例如回合时间限制、AI 难度)。",
"testStrategy": "Unit test room creation with various configs (password, no password). Test joining valid/invalid rooms. Simulate multiple users joining a matchmaking queue and verify room creation. Test room status updates in Redis.",
"testStrategyTrans": "单元测试不同配置(有密码、无密码)的房间创建。测试加入有效/无效房间。模拟多个用户加入匹配队列并验证房间创建。测试 Redis 中的房间状态更新。"
},
{
"id": 12,
"title": "Room Management Frontend (UI for Create, Join, Match)",
"titleTrans": "房间管理前端 (创建、加入、匹配UI)",
"description": "Develop the client-side UI for room management, allowing users to create private rooms, enter room codes to join, and initiate quick matchmaking.",
"descriptionTrans": "开发房间管理的客户端 UI允许用户创建私人房间输入房间代码加入并启动快速匹配。",
"status": "pending",
"dependencies": [
3,
10,
11
],
"priority": "high",
"details": "Create 'Create Room' modal/page with options for password, AI difficulty, round time. Implement 'Join Room' input field for room code. Implement a 'Quick Match' button. Display room lists/states. Use WebSocket to send room actions and receive updates. Show loading/waiting states during matching.",
"detailsTrans": "创建“创建房间”模态框/页面包含密码、AI 难度、回合时间等选项。实现“加入房间”的房间代码输入字段。实现“快速匹配”按钮。显示房间列表/状态。使用 WebSocket 发送房间操作并接收更新。在匹配期间显示加载/等待状态。",
"testStrategy": "Test creating a room and verify room code display. Test joining a room with the correct code. Test quick matchmaking flow. Verify error messages for invalid room codes or full rooms. Ensure UI updates correctly with WebSocket messages.",
"testStrategyTrans": "测试创建房间并验证房间代码显示。测试使用正确代码加入房间。测试快速匹配流程。验证无效房间代码或房间已满的错误消息。确保 UI 通过 WebSocket 消息正确更新。"
},
{
"id": 13,
"title": "Core Game Engine Logic (Server-side Combat)",
"titleTrans": "核心游戏引擎逻辑 (服务器端战斗)",
"description": "Implement the server-side game engine responsible for managing game state, handling player attacks, determining hit/miss/destroy outcomes, and enforcing game rules (turn-based, win conditions).",
"descriptionTrans": "实现服务器端游戏引擎,负责管理游戏状态、处理玩家攻击、确定命中/未命中/击毁结果,并执行游戏规则(回合制、胜利条件)。",
"status": "pending",
"dependencies": [
2,
7,
9,
11
],
"priority": "high",
"details": "Create a `GameEngine` module. Store game state (player boards, plane positions, attack history, current turn) in Redis. Implement `processAttack(roomId, userId, targetCoordinate)` function. This function should: 1. Validate turn. 2. Check target on opponent's board. 3. Determine MISS, HIT, or DESTROY (if head is hit). 4. Update game state. 5. Check win condition (all 3 planes destroyed). 6. Switch turn. Use the `Plane` data structure and collision logic from Task 7. Store game sessions in MongoDB for history.",
"detailsTrans": "创建 `GameEngine` 模块。将游戏状态(玩家棋盘、飞机位置、攻击历史、当前回合)存储在 Redis 中。实现 `processAttack(roomId, userId, targetCoordinate)` 函数。此函数应1. 验证回合。2. 检查对手棋盘上的目标。3. 确定 MISS、HIT 或 DESTROY如果击中机头。4. 更新游戏状态。5. 检查胜利条件(所有 3 架飞机被摧毁。6. 切换回合。使用任务 7 中的 `Plane` 数据结构和碰撞逻辑。将游戏会话存储在 MongoDB 中以供历史记录。",
"testStrategy": "Unit test `processAttack` with various scenarios: miss, hit (body), hit (head/destroy), hitting same spot twice. Test turn switching and win condition detection. Simulate a full game flow via API calls to verify engine logic. Test concurrent attacks (should be prevented by turn validation).",
"testStrategyTrans": "单元测试 `processAttack` 的各种场景:未命中、命中(机身)、命中(机头/击毁)、两次击中同一位置。测试回合切换和胜利条件检测。通过 API 调用模拟完整游戏流程以验证引擎逻辑。测试并发攻击(应通过回合验证阻止)。"
},
{
"id": 14,
"title": "Simple AI Implementation (Random + Basic Exclusion)",
"titleTrans": "简单AI实现 (随机+基本排除)",
"description": "Implement the 'Simple AI' difficulty level. This AI will make random attacks, but with basic exclusion of already attacked cells. It serves as a training partner for new players.",
"descriptionTrans": "实现“简单 AI”难度级别。此 AI 将进行随机攻击,但会基本排除已攻击过的单元格。它可作为新玩家的训练伙伴。",
"status": "pending",
"dependencies": [
13
],
"priority": "high",
"details": "Create an `AIService` module. For 'Simple AI', implement a strategy that: 1. Generates a list of all untargeted cells on the opponent's board. 2. Randomly selects one cell from this list. 3. Calls the `processAttack` function with the chosen coordinate. Ensure AI decision-making time is <500ms.",
"detailsTrans": "创建 `AIService` 模块。对于“简单 AI”实现一个策略1. 生成对手棋盘上所有未被攻击的单元格列表。2. 从此列表中随机选择一个单元格。3. 使用选定的坐标调用 `processAttack` 函数。确保 AI 决策时间 <500ms。",
"testStrategy": "Play multiple games against the Simple AI. Verify AI consistently makes moves, does not attack the same spot twice, and adheres to the turn-based system. Check response time.",
"testStrategyTrans": "与简单 AI 进行多局游戏。验证 AI 始终出招,不会两次攻击同一位置,并遵循回合制。检查响应时间。"
},
{
"id": 15,
"title": "Game Play Loop Integration (Client & Server)",
"titleTrans": "游戏流程集成 (前端与后端)",
"description": "Integrate all core game components (plane placement, attack phase, turn management) into a complete, playable game loop for both player vs. player and player vs. AI modes. This involves extensive WebSocket communication and UI state updates.",
"descriptionTrans": "将所有核心游戏组件(飞机布置、攻击阶段、回合管理)集成到玩家对玩家和玩家对 AI 模式的完整可玩游戏循环中。这涉及广泛的 WebSocket 通信和 UI 状态更新。",
"status": "pending",
"dependencies": [
8,
10,
12,
13,
14
],
"priority": "high",
"details": "Frontend: Implement `GameScreen` component. Display both player's (hidden planes) and opponent's boards. Handle 'your turn' UI, attack target selection, and attack confirmation. Display hit/miss/destroy feedback. Show opponent's moves. Implement round countdown. Backend: Coordinate plane placement phase, transition to battle phase, manage turn timers, handle timeout for attacks (random attack). Broadcast game state changes via WebSocket to all participants.",
"detailsTrans": "前端:实现 `GameScreen` 组件。显示玩家隐藏飞机和对手的棋盘。处理“你的回合”UI、攻击目标选择和攻击确认。显示命中/未命中/击毁反馈。显示对手的移动。实现回合倒计时。后端:协调飞机布置阶段,过渡到战斗阶段,管理回合计时器,处理攻击超时(随机攻击)。通过 WebSocket 向所有参与者广播游戏状态变化。",
"testStrategy": "Conduct full end-to-end tests for PV P and PVE (AI) matches. Verify plane placement, attack sequence, turn switching, hit feedback, and game conclusion. Test round countdown and timeout auto-attack. Check for any desynchronization issues between clients.",
"testStrategyTrans": "对玩家对玩家和玩家对 AI 比赛进行全面的端到端测试。验证飞机布置、攻击序列、回合切换、命中反馈和游戏结束。测试回合倒计时和超时自动攻击。检查客户端之间的任何不同步问题。"
},
{
"id": 16,
"title": "Game End & Settlement System",
"titleTrans": "游戏结束与结算系统",
"description": "Implement the logic and UI for ending a game, determining the winner, and displaying a settlement screen with results and rewards (e.g., experience points).",
"descriptionTrans": "实现游戏结束、确定赢家以及显示带有结果和奖励(例如经验值)的结算屏幕的逻辑和 UI。",
"status": "pending",
"dependencies": [
5,
15
],
"priority": "high",
"details": "Backend: When a win condition is met, update player stats (wins, losses, streaks, experience) in MongoDB. Calculate XP based on victory/defeat (win +100XP, loss +20XP). Frontend: Display a 'Game Over' modal/screen showing winner/loser, final board state, and earned XP/rewards. Include 'Play Again' and 'Return to Lobby' buttons.",
"detailsTrans": "后端:当满足胜利条件时,更新 MongoDB 中玩家的统计数据(胜场、败场、连胜、经验)。根据胜利/失败计算经验值(胜利 +100XP失败 +20XP。前端显示“游戏结束”模态框/屏幕,显示赢家/输家、最终棋盘状态和获得的经验值/奖励。包括“再玩一局”和“返回大厅”按钮。",
"testStrategy": "Play games to completion (win/loss) and verify correct winner determination. Check if XP and stats are updated in the database. Verify settlement screen displays accurate information. Test 'Play Again' and 'Return to Lobby' functionality.",
"testStrategyTrans": "完成游戏(胜利/失败)并验证正确的赢家判定。检查数据库中经验值和统计数据是否更新。验证结算屏幕显示准确信息。测试“再玩一局”和“返回大厅”功能。"
},
{
"id": 17,
"title": "Basic Game Statistics & Display",
"titleTrans": "基础游戏统计与显示",
"description": "Develop the functionality to track and display basic user game statistics, such as total games played, win rate, and current/max win streaks, on the user's profile.",
"descriptionTrans": "开发跟踪和显示用户基本游戏统计数据的功能,例如总游戏局数、胜率和当前/最高连胜记录,并在用户档案中显示。",
"status": "pending",
"dependencies": [
5,
16
],
"priority": "medium",
"details": "Frontend: Create a 'My Profile' section that fetches user stats from the backend. Display `totalGames`, `wins`, `winRate` (calculated from wins/totalGames), `currentStreak`, `maxStreak`. Backend: Ensure that `Game End & Settlement` (Task 16) correctly updates these fields in the User model.",
"detailsTrans": "前端:创建一个“我的档案”部分,从后端获取用户统计数据。显示 `totalGames`、`wins`、`winRate`(根据胜场/总局数计算)、`currentStreak`、`maxStreak`。后端:确保“游戏结束与结算”(任务 16正确更新用户模型中的这些字段。",
"testStrategy": "Play multiple games with varying outcomes. Verify that statistics on the profile page update correctly after each game. Test edge cases like a user's first game, or breaking a win streak.",
"testStrategyTrans": "玩多局不同结果的游戏。验证每局游戏后档案页面上的统计数据是否正确更新。测试边缘情况,例如用户的第一局游戏,或中断连胜。"
},
{
"id": 18,
"title": "Disconnect & Reconnect Mechanism (Client & Server)",
"titleTrans": "断线重连机制 (前端与后端)",
"description": "Implement a robust mechanism for handling client disconnections during a game, allowing players to automatically reconnect and resume their game session.",
"descriptionTrans": "实现一个强大的机制,用于处理游戏期间的客户端断线,允许玩家自动重新连接并恢复他们的游戏会话。",
"status": "pending",
"dependencies": [
9,
10,
13
],
"priority": "high",
"details": "Frontend: Implement exponential backoff for WebSocket reconnection attempts (1s, 2s, 4s...). Display a 'Reconnecting...' UI. Backend: Store a snapshot of the current game state in Redis. When a client reconnects, verify their identity and send the latest game state snapshot. Implement a 30-minute room retention policy for disconnected players to rejoin.",
"detailsTrans": "前端:为 WebSocket 重新连接尝试实现指数退避1s、2s、4s...)。显示“正在重新连接...”UI。后端将当前游戏状态的快照存储在 Redis 中。当客户端重新连接时,验证其身份并发送最新的游戏状态快照。为断线玩家重新加入实施 30 分钟的房间保留策略。",
"testStrategy": "During a live game, forcibly disconnect the client (e.g., turn off Wi-Fi, close and reopen app quickly). Verify automatic reconnection and game state restoration. Test behavior when reconnection fails after multiple attempts (return to lobby). Test if the room persists for 30 mins.",
"testStrategyTrans": "在实时游戏中,强制断开客户端连接(例如,关闭 Wi-Fi快速关闭并重新打开应用程序。验证自动重新连接和游戏状态恢复。测试多次尝试后重新连接失败时的行为返回大厅。测试房间是否持续 30 分钟。"
},
{
"id": 19,
"title": "Initial Performance Optimization & Bug Fixes",
"titleTrans": "初步性能优化与Bug修复",
"description": "Conduct initial performance profiling and address critical bugs identified during MVP development. Focus on improving client-side rendering, network latency, and overall responsiveness.",
"descriptionTrans": "进行初步的性能分析并解决 MVP 开发期间发现的关键错误。重点关注改善客户端渲染、网络延迟和整体响应能力。",
"status": "pending",
"dependencies": [
15,
18
],
"priority": "high",
"details": "Frontend: Optimize rendering of `GameBoard` and `PlaneSprite` components (e.g., memoization, use of `requestAnimationFrame`). Implement message compression for WebSocket. Backend: Review database queries for efficiency (indexing). Monitor server CPU/memory usage. Address any reported bugs. Ensure client-side loading times (<2s first screen, <300ms page switch) and stable 60fps.",
"detailsTrans": "前端:优化 `GameBoard` 和 `PlaneSprite` 组件的渲染(例如,记忆化、使用 `requestAnimationFrame`)。为 WebSocket 实现消息压缩。后端:审查数据库查询以提高效率(索引)。监控服务器 CPU/内存使用情况。解决任何报告的错误。确保客户端加载时间(首屏 <2 秒,页面切换 <300 毫秒)和稳定的 60fps。",
"testStrategy": "Use browser/mini-program developer tools to profile rendering performance (FPS, repaint times). Monitor network requests for latency and payload size. Conduct stress tests on the backend. Perform thorough QA testing to identify and verify bug fixes.",
"testStrategyTrans": "使用浏览器/小程序开发者工具分析渲染性能FPS、重绘时间。监控网络请求的延迟和负载大小。对后端进行压力测试。进行彻底的 QA 测试以识别和验证错误修复。"
},
{
"id": 20,
"title": "User Level & Rank System (Frontend & Backend)",
"titleTrans": "用户等级与段位系统 (前端与后端)",
"description": "Expand the user profile to include a level system (1-100) based on accumulated experience points and a rank system (Bronze-King) based on game performance.",
"descriptionTrans": "扩展用户档案使其包含基于累积经验值的等级系统1-100级和基于游戏表现的段位系统青铜-王者)。",
"status": "pending",
"dependencies": [
5,
16,
17
],
"priority": "medium",
"details": "Backend: Implement logic for XP thresholds for level progression. Define rules for rank promotion/demotion (e.g., every 10 levels for rank, or based on Elo/Glicko rating). Update `User` model and `Game End & Settlement` to reflect these changes. Frontend: Display user's current level and rank visually on their profile and in game UI. Design appropriate badges/icons for ranks.",
"detailsTrans": "后端:实现经验值阈值以进行等级晋升的逻辑。定义段位晋升/降级规则(例如,每 10 级一个段位,或基于 Elo/Glicko 评分)。更新 `User` 模型和“游戏结束与结算”以反映这些变化。前端:在用户的档案和游戏 UI 中视觉化显示当前等级和段位。为段位设计适当的徽章/图标。",
"testStrategy": "Play multiple games to gain XP and level up. Verify level and rank increase correctly. Test edge cases for rank promotion/demotion. Ensure UI accurately reflects the current level and rank.",
"testStrategyTrans": "玩多局游戏以获得经验值并升级。验证等级和段位是否正确提升。测试段位晋升/降级的边缘情况。确保 UI 准确反映当前等级和段位。"
},
{
"id": 21,
"title": "Friend System (WeChat Import & Invite)",
"titleTrans": "好友系统 (微信导入与邀请)",
"description": "Implement a friend system allowing users to import WeChat friends, view online status, and send game invitations. Include a basic blacklist feature.",
"descriptionTrans": "实现一个好友系统,允许用户导入微信好友、查看在线状态并发送游戏邀请。包括一个基本的黑名单功能。",
"status": "pending",
"dependencies": [
4,
10,
12
],
"priority": "medium",
"details": "Frontend: Implement UI for 'Friends List'. Use WeChat API for friend relationship chain authorization and import. Display online/offline status (via WebSocket presence updates). Implement 'Invite to Game' button that generates a shareable link/card. Backend: Store friend relationships. Implement APIs for adding/removing friends and managing blacklists. Use Redis to track online users.",
"detailsTrans": "前端实现“好友列表”UI。使用微信 API 进行好友关系链授权和导入。显示在线/离线状态(通过 WebSocket 在线状态更新)。实现“邀请游戏”按钮,生成可分享的链接/卡片。后端:存储好友关系。实现添加/删除好友和管理黑名单的 API。使用 Redis 跟踪在线用户。",
"testStrategy": "Test WeChat friend import. Verify online status updates in real-time. Send game invitations to friends and confirm they can join the invited room. Test blacklist functionality.",
"testStrategyTrans": "测试微信好友导入。验证在线状态实时更新。向好友发送游戏邀请并确认他们可以加入受邀房间。测试黑名单功能。"
},
{
"id": 22,
"title": "In-Room Chat (Text & Emotes)",
"titleTrans": "房间内聊天 (文字与表情)",
"description": "Add a real-time chat feature within game rooms, supporting both text messages and a selection of emotes.",
"descriptionTrans": "在游戏房间内添加实时聊天功能,支持文字消息和表情。",
"status": "pending",
"dependencies": [
10,
12
],
"priority": "medium",
"details": "Frontend: Implement a chat input field and message display area in the room UI. Include an emote picker. Backend: Implement WebSocket message type for `CHAT_MESSAGE`. Broadcast chat messages to all participants in the room. Implement basic message filtering for offensive content.",
"detailsTrans": "前端:在房间 UI 中实现聊天输入框和消息显示区域。包括表情选择器。后端:为 `CHAT_MESSAGE` 实现 WebSocket 消息类型。将聊天消息广播给房间中的所有参与者。实现基本的冒犯性内容消息过滤。",
"testStrategy": "Join a room with multiple players. Send text messages and emotes. Verify all players receive messages in real-time. Test message filtering. Check for UI responsiveness with many messages.",
"testStrategyTrans": "与多名玩家加入一个房间。发送文本消息和表情。验证所有玩家实时接收消息。测试消息过滤。检查 UI 在大量消息下的响应能力。"
},
{
"id": 23,
"title": "Game Replay Functionality",
"titleTrans": "游戏回放功能",
"description": "Enable users to view replays of their past games, step-by-step, to analyze strategies and share exciting moments.",
"descriptionTrans": "允许用户逐步查看他们过去的游戏回放,以分析策略并分享精彩瞬间。",
"status": "pending",
"dependencies": [
2,
13,
15
],
"priority": "medium",
"details": "Backend: When a game ends, store a condensed `moves` history (sequence of attacks, results) in the `GameSession` model in MongoDB. Frontend: Create a 'Replay' screen. Fetch `GameSession` data. Reconstruct the game state step-by-step, allowing users to navigate through turns (play, pause, next, previous).",
"detailsTrans": "后端:游戏结束时,将精简的 `moves` 历史(攻击序列、结果)存储在 MongoDB 的 `GameSession` 模型中。前端:创建“回放”屏幕。获取 `GameSession` 数据。逐步重建游戏状态,允许用户在回合中导航(播放、暂停、下一回合、上一回合)。",
"testStrategy": "Play several games and verify that replays are saved. Access replay for different games and verify accurate reconstruction of the game. Test playback controls (play/pause, step forward/backward).",
"testStrategyTrans": "玩多局游戏并验证回放是否保存。访问不同游戏的回放并验证游戏重建的准确性。测试播放控制(播放/暂停、前进/后退)。"
},
{
"id": 24,
"title": "Share Game Results (Image Generation)",
"titleTrans": "分享战绩 (图片生成)",
"description": "Implement functionality to generate shareable image cards of game results, allowing users to easily share their achievements on WeChat or other platforms.",
"descriptionTrans": "实现生成可分享的游戏结果图片卡片的功能,允许用户轻松在微信或其他平台分享他们的成就。",
"status": "pending",
"dependencies": [
16,
17
],
"priority": "medium",
"details": "Frontend: After a game, provide a 'Share' button. Use Canvas API to render the game result (winner, score, final board state) into an image. Include user's avatar, nickname, and a QR code for the mini-program. Implement WeChat sharing API to allow sharing the generated image to friends/groups/moments.",
"detailsTrans": "前端:游戏结束后,提供“分享”按钮。使用 Canvas API 将游戏结果(赢家、分数、最终棋盘状态)渲染成图片。包括用户头像、昵称和微信小程序的二维码。实现微信分享 API允许将生成的图片分享给好友/群组/朋友圈。",
"testStrategy": "Complete a game and trigger the share function. Verify the generated image accurately reflects the game results and looks visually appealing. Test sharing to WeChat (friends, groups, moments) and confirm successful delivery.",
"testStrategyTrans": "完成游戏并触发分享功能。验证生成的图片是否准确反映游戏结果并具有视觉吸引力。测试分享到微信(好友、群组、朋友圈)并确认成功发送。"
},
{
"id": 25,
"title": "Achievement System (Badges, Tasks)",
"titleTrans": "成就系统 (徽章、任务)",
"description": "Introduce an achievement system with various badges and tasks to incentivize player engagement and provide long-term goals.",
"descriptionTrans": "引入成就系统,包含各种徽章和任务,以激励玩家参与并提供长期目标。",
"status": "pending",
"dependencies": [
5,
16,
17
],
"priority": "medium",
"details": "Backend: Define `Achievement` model (e.g., 'First Win', '5-Win Streak', 'Hit King'). Implement logic to detect when achievements are unlocked based on game events and player stats. Store unlocked achievements in the `User` model. Frontend: Create an 'Achievements' section in the user profile to display earned badges and progress towards unearned ones. Provide visual flair for new achievements.",
"detailsTrans": "后端:定义 `Achievement` 模型(例如,“首胜”、“五连胜”、“命中王”)。实现根据游戏事件和玩家统计数据检测成就解锁的逻辑。将解锁的成就存储在 `User` 模型中。前端:在用户档案中创建一个“成就”部分,显示已获得的徽章和未获得成就的进度。为新成就提供视觉效果。",
"testStrategy": "Test unlocking various achievements (e.g., win first game, achieve a streak). Verify achievements are correctly recorded in the database. Check if the UI updates to show new achievements and progress.",
"testStrategyTrans": "测试解锁各种成就(例如,赢得第一局游戏,实现连胜)。验证成就是否正确记录在数据库中。检查 UI 是否更新以显示新成就和进度。"
},
{
"id": 26,
"title": "Advanced AI Difficulty Levels (Normal, Hard)",
"titleTrans": "高级AI难度级别 (普通、困难)",
"description": "Enhance the AI system by implementing 'Normal' and 'Hard' difficulty levels, utilizing more sophisticated strategies like probability heatmaps and pattern recognition.",
"descriptionTrans": "通过实现“普通”和“困难”难度级别来增强 AI 系统,利用更复杂的策略,如概率热图和模式识别。",
"status": "pending",
"dependencies": [
14,
15
],
"priority": "medium",
"details": "For 'Normal AI', implement a strategy that prioritizes cells with higher probability of containing a plane part (e.g., based on surrounding hits). For 'Hard AI', introduce multi-step prediction and basic pattern recognition (e.g., if a HIT occurs, prioritize adjacent cells). Ensure AI difficulty can be selected in room creation.",
"detailsTrans": "对于“普通 AI”实现一种策略优先选择包含飞机部件概率更高的单元格例如根据周围命中情况。对于“困难 AI”引入多步预测和基本模式识别例如如果发生命中则优先考虑相邻单元格。确保在房间创建时可以选择 AI 难度。",
"testStrategy": "Play numerous games against Normal and Hard AI. Observe their attack patterns and verify they are more strategic than Simple AI. Ensure AI still makes decisions within the <500ms time limit. Compare win rates against different AI difficulties.",
"testStrategyTrans": "与普通和困难 AI 进行多局游戏。观察它们的攻击模式,并验证它们比简单 AI 更具策略性。确保 AI 仍然在 <500 毫秒的时间限制内做出决策。比较不同 AI 难度下的胜率。"
},
{
"id": 27,
"title": "Leaderboard System",
"titleTrans": "排行榜系统",
"description": "Develop a global leaderboard system to display top players based on various metrics (e.g., rank, wins, win rate).",
"descriptionTrans": "开发一个全球排行榜系统,根据各种指标(例如,段位、胜场、胜率)显示顶尖玩家。",
"status": "pending",
"dependencies": [
5,
17,
20
],
"priority": "medium",
"details": "Backend: Implement API endpoints to fetch leaderboard data (e.g., top 100 players by rank, by wins). This might involve efficient queries on the `Users` collection or pre-calculated data in Redis. Frontend: Create a 'Leaderboard' screen with tabs for different ranking criteria. Display player's own rank within the leaderboard.",
"detailsTrans": "后端:实现 API 端点以获取排行榜数据(例如,按段位、按胜场排名的前 100 名玩家)。这可能涉及对 `Users` 集合的有效查询或 Redis 中的预计算数据。前端:创建一个“排行榜”屏幕,带有不同排名标准的选项卡。在排行榜中显示玩家自己的排名。",
"testStrategy": "Populate the database with dummy user data and varied stats. Verify leaderboard data is fetched correctly and sorted as expected for different criteria. Check UI rendering of top players and user's own position.",
"testStrategyTrans": "用虚拟用户数据和各种统计数据填充数据库。验证排行榜数据是否正确获取并按不同标准排序。检查顶尖玩家和用户自身位置的 UI 渲染。"
},
{
"id": 28,
"title": "Matchmaking Algorithm Optimization",
"titleTrans": "匹配算法优化",
"description": "Refine the matchmaking algorithm to improve player experience by considering factors like player skill (rank/level) and reducing wait times. Implement tiered matching.",
"descriptionTrans": "优化匹配算法,通过考虑玩家技能(段位/等级)和减少等待时间来改善玩家体验。实现阶梯匹配。",
"status": "pending",
"dependencies": [
11,
20,
27
],
"priority": "medium",
"details": "Backend: Modify the matchmaking queue logic. Instead of pure random, prioritize matching players with similar ranks/levels. If no suitable match is found within a short time (e.g., 30s), expand the search criteria (wider rank range, then consider matching with AI or bots). Implement a timeout for matchmaking (e.g., 120s) before offering alternative modes.",
"detailsTrans": "后端:修改匹配队列逻辑。不再纯粹随机,优先匹配段位/等级相似的玩家。如果在短时间内(例如 30 秒)找不到合适的匹配,则扩大搜索范围(更宽的段位范围,然后考虑与 AI 或机器人匹配)。实施匹配超时(例如 120 秒),然后提供替代模式。",
"testStrategy": "Simulate matchmaking with various player populations (e.g., many low-rank, few high-rank). Verify that players are matched with appropriate opponents or that search criteria expand as expected. Measure average matchmaking time.",
"testStrategyTrans": "模拟不同玩家群体(例如,许多低段位,少数高段位)的匹配。验证玩家是否与合适的对手匹配,或者搜索条件是否按预期扩展。测量平均匹配时间。"
},
{
"id": 29,
"title": "System Monitoring & Analytics Integration",
"titleTrans": "系统监控与数据分析集成",
"description": "Integrate comprehensive monitoring and analytics tools (Prometheus, Grafana, ELK, Sentry) to track application health, performance, and key business metrics.",
"descriptionTrans": "集成全面的监控和数据分析工具Prometheus、Grafana、ELK、Sentry以跟踪应用程序健康状况、性能和关键业务指标。",
"status": "pending",
"dependencies": [
1,
19
],
"priority": "medium",
"details": "Set up Prometheus for metric collection and Grafana for dashboard visualization. Configure ELK stack for log aggregation and analysis. Integrate Sentry for real-time error tracking. Implement basic user behavior tracking (e.g., login, room creation, game start/end) using events as described in PRD (8.4.1).",
"detailsTrans": "设置 Prometheus 用于指标收集Grafana 用于仪表盘可视化。配置 ELK 堆栈用于日志聚合和分析。集成 Sentry 用于实时错误跟踪。使用 PRD 中描述的事件8.4.1)实现基本的用户行为跟踪(例如,登录、房间创建、游戏开始/结束)。",
"testStrategy": "Verify all monitoring tools are collecting data (metrics, logs, errors). Trigger specific events/errors and confirm they appear in the respective dashboards. Check if custom business metrics are being tracked correctly.",
"testStrategyTrans": "验证所有监控工具都在收集数据(指标、日志、错误)。触发特定事件/错误并确认它们出现在各自的仪表盘中。检查自定义业务指标是否正确跟踪。"
},
{
"id": 30,
"title": "Interactive Newbie Guide",
"titleTrans": "交互式新手引导",
"description": "Create an interactive, step-by-step tutorial for new users to learn the game rules, controls, and core mechanics, including AI accompaniment.",
"descriptionTrans": "为新用户创建一个交互式的分步教程,以学习游戏规则、控制和核心机制,包括 AI 陪练。",
"status": "pending",
"dependencies": [
3,
8,
15,
14
],
"priority": "medium",
"details": "Design a series of guided steps (e.g., 'how to place planes', 'how to attack'). Implement overlay UI elements to highlight actions. Use a simplified AI to guide the player through their first game. Allow users to skip or review the guide. Store guide progress in user settings.",
"detailsTrans": "设计一系列引导步骤(例如,“如何放置飞机”、“如何攻击”)。实现叠加 UI 元素以突出显示操作。使用简化的 AI 引导玩家完成他们的第一局游戏。允许用户跳过或回顾引导。将引导进度存储在用户设置中。",
"testStrategy": "On a new account, go through the entire newbie guide. Verify each step is clear and interactive. Test skipping and reviewing sections. Ensure progress is saved if the user exits mid-guide.",
"testStrategyTrans": "在新账号上,完成整个新手引导。验证每个步骤都清晰且具有交互性。测试跳过和回顾部分。确保如果用户在引导中途退出,进度会保存。"
},
{
"id": 31,
"title": "Game Sound Effects & Visual Effects",
"titleTrans": "游戏音效与视觉特效",
"description": "Add engaging sound effects for game events (hit, miss, destroy) and UI interactions, along with corresponding visual effects.",
"descriptionTrans": "为游戏事件(命中、未命中、击毁)和 UI 交互添加引人入胜的音效,以及相应的视觉特效。",
"status": "pending",
"dependencies": [
15
],
"priority": "medium",
"details": "Source or create sound assets (e.g., 'miss' splash, 'hit' thud, 'destroy' explosion). Integrate an audio manager to play sounds based on game events. Implement visual effects for attacks (e.g., a laser shot, explosion animations for hits/destroys). Ensure performance is not impacted and provide volume controls.",
"detailsTrans": "获取或创建声音素材(例如,“未命中”水花声,“命中”沉闷声,“击毁”爆炸声)。集成音频管理器以根据游戏事件播放声音。为攻击实现视觉效果(例如,激光射击,命中/击毁的爆炸动画)。确保性能不受影响并提供音量控制。",
"testStrategy": "Play a game and verify that all key actions (placing, attacking, hitting, missing, destroying) have appropriate sound and visual effects. Test UI interaction sounds. Check performance during intense effect sequences. Test volume controls.",
"testStrategyTrans": "玩一局游戏并验证所有关键操作(放置、攻击、命中、未命中、击毁)都具有适当的声音和视觉效果。测试 UI 交互音效。检查在强烈特效序列期间的性能。测试音量控制。"
},
{
"id": 32,
"title": "Activity System (Daily Sign-in, Events)",
"titleTrans": "活动系统 (每日签到、活动)",
"description": "Implement a basic activity system to offer daily rewards (e.g., sign-in bonuses) and temporary event-based incentives.",
"descriptionTrans": "实现一个基本的活动系统,提供每日奖励(例如,签到奖励)和基于临时活动的激励。",
"status": "pending",
"dependencies": [
2,
5
],
"priority": "low",
"details": "Backend: Design a flexible `Activity` data model (e.g., daily sign-in, specific win count event). Implement API endpoints for checking activity status and claiming rewards. Store user activity progress in MongoDB. Frontend: Create an 'Activities' section. Display current activities, progress, and a button to claim rewards. Implement daily sign-in UI with calendar.",
"detailsTrans": "后端:设计一个灵活的 `Activity` 数据模型(例如,每日签到、特定胜场活动)。实现用于检查活动状态和领取奖励的 API 端点。将用户活动进度存储在 MongoDB 中。前端:创建一个“活动”部分。显示当前活动、进度和领取奖励的按钮。实现带日历的每日签到 UI。",
"testStrategy": "Test daily sign-in over several days. Verify rewards are granted correctly and progress is tracked. Test a temporary event (e.g., 'win 3 games for bonus') and ensure rewards are given upon completion.",
"testStrategyTrans": "测试连续几天的每日签到。验证奖励是否正确发放,进度是否跟踪。测试临时活动(例如,“赢得 3 场游戏获得奖励”)并确保完成后发放奖励。"
},
{
"id": 33,
"title": "Basic Shop System (Skins, Effects)",
"titleTrans": "基础商城系统 (皮肤、特效)",
"description": "Implement a basic in-game shop where players can purchase cosmetic items like plane skins and attack effects using virtual currency.",
"descriptionTrans": "实现一个基础的游戏内商城,玩家可以使用虚拟货币购买飞机皮肤和攻击特效等装饰性物品。",
"status": "pending",
"dependencies": [
2,
5,
31
],
"priority": "low",
"details": "Backend: Define `Item` model (ID, type, price, asset path). Implement API for fetching shop items, purchasing items, and managing user's inventory. Users need a virtual currency balance. Frontend: Create a 'Shop' screen displaying available skins and effects. Allow users to preview items and make purchases. Display current virtual currency balance.",
"detailsTrans": "后端:定义 `Item` 模型ID、类型、价格、资产路径。实现用于获取商店物品、购买物品和管理用户库存的 API。用户需要虚拟货币余额。前端创建一个“商店”屏幕显示可用的皮肤和特效。允许用户预览物品并进行购买。显示当前虚拟货币余额。",
"testStrategy": "Add dummy items to the shop. Test purchasing items with sufficient/insufficient currency. Verify item is added to user's inventory and currency is deducted. Test equipping purchased skins/effects in a game.",
"testStrategyTrans": "在商店中添加虚拟物品。测试使用足够/不足的货币购买物品。验证物品是否添加到用户库存中,货币是否扣除。测试在游戏中装备购买的皮肤/特效。"
},
{
"id": 34,
"title": "Push Notifications (WeChat Template Messages)",
"titleTrans": "推送通知 (微信模板消息)",
"description": "Integrate WeChat template messages to send important notifications to users, such as match invitations, game start reminders, or activity updates.",
"descriptionTrans": "集成微信模板消息,向用户发送重要通知,例如比赛邀请、游戏开始提醒或活动更新。",
"status": "pending",
"dependencies": [
4,
11
],
"priority": "low",
"details": "Backend: Implement WeChat template message sending API calls. Obtain user's formId/subscribe message permissions during user interactions (e.g., after login, when creating a room). Define templates for 'Game Invitation', 'Match Found', 'Activity Reminder'. Trigger these messages based on backend events. Frontend: Guide users to authorize template messages.",
"detailsTrans": "后端:实现微信模板消息发送 API 调用。在用户交互期间(例如,登录后、创建房间时)获取用户的 formId/订阅消息权限。定义“游戏邀请”、“匹配成功”、“活动提醒”的模板。根据后端事件触发这些消息。前端:引导用户授权模板消息。",
"testStrategy": "Send a game invitation to a test user and verify they receive a WeChat template message. Test other notification types (e.g., match found, activity reminder). Ensure proper authorization handling.",
"testStrategyTrans": "向测试用户发送游戏邀请,并验证他们是否收到微信模板消息。测试其他通知类型(例如,匹配成功、活动提醒)。确保正确的授权处理。"
},
{
"id": 35,
"title": "Admin Dashboard for Data Analysis",
"titleTrans": "数据分析后台",
"description": "Develop a basic admin dashboard to view key operational data, such as DAU, retention rates, and game statistics, for product analysis and decision-making.",
"descriptionTrans": "开发一个基本的管理后台,用于查看关键运营数据,例如 DAU、留存率和游戏统计数据以进行产品分析和决策。",
"status": "pending",
"dependencies": [
2,
29
],
"priority": "low",
"details": "Develop a separate web application (e.g., using React Admin or a simple Fastify API + static HTML) for the admin dashboard. Implement authentication for admin users. Fetch data from MongoDB (user stats, game logs) and Redis (real-time metrics). Display DAU, new users, retention rates, game counts, top players. Integrate with Grafana dashboards if possible.",
"detailsTrans": "为管理后台开发一个独立的 Web 应用程序(例如,使用 React Admin 或简单的 Fastify API + 静态 HTML。实现管理员用户的认证。从 MongoDB用户统计、游戏日志和 Redis实时指标获取数据。显示 DAU、新用户、留存率、游戏次数、顶尖玩家。如果可能与 Grafana 仪表盘集成。",
"testStrategy": "Access the admin dashboard with admin credentials. Verify data displayed matches direct database queries. Check if DAU, retention, and game stats are calculated and presented correctly. Test dashboard security.",
"testStrategyTrans": "使用管理员凭据访问管理后台。验证显示的数据是否与直接数据库查询匹配。检查 DAU、留存率和游戏统计数据是否正确计算和呈现。测试后台安全性。"
},
{
"id": 36,
"title": "Spectator Mode",
"titleTrans": "观战模式",
"description": "Allow non-playing users to join active game rooms as spectators, viewing the ongoing match in real-time.",
"descriptionTrans": "允许非玩家用户作为观众加入活跃的游戏房间,实时观看正在进行的比赛。",
"status": "pending",
"dependencies": [
10,
13,
21
],
"priority": "low",
"details": "Backend: Extend WebSocket handling to allow 'spectator' roles. When a spectator joins, send them the current game state snapshot. Broadcast game updates to spectators without allowing them to send game actions. Frontend: Add a 'Spectate' option to rooms or friend profiles. Display the game board and actions without interactive controls. Hide opponent's plane placement until destroyed.",
"detailsTrans": "后端:扩展 WebSocket 处理以允许“观众”角色。当观众加入时,向他们发送当前游戏状态快照。向观众广播游戏更新,但不允许他们发送游戏操作。前端:在房间或好友档案中添加“观战”选项。显示游戏棋盘和操作,但不带交互式控件。在飞机被摧毁之前隐藏对手的飞机布置。",
"testStrategy": "Join an active game as a spectator. Verify real-time updates of game state (attacks, hits, turns). Ensure spectators cannot interact with the game. Test joining/leaving spectator mode. Verify performance with multiple spectators.",
"testStrategyTrans": "作为观众加入一个活跃的游戏。验证游戏状态(攻击、命中、回合)的实时更新。确保观众无法与游戏互动。测试加入/离开观战模式。验证多个观众下的性能。"
},
{
"id": 37,
"title": "H5 Version Adaption",
"titleTrans": "H5版本适配",
"description": "Adapt the mini-program to run as a web (H5) application, ensuring full functionality and responsive design across different browsers and devices.",
"descriptionTrans": "将小程序适配为 Web (H5) 应用程序运行,确保在不同浏览器和设备上具有完整功能和响应式设计。",
"status": "pending",
"dependencies": [
1,
15
],
"priority": "low",
"details": "Leverage Taro's H5 compilation capabilities. Address any platform-specific differences (e.g., WeChat APIs vs. browser APIs for login/sharing). Ensure responsive layout for various screen sizes (desktop, tablet, mobile browser). Test performance on H5.",
"detailsTrans": "利用 Taro 的 H5 编译能力。解决任何平台特定的差异(例如,微信 API 与浏览器 API 用于登录/分享)。确保各种屏幕尺寸(桌面、平板、移动浏览器)的响应式布局。在 H5 上测试性能。",
"testStrategy": "Compile and deploy the H5 version. Test all core functionalities (login, room, game, chat, stats) on different browsers (Chrome, Firefox, Safari) and devices. Verify responsive layout and performance.",
"testStrategyTrans": "编译并部署 H5 版本。在不同浏览器Chrome、Firefox、Safari和设备上测试所有核心功能登录、房间、游戏、聊天、统计。验证响应式布局和性能。"
},
{
"id": 38,
"title": "Internationalization (i18n) Support",
"titleTrans": "国际化 (i18n) 支持",
"description": "Implement internationalization to support multiple languages, starting with English and Chinese.",
"descriptionTrans": "实施国际化以支持多种语言,从英语和中文开始。",
"status": "pending",
"dependencies": [
1,
3
],
"priority": "low",
"details": "Integrate an i18n library (e.g., `react-i18next`). Extract all hardcoded strings from the UI and backend messages into translation files (JSON/YAML). Implement a language switcher in settings. Ensure dynamic content (e.g., user-generated names) is handled correctly.",
"detailsTrans": "集成 i18n 库(例如,`react-i18next`)。将 UI 和后端消息中所有硬编码的字符串提取到翻译文件JSON/YAML中。在设置中实现语言切换器。确保动态内容例如用户生成的名字得到正确处理。",
"testStrategy": "Add English translation files. Switch language in settings and verify all UI text and messages are translated correctly. Test with different locales. Check for text overflow issues with longer translations.",
"testStrategyTrans": "添加英文翻译文件。在设置中切换语言并验证所有 UI 文本和消息是否正确翻译。使用不同的区域设置进行测试。检查较长翻译的文本溢出问题。"
}
],
"metadata": {
"projectName": "打飞机对战小程序",
"totalTasks": 38,
"sourceFile": ".scripts\\PRD.txt",
"generatedAt": "2024-01-20"
}
}