二次元配列による盤面管理と合法手探索の仕組み
本ツールにおけるオセロ盤面は,要素数8×8の二次元配列(Board型)によって表現および管理されています.配列内の各セル(CellValue)には,0が空きマス,1が黒石(先手),2が白石(後手)として整数値が割り当てられており,初期状態では配列の中心となる座標[3][3]および[4][4]に白石(2)が,[3][4]および[4][3]に黒石(1)が配置される仕様です.
ゲームの進行において最も根幹となる「どこに石を置けるか」を判定する合法手探索プロセス(getValidMoves)は,この配列データに対して全探索を行うことで実現されています.
具体的な処理手順として,まず盤面上のすべての空きマス(値が0のセル)を対象とし,そのマスを起点とした8方向(上下左右および斜めの45度刻み)のベクトル配列を定義して走査を開始します.走査方向において隣接するセルが相手の石であるかを確認し,相手の石が連続している限り同じベクトル方向へ座標を進めながら,その経路上の座標を一時リスト(path)に保持します.
相手の石が途切れた直後のセルを調べ,それが自分の石であった場合に限り,挟み込みが成立したとみなして一時リストの座標群を「反転可能なマスのリスト(flipped)」として確定させます.
この探索処理による結果は,盤面上で石を置ける座標(r, c)と,その位置に置いた際に反転する石の座標群をまとめたオブジェクトの配列として出力されます.実務上の使い方として,この配列データはユーザーインターフェースに直結しており,プレイヤーの手番中には,合法手に該当するマスの背景にCSSアニメーション(animate-ping)を用いたハイライト表示を適用することで,視覚的なガイドを提供しています.
制約として,この探索アルゴリズムは盤面の境界(インデックス0未満,または7超過)に到達した時点で当該方向への探索を打ち切るよう設計されています.また,配列外参照を防ぐための境界チェックは反復処理の条件式に組み込まれており,無効なマスやすでに石が置かれているマスに対する探索は実行前に除外されるため,処理の無駄を省いた順次探索となっています.
結果の読み方としては,この関数が返す配列の要素数が1以上であれば行動可能,0であれば打てる場所がないと判断されるため,次章のパス判定やゲーム終了判定の基準値としてそのまま流用される仕組みとなっています.
石の反転処理とパス判定のアルゴリズム
プレイヤーまたはAIによって盤面に石が置かれた際の状態更新は,非破壊的な配列操作(getNextBoardState)を通じて実行されます.Reactの状態管理パラダイムに則り,現在の盤面配列(currentBoard)を直接書き換えることはせず,mapメソッドを用いて行ごとにスプレッド構文を展開し,新しい二次元配列を生成します.
その後,着手した座標(r, c)に手番のプレイヤーを示す値(1または2)を代入し,第1章の探索で取得済みの「反転可能なマスのリスト(flipped)」に含まれるすべての座標に対して反復処理を行い,同様に手番のプレイヤーの値を上書きします.この新しい配列を状態(State)としてセットすることで,画面上の石の色が一斉に更新される処理となっています.
オセロの基本ルールであるパス処理と終局判定については,合法手の有無に基づいて制御されます.手番が切り替わる際に現在側の合法手リストを取得し,その配列長が0であった場合をパスと判定します.パスが発生した場合,即座に相手の合法手も確認し,相手の配列長も0であれば「両者ともに打てる場所がない」という条件を満たすため,ゲーム終了(isGameOver)フラグが真となります.
この際,盤面上の1と2の個数を単純集計(counts処理)し,数の多い方を勝者とするメッセージが出力されます.
一方で,片方のみが打てない場合は手番のスキップ処理が行われます.プレイヤー側が打てない場合は,メッセージ領域にパスを通知したうえで,AIの連続手番処理(runAiMove)を再帰的に呼び出します.AI側が打てない場合は手番をプレイヤーに戻し,ユーザーのクリック入力を待機する状態へ遷移します.
「1手戻す」では,人間が合法な着手をする直前の盤面だけを履歴に保存します.この操作を実行すると人間の直前着手の直前へ戻り,その後にパスを挟んでAIが連続して打った分もまとめて取り消され,正しい人間の手番へと戻ります.
本機能はAI思考中には戻せません.人間がまだ打っていない状態では戻せず,白を選んだ場合もAIの初手だけを戻すことはしません.リスタート操作を行うとゲームは最初から開始され,対局中の着手は合法手だけを受け付けます.
プレイヤーに合わせた三段階の難易度設定
本ツールに実装されているAIは,プレイスタイルに応じて「初級(easy)」「中級(normal)」「上級(hard)」の3つの難易度(difficulty)を切り替えることが可能です.この難易度設定は,AIが次の一手を決定する際の選択アルゴリズムの分岐として直接機能します.
初級設定においては,盤面の有利不利を一切考慮しない処理が適用されます.取得した合法手リストの配列長に対して,JavaScriptのMath.random関数を用いた乱数生成と小数点以下切り捨て(Math.floor)を組み合わせ,配列のインデックスを無作為に指定します.
これにより,置ける場所の中からMath.randomに基づく擬似ランダムな一手を選択するため,ユーザーは予測不可能な動きへの対応や,ハンデ戦のような感覚でプレイすることができます.
中級設定では,現在の局面から1手先(次に自分が石を置いた直後の盤面)のみを予測する探索が行われます.すべての合法手に対して,仮にそのマスへ着手した場合の新しい盤面配列を生成し,次章で詳述する評価関数(evaluateBoard)に渡して盤面のスコアを算出します.
各候補手のスコアを比較し,最も高い数値(bestScore)を記録した手を最終的な着手として選択します.この処理により,直近で最も有利になる手(例えば角を取る,多数の石を返すなど)を機械的に選び出します.
上級設定では,第5章で解説するアルファベータ探索アルゴリズム(minimax関数)を呼び出します.探索の深さは引数として「4」が固定で渡されており,相手の最善手も考慮したうえで4手先の局面まで展開をシミュレーションします.
実務上の使い方として,ユーザーは設定パネルから任意の手番(先手・後手)を選択でき,選ばなかった方の色が自動的にAIに割り当てられます.AIのターンが回ってきた際(runAiMove)には,思考中の表示を先に描画するため,setTimeout関数で約600ミリ秒後に同期探索を開始します.
探索そのものはメインスレッドで実行されます.これにより「AIが思考中...」というメッセージを画面に描画する余白を確保し,視覚的なフィードバックを与えています.
制約として,中級および上級の処理はJavaScriptのメインスレッド上で同期的に実行されるため,探索対象のノード数が増加する局面(特に合法手が多い中盤)では,ブラウザの描画やユーザー操作が一時的にブロックされる仕様となっています.相手AIの着手決定にはWeb Workerを用いていないため,処理負荷とレスポンスのバランスを取る目的で,上級の探索深度は4に固定されています.
固定ウェイトと合法手数差を用いた評価関数
中級AIの着手決定および上級AIの探索末端における局面評価は,単一の評価関数(evaluateBoard)によって処理されます.この関数は,現在の盤面配列(currentBoard)と評価対象のプレイヤー色(player)を引数として受け取り,最終的な盤面の優劣を整数値(score)として返却します.
スコア算出のロジックは,「マス位置の固定ウェイト」と「行動力(開放度)」の2つの評価軸を加算する構造で構成されています.
第1の評価軸であるマス位置の固定ウェイトは,8×8の二次元配列(EVAL_BOARD)として静的に定義された得点表を参照します.オセロにおける一般的な定石を数値化しており,合法手で置かれた後は反転しない四隅の角([0][0], [0][7], [7][0], [7][7])には最も高い「120」というプラス評価が設定されています.
逆に,相手に角を取られやすくなる角の斜め内側のマス(いわゆるX打ち)には「-40」,角に隣接する辺のマス(C打ち)には「-20」という強いマイナス評価(ペナルティ)が与えられています.外周の辺は「20」または「5」,中央付近のマスは「3」または「5」と相対的に低いプラス値となっています.
盤面全体を走査し,自分の石があるマスのウェイトを加算,相手の石があるマスのウェイトを減算することで,位置取りの優位性を算出します.
第2の評価軸である行動力は,両者が選択可能な「合法手の数の差分」を用いて計算されます.現在の盤面において,自身の合法手リストの配列長(myMoves)から,相手の合法手リストの配列長(oppMoves)を減算し,その差分に対して一律で「10」の係数を乗算した値をスコアに加算します.
これにより,自分の置ける選択肢が多く,相手の選択肢が少ない(相手を追い詰めている)状態であるほど,評価値が底上げされる仕組みです.
結果の読み方として,返却されるスコアが正の数であれば自分に有利な局面,負の数であれば不利な局面と解釈され,この数値の大小のみを用いてAIは行動を決定します.
制約として,この評価関数はゲームの進行度(序盤・中盤・終盤)に関わらず,常に同じ固定ウェイトと係数を使用します.進行度に応じて角の価値を変動させたり,辺の確定石(安定度)を動的に計算したりする処理は含まれていません.また,ゲーム終盤において残りの空きマスをすべて読み切り,最終的な石の枚数差や完全な勝敗を評価値として返す「終局読み切り(完全読み)」のソルバーは実装されていないため,最後までウェイトベースの近似的な評価に依存する仕様となっています.
アルファベータ枝刈りを適用した固定深度探索
上級AIの根幹を成す思考ルーチンには,ミニマックス法(minimax)をベースとし,計算量を削減するためのアルファベータ枝刈り(Alpha-Beta Pruning)を組み合わせた再帰関数が実装されています.この関数は,現在の盤面状態,残りの探索深度(depth),アルファ値(最小保証スコア),ベータ値(最大許容スコア),手番の真偽値(maximizingPlayer),および現在評価中のプレイヤー色を引数として受け取り,シミュレーション結果の最適スコアと,そのスコアをもたらす着手座標を返します.
処理フローとして,関数が呼び出されるとまず現在の合法手リストを取得します.探索が指定された深さ上限(depth === 0)に達した場合は,それ以上の盤面展開を行わず,第4章の評価関数(evaluateBoard)を呼び出してその時点の静的なスコアを返し,再帰を終了(終端ノードとして処理)します.
パスが発生する局面では,次の分岐を行います.現在の手番に合法手がない(validMoves.length === 0)場合,即座に相手の合法手を確認します.相手も打てない場合はゲーム終了状態であるため,その局面の評価値を返して探索を打ち切ります.相手が打てる場合は,盤面状態を変更せずに探索深度(depth)を1減らし,手番フラグ(maximizingPlayer)を反転させたうえで,再帰的にminimax関数を呼び出して探索を継続します.
これにより,パスを挟んだ連続手番のシミュレーションが正しく評価されます.
枝刈りのメカニズムとして,自分の手番(maximizingPlayerが真)では,初期値-Infinityの評価値(maxEval)を順次更新しながら最も高いスコアを探します.この過程でアルファ値を更新し,ベータ値以下になった場合(beta <= alpha)は,相手がこれ以上良い条件を許さない(別の分岐ですでに不利な結果が確定している)と判断し,残りの合法手の探索をループから脱出(break)して省略します(βカット).
逆に相手の手番では最小スコアを探しながらベータ値を更新し,条件を満たせば探索を打ち切ります(αカット).
実務上の使い方として,この関数は上級AIの着手決定だけでなく,次章のヒートマップ描画処理からも異なる深度指定で呼び出される汎用的な設計となっています.
制約として,探索順序を最適化する処理(評価値の高い手を先に探索するなどの事前ソート)は実装されておらず,配列に格納された合法手の順序でそのまま探索を行うため,枝刈りの効率は盤面状況に依存します.また,反復深化(Iterative Deepening)や持ち時間による動的な深度調整などの機能はなく,上級AIでは固定深度4の範囲で評価値を選ぶ仕様です.
相対ヒートマップの算出と活用の限界
本ツールに搭載されている「候補手の相対評価」機能(ヒートマップ)は,プレイヤーのターンにおける各合法手の価値を可視化する独自の解析システムです.この機能は,UIのトグルスイッチ(showHeatmap)が有効であり,かつ現在がプレイヤーの手番である場合にのみ作動し,ヒントの計算が実行されます.
ヒント機能は,プレイヤーの各候補手を打った直後の局面から,指定された強さに応じて残り0手・2手・4手を探索することで,自身の候補手を含めた合計1手先・3手先・5手先の先読み計算を行います.探索は相手AIの難易度設定とは完全に独立して実行され,固定のマス重みと合法手数の差分を用いてプレイヤー側の視点で評価値を算出します.
探索中にパスが発生した場合も1手分の深さを消費し,両者ともに着手できずゲームが終了する局面ではその時点で探索を早期終了します.
算出された評価値のリストから,その局面内における最大スコア(maxScore)と最小スコア(minScore)を抽出します.この差分(range)を用いて,各候補手のスコアを「30から90」の数値範囲に収まるよう一次変換(正規化)を行います.計算式は,スコアから最小値を引いた値を差分で割り,60を掛けた後に30を足す構成となっており,小数点以下は四捨五入(Math.round)されます.
すべての候補手が同じ評価値で差分が0となる場合は,デフォルト値として一律「50」が割り当てられます.
盤面に表示される30〜90の数値は,現在の局面における候補手同士の相対的な評価を比較するためのスコアです.数値に応じて色分け表示され,75以上はアンバー(オレンジ)色,50〜74はエメラルド色,49以下はブルー色のスコア帯として視覚化されます.これらの数値や色は勝率や最善手の保証ではなく,その局面内の選択肢を相対的に比較する目安として利用できます.
表示される90のスコアは局面内での相対評価であり,90%の勝率や勝利・最善手を保証するものではなく,異なるターン間で数値を直接比較して絶対的な形勢を判断することはできません. 強化(5手先)は端末のブラウザ内別処理で計算し,完了までヒントの計算中表示を出します.
環境により動作しない場合は標準(3手先)をご利用いただけます.ヒントをOFFにすると計算は即時停止し,相手AIの対戦処理はそのまま継続します.手数を深く読んでも勝利や最善手を保証するものではありません.