バイブコーディングで作ったゲーム、中身を覗く

AI活用

先日、計算問題を解きながら盤面を塗り合う「陣取りペンキ」というブラウザゲームを作りました。エンジニアとして胸を張れるような大作ではありません。AIと対話しながら、仕掛けを一つずつ思いついては形にしていった、という感じでしょうか。

かかった時間は、およそ1日。この開発スタイルには「バイブコーディング」という名前が付き始めているようです。

この記事では、インク量の決め方やCPUの強さの正体など、ゲームの中身をプログラムごと覗いていきます。難しい話は最小限に、コードの意味を日本語に置き換えながら進めるつもりです。

まずは遊んでみてください

説明の前に、まず触ってみてください。細かいルールを読むより、「速く解くと大きく塗れる」感覚は遊んだほうが早く伝わります。

▶ 全画面で遊ぶ

ルールはシンプルです。

  • 計算問題に4択で答える
  • 正解までの速さで、次に使えるインクの量が決まる
  • 盤面をタップして、そのインクで塗る
  • 8×8マスを全部塗るか、CPUと面積を競う

難易度は5段階。計算が苦手な方はLv1〜2、大人はLv3の九九あたりから試すとちょうどいい塩梅だと思います。細かいルールは別記事に譲るとして、ここからはプログラムの中身の話に入ります。

576行、たった1枚のファイルで完結している

種明かしから始めると、このゲームはindex.htmlという1つのファイルだけでできています。画面のデザイン(CSS)も、動きを作るプログラム(JavaScript)も、全部同じファイルの中。行数を数えると576行でした。npmもフレームワークも画像素材も一切使っていません。

塗ったときの「ピコン」という効果音も、音声ファイルを鳴らしているわけではなく、その場で音を合成しています。

function tone(freq, dur, type = "square", vol = 0.12, delay = 0) {
  try {
    const ctx = audio(), t = ctx.currentTime + delay;
    const o = ctx.createOscillator(), g = ctx.createGain();
    o.type = type; o.frequency.value = freq;
    g.gain.setValueAtTime(vol, t);
    g.gain.exponentialRampToValueAtTime(0.001, t + dur);
    o.connect(g).connect(ctx.destination);
    o.start(t); o.stop(t + dur);
  } catch (e) { /* 音は失敗しても無視 */ }
}

難しそうに見えて、やっていることは「指定した高さの音を、指定した長さだけ鳴らして、だんだん小さくフェードアウトする」だけです。呼び出す高さや長さを変えて、正解音・不正解音・塗った音を作り分けています。

ファイルが1枚だけという構成は、地味に効いています。軽いし、壊れにくいし、ブログ記事にそのままiframeで埋め込めたのもこの単一ファイル構成のおかげ。中小企業の業務用ツールも、実はここまでシンプルに作れる場面が案外多いものです。

「速く解くほど大きく塗れる」を作る数行

