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

脱出ゲームの作り方|HTMLで作る手順と、詰みをコードで見つける方法

「脱出ゲームを作る」の文字と、背景に映る、鍵を選ぶ画面

脱出ゲームは、「必要なもの」と「手に入るもの」を書いた行動のリストで作れます。この作り方だと、プログラムが、最初の状態から行ける状態を全部たどって、クリアできるか、詰む場面がないかを数えてくれます。この記事のコードは、小さな部屋から脱出する1本のHTMLで、詰みの検査も付いています。

コードは、画面を出さないChromeで動かして確かめました。クリアまでの手数と、わざと壊した部屋での検査の結果も、実際に数えた値です。

この記事で分かること

  • 脱出ゲームの行動を、データで書く方法
  • そのまま動くコード全体(クリアの検査と、詰みの検知つき)
  • この部屋の状態の数・最短の手数・詰みの数(実測)
  • 壊した部屋を検査して、クリアできないことを見つけた結果
  • 確かめられていないこと

分岐のある物語を作る場合は、AIでゲームブックを作る方法で、行き止まりの見つけ方を扱っています。HTML1枚で遊べるゲームの基本は、ブラウザゲームの作り方にあります。

この記事の目次
  1. 01脱出ゲームの仕組み
  2. 02コード全体
  3. 03状態を全部たどって数える
  4. 04壊した部屋を検査する
  5. 05確かめられていないこと
  6. 06うまくいかないとき
  7. 07よくある疑問
  8. 08この記事のまとめ

脱出ゲームの仕組み

1つの行動は、次の3つで書きます。

  • needs:その行動をするのに必要な、持ち物や手がかり
  • gives:その行動で手に入るもの
  • consumes:その行動で、なくなるもの

ゲームの状態は、「いま持っているもの」と「済んだ行動」の集まりです。今できる行動は、needsがそろっていて、まだ済んでいない行動です。

この部屋の正解は6手で罠が1つ。絵画を調べて314を知る、本棚を調べて古い本、古い本を読んで手がかり、引き出しを開けて小さな鍵、金庫を開けて扉の鍵、扉を開けて脱出。小さな鍵を手に入れたあとに、暖炉に投げ込む罠があり、鍵が消えて金庫が開かなくなる
この記事の部屋の、正解の手順と罠です

コード全体

次のコードを、index.htmlという名前で保存してブラウザで開くと、そのまま遊べます。サーバーは要りません。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>小さな部屋からの脱出</title>
<style>
body{font:17px/1.8 sans-serif;max-width:560px;margin:0 auto;padding:16px}
button{display:block;width:100%;margin:6px 0;padding:10px;font-size:16px;text-align:left}
#log{min-height:5em;border:1px solid #bbb;padding:8px;background:#fafafa}
#inv{color:#555}
</style>
</head>
<body>
<h1>小さな部屋からの脱出</h1>
<p id="log">薄暗い部屋に閉じ込められた。出口は、鍵のかかった扉だけだ。</p>
<p id="inv"></p>
<div id="acts"></div>
<script>
// 1. ゲームのデータ。needs=必要な持ち物・手がかり、gives=手に入るもの、consumes=なくなるもの
const GOAL = 'flag:escaped';
const ACTIONS = [
  { id: 'picture', text: '絵画を調べる', gives: ['flag:number'], msg: '絵の裏に「314」と書いてある。' },
  { id: 'shelf', text: '本棚を調べる', gives: ['古い本'], msg: '古い本を見つけた。' },
  { id: 'book', text: '古い本を読む', needs: ['古い本'], gives: ['flag:hint'], msg: '「鍵は、机の引き出しの奥」と書いてある。' },
  { id: 'drawer', text: '机の引き出しを開ける', needs: ['flag:hint'], gives: ['小さな鍵'], msg: '奥から小さな鍵が出てきた。' },
  { id: 'safe', text: '金庫を開ける(314)', needs: ['flag:number', '小さな鍵'], gives: ['扉の鍵'], msg: '金庫の中に、扉の鍵があった。' },
  { id: 'burn', text: '小さな鍵を暖炉に投げ込む', needs: ['小さな鍵'], consumes: ['小さな鍵'], msg: '鍵は燃えて、灰になった。' },
  { id: 'door', text: '扉を開ける', needs: ['扉の鍵'], gives: [GOAL], msg: '扉が開いた。外の光がまぶしい。' },
];

// 2. 状態は「持っているものと、済んだ行動」の集合。今できる行動を調べる
const done = (s, a) => s.has('done:' + a.id);
function available(s, actions = ACTIONS) {
  return actions.filter((a) => !done(s, a) && (a.needs || []).every((n) => s.has(n)));
}
function apply(s, a) {
  const t = new Set(s);
  t.add('done:' + a.id);
  (a.consumes || []).forEach((c) => t.delete(c));
  (a.gives || []).forEach((g) => t.add(g));
  return t;
}
const key = (s) => [...s].sort().join('|');

// 3. 検査: 最初の状態から、行ける状態を全部たどる
function explore(actions = ACTIONS, start = new Set()) {
  const seen = new Map([[key(start), { s: start, dist: 0 }]]);
  const queue = [start];
  while (queue.length) {
    const s = queue.shift();
    for (const a of available(s, actions)) {
      const t = apply(s, a), k = key(t);
      if (!seen.has(k)) { seen.set(k, { s: t, dist: seen.get(key(s)).dist + 1 }); queue.push(t); }
    }
  }
  const win = [...seen.values()].filter((v) => v.s.has(GOAL));
  // 詰み: その状態から、脱出できる状態へ行けない
  const canWin = (st) => explore1(st, actions);
  const stuck = [...seen.values()].filter((v) => !v.s.has(GOAL) && !canWin(v.s));
  return { states: seen.size, shortest: win.length ? Math.min(...win.map((v) => v.dist)) : null, stuck: stuck.length };
}
function explore1(start, actions) {
  const seen = new Set([key(start)]), q = [start];
  while (q.length) {
    const s = q.shift();
    if (s.has(GOAL)) return true;
    for (const a of available(s, actions)) { const t = apply(s, a), k = key(t); if (!seen.has(k)) { seen.add(k); q.push(t); } }
  }
  return false;
}

