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

AIと対戦するゲームは、すべての手を先まで試して、いちばん良い手を選ぶ「ミニマックス法」で作れます。まるばつ(三目並べ)なら、この方法で、AIは負けなくなります。この記事のコードは、1本のHTMLで動く、まるばつです。
コードは、Nodeで関数をそのまま呼び出して数え、画面は画面を出さないChromeで確かめました。機械学習のAIではなく、ゲームの敵の動きを決める、昔からのAIの作り方です。
この記事で分かること
- 負けないまるばつAIの、そのまま動くコード全体
- ミニマックス法の考え方と、調べる局面の数(549,946個)
- 調べる数を約30分の1にするαβ法と、評価が同じになる確認
- わざとミスさせて、弱いAIにするときの勝敗の割合
- 確かめられていないこと
機械学習を使った、AIと会話するゲームの作り方は、AIと会話できるゲームおすすめの記事で扱っています。ここでは、ルールが決まった対戦ゲームの敵を作ります。
この記事の目次
負けないAIの考え方
AIは、自分が打てる手を1つずつ仮に置き、次に相手が打つ手も、決着がつくまで全部試します。決着の局面に点をつけて、いちばん高い点になる手を選びます。点は、AIの勝ちが+1、引き分けが0、負けが-1です。相手も、AIにとっていちばん悪い手(点が低くなる手)を選ぶものとして考えます。

コード全体
次のコードを、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手目で防げなかった局面で勝ちました。
どのくらい強いか
人は1度も勝てない
人が先手のときの、人の打ち方を全部試しました。AIは、同点の手が複数あるときは、最初の手を選ぶようにして固定しています。人の打ち方は681通りあり、結果は、AIの勝ちと引き分けだけで、人の勝ちは0でした。
調べる局面の数
空の盤面から、AIが最初の1手を決めるまでに調べる局面は、ミニマックス法で549,946個でした。αβ法を使うと18,297個で、約30分の1です。

αβ法は、「これ以上調べても、結果が変わらない」と分かった枝を、途中でやめる方法です。コードのalphabetaがそれで、結果はミニマックスと同じになります。空の盤面と、1手目と2手目の72通りの局面で、評価が全部一致しました。まるばつでは、どちらでも一瞬で終わります。盤面が大きいゲームでは、調べる数が急に増えるので、αβ法が効いてきます。この点は、まるばつ以外では確かめていません。
わざとミスさせて、弱いAIにする
子ども向けのゲームなどでは、負けないAIは楽しくありません。コードのaiMoveには、mistake(0から1)という引数があります。この確率で、最善手ではなく、でたらめな手を打ちます。
でたらめに打つ相手と、1万回ずつ対戦させた結果です。AIは後手です。

ミス確率が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にできる
- 人が遊んだときの楽しさと、大きな盤面での速さは、確かめていない












