美丽宝磊 發表於 2026-4-21 22:06:00

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":
      # 落子处理:验证轮次 -&gt; place_stone() -&gt; 广播 -&gt; 检查game_over或AI轮次
      ...
    elif msg_type == "pass":
      # Pass处理:pass_turn() -&gt; 广播 -&gt; 检查game_over或AI轮次
      ...
    elif msg_type == "analyze":
      # 点目:VIP权限检查 -&gt; analyze_territory() -&gt; 返回analysis_result
      ...
    elif msg_type == "undo":
      # 悔棋:undo_move() -&gt; 广播state_update
      ...
    elif msg_type == "resign":
      # 认输:设置game_over/winner/final_score -&gt; 保存棋谱(&gt;=50手) -&gt; 广播 -&gt; 清理
      ...
    elif msg_type == "end_game":
      # 结束对局:点目计算 -&gt; AI对弈直接结束 / 人类对弈(&gt;=170手直接结束 / &lt;170手需对方确认)
      ...
    elif msg_type == "end_game_confirm":
      # 确认结束:end_game_manually() -&gt; 保存棋谱 -&gt; 计算积分 -&gt; 广播 -&gt; 清理
      ...
</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 → 保存棋谱(&gt;=50手) → 广播game_over → 清理</td>
</tr>
<tr>
<td><code>end_game</code></td>
<td>点目计算 → AI直接结束 / 人类(&gt;=170手直接结束 / &lt;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) -&gt; 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. 检查提子机会(&gt;=3子直接提)
    2. 获取所有合法落子
    3. 调用KataGo引擎分析
    4. 返回推荐落子
    """
</code></pre>
<p><strong>性能优化</strong>: 如果能提掉&gt;=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) -&gt; str:
    """获取当前用户,验证token有效性"""

def require_admin(token: str) -&gt; str:
    """要求管理员权限"""

def check_vip_feature(token: str, feature_key: str) -&gt; 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(影响力&gt;0.3为黑,&lt;-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>&lt;10ms</td>
<td>&lt;50ms</td>
<td>0.3-2s</td>
</tr>
<tr>
<td>CPU占用</td>
<td>极低</td>
<td>低</td>
<td>中-高</td>
</tr>
<tr>
<td>内存占用</td>
<td>&lt;1MB</td>
<td>&lt;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]
查看完整版本: x01.weiqi.15: AI 对弈