Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

545 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Net-Games 网游系统架构文档
> **版本**: v2.0
> **更新日期**: 2026-04-09
> **状态**: 生产就绪
---
## 1. 系统概述
### 1.1 项目定位
基于 **Flask-SocketIO** 的多人实时棋牌游戏引擎,支持人类玩家与 AI 玩家对战。采用权威服务端架构,所有游戏逻辑在服务端验证执行。
### 1.2 核心特性
- ✅ 权威服务端架构(防作弊)
- ✅ 用户认证系统(登录/注册/积分)
- ✅ 多角色支持:人类、规则AI、DeepSeek大模型AI
- ✅ 房间制对战(2-3人)
- ✅ AI托管机制(断线/超时自动托管)
- ✅ 回合超时管理(30秒/回合)
- ✅ 断线重连支持
- ✅ 单点登录控制
- ✅ 可扩展的游戏插件机制
### 1.3 技术栈
| 层级 | 技术 | 说明 |
|------|------|------|
| 服务端 | Python 3.11 + Flask | Web框架 |
| 实时通信 | Flask-SocketIO | WebSocket封装 |
| 数据库 | SQLite | 用户认证、积分存储 |
| 前端 | 原生HTML/JS/CSS | 无需构建 |
| AI接口 | DeepSeek API | 大模型决策 |
| 部署 | Systemd | 系统服务 |
---
## 2. 目录结构
```
/home/ubuntu/nanocode/net-games/
├── server.py # 主服务入口 (1772行)
├── config.py # 全局配置
├── net_games_auth.db # SQLite数据库
│
├── engine/ # 游戏引擎核心
│ ├── __init__.py
│ ├── room_manager.py # 房间管理器 (391行)
│ ├── game_base.py # 游戏基类 (236行)
│ ├── player.py # 玩家抽象 (154行)
│ ├── ai_adapter.py # AI适配器基类
│ └── test_game.py # 测试游戏
│
├── doudizhu/ # 斗地主游戏实现
│ ├── __init__.py
│ ├── game.py # 斗地主游戏逻辑 (470行)
│ ├── game_logic.py # 牌型判断、规则
│ └── ai_adapters.py # 斗地主AI适配器 (443行)
│
├── chinese-chess/ # 中国象棋(预留)
│ └── ...
│
├── client/ # 斗地主客户端
│ ├── index.html
│ ├── style.css
│ └── game.js # 前端逻辑 (1617行)
│
├── hub/ # 游戏大厅
│ └── index.html
│
├── docs/ # 文档
│ ├── ARCHITECTURE.md # 本文件
│ ├── API.md # API接口文档
│ ├── DATABASE.md # 数据库设计
│ ├── UI_DESIGN_GUIDELINES.md # UI设计规范
│ └── PHASE1_SUMMARY.md # 阶段总结
│
└── instance/ # 数据目录
└── net_games_auth.db
```
---
## 3. 系统架构
### 3.1 整体架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 浏览器玩家 │ │ 浏览器玩家 │ │ AI玩家 │ │
│ │ (Socket.IO)│ │ (Socket.IO)│ │ (内部调用) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼────────────────────┘
│ │ │
└────────────────┴────────────────┘
│ WebSocket (Socket.IO)
▼
┌─────────────────────────────────────────────────────────────────┐
│ 服务层 (Flask) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ HTTP Routes │ │
│ │ /api/auth/* /api/user/* /api/rooms/* /api/admin/* │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Socket.IO Handlers │ │
│ │ connect create_room join_room game_action ... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ RoomManager (单例) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Room #1 │ │ Room #2 │ │ Room #N │ │ │
│ │ │ 斗地主 │ │ 斗地主 │ │ 象棋 │ │ │
│ │ │ 3玩家 │ │ 2玩家 │ │ 2玩家 │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ └───────┼────────────┼────────────┼───────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Game Instances │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ DoudizhuGame │ │ TestGame │ ... │ │
│ │ │ - game logic │ │ - game logic │ │ │
│ │ │ - state mgmt │ │ - state mgmt │ │ │
│ │ └──────┬───────┘ └──────────────┘ │ │
│ └─────────┼───────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ AI Adapter Center │ │
│ │ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ RuleAI │ │ DeepSeekAI │ │ MinimaxAI │ │ │
│ │ │ (斗地主) │ │ (通用) │ │ (象棋预留) │ │ │
│ │ └──────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Database (SQLite) │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ users │ │ rooms │ (内存缓存) │ │
│ │ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 3.2 核心组件职责
| 组件 | 文件 | 职责 |
|------|------|------|
| **Flask App** | `server.py` | HTTP服务、Socket.IO、认证、API路由 |
| **RoomManager** | `engine/room_manager.py` | 单例模式管理所有房间生命周期 |
| **Room** | `engine/room_manager.py` | 房间状态、玩家管理、游戏实例容器 |
| **GameBase** | `engine/game_base.py` | 游戏抽象基类,定义通用接口 |
| **Player** | `engine/player.py` | 玩家基类,HumanPlayer/AIPlayer |
| **AIAdapter** | `engine/ai_adapter.py` | AI决策接口统一封装 |
| **DoudizhuGame** | `doudizhu/game.py` | 斗地主具体实现 |
### 3.3 关键数据流
#### 3.3.1 游戏操作处理流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │ │ Server │ │ Room │ │ Game │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ game_action │ │ │
│ ──────────────>│ │ │
│ │ handle_game_action │
│ │ ──────────────────────────────>│
│ │ │ │
│ │ │ game.handle_action()
│ │ │ │
│ │ │ │
│ │ │ 验证操作合法性 │
│ │ │ 更新游戏状态 │
│ │ │ │
│ │ state_update │ │
│ <──────────────│ (broadcast) │ │
│ │ │ │
│ │ check_winner() │ │
│ │ ──────────────────────────────>│
│ │ │ │
│ game_ended │ │ │
│ <──────────────│ (broadcast) │ │
```
#### 3.3.2 AI决策流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Game │ │ Server │ │ AI Thread│ │ AIAdapter│
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
│ get_current_player() │ │
│ ───────────────────────────────>│ │
│ │ │ │
│ │ process_ai_turn│ │
│ │ ──────────────>│ │
│ │ │ make_decision()│
│ │ │ ──────────────>│
│ │ │ │
│ │ │ 异步API调用 │
│ │ │ (DeepSeek) │
│ │ │ <──────────────│
│ │ │ │
│ │ │ handle_action()│
│ │ │ ──────────────>│
│ │ state_update │ │
│ │ (broadcast) │ │
```
---
## 4. 核心类设计
### 4.1 RoomManager (单例模式)
```python
class RoomManager:
_instance = None
_lock = Lock()
def __new__(cls):
# 线程安全的单例
def create_room(game_type, max_players, config)
def join_room(room_code, player) -> (bool, str)
def start_game(room_id) -> (bool, str)
def get_room(room_id=None, room_code=None) -> Room
def cleanup_inactive_rooms(max_inactive_minutes=30)
```
### 4.2 Room 类
```python
class Room:
id: str # room_uuid
code: str # A1B2C3 (6位房间号)
game_type: str # doudizhu
status: str # waiting/playing/ended/closed
players: Dict[str, Player]
seats: Dict[int, Optional[str]]
host_id: Optional[str]
game: Optional[GameBase]
def add_player(player) -> bool
def remove_player(player_id) -> bool
def start_game(game_class) -> bool
def to_dict(include_game_state=True) -> dict
```
### 4.3 GameBase 抽象基类
```python
class GameBase(ABC):
room_id: str
game_type: str
state: GameState
players: Dict[str, Player]
player_order: List[str]
@abstractmethod
def handle_action(player_id, action, data) -> dict
@abstractmethod
def get_valid_actions(player_id) -> List[str]
@abstractmethod
def check_winner() -> Optional[str]
def to_dict() -> dict
```
### 4.4 Player 类层次
```python
class Player(ABC):
id: str
name: str
type: PlayerType
is_connected: bool
is_ai_controlled: bool # AI托管状态
offline_since: Optional[float]
def connect(socket_id)
def disconnect()
def enable_ai_control()
def disable_ai_control()
class HumanPlayer(Player):
@property
def is_ai(self) -> False
class AIPlayer(Player):
ai_type: str # rule/deepseek/minimax
@property
def is_ai(self) -> True
```
---
## 5. 关键机制
### 5.1 单点登录与身份识别
```python
# 使用用户名或昵称作为身份标识
identity_key = current_user.username if current_user.is_authenticated else player_name
# 活跃玩家映射
active_usernames: Dict[str, str] = {} # username -> player_id
# 用户-房间映射(支持跨浏览器重连)
user_room_map: Dict[str, str] = {} # username -> room_id
# Socket-玩家映射
socket_player_map: Dict[str, str] = {} # socket_id -> player_id
```
**单点登录检查流程:**
1. 创建房间/加入房间时检查 `identity_key` 是否在 `active_usernames`
2. 如果存在且玩家在线,拒绝新连接
3. 如果存在但玩家离线,允许重连并替换
4. 断开连接时从 `active_usernames` 移除
### 5.2 AI托管机制
**触发条件:**
1. 玩家主动断开连接(关闭浏览器)→ 30秒后自动托管
2. 回合超时(30秒无操作)→ 累计3次后自动托管
3. 玩家主动点击"托管"按钮
**托管流程:**
```python
def ai_control_after_timeout(room_id, player_id, timeout=30):
time.sleep(timeout)
if player.offline_since is None:
return # 已重连,取消托管
player.enable_ai_control()
# 通知所有玩家
emit('player_ai_controlled', {...})
# 如果是当前回合,立即触发AI
if current_turn == player_id:
check_and_trigger_ai(room_id)
```
**取消托管:**
- 玩家重连时自动取消托管
- 玩家手动点击"取消托管"
### 5.3 回合超时管理
```python
# 配置
TURN_TIMEOUT_SECONDS = 30 # 每回合30秒
MAX_TIMEOUT_COUNT = 3 # 最大超时3次
# 定时器管理
room_turn_timers: Dict[str, Timer] = {} # room_id -> Timer
player_timeout_count: Dict[str, int] = {} # player_id -> count
def start_turn_timer(room_id):
cancel_turn_timer(room_id)
timer = threading.Timer(TURN_TIMEOUT_SECONDS, handle_turn_timeout, args=[room_id])
room_turn_timers[room_id] = timer
def handle_turn_timeout(room_id):
player_timeout_count[player_id] += 1
if timeout_count >= MAX_TIMEOUT_COUNT:
player.enable_ai_control() # 启用托管
else:
_execute_auto_pass(room, game, player_id) # 自动不要
```
### 5.4 断线重连机制
**重连匹配优先级:**
1. 已登录用户通过 `username` 匹配
2. 通过 `player_id` 匹配(保存在localStorage)
3. 通过 `player_name` 匹配(已断开状态的玩家)
**重连流程:**
```
1. 客户端尝试 reconnect_room
2. 服务端通过 user_room_map 或 player_name 查找原玩家
3. 单点登录检查(确保没有其他活跃连接)
4. player.connect(new_socket_id) - 更新连接状态
5. player.disable_ai_control() - 取消托管
6. 加入 Socket.IO room
7. 广播 player_reconnected 事件
8. 恢复游戏状态
```
### 5.5 房间清理策略
```python
def cleanup_inactive_rooms(max_inactive_minutes=30):
for room in rooms:
all_disconnected = all(not p.is_connected for p in room.human_players)
inactive_time = now - room.last_activity
if all_disconnected:
if room.status == 'ended' and inactive_time > 5*60:
cleanup_room(room) # 已结束,5分钟后清理
elif inactive_time > 10*60:
cleanup_room(room) # 进行中,10分钟后清理
elif inactive_time > max_inactive_minutes*60:
cleanup_room(room) # 不活跃,30分钟后清理
```
清理任务每5分钟运行一次(后台线程)。
---
## 6. 游戏扩展机制
### 6.1 添加新游戏的步骤
1. **创建游戏目录**
```
newgame/
├── __init__.py
├── game.py # 继承 GameBase
├── game_logic.py # 游戏规则
└── ai_adapters.py # AI适配器
```
2. **实现 GameBase 子类**
```python
class NewGame(GameBase):
@property
def min_players(self) -> int: return 2
@property
def max_players(self) -> int: return 2
def handle_action(self, player_id, action, data) -> dict:
# 实现游戏逻辑
pass
def check_winner(self) -> Optional[str]:
# 检查获胜者
pass
```
3. **注册游戏类型** (server.py)
```python
from newgame import NewGame
room_manager.register_game('newgame', NewGame)
```
### 6.2 AI适配器接口
```python
class AIAdapter(ABC):
@abstractmethod
async def make_decision(self, game_state, available_actions, player_context):
return {
"action": "play",
"data": {"card_ids": [...]},
"reasoning": "决策理由"
}
```
---
## 7. 部署与运维
### 7.1 服务配置
```ini
# /etc/systemd/system/net-games.service
[Unit]
Description=Net-Games Server
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/nanocode/net-games
Environment=NET_GAMES_HOST=0.0.0.0
Environment=NET_GAMES_PORT=8900
Environment=NET_GAMES_DEBUG=false
Environment=DEEPSEEK_API_KEY=xxx
ExecStart=/usr/bin/python3 /home/ubuntu/nanocode/net-games/server.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
### 7.2 端口规划
| 服务 | 端口 | 说明 |
|------|------|------|
| Net-Games | 8900 | 网游系统(本服务)|
| Happy-Games | 8895 | 单机小游戏 |
| Personal-Blog | 8888/8889 | 博客系统 |
### 7.3 监控指标
- 在线房间数 (`/api/stats`)
- 在线玩家数
- AI调用次数/成本
- 平均游戏时长
---
## 8. 安全设计
### 8.1 认证安全
- 密码使用 werkzeug 哈希存储
- Session cookie 设置 HttpOnly, SameSite
- Flask-Login 管理登录状态
### 8.2 游戏安全
- 权威服务端:所有操作服务端验证
- 客户端仅展示,不执行游戏逻辑
- AI API Key 仅在服务端使用
### 8.3 防作弊
- 牌面仅发送给对应玩家(player_view)
- 操作合法性服务端验证
- 单点登录防止多开
---
## 9. 更新日志
| 日期 | 版本 | 变更内容 |
|------|------|----------|
| 2026-04-09 | v2.0 | 更新为实际实现版本,添加认证系统、AI托管、超时管理、断线重连 |
| 2026-04-08 | v1.0 | 初始设计文档,基础架构设计 |
---
## 10. 参考文档
- [API.md](./API.md) - 详细接口文档
- [DATABASE.md](./DATABASE.md) - 数据库设计
- [UI_DESIGN_GUIDELINES.md](./UI_DESIGN_GUIDELINES.md) - UI设计规范
---
*文档版本: v2.0*
*最后更新: 2026-04-09*
*维护者: 开发团队*