545 lines
22 KiB
Markdown
545 lines
22 KiB
Markdown
# 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*
|
||
*维护者: 开发团队*
|