NI BLOG

ネイチャーインサイト株式会社の情報発信ブログ
SASに関する技術情報など

2026/10/21 SAS関連

SASユーザー総会2026のリバーシチャンピオンシップで優勝しました!

SASユーザー総会2026のリバーシチャンピオンシップで優勝しました!

どうも、shingoです。
先日開催されたSASユーザー総会2026のリバーシチャンピオンシップにおいて、私の開発したリバーシAIが見事優勝することができました!
企画していただいた方、対戦していただいた参加者の皆様、本当にありがとうございました。

SASユーザー総会2026 リバーシチャンピオンシップ結果(PDF)

このブログでは、SASユーザー総会2026のリバーシチャンピオンシップの様子と、どんな開発をしたか、について解説します。

SASユーザー総会2026 リバーシチャンピオンシップ 表彰の様子

1. 前回のリバーシチャンピオンシップ

SASによるリバーシチャンピオンシップは昨年の2025年から始まり、今年で2年目になります。

昨年は8名が参加し、私は準決勝敗退、チャンピオンは弊社のIIJIMAで、圧倒的な強さでした。

SASユーザー総会2025 プログラミングコンテストで優勝 | Nature Insight ネイチャーインサイト株式会社

2. リバーシチャンピオンシップの様子

今年はSASリバーシの開発者IZUMIさん、弊社の前回チャンピオンIIJIMAという最強メンバーで行われました。先手後手を入れ替えての総当たり戦で、それぞれ4戦行う形式でした。

当日は、モニターに盤面が映されて、リアルタイムでリバーシの対戦が行われています。
動いている間はずっと緊張しています。

そして激闘の末、なんとか私が勝つことができました!

対戦結果表

他の2名は自力で書いていたり、一部生成AIを使用したとのことで、私はガッツリ生成AIを使っている、というのもありましたが、勝つことができてとてもうれしかったです。
開発自体はClaude Codeを使用しました。

3. プログラムの解説

ここからは、SASリバーシチャンピオンシップで使用したプログラムについての解説になります。
理論寄りの話になるので、興味があれば読んでみてください。

3.1. リバーシAIの基本概念(Min-Max法とα-β法)

まず、リバーシAIの基本構成について少し触れておきます。もちろん今は様々な理論がありますが、SASで実装できるとなると、基本であるMin-Max法がベースになるかと思います。
大雑把にいうと、数手先まで盤面を読み、その盤面の良し悪しを点数化するという2つのエンジンで動いています。

  • Min-Max法:「自分はスコアを最大化する手を、相手はスコアを最小化する手を選ぶ」という前提で、お互いが最善を尽くした場合の未来を計算するアルゴリズムです。
  • α-β法:Min-Max法を高速化する手法です。明らかに打たない無駄な手を途中で計算から除外することで、より深く先読みできるようにします。

確か、IIJIMAがMin-Max法、IZUMIさんがα-β法でした。私もα-β法を採用しました。
また、この盤面のスコアを計算する関数を評価関数といいます。強いリバーシAIを作るには、高速な探索方法と適切な評価関数の設定が必要です。

3.2. 探索の高速化

3.2.1. 1つのデータステップで実行する

探索を高速化させるためには、SASの深い知識が必要になります。
SASのデータステップとプロシジャは一度切れると大きな時間ロスが生まれます。大量のデータ処理をするのであれば誤差レベルですが、1手数秒の世界でオーバーヘッドが生まれると大変厄介です。
そこで、高速にするために1データステップに全て詰め込む方針を取りました。
また、深さごとの手番、合法手リストなどを全て配列で定義し、再帰関数を擬似的に再現しました。

さらに、Claude Codeからのアイディアで「番兵」を導入しました。実装では黒=1、白=2としていますが、リバーシの盤面を8×8ではなく10×10とみなし、外周を番兵=3とすることで、盤外に出たかどうかのif文が不要となり、速度が向上しています。
上記によって、以前より相当な高速化に成功しました。

3.2.2. 深さを打ち切りする

SASの環境によって実行時間が変わるので、探索の深さは自動で決めています。
探索時間の制限は6秒ですが、他にも処理があるため、安全のため制限時間を4.6秒に設定し、そこで終了するようにしています。
打ち切り方法について、大きく2種類を入れています。

  1. 次の深さに行く直前に、制限時間の1/4を超えていた場合、そこで終了します。つまり、今回は 4.6 / 4 = 1.15秒 を超えたときはストップします。
  2. さらに、同じ深さでも1024手ごとに1回時間を測り、制限時間(4.6秒)を超えたらストップします。その深さは計算に入れません。

上記を実装することにより、自動で深さを打ち切りつつ、最大限時間を活用します。

3.3. 評価関数の作成方法

評価関数は、盤面の状態を数値化する線形モデルとして実装しています。
盤面から抽出した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 \]

3.3.1. 特徴量の選択

抽出する特徴量には、リバーシの理論を網羅的に詰め込みました。

① 位置評価

  • pos_0(角):4隅のマス。
  • pos_1(Cマス):角の上下左右に隣接する辺のマス。
  • pos_2(辺の3マス目):角から2つ離れた辺のマス。
  • pos_3(辺の中央寄り):辺の中央2マス。
  • pos_4(Xマス):角の斜め内側に隣接するマス。
  • pos_5〜pos_9:その他、盤面の内側や中央などを距離に応じて分類。
