一、项目概述

1.1 项目简介

项目名称: x01.weiqi (围棋对弈系统)
技术栈: Python 3.8+ + FastAPI + WebSocket + KataGo AI引擎
主要功能: 在线围棋对弈平台,支持人机对弈、人人对弈、棋谱管理、VIP会员系统等

1.2 核心特性

  • 实时对弈: 基于 WebSocket 的低延迟实时通信
  • AI引擎: 集成 KataGo 引擎,提供职业级围棋AI
  • 精确点目: 基于 Benson 算法的终局死子识别
  • 多策略AI: 16种不同风格的AI策略
  • VIP系统: 完整的会员体系和微信支付集成
  • 国际化: 支持中英文双语界面

1.3 项目统计

类别 文件数量 总行数 总大小
根目录 Python 文件 2 126 3.4 KB
core/ Python 文件 15 10,989 418 KB
routers/ Python 文件 8 1,643 64 KB
static/ 前端文件 6 7,425 393 KB
static/views/ 模板 7 525 31 KB
data/ 配置文件 3 - 25 KB
总计 41 约 20,100 约 931 KB

二、项目架构

2.1 目录结构


x01.weiqi/
├── main.py # FastAPI应用入口(57行)
├── tool.py # 开发运维工具(69行)
├── requirements.txt # Python依赖清单
├── Makefile # 构建部署命令
├── core/ # 核心业务逻辑模块
│ ├── state.py # 游戏状态管理(3559行)⭐核心
│ ├── ai.py # AI策略系统(2193行)⭐核心
│ ├── auth.py # 用户认证授权(834行)
│ ├── connect.py # WebSocket连接管理(862行)
│ ├── game.py # 游戏规则实现(800行)⭐核心
│ ├── engine.py # KataGo引擎接口(455行)
│ ├── game_node.py # 游戏树节点(452行)
│ ├── sgf_parser.py # SGF棋谱解析(713行)
│ ├── katabase.py # KataGo配置管理(233行)
│ ├── goai.py # AI封装接口(173行)
│ ├── wechat_pay.py # 微信支付集成(273行)
│ ├── constants.py # 全局常量定义(280行)
│ ├── utils.py # 工具函数(76行)
│ ├── email_config.py # 邮件服务配置(68行)
│ └── bj_time.py # 北京时间工具(18行)
├── routers/ # API路由模块
│ ├── ws.py # WebSocket路由(755行)⭐核心
│ ├── game.py # 游戏相关路由(362行)
│ ├── vip.py # VIP会员路由(171行)
│ ├── doc.py # 文档管理路由(105行)
│ ├── auth.py # 认证相关路由(107行)
│ ├── admin.py # 管理员路由(98行)
│ ├── deps.py # 路由依赖项(36行)
│ └── __init__.py # 路由模块导出(9行)
├── static/ # 静态资源
│ ├── script.js # 前端主逻辑(4371行)⭐核心
│ ├── style.css # 样式表(1668行)
│ ├── index.html # 主页HTML(36行)
│ ├── template-loader.js # 模板加载器(69行)
│ ├── highlight.min.js # 代码高亮库(121KB)
│ └── marked.min.js # Markdown解析库(39KB)
├── static/views/ # 页面视图模板
│ ├── modals.html # 模态框模板(111行)
│ ├── help.html # 帮助页面(97行)
│ ├── home.html # 主页视图(92行)
│ ├── admin.html # 管理页面(73行)
│ ├── game.html # 对弈页面(68行)
│ ├── doc.html # 文档页面(62行)
│ └── menu.html # 菜单栏(22行)
├── data/ # 数据和配置文件
│ ├── config.json # 主配置文件(251行)
│ ├── analysis_config.cfg # KataGo分析配置
│ └── contribute_config.cfg # KataGo贡献配置
├── users.db # SQLite用户数据库
└── .env # 环境变量配置

2.2 技术架构图


┌─────────────────────────────────────────────────────────────┐
│ 前端层 (static/) │
│ index.html + script.js + style.css + views/ │
│ - WebSocket客户端 │
│ - Canvas棋盘渲染 │
│ - 国际化支持(i18n) │
└────────────────────┬────────────────────────────────────────┘
│ WebSocket + HTTP REST API
┌────────────────────┴────────────────────────────────────────┐
│ 应用层 (main.py) │
│ FastAPI应用 + 生命周期管理 + 路由注册 │
│ - lifespan: 应用启动/关闭管理 │
│ - session_cleanup_task: 会话清理后台任务 │
└────────────────────┬────────────────────────────────────────┘
┌────────────┼────────────┬────────────┐
│ │ │ │
┌───────┴────┐ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐
│ routers/ │ │ core/ │ │ data/ │ │ static/ │
│ API路由 │ │ 核心逻辑 │ │ AI引擎 │ │ 静态资源 │
│ │ │ │ │ │ │ │
│ - ws.py │ │- state.py│ │- KataGo │ │- JS/CSS │
│ - auth.py │ │- game.py │ │ 引擎 │ │- HTML │
│ - game.py │ │- ai.py │ │- 配置 │ │- 模板 │
│ - vip.py │ │- engine │ │ │ │ │
│ - doc.py │ │- connect │ │ │ │ │
└────────────┘ └──────────┘ └──────────┘ └──────────┘

2.3 数据流图


用户操作 → WebSocket消息 → routers/ws.py → core/connect.py
core/state.py
core/game.py
core/engine.py → KataGo引擎
状态更新 → WebSocket广播 → 前端渲染

三、核心模块详解

3.1 main.py - 应用入口

文件位置main.py
文件大小: 1542 字节 (57 行)
功能: FastAPI应用初始化和生命周期管理

3.1.1 核心代码结构

# 1. 后台任务:定期清理过期会话
async def session_cleanup_task(manager):
"""每300秒清理一次过期会话"""
while True:
await asyncio.sleep(300)
if manager:
manager.cleanup_expired_sessions()
# 2. 应用生命周期管理
@asynccontextmanager
async def lifespan(app: FastAPI):
"""启动时创建ConnectionManager,关闭时取消清理任务"""
manager = ConnectionManager()
init_manager(manager)
cleanup_task = asyncio.create_task(session_cleanup_task(manager))
logging.info("[LIFESPAN] 应用启动")
yield
cleanup_task.cancel()
logging.info("[LIFESPAN] 应用关闭")
# 3. 创建应用并注册路由
app = FastAPI(lifespan=lifespan)
app.mount("/static", StaticFiles(directory="static"), name="static")
# 注册路由
app.include_router(auth_router) # 认证路由
app.include_router(vip_router) # VIP路由
app.include_router(admin_router) # 管理员路由
app.include_router(game_router) # 游戏路由
app.include_router(doc_router) # 文档路由
app.include_router(ws_router) # WebSocket路由
3.1.2 关键特性
  • 生命周期管理: 使用FastAPI的lifespan上下文管理器管理应用生命周期
  • 后台任务: 定期清理过期会话(默认1800秒超时)
  • 静态文件: 挂载到 /static 路径
  • 路由注册: 集成所有路由模块
  • 日志配置: 设置INFO级别日志
3.1.3 启动方式

# 开发模式
python main.py
# 生产模式
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# 或使用gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker

3.2 tool.py - 开发运维工具

文件位置tool.py
文件大小: 1864 字节 (69 行)
功能: 开发运维辅助脚本

3.2.1 核心功能

# 1. 生成安全密钥
def gen_secretkey():
"""生成32字节的URL安全密钥"""
return secrets.token_urlsafe(32)
# 2. 删除短棋谱
def delete_short_games(min_moves: int = 50):
"""
删除少于指定手数的棋谱
Args:
min_moves: 最小手数,默认50手
Returns:
deleted_count: 删除的棋谱数量
"""
# 计算手数:sgf_content.count(';') - 1
# 删除少于min_moves手的棋谱
# 3. 清理临时文件
def clean():
"""清理指定目录和文件"""
del_dirs = ['__pycache__', '.vscode', '.codeartsdoer', '.pytest_cache']
# 递归删除这些目录
# 4. 设置用户角色
set_user_role(username='admin', role='admin')
3.2.2 使用场景
  • 初始化: 生成JWT密钥、创建管理员账户
  • 维护: 清理临时文件、删除无效棋谱
  • 管理: 修改用户角色、权限管理

3.3 core/state.py - 游戏状态管理 ⭐核心

文件位置core/state.py
文件大小: 156590 字节 (3559 行)
功能: 围棋游戏核心逻辑,游戏状态管理,集成KataGo分析引擎

3.3.1 GameState类 - 核心属性

class GameState:
# 棋盘状态
board: List[List[int]] # 0=空,1=黑,2=白
move_numbers: List[List[int]] # 每个位置的落子手数
black_turn: bool # 当前是否黑方行棋
ko_point: Optional[Tuple[int, int]] # 劫点位置
# 提子统计
black_captured: int # 黑方提子数
white_captured: int # 白方提子数
# 历史记录
move_history: List[Dict] # 历史记录(支持悔棋)
# KataGo集成
katago_game: Optional[Game] # KataGo游戏对象
territory_board: List[List[int]] # 领地标记(用于点目显示)
# 游戏模式
mode: str # 'play', 'analyze', 'selfplay'
ai_enabled: bool # 是否启用AI
ai_color: int # AI执子颜色
3.3.2 基础落子规则

is_valid_move(row, col, stone) - 判断落子是否合法


def is_valid_move(self, row: int, col: int, stone: int) -> bool:
"""
判断落子是否合法
检查步骤:
1. 位置是否在棋盘内
2. 位置是否为空
3. 是否为劫点
4. 临时落子,检查是否能提子或己方有气
"""
# 边界检查
if not (0 <= row < self.size and 0 <= col < self.size):
return False
# 空位检查
if self.board[row][col] != 0:
return False
# 劫点检查
if self.ko_point == (row, col):
return False
# 临时落子,检查合法性
# ...(详细实现见源码)

