この記事で作るもの

最初の段階の成果物は派手なモデルではなく、一枚の設計表です。囲碁AIは、手を推薦するモデル、ルールを判定するエンジン、対局を保存するサーバー、ユーザーが石を置く画面が一緒に動かなければなりません。この境界を決めずにモデルから学習すると、反則手や保存エラーをモデルの性能の問題だと誤解しやすくなります。

  1. 19×19の正式な碁盤を使います。
  2. 人が黒、AIが白を担当します。
  3. 人の着手には時間制限を設けません。
  4. モデルの答えはルールエンジンがもう一度検査します。
  5. 毎手の盤面と手順をサーバーに保存します。

ステップ1: モデルとルールエンジンを分けます

モデルの責任は、現在の盤面を見て次の座標の候補を出すところまでです。その点が空いているか、自殺手か、直前の盤面を繰り返すコウかは、ルールエンジンが判定します。モデルがすでに石のある点を答えたら、候補を拒否してもう一度要求します。再要求も失敗したときは、勝手な手に置き換えずにエラーを記録します。

この分離が大切なのは再現性のためです。同じ盤面で問題が起きたとき、モデルの選択が悪かったのか、ルールのコードが間違っていたのかを別々に確かめられます。ルールはテストで固定し、モデルは交換可能な部品として残しておきます。

ステップ2: 対局の状態を一つのオブジェクトとして定義します

最小限の状態には、19×19の盤面、次の手番、取った石の数、コウで禁止された座標、全手順、最後の保存日時が入ります。画面だけを見て状態を復元しようとせず、サーバーが原本を保管すべきです。ブラウザを開き直しても同じ対局を続けるには、対局コードとアクセスキーも必要です。

ステップ3: 完了条件を書き出します

最初のバージョンの完了条件は「強いAI」ではありません。人が合法的に着手でき、AIの反則の答えを拒否し、再読み込みしても手順が残り、二回続けてパスすると終局する状態であれば十分です。この基準を通過してから学習データと棋力を改善すれば、問題の原因を一度に一つずつ追えます。

次の記事では、SGF棋譜一局を学習用の入力と正解座標に分ける過程を説明します。

この段階の実際のコード

MariaGO のWeb対局は、ステップ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. 全ソースコードと使用したモデル」の記事で見られます。