// 4. 画面
let state = new Set();
const log = document.getElementById('log');
function render() {
  document.getElementById('inv').textContent = '持ち物:' + ([...state].filter((x) => !x.includes(':')).join('、') || 'なし');
  const box = document.getElementById('acts'); box.innerHTML = '';
  if (state.has(GOAL)) return;
  if (!explore1(state, ACTIONS)) { log.textContent += '\n(もう、この部屋から出られない…)'; return; }
  for (const a of available(state)) {
    const b = document.createElement('button'); b.textContent = a.text;
    b.onclick = () => { state = apply(state, a); log.textContent = a.msg; render(); };
    box.appendChild(b);
  }
}
render();
</script>
</body>
</html>
動かした画面。左は、小さな鍵を手に入れた場面で、金庫を開けるか、鍵を暖炉に投げ込むかを選べる。右は、鍵を暖炉に投げ込んだあと。もうこの部屋から出られないと表示され、押せるボタンが1つもない
罠を選ぶと、ゲームが詰みを教えます

画面に出るボタンは、いま選べる行動だけです。罠の行動(小さな鍵を暖炉に投げ込む)を選ぶと、ゲームは、その状態からクリアできるかを調べます。クリアできないときは「もう、この部屋から出られない…」と表示して、ボタンを出さなくします。

状態を全部たどって数える

exploreは、何も持っていない最初の状態から、できる行動を順にたどって、行ける状態をすべて集めます。同じ状態は1回だけ数えます。そこから、次の3つを返します。

  • 状態の数
  • クリアまでの最短の手数
  • 詰みの状態の数(そこからクリアに行けない状態)

この部屋で実行すると、状態は14個、最短の手数は6手、詰みの状態は2個でした。詰みの2個は、すべて、小さな鍵を暖炉に投げ込んだあとの状態です。実際にボタンを押して、6手で脱出できることも確かめています。

壊した部屋を検査する

本物の検査かどうかを確かめるために、部屋のデータをわざと壊しました。

壊した部屋の検査結果の表。元の部屋は、状態14、最短6手、詰み2。罠の行動を消すと、状態10、最短6手、詰み0。本棚を調べる行動を消すと、状態2でクリア不能。引き出しに小さな鍵を必要にした堂々巡りは、状態6でクリア不能。持ち物の名前を書き間違えると、状態10でクリア不能
クリア不能のときは、最短の手数がnullになります

罠の行動を消した部屋は、詰みが0になりました。本棚を調べる行動を消すと、古い本が手に入らず、状態は2個で止まります。引き出しを開けるのに「小さな鍵」を必要にすると、鍵が引き出しの中にあるため、堂々巡りでクリアできません。持ち物の名前を1文字書き間違えた場合(「小さな鍵」と「ちいさな鍵」)も、クリアできませんでした。AIにデータを書かせたときに起きやすい、名前のずれも、この検査で見つかります。

確かめられていないこと

  • AIが書いた部屋のデータこの記事では、部屋のデータを自分で書いています。AIに作らせた部屋を検査した結果は、確かめていません。
  • 部屋が増えたときの、状態の数と速さ(このコードは、持ち物の組み合わせが多いと、状態が急に増えます)
  • 画像を使った、クリックで調べる画面
  • スマートフォンでの表示と操作
  • 時間制限や、同時に持てる数の上限がある場合

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

うまくいかないとき

症状 確認すること
ボタンが1つも出ない needsに書いた名前が、givesに書いた名前と、1文字も違わないかを確かめます
クリア不能と出る exploreで、最短の手数がnullになる行動を探します。必要なものを手に入れる行動が、どこかにあるかを見ます
鍵を使っても消えない consumesに、消したい持ち物の名前を書きます
同じ行動を何度もできる この記事のコードでは、1つの行動は1回だけです。繰り返したい場合は、コードを変える必要があります

関連記事AIでゲームブックを作る方法|分岐を機械で検査して、AIが作った9冊を調べた【2026年版】

よくある疑問

部屋を増やすには、どうしますか

行動のリストに、行動を足します。場所の移動も、「どの部屋にいるか」を持ち物と同じように書けば、足せると考えられます。ただ、この記事では、1部屋だけを確かめています。

AIにデータを作らせても、使えますか

データの形が合っていれば使えます。ただし、この記事では、AIに作らせたデータでは確かめていません。作らせたあとは、exploreで、クリアできることと、詰みの数を確かめてください。

詰みは、あっていけませんか

ゲームの狙い次第です。この記事のコードは、詰みが起きたときにそれを知らせる形にしました。

この記事のまとめ

この記事のまとめ

  • 脱出ゲームは、needs・gives・consumesを持つ行動のリストで作れる
  • 状態を全部たどると、クリアまでの最短の手数と、詰みの状態を数えられる
  • この部屋は、14状態・最短6手・詰みの状態が2個だった
  • データの書き間違いや堂々巡りは、検査でクリア不能として見つかった
  • AIが作った部屋での検査は、確かめていない