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

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

開いた大きな本から、光る道が木の枝のように分かれて広がっているイラスト

ゲームブックは、本文と選択肢を「節」に分けてJSONで持てば、短いHTMLで動かせます。AIに節のデータを作らせると手早く作れます。ただし行き止まりや、たどり着けない節があるとゲームになりません。この記事では、データを機械で検査するコードを付けて、AIが作った本を実際に検査しました。

コードは画面を出さないChromeで動かし、本の検査はNodeで実行しました。AIの本は、OpenAIのCodex(モデルはgpt-5.6-terra、思考の強さは低)に同じ条件文を3回ずつ渡して作っています。

この記事で分かること

  • ゲームブックを、JSONと1本のHTMLで動かすコード全体
  • 行き止まり・存在しない行き先・たどり着けない節を見つける検査のコード
  • わざと壊した本5通りを、検査が見つけた結果
  • AIに作らせた9冊を検査した結果と、条件の書き方による違い
  • 確かめられていないこと

会話で進むタイプのゲームは、AIと会話できるゲームおすすめとAI TRPGの遊び方で扱っています。ここでは、分岐があらかじめ決まっているゲームブックの作り方です。

この記事の目次
  1. 01ゲームブックの仕組み
  2. 02コード全体
  3. 03検査が見つけるもの
  4. 04AIに本を作らせて、検査する
    1. 手数の条件を足すと、どうなるか
  5. 05確かめられていないこと
  6. 06うまくいかないとき
  7. 07よくある疑問
  8. 08この記事のまとめ

ゲームブックの仕組み

1つの節に、本文と、選択肢のリストを持たせます。選択肢は「表示する文」と「行き先の節」のペアです。結末の節は、選択肢を持たず、結末の種類だけを持ちます。

12節のゲームブックを図にしたもの。上の開始の節から下へ進むほど結末に近づく。丸が節で、結末は、よい終わりが緑、悪い終わりが赤、真の結末がオレンジ。この本は結末が5つで、最短2〜3手
丸の中の数字は節の番号です

次が、図にした本のデータです。保存するときは、book.jsonという名前にします。

{
  "title": "森の分かれ道",
  "start": "s1",
  "sections": {
    "s1": { "text": "夕暮れの森で、道が三つに分かれている。左は細い道、真ん中は広い道、右からは水の音がする。", "choices": [
      { "text": "左の細い道へ進む", "to": "s2" }, { "text": "真ん中の広い道へ進む", "to": "s3" }, { "text": "水の音のほうへ行く", "to": "s4" } ] },
    "s2": { "text": "細道の奥に、明かりのついた小屋があった。", "choices": [
      { "text": "戸を叩く", "to": "s5" }, { "text": "通り過ぎて広い道へ出る", "to": "s3" } ] },
    "s3": { "text": "広い道は、倒木でふさがれている。", "choices": [
      { "text": "倒木を乗り越える", "to": "s6" }, { "text": "分かれ道まで引き返す", "to": "s1" } ] },
    "s4": { "text": "川辺で、青く光る石を見つけた。", "choices": [
      { "text": "石を拾う", "to": "s7" }, { "text": "石を無視して川を渡る", "to": "s8" } ] },
    "s5": { "text": "老人が出てきて、温かいお茶を出してくれた。", "choices": [
      { "text": "森の出口までの地図を頼む", "to": "s9" }, { "text": "そのまま眠らせてもらう", "to": "s10" } ] },
    "s6": { "text": "倒木を越えると、目の前は崖だった。", "choices": [
      { "text": "崖を飛び降りる", "to": "s11" }, { "text": "縄を探しに小屋へ戻る", "to": "s5" } ] },
    "s7": { "text": "石は、古い鍵の形をしていた。", "choices": [
      { "text": "鍵を持って、小屋へ向かう", "to": "s12" } ] },
    "s8": { "text": "川を渡った先で、同じ景色を何度も回り続けた。朝になっても、森から出られなかった。", "ending": "bad" },
    "s9": { "text": "地図を頼りに歩くと、夜明けに森の出口へ着いた。街の灯が見える。", "ending": "good" },
    "s10": { "text": "朝、目を覚ますと荷物がなくなっていた。老人の姿もなかった。", "ending": "bad" },
    "s11": { "text": "足を滑らせて、谷底へ落ちてしまった。", "ending": "bad" },
    "s12": { "text": "鍵は小屋の床下の隠し扉に合った。中には、旅の資金と古い地図があった。", "ending": "true" }
  }
}

