a
This commit is contained in:
@@ -0,0 +1,852 @@
|
||||
# Net-Games API 接口文档
|
||||
|
||||
> **版本**: v2.0
|
||||
> **更新日期**: 2026-04-09
|
||||
> **服务端**: Flask-SocketIO on port 8900
|
||||
|
||||
---
|
||||
|
||||
## 1. HTTP REST API
|
||||
|
||||
### 1.1 认证相关
|
||||
|
||||
#### `GET /api/auth/status` - 检查登录状态
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"authenticated": true,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "caixianming",
|
||||
"nickname": "蔡叔叔",
|
||||
"created_at": "2026-04-01T10:00:00",
|
||||
"role": "user",
|
||||
"is_admin": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/auth/login` - 用户登录
|
||||
**请求:**
|
||||
```json
|
||||
{
|
||||
"username": "caixianming",
|
||||
"password": "cxm123456"
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "登录成功",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "caixianming",
|
||||
"nickname": "蔡叔叔",
|
||||
"role": "user",
|
||||
"is_admin": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/auth/register` - 用户注册
|
||||
**请求:**
|
||||
```json
|
||||
{
|
||||
"username": "newuser",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "注册成功",
|
||||
"user": {...}
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/auth/logout` - 用户登出
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "已登出"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 用户相关
|
||||
|
||||
#### `GET /api/user/profile` - 获取个人资料
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "caixianming",
|
||||
"nickname": "蔡叔叔",
|
||||
"created_at": "2026-04-01T10:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `PUT /api/user/profile` - 更新昵称
|
||||
**请求:**
|
||||
```json
|
||||
{
|
||||
"nickname": "新昵称"
|
||||
}
|
||||
```
|
||||
|
||||
#### `PUT /api/user/password` - 修改密码
|
||||
**请求:**
|
||||
```json
|
||||
{
|
||||
"old_password": "旧密码",
|
||||
"new_password": "新密码"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/user/scores` - 获取各游戏积分
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"scores": {
|
||||
"doudizhu": 100,
|
||||
"chinese_chess": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 房间相关
|
||||
|
||||
#### `GET /api/rooms` - 获取房间列表
|
||||
**查询参数:**
|
||||
- `game_type` - 游戏类型过滤 (可选)
|
||||
- `status` - 状态过滤: waiting/playing (默认: waiting)
|
||||
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"rooms": [
|
||||
{
|
||||
"id": "room_xxx",
|
||||
"code": "A1B2C3",
|
||||
"game_type": "doudizhu",
|
||||
"status": "waiting",
|
||||
"max_players": 3,
|
||||
"player_count": 1,
|
||||
"host_id": "player_xxx",
|
||||
"created_at": 1234567890,
|
||||
"players": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/rooms/<room_code>` - 获取房间信息
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"room": {
|
||||
"id": "room_xxx",
|
||||
"code": "A1B2C3",
|
||||
"game_type": "doudizhu",
|
||||
"status": "playing",
|
||||
"players": [...],
|
||||
"game": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 统计相关
|
||||
|
||||
#### `GET /api/stats` - 获取服务统计(公开)
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"stats": {
|
||||
"total_rooms": 10,
|
||||
"waiting_rooms": 3,
|
||||
"playing_rooms": 7,
|
||||
"total_players": 21
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/admin/stats` - 管理员详细统计
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"stats": {
|
||||
"total_rooms": 10,
|
||||
"waiting_rooms": 3,
|
||||
"playing_rooms": 7,
|
||||
"total_players": 21,
|
||||
"online_users": 15,
|
||||
"by_game": {
|
||||
"doudizhu": {...}
|
||||
},
|
||||
"rooms": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `DELETE /api/admin/rooms/<room_code>` - 强制关闭房间
|
||||
**响应:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "房间 A1B2C3 已关闭"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. Socket.IO 事件
|
||||
|
||||
### 2.1 连接管理
|
||||
|
||||
#### `connect` - 客户端连接
|
||||
**服务端响应:**
|
||||
```json
|
||||
{
|
||||
"event": "connected",
|
||||
"data": {
|
||||
"socket_id": "xxx",
|
||||
"message": "欢迎连接到 Net-Games",
|
||||
"has_active_game": true,
|
||||
"room_code": "A1B2C3",
|
||||
"room_id": "room_xxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `disconnect` - 客户端断开
|
||||
- 服务端自动处理,标记玩家为离线
|
||||
- 30秒后启用AI托管
|
||||
|
||||
#### `ping` / `pong` - 心跳
|
||||
**客户端发送:**
|
||||
```json
|
||||
{
|
||||
"event": "ping",
|
||||
"data": {"timestamp": 1234567890}
|
||||
}
|
||||
```
|
||||
|
||||
**服务端响应:**
|
||||
```json
|
||||
{
|
||||
"event": "pong",
|
||||
"data": {
|
||||
"timestamp": 1234567890,
|
||||
"server_time": 1234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 房间管理事件
|
||||
|
||||
#### `create_room` - 创建房间
|
||||
**客户端发送:**
|
||||
```json
|
||||
{
|
||||
"game_type": "doudizhu",
|
||||
"player_name": "蔡叔叔",
|
||||
"config": {
|
||||
"max_players": 3
|
||||
},
|
||||
"ai_players": [
|
||||
{"name": "AI-规则", "ai_type": "rule"},
|
||||
{"name": "AI-DeepSeek", "ai_type": "deepseek"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**服务端响应 (创建者):**
|
||||
```json
|
||||
{
|
||||
"event": "room_created",
|
||||
"data": {
|
||||
"success": true,
|
||||
"room": {
|
||||
"id": "room_xxx",
|
||||
"code": "A1B2C3",
|
||||
"game_type": "doudizhu",
|
||||
"status": "waiting",
|
||||
"max_players": 3,
|
||||
"player_count": 3,
|
||||
"host_id": "player_xxx",
|
||||
"players": [...]
|
||||
},
|
||||
"player": {
|
||||
"id": "player_xxx",
|
||||
"name": "蔡叔叔",
|
||||
"seat_number": 0,
|
||||
"is_ready": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应:**
|
||||
```json
|
||||
{
|
||||
"event": "error",
|
||||
"data": {
|
||||
"code": "ALREADY_ONLINE",
|
||||
"message": "账号 \"蔡叔叔\" 已在游戏中"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `join_room` - 加入房间
|
||||
**客户端发送:**
|
||||
```json
|
||||
{
|
||||
"room_code": "A1B2C3",
|
||||
"player_name": "玩家B"
|
||||
}
|
||||
```
|
||||
|
||||
**服务端响应 (加入者):**
|
||||
```json
|
||||
{
|
||||
"event": "room_joined",
|
||||
"data": {
|
||||
"success": true,
|
||||
"room": {...},
|
||||
"player": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**广播 (其他玩家):**
|
||||
```json
|
||||
{
|
||||
"event": "player_joined",
|
||||
"data": {
|
||||
"player": {
|
||||
"id": "player_yyy",
|
||||
"name": "玩家B",
|
||||
"seat_number": 1,
|
||||
"is_ready": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `leave_room` - 离开房间
|
||||
**服务端响应 (离开者):**
|
||||
```json
|
||||
{
|
||||
"event": "room_left",
|
||||
"data": {"success": true}
|
||||
}
|
||||
```
|
||||
|
||||
**广播 (其他玩家):**
|
||||
```json
|
||||
{
|
||||
"event": "player_left",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `reconnect_room` - 断线重连
|
||||
**客户端发送:**
|
||||
```json
|
||||
{
|
||||
"room_code": "A1B2C3",
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔"
|
||||
}
|
||||
```
|
||||
|
||||
**服务端响应 (重连者):**
|
||||
```json
|
||||
{
|
||||
"event": "reconnect_success",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player": {...},
|
||||
"room": {...},
|
||||
"was_ai_controlled": true,
|
||||
"message": "重连成功,欢迎回到游戏!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**广播 (其他玩家):**
|
||||
```json
|
||||
{
|
||||
"event": "player_reconnected",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔",
|
||||
"was_ai_controlled": true,
|
||||
"message": "蔡叔叔 已回到游戏"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 游戏准备事件
|
||||
|
||||
#### `set_ready` - 设置准备状态
|
||||
**客户端发送:**
|
||||
```json
|
||||
{
|
||||
"ready": true
|
||||
}
|
||||
```
|
||||
|
||||
**广播 (所有人):**
|
||||
```json
|
||||
{
|
||||
"event": "room_updated",
|
||||
"data": {
|
||||
"room": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `start_game` - 开始游戏(仅房主)
|
||||
**广播 (所有人):**
|
||||
```json
|
||||
{
|
||||
"event": "game_started",
|
||||
"data": {
|
||||
"room": {
|
||||
"id": "room_xxx",
|
||||
"status": "playing",
|
||||
"game": {
|
||||
"state": {
|
||||
"phase": "playing",
|
||||
"current_turn": "player_xxx",
|
||||
"data": {
|
||||
"phase": "calling",
|
||||
"lord_id": null,
|
||||
"bottom_cards": [],
|
||||
"player_cards_count": {"player_xxx": 17, ...}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 2.4 游戏操作事件
|
||||
|
||||
#### `game_action` - 游戏操作
|
||||
**客户端发送:**
|
||||
```json
|
||||
{
|
||||
"action": "call",
|
||||
"data": {"want": true}
|
||||
}
|
||||
```
|
||||
|
||||
**action 类型:**
|
||||
- `call` - 叫地主 (data: `{want: true/false}`)
|
||||
- `play` - 出牌 (data: `{card_ids: [...]}`)
|
||||
- `pass` - 不出/过 (data: `{}`)
|
||||
- `toggle_trustee` - 切换托管状态
|
||||
|
||||
**成功广播 (所有人):**
|
||||
```json
|
||||
{
|
||||
"event": "state_update",
|
||||
"data": {
|
||||
"action": "play",
|
||||
"player_id": "player_xxx",
|
||||
"result": {
|
||||
"success": true,
|
||||
"message": "出牌成功",
|
||||
"played_cards": [...],
|
||||
"card_type": {"type": "pair", "name": "对子"}
|
||||
},
|
||||
"game_state": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**失败响应 (仅操作者):**
|
||||
```json
|
||||
{
|
||||
"event": "action_failed",
|
||||
"data": {
|
||||
"action": "play",
|
||||
"message": "牌型不合法"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 游戏结束事件
|
||||
|
||||
#### `game_ended` - 游戏结束广播
|
||||
```json
|
||||
{
|
||||
"event": "game_ended",
|
||||
"data": {
|
||||
"winner": {
|
||||
"id": "player_xxx",
|
||||
"player_id": "player_xxx",
|
||||
"name": "蔡叔叔",
|
||||
"is_lord": true
|
||||
},
|
||||
"winner_id": "player_xxx",
|
||||
"room": {...},
|
||||
"scores": {
|
||||
"player_xxx": {
|
||||
"name": "蔡叔叔",
|
||||
"change": 6,
|
||||
"total": 106,
|
||||
"is_lord": true,
|
||||
"is_winner": true,
|
||||
"is_ai": false
|
||||
},
|
||||
"player_yyy": {
|
||||
"name": "AI-规则",
|
||||
"change": 0,
|
||||
"total": 0,
|
||||
"is_ai": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 托管与超时事件
|
||||
|
||||
#### `trustee_changed` - 托管状态变更
|
||||
```json
|
||||
{
|
||||
"event": "trustee_changed",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔",
|
||||
"is_trustee": true,
|
||||
"message": "蔡叔叔 进入了托管模式"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `player_ai_controlled` - 玩家被AI托管
|
||||
```json
|
||||
{
|
||||
"event": "player_ai_controlled",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔",
|
||||
"reason": "timeout",
|
||||
"message": "蔡叔叔 超时3次,已启用AI托管"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `player_disconnected` - 玩家断开连接
|
||||
```json
|
||||
{
|
||||
"event": "player_disconnected",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔",
|
||||
"will_ai_control": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `turn_timeout_auto` - 超时自动操作
|
||||
```json
|
||||
{
|
||||
"event": "turn_timeout_auto",
|
||||
"data": {
|
||||
"player_id": "player_xxx",
|
||||
"player_name": "蔡叔叔",
|
||||
"action": "pass",
|
||||
"action_name": "不要",
|
||||
"timeout_count": 1,
|
||||
"game_state": {...},
|
||||
"message": "蔡叔叔 超时,自动不要 (1/3)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.7 错误事件
|
||||
|
||||
#### `error` - 错误通知
|
||||
```json
|
||||
{
|
||||
"event": "error",
|
||||
"data": {
|
||||
"code": "NOT_YOUR_TURN",
|
||||
"message": "不是你的回合"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**常见错误码:**
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| `NOT_IN_ROOM` | 不在任何房间中 |
|
||||
| `ROOM_NOT_FOUND` | 房间不存在 |
|
||||
| `ROOM_FULL` | 房间已满 |
|
||||
| `GAME_NOT_STARTED` | 游戏未开始 |
|
||||
| `NOT_HOST` | 只有房主可以开始游戏 |
|
||||
| `NOT_YOUR_TURN` | 不是你的回合 |
|
||||
| `ALREADY_ONLINE` | 已在其他游戏中 |
|
||||
| `INVALID_CARDS` | 牌型不合法 |
|
||||
| `CANNOT_BEAT` | 管不起上家的牌 |
|
||||
| `AI_PLAYER` | AI玩家不能进行此操作 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 游戏状态结构
|
||||
|
||||
### 3.1 斗地主游戏状态
|
||||
```json
|
||||
{
|
||||
"room_id": "room_xxx",
|
||||
"game_type": "doudizhu",
|
||||
"min_players": 3,
|
||||
"max_players": 3,
|
||||
"player_count": 3,
|
||||
"is_started": true,
|
||||
"is_ended": false,
|
||||
|
||||
"state": {
|
||||
"game_type": "doudizhu",
|
||||
"room_id": "room_xxx",
|
||||
"phase": "playing",
|
||||
"round": 1,
|
||||
"turn_count": 5,
|
||||
"current_turn": "player_xxx",
|
||||
"winner": null,
|
||||
|
||||
"player_states": {
|
||||
"player_xxx": {
|
||||
"name": "蔡叔叔",
|
||||
"is_ai": false,
|
||||
"seat": 0,
|
||||
"cards": [...],
|
||||
"card_count": 17
|
||||
}
|
||||
},
|
||||
|
||||
"data": {
|
||||
"phase": "playing",
|
||||
"current_turn": "player_xxx",
|
||||
"lord_id": "player_xxx",
|
||||
"bottom_cards": [...],
|
||||
"desktop_cards": {
|
||||
"type": "pair",
|
||||
"value": 5,
|
||||
"name": "对子"
|
||||
},
|
||||
"last_played_id": "player_yyy",
|
||||
"pass_count": 0,
|
||||
"player_cards_count": {
|
||||
"player_xxx": 17,
|
||||
"player_yyy": 15,
|
||||
"player_zzz": 16
|
||||
}
|
||||
},
|
||||
|
||||
"history": [...]
|
||||
},
|
||||
|
||||
"players": {
|
||||
"player_xxx": {
|
||||
"id": "player_xxx",
|
||||
"name": "蔡叔叔",
|
||||
"type": "human",
|
||||
"is_ai": false,
|
||||
"is_connected": true,
|
||||
"is_ai_controlled": false,
|
||||
"seat_number": 0,
|
||||
"is_ready": true,
|
||||
"role": "landlord"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 玩家视角 (Player View)
|
||||
客户端通过 `player_view` 字段只能看到自己的手牌:
|
||||
```json
|
||||
{
|
||||
"my_cards": [
|
||||
{"id": "card_1", "suit": "spade", "rank": "A", "value": 14},
|
||||
{"id": "card_2", "suit": "heart", "rank": "K", "value": 13}
|
||||
],
|
||||
"my_cards_count": 17
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. AI 接口
|
||||
|
||||
### 4.1 AI 适配器基类
|
||||
```python
|
||||
class AIAdapter(ABC):
|
||||
@abstractmethod
|
||||
async def make_decision(self, game_state: dict,
|
||||
available_actions: list,
|
||||
player_context: dict) -> dict:
|
||||
"""
|
||||
返回决策结果
|
||||
{
|
||||
"action": "play" | "pass" | "call",
|
||||
"data": {...},
|
||||
"reasoning": "决策理由"
|
||||
}
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 4.2 AI 类型
|
||||
| AI类型 | 说明 | 适用游戏 |
|
||||
|--------|------|----------|
|
||||
| `rule` | 基于规则的贪心策略 | 斗地主 |
|
||||
| `deepseek` | DeepSeek 大模型 | 通用 |
|
||||
| `minimax` | Minimax + Alpha-Beta | 象棋 |
|
||||
|
||||
### 4.3 AI 输入格式
|
||||
```json
|
||||
{
|
||||
"game_state": {
|
||||
"status": "playing",
|
||||
"current_turn": "ai_player_1",
|
||||
"phase": "playing",
|
||||
"desktop_cards": {...},
|
||||
"player_cards_count": {...}
|
||||
},
|
||||
"available_actions": ["play", "pass"],
|
||||
"player_context": {
|
||||
"cards": [...],
|
||||
"is_lord": false,
|
||||
"teammate_cards": 17
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 游戏流程时序图
|
||||
|
||||
### 5.1 创建房间并游戏
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Client │ │ Server │ │ AI │
|
||||
└────┬────┘ └────┬─────┘ └────┬─────┘
|
||||
│ │ │
|
||||
│ create_room │ │
|
||||
│ ─────────────>│ │
|
||||
│ │ create room │
|
||||
│ │ add AI players │
|
||||
│ room_created │ │
|
||||
│ <─────────────│ │
|
||||
│ │ │
|
||||
│ start_game │ │
|
||||
│ ─────────────>│ │
|
||||
│ │ start game │
|
||||
│ game_started │ │
|
||||
│ <─────────────│ │
|
||||
│ │ │
|
||||
│ state_update │ │
|
||||
│ <─────────────│ │
|
||||
│ │ │
|
||||
│ │ ──────────────>│
|
||||
│ │ AI decision │
|
||||
│ │ <──────────────│
|
||||
│ │ │
|
||||
│ state_update │ │
|
||||
│ <─────────────│ (broadcast) │
|
||||
```
|
||||
|
||||
### 5.2 断线重连流程
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Client │ │ Server │ │ Room │
|
||||
└────┬────┘ └────┬─────┘ └────┬─────┘
|
||||
│ │ │
|
||||
│ disconnect │ │
|
||||
│ ────────X │ │
|
||||
│ │ mark offline │
|
||||
│ │ start 30s timer│
|
||||
│ │ │
|
||||
│ reconnect │ │
|
||||
│ ─────────────>│ │
|
||||
│ │ find player │
|
||||
│ │ ──────────────>│
|
||||
│ │ <──────────────│
|
||||
│ │ │
|
||||
│ reconnect_success │
|
||||
│ <─────────────│ │
|
||||
│ │ cancel timer │
|
||||
│ │ │
|
||||
│ (30s后如果没有重连) │
|
||||
│ │ enable AI ctrl │
|
||||
│ │ │
|
||||
│ player_ai_controlled │
|
||||
│ <─────────────│ (broadcast) │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 配置说明
|
||||
|
||||
### 6.1 环境变量
|
||||
| 变量名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `NET_GAMES_HOST` | `0.0.0.0` | 服务监听地址 |
|
||||
| `NET_GAMES_PORT` | `8900` | 服务端口 |
|
||||
| `NET_GAMES_DEBUG` | `true` | 调试模式 |
|
||||
| `DATABASE_URL` | `sqlite:///./netgames.db` | 数据库连接 |
|
||||
| `DEEPSEEK_API_KEY` | - | DeepSeek API 密钥 |
|
||||
| `LOG_LEVEL` | `INFO` | 日志级别 |
|
||||
|
||||
### 6.2 游戏配置 (config.py)
|
||||
```python
|
||||
ROOM_MAX_PLAYERS = {
|
||||
'doudizhu': 3,
|
||||
'chinese_chess': 2,
|
||||
}
|
||||
|
||||
ROOM_CODE_LENGTH = 6
|
||||
ROOM_INACTIVE_TIMEOUT_MINUTES = 30
|
||||
AI_TIMEOUT_SECONDS = 30
|
||||
TURN_TIMEOUT_SECONDS = 30 # 回合超时
|
||||
MAX_TIMEOUT_COUNT = 3 # 最大超时次数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 更新日志
|
||||
|
||||
| 日期 | 版本 | 变更内容 |
|
||||
|------|------|----------|
|
||||
| 2026-04-09 | v2.0 | 更新为实际实现版本,添加认证API、托管机制、超时处理 |
|
||||
| 2026-04-08 | v1.0 | 初始设计文档 |
|
||||
|
||||
---
|
||||
|
||||
*文档版本: v2.0*
|
||||
*最后更新: 2026-04-09*
|
||||
@@ -0,0 +1,544 @@
|
||||
# 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*
|
||||
*维护者: 开发团队*
|
||||
@@ -0,0 +1,373 @@
|
||||
# Net-Games 数据库设计文档
|
||||
|
||||
> **版本**: v2.0
|
||||
> **更新日期**: 2026-04-09
|
||||
> **数据库**: SQLite (开发/生产)
|
||||
|
||||
---
|
||||
|
||||
## 1. 技术选型
|
||||
|
||||
| 环境 | 数据库 | ORM | 说明 |
|
||||
|------|--------|-----|------|
|
||||
| 开发/生产 | SQLite | SQLAlchemy | 零配置,文件存储,适合当前规模 |
|
||||
|
||||
**选择理由:**
|
||||
- 单机部署,无需外部数据库服务
|
||||
- 数据量小(用户<1000,房间实时<100)
|
||||
- 备份简单(复制文件即可)
|
||||
- 未来可无缝迁移到PostgreSQL
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库文件
|
||||
|
||||
```
|
||||
/home/ubuntu/nanocode/net-games/
|
||||
├── net_games_auth.db # 主数据库(生产)
|
||||
└── instance/
|
||||
└── net_games_auth.db # Flask实例目录(开发)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 表结构设计
|
||||
|
||||
### 3.1 users - 用户表
|
||||
|
||||
存储注册用户信息和游戏积分。
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username VARCHAR(80) UNIQUE NOT NULL, -- 登录用户名(唯一)
|
||||
nickname VARCHAR(80), -- 显示昵称(默认同用户名)
|
||||
password_hash VARCHAR(200) NOT NULL, -- 密码哈希
|
||||
role VARCHAR(20) DEFAULT 'user', -- 角色: admin/user
|
||||
game_scores TEXT DEFAULT '{}', -- JSON格式各游戏积分
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
**game_scores JSON格式:**
|
||||
```json
|
||||
{
|
||||
"doudizhu": 100,
|
||||
"chinese_chess": 50
|
||||
}
|
||||
```
|
||||
|
||||
**Python模型** (server.py):
|
||||
```python
|
||||
class User(db.Model):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
nickname = db.Column(db.String(80), nullable=True)
|
||||
password_hash = db.Column(db.String(200), nullable=False)
|
||||
role = db.Column(db.String(20), default='user')
|
||||
game_scores = db.Column(db.Text, default='{}')
|
||||
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
|
||||
|
||||
def get_game_score(self, game_id):
|
||||
scores = json.loads(self.game_scores or '{}')
|
||||
return scores.get(game_id, 0)
|
||||
|
||||
def add_game_score(self, game_id, points):
|
||||
scores = json.loads(self.game_scores or '{}')
|
||||
scores[game_id] = scores.get(game_id, 0) + points
|
||||
self.game_scores = json.dumps(scores)
|
||||
return scores[game_id]
|
||||
```
|
||||
|
||||
**默认用户:**
|
||||
| 用户名 | 密码 | 昵称 | 角色 |
|
||||
|--------|------|------|------|
|
||||
| admin | abcd1234 | 管理员 | admin |
|
||||
| caixianming | cxm123456 | 蔡叔叔 | user |
|
||||
|
||||
---
|
||||
|
||||
### 3.2 内存数据 vs 持久化数据
|
||||
|
||||
**内存中维护(运行时):**
|
||||
| 数据 | 存储位置 | 说明 |
|
||||
|------|----------|------|
|
||||
| 房间列表 | RoomManager._rooms | Dict[room_id, Room] |
|
||||
| 玩家映射 | socket_player_map | Dict[socket_id, player_id] |
|
||||
| 用户映射 | user_socket_map | Dict[user_id, socket_id] |
|
||||
| 活跃用户 | active_usernames | Dict[username, player_id] |
|
||||
| 用户房间 | user_room_map | Dict[username, room_id] |
|
||||
|
||||
**持久化存储(SQLite):**
|
||||
| 数据 | 表 | 说明 |
|
||||
|------|-----|------|
|
||||
| 用户信息 | users | 账号、密码、积分 |
|
||||
|
||||
**注意:** 房间和玩家数据仅在内存中维护,游戏结束后房间被清理。如需持久化游戏记录,需要额外设计GameRecord表。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. 积分系统设计
|
||||
|
||||
### 4.1 积分规则(斗地主)
|
||||
|
||||
| 角色 | 胜利 | 失败 |
|
||||
|------|------|------|
|
||||
| 地主 | +6分 | -4分 |
|
||||
| 农民 | +3分 | -2分 |
|
||||
|
||||
**AI玩家不计积分**,仅人类玩家累计积分。
|
||||
|
||||
### 4.2 积分更新流程
|
||||
|
||||
```python
|
||||
def calculate_and_update_scores(room, winner_id):
|
||||
score_changes = {}
|
||||
|
||||
for pid, player in game.players.items():
|
||||
if player.is_ai:
|
||||
score_changes[pid] = {'change': 0, 'is_ai': True}
|
||||
continue
|
||||
|
||||
is_lord = (pid == game.lord_id)
|
||||
is_winner = (pid == winner_id)
|
||||
|
||||
# 计算积分变动
|
||||
if is_lord:
|
||||
change = 6 if is_winner else -4
|
||||
else:
|
||||
change = 3 if is_winner else -2
|
||||
|
||||
# 更新数据库
|
||||
db_user = User.query.filter_by(username=player.name).first()
|
||||
if db_user:
|
||||
new_total = db_user.add_game_score('doudizhu', change)
|
||||
db.session.commit()
|
||||
score_changes[pid] = {
|
||||
'change': change,
|
||||
'total': new_total,
|
||||
'is_ai': False
|
||||
}
|
||||
|
||||
return score_changes
|
||||
```
|
||||
|
||||
### 4.3 查询积分示例
|
||||
|
||||
```python
|
||||
# 获取用户斗地主积分
|
||||
user = User.query.filter_by(username='蔡叔叔').first()
|
||||
score = user.get_game_score('doudizhu') # 返回: 100
|
||||
|
||||
# 获取所有游戏积分
|
||||
scores = json.loads(user.game_scores) # 返回: {'doudizhu': 100, ...}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 预留表设计(未来扩展)
|
||||
|
||||
### 5.1 game_records - 游戏记录表(预留)
|
||||
|
||||
用于保存游戏历史记录和回放数据。
|
||||
|
||||
```sql
|
||||
CREATE TABLE game_records (
|
||||
id VARCHAR(32) PRIMARY KEY,
|
||||
room_id VARCHAR(32) NOT NULL,
|
||||
game_type VARCHAR(20) NOT NULL,
|
||||
|
||||
started_at TIMESTAMP NOT NULL,
|
||||
ended_at TIMESTAMP,
|
||||
duration_seconds INTEGER,
|
||||
|
||||
-- 玩家信息 (JSON数组)
|
||||
players JSON NOT NULL,
|
||||
/* 格式:
|
||||
[
|
||||
{"player_id": "xxx", "name": "玩家A", "role": "landlord",
|
||||
"is_ai": false, "score_change": 6},
|
||||
{"player_id": "ai_1", "name": "AI-规则", "role": "farmer",
|
||||
"is_ai": true, "ai_type": "rule", "score_change": -3}
|
||||
]
|
||||
*/
|
||||
|
||||
-- 结果
|
||||
winner_id VARCHAR(32),
|
||||
result_type VARCHAR(20), -- normal/surrender/timeout
|
||||
|
||||
-- 回放数据 (压缩存储)
|
||||
replay_data BLOB, -- JSON序列化后 gzip 压缩
|
||||
|
||||
-- 统计
|
||||
total_turns INTEGER,
|
||||
max_combo INTEGER,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 5.2 ai_usage - AI调用记录表(预留)
|
||||
|
||||
用于监控AI调用成本和性能。
|
||||
|
||||
```sql
|
||||
CREATE TABLE ai_usage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ai_type VARCHAR(20) NOT NULL, -- deepseek/rule/minimax
|
||||
model_name VARCHAR(50), -- deepseek-chat
|
||||
|
||||
room_id VARCHAR(32),
|
||||
game_type VARCHAR(20),
|
||||
|
||||
-- Token用量
|
||||
input_tokens INTEGER,
|
||||
output_tokens INTEGER,
|
||||
total_tokens INTEGER,
|
||||
|
||||
-- 性能
|
||||
latency_ms INTEGER,
|
||||
|
||||
-- 成本估算 (美元)
|
||||
estimated_cost DECIMAL(10,6),
|
||||
|
||||
-- 调试信息
|
||||
request_prompt TEXT,
|
||||
response_text TEXT,
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 5.3 rooms - 房间历史表(预留)
|
||||
|
||||
如需持久化房间信息,可创建此表。
|
||||
|
||||
```sql
|
||||
CREATE TABLE rooms (
|
||||
id VARCHAR(32) PRIMARY KEY,
|
||||
room_code VARCHAR(10) UNIQUE,
|
||||
game_type VARCHAR(20),
|
||||
|
||||
status VARCHAR(20), -- waiting/playing/ended/closed
|
||||
created_at TIMESTAMP,
|
||||
started_at TIMESTAMP,
|
||||
ended_at TIMESTAMP,
|
||||
|
||||
max_players INTEGER,
|
||||
config JSON,
|
||||
|
||||
winner_id VARCHAR(32),
|
||||
final_state JSON,
|
||||
|
||||
is_active BOOLEAN DEFAULT TRUE
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据库操作示例
|
||||
|
||||
### 6.1 初始化数据库
|
||||
|
||||
```python
|
||||
# server.py
|
||||
with app.app_context():
|
||||
db.create_all() # 创建所有表
|
||||
init_default_users() # 创建默认用户
|
||||
```
|
||||
|
||||
### 6.2 常用查询
|
||||
|
||||
```python
|
||||
# 用户认证
|
||||
user = User.query.filter_by(username='caixianming').first()
|
||||
if user and user.check_password('password'):
|
||||
login_user(user)
|
||||
|
||||
# 获取排行榜
|
||||
top_players = User.query.filter(
|
||||
User.game_scores != '{}'
|
||||
).order_by(
|
||||
User.id # 简化排序,实际需解析JSON
|
||||
).limit(10).all()
|
||||
|
||||
# 更新积分
|
||||
user = User.query.filter_by(username='蔡叔叔').first()
|
||||
new_score = user.add_game_score('doudizhu', 6)
|
||||
db.session.commit()
|
||||
```
|
||||
|
||||
### 6.3 数据库备份
|
||||
|
||||
```bash
|
||||
# 备份SQLite数据库
|
||||
cp /home/ubuntu/nanocode/net-games/net_games_auth.db \
|
||||
/home/ubuntu/nanocode/net-games/backups/net_games_auth_$(date +%Y%m%d).db
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 索引设计
|
||||
|
||||
```sql
|
||||
-- 用户查询优化
|
||||
CREATE INDEX idx_users_username ON users(username);
|
||||
CREATE INDEX idx_users_role ON users(role);
|
||||
|
||||
-- 预留:游戏记录查询
|
||||
-- CREATE INDEX idx_records_game_type ON game_records(game_type);
|
||||
-- CREATE INDEX idx_records_created ON game_records(created_at DESC);
|
||||
-- CREATE INDEX idx_records_winner ON game_records(winner_id);
|
||||
|
||||
-- 预留:AI用量查询
|
||||
-- CREATE INDEX idx_ai_usage_time ON ai_usage(created_at);
|
||||
-- CREATE INDEX idx_ai_usage_type ON ai_usage(ai_type);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据保留策略
|
||||
|
||||
| 数据类型 | 当前策略 | 未来规划 |
|
||||
|----------|----------|----------|
|
||||
| 用户数据 | 永久保留 | 永久保留 |
|
||||
| 积分记录 | 实时计算,无历史 | 可考虑添加积分变动日志 |
|
||||
| 房间数据 | 内存存储,游戏结束即清理 | 如需持久化,添加rooms表 |
|
||||
| 游戏记录 | 不保存 | 如需回放,添加game_records表 |
|
||||
| AI调用记录 | 不保存 | 如需成本控制,添加ai_usage表 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 更新日志
|
||||
|
||||
| 日期 | 版本 | 变更内容 |
|
||||
|------|------|----------|
|
||||
| 2026-04-09 | v2.0 | 更新为实际实现,当前仅使用users表,其他为预留设计 |
|
||||
| 2026-04-08 | v1.0 | 初始设计,包含完整ER图和多表设计 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 总结
|
||||
|
||||
**当前实现 (v2.0):**
|
||||
- 极简设计,仅使用 `users` 表存储用户信息和积分
|
||||
- 房间和玩家数据全部在内存中管理
|
||||
- 适合当前小规模部署
|
||||
|
||||
**未来扩展方向:**
|
||||
1. 添加 `game_records` 表保存游戏历史
|
||||
2. 添加 `ai_usage` 表监控AI成本
|
||||
3. 添加 `rooms` 表持久化房间信息
|
||||
4. 如需更复杂功能,迁移到PostgreSQL
|
||||
|
||||
---
|
||||
|
||||
*文档版本: v2.0*
|
||||
*最后更新: 2026-04-09*
|
||||
*数据库文件: net_games_auth.db*
|
||||
@@ -0,0 +1,142 @@
|
||||
# Phase 1 完成总结
|
||||
|
||||
## 完成内容
|
||||
|
||||
### 1. 项目结构
|
||||
```
|
||||
/home/ubuntu/nanocode/net-games/
|
||||
├── config.py # 全局配置
|
||||
├── server.py # Flask-SocketIO 主服务器 (端口 8900)
|
||||
├── test_engine.py # 单元测试 (31个测试)
|
||||
├── test_client.html # 测试客户端 (HTML + Socket.IO)
|
||||
├── engine/ # 游戏引擎核心
|
||||
│ ├── __init__.py
|
||||
│ ├── player.py # Player / HumanPlayer / AIPlayer
|
||||
│ ├── game_base.py # GameBase / GameState 游戏基类
|
||||
│ ├── room_manager.py # RoomManager / Room 房间管理
|
||||
│ ├── ai_adapter.py # AIAdapter 适配器基类
|
||||
│ └── test_game.py # 示例游戏(猜数字)
|
||||
├── doudizhu/ # 斗地主游戏 (Phase 2)
|
||||
├── chinese-chess/ # 中国象棋 (Phase 3)
|
||||
└── docs/ # 设计文档
|
||||
├── ARCHITECTURE.md
|
||||
├── API.md
|
||||
├── DATABASE.md
|
||||
└── PHASE1_SUMMARY.md # 本文件
|
||||
```
|
||||
|
||||
### 2. 核心组件
|
||||
|
||||
| 组件 | 文件 | 功能 |
|
||||
|------|------|------|
|
||||
| **Player** | `engine/player.py` | 玩家抽象基类,支持人类/AI |
|
||||
| **Room** | `engine/room_manager.py` | 房间状态管理 |
|
||||
| **RoomManager** | `engine/room_manager.py` | 单例模式,管理所有房间 |
|
||||
| **GameBase** | `engine/game_base.py` | 游戏抽象基类 |
|
||||
| **GameState** | `engine/game_base.py` | 游戏状态管理 |
|
||||
| **AIAdapter** | `engine/ai_adapter.py` | AI适配器基类 |
|
||||
| **Server** | `server.py` | Flask-SocketIO 服务 |
|
||||
|
||||
### 3. 测试验证
|
||||
|
||||
**单元测试**: 31个测试全部通过 ✅
|
||||
```bash
|
||||
cd /home/ubuntu/nanocode/net-games
|
||||
python3 test_engine.py
|
||||
```
|
||||
|
||||
测试结果:
|
||||
- TestPlayer: 6个测试
|
||||
- TestRoom: 5个测试
|
||||
- TestRoomManager: 6个测试
|
||||
- TestTestGame: 9个测试
|
||||
- TestGameState: 5个测试
|
||||
- IntegrationTest: 1个测试
|
||||
|
||||
**服务器状态**: ✅ 运行中 (端口 8900)
|
||||
```
|
||||
PID: 743650, 743652
|
||||
监听: 0.0.0.0:8900
|
||||
```
|
||||
|
||||
### 4. API 端点
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/` | GET | 服务状态 |
|
||||
| `/api/rooms` | GET | 房间列表 |
|
||||
| `/api/rooms/<code>` | GET | 房间详情 |
|
||||
| `/api/stats` | GET | 服务统计 |
|
||||
|
||||
### 5. Socket.IO 事件
|
||||
|
||||
**通用事件**:
|
||||
- `connect` / `disconnect` - 连接管理
|
||||
- `create_room` - 创建房间
|
||||
- `join_room` - 加入房间
|
||||
- `leave_room` - 离开房间
|
||||
- `set_ready` - 准备状态
|
||||
- `start_game` - 开始游戏
|
||||
- `game_action` - 游戏操作
|
||||
- `ping` / `pong` - 心跳
|
||||
|
||||
### 6. 技术选型确认
|
||||
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| Python | 3.12.3 | 运行环境 |
|
||||
| Flask | 3.1.3 | Web框架 |
|
||||
| Flask-SocketIO | 最新 | 实时通信 |
|
||||
| Socket.IO | 4.x | WebSocket 协议 |
|
||||
| 线程模式 | threading | 开发模式 |
|
||||
|
||||
### 7. 验证方式
|
||||
|
||||
**1. 访问 HTTP API:**
|
||||
```bash
|
||||
curl http://129.211.173.87:8900/
|
||||
curl http://129.211.173.87:8900/api/stats
|
||||
curl http://129.211.173.87:8900/api/rooms
|
||||
```
|
||||
|
||||
**2. 打开测试客户端:**
|
||||
在浏览器中打开 `http://129.211.173.87:8900/test_client.html`
|
||||
(需要将 test_client.html 放到服务器能访问的位置)
|
||||
|
||||
**3. 运行单元测试:**
|
||||
```bash
|
||||
cd /home/ubuntu/nanocode/net-games
|
||||
python3 test_engine.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 下一阶段 (Phase 2): 斗地主实现
|
||||
|
||||
### 计划内容
|
||||
1. **斗地主规则引擎** (`doudizhu/game_logic.py`)
|
||||
- 牌型定义(单张、对子、顺子、炸弹等)
|
||||
- 出牌合法性验证
|
||||
- 胜负判定
|
||||
|
||||
2. **AI 适配器**
|
||||
- `RuleBasedAdapter` - 基于规则的贪心策略
|
||||
- `DeepSeekAdapter` - 大模型 AI(服务端调用)
|
||||
|
||||
3. **Socket.IO 事件处理**
|
||||
- 叫地主/抢地主
|
||||
- 出牌/不出
|
||||
- 状态同步
|
||||
|
||||
4. **前端界面**
|
||||
- 复用现有斗地主 UI
|
||||
- 对接 Socket.IO 通信
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
- 服务器当前运行在端口 **8900**
|
||||
- 使用 **TestGame**(猜数字)验证引擎功能
|
||||
- 所有 API Key 将在服务端管理,前端无法访问
|
||||
- 可随时重启服务器:`pkill -f "python3 server.py" && python3 server.py`
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user