place_stone(row, col) - 执行落子


def place_stone(self, row: int, col: int) -> bool:
"""
执行落子
流程:
1. 验证落子合法性
2. 更新棋盘状态
3. 执行提子
4. 检查劫争
5. 保存历史记录
6. 检查游戏是否结束
"""
# 验证
if not self.is_valid_move(row, col, stone):
return False
# 落子
self.board[row][col] = stone
self.move_numbers[row][col] = self.move_count
# 提子
captured = self._capture_opponent(row, col, stone)
# 劫争检查
self._check_ko(row, col, stone, captured)
# 保存历史
self.save_game_state()
return True
3.3.3 终局精确点目(核心算法)

calculate_final_score() - 终局计分


def calculate_final_score(self) -> Dict:
"""
终局计分
算法流程:
1. 优先使用KataGo计分(准确率最高)
- 调用 _calculate_score_by_katago()
- 使用KataGo的ownership数据进行计分
- 如果成功,直接返回结果
2. 回退到启发式算法
- 使用 _find_dead_stones_enhanced() 识别死子
- 移除死子后,用洪水填充计算空点归属
- 使用中国规则(数子法)计分:子空皆地
返回:
{
'black_score': float, # 黑方得分
'white_score': float, # 白方得分
'winner': str, # 'B' 或 'W'
'margin': float, # 胜差
'method': str # 'katago' 或 'heuristic'
}
"""
3.3.4 Benson算法 - 无条件活棋识别

_benson_alive_groups(board, color) - Benson算法核心


def _benson_alive_groups(self, board: List[List[int]], color: int) -> List[Set]:
"""
Benson算法:识别无条件活棋
算法原理:
Benson算法通过迭代收缩"健康连通分量"来识别无条件活棋,
比"2+真眼"更精确。
算法步骤:
1. 找到所有同色连通块
2. 预计算所有空点的连通区域
3. 判断每个空区域是否为"健康眼"(不接触对方棋子)
4. 迭代收缩:
- 计算每个候选块的"私有健康眼"数量
- 私有眼 ≥ 2 的块保留
- 其他块移除
- 重复直到收敛
5. 收敛后剩余的块即为无条件活棋
优势:
- 能识别大眼活棋(如直四、曲四、板六)
- 能识别借劲活棋
- 比简单的"2+真眼"判断更精确
返回:无条件活棋的棋块列表
"""
3.3.5 增强版死子识别

_find_dead_stones_enhanced(board, strict, use_katago) - 死子识别


def _find_dead_stones_enhanced(
self,
board: List[List[int]],
strict: bool = True,
use_katago: bool = True
) -> Tuple[Set[Tuple], Set[Tuple]]:
"""
增强版死子识别
算法流程:
1. 优先使用KataGo(如果允许)
- 调用 _find_dead_stones_by_katago()
- 使用KataGo的ownership数据判断死活
- ownership > 0.5: 黑方领地
- ownership < -0.5: 白方领地
2. 启发式算法:
- 用Benson算法识别无条件活棋
- 收集所有非活棋的棋块
- 合并共气棋块(共气棋子应作为整体判断)
- 对每个合并后的棋块进行死活判断
死活判断因素:
- 气数:气越少越可能死
- 被包围程度:被对方棋子包围的比例
- 眼位潜力:能否做出两眼
- 周围对方棋子的强度
- 逃逸路线:是否有向开阔区域延伸的可能
- 气点封闭度:气点是否被对方包围
- 对杀比气:双方互相包围时比较气数
- 大眼活棋:大眼区域是否为活形
返回:(黑方死子集合, 白方死子集合)
"""
3.3.6 对杀比气分析

_analyze_capture_race(group, stone, board) - 对杀分析


def _analyze_capture_race(
self,
group: Set[Tuple[int, int]],
stone: int,
board: List[List[int]]
) -> str:
"""
对杀比气分析
算法:
1. 找到与本块相邻的所有对方棋块
2. 检查对方是否也在对杀中(依赖共享气)
3. 计算双方的有效气数
4. 比较气数决定胜负
返回值:
- 'win': 对杀胜,活棋
- 'lose': 对杀败,死棋
- 'none': 不在对杀中
"""
3.3.7 大眼活棋判断

_is_big_eye_alive(group, stone, board) - 大眼活棋判断


def _is_big_eye_alive(
self,
group: Set[Tuple[int, int]],
stone: int,
board: List[List[int]]
) -> bool:
"""
大眼活棋判断
死形列表:
- 直三/曲三:被对方先手包围时可点杀
- 方块四:2x2正方形
- 斗笠四:4点对角形
- 刀把五:5点T形
- 梅花六:6点十字形
- 盘角曲四:劫活(按死处理)
- 角部板六:劫活(按死处理)
活形列表:
- 直四:4点在一条线上
- 曲四:4点弯折形(非角上)
- 板六:2x3矩形(非角部)
- 更大区域(≥7点)
返回:True=活棋,False=死棋
"""
3.3.8 历史记录与悔棋

def save_game_state(self):
"""
保存游戏状态
保存内容:
- 棋盘状态
- 手数标记
- 劫点
- 提子数
最多保存350步历史
"""
state = {
'board': [row[:] for row in self.board],
'move_numbers': [row[:] for row in self.move_numbers],
'ko_point': self.ko_point,
'black_captured': self.black_captured,
'white_captured': self.white_captured,
'move_count': self.move_count
}
self.move_history.append(state)
# 限制历史长度
if len(self.move_history) > 350:
self.move_history.pop(0)
def undo_move(self) -> bool:
"""
悔棋
流程:
1. 从历史记录恢复上一个状态
2. 同步更新游戏节点树
3. 清除劫点
4. 更新手数
"""
if len(self.move_history) <= 1:
return False
self.move_history.pop()
prev_state = self.move_history[-1]
# 恢复状态
self.board = [row[:] for row in prev_state['board']]
self.move_numbers = [row[:] for row in prev_state['move_numbers']]
# ...
return True

3.4 core/game.py - 游戏规则实现 ⭐核心

文件位置core/game.py
文件大小: 34517 字节 (800 行)
功能: 围棋游戏的核心规则实现,包括落子规则、提子、劫争等

3.4.1 BaseGame类 - 游戏基类

class BaseGame:
"""
围棋游戏基类
主要属性:
- katago: KataGo引擎引用
- root: 游戏树的根节点(GameNode)
- current_node: 当前游戏节点
- board: 棋盘状态(二维数组,存储chain id)
- chains: 棋块列表(每个棋块是一个Move列表)
- prisoners: 被提掉的棋子列表
"""
def __init__(self, size=19, rules='chinese', komi=7.5):
self.size = size
self.rules = rules
self.komi = komi
self.board = [[0] * size for _ in range(size)]
self.chains = []
self.prisoners = []
self.root = GameNode()
self.current_node = self.root
3.4.2 核心方法详解

_validate_move_and_update_chains(move, ignore_ko) - 落子验证与棋块更新


def _validate_move_and_update_chains(
self,
move: Move,
ignore_ko: bool = False
) -> Tuple[bool, Optional[str]]:
"""
落子验证与棋块更新
检查步骤:
1. 检查落子是否合法(位置是否被占用)
2. 合并相邻的同色棋块
3. 检查并执行提子(对方无气的棋块)
4. 检查劫争(ko规则)
5. 检查自杀(根据规则集判断是否允许)
返回:(是否合法, 错误信息)
"""
# 位置检查
if self.board[move.y][move.x] != 0:
return False, "位置已被占用"
# 合并相邻同色棋块
adjacent_chains = self._get_adjacent_chains(move)
new_chain = [move]
for chain_id in adjacent_chains:
if self.chains[chain_id][0].player == move.player:
new_chain.extend(self.chains[chain_id])
# 检查提子
opponent = 'W' if move.player == 'B' else 'B'
captured = self._check_captures(move, opponent)
# 检查劫争
if not ignore_ko and self._is_ko(move, captured):
return False, "劫争禁着"
# 检查自杀
if self._is_suicide(new_chain):
if self.rules == 'chinese':
return False, "自杀禁着"
return True, None

play(move, ignore_ko=False) - 执行落子


def play(self, move: Move, ignore_ko: bool = False) -> Optional[GameNode]:
"""
执行落子
流程:
1. 验证落子合法性
2. 更新棋盘状态
3. 创建新的游戏节点
4. 返回新节点
参数:
- move: 落子对象(包含坐标和颜色)
- ignore_ko: 是否忽略劫争检查
返回:新游戏节点,如果非法则返回None
"""
valid, error = self._validate_move_and_update_chains(move, ignore_ko)
if not valid:
return None
# 更新棋盘
self.board[move.y][move.x] = len(self.chains)
# 创建新节点
new_node = self.current_node.play(move)
self.current_node = new_node
return new_node

undo(n_times=1) - 悔棋


def undo(self, n_times: int = 1) -> bool:
"""
悔棋
支持:
- 多次悔棋
- 悔到分支点
- 悔到主分支
参数:
- n_times: 悔棋次数
返回:是否成功
"""
for _ in range(n_times):
if self.current_node.parent is None:
return False
self.current_node = self.current_node.parent
# 重建棋盘状态
self._rebuild_board_from_node(self.current_node)
return True

redo(n_times=1) - 重做


def redo(self, n_times: int = 1) -> bool:
"""
重做
支持:
- 前进到下一个节点
- 快捷节点跳转
参数:
- n_times: 重做次数
返回:是否成功
"""
for _ in range(n_times):
if not self.current_node.children:
return False
self.current_node = self.current_node.children[0]
self._rebuild_board_from_node(self.current_node)
return True
3.4.3 Game类 - 扩展游戏类

