AIゲームの作り方 約10分で読めます

AIと対戦するゲームの作り方|負けないまるばつAI(ミニマックス法)と弱くする方法

「AIと対戦するゲーム」の文字と、背景に映る、まるばつの盤面

AIと対戦するゲームは、すべての手を先まで試して、いちばん良い手を選ぶ「ミニマックス法」で作れます。まるばつ(三目並べ)なら、この方法で、AIは負けなくなります。この記事のコードは、1本のHTMLで動く、まるばつです。

コードは、Nodeで関数をそのまま呼び出して数え、画面は画面を出さないChromeで確かめました。機械学習のAIではなく、ゲームの敵の動きを決める、昔からのAIの作り方です。

この記事で分かること

  • 負けないまるばつAIの、そのまま動くコード全体
  • ミニマックス法の考え方と、調べる局面の数(549,946個)
  • 調べる数を約30分の1にするαβ法と、評価が同じになる確認
  • わざとミスさせて、弱いAIにするときの勝敗の割合
  • 確かめられていないこと

機械学習を使った、AIと会話するゲームの作り方は、AIと会話できるゲームおすすめの記事で扱っています。ここでは、ルールが決まった対戦ゲームの敵を作ります。

この記事の目次
  1. 01負けないAIの考え方
  2. 02コード全体
  3. 03どのくらい強いか
    1. 人は1度も勝てない
    2. 調べる局面の数
  4. 04わざとミスさせて、弱いAIにする
  5. 05確かめられていないこと
  6. 06うまくいかないとき
  7. 07よくある疑問
  8. 08この記事のまとめ

負けないAIの考え方

AIは、自分が打てる手を1つずつ仮に置き、次に相手が打つ手も、決着がつくまで全部試します。決着の局面に点をつけて、いちばん高い点になる手を選びます。点は、AIの勝ちが+1、引き分けが0、負けが-1です。相手も、AIにとっていちばん悪い手(点が低くなる手)を選ぶものとして考えます。

負けないAIの考え方を示した4段の図。1は空きマスに打つ。打てる手を1つずつ仮に置く。2は相手の番を試す。相手も最善を選ぶと考える。3は決着まで繰り返す。勝ちは+1、引き分けは0、負けは-1。4は最高の点の手を選ぶ。同点ならランダムに選ぶ。空の盤面から全部試すと、調べる局面は549,946個。αβ法を使うと18,297個
この記事のコードの処理の順です

コード全体

次のコードを、index.htmlという名前で保存してブラウザで開くと、そのまま遊べます。サーバーは要りません。プレイヤーは「○」で先手、AIは「×」で後手です。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>まるばつ(AIと対戦)</title>
<style>
body{font:18px/1.6 sans-serif;text-align:center}
#board{display:grid;grid-template-columns:repeat(3,90px);gap:6px;justify-content:center;margin:16px auto}
#board button{width:90px;height:90px;font-size:44px}
</style>
</head>
<body>
<h1>まるばつ</h1>
<p>あなたは「○」。AIは「×」です。</p>
<div id="board"></div>
<p id="msg"></p>
<button id="reset">もう一度</button>
<script>
const LINES = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
// 盤面は9文字の配列。'O'(人)・'X'(AI)・null(空き)
function winner(b) {
  for (const [a, c, d] of LINES) if (b[a] && b[a] === b[c] && b[a] === b[d]) return b[a];
  return b.every((v) => v) ? 'draw' : null;
}

let nodes = 0;
// ミニマックス法: AI(X)にとっての良さを、勝ち +1・引き分け 0・負け -1 で返す
function minimax(b, turn) {
  nodes++;
  const w = winner(b);
  if (w === 'X') return 1;
  if (w === 'O') return -1;
  if (w === 'draw') return 0;
  let best = turn === 'X' ? -2 : 2;
  for (let i = 0; i < 9; i++) {
    if (b[i]) continue;
    b[i] = turn;
    const s = minimax(b, turn === 'X' ? 'O' : 'X');
    b[i] = null;
    best = turn === 'X' ? Math.max(best, s) : Math.min(best, s);
  }
  return best;
}

// 速くしたいとき用: αβ法。結果はミニマックスと同じで、調べる局面だけが減る
function alphabeta(b, turn, alpha = -2, beta = 2) {
  nodes++;
  const w = winner(b);
  if (w === 'X') return 1;
  if (w === 'O') return -1;
  if (w === 'draw') return 0;
  for (let i = 0; i < 9; i++) {
    if (b[i]) continue;
    b[i] = turn;
    const s = alphabeta(b, turn === 'X' ? 'O' : 'X', alpha, beta);
    b[i] = null;
    if (turn === 'X') alpha = Math.max(alpha, s); else beta = Math.min(beta, s);
    if (alpha >= beta) break;
  }
  return turn === 'X' ? alpha : beta;
}

// 最善手を選ぶ。mistake(0〜1)の確率で、わざとランダムな手を打つ
function aiMove(b, mistake = 0) {
  const empty = [];
  b.forEach((v, i) => { if (!v) empty.push(i); });
  if (Math.random() < mistake) return empty[Math.floor(Math.random() * empty.length)];
  let bestScore = -2, moves = [];
  for (const i of empty) {
    b[i] = 'X';
    const s = minimax(b, 'O');
    b[i] = null;
    if (s > bestScore) { bestScore = s; moves = [i]; } else if (s === bestScore) moves.push(i);
  }
  return moves[Math.floor(Math.random() * moves.length)];
}

