Excel VBAで実装する将棋エンジン:千日手と連続王手の千日手の判定ロジック
将棋プログラムをExcel VBAで構築する際、最も難易度が高く、かつ避けては通れないのが「千日手」の判定です。千日手とは、同一局面が4回出現することで引き分けとなるルールを指します。また、これに「連続王手の千日手」という特殊ルールが絡むと、判定ロジックは一気に複雑化します。本稿では、VBAを用いてこの複雑な判定システムをいかに効率的かつ正確に実装するか、そのアーキテクチャを詳細に解説します。
千日手判定の理論的背景とデータ構造
千日手を判定するためには、対局開始からの全局面を記録し、現在の局面と一致するものが過去に何回出現したかをカウントする必要があります。ここで重要なのは「局面の定義」です。
盤上の駒の配置情報(81マスの状態)だけでなく、手番、持ち駒、そして「直前の指し手」や「千日手カウンター」を管理する構造体(Type)を定義するのが定石です。
VBAでこれを実現するには、Dictionaryオブジェクトを活用し、局面を文字列ハッシュ化して格納するのが最もパフォーマンスに優れています。例えば、盤面を「歩:P, 香:L, …」のように文字列に変換し、それをキーとして出現回数を管理します。
連続王手の千日手という難問
単なる千日手と異なり、連続王手の千日手は「王手をかけ続けた側が反則負けとなる」というルールです。これを判定するには、単に「局面が一致したか」を見るだけでなく、その局面に至るまでの過程で「王手をかけ続けていたか」というフラグの追跡が必要です。
実装上のポイントは、各指し手に対して「王手判定」を行い、それをスタック構造として保持することです。直近の指し手が王手であり、かつその前の指し手も王手であった場合、その「連続王手回数」を記録しておきます。もし同一局面が4回繰り返された際、連続王手回数がその期間中ずっと1以上であれば、王手をかけていた側が負けとなります。
サンプルコード:千日手判定エンジン
以下に、局面のハッシュ化と千日手判定を行うためのコアロジックを示します。
Option Explicit
' 局面情報を保持する構造体
Public Type BoardState
BoardHash As String
IsCheck As Boolean
End Type
' 千日手判定クラスのロジック抜粋
Public Function CheckSennichite(ByRef History As Collection) As Integer
' Historyには過去のBoardStateが格納されている
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long
Dim state As BoardState
Dim key As String
' 過去の局面をスキャン
For i = 1 To History.Count
state = History(i)
key = state.BoardHash
If dict.Exists(key) Then
dict(key) = dict(key) + 1
Else
dict.Add key, 1
End If
' 同一局面が4回出現したか
If dict(key) >= 4 Then
' 連続王手の判定ロジックへ分岐
If IsContinuousCheck(History, i) Then
CheckSennichite = 2 ' 連続王手千日手による負け
Exit Function
Else
CheckSennichite = 1 ' 通常の千日手(引き分け)
Exit Function
End If
End If
Next i
CheckSennichite = 0 ' 千日手なし
End Function
' 連続王手の判定補助関数
Private Function IsContinuousCheck(ByRef History As Collection, ByVal CurrentIdx As Long) As Boolean
' 直近の指し手が連続して王手であったかを逆順に検証する
Dim i As Long
' 簡易的な判定ロジック:直近の局面がすべて王手状態かを確認
For i = CurrentIdx To CurrentIdx - 3 Step -1
If Not History(i).IsCheck Then
IsContinuousCheck = False
Exit Function
End If
Next i
IsContinuousCheck = True
End Function
実務アドバイス:パフォーマンス最適化の極意
VBAで将棋エンジンを開発する場合、最大の敵は「実行速度」です。特に千日手判定は、すべての指し手ごとに毎回全履歴を走査すると、対局が進むにつれて指数関数的に処理が重くなります。
1. ハッシュ値のキャッシュ:局面のハッシュ生成は、指し手ごとに盤面全体をスキャンするのではなく、直前の指し手による差分更新(差分計算)を用いることで、計算量をO(1)に抑えることが可能です。
2. コレクションの制限:100手を超えるような対局では、古い局面をメモリから解放するのではなく、最新の局面のみを比較対象としてインデックス化しておく工夫が必要です。
3. 判定タイミング:すべての指し手で判定するのではなく、王手がかかった局面、あるいは持ち駒が変化した局面など、千日手が成立しうる「イベント」が発生した時のみ判定関数を呼び出すように設計してください。
また、Excelのセルに盤面を表示している場合、千日手判定のたびに画面描画(ScreenUpdating)を行うと劇的に遅くなります。必ず `Application.ScreenUpdating = False` を活用し、論理演算のみを先行させてください。
デバッグとテストケースの構築
千日手のような複雑なルールは、単体テストが不可欠です。あえて千日手になるような棋譜(例えば、飛車を往復させるだけの無意味な棋譜)を作成し、4回目に正しく「千日手成立」のフラグが立つかを検証してください。
特に「連続王手の千日手」はエッジケースが多く発生します。
・王手をかけ続けたが、途中で王手にならない手があった場合
・千日手成立直前に王手を解除した場合
これらのケースで誤判定がないか、ユニットテストを作成して網羅的にテストすることを強く推奨します。VBAのイミディエイトウィンドウを活用し、局面ハッシュが正しく生成されているかを随時確認するデバッグ手法が有効です。
まとめ:VBAで将棋を極めるために
千日手と連続王手の千日手判定は、将棋プログラムにおける論理的思考の集大成です。VBAは決して高速な言語ではありませんが、適切なデータ構造(DictionaryやCollectionの活用)と、差分更新による計算量の削減を行うことで、十分に実用的な将棋エンジンを構築することが可能です。
今回解説したロジックをベースに、さらに「禁じ手判定」や「AIの評価関数」を組み込んでいくことで、Excelという身近な環境で本格的な知能ゲームを動かすことができます。プログラミングの醍醐味は、こうした複雑なルールをいかにシンプルで美しいコードに落とし込むかという点にあります。ぜひ、ご自身の将棋エンジンにこのロジックを実装し、盤上の熱い戦いをコードの上で再現してください。