class Game(BaseGame):
"""
扩展游戏类,集成KataGo分析引擎
新增功能:
- 集成KataGo分析引擎
- 支持AI分析和建议
- 支持棋谱分析(sweep、equalize、alternative等模式)
- 支持自我对弈(selfplay)
"""
def analyze_all_nodes(
self,
priority: int = 0,
analyze_fast: bool = False,
even_if_present: bool = False
):
"""
分析所有节点
参数:
- priority: 分析优先级
- analyze_fast: 是否快速分析
- even_if_present: 是否强制重新分析
流程:
1. 遍历游戏树的所有节点
2. 对每个节点调用KataGo进行分析
3. 存储分析结果
"""
for node in self.root.nodes_in_tree:
if not node.analysis or even_if_present:
node.analyze(
self.katago,
priority=priority,
fast=analyze_fast
)
def analyze_extra(self, mode: str, **kwargs):
"""
额外分析
模式:
- ponder: 持续思考模式
- extra: 增加访问次数的深度分析
- game: 全局分析
- sweep: 扫描所有可能着法
- equalize: 均衡化分析
- alternative: 寻找替代着法
"""
if mode == 'ponder':
self._analyze_ponder(**kwargs)
elif mode == 'extra':
self._analyze_extra(**kwargs)
# ...
def selfplay(
self,
until_move: int = 100,
target_b_advantage: float = None
):
"""
自我对弈
参数:
- until_move: 下到第几手
- target_b_advantage: 目标黑方优势(用于教学)
流程:
AI自动下棋直到指定手数或达到目标分数
"""
while self.current_node.move_number < until_move:
best_move = self.get_best_move()
self.play(best_move)

3.5 core/ai.py - AI策略系统 ⭐核心

文件位置core/ai.py
文件大小: 83404 字节 (2193 行)
功能: 多种AI对弈策略,策略注册机制,AI等级评估

3.5.1 策略注册机制

# 策略注册表
STRATEGIES = {}
def register_strategy(name: str):
"""
策略注册装饰器
使用方式:
@register_strategy("my_strategy")
def ai_my_strategy(game, **kwargs):
# 实现你的策略
return best_move
"""
def decorator(func):
STRATEGIES[name] = func
return func
return decorator
3.5.2 内置策略详解

1. AI_DEFAULT - 默认策略


@register_strategy("default")
def ai_default(game: Game, **kwargs) -> Optional[Move]:
"""
默认策略:选择KataGo推荐的最佳着法
流程:
1. 获取当前节点的分析结果
2. 提取最佳着法
3. 返回Move对象
"""
analysis = game.current_node.analysis
if not analysis or not analysis.get('moveInfos'):
return None
best_move_info = analysis['moveInfos'][0]
move = Move.from_gtp(best_move_info['move'], game.current_player)
return move

2. AI_RANK - 段位匹配策略


@register_strategy("rank")
def ai_rank(game: Game, user_rank: int = 1, **kwargs) -> Optional[Move]:
"""
段位匹配策略:根据用户段位调整AI强度
参数:
- user_rank: 用户段位(1-10段)
策略:
- 低段位(1-3段):选择第3-5候选着法
- 中段位(4-6段):选择第2-3候选着法
- 高段位(7-10段):选择最佳着法
"""
analysis = game.current_node.analysis
move_infos = analysis['moveInfos']
# 根据段位选择候选着法索引
if user_rank <= 3:
idx = min(2, len(move_infos) - 1) # 第3候选
elif user_rank <= 6:
idx = min(1, len(move_infos) - 1) # 第2候选
else:
idx = 0 # 最佳着法
move = Move.from_gtp(move_infos[idx]['move'], game.current_player)
return move

3. AI_HUMAN - 人类风格


@register_strategy("human")
def ai_human(game: Game, **kwargs) -> Optional[Move]:
"""
人类风格:模拟人类思考时间和着法选择
特点:
- 模拟思考时间(1-3秒)
- 偶尔选择非最优着法(10%概率)
- 避免过于精确的着法
"""
import time
import random
# 模拟思考时间
think_time = random.uniform(1, 3)
time.sleep(think_time)
analysis = game.current_node.analysis
move_infos = analysis['moveInfos']
# 10%概率选择次优着法
if random.random() < 0.1 and len(move_infos) > 1:
idx = 1
else:
idx = 0
move = Move.from_gtp(move_infos[idx]['move'], game.current_player)
return move

4. AI_PRO - 职业棋手风格


@register_strategy("pro")
def ai_pro(game: Game, **kwargs) -> Optional[Move]:
"""
职业棋手风格:高访问次数,选择最强着法
特点:
- 高访问次数(10000+)
- 选择最强着法
- 深度分析
"""
# 强制深度分析
game.current_node.analyze(
game.katago,
max_visits=10000,
priority=10
)
analysis = game.current_node.analysis
best_move_info = analysis['moveInfos'][0]
move = Move.from_gtp(best_move_info['move'], game.current_player)
return move

5. AI_WEIGHTED - 加权选择


@register_strategy("weighted")
def ai_weighted(game: Game, **kwargs) -> Optional[Move]:
"""
加权选择:根据policy概率加权选择
特点:
- 根据policyPrior加权
- 增加变化
- 避免重复着法
"""
analysis = game.current_node.analysis
move_infos = analysis['moveInfos'][:10] # 前10候选
# 提取policy权重
weights = [info['policyPrior'] for info in move_infos]
total = sum(weights)
weights = [w / total for w in weights]
# 加权随机选择
import random
r = random.random()
cumsum = 0
for i, w in enumerate(weights):
cumsum += w
if r <= cumsum:
move = Move.from_gtp(move_infos[i]['move'], game.current_player)
return move
# 默认返回最佳
return Move.from_gtp(move_infos[0]['move'], game.current_player)
3.5.3 更多策略

# AI_LOCAL: 局部战斗型
@register_strategy("local")
def ai_local(game, **kwargs):
"""优先选择局部战斗着法"""
pass
# AI_TENUKI: 脱先型
@register_strategy("tenuki")
def ai_tenuki(game, **kwargs):
"""优先选择脱先着法"""
pass
# AI_INFLUENCE: 势力型
@register_strategy("influence")
def ai_influence(game, **kwargs):
"""优先选择扩张势力的着法"""
pass
# AI_TERRITORY: 实地型
@register_strategy("territory")
def ai_territory(game, **kwargs):
"""优先选择围空的着法"""
pass
# AI_ATTACK: 攻击型
@register_strategy("attack")
def ai_attack(game, **kwargs):
"""优先选择攻击着法"""
pass
# AI_DEFEND: 防守型
@register_strategy("defend")
def ai_defend(game, **kwargs):
"""优先选择防守着法"""
pass
# AI_BALANCE: 均衡型
@register_strategy("balance")
def ai_balance(game, **kwargs):
"""均衡发展"""
pass
# AI_SABAKI: 治孤型
@register_strategy("sabaki")
def ai_sabaki(game, **kwargs):
"""优先选择治孤着法"""
pass
# AI_THICK: 厚势型
@register_strategy("thick")
def ai_thick(game, **kwargs):
"""优先选择厚势着法"""
pass
# AI_LIGHT: 轻灵型
@register_strategy("light")
def ai_light(game, **kwargs):
"""优先选择轻灵着法"""
pass
# AI_HEAVY: 重型
@register_strategy("heavy")
def ai_heavy(game, **kwargs):
"""优先选择重型着法"""
pass
# AI_RANDOM: 随机型
@register_strategy("random")
def ai_random(game, **kwargs):
"""随机选择候选着法"""
pass
3.5.4 ELO等级系统

# 策略ELO等级
STRATEGY_ELO = {
'default': 2800, # 职业强豪
'pro': 2900, # 顶尖职业
'rank': 2700, # 职业中游
'human': 2600, # 业余强豪
'weighted': 2750, # 职业上游
'local': 2650, # 业余强手
'tenuki': 2600, # 业余强手
'influence': 2680, # 业余顶尖
'territory': 2680, # 业余顶尖
'attack': 2700, # 职业入门
'defend': 2650, # 业余强手
'balance': 2720, # 职业中游
'sabaki': 2670, # 业余顶尖
'thick': 2690, # 业余顶尖
'light': 2660, # 业余强手
'heavy': 2640, # 业余强手
'random': 2000, # 业余初段
}
def get_strategy_by_elo(target_elo: int) -> str:
"""
根据目标ELO选择最接近的策略
用于匹配不同水平的玩家
"""
closest = min(
STRATEGY_ELO.items(),
key=lambda x: abs(x[1] - target_elo)
)
return closest[0]

3.6 core/connect.py - WebSocket连接管理

文件位置core/connect.py
文件大小: 35048 字节 (862 行)
功能: 管理WebSocket连接、房间系统、断线重连

3.6.1 ConnectionManager类 - 核心属性

class ConnectionManager:
"""
WebSocket连接管理器
配置参数:
- MAX_AI_ROOM_USERS: AI房间最大用户数(1)
- SESSION_TIMEOUT: 会话过期时间(1800秒)
- RECONNECT_TIMEOUT: 断线重连超时(180秒)
- MAX_DISCONNECT_COUNT: 最大断线次数(2)
- MOVE_TIMEOUT: 落子超时(60秒)
- MAX_TIMEOUT_COUNT: 最大超时次数(3)
- MAX_REPLAY_ANALYSIS: 打谱点目最大次数(3)
"""
def __init__(self):
# 房间管理
self.rooms: Dict[str, Dict[str, WebSocket]] = {}
# room_id -> {'black': ws1, 'white': ws2}
# 游戏状态
self.games: Dict[str, GameState] = {}
# room_id -> GameState
# 在线用户
self.online_users: Dict[str, WebSocket] = {}
# username -> WebSocket
# 用户对局状态
self.user_game_status: Dict[str, Dict] = {}
# username -> {'room_id': str, 'color': str, 'opponent': str}
# 断线用户(支持重连)
self.disconnected_users: Dict[str, Dict] = {}
# username -> {'room_id', 'color', 'opponent', 'disconnect_time', 'reconnect_timer'}
# AI房间用户管理
self.ai_room_users: Dict[str, List[str]] = {}
# room_id -> [username1, username2, ...]
3.6.2 房间管理