if (typeof document !== 'undefined') {
  let board, over;
  const el = document.getElementById('board'), msg = document.getElementById('msg');
  function draw() {
    el.innerHTML = '';
    board.forEach((v, i) => {
      const b = document.createElement('button');
      b.textContent = v === 'O' ? '○' : v === 'X' ? '×' : '';
      b.onclick = () => play(i);
      el.appendChild(b);
    });
  }
  function end() {
    const w = winner(board);
    if (!w) return false;
    over = true;
    msg.textContent = w === 'draw' ? '引き分けです' : w === 'O' ? 'あなたの勝ちです' : 'AIの勝ちです';
    return true;
  }
  function play(i) {
    if (over || board[i]) return;
    board[i] = 'O';
    if (!end()) { board[aiMove(board)] = 'X'; end(); }
    draw();
  }
  function reset() { board = Array(9).fill(null); over = false; msg.textContent = ''; draw(); }
  document.getElementById('reset').onclick = reset;
  reset();
}
</script>
</body>
</html>
コードを動かした画面。人が中央と左上と下中央に打ち、AIは右の列を3つそろえて勝った。人が先手でも、全681通りの打ち方で、人は1度も勝てない
画面は、画面を出さないChromeで撮りました

人が最初に中央、次に左上に打ったあと、AIは右の列をそろえ、人が3手目で防げなかった局面で勝ちました。

どのくらい強いか

人は1度も勝てない

人が先手のときの、人の打ち方を全部試しました。AIは、同点の手が複数あるときは、最初の手を選ぶようにして固定しています。人の打ち方は681通りあり、結果は、AIの勝ちと引き分けだけで、人の勝ちは0でした。

調べる局面の数

空の盤面から、AIが最初の1手を決めるまでに調べる局面は、ミニマックス法で549,946個でした。αβ法を使うと18,297個で、約30分の1です。

調べる局面の数と、かかった時間の図。ミニマックス法は549,946個で約50ミリ秒。αβ法は18,297個で約2ミリ秒。評価はどちらも引き分けで、2手目までの72通りの局面でも評価が一致した
時間は、このパソコンでの1回の測定です

αβ法は、「これ以上調べても、結果が変わらない」と分かった枝を、途中でやめる方法です。コードのalphabetaがそれで、結果はミニマックスと同じになります。空の盤面と、1手目と2手目の72通りの局面で、評価が全部一致しました。まるばつでは、どちらでも一瞬で終わります。盤面が大きいゲームでは、調べる数が急に増えるので、αβ法が効いてきます。この点は、まるばつ以外では確かめていません。

わざとミスさせて、弱いAIにする

子ども向けのゲームなどでは、負けないAIは楽しくありません。コードのaiMoveには、mistake(0から1)という引数があります。この確率で、最善手ではなく、でたらめな手を打ちます。

でたらめに打つ相手と、1万回ずつ対戦させた結果です。AIは後手です。

わざとミスする確率と勝敗の割合の図。ミス確率0パーセントではAIが78パーセント勝ち、引き分け22パーセント。25パーセントではAI勝ち64パーセント、引き分け21パーセント、人の勝ち16パーセント。50パーセントではAI勝ち51パーセント、人の勝ち30パーセント。75パーセントではAI勝ち40パーセント、人の勝ち45パーセント。100パーセントではAI勝ち29パーセント、人の勝ち59パーセント
相手はでたらめに打つ人です

ミス確率が0%のときは、AIは負けず、約78%で勝ちました。ミス確率を上げると、AIの勝ちが減り、100%(毎回でたらめ)では、先手の相手が約59%で勝ちました。先手が有利なゲームなので、でたらめな相手同士でも、先手が勝ちやすいためです。強さの目安としては、ミス確率を25%から50%にすると、AIは勝ち越しながら、人が勝てる場面も出ます。ただし、実際の人が遊んで、どう感じるかは確かめていません。

確かめられていないこと

  • 実際に人が遊んだときの楽しさ勝敗の割合は、でたらめに打つ相手との結果です。人が相手の場合は、確かめていません。
  • オセロ・将棋など、盤面が大きいゲームでの速さ(探索の深さを区切る方法は、この記事では扱っていません)
  • AIが先手のときの勝敗の割合
  • スマートフォンでの操作
  • 機械学習で作るAI(強化学習など)との比較

確かめていないものは、動かして確かめてから、別の記事で扱います。

うまくいかないとき

症状 確認すること
マスを押しても何も起きない コンソールにエラーがないかを見ます。コードを途中で切って貼っていないかも確かめます
AIが弱い aiMoveの呼び出しで、mistakeを渡していないかを確かめます。何も渡さなければ0(最善手)です
AIが負ける 勝ち+1・負け-1の点の向きが、AI(X)の側になっているかを確かめます
盤面を大きくしたら止まる 調べる局面が急に増えます。αβ法に替え、探索の深さを区切ります

関連記事AI会話ゲームの作り方|NPCと話すコード全体と、20往復の費用の試算【2026年版】

よくある疑問

先にAIが打つ形にできますか

aiMoveを、最初に呼べば作れます。ただ、この記事では、AIが先手の勝敗の割合を確かめていません。

ミニマックス法は、AIと呼べますか

ゲームでは、敵の動きを決める仕組みを、AIと呼ぶのが一般的です。ChatGPTのような、学習済みのモデルを使った仕組みとは別のものです。

ほかのゲームにも使えますか

交互に打ち、運の要素がないゲームに向いています。オセロや将棋では、盤面が大きいため、探索を区切る工夫が要ります。この記事では確かめていません。

この記事のまとめ

この記事のまとめ

  • ミニマックス法で、まるばつは負けないAIになる(人が先手でも、全681通りで人の勝ち0)
  • αβ法で、調べる局面を549,946個から18,297個に減らせる
  • わざとミスする確率を入れると、弱いAIにできる
  • 人が遊んだときの楽しさと、大きな盤面での速さは、確かめていない