マガジン

同じゲームを10回修正させたら、コードはどれだけ膨らむか

遊びをつくる

執筆にClaudeを使用しています。

小さなゲーム模型を四つの試験コースで動かし、修正を確かめる机。
記事のテーマをもとに制作したAI生成イラスト。

的をクリックして消すだけの小さなゲームがあるとします。そこへ「制限時間をつけて」「スコアを出して」「難易度を選べるように」と、AIに1つずつ頼んでいく場面を考えてみます。10回目の依頼を出すころ、ファイルの中身は最初と同じ形をしているでしょうか。それとも、継ぎ足しの跡だらけになっているでしょうか。 この記事は、その答えを書くものではありません。答えは使うAIや依頼文、ゲームの種類で変わります。ここでは、読者が自分で10回の修正を行い、膨らみ方を行数と構造の両方で記録するための設計書を示します。 ポイントは、行数だけを見ないことです。機能が10個増えれば行数が増えるのは当然です。確かめたいのは、増え方が機能の数に見合っているかどうかです。関数が整理されて増えているのか、同じ処理のコピーが並んでいるのか、状態を持つ変数が散らばっているのかは、数字の組み合わせで見分けられます。

この記事の目次
  1. 「膨らむ」を何で測るか
  2. 準備するもの
  3. 10回の手順
  4. 記録の読み方
  5. 誤りやすい点
  6. 次の行動

「膨らむ」を何で測るか

最初に、何を数えるかを決めます。測る項目は、手作業でも小さなスクリプトでも数えられる、粗いものに限ります。

  • JS行数(空行を除く)、何を示すか:全体の量、読むときの注意:整形ルールが変わると見かけが変わります
  • 関数の数、何を示すか:処理が分割されているか、読むときの注意:無名の小さな関数が増えても数は増えます
  • トップレベル宣言の数、何を示すか:状態(変数)が散らばっているか、読むときの注意:行頭にインデントなしで書かれている前提の近似です
  • 分岐の数(if・switch・case)、何を示すか:条件の積み重なり、読むときの注意:機能追加で増えるのは自然です
  • 重複する行の種類、何を示すか:コピーの多さ、読むときの注意:閉じ括弧など短い行は除いて数えます
  • イベント登録の数、何を示すか:入口の増え方、読むときの注意:同じ要素に何度も登録していないかの手がかりです

もう1つ、数字にならない項目を足します。その回の修正で、AIがファイルのどこを書き換えたかです。依頼は1行分の変更で済むはずなのに、全体が書き換わっていないかを見ます。前回版と今回版を差分表示できるツールやエディタの比較機能に並べると、一目でわかります。

準備するもの

用意するものは次の4つです。

  1. ベースのゲーム(v0)。的が1つ出て、クリックすると消えて別の場所に出る程度の、100行前後のHTMLを1ファイルで用意します。自作でも、AIに最初の1回だけ作らせたものでも構いません。ただし、条件を比べる場合は全員が同じv0から始めます。
  2. 固定した修正依頼10個。事前に書き出して、途中で変えません。例を挙げます。
  3. 制限時間30秒とカウントダウン表示を追加する
  4. スコアを画面に表示し、的を消すたびに加点する
  5. 的の大きさをランダムにし、小さいほど高得点にする
  6. 時間切れでリザルト表示とリトライボタンを出す
  7. 難易度(やさしい・ふつう・むずかしい)を選べるようにする
  8. 的が一定時間で消え、逃したらミスとして数える
  9. ミスが3回でゲームオーバーにする
  10. 連続命中でコンボ倍率をつける
  11. 命中時に短い効果音を鳴らし、ミュートボタンをつける
  12. 狭い画面でも遊べるよう、タッチ操作と的の大きさ調整に対応する
  13. 依頼文の雛形。毎回この形で出します。「次の修正をしてください:(依頼)。ほかの挙動は変えないでください。修正後のファイル全体を省略せずに返してください」。全体を返させるのは、後で説明する「省略」による見かけの行数減少を避けるためです。
  14. 計測ツール。次のHTMLを保存してブラウザで開き、ゲームのHTML全体を貼り付けて測ります。外部ライブラリは使わず、正規表現による近似です。
html
<!doctype html>
<meta charset="utf-8">
<textarea id="src" rows="10" cols="60" placeholder="ゲームのHTMLを貼る"></textarea>
<button id="go">計測</button>
<pre id="out"></pre>
<script>
document.getElementById('go').onclick = () => {
  const html = document.getElementById('src').value;
  const blocks = [...html.matchAll(/<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi)];
  const js = blocks.map(b => b[1]).join('\n');
  const lines = js.split('\n').filter(l => l.trim() !== '');
  const count = re => (js.match(re) || []).length;
  const seen = {};
  lines.forEach(l => {
    const t = l.trim();
    if (t.length >= 12) seen[t] = (seen[t] || 0) + 1;
  });
  const result = {
    'HTML全体の行数': html.split('\n').length,
    'JS行数(空行除く)': lines.length,
    '関数の数': count(/\bfunction\b/g) + count(/=>/g),
    'トップレベル宣言': lines.filter(l => /^(let|const|var)\s/.test(l)).length,
    '分岐(if/switch/case)': count(/\b(if|switch|case)\b/g),
    'イベント登録': count(/addEventListener/g),
    '重複する行の種類': Object.values(seen).filter(n => n > 1).length
  };
  document.getElementById('out').textContent =
    Object.entries(result).map(([k, v]) => k + ': ' + v).join('\n');
};
</script>