join_room(room_id, websocket, username) - 加入房间


async def join_room(
self,
room_id: str,
websocket: WebSocket,
username: str
) -> Tuple[bool, Optional[str], Optional[str]]:
"""
加入房间
流程:
1. 重连检测
- 检查用户是否在对局中
- 检查是否在断线重连超时内
- 检查断线次数是否超限
2. AI房间限制检查
- VIP用户无限制
- 普通用户每日限制次数
- 检查AI是否已被占用
3. 初始化房间和游戏
- 创建新的GameState
- 如果是AI房间,启用AI
4. 重连处理
- 恢复用户颜色
- 取消重连等待计时器
- 替换旧的WebSocket连接
5. 新用户处理
- 分配颜色(黑/白)
- 记录用户状态
- 广播用户列表更新
返回:(成功, 颜色, 错误信息)
"""
# 重连检测
if username in self.disconnected_users:
disconnect_info = self.disconnected_users[username]
elapsed = time.time() - disconnect_info['disconnect_time']
if elapsed < RECONNECT_TIMEOUT:
# 重连成功
return await self._handle_reconnect(username, websocket)
# AI房间限制
if room_id.startswith('ai:'):
if not self._check_ai_room_permission(username, room_id):
return False, None, "AI房间使用次数已达上限"
# 初始化房间
if room_id not in self.rooms:
self.rooms[room_id] = {}
self.games[room_id] = GameState()
# AI房间启用AI
if room_id.startswith('ai:'):
self.games[room_id].ai_enabled = True
self.games[room_id].ai_color = 2 # 白方
# 分配颜色
if 'black' not in self.rooms[room_id]:
color = 'black'
self.rooms[room_id]['black'] = websocket
elif 'white' not in self.rooms[room_id]:
color = 'white'
self.rooms[room_id]['white'] = websocket
else:
return False, None, "房间已满"
# 记录用户状态
self.user_game_status[username] = {
'room_id': room_id,
'color': color,
'opponent': self._get_opponent(room_id, color)
}
return True, color, None
3.6.3 断线重连机制

handle_disconnect(room_id, color_str, username) - 处理断线


async def handle_disconnect(
self,
room_id: str,
color_str: str,
username: str
):
"""
处理断线
流程:
1. 记录断线信息(时间、房间、颜色、对手)
2. 启动重连等待计时器(180秒)
3. 如果超时未重连:
- 增加断线次数
- 如果超过最大次数(2次),判负
- 否则,等待下次重连
重连恢复:
- 恢复用户颜色和房间
- 同步当前游戏状态
- 通知对手重连成功
"""
# 记录断线信息
self.disconnected_users[username] = {
'room_id': room_id,
'color': color_str,
'opponent': self._get_opponent(room_id, color_str),
'disconnect_time': time.time(),
'disconnect_count': self._get_disconnect_count(username) + 1
}
# 启动重连计时器
async def reconnect_timer():
await asyncio.sleep(RECONNECT_TIMEOUT)
# 超时未重连
if username in self.disconnected_users:
disconnect_info = self.disconnected_users[username]
# 检查断线次数
if disconnect_info['disconnect_count'] >= MAX_DISCONNECT_COUNT:
# 判负
await self._handle_timeout_loss(room_id, color_str, username)
else:
# 等待下次重连
del self.disconnected_users[username]
asyncio.create_task(reconnect_timer())
3.6.4 落子超时机制

start_move_timer(room_id, color) - 启动落子计时器


def start_move_timer(self, room_id: str, color: str):
"""
启动落子计时器
流程:
1. 设置60秒超时
2. 如果超时未落子:
- 增加超时次数
- 如果超过最大次数(3次),判负
- 否则,继续等待
"""
async def timeout_handler():
await asyncio.sleep(MOVE_TIMEOUT)
game = self.games.get(room_id)
if not game:
return
# 检查是否轮到该颜色
if (color == 'black' and game.black_turn) or \
(color == 'white' and not game.black_turn):
# 超时
timeout_count = self._get_timeout_count(room_id, color) + 1
self._set_timeout_count(room_id, color, timeout_count)
if timeout_count >= MAX_TIMEOUT_COUNT:
# 判负
await self._handle_timeout_loss(room_id, color)
else:
# 继续等待
self.start_move_timer(room_id, color)
asyncio.create_task(timeout_handler())
3.6.5 AI对弈管理

_schedule_ai_move(room_id) - 调度AI落子


async def _schedule_ai_move(self, room_id: str):
"""
调度AI落子
流程:
1. 获取当前游戏状态
2. 调用GoAI获取最佳着法
3. 执行AI落子
4. 广播状态更新
5. 如果游戏未结束,继续等待玩家落子
"""
game = self.games.get(room_id)
if not game or not game.ai_enabled:
return
# 获取AI着法
ai_move = await self._get_ai_move(game)
if not ai_move:
return
# 执行落子
success = game.place_stone(ai_move[0], ai_move[1])
if not success:
return
# 广播状态更新
await self._broadcast_state(room_id)
# 检查游戏是否结束
if game.game_over:
await self._handle_game_over(room_id)

3.7 core/auth.py - 用户认证与授权

文件位置core/auth.py
文件大小: 27438 字节 (834 行)
功能: 用户认证、权限管理、数据库操作

3.7.1 数据库表结构

users表 - 用户信息


CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
hashed_password TEXT NOT NULL,
email TEXT,
score INTEGER DEFAULT 0, -- 积分
rank INTEGER DEFAULT 1, -- 段位(1-10段)
role TEXT DEFAULT 'user', -- 角色:user/admin
is_vip BOOLEAN DEFAULT FALSE, -- VIP状态
vip_expire_at DATETIME, -- VIP到期时间
reset_token TEXT, -- 密码重置token
reset_token_expires DATETIME, -- token过期时间
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

games表 - 对局记录


CREATE TABLE games (
id INTEGER PRIMARY KEY AUTOINCREMENT,
black_player TEXT NOT NULL,
white_player TEXT NOT NULL,
winner TEXT,
final_score TEXT, -- 如 "B+3.5"
end_reason TEXT, -- 如 "resign", "timeout"
sgf_content TEXT, -- SGF棋谱内容
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (black_player) REFERENCES users(username),
FOREIGN KEY (white_player) REFERENCES users(username)
);

vip_orders表 - VIP订单


CREATE TABLE vip_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
plan_type TEXT NOT NULL, -- 如 "monthly", "yearly"
amount REAL NOT NULL,
status TEXT DEFAULT 'pending', -- pending/paid/failed
trade_no TEXT UNIQUE, -- 商户订单号
transaction_id TEXT, -- 微信支付交易号
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (username) REFERENCES users(username)
);

vip_settings表 - VIP功能开关


CREATE TABLE vip_settings (
key TEXT PRIMARY KEY,
value BOOLEAN,
description TEXT
);

doc_markdown_files表 - 文档管理


CREATE TABLE doc_markdown_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
content TEXT NOT NULL,
uploaded_by TEXT NOT NULL,
approved BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (uploaded_by) REFERENCES users(username)
);
3.7.2 核心功能

用户注册与登录


def register_user(username: str, password: str, email: str = None) -> bool:
"""
用户注册
流程:
1. 检查用户名是否已存在
2. 使用bcrypt加密密码
3. 插入数据库
"""
# 检查用户名
if get_user(username):
return False
# 加密密码
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
# 插入数据库
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"INSERT INTO users (username, hashed_password, email) VALUES (?, ?, ?)",
(username, hashed, email)
)
conn.commit()
conn.close()
return True
def login_user(username: str, password: str) -> Optional[str]:
"""
用户登录
流程:
1. 查询用户
2. 验证密码
3. 生成JWT token
返回:JWT token,失败返回None
"""
user = get_user(username)
if not user:
return None
# 验证密码
if not bcrypt.checkpw(password.encode(), user['hashed_password']):
return None
# 生成JWT token
token = jwt.encode({
'username': username,
'exp': datetime.utcnow() + timedelta(hours=24)
}, SECRET_KEY, algorithm='HS256')
return token

权限管理


def is_admin(username: str) -> bool:
"""检查是否为管理员"""
user = get_user(username)
return user and user['role'] == 'admin'
def is_vip(username: str) -> bool:
"""检查是否为VIP"""
user = get_user(username)
if not user or not user['is_vip']:
return False
# 检查是否过期
if user['vip_expire_at']:
expire_at = datetime.fromisoformat(user['vip_expire_at'])
return datetime.utcnow() < expire_at
return False
def is_vip_or_admin(username: str) -> bool:
"""检查是否为VIP或管理员"""
return is_vip(username) or is_admin(username)

积分与段位