外周:番兵(走査停止用)
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
0:角
5:Xマスの内側
1:Cマス
6:第二外周
2:辺の3マス目
7:中央外側
3:辺の中央寄り
8:中央内側
4:Xマス
9:最中央

② 選択肢・モビリティ

  • mobility(合法手数の差):いま自分が打てるマスの数 − 相手が打てるマスの数。
  • pot_mob(潜在モビリティの差):相手石に隣接している空きマスの数。

③ 石の安全性・取られにくさ

  • frontier(開放度の差):上下左右に空きマスがある石の数。「相手の石の数」−「自分の石の数」。
  • stable(確定石の数差):角から辺に沿って連続している、絶対に取り返されない石の数。

④ 角の周辺マス

  • corners(獲得角数):すでに確保している角の数。
  • x_pen(Xマスペナルティ):角が空いている状態でXマスに石を置いているペナルティ。
  • c_pen(Cマスペナルティ):角が空いている状態でCマスに石を置いているペナルティ。
  • corner_access(角への着手権):現時点で「次の1手として角を取る合法手」を持っている数。

⑤ 終盤・決着

  • parity(パリティ):盤面の空きマスが偶数なら+1、奇数なら−1。
  • stones(単純な石数差):「自分の石の数」−「相手の石の数」。

3.3.2. 進行度による「重みの区分線形補間」

リバーシは序盤と終盤で定石が全く異なります。そのため、重み \( w \) を固定せず、盤上の総石数 \( s \) に応じて動的に変化させる3フェーズ補間を導入しました。
序盤・中盤・終盤のアンカーを \( s \in \{4, 34, 64\} \) とし、それぞれで学習した重みベクトルを \( w^{(1)}, w^{(2)}, w^{(3)} \) とします。
現在の石数 \( s \) によって、下記のように重みを切り替えます。

  1. 序盤〜中盤の区間(\( 4 \le s \le 34 \))

\[ w(s) = \left(1 – \frac{s-4}{34-4}\right) w^{(1)} + \frac{s-4}{34-4} w^{(2)} \]

  1. 中盤〜終盤の区間(\( 34 < s \le 64 \))

\[ w(s) = \left(1 – \frac{s-34}{64-34}\right) w^{(2)} + \frac{s-34}{64-34} w^{(3)} \]

3.3.3. Optunaによるハイパーパラメータチューニング

チューニング方法

特徴量を決めたことにより、評価関数の形が定まりました。
ここが最も重要だと思っていますが、次に重みをどのように決定するかを考えました。
重みの推定方法として遺伝的アルゴリズムやOptunaによるハイパーパラメータチューニングがありますが、今回は実装に慣れていて、わかりやすいOptunaで推定することにしました。

当然SASにはOptunaは実装されていないので、一度プログラムをSASからPythonに置き換えました。Claude Codeで実装していましたが、一筋縄ではいかず、テストを何度も繰り返して完全再現しました。
特に、深さは実行機によるため、何パターンか用意し、ローカルのPythonとSAS OnDemand for Academicsで完全一致するかどうかを試していきました。

また、チューニングでは深さも逐次ランダムに変え、実行機によって多少深さがぶれても問題ないように重みを調整しました。

対戦相手

Optunaを回すときに最も重視しなければいけないのは対戦相手です。前回の大会でも私はそれなりに強いリバーシAIを作成していたので、それを使用することにしました。
しかし、そのリバーシAIのみを対戦相手にしてしまうと簡単に過学習してしまいます。そこで、最初の8手はランダムにし、それ以降は過去のリバーシAIを使用しました。
また、評価指標は勝率ではなく石の差にしました。こちらの方が精度が高く、圧勝してくれます。

3.4. 最後の調整

上記が終わったら、その重みをSASのプログラムに反映します。
また、Claude CodeにSASと全く同じロジックをJavaScriptで書いてもらい、デモプレイをします。
私が対戦してみて明らかに弱い部分などがあり、その場合は大体SASプログラムの実装ミスでした。
これを私が確認したり、Claude Codeに修正してもらったりしながら完成形を目指していきます。
また、ハイパーパラメータチューニングを繰り返すことにより、さらに強くしていきました。
最終的には、私では全く歯が立たないリバーシAIができていました。

3.5. 課題

それなりに改良していきましたが、まだまだ改良する余地があります。

  • Optunaのハイパーパラメータチューニングの対戦相手が過去の私のプログラムのみになっているので、様々なリバーシAIを用いて学習したい
  • 別のチューニング方法(遺伝的アルゴリズムなど)を試してみたい
  • もしできるなら、Q-LearningをSASで実装してみたい

4. 最後に

最後まで読んでいただきありがとうございました。
SASリバーシチャンピオンシップは通常のゲームAI作成とは異なり、1から自分で理解して実装する必要があるため、とても面白いですし、基礎から学ぶきっかけにもなります。
また企画があることを期待しております。ありがとうございました!

コメント

※投稿されたコメントは、管理者によって承認された後に掲載されます。