どうも、shingoです。
先日開催されたSASユーザー総会2026のリバーシチャンピオンシップにおいて、私の開発したリバーシAIが見事優勝することができました!
企画していただいた方、対戦していただいた参加者の皆様、本当にありがとうございました。
SASユーザー総会2026 リバーシチャンピオンシップ結果(PDF)
このブログでは、SASユーザー総会2026のリバーシチャンピオンシップの様子と、どんな開発をしたか、について解説します。
SASによるリバーシチャンピオンシップは昨年の2025年から始まり、今年で2年目になります。
昨年は8名が参加し、私は準決勝敗退、チャンピオンは弊社のIIJIMAで、圧倒的な強さでした。
SASユーザー総会2025 プログラミングコンテストで優勝 | Nature Insight ネイチャーインサイト株式会社
今年はSASリバーシの開発者IZUMIさん、弊社の前回チャンピオンIIJIMAという最強メンバーで行われました。先手後手を入れ替えての総当たり戦で、それぞれ4戦行う形式でした。
当日は、モニターに盤面が映されて、リアルタイムでリバーシの対戦が行われています。
動いている間はずっと緊張しています。
そして激闘の末、なんとか私が勝つことができました!
他の2名は自力で書いていたり、一部生成AIを使用したとのことで、私はガッツリ生成AIを使っている、というのもありましたが、勝つことができてとてもうれしかったです。
開発自体はClaude Codeを使用しました。
ここからは、SASリバーシチャンピオンシップで使用したプログラムについての解説になります。
理論寄りの話になるので、興味があれば読んでみてください。
まず、リバーシAIの基本構成について少し触れておきます。もちろん今は様々な理論がありますが、SASで実装できるとなると、基本であるMin-Max法がベースになるかと思います。
大雑把にいうと、数手先まで盤面を読み、その盤面の良し悪しを点数化するという2つのエンジンで動いています。
確か、IIJIMAがMin-Max法、IZUMIさんがα-β法でした。私もα-β法を採用しました。
また、この盤面のスコアを計算する関数を評価関数といいます。強いリバーシAIを作るには、高速な探索方法と適切な評価関数の設定が必要です。
探索を高速化させるためには、SASの深い知識が必要になります。
SASのデータステップとプロシジャは一度切れると大きな時間ロスが生まれます。大量のデータ処理をするのであれば誤差レベルですが、1手数秒の世界でオーバーヘッドが生まれると大変厄介です。
そこで、高速にするために1データステップに全て詰め込む方針を取りました。
また、深さごとの手番、合法手リストなどを全て配列で定義し、再帰関数を擬似的に再現しました。
さらに、Claude Codeからのアイディアで「番兵」を導入しました。実装では黒=1、白=2としていますが、リバーシの盤面を8×8ではなく10×10とみなし、外周を番兵=3とすることで、盤外に出たかどうかのif文が不要となり、速度が向上しています。
上記によって、以前より相当な高速化に成功しました。
SASの環境によって実行時間が変わるので、探索の深さは自動で決めています。
探索時間の制限は6秒ですが、他にも処理があるため、安全のため制限時間を4.6秒に設定し、そこで終了するようにしています。
打ち切り方法について、大きく2種類を入れています。
上記を実装することにより、自動で深さを打ち切りつつ、最大限時間を活用します。
評価関数は、盤面の状態を数値化する線形モデルとして実装しています。
盤面から抽出した20種類の特徴量をベクトル \( f = (f_1, f_2, \ldots, f_{20}) \) とし、対応する重みベクトルを \( w = (w_1, w_2, \ldots, w_{20}) \) としたとき、盤面の評価値 \( V \) は以下の内積で計算されます。
\[ V = \sum_{i=1}^{20} w_i \cdot f_i \]
抽出する特徴量には、リバーシの理論を網羅的に詰め込みました。
① 位置評価
| 外周:番兵(走査停止用) | |||||||||
| 0 | 1 | 2 | 3 | 3 | 2 | 1 | 0 | ||
| 1 | 4 | 5 | 6 | 6 | 5 | 4 | 1 | ||
| 2 | 5 | 7 | 8 | 8 | 7 | 5 | 2 | ||
| 3 | 6 | 8 | 9 | 9 | 8 | 6 | 3 | ||
| 3 | 6 | 8 | 9 | 9 | 8 | 6 | 3 | ||
| 2 | 5 | 7 | 8 | 8 | 7 | 5 | 2 | ||
| 1 | 4 | 5 | 6 | 6 | 5 | 4 | 1 | ||
| 0 | 1 | 2 | 3 | 3 | 2 | 1 | 0 | ||
② 選択肢・モビリティ
③ 石の安全性・取られにくさ
④ 角の周辺マス
⑤ 終盤・決着
リバーシは序盤と終盤で定石が全く異なります。そのため、重み \( w \) を固定せず、盤上の総石数 \( s \) に応じて動的に変化させる3フェーズ補間を導入しました。
序盤・中盤・終盤のアンカーを \( s \in \{4, 34, 64\} \) とし、それぞれで学習した重みベクトルを \( w^{(1)}, w^{(2)}, w^{(3)} \) とします。
現在の石数 \( s \) によって、下記のように重みを切り替えます。
\[ w(s) = \left(1 – \frac{s-4}{34-4}\right) w^{(1)} + \frac{s-4}{34-4} w^{(2)} \]
\[ w(s) = \left(1 – \frac{s-34}{64-34}\right) w^{(2)} + \frac{s-34}{64-34} w^{(3)} \]
チューニング方法
特徴量を決めたことにより、評価関数の形が定まりました。
ここが最も重要だと思っていますが、次に重みをどのように決定するかを考えました。
重みの推定方法として遺伝的アルゴリズムやOptunaによるハイパーパラメータチューニングがありますが、今回は実装に慣れていて、わかりやすいOptunaで推定することにしました。
当然SASにはOptunaは実装されていないので、一度プログラムをSASからPythonに置き換えました。Claude Codeで実装していましたが、一筋縄ではいかず、テストを何度も繰り返して完全再現しました。
特に、深さは実行機によるため、何パターンか用意し、ローカルのPythonとSAS OnDemand for Academicsで完全一致するかどうかを試していきました。
また、チューニングでは深さも逐次ランダムに変え、実行機によって多少深さがぶれても問題ないように重みを調整しました。
対戦相手
Optunaを回すときに最も重視しなければいけないのは対戦相手です。前回の大会でも私はそれなりに強いリバーシAIを作成していたので、それを使用することにしました。
しかし、そのリバーシAIのみを対戦相手にしてしまうと簡単に過学習してしまいます。そこで、最初の8手はランダムにし、それ以降は過去のリバーシAIを使用しました。
また、評価指標は勝率ではなく石の差にしました。こちらの方が精度が高く、圧勝してくれます。
上記が終わったら、その重みをSASのプログラムに反映します。
また、Claude CodeにSASと全く同じロジックをJavaScriptで書いてもらい、デモプレイをします。
私が対戦してみて明らかに弱い部分などがあり、その場合は大体SASプログラムの実装ミスでした。
これを私が確認したり、Claude Codeに修正してもらったりしながら完成形を目指していきます。
また、ハイパーパラメータチューニングを繰り返すことにより、さらに強くしていきました。
最終的には、私では全く歯が立たないリバーシAIができていました。
それなりに改良していきましたが、まだまだ改良する余地があります。
最後まで読んでいただきありがとうございました。
SASリバーシチャンピオンシップは通常のゲームAI作成とは異なり、1から自分で理解して実装する必要があるため、とても面白いですし、基礎から学ぶきっかけにもなります。
また企画があることを期待しております。ありがとうございました!
コメント