def update_score(username: str, delta: int):
"""
更新积分
积分规则:
- 同段位:胜方+1,负方-1
- 不同段位:
- 高段位胜:不加积分
- 高段位负:-1
- 低段位胜:+1
- 低段位负:不减积分
- AI固定为9段,不计算积分
- 10段积分到100后:胜不加,负减1
"""
user = get_user(username)
new_score = user['score'] + delta
# 更新段位
new_rank = calculate_rank(new_score)
# 更新数据库
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"UPDATE users SET score = ?, rank = ? WHERE username = ?",
(new_score, new_rank, username)
)
conn.commit()
conn.close()
def calculate_rank(score: int) -> int:
"""
根据积分计算段位
段位规则:
- 1段:0-9分
- 2段:10-19分
- ...
- 10段:90+分
"""
return min(10, max(1, score // 10 + 1))

3.8 core/engine.py - KataGo引擎接口

文件位置core/engine.py
文件大小: 19815 字节 (455 行)
功能: 与KataGo AI引擎通信

3.8.1 KataGoEngine类

class KataGoEngine:
"""
KataGo引擎接口
主要方法:
- start(): 启动KataGo进程
- request_analysis(): 请求分析
- stop_pondering(): 停止思考
"""
def __init__(self, config_path: str = "data/config.json"):
self.config = self._load_config(config_path)
self.process = None
self.query_id = 0
self.pending_queries = {}
3.8.2 启动引擎

def start(self):
"""
启动KataGo进程
流程:
1. 加载配置文件
2. 启动子进程
3. 建立管道通信
"""
# 构建命令
cmd = [
self.config['katago_path'],
'analysis',
'-config', self.config['config_path'],
'-model', self.config['model_path']
]
# 启动进程
self.process = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
# 启动结果读取线程
threading.Thread(target=self._read_results, daemon=True).start()
3.8.3 请求分析

def request_analysis(
self,
node: GameNode,
callback: Callable,
priority: int = 0,
**kwargs
):
"""
请求分析
参数:
- node: 游戏节点
- callback: 结果回调函数
- priority: 优先级(越高越优先)
- kwargs: 其他参数(max_visits, include_policy等)
分析请求格式:
{
"id": "query-id",
"moves": ["BQ16", "WD4", ...],
"rules": "chinese",
"komi": 7.5,
"boardXSize": 19,
"boardYSize": 19,
"maxVisits": 1000,
"includePolicy": true,
"includeOwnership": true
}
"""
# 构建请求
self.query_id += 1
query_id = f"query-{self.query_id}"
moves = [node.move.gtp() for node in node.nodes_from_root if node.move]
request = {
"id": query_id,
"moves": moves,
"rules": self.config['rules'],
"komi": self.config['komi'],
"boardXSize": self.config['size'],
"boardYSize": self.config['size'],
"maxVisits": kwargs.get('max_visits', 1000),
"includePolicy": kwargs.get('include_policy', True),
"includeOwnership": kwargs.get('include_ownership', True)
}
# 保存回调
self.pending_queries[query_id] = callback
# 发送请求
self.process.stdin.write(json.dumps(request) + '\n')
self.process.stdin.flush()
3.8.4 结果处理

def _read_results(self):
"""
读取结果线程
分析结果格式:
{
"id": "query-id",
"moveInfos": [
{
"move": "Q16",
"visits": 500,
"winrate": 0.65,
"scoreLead": 3.5,
"policyPrior": 0.1
},
...
],
"ownership": [0.9, 0.8, -0.7, ...],
"rootInfo": {
"winrate": 0.6,
"scoreLead": 2.5
}
}
"""
while True:
line = self.process.stdout.readline()
if not line:
break
try:
result = json.loads(line)
query_id = result['id']
# 调用回调
if query_id in self.pending_queries:
callback = self.pending_queries.pop(query_id)
callback(result)
except Exception as e:
logging.error(f"解析结果失败: {e}")

3.9 core/game_node.py - 游戏树节点

文件位置core/game_node.py
文件大小: 18857 字节 (452 行)
功能: 棋谱树结构管理

3.9.1 GameNode类

class GameNode:
"""
游戏树节点
主要属性:
- parent: 父节点
- children: 子节点列表
- move: 本节点着法(Move对象)
- properties: SGF属性字典
- analysis: 分析结果(从KataGo获取)
"""
def __init__(self, parent=None, move=None):
self.parent = parent
self.children = []
self.move = move
self.properties = {}
self.analysis = None
self.move_number = 0
if parent:
self.move_number = parent.move_number + 1
3.9.2 核心方法

def play(self, move: Move) -> 'GameNode':
"""
执行着法
流程:
1. 查找是否已有该着法的子节点
2. 如果有,返回现有节点
3. 如果没有,创建新节点
返回:子节点
"""
# 查找现有子节点
for child in self.children:
if child.move and child.move.equals(move):
return child
# 创建新节点
new_node = GameNode(parent=self, move=move)
self.children.append(new_node)
return new_node
@property
def nodes_from_root(self) -> List['GameNode']:
"""
从根节点到本节点的路径
返回:节点列表
"""
nodes = []
current = self
while current:
nodes.insert(0, current)
current = current.parent
return nodes
@property
def nodes_in_tree(self) -> List['GameNode']:
"""
树中的所有节点
递归遍历所有子孙节点
返回:节点列表
"""
nodes = [self]
for child in self.children:
nodes.extend(child.nodes_in_tree)
return nodes
def analyze(self, engine: KataGoEngine, **kwargs):
"""
分析本节点
参数:
- engine: KataGo引擎
- kwargs: 分析参数
流程:
1. 调用KataGo引擎
2. 存储分析结果
"""
def callback(result):
self.analysis = result
engine.request_analysis(self, callback, **kwargs)
3.9.3 分析数据压缩

# 分析结果可能很大(数百KB)
# 使用压缩存储减少内存占用
import gzip
import pickle
def set_analysis(self, analysis: dict):
"""压缩存储分析结果"""
self._analysis_compressed = gzip.compress(
pickle.dumps(analysis)
)
def get_analysis(self) -> dict:
"""解压获取分析结果"""
if hasattr(self, '_analysis_compressed'):
return pickle.loads(
gzip.decompress(self._analysis_compressed)
)
return None

3.10 core/sgf_parser.py - SGF棋谱解析

文件位置core/sgf_parser.py
文件大小: 26249 字节 (713 行)
功能: 解析和生成SGF格式棋谱

3.10.1 Move类 - 棋步

class Move:
"""
棋步类
坐标系统:
- GTP: A1-T19(列字母+行数字)
- SGF: aa-ss(双字母,左上角为aa)
- 内部: (x, y) 元组,x=列,y=行
"""
def __init__(self, coords: Optional[Tuple[int, int]], player: str):
"""
参数:
- coords: (x, y) 或 None(pass)
- player: 'B' 或 'W'
"""
self.coords = coords
self.player = player
def gtp(self) -> str:
"""转换为GTP坐标:如 "Q16" """
if not self.coords:
return "pass"
x, y = self.coords
col = chr(ord('A') + x)
row = 19 - y
return f"{col}{row}"
def sgf(self) -> str:
"""转换为SGF坐标:如 "pd" """
if not self.coords:
return ""
x, y = self.coords
col = chr(ord('a') + x)
row = chr(ord('a') + y)
return f"{col}{row}"
@staticmethod
def from_gtp(gtp: str, player: str) -> 'Move':
"""从GTP坐标创建"""
if gtp.lower() == 'pass':
return Move(None, player)
col = gtp[0].upper()
row = int(gtp[1:])
x = ord(col) - ord('A')
y = 19 - row
return Move((x, y), player)
@staticmethod
def from_sgf(sgf: str, player: str) -> 'Move':
"""从SGF坐标创建"""
if not sgf:
return Move(None, player)
x = ord(sgf[0]) - ord('a')
y = ord(sgf[1]) - ord('a')
return Move((x, y), player)
3.10.2 SGF类 - SGF文件解析

class SGF:
"""
SGF文件解析
SGF属性:
- GM: 游戏类型(1=围棋)
- FF: 文件格式(4)
- SZ: 棋盘大小(19)
- KM: 贴目(7.5)
- RU: 规则(chinese)
- PB/PW: 黑/白方姓名
- DT: 日期
- RE: 结果(如 "B+3.5")
"""
@staticmethod
def parse(sgf_content: str) -> GameNode:
"""
解析SGF文件
流程:
1. 解析SGF属性(如GM、FF、SZ、KM等)
2. 构建游戏树(支持分支)
3. 返回根节点(GameNode)
"""
# 解析属性
properties = SGF._parse_properties(sgf_content)
# 创建根节点
root = GameNode()
root.properties = properties
# 解析着法
current = root
SGF._parse_moves(sgf_content, current)
return root
@staticmethod
def generate(root: GameNode) -> str:
"""
生成SGF文件
返回:SGF格式字符串
"""
sgf = "(;"
# 添加属性
for key, value in root.properties.items():
sgf += f"{key}[{value}]"
# 添加着法
sgf += SGF._generate_moves(root)
sgf += ")"
return sgf

3.11 core/goai.py - AI封装接口

文件位置core/goai.py
文件大小: 7230 字节 (173 行)
功能: 简化AI调用接口

3.11.1 GoAI类

class GoAI:
"""
围棋AI封装
主要方法:
- get_best_move(): 获取最佳着法
"""
def __init__(self, strategy: str = 'default'):
self.strategy = strategy
def get_best_move(self, game: Game, color: int) -> Optional[Move]:
"""
获取最佳着法
流程:
1. 优先提子逻辑
- 检查是否能提掉对方≥3子的棋块
- 如果能,直接提子(避免错失提子机会)
2. 调用KataGo分析
- 获取当前节点的分析结果
- 提取候选着法列表
3. 合法落子检查
- 过滤掉非法着法(如自杀、劫)
- 返回最佳合法着法
参数:
- game: 游戏对象
- color: 颜色(1=黑,2=白)
返回:Move对象,失败返回None
"""
# 提子优先
capture_move = self._check_capture_opportunity(game, color)
if capture_move:
return capture_move
# 调用策略
strategy_func = STRATEGIES[self.strategy]
return strategy_func(game)
def _check_capture_opportunity(
self,
game: Game,
color: int
) -> Optional[Move]:
"""
检查提子机会
如果能提掉对方≥3子的棋块,直接提子
"""
# ...(详细实现见源码)
pass

3.12 core/wechat_pay.py - 微信支付集成

文件位置core/wechat_pay.py
文件大小: 8538 字节 (273 行)
功能: 微信支付V3 API封装

3.12.1 核心功能

class WeChatPay:
"""
微信支付V3
主要功能:
1. Native支付(扫码支付)
2. 订单管理
3. 支付回调
"""
def __init__(self, app_id, mch_id, api_key):
self.app_id = app_id
self.mch_id = mch_id
self.api_key = api_key
def create_order(
self,
out_trade_no: str,
total_amount: int,
description: str
) -> str:
"""
创建订单
参数:
- out_trade_no: 商户订单号
- total_amount: 金额(分)
- description: 商品描述
返回:支付二维码链接
"""
# 构建请求
# ...
# 返回code_url
return code_url
def query_order(self, out_trade_no: str) -> dict:
"""
查询订单状态
返回:
{
'trade_state': 'SUCCESS', # SUCCESS/NOTPAY/CLOSED
'transaction_id': '...',
...
}
"""
pass
def close_order(self, out_trade_no: str):
"""关闭订单"""
pass
def verify_callback(self, headers: dict, body: str) -> dict:
"""
验证支付回调
安全措施:
- 使用微信平台证书验证签名
- 敏感数据加密传输(AES-256-GCM)
- 订单幂等性处理
返回:解密后的回调数据
"""
# 验证签名
# ...
# 解密数据
# ...
return decrypted_data

3.13 其他核心模块

3.13.1 core/constants.py - 全局常量

# AI策略类型
AI_DEFAULT = 'default'
AI_RANK = 'rank'
AI_HUMAN = 'human'
AI_PRO = 'pro'
AI_WEIGHTED = 'weighted'
# ... 更多策略
# 输出级别
OUTPUT_RAW = 'raw'
OUTPUT_ANALYSIS = 'analysis'
OUTPUT_OWNERSHIP = 'ownership'
# 优先级
PRIORITY_HIGH = 10
PRIORITY_NORMAL = 5
PRIORITY_LOW = 1
# 游戏模式
MODE_PLAY = 'play'
MODE_ANALYZE = 'analyze'
MODE_SELFPLAY = 'selfplay'
3.13.2 core/utils.py - 工具函数

def coord_to_gtp(x: int, y: int) -> str:
"""坐标转GTP格式"""
col = chr(ord('A') + x)
row = 19 - y
return f"{col}{row}"
def weighted_choice(items: List, weights: List[float]) -> Any:
"""加权随机选择"""
total = sum(weights)
r = random.random() * total
cumsum = 0
for item, weight in zip(items, weights):
cumsum += weight
if r <= cumsum:
return item
return items[-1]
def truncate_json_array(data: dict, key: str, max_items: int):
"""截断JSON数组"""
if key in data and isinstance(data[key], list):
data[key] = data[key][:max_items]
3.13.3 core/email_config.py - 邮件服务

import smtplib
from email.mime.text import MIMEText
def send_email(to: str, subject: str, body: str):
"""
发送邮件
用于:
- 密码重置
- VIP到期提醒
"""
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = SMTP_USER
msg['To'] = to
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls()
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(msg)
3.13.4 core/bj_time.py - 北京时间

from datetime import datetime, timezone, timedelta
def get_beijing_time() -> datetime:
"""获取当前北京时间"""
utc_now = datetime.utcnow()
beijing_tz = timezone(timedelta(hours=8))
return utc_now.replace(tzinfo=beijing_tz)
3.13.5 core/katabase.py - KataGo配置管理

class KatagoBase:
"""
KataGo基础配置
主要属性:
- size: 棋盘大小
- komi: 贴目
- rules: 规则集
- max_visits: 最大访问次数
"""
def __init__(self, config_path: str = "data/config.json"):
self.config = self._load_config(config_path)
self.size = self.config.get('size', 19)
self.komi = self.config.get('komi', 7.5)
self.rules = self.config.get('rules', 'chinese')
self.max_visits = self.config.get('max_visits', 1000)
class SimpleJsonStore:
"""轻量级JSON存储器"""
def __init__(self, filepath: str):
self.filepath = filepath
self.data = self._load()
def save(self):
"""保存到文件"""
with open(self.filepath, 'w') as f:
json.dump(self.data, f, indent=2)

四、路由模块详解

4.1 routers/ws.py - WebSocket路由 ⭐核心

文件位置routers/ws.py
文件大小: 31361 字节 (755 行)
功能: WebSocket路由和实时对弈处理

4.1.1 端点定义

@router.get("/")
async def get_home():
"""主页路由"""
return FileResponse('static/index.html')
@router.websocket("/ws/{room_id}")
async def websocket_endpoint(
websocket: WebSocket,
room_id: str,
token: str = Query(...)
):
"""
WebSocket连接端点
参数:
- room_id: 房间ID
- token: JWT认证token
流程:
1. 验证token
2. 接受WebSocket连接
3. 加入房间
4. 循环处理消息
5. 处理断线
"""
# 验证token
username = decode_token(token)
if not username:
await websocket.close(code=4001)
return
# 接受连接
await websocket.accept()
# 加入房间
success, color, error = await manager.join_room(
room_id, websocket, username
)
if not success:
await websocket.send_json({'type': 'error', 'message': error})
await websocket.close()
return
try:
# 消息循环
while True:
data = await websocket.receive_json()
await handle_websocket_message(room_id, username, data, websocket)
except WebSocketDisconnect:
# 处理断线
await manager.handle_disconnect(room_id, color, username)
4.1.2 消息类型
类型 方向 数据 说明
move 客户端→服务器 落子
pass 客户端→服务器 虚手
undo 客户端→服务器 {} 悔棋
resign 客户端→服务器 认输
territory_request 客户端→服务器 {} 请求点目
state_update 服务器→客户端 状态更新
game_over 服务器→客户端 游戏结束
error 服务器→客户端 错误消息
4.1.3 消息处理

async def handle_websocket_message(
room_id: str,
username: str,
data: dict,
websocket: WebSocket
):
"""
处理WebSocket消息
消息类型:
- move: 落子
- pass: 虚手
- undo: 悔棋
- resign: 认输
- territory_request: 点目请求
"""
msg_type = data.get('type')
game = manager.games.get(room_id)
if msg_type == 'move':
# 验证轮次
color = data.get('color')
if not _is_player_turn(game, color):
await websocket.send_json({
'type': 'error',
'message': '不是你的回合'
})
return
# 执行落子
x, y = data['x'], data['y']
success = game.place_stone(x, y)
if success:
# 广播状态更新
await manager.broadcast_state(room_id)
# 检查游戏是否结束
if game.game_over:
await manager.handle_game_over(room_id)
# AI落子
if game.ai_enabled and not game.game_over:
await manager.schedule_ai_move(room_id)
else:
await websocket.send_json({
'type': 'error',
'message': '非法落子'
})
elif msg_type == 'pass':
# 执行虚手
game.pass_turn()
# 检查是否双pass(游戏结束)
if game.consecutive_passes >= 2:
await manager.handle_game_over(room_id)
else:
await manager.broadcast_state(room_id)
elif msg_type == 'undo':
# 执行悔棋
success = game.undo_move()
if success:
await manager.broadcast_state(room_id)
else:
await websocket.send_json({
'type': 'error',
'message': '无法悔棋'
})
elif msg_type == 'resign':
# 处理认输
winner = 'W' if data['color'] == 'black' else 'B'
await manager.handle_game_over(room_id, winner=winner, reason='resign')
elif msg_type == 'territory_request':
# 执行点目
result = game.calculate_final_score()
await websocket.send_json({
'type': 'territory_result',
'data': result
})
4.1.4 积分计算

def calculate_score_change(
winner_username: str,
loser_username: str
) -> Tuple[int, int]:
"""
计算积分变化
规则:
- 同段位:胜方+1,负方-1
- 不同段位:
- 高段位胜:不加积分
- 高段位负:-1
- 低段位胜:+1
- 低段位负:不减积分
- AI固定为9段,不计算积分
- 10段积分到100后:胜不加,负减1
返回:(胜方积分变化, 负方积分变化)
"""
winner = get_user(winner_username)
loser = get_user(loser_username)
# AI不计算积分
if winner_username.startswith('AI_') or loser_username.startswith('AI_'):
return 0, 0
winner_rank = winner['rank']
loser_rank = loser['rank']
if winner_rank == loser_rank:
# 同段位
return 1, -1
elif winner_rank > loser_rank:
# 高段位胜
if winner['score'] >= 100 and winner_rank == 10:
return 0, -1
return 0, -1
else:
# 低段位胜
return 1, 0

4.2 routers/game.py - 游戏路由

文件位置routers/game.py
文件大小: 13261 字节 (362 行)
功能: 游戏相关路由

4.2.1 端点列表
方法 路径 功能 权限
GET /api/online-users 获取在线用户 登录
GET /api/generate-room 生成房间ID 登录
GET /api/user-status 用户对局状态 登录
POST /api/invite 发送对弈邀请 登录
POST /api/accept-invite 接受邀请 登录
POST /api/reject-invite 拒绝邀请 登录
GET /api/games/history 棋谱历史 登录
GET /api/games/{id}/sgf 下载SGF 登录
4.2.2 核心实现

@router.get("/api/online-users")
async def get_online_users(token: str = Query(...)):
"""获取在线用户列表"""
username = decode_token(token)
if not username:
raise HTTPException(status_code=401, detail="未登录")
users = manager.get_online_users()
return {"users": users}
@router.post("/api/invite")
async def send_invite(
data: InviteRequest,
token: str = Query(...)
):
"""
发送对弈邀请
流程:
1. 验证token
2. 检查目标用户是否在线
3. 发送邀请通知(WebSocket)
"""
username = decode_token(token)
if not username:
raise HTTPException(status_code=401, detail="未登录")
# 检查目标用户
if data.to not in manager.online_users:
raise HTTPException(status_code=404, detail="用户不在线")
# 发送邀请
websocket = manager.online_users[data.to]
await websocket.send_json({
'type': 'invite',
'from': username,
'room_id': data.room_id
})
return {"message": "邀请已发送"}
@router.get("/api/games/history")
async def get_game_history(token: str = Query(...)):
"""获取棋谱历史"""
username = decode_token(token)
if not username:
raise HTTPException(status_code=401, detail="未登录")
games = get_user_games(username)
return {"games": games}
@router.get("/api/games/{game_id}/sgf")
async def download_sgf(game_id: int, token: str = Query(...)):
"""下载SGF棋谱"""
username = decode_token(token)
if not username:
raise HTTPException(status_code=401, detail="未登录")
sgf_content = get_game_sgf(game_id)
if not sgf_content:
raise HTTPException(status_code=404, detail="棋谱不存在")
return Response(
content=sgf_content,
media_type="application/x-go-sgf",
headers={
"Content-Disposition": f"attachment; filename=game_{game_id}.sgf"
}
)

4.3 routers/auth.py - 认证路由

文件位置routers/auth.py
文件大小: 3307 字节 (107 行)
功能: 认证相关路由

4.3.1 端点列表
方法 路径 功能 权限
POST /api/register 用户注册 公开
POST /api/login 用户登录 公开
POST /api/change-password 修改密码 登录
POST /api/update-email 更新邮箱 登录
POST /api/request-reset-password 请求重置密码 公开
POST /api/reset-password 重置密码 公开
4.3.2 核心实现

@router.post("/api/register")
async def register(data: RegisterRequest):
"""
用户注册
请求:
{
"username": "player1",
"password": "secure123",
"email": "player1@example.com"
}
响应:
{
"message": "注册成功",
"username": "player1"
}
"""
success = register_user(data.username, data.password, data.email)
if not success:
raise HTTPException(status_code=400, detail="用户名已存在")
return {"message": "注册成功", "username": data.username}
@router.post("/api/login")
async def login(data: LoginRequest):
"""
用户登录
请求:
{
"username": "player1",
"password": "secure123"
}
响应:
{
"access_token": "eyJ...",
"token_type": "bearer",
"username": "player1",
"role": "user",
"is_vip": false
}
"""
token = login_user(data.username, data.password)
if not token:
raise HTTPException(status_code=401, detail="用户名或密码错误")
user = get_user(data.username)
return {
"access_token": token,
"token_type": "bearer",
"username": data.username,
"role": user['role'],
"is_vip": is_vip(data.username)
}

4.4 routers/admin.py - 管理员路由

文件位置routers/admin.py
文件大小: 3268 字节 (98 行)
功能: 管理员相关路由

4.4.1 端点列表
方法 路径 功能 权限
GET /api/admin/users 用户列表 管理员
PUT /api/admin/users/{username}/role 修改角色 管理员
DELETE /api/admin/users/{username} 删除用户 管理员
GET /api/admin/games 棋谱列表 管理员
DELETE /api/admin/games/{id} 删除棋谱 管理员
POST /api/admin/init 初始化管理员 公开

4.5 routers/vip.py - VIP路由

文件位置routers/vip.py
文件大小: 6017 字节 (171 行)
功能: VIP相关路由

4.5.1 端点列表
方法 路径 功能 权限
GET /api/vip/info VIP信息 登录
POST /api/vip/create-order 创建订单 登录
GET /api/vip/order-status 订单状态 登录
POST /api/vip/notify 支付回调 微信
GET /api/vip/settings 功能开关 管理员

4.6 routers/doc.py - 文档路由

文件位置routers/doc.py
文件大小: 3900 字节 (105 行)
功能: 文档管理路由

4.6.1 端点列表
方法 路径 功能 权限
POST /api/doc/markdown/upload 上传文档 登录
GET /api/doc/markdown/files 文档列表 登录
GET /api/doc/markdown/{id} 获取文档 登录
PUT /api/doc/markdown/{id} 更新文档 所有者
DELETE /api/doc/markdown/{id} 删除文档 管理员
POST /api/doc/markdown/{id}/approve 审核文档 管理员
4.6.2 核心实现

@router.post("/api/doc/markdown/upload")
async def upload_markdown(data: DocMarkdownUpload, token: str):
"""
上传Markdown文档
请求:
{
"filename": "tutorial.md",
"content": "# 教程\n\n..."
}
"""
username = check_doc_permission(token)
file_id = upload_doc_markdown(data.filename, data.content, username)
return {"message": "上传成功", "file_id": file_id}
@router.put("/api/doc/markdown/{file_id}")
async def update_markdown(file_id: int, data: DocMarkdownUpdate, token: str):
"""
更新Markdown文档
权限:仅文档所有者可编辑
"""
username = check_doc_permission(token)
file_data = get_doc_markdown_content(file_id)
if not file_data:
raise HTTPException(status_code=404, detail="文件不存在")
# 权限检查
if file_data.get("uploaded_by") != username:
raise HTTPException(status_code=403, detail="只有文件所有者可以编辑")
success = update_doc_markdown(file_id, data.content)
if not success:
raise HTTPException(status_code=500, detail="更新失败")
return {"message": "更新成功"}

4.7 routers/deps.py - 路由依赖项

文件位置routers/deps.py
文件大小: 1278 字节 (36 行)
功能: 路由依赖项


_manager: ConnectionManager = None
def init_manager(manager: ConnectionManager):
"""初始化连接管理器"""
global _manager
_manager = manager
def get_manager() -> ConnectionManager:
"""获取连接管理器"""
return _manager
async def get_current_user(token: str = Query(...)) -> str:
"""获取当前用户"""
username = decode_token(token)
if not username:
raise HTTPException(status_code=401, detail="未登录")
return username
async def require_admin(token: str = Query(...)) -> str:
"""要求管理员权限"""
username = decode_token(token)
if not username:
raise HTTPException(status_code=401, detail="未登录")
if not is_admin(username):
raise HTTPException(status_code=403, detail="需要管理员权限")
return username

五、前端实现

5.1 static/script.js - 前端主逻辑 ⭐核心

文件位置static/script.js
文件大小: 194157 字节 (4371 行)
功能: 前端主逻辑脚本

5.1.1 核心模块

1. WebSocket连接管理


let ws = null;
let reconnectTimer = null;
function connectWebSocket(roomId) {
/**
* 连接WebSocket
*
* 流程:
* 1. 创建WebSocket连接
* 2. 设置事件处理器
* 3. 启动心跳检测
*/
const wsUrl = `ws://${window.location.host}/ws/${roomId}?token=${authToken}`;
ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log('WebSocket连接成功');
startHeartbeat();
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
handleMessage(data);
};
ws.onclose = function() {
console.log('WebSocket断开');
scheduleReconnect();
};
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
};
}
function startHeartbeat() {
/**
* 心跳检测
* 每30秒发送一次ping
*/
setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({type: 'ping'}));
}
}, 30000);
}
function scheduleReconnect() {
/**
* 重连机制
* 5秒后尝试重连
*/
if (reconnectTimer) {
clearTimeout(reconnectTimer);
}
reconnectTimer = setTimeout(() => {
console.log('尝试重连...');
connectWebSocket(currentRoomId);
}, 5000);
}