コード全体

book.jsonと同じフォルダに、index.htmlを置きます。fetchでbook.jsonを読むので、ファイルを直接開くのではなく、サーバーを起動して開きます。python -m http.server などで足ります。

<!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:8px 0;padding:10px;font-size:16px;text-align:left}
#end{font-weight:bold;color:#a20}
</style>
</head>
<body>
<h1 id="title"></h1>
<p id="text"></p>
<div id="choices"></div>
<p id="end"></p>
<script>
let book = null;

// 本の検査: 行き止まり・存在しない行き先・たどり着けない節を見つける
function validate(book) {
  const ids = Object.keys(book.sections);
  const problems = [];
  const next = {};
  for (const id of ids) {
    const s = book.sections[id];
    const to = (s.choices || []).map((c) => c.to);
    next[id] = to;
    for (const t of to) if (!book.sections[t]) problems.push(`${id}: 行き先 ${t} が存在しない`);
    if (!s.ending && to.length === 0) problems.push(`${id}: 結末でないのに選択肢がない(行き止まり)`);
    if (s.ending && to.length > 0) problems.push(`${id}: 結末なのに選択肢がある`);
  }
  // 開始地点から幅優先でたどる(最短の手数も数える)
  const dist = { [book.start]: 0 };
  const queue = [book.start];
  while (queue.length) {
    const id = queue.shift();
    for (const t of next[id] || []) {
      if (book.sections[t] && !(t in dist)) { dist[t] = dist[id] + 1; queue.push(t); }
    }
  }
  for (const id of ids) if (!(id in dist)) problems.push(`${id}: 開始地点からたどり着けない`);
  const endings = ids.filter((id) => book.sections[id].ending && id in dist);
  return { problems, reachable: Object.keys(dist).length, endings: endings.map((id) => [id, book.sections[id].ending, dist[id]]) };
}

function show(id) {
  const s = book.sections[id];
  document.getElementById('text').textContent = s.text;
  const box = document.getElementById('choices');
  box.innerHTML = '';
  document.getElementById('end').textContent = s.ending ? '結末:' + { good: 'よい終わり', bad: '悪い終わり', true: '真の結末' }[s.ending] : '';
  for (const c of s.choices || []) {
    const b = document.createElement('button');
    b.textContent = c.text;
    b.onclick = () => show(c.to);
    box.appendChild(b);
  }
}

fetch('book.json').then((r) => r.json()).then((b) => {
  book = b;
  document.getElementById('title').textContent = b.title;
  show(b.start);
});
</script>
</body>
</html>

validateが、本の検査です。表示する側のコードとは別に、データだけを受け取って、問題の一覧を返します。

検査が見つけるもの

validateが調べるのは、次の4つです。

  • 行き先に書いた節が存在しない
  • 結末でないのに、選択肢が1つもない(行き止まり)
  • 結末なのに、選択肢がある
  • 開始の節から、どう選んでもたどり着けない節がある

最後の項目は、開始の節から幅優先でたどって調べます。このとき、各節までの最短の手数も数えます。

検査が壊れた本を見つけた結果。選択肢をすべて消した節があると、行き止まりと、その先の節にたどり着けないことが見つかる。存在しない節へ行く選択肢は、行き先が存在しないと出る。どこからも行けない節を足すと、たどり着けないと出る。結末の印を消すと行き止まりと出る。入口の選択肢を1つ消すと、4つの節にたどり着けなくなる。壊していない元の本は問題0件
元の本を5通りに壊して、検査にかけました

元の本を、5通りに壊して検査にかけました。どれも、壊した場所と、その影響が問題として出ました。壊していない元の本は、問題が0件で、12節すべてに到達でき、結末が5つでした。実際にボタンを押して、3つの結末(真の結末・よい終わり・悪い終わり)まで遊べることも確かめています。

AIに本を作らせて、検査する

AIに作らせるときは、形式と条件を、文で指定します。次の条件文を、そのまま渡しました。

ゲームブックのデータを、JSONだけで作ってください。説明の文章は付けないでください。

形式:
{"title":"題名","start":"s1","sections":{"s1":{"text":"本文","choices":[{"text":"選択肢","to":"s2"}]}, "s9":{"text":"本文","ending":"good"}}}

