本篇要做的东西

第一阶段的成果不是华丽的模型,而是一张一页纸的设计表。围棋AI需要推荐着法的模型、判定规则的引擎、保存对局的服务器以及用户落子的界面一起协同运作。如果不先划定这些边界就开始训练模型,很容易把非法着法和保存错误误判为模型性能问题。

  1. 使用19×19标准棋盘。
  2. 人执黑,AI执白。
  3. 人的落子不设时间限制。
  4. 模型的回答由规则引擎再次检查。
  5. 每一手的盘面和着法顺序都保存到服务器。

第1步:把模型和规则引擎分开

模型的职责仅限于看当前盘面并给出下一手的候选坐标。该点是否为空、是否为自杀、是否是重复上一盘面的劫,都由规则引擎判定。如果模型回答了一个已经有棋子的点,就拒绝该候选并重新请求。如果重新请求也失败,不会换成随机着法,而是记录错误。

这种分离之所以重要,是因为可复现性。当某个盘面出现问题时,可以分别确认是模型选得不好,还是规则代码写错了。规则用测试固定下来,模型则作为可替换的部件保留。

第2步:把对局状态定义为一个对象

最小状态包括19×19棋盘、下一手轮到谁、提子数、劫禁止坐标、完整着法顺序以及最后保存时间。不要试图只凭界面来恢复状态,服务器必须保存原始数据。要想重新打开浏览器后继续同一局,还需要对局代码和访问密钥。

第3步:写下完成条件

第一个版本的完成条件不是“强大的AI”。只要人能合法落子、AI的非法回答会被拒绝、刷新后着法顺序依然保留、连续两次停一手就结束对局,就可以了。通过这个标准之后再去改进训练数据和棋力,才能一次一个地追踪问题的原因。

下一篇将讲解如何把一局SGF棋谱拆分为训练用输入和正确坐标。

这一步的实际代码

MariaGO网页对局把第2步所说的“对局状态作为一个对象”以下面的GameRow类型保存到服务器。棋盘、下一手、劫的坐标、提子、连续停一手的次数和完整着法顺序都放在一行里,同时还保存用于在其他设备上继续对局的访问密钥。

mariago-web/lib/gameTypes.tstypescript · 32 行
export type MoveRec = {
  ply: number;
  color: 1 | 2;
  pass?: boolean;
  r?: number;
  c?: number;
};

export type GameWinner = "black" | "white" | "draw";

export type GameRow = {
  code: string;
  guest_id: string;
  /** 비공개 키 — 다른 기기 이어두기용. 남에게 코드만 공유하면 안 됨 */
  access_key: string;
  board: number[];
  to_play: number;
  ko: { r: number; c: number } | null;
  last_move: { r: number; c: number } | null;
  move_no: number;
  cap_b: number;
  cap_w: number;
  pass_streak: number;
  epoch: number;
  started: boolean;
  status: "playing" | "ended";
  end_text: string | null;
  winner: GameWinner | null;
  moves: MoveRec[];
  created_at: string;
  updated_at: string;
};

使用的模型:Qwen3-VL-8B-Instruct + LoRA适配器(lora_mix_qwen_r5)。完整源代码和模型说明可以在“8. 完整源代码与使用的模型”一文中查看。