2. 棋盘渲染


const canvas = document.getElementById('board');
const ctx = canvas.getContext('2d');
function renderBoard(gameState) {
/**
* 渲染棋盘
*
* 流程:
* 1. 绘制棋盘网格
* 2. 绘制星位
* 3. 绘制棋子
* 4. 绘制手数标记
* 5. 绘制领地标记
*/
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格
drawGrid();
// 绘制星位
drawStarPoints();
// 绘制棋子
for (let y = 0; y < 19; y++) {
for (let x = 0; x < 19; x++) {
if (gameState.board[y][x] !== 0) {
drawStone(x, y, gameState.board[y][x]);
}
}
}
// 绘制手数标记
if (showMoveNumbers) {
drawMoveNumbers(gameState.move_numbers);
}
// 绘制领地标记
if (gameState.territory_board) {
drawTerritory(gameState.territory_board);
}
}
function drawStone(x, y, color) {
/**
* 绘制棋子
*
* 参数:
* - x, y: 棋盘坐标
* - color: 1=黑,2=白
*/
const px = x * CELL_SIZE + OFFSET;
const py = y * CELL_SIZE + OFFSET;
ctx.beginPath();
ctx.arc(px, py, STONE_RADIUS, 0, 2 * Math.PI);
if (color === 1) {
// 黑子
ctx.fillStyle = '#000';
} else {
// 白子
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
}
ctx.fill();
ctx.stroke();
}