条件:
- 舞台は、夜の古い洋館
- 節は12個(s1からs12)。開始はs1
- 結末の節は4つ以上。ending の値は good、bad、true のどれか。結末の節には choices を付けない
- 結末でない節には、選択肢を2つか3つ付ける
- すべての節に、開始のs1からたどり着けること
- 本文は、1節あたり60文字以内

この条件文を、12節で3回、節を30個・結末を8つ以上に変えて3回、合わせて6冊作らせました。6冊とも、JSONとして読め、検査の問題は0件でした。12節の本は結末が4〜5つ、30節の本は8〜14個でした。

AIに作らせた6冊の本の検査結果の表。12節の条件の3冊は、結末が4つ、4つ、5つで、結末までの手数は2〜4手、3手のみ、3手のみ。30節の条件の3冊は、結末が8つ、10個、14個で、手数は4手のみ、3〜4手、4手のみ。問題はどれも0件。30節の本でも、どの結末も4手以内
構造に問題はなく、広くて浅い本でした

問題はなかった一方で、30節の本でも、開始から結末までの最短の手数は、3〜4手でした。節は多くても、数回の選択で終わってしまう本です。30節の本の1冊を図にすると、次のとおりです。

AIが作った30節の本の形。開始の節から結末まで最大4段。下の段に節が横に広く並んでいる
同じ段や前の節へ戻る矢印は省いています

手数の条件を足すと、どうなるか

条件文に「どの結末も、開始から最短でも6回以上の選択が必要なこと」の1行を足して、30節で3回作らせました。

条件に最短6手以上を足して30節を3回作らせた結果。1回目と2回目は、結末が8つで、最短7手のみ、6〜7手で、条件を満たした。3回目は結末が3つで、手数は5〜6手のため、条件を満たさなかった。構造の検査は3冊とも問題0件
条件の不一致は、構造の検査だけでは見つかりません

2回は、結末8つ・最短6手以上を満たしました。3回目は、結末が3つで、最短が5手で、2つの条件とも守られていませんでした。構造の検査は、3冊とも問題0件でした。条件が守られたかどうかは、結末の数と最短の手数を数えて、別に確かめる必要があります。validateの戻り値には、結末の種類と最短の手数が入っているので、その一覧を見れば確かめられます。

これらの結果は、1つの条件文を使った、少ない回数の試験です。ほかのAIやほかの条件文で、同じ結果になるとは言えません。

確かめられていないこと

  • ほかのAIでの結果Codex(gpt-5.6-terra)だけで試しました。ChatGPTやGeminiの画面で作らせた場合は、確かめていません。
  • 物語としての面白さと、日本語の自然さ(検査は、構造だけを見ています)
  • アイテムや体力など、状態を持つゲームブック(この記事の本は、状態を持ちません)
  • 節が100を超える長い本での検査の速さ
  • スマートフォンでの表示と操作

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

うまくいかないとき

症状 確認すること
画面に何も出ない ファイルを直接開いていないかを確かめます。サーバーを起動して、アドレスから開きます
AIの返事がJSONとして読めない 返事の前後に説明の文が付いていないかを見ます。条件文に「JSONだけ」と入れます
行き止まりがあると出る 該当の節に、選択肢を足すか、結末の印(ending)を付けます
たどり着けない節があると出る その節へ向かう選択肢を、どこかに足すか、不要なら節を消します
結末が少ない・浅い 条件文に、結末の数と最短の手数を書きます。そのうえで、結果を数えて確かめます

関連記事AIと会話できるゲームおすすめ【2026年版】スマホ・Steam・ブラウザ別

よくある疑問

ChatGPTやGeminiでも、同じ条件文で作れますか

条件文は、そのまま渡せます。ただ、この記事で試したのはCodexだけです。作らせたあとは、同じ検査にかければ、構造の問題は確かめられます。

節が増えても、検査は使えますか

30節までは確かめました。もっと長い本の速さは、確かめていません。

アイテムを持つ仕組みは、足せますか

この記事では扱っていません。状態を持たせると、同じ節でも状況が分かれるため、検査の考え方も変わります。

この記事のまとめ

この記事のまとめ

  • ゲームブックは、節のJSONと短いHTMLで動かせる
  • 行き止まり・存在しない行き先・到達できない節は、コードで見つけられる
  • AIが作った9冊のうち、構造に問題があった本は0冊だった(Codexでの試験)
  • 30節でも、条件を書かないと最短3〜4手の浅い本になった。条件を足すと深くなったが、守られない回もあった