x01.weiqi.15: AI 对弈
<h1 id="x01weiqi">x01.weiqi</h1><blockquote>
<p>本文档是 x01.weiqi 围棋对弈平台的核心技术参考资料,详细解释系统架构、核心模块实现、关键算法和前后端交互协议。</p>
</blockquote>
<ul>
<li>项目地址: x01.weiqi</li>
</ul>
<hr>
<h2 id="目录">目录</h2>
<ol>
<li>系统架构概览</li>
<li>核心模块详解
<ul>
<li>2.1 main.py - FastAPI主应用</li>
<li>2.2 core/connect.py - 连接管理器</li>
<li>2.3 core/state.py - 游戏状态引擎</li>
<li>2.4 core/goai.py - AI引擎封装</li>
<li>2.5 core/auth.py - 认证与VIP系统</li>
<li>2.6 routers/ - 路由模块</li>
<li>2.7 static/script.js - 前端核心逻辑</li>
</ul>
</li>
<li>WebSocket通信协议</li>
<li>Katago引擎集成</li>
<li>关键算法实现</li>
<li>部署指南</li>
<li>修复记录</li>
</ol>
<hr>
<h2 id="1-系统架构概览">1. 系统架构概览</h2>
<h3 id="11-系统定位">1.1 系统定位</h3>
<p>x01.weiqi 是一个基于 Web 的围棋对弈平台,集成了 KataGo AI 引擎(职业级水平),提供从入门到专业级的 AI 对手。系统采用前后端分离架构,支持实时对弈、棋谱管理、VIP会员等功能。</p>
<h3 id="12-技术栈">1.2 技术栈</h3>
<table>
<thead>
<tr>
<th>层级</th>
<th>技术选型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>后端框架</strong></td>
<td>Python 3.12 + FastAPI</td>
<td>高性能异步Web框架,支持WebSocket</td>
</tr>
<tr>
<td><strong>实时通信</strong></td>
<td>WebSocket</td>
<td>双向实时通信,支持断线重连</td>
</tr>
<tr>
<td><strong>数据库</strong></td>
<td>SQLite</td>
<td>轻量级嵌入式数据库,适合中小规模</td>
</tr>
<tr>
<td><strong>AI引擎</strong></td>
<td>KataGo v1.16.0</td>
<td>职业级围棋AI,EigenAVX2优化版本</td>
</tr>
<tr>
<td><strong>AI框架</strong></td>
<td>Katago</td>
<td>KataGo的Python封装,提供多种AI策略</td>
</tr>
<tr>
<td><strong>前端</strong></td>
<td>原生 JavaScript + Canvas</td>
<td>无框架依赖,高性能棋盘渲染</td>
</tr>
<tr>
<td><strong>支付</strong></td>
<td>微信支付V3</td>
<td>Native扫码支付</td>
</tr>
</tbody>
</table>
<h3 id="13-项目目录结构">1.3 项目目录结构</h3>
<pre><code>x01.weiqi/
├── main.py # FastAPI主应用(WebSocket端点、打谱API、在线用户API)
├── tool.py # 运维工具脚本(密钥生成、棋谱清理)
├── core/ # 核心后端模块
│ ├── state.py # GameState类 - 围棋游戏核心逻辑(~3534行)
│ ├── connect.py # ConnectionManager类 - WebSocket连接管理(~473行)
│ ├── auth.py # 认证、用户管理、VIP、数据库操作(~829行)
│ ├── goai.py # GoAI类 - KataGo AI引擎集成
│ ├── ai.py # AI策略系统(15+种策略注册表)
│ ├── game.py # Game/BaseGame类(棋局树、落子、提子)
│ ├── game_node.py # GameNode类(SGF节点、分析数据)
│ ├── engine.py # KataGoEngine引擎通信(子进程+JSON协议)
│ ├── katabase.py # KatagoBase基类(配置加载、Player类)
│ ├── sgf_parser.py # SGF解析器(Move类、SGF类)
│ ├── constants.py # 全局常量定义
│ ├── utils.py # 工具函数
│ ├── wechat_pay.py # 微信支付V3 API封装
│ ├── email_config.py # 邮件发送配置
│ └── bj_time.py # 北京时间工具
├── routers/ # 路由模块(从main.py拆分)
│ ├── __init__.py # 路由注册汇总
│ ├── deps.py # 依赖注入(get_current_user, require_admin, check_vip_feature)
│ ├── auth.py # 认证路由(注册/登录/改密码/重置密码)
│ ├── vip.py # VIP路由(订单/支付/设置)
│ ├── admin.py # 管理路由(用户/棋谱管理)
│ ├── game.py # 游戏路由(棋谱查看)
│ └── doc.py # 文档路由(Markdown管理)
├── static/ # 前端静态资源
│ ├── index.html # 主HTML入口
│ ├── script.js # 前端核心JS(~4034行)
│ ├── style.css # 样式(~1654行)
│ ├── template-loader.js # 模板加载器(异步加载视图HTML)
│ ├── marked.min.js # Markdown解析库
│ ├── highlight.min.js # 代码高亮库
│ └── views/ # HTML视图模板
│ ├── menu.html # 菜单栏
│ ├── home.html # 主页视图
│ ├── game.html # 围棋对弈视图
│ ├── help.html # 帮助视图
│ ├── doc.html # 文档视图
│ ├── admin.html # 管理视图
│ └── modals.html # 模态框(密码找回、VIP购买等)
├── data/ # 数据与配置
│ ├── config.json # Katago配置(引擎路径、AI策略参数)
│ ├── analysis_config.cfg # KataGo分析配置
│ └── *.bin.gz # KataGo神经网络模型
├── requirements.txt # Python依赖
└── Makefile # 构建脚本
</code></pre>
<h3 id="14-架构图">1.4 架构图</h3>
<pre><code>┌─────────────────────────────────────────────────────────────────┐
│ 前端层 (Browser) │
│┌──────────────┐┌──────────────┐┌──────────────┐ │
││UI渲染层 ││事件处理层││通信层 │ │
││(Canvas) ││(Events) ││(WebSocket) │ │
│└──────────────┘└──────────────┘└──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↕ WebSocket (JSON协议)
┌─────────────────────────────────────────────────────────────────┐
│ 后端层 (FastAPI) │
│┌──────────────┐┌──────────────┐┌──────────────┐ │
││路由层 ││连接管理器││游戏状态 │ │
││(routers/)││(connect.py)││(GameState) │ │
│└──────────────┘└──────────────┘└──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↕ API调用
┌─────────────────────────────────────────────────────────────────┐
│ AI引擎层 (GoAI) │
│┌──────────────────────────────────────────────────────────┐│
││KataGo引擎 (Katago) → 策略选择 → 落子生成 ││
│└──────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
↕ 子进程通信 (stdin/stdout JSON)
┌─────────────────────────────────────────────────────────────────┐
│ KataGo引擎进程 │
│┌──────────────┐┌──────────────┐┌──────────────┐ │
││MCTS搜索 ││神经网络 ││分析输出 │ │
│└──────────────┘└──────────────┘└──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
</code></pre>
<hr>
<h2 id="2-核心模块详解">2. 核心模块详解</h2>
<h3 id="21-mainpy---fastapi主应用">2.1 main.py - FastAPI主应用</h3>
<h4 id="211-文件概述">2.1.1 文件概述</h4>
<p><strong>文件路径</strong>: <code>main.py</code><br>
<strong>代码行数</strong>: ~889行<br>
<strong>主要职责</strong>: FastAPI应用入口、WebSocket端点、打谱模式API、在线用户API、积分计算</p>
<p><strong>核心组件</strong>:</p>
<ol>
<li>应用生命周期管理(lifespan)</li>
<li>路由注册(从routers/模块导入)</li>
<li>WebSocket端点(游戏通信)</li>
<li>WebSocket消息处理(handle_websocket_message)</li>
<li>游戏结束处理(handle_game_over)</li>
<li>积分计算(calculate_score_change)</li>
<li>打谱模式API</li>
</ol>
<h4 id="212-应用生命周期管理">2.1.2 应用生命周期管理</h4>
<pre><code class="language-python"># main.py:31-49
async def session_cleanup_task():
"""定期清理过期会话"""
while True:
await asyncio.sleep(300)# 5分钟
if manager:
manager.cleanup_expired_sessions()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
global manager
manager = ConnectionManager()
cleanup_task = asyncio.create_task(session_cleanup_task())
logging.info(" 应用启动")
yield
cleanup_task.cancel()
logging.info(" 应用关闭")
app = FastAPI(lifespan=lifespan)
app.mount("/static", StaticFiles(directory="static"), name="static")
# 注册路由
app.include_router(auth_router)
app.include_router(vip_router)
app.include_router(admin_router)
app.include_router(game_router)
app.include_router(doc_router)
</code></pre>
<p><strong>代码解释</strong>:</p>
<ol>
<li><strong>session_cleanup_task()</strong>: 异步后台任务,每5分钟清理一次过期的独立游戏会话(打谱模式)</li>
<li><strong>lifespan()</strong>: FastAPI应用生命周期上下文管理器
<ul>
<li>启动时:创建全局 <code>ConnectionManager</code> 单例,启动会话清理后台任务</li>
<li>关闭时:取消后台任务</li>
</ul>
</li>
<li><strong>路由注册</strong>: 从 <code>routers/</code> 模块导入5个路由器并注册到应用</li>
</ol>
<p><strong>设计模式</strong>: 生命周期管理模式 + 路由模块化</p>
<h4 id="213-websocket端点">2.1.3 WebSocket端点</h4>
<pre><code class="language-python"># main.py:109-169
@app.websocket("/ws/{room_id}")
async def websocket_endpoint(websocket: WebSocket, room_id: str, token: str = None):
"""WebSocket连接处理"""
# 验证token
if not token:
await websocket.close(code=1008, reason="Missing authentication token")
return
username = decode_token(token)
if not username:
await websocket.close(code=1008, reason="Invalid authentication token")
return
color_str = None
try:
success = await manager.join_room(room_id, websocket, username)
if not success:
return
# lobby房间只保持连接
if room_id == "lobby":
while True:
msg = await websocket.receive_json()
if msg.get("type") == "ping":
await websocket.send_json({"type": "pong"})
return
# 接收颜色确认
data = await websocket.receive_json()
if data.get("type") == "color_ack":
color_str = str(data["color"])
# 存储用户的颜色信息
...
game = manager.games
# 发送当前状态
await websocket.send_json({
"type": "state_update",
"state": game_state_to_frontend(game)
})
# 消息循环
while True:
data = await websocket.receive_json()
await handle_websocket_message(room_id, username, data, websocket)
except WebSocketDisconnect:
pass
finally:
if color_str:
manager.leave_room(room_id, color_str, username)
</code></pre>
<p><strong>代码解释</strong>:</p>
<p>WebSocket端点处理流程:</p>
<ol>
<li><strong>验证token</strong>: 检查JWT令牌是否有效</li>
<li><strong>加入房间</strong>: 调用 <code>manager.join_room()</code></li>
<li><strong>lobby处理</strong>: lobby房间只保持连接,响应ping</li>
<li><strong>颜色确认</strong>: 接收 <code>color_ack</code> 消息,存储颜色信息</li>
<li><strong>发送状态</strong>: 发送当前游戏状态</li>
<li><strong>消息循环</strong>: 委托给 <code>handle_websocket_message()</code> 处理</li>
<li><strong>断线清理</strong>: finally块中调用 <code>manager.leave_room()</code></li>
</ol>
<h4 id="214-websocket消息处理">2.1.4 WebSocket消息处理</h4>
<pre><code class="language-python"># main.py:236-622
async def handle_websocket_message(room_id: str, username: str, data: dict, websocket: WebSocket):
"""处理WebSocket消息"""
msg_type = data.get("type")
game = manager.games.get(room_id)
if not game:
return
if msg_type == "move":
# 落子处理:验证轮次 -> place_stone() -> 广播 -> 检查game_over或AI轮次
...
elif msg_type == "pass":
# Pass处理:pass_turn() -> 广播 -> 检查game_over或AI轮次
...
elif msg_type == "analyze":
# 点目:VIP权限检查 -> analyze_territory() -> 返回analysis_result
...
elif msg_type == "undo":
# 悔棋:undo_move() -> 广播state_update
...
elif msg_type == "resign":
# 认输:设置game_over/winner/final_score -> 保存棋谱(>=50手) -> 广播 -> 清理
...
elif msg_type == "end_game":
# 结束对局:点目计算 -> AI对弈直接结束 / 人类对弈(>=170手直接结束 / <170手需对方确认)
...
elif msg_type == "end_game_confirm":
# 确认结束:end_game_manually() -> 保存棋谱 -> 计算积分 -> 广播 -> 清理
...
</code></pre>
<p><strong>消息类型说明</strong>:</p>
<table>
<thead>
<tr>
<th>消息类型</th>
<th>处理逻辑</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>move</code></td>
<td>验证轮次 → place_stone() → 广播state_update → 检查game_over或AI轮次</td>
</tr>
<tr>
<td><code>pass</code></td>
<td>pass_turn() → 广播 → 检查game_over或AI轮次</td>
</tr>
<tr>
<td><code>analyze</code></td>
<td>VIP权限检查 → analyze_territory() → 返回analysis_result</td>
</tr>
<tr>
<td><code>undo</code></td>
<td>undo_move() → 广播state_update</td>
</tr>
<tr>
<td><code>resign</code></td>
<td>设置game_over → 保存棋谱(>=50手) → 广播game_over → 清理</td>
</tr>
<tr>
<td><code>end_game</code></td>
<td>点目计算 → AI直接结束 / 人类(>=170手直接结束 / <170手需确认)</td>
</tr>
<tr>
<td><code>end_game_confirm</code></td>
<td>确认结束 → 保存棋谱 → 计算积分 → 广播 → 清理</td>
</tr>
</tbody>
</table>
<h4 id="215-积分计算">2.1.5 积分计算</h4>
<pre><code class="language-python"># main.py:171-233
def calculate_score_change(winner_username: str, loser_username: str):
"""计算积分变化
规则:
- 同段位:胜方+1,负方-1
- 不同段位:
- 高段位胜:不加积分
- 高段位负:-1
- 低段位胜:+1
- 低段位负:不减积分
- AI固定为9段,不计算积分
- 10段积分到100后:胜不加,负减1
"""
</code></pre>
<h4 id="216-打谱模式api">2.1.6 打谱模式API</h4>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST</td>
<td><code>/api/import_sgf</code></td>
<td>独立SGF导入(创建会话)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/export_sgf_session</code></td>
<td>基于会话ID导出SGF</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/replay_next</code></td>
<td>打谱下一步</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/replay_prev</code></td>
<td>打谱上一步</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/analyze</code></td>
<td>独立模式点目</td>
</tr>
<tr>
<td>POST</td>
<td><code>/api/analyze_board</code></td>
<td>基于棋盘数据的点目分析</td>
</tr>
</tbody>
</table>
<h4 id="217-其他api">2.1.7 其他API</h4>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/</code></td>
<td>主页</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/online-users</code></td>
<td>获取在线用户列表</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/generate-room</code></td>
<td>生成新房间号</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/user-status</code></td>
<td>获取用户对局状态</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="22-coreconnectpy---连接管理器">2.2 core/connect.py - 连接管理器</h3>
<h4 id="221-文件概述">2.2.1 文件概述</h4>
<p><strong>文件路径</strong>: <code>core/connect.py</code><br>
<strong>代码行数</strong>: ~473行<br>
<strong>主要职责</strong>: WebSocket连接管理、房间管理、断线重连、AI落子调度、状态序列化</p>
<p><strong>核心组件</strong>:</p>
<ol>
<li><code>game_state_to_frontend()</code> - 游戏状态序列化函数</li>
<li><code>ConnectionManager</code> - 连接管理器类</li>
</ol>
<h4 id="222-game_state_to_frontend函数">2.2.2 game_state_to_frontend函数</h4>
<pre><code class="language-python"># core/connect.py:18-61
def game_state_to_frontend(game: GameState) -> dict:
"""将游戏状态转换为前端格式
转换规则:
- 棋盘转置:board → board_transposed
- 劫点转换:{row, col} →
- 最后一手:{row, col, stone} →
- 地盘棋盘:同棋盘转置规则
"""
return {
"board": board_transposed,
"turn": turn,
"game_over": game.game_over,
"winner": game.winner,
"final_score": game.final_score,
"ko": ko,
"black_captured": game.black_captured,
"white_captured": game.white_captured,
"last_move": last_move,
"territory_board": territory_board,
"moves_count": game.current_move_number,
}
</code></pre>
<p><strong>关键点</strong>: 前端使用 <code>(x, y)</code> 坐标系(x=列, y=行),后端使用 <code>(row, col)</code> 坐标系,需要转置。</p>
<h4 id="223-connectionmanager类">2.2.3 ConnectionManager类</h4>
<pre><code class="language-python"># core/connect.py:64-102
class ConnectionManager:
"""WebSocket连接管理器"""
# 类常量
MAX_AI_ROOM_USERS = 1 # AI房间最大用户数
SESSION_TIMEOUT = 1800 # 会话过期时间(30分钟)
RECONNECT_TIMEOUT = 60 # 断线重连超时(60秒)
MOVE_TIMEOUT = 60 # 落子超时(60秒)
MAX_TIMEOUT_COUNT = 3 # 最大超时次数
MAX_REPLAY_ANALYSIS = 3 # 打谱点目最大次数
def __init__(self):
# 房间管理
self.rooms: Dict] = {} # {room_id: {color_str: WebSocket}}
self.games: Dict = {} # {room_id: GameState}
self.ai_tasks: Dict = {} # {room_id: asyncio.Task}
self.room_counter = 0 # 房间号计数器
# 用户管理
self.online_users: Dict = {} # {username: WebSocket}
self.user_game_status: Dict = {} # {username: {color, room_id, opponent}}
self.invitations: Dict = {} # {invite_id: {from, to, room_id, status}}
# AI房间管理
self.ai_room_users: Dict = {} # {room_id: }
self.room_users: Dict = {} # {room_id: }
# 会话管理
self.standalone_games: Dict = {} # {session_id: {game, created_at, last_accessed}}
self.daily_ai_usage: Dict = {} # {username: {date, count}}
self.disconnected_users: Dict = {} # {username: {room_id, color, disconnect_time}}
self.move_timers: Dict = {} # {room_id: {turn_start_time, current_player, timeout_count}}
self.replay_analysis_counts: Dict = {} # {session_id: {count}}
self.timer_tasks: Dict = {} # {room_id: asyncio.Task}
</code></pre>
<p><strong>数据结构详解</strong>:</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>类型</th>
<th>用途</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>rooms</code></td>
<td><code>Dict]</code></td>
<td>房间连接池,按颜色索引WebSocket</td>
<td><code>{"room1": {"1": ws1, "2": ws2}}</code></td>
</tr>
<tr>
<td><code>games</code></td>
<td><code>Dict</code></td>
<td>每个房间的游戏状态实例</td>
<td><code>{"room1": GameState实例}</code></td>
</tr>
<tr>
<td><code>ai_tasks</code></td>
<td><code>Dict</code></td>
<td>AI落子异步任务,支持取消</td>
<td><code>{"room1": Task对象}</code></td>
</tr>
<tr>
<td><code>online_users</code></td>
<td><code>Dict</code></td>
<td>在线用户列表,用于大厅显示和邀请</td>
<td><code>{"user1": ws1}</code></td>
</tr>
<tr>
<td><code>user_game_status</code></td>
<td><code>Dict</code></td>
<td>用户对局状态,用于断线重连</td>
<td><code>{"user1": {"room_id": "room1", "color": 1}}</code></td>
</tr>
<tr>
<td><code>disconnected_users</code></td>
<td><code>Dict</code></td>
<td>断线缓冲区,60秒重连窗口</td>
<td><code>{"user1": {"room_id": "room1", "disconnect_time": datetime}}</code></td>
</tr>
<tr>
<td><code>move_timers</code></td>
<td><code>Dict</code></td>
<td>落子计时器,三次超时判负</td>
<td><code>{"room1": {"timeout_count": {1: 0, 2: 0}}}</code></td>
</tr>
<tr>
<td><code>standalone_games</code></td>
<td><code>Dict</code></td>
<td>独立游戏实例(打谱模式)</td>
<td><code>{"session1": {"game": GameState}}</code></td>
</tr>
<tr>
<td><code>daily_ai_usage</code></td>
<td><code>Dict</code></td>
<td>非VIP用户今日AI对弈次数</td>
<td><code>{"user1": {"date": "2026-04-23", "count": 2}}</code></td>
</tr>
</tbody>
</table>
<p><strong>常量配置</strong>:</p>
<table>
<thead>
<tr>
<th>常量</th>
<th>值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MAX_AI_ROOM_USERS</code></td>
<td>1</td>
<td>AI房间最大用户数(避免卡顿)</td>
</tr>
<tr>
<td><code>SESSION_TIMEOUT</code></td>
<td>1800</td>
<td>会话过期时间(30分钟)</td>
</tr>
<tr>
<td><code>RECONNECT_TIMEOUT</code></td>
<td>60</td>
<td>断线重连超时时间(60秒)</td>
</tr>
<tr>
<td><code>MOVE_TIMEOUT</code></td>
<td>60</td>
<td>落子超时时间(60秒)</td>
</tr>
<tr>
<td><code>MAX_TIMEOUT_COUNT</code></td>
<td>3</td>
<td>最大超时次数(3次判负)</td>
</tr>
<tr>
<td><code>MAX_REPLAY_ANALYSIS</code></td>
<td>3</td>
<td>打谱点目最大次数</td>
</tr>
</tbody>
</table>
<h4 id="224-核心方法">2.2.4 核心方法</h4>
<p><strong>join_room()</strong> - 加入房间核心逻辑:</p>
<pre><code class="language-python"># core/connect.py:190-351
async def join_room(self, room_id: str, websocket: WebSocket, username: str):
"""加入房间核心逻辑
流程:
1. 重连检测
2. AI房间限制检查(VIP权限、用户已在其他AI房间、总用户数限制)
3. 房间初始化
4. lobby房间处理
5. 重连处理(恢复颜色、发送状态、通知对手)
6. 正常加入(分配颜色、判断游戏开始)
"""
</code></pre>
<p><strong>leave_room()</strong> - 离开房间:</p>
<pre><code class="language-python"># core/connect.py:353-383
def leave_room(self, room_id: str, color_str: str, username: str):
"""离开房间
流程:
1. 从房间连接中移除
2. 从AI房间用户列表中移除
3. 清理用户游戏状态
4. AI房间:清理游戏、房间、取消AI任务
"""
</code></pre>
<p><strong>_schedule_ai_move() / _ai_move_task()</strong> - AI落子调度:</p>
<pre><code class="language-python"># core/connect.py:385-460
def _schedule_ai_move(self, room_id: str):
"""调度AI落子任务:取消旧任务,创建新异步任务"""
async def _ai_move_task(self, room_id: str):
"""AI落子异步任务
流程:
1. 等待0.3秒(给前端渲染时间)
2. 检查游戏状态和是否轮到AI
3. 执行AI落子(game.make_ai_move())
4. 广播状态更新
5. 检查游戏是否结束(保存棋谱、清理资源)
6. 如果AI连续落子,递归调度
"""
</code></pre>
<p><strong>其他方法</strong>:</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>cleanup_expired_sessions()</code></td>
<td>清理过期独立游戏会话</td>
</tr>
<tr>
<td><code>broadcast(room_id, message)</code></td>
<td>广播消息到房间</td>
</tr>
<tr>
<td><code>broadcast_user_list_update()</code></td>
<td>广播用户列表更新</td>
</tr>
<tr>
<td><code>get_online_users()</code></td>
<td>获取在线用户列表</td>
</tr>
<tr>
<td><code>get_online_users_with_status()</code></td>
<td>获取带状态的在线用户列表</td>
</tr>
<tr>
<td><code>generate_room_id()</code></td>
<td>生成房间ID</td>
</tr>
<tr>
<td><code>is_user_playing(username)</code></td>
<td>检查用户是否在对局中</td>
</tr>
<tr>
<td><code>cancel_ai_task(room_id)</code></td>
<td>取消AI任务</td>
</tr>
<tr>
<td><code>rejoin_users_to_online(room_id)</code></td>
<td>将对局用户重新加入大厅</td>
</tr>
</tbody>
</table>
<p><strong>设计模式</strong>: 单例模式 + 数据管理中心模式</p>
<hr>
<h3 id="23-corestatepy---游戏状态引擎">2.3 core/state.py - 游戏状态引擎</h3>
<h4 id="231-文件概述">2.3.1 文件概述</h4>
<p><strong>文件路径</strong>: <code>core/state.py</code><br>
<strong>代码行数</strong>: ~3534行<br>
<strong>主要职责</strong>: 围棋游戏核心逻辑、落子规则、提子算法、终局计分、AI集成、SGF导入导出、打谱模式</p>
<p><strong>核心类</strong>:</p>
<ol>
<li><code>GameState</code>: 游戏状态主类</li>
</ol>
<h4 id="232-gamestate类结构">2.3.2 GameState类结构</h4>
<pre><code class="language-python"># core/state.py
class GameState:
def __init__(self, board_size=19, enable_analysis=False):
# ========== 棋盘状态 ==========
self.board_size = board_size
self.black_turn = True # 当前回合(True=黑方)
self.board = [] # 棋盘状态:0=空, 1=黑, 2=白
self.move_numbers = [] # 落子序号(用于显示步数)
self.last_move = None # 最后一手 {row, col, stone} 或 {stone, pass}
self.ko_point = None # 劫点 {row, col}
# ========== 提子统计 ==========
self.black_captured = 0 # 黑方提子数
self.white_captured = 0 # 白方提子数
self.current_move_number = 0 # 当前手数
# ========== 地盘分析 ==========
self.territory_board = None # 地盘归属:0=中立, 1=黑, 2=白
self.show_territory = False # 是否显示地盘
# ========== 历史记录(支持悔棋)==========
self.move_history = [] # 落子历史
self.board_history = [] # 棋盘快照历史
self.ko_history = [] # 劫点历史
self.captured_history = [] # 提子历史
# ========== AI相关 ==========
self.ai_enabled = False # AI是否启用
self.ai_playing = False # AI是否正在思考
self.ai_color = 2 # AI执子颜色(1=黑, 2=白)
self.goai = None # GoAI实例(延迟创建)
# ========== SGF节点树 ==========
self.root_node = GameNode(parent=None)
self.current_node = self.root_node
# ========== 终局结果 ==========
self.game_over = False
self.winner = None # 1=黑胜, 2=白胜
self.final_score = None # 如 "B+12.5"
# ========== KataGo引擎 ==========
self.katago = None
self.katago_game = None
self.enable_analysis = enable_analysis
</code></pre>
<h4 id="233-主要方法分组">2.3.3 主要方法分组</h4>
<table>
<thead>
<tr>
<th>功能组</th>
<th>方法</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>初始化/重置</strong></td>
<td><code>initialize_board()</code>, <code>save_game_state()</code></td>
</tr>
<tr>
<td><strong>落子/提子</strong></td>
<td><code>place_stone(row, col)</code>, <code>pass_turn()</code>, <code>undo_move()</code>, <code>is_valid_move()</code>, <code>_get_captured_stones()</code>, <code>_check_ko()</code></td>
</tr>
<tr>
<td><strong>终局计分</strong></td>
<td><code>check_game_over()</code>, <code>calculate_final_score()</code>, <code>end_game_manually()</code>, <code>_fallback_score()</code>, <code>analyze_territory()</code></td>
</tr>
<tr>
<td><strong>Benson算法</strong></td>
<td><code>_benson_alive_groups()</code>, <code>_find_alive_groups()</code>, <code>identify_alive_groups()</code></td>
</tr>
<tr>
<td><strong>死子识别</strong></td>
<td><code>_find_dead_stones_enhanced()</code>, <code>_is_group_dead()</code>, <code>_check_influence_strength()</code>, <code>_is_big_eye_alive()</code>, <code>_is_special_dead_eye()</code>, <code>_is_true_eye()</code>, <code>_check_opponent_also_dead()</code></td>
</tr>
<tr>
<td><strong>辅助判断</strong></td>
<td><code>_analyze_capture_race()</code>, <code>_calc_escape_potential()</code>, <code>_count_eye_potential()</code>, <code>_calc_liberty_enclosure()</code>, <code>_calc_opponent_strength_ratio()</code>, <code>_is_point_in_opponent_territory()</code></td>
</tr>
<tr>
<td><strong>KataGo集成</strong></td>
<td><code>_init_analysis_engine()</code>, <code>_ensure_analysis_engine()</code>, <code>release_analysis_engine()</code>, <code>_sync_katago_game()</code>, <code>_query_katago_ownership()</code>, <code>_find_dead_stones_by_katago()</code>, <code>_calculate_score_by_katago()</code></td>
</tr>
<tr>
<td><strong>点目分析</strong></td>
<td><code>calculate_territory_smart()</code>, <code>_calculate_territory_heuristic()</code>, <code>_calculate_territory_endgame_style()</code>, <code>_calculate_territory_heuristic_with_verification()</code></td>
</tr>
<tr>
<td><strong>AI</strong></td>
<td><code>enable_ai()</code>, <code>disable_ai()</code>, <code>is_ai_turn()</code>, <code>get_ai_move()</code>, <code>make_ai_move()</code></td>
</tr>
<tr>
<td><strong>SGF</strong></td>
<td><code>export_sgf()</code>, <code>import_sgf()</code></td>
</tr>
<tr>
<td><strong>打谱</strong></td>
<td><code>enter_replay_mode()</code>, <code>replay_next()</code>, <code>replay_prev()</code>, <code>exit_replay_mode()</code></td>
</tr>
<tr>
<td><strong>其他</strong></td>
<td><code>cleanup()</code>, <code>clear_territory()</code>, <code>update_status()</code>, <code>analyze_position()</code>, <code>get_game_phase()</code></td>
</tr>
</tbody>
</table>
<h4 id="234-落子合法性检查">2.3.4 落子合法性检查</h4>
<pre><code class="language-python"># core/state.py
def is_valid_move(self, row, col, stone):
"""验证落子合法性
检查步骤:
1. 边界检查
2. 空位检查
3. 劫争检查
4. 模拟落子,检查是否有气或能提子
"""
</code></pre>
<p><strong>检查步骤</strong>:</p>
<ol>
<li><strong>边界检查</strong>: 确保坐标在棋盘范围内(0-18)</li>
<li><strong>空位检查</strong>: 确保目标位置为空</li>
<li><strong>劫争检查</strong>: 确保不是劫点(禁止立即回提)</li>
<li><strong>自杀检查</strong>: 模拟落子,检查是否有气或能提子</li>
</ol>
<h4 id="235-提子算法">2.3.5 提子算法</h4>
<pre><code class="language-python"># core/state.py
def _get_captured_stones(self, row, col, stone):
"""获取落子后能提掉的对方棋子
算法:
1. 检查四个方向的相邻对方棋子
2. 获取连通块
3. 检查该连通块是否有气
4. 如果无气,加入提子列表
"""
</code></pre>
<h4 id="236-连通块与气计算">2.3.6 连通块与气计算</h4>
<pre><code class="language-python"># core/state.py
def _get_group(self, row, col):
"""获取指定位置的连通块(使用DFS)"""
def _count_group_liberties(self, row, col):
"""计算指定位置连通块的气数(DFS遍历连通块,统计周围空位数量)"""
</code></pre>
<h4 id="237-劫争检测">2.3.7 劫争检测</h4>
<pre><code class="language-python"># core/state.py
def _check_ko(self, row, col, stone, captured_stones):
"""检测是否形成劫争
劫争条件:
1. 只提掉一颗子
2. 对方立即回提也能提掉一颗子(且是刚才落下的子)
"""
</code></pre>
<h4 id="238-落子执行">2.3.8 落子执行</h4>
<pre><code class="language-python"># core/state.py
def place_stone(self, row, col):
"""执行落子
流程:
1. 检查游戏是否结束
2. 验证落子合法性
3. 放置棋子
4. 提子
5. 检查劫争
6. 保存历史状态
7. 切换回合
8. 检查终局
"""
</code></pre>
<hr>
<h3 id="24-coregoaipy---ai引擎封装">2.4 core/goai.py - AI引擎封装</h3>
<h4 id="241-文件概述">2.4.1 文件概述</h4>
<p><strong>文件路径</strong>: <code>core/goai.py</code><br>
<strong>主要职责</strong>: KataGo AI引擎封装、AI落子决策、策略选择</p>
<p><strong>核心类</strong>:</p>
<ol>
<li><code>GoAI</code>: AI引擎主类</li>
</ol>
<h4 id="242-goai类结构">2.4.2 GoAI类结构</h4>
<pre><code class="language-python"># core/goai.py
class GoAI:
def __init__(self, game_state):
"""AI引擎初始化"""
self.game_state = game_state
self.board_size = game_state.board_size
self.katago = CallableKatagoWrapper()
self.katago.start()
self.last_move_count = 0# 缓存,避免重复重放
</code></pre>
<h4 id="243-ai落子主流程">2.4.3 AI落子主流程</h4>
<pre><code class="language-python"># core/goai.py
def get_best_move(self):
"""获取AI推荐落子
流程:
1. 检查提子机会(>=3子直接提)
2. 获取所有合法落子
3. 调用KataGo引擎分析
4. 返回推荐落子
"""
</code></pre>
<p><strong>性能优化</strong>: 如果能提掉>=3子,直接提子,无需引擎分析。</p>
<h4 id="244-katago引擎调用">2.4.4 KataGo引擎调用</h4>
<pre><code class="language-python"># core/goai.py
def _ai_move(self, legal_moves, stone):
"""高难度AI落子:使用KataGo引擎
流程:
1. 重置Katago游戏
2. 重放历史记录(禁用分析以提高性能)
3. 触发当前节点分析
4. 使用策略生成落子
5. 处理特殊情况(人类pass后AI不应pass)
6. 验证合法性
7. 降级:随机落子
"""
</code></pre>
<hr>
<h3 id="25-coreauthpy---认证与vip系统">2.5 core/auth.py - 认证与VIP系统</h3>
<h4 id="251-文件概述">2.5.1 文件概述</h4>
<p><strong>文件路径</strong>: <code>core/auth.py</code><br>
<strong>代码行数</strong>: ~829行<br>
<strong>主要职责</strong>: 用户认证、JWT令牌、VIP会员、数据库操作</p>
<h4 id="252-数据库表结构">2.5.2 数据库表结构</h4>
<table>
<thead>
<tr>
<th>表名</th>
<th>字段</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>users</code></td>
<td>id, username, hashed_password, score, rank, role, email, reset_token, reset_token_expires, is_vip, vip_expire_at, created_at</td>
<td>用户表</td>
</tr>
<tr>
<td><code>games</code></td>
<td>id, black_player, white_player, winner, final_score, sgf_content, end_reason, created_at</td>
<td>棋谱表</td>
</tr>
<tr>
<td><code>vip_orders</code></td>
<td>id, username, plan_type, amount, status, trade_no, transaction_id, created_at, paid_at</td>
<td>VIP订单表</td>
</tr>
<tr>
<td><code>vip_settings</code></td>
<td>key, value, description</td>
<td>VIP功能开关表</td>
</tr>
<tr>
<td><code>doc_markdown_files</code></td>
<td>id, filename, content, uploaded_by, approved, created_at</td>
<td>文档表</td>
</tr>
</tbody>
</table>
<h4 id="253-vip套餐配置">2.5.3 VIP套餐配置</h4>
<pre><code class="language-python"># core/auth.py
VIP_PLANS = {
"monthly": {"name": "月度VIP", "price": 3000, "days": 30}, # 30元/月
"yearly": {"name": "年度VIP", "price": 28800, "days": 365} # 288元/年
}
FREE_USER_DAILY_AI_LIMIT = 3# 非VIP用户每日AI对弈限制
</code></pre>
<h4 id="254-主要函数">2.5.4 主要函数</h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>init_db()</code></td>
<td>初始化数据库表</td>
</tr>
<tr>
<td><code>verify_password()</code>, <code>get_password_hash()</code></td>
<td>密码验证和哈希</td>
</tr>
<tr>
<td><code>get_user()</code>, <code>create_user()</code>, <code>delete_user()</code></td>
<td>用户CRUD</td>
</tr>
<tr>
<td><code>update_user_score()</code>, <code>get_user_rank_info()</code></td>
<td>积分和段位</td>
</tr>
<tr>
<td><code>save_game()</code>, <code>get_user_games()</code>, <code>get_game_sgf()</code></td>
<td>棋谱存储和查询</td>
</tr>
<tr>
<td><code>create_access_token()</code>, <code>decode_token()</code></td>
<td>JWT令牌</td>
</tr>
<tr>
<td><code>is_admin()</code>, <code>is_vip_or_admin()</code></td>
<td>权限检查</td>
</tr>
<tr>
<td><code>is_user_vip()</code>, <code>get_user_vip_info()</code>, <code>activate_vip()</code></td>
<td>VIP状态管理</td>
</tr>
<tr>
<td><code>create_vip_order()</code>, <code>complete_vip_order()</code>, <code>get_vip_order_by_trade_no()</code></td>
<td>VIP订单</td>
</tr>
<tr>
<td><code>get_vip_settings()</code>, <code>get_vip_setting()</code>, <code>set_vip_setting()</code></td>
<td>VIP功能开关</td>
</tr>
<tr>
<td><code>upload_doc_markdown()</code>, <code>get_doc_markdown_files()</code>, <code>get_doc_markdown_content()</code>, <code>delete_doc_markdown()</code>, <code>approve_doc_markdown()</code></td>
<td>文档Markdown管理</td>
</tr>
</tbody>
</table>
<h4 id="255-jwt认证">2.5.5 JWT认证</h4>
<pre><code class="language-python"># core/auth.py
def create_access_token(data: dict, expires_delta: timedelta = None):
"""创建JWT令牌(默认24小时过期)"""
def decode_token(token: str):
"""解码JWT令牌,返回用户名或None"""
</code></pre>
<p><strong>JWT结构</strong>:</p>
<pre><code class="language-json">{
"sub": "username",
"exp": 1234567890
}
</code></pre>
<hr>
<h3 id="26-routers---路由模块">2.6 routers/ - 路由模块</h3>
<h4 id="261-概述">2.6.1 概述</h4>
<p>路由模块从原 <code>main.py</code> 中拆分,按功能域组织为5个独立模块,每个模块使用 <code>APIRouter</code>。</p>
<h4 id="262-routersdepspy---依赖注入">2.6.2 routers/deps.py - 依赖注入</h4>
<pre><code class="language-python"># routers/deps.py
def get_current_user(token: str) -> str:
"""获取当前用户,验证token有效性"""
def require_admin(token: str) -> str:
"""要求管理员权限"""
def check_vip_feature(token: str, feature_key: str) -> None:
"""检查VIP功能权限"""
</code></pre>
<p><strong>设计模式</strong>: 依赖注入模式,统一token验证和权限检查逻辑,避免各路由重复代码。</p>
<h4 id="263-routersauthpy---认证路由">2.6.3 routers/auth.py - 认证路由</h4>
<p><strong>前缀</strong>: <code>/api</code><br>
<strong>标签</strong>: <code>auth</code></p>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST</td>
<td><code>/register</code></td>
<td>用户注册</td>
</tr>
<tr>
<td>POST</td>
<td><code>/login</code></td>
<td>用户登录</td>
</tr>
<tr>
<td>POST</td>
<td><code>/change-password</code></td>
<td>修改密码</td>
</tr>
<tr>
<td>POST</td>
<td><code>/update-email</code></td>
<td>更新邮箱</td>
</tr>
<tr>
<td>GET</td>
<td><code>/user-email</code></td>
<td>获取用户邮箱</td>
</tr>
<tr>
<td>POST</td>
<td><code>/request-reset-password</code></td>
<td>请求重置密码(发送邮件)</td>
</tr>
</tbody>
</table>
<h4 id="264-routersvippy---vip路由">2.6.4 routers/vip.py - VIP路由</h4>
<p><strong>前缀</strong>: <code>/api/vip</code><br>
<strong>标签</strong>: <code>vip</code></p>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/info</code></td>
<td>获取VIP信息</td>
</tr>
<tr>
<td>POST</td>
<td><code>/create-order</code></td>
<td>创建VIP购买订单</td>
</tr>
<tr>
<td>GET</td>
<td><code>/order-status</code></td>
<td>查询订单支付状态</td>
</tr>
<tr>
<td>GET</td>
<td><code>/orders</code></td>
<td>获取用户订单列表</td>
</tr>
<tr>
<td>POST</td>
<td><code>/notify</code></td>
<td>微信支付回调通知</td>
</tr>
<tr>
<td>POST</td>
<td><code>/activate-test</code></td>
<td>测试用:直接激活VIP</td>
</tr>
<tr>
<td>GET</td>
<td><code>/settings</code></td>
<td>获取VIP功能开关(管理员)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/settings</code></td>
<td>更新VIP功能开关(管理员)</td>
</tr>
<tr>
<td>GET</td>
<td><code>/settings/public</code></td>
<td>获取VIP功能开关公开状态</td>
</tr>
</tbody>
</table>
<h4 id="265-routersadminpy---管理路由">2.6.5 routers/admin.py - 管理路由</h4>
<p><strong>前缀</strong>: <code>/api/admin</code><br>
<strong>标签</strong>: <code>admin</code></p>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/users</code></td>
<td>获取所有用户列表</td>
</tr>
<tr>
<td>PUT</td>
<td><code>/users/{username}/role</code></td>
<td>修改用户角色</td>
</tr>
<tr>
<td>DELETE</td>
<td><code>/users/{username}</code></td>
<td>删除用户</td>
</tr>
<tr>
<td>GET</td>
<td><code>/games</code></td>
<td>获取所有棋谱列表</td>
</tr>
<tr>
<td>DELETE</td>
<td><code>/games/{id}</code></td>
<td>删除棋谱</td>
</tr>
<tr>
<td>POST</td>
<td><code>/init</code></td>
<td>初始化管理员账户</td>
</tr>
</tbody>
</table>
<h4 id="266-routersgamepy---游戏路由">2.6.6 routers/game.py - 游戏路由</h4>
<p><strong>前缀</strong>: <code>/api</code><br>
<strong>标签</strong>: <code>game</code></p>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/my-games</code></td>
<td>获取用户历史棋谱</td>
</tr>
<tr>
<td>GET</td>
<td><code>/user-games/{username}</code></td>
<td>获取指定用户的棋谱</td>
</tr>
<tr>
<td>GET</td>
<td><code>/game-sgf/{id}</code></td>
<td>获取棋谱SGF内容</td>
</tr>
</tbody>
</table>
<h4 id="267-routersdocpy---文档路由">2.6.7 routers/doc.py - 文档路由</h4>
<p><strong>前缀</strong>: <code>/api/doc</code><br>
<strong>标签</strong>: <code>doc</code></p>
<table>
<thead>
<tr>
<th>方法</th>
<th>路径</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST</td>
<td><code>/markdown/upload</code></td>
<td>上传Markdown文件</td>
</tr>
<tr>
<td>GET</td>
<td><code>/markdown/files</code></td>
<td>获取Markdown文件列表</td>
</tr>
<tr>
<td>GET</td>
<td><code>/markdown/{id}</code></td>
<td>获取单个Markdown文件内容</td>
</tr>
<tr>
<td>DELETE</td>
<td><code>/markdown/{id}</code></td>
<td>删除Markdown文件(管理员)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/markdown/{id}/approve</code></td>
<td>审核Markdown文件(管理员)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/markdown/{id}/unapprove</code></td>
<td>取消审核(管理员)</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="27-staticscriptjs---前端核心逻辑">2.7 static/script.js - 前端核心逻辑</h3>
<h4 id="271-文件概述">2.7.1 文件概述</h4>
<p><strong>文件路径</strong>: <code>static/script.js</code><br>
<strong>代码行数</strong>: ~4034行<br>
<strong>主要职责</strong>: 前端核心逻辑、棋盘渲染、WebSocket通信、UI交互</p>
<p><strong>核心功能</strong>:</p>
<ol>
<li>棋盘Canvas渲染</li>
<li>WebSocket通信</li>
<li>用户交互(落子、pass、认输等)</li>
<li>国际化(i18n)</li>
<li>模态框管理</li>
<li>视图切换</li>
</ol>
<h4 id="272-国际化配置">2.7.2 国际化配置</h4>
<pre><code class="language-javascript">// static/script.js
let currentLang = localStorage.getItem('lang') || 'zh';
const i18n = {
menu_home: { zh: '主页', en: 'Home' },
menu_game: { zh: '围棋', en: 'Go' },
// ... 更多翻译
};
function t(key, args = {}) {
"""翻译函数,支持模板参数替换"""
let text = i18n?. || key;
for (const of Object.entries(args)) {
text = text.replace(`{${k}}`, v);
}
return text;
}
</code></pre>
<h4 id="273-websocket连接">2.7.3 WebSocket连接</h4>
<pre><code class="language-javascript">// static/script.js
function connectWebSocket(roomId) {
const token = localStorage.getItem('token');
const wsUrl = `${wsProtocol}://${window.location.host}/ws/${roomId}?token=${token}`;
ws = new WebSocket(wsUrl);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
handleMessage(data);
};
}
function handleMessage(data) {
switch(data.type) {
case 'assign_color': ...break;
case 'state_update': ...break;
case 'game_over': ...break;
case 'move_timeout': ...break;
// ... 更多消息类型
}
}
</code></pre>
<h4 id="274-棋盘渲染">2.7.4 棋盘渲染</h4>
<pre><code class="language-javascript">// static/script.js
function renderBoard() {
// 清空画布
// 绘制棋盘线(19x19)
// 绘制星位(9个星位点)
// 绘制棋子(径向渐变,更真实)
// 绘制最后一手标记
// 绘制步数(如果启用)
}
function drawStone(ctx, row, col, stone) {
// 绘制阴影(偏移2像素)
// 黑子:深灰到黑色渐变
// 白子:白色到浅灰渐变
}
</code></pre>
<h4 id="275-模板加载">2.7.5 模板加载</h4>
<pre><code class="language-javascript">// static/template-loader.js
// 异步加载7个视图HTML模板,注入DOM插槽,触发templatesLoaded事件
</code></pre>
<hr>
<h2 id="3-websocket通信协议">3. WebSocket通信协议</h2>
<h3 id="31-连接流程">3.1 连接流程</h3>
<pre><code>客户端 服务端
│ │
│──── WebSocket连接请求 ──────────→│
│ /ws/{room_id}?token=xxx │
│ │
│←─── 验证token,调用join_room() ──│
│ │
│←───── assign_color ─────────────│分配颜色
│ {type, color, username} │
│ │
│──── color_ack ─────────────────→│确认颜色
│ {type, color} │
│ │
│←───── state_update ─────────────│发送当前状态
│ {type, state} │
│ │
│ 进入消息循环 │
</code></pre>
<h3 id="32-客户端消息类型">3.2 客户端消息类型</h3>
<table>
<thead>
<tr>
<th>消息类型</th>
<th>数据格式</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>color_ack</code></td>
<td><code>{type, color}</code></td>
<td>确认颜色分配</td>
</tr>
<tr>
<td><code>move</code></td>
<td><code>{type, x, y, color}</code></td>
<td>落子</td>
</tr>
<tr>
<td><code>pass</code></td>
<td><code>{type, color}</code></td>
<td>跳过</td>
</tr>
<tr>
<td><code>resign</code></td>
<td><code>{type, color}</code></td>
<td>认输</td>
</tr>
<tr>
<td><code>undo</code></td>
<td><code>{type}</code></td>
<td>AI对弈悔棋</td>
</tr>
<tr>
<td><code>undo_request</code></td>
<td><code>{type}</code></td>
<td>请求悔棋(人对弈)</td>
</tr>
<tr>
<td><code>undo_accept</code></td>
<td><code>{type}</code></td>
<td>同意悔棋</td>
</tr>
<tr>
<td><code>undo_reject</code></td>
<td><code>{type}</code></td>
<td>拒绝悔棋</td>
</tr>
<tr>
<td><code>analyze</code></td>
<td><code>{type}</code></td>
<td>点目分析</td>
</tr>
<tr>
<td><code>end_game</code></td>
<td><code>{type}</code></td>
<td>请求结束对局</td>
</tr>
<tr>
<td><code>end_game_confirm</code></td>
<td><code>{type}</code></td>
<td>确认结束对局</td>
</tr>
<tr>
<td><code>end_game_reject</code></td>
<td><code>{type}</code></td>
<td>拒绝结束对局</td>
</tr>
<tr>
<td><code>export_sgf</code></td>
<td><code>{type}</code></td>
<td>导出SGF</td>
</tr>
<tr>
<td><code>import_sgf</code></td>
<td><code>{type, sgf_content}</code></td>
<td>导入SGF</td>
</tr>
<tr>
<td><code>enter_replay</code></td>
<td><code>{type}</code></td>
<td>进入打谱模式</td>
</tr>
<tr>
<td><code>replay_next</code></td>
<td><code>{type}</code></td>
<td>打谱下一步</td>
</tr>
<tr>
<td><code>replay_prev</code></td>
<td><code>{type}</code></td>
<td>打谱上一步</td>
</tr>
<tr>
<td><code>exit_replay</code></td>
<td><code>{type}</code></td>
<td>退出打谱模式</td>
</tr>
<tr>
<td><code>ping</code></td>
<td><code>{type}</code></td>
<td>心跳</td>
</tr>
</tbody>
</table>
<h3 id="33-服务端消息类型">3.3 服务端消息类型</h3>
<table>
<thead>
<tr>
<th>消息类型</th>
<th>数据格式</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>assign_color</code></td>
<td><code>{type, color, username}</code></td>
<td>分配颜色</td>
</tr>
<tr>
<td><code>waiting</code></td>
<td><code>{type, message}</code></td>
<td>等待对手</td>
</tr>
<tr>
<td><code>game_start</code></td>
<td><code>{type, state}</code></td>
<td>游戏开始</td>
</tr>
<tr>
<td><code>state_update</code></td>
<td><code>{type, state}</code></td>
<td>状态更新</td>
</tr>
<tr>
<td><code>game_over</code></td>
<td><code>{type, message, winner, score, state}</code></td>
<td>游戏结束</td>
</tr>
<tr>
<td><code>error</code></td>
<td><code>{type, message}</code></td>
<td>错误消息</td>
</tr>
<tr>
<td><code>analysis_result</code></td>
<td><code>{type, data}</code></td>
<td>分析结果</td>
</tr>
<tr>
<td><code>move_timeout</code></td>
<td><code>{type, player, timeout_count, max_timeout, message}</code></td>
<td>落子超时</td>
</tr>
<tr>
<td><code>timeout_loss</code></td>
<td><code>{type, winner, final_score, message, state}</code></td>
<td>三次超时判负</td>
</tr>
<tr>
<td><code>opponent_left</code></td>
<td><code>{type, message}</code></td>
<td>对手离开</td>
</tr>
<tr>
<td><code>opponent_reconnected</code></td>
<td><code>{type, message}</code></td>
<td>对手重连</td>
</tr>
<tr>
<td><code>opponent_disconnected_temporary</code></td>
<td><code>{type, message, timeout}</code></td>
<td>对手暂时断线</td>
</tr>
<tr>
<td><code>opponent_disconnected</code></td>
<td><code>{type, message, winner, final_score, state}</code></td>
<td>对手断线认输</td>
</tr>
<tr>
<td><code>invitation</code></td>
<td><code>{type, invite_id, from, to, room_id}</code></td>
<td>邀请通知</td>
</tr>
<tr>
<td><code>invitation_accepted</code></td>
<td><code>{type, invite_id, to, room_id}</code></td>
<td>邀请被接受</td>
</tr>
<tr>
<td><code>invitation_rejected</code></td>
<td><code>{type, invite_id, to}</code></td>
<td>邀请被拒绝</td>
</tr>
<tr>
<td><code>user_list_update</code></td>
<td><code>{type, users}</code></td>
<td>用户列表更新</td>
</tr>
<tr>
<td><code>end_game_request</code></td>
<td><code>{type, from, message, result, winner, final_score}</code></td>
<td>结束对局请求</td>
</tr>
<tr>
<td><code>end_game_rejected</code></td>
<td><code>{type, message}</code></td>
<td>结束对局被拒绝</td>
</tr>
<tr>
<td><code>undo_request</code></td>
<td><code>{type, from}</code></td>
<td>悔棋请求</td>
</tr>
<tr>
<td><code>undo_accepted</code></td>
<td><code>{type, state}</code></td>
<td>悔棋被接受</td>
</tr>
<tr>
<td><code>undo_rejected</code></td>
<td><code>{type, message}</code></td>
<td>悔棋被拒绝</td>
</tr>
<tr>
<td><code>sgf_export_result</code></td>
<td><code>{type, data}</code></td>
<td>SGF导出结果</td>
</tr>
<tr>
<td><code>sgf_import_result</code></td>
<td><code>{type, data}</code></td>
<td>SGF导入结果</td>
</tr>
<tr>
<td><code>replay_result</code></td>
<td><code>{type, data}</code></td>
<td>打谱结果</td>
</tr>
<tr>
<td><code>lobby_joined</code></td>
<td><code>{type, message}</code></td>
<td>大厅加入成功</td>
</tr>
<tr>
<td><code>pong</code></td>
<td><code>{type}</code></td>
<td>心跳回复</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="4-katago引擎集成">4. Katago引擎集成</h2>
<h3 id="41-katago架构">4.1 Katago架构</h3>
<pre><code>GoAI (goai.py)
│
├── CallableKatagoWrapper (KatagoBase)
│ │
│ ├── KataGoEngine (engine.py)
│ │ │
│ │ ├── katago_process (子进程)
│ │ ├── queries (查询队列)
│ │ └── 3个守护线程
│ │ ├── _analysis_read_thread (读取stdout)
│ │ ├── _read_stderr_thread (读取stderr)
│ │ └── _write_stdin_thread (写入stdin)
│ │
│ └── Game (game.py)
│ │
│ ├── root (GameNode)
│ ├── current_node (GameNode)
│ └── engines (Dict)
│
└── STRATEGY_REGISTRY (ai.py)
│
├── ai:default (DefaultStrategy)
├── ai:handicap (HandicapStrategy)
├── ai:human (HumanStyleStrategy)
└── ... (15+种策略)
</code></pre>
<h3 id="42-katagoengine通信协议">4.2 KataGoEngine通信协议</h3>
<pre><code class="language-python"># core/engine.py
class KataGoEngine(BaseEngine):
def start(self):
"""启动KataGo子进程"""
cmd = [
self.katago_path, "analysis",
"-model", self.model_path,
"-config", self.config_path,
"-analysis-threads", str(self.num_threads),
]
self.katago_process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 启动3个守护线程
threading.Thread(target=self._analysis_read_thread, daemon=True).start()
threading.Thread(target=self._read_stderr_thread, daemon=True).start()
threading.Thread(target=self._write_stdin_thread, daemon=True).start()
def request_analysis(self, node, callback, ...):
"""请求分析"""
query = {
"id": str(uuid.uuid4()),
"rules": "chinese", "komi": 7.5,
"boardXSize": 19, "boardYSize": 19,
"moves": [...], "maxVisits": 200,
"includePolicy": True, "includeOwnership": True,
}
self.send_query(query, callback)
</code></pre>
<p><strong>KataGo查询示例</strong>:</p>
<pre><code class="language-json">{
"id": "query-123",
"rules": "chinese",
"komi": 7.5,
"boardXSize": 19,
"boardYSize": 19,
"moves": [["B", "Q16"], ["W", "D4"], ["B", "Q3"]],
"maxVisits": 200,
"includePolicy": true,
"includeOwnership": true
}
</code></pre>
<p><strong>KataGo响应示例</strong>:</p>
<pre><code class="language-json">{
"id": "query-123",
"turnNumber": 3,
"rootInfo": {"winrate": 0.52, "scoreLead": 1.3, "visits": 200},
"moveInfos": [
{"move": "Q4", "winrate": 0.55, "scoreLead": 2.1, "visits": 150},
{"move": "D16", "winrate": 0.53, "scoreLead": 1.8, "visits": 30}
],
"policy": ,
"ownership":
}
</code></pre>
<h3 id="43-ai策略系统">4.3 AI策略系统</h3>
<pre><code class="language-python"># core/ai.py
STRATEGY_REGISTRY = {}
def register_strategy(strategy_name):
"""策略注册装饰器"""
def decorator(strategy_class):
STRATEGY_REGISTRY = strategy_class
return strategy_class
return decorator
@register_strategy("ai:default")
class DefaultStrategy(AIStrategy):
"""默认策略:选择引擎推荐的首选"""
def generate_move(self):
self.wait_for_analysis()
moves = self.cn.candidate_moves
if moves:
return Move.from_gtp(moves["move"], self.cn.next_player), "Best move"
return Move.pass_move(), "Pass"
</code></pre>
<p><strong>内置策略列表</strong>:</p>
<table>
<thead>
<tr>
<th>策略名</th>
<th>说明</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ai:default</code></td>
<td>默认策略</td>
<td>专业对弈</td>
</tr>
<tr>
<td><code>ai:handicap</code></td>
<td>让子策略</td>
<td>让子局</td>
</tr>
<tr>
<td><code>ai:jigo</code></td>
<td>和棋策略</td>
<td>目标分数0.5</td>
</tr>
<tr>
<td><code>ai:scoreloss</code></td>
<td>分损策略</td>
<td>教学训练</td>
</tr>
<tr>
<td><code>ai:p:weighted</code></td>
<td>加权策略</td>
<td>可调难度</td>
</tr>
<tr>
<td><code>ai:p:pick</code></td>
<td>选取策略</td>
<td>变化探索</td>
</tr>
<tr>
<td><code>ai:human</code></td>
<td>人类风格</td>
<td>教学演示</td>
</tr>
<tr>
<td><code>ai:p:local</code></td>
<td>局部策略</td>
<td>局部训练</td>
</tr>
<tr>
<td><code>ai:p:tenuki</code></td>
<td>脱先策略</td>
<td>战术训练</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="5-关键算法实现">5. 关键算法实现</h2>
<h3 id="51-终局计分算法benson算法">5.1 终局计分算法(Benson算法)</h3>
<pre><code class="language-python"># core/state.py
def calculate_final_score(self):
"""终局精确点目
算法:
1. 识别活棋(Benson算法:2+真眼=无条件活)
2. 标记死子并移除
3. 洪水填充计算空点归属
4. 中国规则计分:子空皆地
"""
</code></pre>
<p><strong>算法流程</strong>:</p>
<ol>
<li><strong>识别活棋</strong>: 使用Benson算法(2+真眼=无条件活)</li>
<li><strong>标记死子</strong>: 不在活棋组中的棋子标记为死子</li>
<li><strong>洪水填充</strong>: 计算空点归属(只与一种颜色相邻的空点属于该颜色)</li>
<li><strong>中国规则计分</strong>: 子空皆地 + 贴目(7.5目)</li>
</ol>
<h3 id="52-中盘形势分析">5.2 中盘形势分析</h3>
<pre><code class="language-python"># core/state.py
def calculate_territory_smart(self):
"""中盘形势分析
算法:
- 终局阶段:使用严格死子识别
- 布局/中盘:使用影响力方法(8格范围,非线性衰减)
"""
</code></pre>
<p><strong>影响力方法</strong>:</p>
<ul>
<li><strong>扩散范围</strong>: 8格</li>
<li><strong>衰减函数</strong>: <code>1.0 / (1.0 + dist)</code>(非线性)</li>
<li><strong>阈值</strong>: 0.3(影响力>0.3为黑,<-0.3为白)</li>
</ul>
<h3 id="53-katago增强死子识别">5.3 KataGo增强死子识别</h3>
<pre><code class="language-python"># core/state.py
def _find_dead_stones_by_katago(self):
"""使用KataGo的ownership数据识别死子
优势:比纯规则方法更准确,特别是在复杂局面中
"""
def _calculate_score_by_katago(self):
"""使用KataGo的ownership数据计算得分"""
</code></pre>
<hr>
<h2 id="6-部署指南">6. 部署指南</h2>
<h3 id="61-服务器配置">6.1 服务器配置</h3>
<table>
<thead>
<tr>
<th>配置项</th>
<th>推荐值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>操作系统</td>
<td>Ubuntu 22.04/20.04</td>
<td>LTS版本</td>
</tr>
<tr>
<td>CPU</td>
<td>4核+</td>
<td>AI计算需求</td>
</tr>
<tr>
<td>内存</td>
<td>8GB+</td>
<td>KataGo内存占用</td>
</tr>
<tr>
<td>网络</td>
<td>弹性公网IP</td>
<td>公网访问</td>
</tr>
<tr>
<td>安全组</td>
<td>22, 80, 443</td>
<td>SSH, HTTP, HTTPS</td>
</tr>
</tbody>
</table>
<h3 id="62-部署架构">6.2 部署架构</h3>
<pre><code>┌─────────────┐
│ Nginx │(反向代理, HTTPS, 静态文件)
│ :443 │
└─────────────┘
↓
┌─────────────┐
│Gunicorn │(进程管理, 多worker)
│ :unix │
└─────────────┘
↓
┌─────────────┐
│Uvicorn │(ASGI服务器, 异步处理)
│FastAPI │
└─────────────┘
↓
┌─────────────┐
│ KataGo │(AI引擎进程)
│ Engine │
└─────────────┘
</code></pre>
<h3 id="63-gunicorn配置">6.3 Gunicorn配置</h3>
<pre><code class="language-bash">#!/bin/bash
# gunicorn_start.sh
NAME="x01.weiqi"
DIR=/opt/x01.weiqi
WORKERS=3
BIND=unix:/opt/x01.weiqi/app.sock
WORKER_CLASS=uvicorn.workers.UvicornWorker
cd $DIR
source .venv/bin/activate
export PYTHONPATH=$DIR:$PYTHONPATH
exec .venv/bin/gunicorn main:app \
--name $NAME \
--workers $WORKERS \
--bind=$BIND \
--worker-class $WORKER_CLASS \
--timeout 1200
</code></pre>
<h3 id="64-nginx配置">6.4 Nginx配置</h3>
<pre><code class="language-nginx">server {
listen 80;
server_name your-domain.com;
location /static/ {
alias /opt/x01.weiqi/static/;
}
location / {
proxy_pass http://unix:/opt/x01.weiqi/app.sock;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
</code></pre>
<h3 id="65-环境变量配置">6.5 环境变量配置</h3>
<pre><code class="language-bash"># .env
# JWT配置
JWT_SECRET_KEY=your-secret-key
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=24
# SMTP配置
SMTP_SERVER=smtp.qq.com
SMTP_PORT=587
SMTP_USERNAME=your-email@qq.com
SMTP_PASSWORD=your-smtp-password
# 微信支付配置
WECHAT_PAY_MCHID=1230000109
WECHAT_PAY_APPID=wx1234567890abcdef
WECHAT_PAY_API_KEY=your32charapiv3key...
WECHAT_PAY_SERIAL_NO=444F4864EA9B34415...
WECHAT_PAY_PRIVATE_KEY_PATH=/opt/x01.weiqi/apiclient_key.pem
WECHAT_PAY_NOTIFY_URL=https://your-domain.com/api/vip/notify
</code></pre>
<hr>
<h2 id="7-修复记录">7. 修复记录</h2>
<h3 id="71-2025-04-25-断线认输逻辑修复">7.1 2025-04-25 断线认输逻辑修复</h3>
<p><strong>问题描述</strong>: 玩家刷新页面断线后,对手仍处于等待落子状态,未正确显示终局信息</p>
<p><strong>修复方案</strong>: 修改前端 <code>opponent_disconnected</code> 消息处理逻辑</p>
<ul>
<li>先调用 <code>handleStateUpdate(data.state)</code> 更新游戏状态</li>
<li>显示终局棋盘和胜利消息</li>
<li>3秒后清空UI并返回大厅</li>
</ul>
<h3 id="72-2026-04-25-断线重连机制重构重大修改">7.2 2026-04-25 断线重连机制重构(重大修改)</h3>
<p><strong>核心问题</strong>: 原有逻辑将断线(包括刷新)直接等同于认输,这是严重的逻辑错误</p>
<p><strong>修复方案</strong>: 实现断线缓冲机制,区分"暂时断线"和"断线认输"</p>
<p><strong>核心改进点</strong>:</p>
<table>
<thead>
<tr>
<th>场景</th>
<th>修改前</th>
<th>修改后</th>
</tr>
</thead>
<tbody>
<tr>
<td>用户刷新页面</td>
<td>立即判定认输</td>
<td>60秒内可重连恢复对局</td>
</tr>
<tr>
<td>网络短暂波动</td>
<td>对局结束</td>
<td>60秒内重连继续对局</td>
</tr>
<tr>
<td>真正断线</td>
<td>立即结束</td>
<td>60秒后判定认输</td>
</tr>
<tr>
<td>对手体验</td>
<td>立即看到胜利</td>
<td>先看到"等待重连",超时后看到胜利</td>
</tr>
</tbody>
</table>
<h3 id="73-路由模块化重构">7.3 路由模块化重构</h3>
<p><strong>重构内容</strong>: 将原 <code>main.py</code>(~2500行)中的HTTP API路由拆分为独立模块</p>
<p><strong>新增文件</strong>:</p>
<ul>
<li><code>routers/__init__.py</code> - 路由注册汇总</li>
<li><code>routers/deps.py</code> - 依赖注入(get_current_user, require_admin, check_vip_feature)</li>
<li><code>routers/auth.py</code> - 认证路由</li>
<li><code>routers/vip.py</code> - VIP路由</li>
<li><code>routers/admin.py</code> - 管理路由</li>
<li><code>routers/game.py</code> - 游戏路由</li>
<li><code>routers/doc.py</code> - 文档路由</li>
</ul>
<p><strong>新增模块</strong>:</p>
<ul>
<li><code>core/connect.py</code> - ConnectionManager类和game_state_to_frontend()函数从main.py拆出</li>
<li><code>core/email_config.py</code> - 邮件发送配置从auth.py拆出</li>
</ul>
<p><strong>重构效果</strong>:</p>
<ul>
<li><code>main.py</code> 从 ~2500行 减至 ~889行</li>
<li>路由按功能域组织,职责清晰</li>
<li>依赖注入模式统一权限检查逻辑</li>
<li>ConnectionManager独立模块,连接管理与路由逻辑分离</li>
</ul>
<hr>
<h2 id="附录">附录</h2>
<h3 id="a-关键文件清单">A. 关键文件清单</h3>
<table>
<thead>
<tr>
<th>文件</th>
<th>职责</th>
<th>大小</th>
</tr>
</thead>
<tbody>
<tr>
<td>main.py</td>
<td>FastAPI主应用(WebSocket、打谱API)</td>
<td>~889行</td>
</tr>
<tr>
<td>core/state.py</td>
<td>游戏状态引擎</td>
<td>~3534行</td>
</tr>
<tr>
<td>core/connect.py</td>
<td>连接管理器</td>
<td>~473行</td>
</tr>
<tr>
<td>core/auth.py</td>
<td>认证与VIP</td>
<td>~829行</td>
</tr>
<tr>
<td>core/goai.py</td>
<td>AI引擎封装</td>
<td>~190行</td>
</tr>
<tr>
<td>core/ai.py</td>
<td>AI策略系统</td>
<td>~1500行</td>
</tr>
<tr>
<td>core/engine.py</td>
<td>引擎接口</td>
<td>~500行</td>
</tr>
<tr>
<td>core/game.py</td>
<td>棋局树管理</td>
<td>~600行</td>
</tr>
<tr>
<td>core/game_node.py</td>
<td>SGF节点</td>
<td>~400行</td>
</tr>
<tr>
<td>core/katabase.py</td>
<td>Katago基类</td>
<td>~300行</td>
</tr>
<tr>
<td>core/sgf_parser.py</td>
<td>SGF解析器</td>
<td>~400行</td>
</tr>
<tr>
<td>core/wechat_pay.py</td>
<td>微信支付</td>
<td>~200行</td>
</tr>
<tr>
<td>core/email_config.py</td>
<td>邮件配置</td>
<td>~69行</td>
</tr>
<tr>
<td>core/utils.py</td>
<td>工具函数</td>
<td>~78行</td>
</tr>
<tr>
<td>core/bj_time.py</td>
<td>北京时间</td>
<td>~19行</td>
</tr>
<tr>
<td>routers/auth.py</td>
<td>认证路由</td>
<td>~108行</td>
</tr>
<tr>
<td>routers/vip.py</td>
<td>VIP路由</td>
<td>~172行</td>
</tr>
<tr>
<td>routers/admin.py</td>
<td>管理路由</td>
<td>~99行</td>
</tr>
<tr>
<td>routers/game.py</td>
<td>游戏路由</td>
<td>~63行</td>
</tr>
<tr>
<td>routers/doc.py</td>
<td>文档路由</td>
<td>~86行</td>
</tr>
<tr>
<td>routers/deps.py</td>
<td>依赖注入</td>
<td>~27行</td>
</tr>
<tr>
<td>static/script.js</td>
<td>前端逻辑</td>
<td>~4034行</td>
</tr>
<tr>
<td>static/style.css</td>
<td>样式</td>
<td>~1654行</td>
</tr>
</tbody>
</table>
<h3 id="b-api接口清单">B. API接口清单</h3>
<table>
<thead>
<tr>
<th>接口</th>
<th>方法</th>
<th>模块</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>/ws/</td>
<td>WebSocket</td>
<td>main.py</td>
<td>游戏连接</td>
</tr>
<tr>
<td>/api/register</td>
<td>POST</td>
<td>routers/auth.py</td>
<td>用户注册</td>
</tr>
<tr>
<td>/api/login</td>
<td>POST</td>
<td>routers/auth.py</td>
<td>用户登录</td>
</tr>
<tr>
<td>/api/change-password</td>
<td>POST</td>
<td>routers/auth.py</td>
<td>修改密码</td>
</tr>
<tr>
<td>/api/update-email</td>
<td>POST</td>
<td>routers/auth.py</td>
<td>更新邮箱</td>
</tr>
<tr>
<td>/api/request-reset-password</td>
<td>POST</td>
<td>routers/auth.py</td>
<td>重置密码</td>
</tr>
<tr>
<td>/api/user-email</td>
<td>GET</td>
<td>routers/auth.py</td>
<td>获取邮箱</td>
</tr>
<tr>
<td>/api/vip/info</td>
<td>GET</td>
<td>routers/vip.py</td>
<td>VIP信息</td>
</tr>
<tr>
<td>/api/vip/create-order</td>
<td>POST</td>
<td>routers/vip.py</td>
<td>VIP购买</td>
</tr>
<tr>
<td>/api/vip/order-status</td>
<td>GET</td>
<td>routers/vip.py</td>
<td>订单状态</td>
</tr>
<tr>
<td>/api/vip/orders</td>
<td>GET</td>
<td>routers/vip.py</td>
<td>订单列表</td>
</tr>
<tr>
<td>/api/vip/notify</td>
<td>POST</td>
<td>routers/vip.py</td>
<td>微信回调</td>
</tr>
<tr>
<td>/api/vip/settings</td>
<td>GET/POST</td>
<td>routers/vip.py</td>
<td>VIP设置</td>
</tr>
<tr>
<td>/api/admin/users</td>
<td>GET</td>
<td>routers/admin.py</td>
<td>用户管理</td>
</tr>
<tr>
<td>/api/admin/games</td>
<td>GET</td>
<td>routers/admin.py</td>
<td>棋谱管理</td>
</tr>
<tr>
<td>/api/my-games</td>
<td>GET</td>
<td>routers/game.py</td>
<td>我的棋谱</td>
</tr>
<tr>
<td>/api/user-games/</td>
<td>GET</td>
<td>routers/game.py</td>
<td>用户棋谱</td>
</tr>
<tr>
<td id="">/api/game-sgf/</td>
<td>GET</td>
<td>routers/game.py</td>
<td>棋谱SGF</td>
</tr>
<tr>
<td>/api/doc/markdown/upload</td>
<td>POST</td>
<td>routers/doc.py</td>
<td>上传文档</td>
</tr>
<tr>
<td>/api/doc/markdown/files</td>
<td>GET</td>
<td>routers/doc.py</td>
<td>文档列表</td>
</tr>
<tr>
<td>/api/online-users</td>
<td>GET</td>
<td>main.py</td>
<td>在线用户</td>
</tr>
<tr>
<td>/api/generate-room</td>
<td>GET</td>
<td>main.py</td>
<td>生成房间</td>
</tr>
<tr>
<td>/api/user-status</td>
<td>GET</td>
<td>main.py</td>
<td>用户状态</td>
</tr>
<tr>
<td>/api/import_sgf</td>
<td>POST</td>
<td>main.py</td>
<td>SGF导入</td>
</tr>
<tr>
<td>/api/replay_next</td>
<td>POST</td>
<td>main.py</td>
<td>打谱下一步</td>
</tr>
<tr>
<td>/api/replay_prev</td>
<td>POST</td>
<td>main.py</td>
<td>打谱上一步</td>
</tr>
<tr>
<td>/api/analyze</td>
<td>POST</td>
<td>main.py</td>
<td>点目分析</td>
</tr>
<tr>
<td>/api/analyze_board</td>
<td>POST</td>
<td>main.py</td>
<td>棋盘点目</td>
</tr>
</tbody>
</table>
<h3 id="c-模块依赖关系">C. 模块依赖关系</h3>
<pre><code>main.py
├── core.state.GameState
├── core.connect.ConnectionManager, game_state_to_frontend
├── core.auth (decode_token, is_vip_or_admin, get_vip_setting, ...)
└── routers (auth_router, vip_router, admin_router, game_router, doc_router)
core/connect.py
├── core.state.GameState
├── core.auth (is_vip_or_admin, get_vip_setting, get_user_rank_info, ...)
└── core.bj_time
core/state.py
├── core.bj_time
├── core.goai.GoAI
├── core.game_node.GameNode
├── core.sgf_parser.Move, SGF
├── core.katabase.KatagoBase
├── core.engine.KataGoEngine
└── core.game.Game
core/goai.py
├── core.sgf_parser.Move
├── core.katabase.KatagoBase
├── core.ai (AI_DEFAULT, STRATEGY_REGISTRY)
└── core.constants
core/auth.py
└── core.bj_time
routers/*
├── core.auth (各认证/数据库函数)
├── core.wechat_pay (VIP路由)
└── routers.deps (get_current_user, require_admin, check_vip_feature)
</code></pre>
<h3 id="d-性能指标">D. 性能指标</h3>
<table>
<thead>
<tr>
<th>指标</th>
<th>简单AI</th>
<th>中等AI</th>
<th>困难AI (KataGo)</th>
</tr>
</thead>
<tbody>
<tr>
<td>响应时间</td>
<td><10ms</td>
<td><50ms</td>
<td>0.3-2s</td>
</tr>
<tr>
<td>CPU占用</td>
<td>极低</td>
<td>低</td>
<td>中-高</td>
</tr>
<tr>
<td>内存占用</td>
<td><1MB</td>
<td><1MB</td>
<td>100-500MB</td>
</tr>
<tr>
<td>准确度</td>
<td>随机</td>
<td>中等</td>
<td>职业级</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="总结">总结</h2>
<p>x01.weiqi AI对弈围棋系统是一个功能完整、架构清晰、性能优良的围棋对弈平台。系统核心特点:</p>
<ol>
<li><strong>架构清晰</strong>: 前后端分离,路由模块化,职责明确</li>
<li><strong>AI强大</strong>: 集成KataGo职业级引擎,支持多难度</li>
<li><strong>功能完整</strong>: 对弈、棋谱、VIP、支付等完整功能</li>
<li><strong>性能优化</strong>: 异步调度、缓存优化、增量更新</li>
<li><strong>稳定可靠</strong>: 完善的异常处理和降级机制</li>
<li><strong>易于部署</strong>: 标准化部署流程,systemd管理</li>
<li><strong>可扩展</strong>: 支持自定义策略、多引擎、配置化</li>
<li><strong>模块化</strong>: 路由按功能域拆分,依赖注入统一权限检查</li>
</ol><br><br>
来源:https://www.cnblogs.com/china_x01/p/19903950
頁:
[1]