3. 游戏逻辑


function handleClick(event) {
/**
* 处理点击事件
*
* 流程:
* 1. 计算棋盘坐标
* 2. 检查是否轮到自己
* 3. 发送落子消息
*/
const rect = canvas.getBoundingClientRect();
const x = Math.round((event.clientX - rect.left - OFFSET) / CELL_SIZE);
const y = Math.round((event.clientY - rect.top - OFFSET) / CELL_SIZE);
// 检查坐标合法性
if (x < 0 || x >= 19 || y < 0 || y >= 19) {
return;
}
// 检查是否轮到自己
if (!isMyTurn()) {
return;
}
// 发送落子消息
ws.send(JSON.stringify({
type: 'move',
x: x,
y: y,
color: myColor
}));
}
function handleUndo() {
/**
* 处理悔棋
*/
ws.send(JSON.stringify({
type: 'undo'
}));
}
function handleResign() {
/**
* 处理认输
*/
if (confirm('确定要认输吗?')) {
ws.send(JSON.stringify({
type: 'resign',
color: myColor
}));
}
}
function requestTerritory() {
/**
* 请求点目
*/
ws.send(JSON.stringify({
type: 'territory_request'
}));
}

4. UI交互


function handleMessage(data) {
/**
* 处理WebSocket消息
*
* 消息类型:
* - state_update: 状态更新
* - game_over: 游戏结束
* - error: 错误消息
* - invite: 对弈邀请
* - territory_result: 点目结果
*/
switch (data.type) {
case 'state_update':
updateGameState(data.state);
break;
case 'game_over':
showGameOver(data.winner, data.score);
break;
case 'error':
showError(data.message);
break;
case 'invite':
showInvite(data.from, data.room_id);
break;
case 'territory_result':
showTerritoryResult(data.data);
break;
}
}
function updateGameState(state) {
/**
* 更新游戏状态
*
* 流程:
* 1. 更新棋盘
* 2. 更新信息显示
* 3. 重新渲染
*/
gameState = state;
renderBoard(state);
updateInfo(state);
}