このゲームの一番の売りである「速さ→インクの量」は、種を明かすと拍子抜けするほど単純です。正解した瞬間の経過時間を、performance.now()という時刻取得の仕組みで測っているだけ。

  if (val === p.problem.ans) {
    const dt = (performance.now() - p.t0) / 1000;
    let tier = dt <= 2 ? 2 : dt <= 4 ? 1 : 0;
    p.combo++;
    const bonus = Math.min(2, Math.floor(p.combo / 3));
    tier = Math.min(3, tier + bonus + (game.lastSpurt ? 1 : 0));
    p.pending = tier;

日本語に直すと、こうなります。

正解までの時間インクの大きさ
2秒以内
4秒以内
それ以降

さらに3連続で正解すると、コンボとしてインクが1段階アップ。条件分岐と足し算の組み合わせだけで「速さがそのまま攻撃力になる」感覚を作っています。

塗る形が四角ではなく「ひし形」なのも、地味な工夫のひとつです。中心のマスから縦と横にどれだけ離れているか(この距離の測り方を「マンハッタン距離」と呼びます)を数えて、範囲内かどうかを判定しています。

function diamondCells(cx, cy, r) {
  const out = [];
  for (let dy = -r; dy <= r; dy++) for (let dx = -r; dx <= r; dx++) {
    if (Math.abs(dx) + Math.abs(dy) > r) continue;
    const x = cx + dx, y = cy + dy;
    if (x >= 0 && x < N && y >= 0 && y < N) out.push(y * N + x);
  }
  return out;
}

「縦の移動距離+横の移動距離」が半径以下のマスだけを塗る対象に加える。それだけでスプラトゥーンのような塗り広がる気持ちよさが出るのは、正直ちょっと感動しました。

4択のひっかけは、ちゃんと考えて作られている

誤答の選択肢は、ランダムな数字を並べているわけではありません。「間違えそうな数字」をわざと混ぜてあります。

基本形は正解の「±1」「±2」「±10」。うっかり指がずれた時のミスを再現するような数字です。九九の問題では、もう一段仕掛けがあります。

  } else if (meta.mult) {
    pool.push((meta.a + 1) * meta.b, (meta.a - 1) * meta.b,
              meta.a * (meta.b + 1), meta.a * (meta.b - 1),
              ans + 1, ans - 1, ans + 10, ans - 10);

例えば「7×8」なら、正解の56だけでなく、48(6×8)や63(7×9)といった「隣の段・隣の数の答え」も候補に混ざります。九九を覚えたての子どもがよくつまずくポイントを、そのまま選択肢にしている形ですね。大人が解いても、一瞬指が止まる場面があります。

CPUの強さは、サイコロの重みでできている

CPU対戦モードの「よわい」「ふつう」「つよい」。この差、複雑な判断ロジックで作られていると思うかもしれません。正体は、数値の重みの違いだけです。

const CPU_DEF = [
  { name:"よわい", base:6.0, jit:2.0, tiers:[.7,.25,.05,0],  errRate:.20, samples:1  },
  { name:"ふつう", base:4.0, jit:1.5, tiers:[.25,.55,.20,0], errRate:.10, samples:8  },
  { name:"つよい", base:2.5, jit:1.0, tiers:[.05,.35,.50,.10], errRate:.04, samples:20 },
];

見た目は暗号のようですが、意味は拍子抜けするほどシンプル。

  • base:平均何秒で答えるか(よわいは6秒、つよいは2.5秒)
  • tiers:小・中・大・特大のインクをどの確率で選ぶか
  • errRate:わざと間違えて硬直する確率(よわいは20%、つよいは4%)
  • samples:塗る場所を選ぶ前に、何箇所を下見して比較するか

地味に効いているのが、最後のsamples。「つよい」は20箇所を見比べてから一番良さそうな場所を選ぶのに対し、「よわい」はたった1箇所しか見ません。賢く見える動きの正体は、実は「たくさん下見してから選んでいるだけ」というわけですね。

わざと一定確率でミスをする設計も仕込んであります。狙いは、理不尽に強すぎるCPUにしないための保険でしょう。実際に動かして計測したところ、「つよい」CPUは9秒間でおよそ35マスを塗っていました。大人がLv3(九九)くらいの速さで対抗すると、ちょうど競り合う強さです。

これ、AIとの1日のバイブコーディングで作りました

ここまで紹介した仕掛けは、コードを1行ずつ手で書いていったわけではありません。Claude Code(AIによるコーディング支援ツール)に「こういうゲームを作りたい」「速さでインクの量を変えたい」と話しかけながら、少しずつ形にしていく——いわゆる「バイブコーディング」という作り方(内部リンク: バイブコーディング入門)。

正直に言うと、一発で思い通りに動いたわけではありません。塗る形が不格好だったり、CPUが強すぎたりして、動かしては直すの繰り返し。それでも企画から動くものができるまで丸1日というのは、自分でも少し驚きました。

プログラマーではない人でも、小さく試すところから始められる。そこがこのやり方の面白さです。「うちの業務にも、こういう小さなツールが使えないか」——そう感じた方は、一度話を聞かせてください。

まとめ

  • 「陣取りペンキ」は576行・1枚のファイルだけで完結している
  • 「速く解くほど大きく塗れる」は、経過時間を測って条件分岐しているだけ
  • 誤答の選択肢は「間違えそうな数字」をわざと混ぜて作られている
  • CPUの強さは、平均解答時間・インクの確率・下見する候補地点の数といった数値の違いだけで表現されている
  • これら全部を、AIとの1日の対話(バイブコーディング)で組み立てた

「うちの業務にも、こういう小さな自作ツールが使えるかもしれない」——そう感じた方は、個別相談も承っています。まずはお問い合わせから、お気軽にご連絡ください。

コメント

タイトルとURLをコピーしました