このツールには限界があります。コメント内の語も数え、複数行にまたがる式も区別しません。絶対値の正確さより、同じツールで測った版どうしの比較に意味がある道具だと考えてください。

10回の手順

条件はできるだけ固定し、変える要素は1回の実験につき1つだけにします。AIの種類、会話の方式(同じ会話を続けるか、毎回新しい会話にコードを貼るか)、依頼文の雛形は、条件ごとに決めて記録します。

  1. v0をそのまま計測し、記録シートの0行目に書きます。ブラウザで開き、動くことも確認します。
  2. 依頼1を雛形に当てはめてAIに送ります。
  3. 返ってきたコードを丸ごと新しいファイル(v1)として保存します。手で直さず、エラーが出ても自分では修正しません。
  4. v1をブラウザで開き、「起動する」「その回の機能が動く」「前の機能が壊れていない」の3点を、○△×で記録します。
  5. v1を計測ツールに貼り、数値を記録します。
  6. 前回版との差分を表示し、変更された行の割合と、変更箇所が1か所に集中しているか全体に散っているかをメモします。
  7. 依頼2から10まで、2〜6を繰り返します。動かなくなった回は、直させる依頼を足さずに、×のまま次の依頼へ進む方法と、1回だけ「動かない」と伝えて直させる方法のどちらかに決めておきます。途中で方法を変えると比較できなくなります。
  8. 同じ条件で、最低3セット行います。AIの出力は毎回違うため、1セットだけでは偶然の差と傾向を区別できません。

3セット×10回で、1条件につき30回の依頼と計測になります。時間がかかる場合は、依頼を5個に絞って揃えて行う方法もありますが、その場合は題名の「10回」とは別の実験だと記録に書き添えてください。

記録の読み方

記録シートは、縦に版(v0〜v10)、横に各指標と○△×、差分のメモを並べます。3セット分を並べたら、次の順に読みます。

行数の伸び方を見ます。 各版の増分を並べます。毎回ほぼ同じだけ増えているなら、機能追加にほぼ比例しています。後半で増分が大きくなっているなら、既存部分への継ぎ足しや、条件分岐の複製が起きている可能性があります。

行数と他の指標のずれを見ます。 判断の目安は次のとおりです。

  • 行数と関数の数が一緒に増える:処理が分割されながら育っています。
  • 行数だけが増え、関数の数があまり増えない:既存の関数の中身が太っています。
  • 分岐の数が行数より先に増える:条件の継ぎ足しが起きやすい状態です。
  • 重複する行の種類が増え続ける:似た処理のコピーが増えています。
  • トップレベル宣言が増え続ける:状態があちこちに散らばっています。依頼6〜8のような「状態を増やす依頼」で跳ねるかを見ます。

途中で数値が下がる回に注目します。 AIが勝手に整理した場合と、機能が壊れて消えた場合があります。○△×の欄と見比べれば区別できます。下がったのに前の機能が×になっていたら、整理ではなく欠落です。

依頼の種類で分けて見ます。 「機能を足す依頼(1・2・4)」「既存の挙動を変える依頼(3・5・8)」「横断的な依頼(9・10)」にグループ分けし、どのグループで差分の範囲が広がったかを比べます。横断的な依頼で全体が書き換わるのは自然ですが、足すだけの依頼で全体が書き換わるなら、注意が必要な兆候です。

条件を変えて比べる場合は、たとえば「毎回の会話を新しくする」と「同じ会話を続ける」を並べ、どの指標に差が出たかを3セットの範囲のばらつきと比べます。差がセット間のばらつきより小さいなら、差があるとは言えません。

誤りやすい点

  • 「以前と同じ」の省略。 AIが「ここは変更なし」とコメントだけ残して中身を省くと、行数が減ったように見えます。雛形で全体を返させるのはそのためで、返ってきた版に省略の表現がないか、毎回目で確認します。
  • 整形ルールの変化。 インデントや括弧の位置が途中で変わると、行数やトップレベル宣言の数がずれます。大きく変わったら、その旨を記録します。
  • 動作確認の省略。 動かない版の数字は、動く版と同列には比べられません。○△×を必ず付けます。
  • 依頼の言い換え。 条件ごとに依頼文を微調整すると、何を比べているのかわからなくなります。文面は固定します。
  • 1回の結果での一般化。 3セットのばらつきを見ないまま「このAIは膨らみやすい」と言い切るのは避けます。
  • ツールの数字の過信。 正規表現の近似です。気になる版は、実際のコードを自分で読んで確かめてください。
  • 保存機能の依頼を混ぜるとき。 ハイスコアの保存のような依頼を足すなら、保存先の方法で挙動が変わります。アソバースにアップロードした作品内のlocalStorageは一時メモリーの互換処理として扱われる、という点を踏まえ、今回の10個の依頼には保存を含めていません。

次の行動

まずv0を1つ決めて、計測ツールで測り、記録シートの0行目を埋めてください。そのうえで、依頼1と2だけをAIに出して、差分と数字を並べてみると、実験の勘所がつかめます。

遊んで確かめたい場合は、v0とv10のHTMLをそれぞれアソバースにアップロードして、同じ端末で続けて遊び比べる方法もあります。アップロードはHTMLの直接投稿ができ、公開にはメールアドレスの確認が必要です。非公開のまま下書きで保管する使い方もできます。