5. 国际化支持


const i18n = {
'zh-CN': {
'login': '登录',
'register': '注册',
'play': '对弈',
'undo': '悔棋',
'resign': '认输',
'pass': '虚手',
// ... 更多翻译
},
'en-US': {
'login': 'Login',
'register': 'Register',
'play': 'Play',
'undo': 'Undo',
'resign': 'Resign',
'pass': 'Pass',
// ... 更多翻译
}
};
let currentLang = 'zh-CN';
function t(key) {
/**
* 翻译函数
*
* 用法:t('login') -> '登录'
*/
return i18n[currentLang][key] || key;
}
function switchLanguage(lang) {
/**
* 切换语言
*/
currentLang = lang;
updateUI();
}

6. Markdown渲染


function renderMarkdown(content) {
/**
* 渲染Markdown
*
* 使用marked.js库
*/
const html = marked.parse(content);
document.getElementById('docContent').innerHTML = html;
// 代码高亮
document.querySelectorAll('pre code').forEach(block => {
hljs.highlightElement(block);
});
}

5.2 static/style.css - 样式表

文件位置static/style.css
文件大小: 33983 字节 (1668 行)
功能: 样式表

5.2.1 核心样式

/* 棋盘样式 */
#board {
border: 2px solid #000;
background: #DCB35C;
cursor: pointer;
}
/* 棋子样式 */
.stone {
border-radius: 50%;
box-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.stone.black {
background: #000;
}
.stone.white {
background: #fff;
border: 1px solid #000;
}
/* 响应式布局 */
@media (max-width: 768px) {
#board {
width: 100%;
height: auto;
}
.sidebar {
display: none;
}
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}

5.3 static/views/ - 页面视图模板

5.3.1 modals.html - 模态框模板

<!-- 修改密码模态框 -->
<div id="changePasswordModal" class="modal">
<div class="modal-content">
<h2>修改密码</h2>
<form onsubmit="handleChangePassword(event)">
<input type="password" id="oldPassword" placeholder="旧密码" required>
<input type="password" id="newPassword" placeholder="新密码" required>
<button type="submit">确认</button>
</form>
</div>
</div>
<!-- VIP购买模态框 -->
<div id="vipModal" class="modal">
<div class="modal-content">
<h2>VIP会员</h2>
<div class="vip-plans">
<div class="plan" onclick="buyVIP('monthly')">
<h3>月度会员</h3>
<p>¥30/月</p>
</div>
<div class="plan" onclick="buyVIP('yearly')">
<h3>年度会员</h3>
<p>¥300/年</p>
</div>
</div>
</div>
</div>
5.3.2 game.html - 对弈页面

<div id="gameView" class="view-container">
<!-- 棋盘区域 -->
<div class="board-container">
<canvas id="board" width="570" height="570"></canvas>
</div>
<!-- 信息面板 -->
<div class="info-panel">
<div class="player-info">
<div class="black">
<span class="name">黑方</span>
<span class="captures">提子: 0</span>
</div>
<div class="white">
<span class="name">白方</span>
<span class="captures">提子: 0</span>
</div>
</div>
<!-- 控制按钮 -->
<div class="controls">
<button onclick="handleUndo()">悔棋</button>
<button onclick="handlePass()">虚手</button>
<button onclick="handleResign()">认输</button>
<button onclick="requestTerritory()">点目</button>
</div>
</div>
</div>
5.3.3 doc.html - 文档页面

<div id="docView" class="view-container">
<!-- 文档工具栏 -->
<div id="docToolbar">
<button id="docEditBtn" onclick="editDocContent()">✏️ 编辑</button>
<button id="docSaveBtn" onclick="saveDocContent()">💾 保存</button>
<button id="docDownloadBtn" onclick="downloadDocContent()">📥 下载</button>
<button id="docPrintBtn" onclick="printDocContent()">🖨️ 打印</button>
</div>
<!-- 文档内容 -->
<div id="docMarkdownContent">
<!-- Markdown渲染区域 -->
</div>
<!-- 文件列表 -->
<div id="docFileList">
<!-- 文件列表 -->
</div>
</div>

六、配置文件

6.1 data/config.json - 主配置文件

文件位置data/config.json
文件大小: 6572 字节 (251 行)
功能: 主配置文件


{
"game": {
"size": 19,
"komi": 7.5,
"rules": "chinese",
"handicap": 0
},
"engine": {
"katago_path": "data/katago",
"model_path": "data/b18c384nbt-s*.bin.gz",
"config_path": "data/analysis_config.cfg",
"max_visits": 1000,
"fast_visits": 100,
"time_limit": 10.0
},
"ai": {
"default_strategy": "default",
"rank_strategy": "rank",
"human_strategy": "human"
},
"ui": {
"show_move_numbers": false,
"show_coordinates": true,
"sound_enabled": true
},
"timer": {
"move_timeout": 60,
"max_timeout_count": 3
}
}

6.2 data/analysis_config.cfg - KataGo分析配置

文件位置data/analysis_config.cfg
文件大小: 13353 字节
功能: KataGo分析引擎配置


# 日志配置
logToStdout = true
logAllRequests = false
# 搜索参数
maxVisits = 1000
maxTime = 10.0
# 规则设置
rules = chinese
komi = 7.5
# 性能优化
numSearchThreads = 4
maxConcurrentEvals = 8

七、关键技术特性

7.1 实时对弈

WebSocket通信:

  • 低延迟实时通信
  • 双向消息推送
  • 自动重连机制

断线重连:

  • 3分钟内自动重连
  • 最多允许2次断线
  • 第3次断线直接判负

7.2 AI引擎集成

KataGo引擎:

  • 强大的围棋AI引擎
  • 支持多种规则集
  • 提供精确的形势判断

AI策略:

  • 16种不同策略
  • 段位匹配
  • 人类风格模拟

7.3 精确点目

Benson算法:

  • 识别无条件活棋
  • 比传统方法更精确
  • 支持大眼活棋、借劲活棋

死子识别:

  • 优先使用KataGo的ownership数据
  • 回退到启发式算法
  • 综合考虑多个因素

7.4 权限系统

三级权限:

  • 普通用户:基本功能
  • VIP用户:无限AI对弈、专属功能
  • 管理员:用户管理、棋谱管理

VIP功能开关:

  • AI对弈限制
  • 优先匹配
  • 专属功能

7.5 支付集成

微信支付V3:

  • Native支付(扫码)
  • 订单管理
  • 支付回调

安全措施:

  • 签名验证
  • 数据加密
  • 幂等性处理

八、性能优化

8.1 数据库优化

  • 使用索引加速查询
  • 定期清理过期数据
  • 连接池管理

8.2 内存优化

  • 分析数据压缩存储
  • 按需解压
  • 定期清理缓存

8.3 网络优化

  • WebSocket长连接
  • 消息压缩
  • 心跳检测

九、安全措施

9.1 认证安全

  • JWT token认证
  • 密码bcrypt加密
  • Token过期机制

9.2 数据安全

  • SQL注入防护(参数化查询)
  • XSS防护(输入过滤)
  • CSRF防护(token验证)

9.3 支付安全

  • 签名验证
  • 数据加密
  • 幂等性处理

十、部署说明

10.1 环境要求

  • Python 3.8+
  • SQLite 3
  • KataGo引擎

10.2 安装步骤


# 1. 安装依赖
pip install -r requirements.txt
# 2. 初始化数据库
python -c "from core.auth import init_db; init_db()"
# 3. 下载KataGo引擎
从 https://github.com/lightvector/KataGo 下载
# 4. 启动服务
python main.py

10.3 生产部署


# 使用uvicorn
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
# 或使用gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker

十一、开发指南

11.1 代码风格

  • 遵循PEP 8规范
  • 使用类型注解
  • 编写文档字符串

11.2 测试


# 运行测试
pytest tests/
# 代码覆盖率
pytest --cov=core tests/

11.3 调试


# 启用调试日志
import logging
logging.basicConfig(level=logging.DEBUG)

十二、常见问题

Q1: 如何添加新的AI策略?

在 core/ai.py 中使用装饰器注册:


@register_strategy("my_strategy")
def ai_my_strategy(game, **kwargs):
# 实现你的策略
return best_move

Q2: 如何修改点目算法?

修改 core/state.py 中的 calculate_final_score() 方法。

Q3: 如何添加新的WebSocket消息类型?

在 routers/ws.py 的 handle_websocket_message() 中添加新的处理分支。


十三、更新日志

v1.0.0 (2026-01-01)

  • 初始版本发布
  • 支持人机对弈、人人对弈
  • 集成KataGo引擎

更多推荐