メインコンテンツへスキップ
見出し画像

「0774ってどこ?」市外局番だけで日本のどこかを当てるゲームを、実作業4時間で公開した話

    「0774」

    これ、どこの市外局番か分かりますか。

    03と06なら分かる。実家の番号も出てくる。でも3つ目から急に怪しくなる。この「知ってるようで知らない」のギャップが面白いと思って、市外局番だけで日本のどこかを当てるゲームを作りました。

    画像

    GeoGuessr の「1プレイ5ラウンド・25,000点満点・結果を共有」というテンポをそのまま市外局番でプレイします。

    誤解がないように先に言っておくと、作業時間の合計は4時間。ただしカレンダー上は2日にまたがっていて、1日目に作って公開、2日目に公開後の調整を入れました。
    そしてコードのほとんどは Claude Code に書かせています。

    なので、これは「4時間で作れてすごい」という話ではなく、その4時間で人間が何を決めて、何をAIに投げたかという話です。

    できたもの

    • 4モード(市街局番→市区町村/市区町村→市街局番 × 4択/自由回答)

    • 収録は365局番・940市区町村

    • 244ファイル/約11,000行/コンポーネント42個

    • バックエンドなし。静的書き出しのみでランニングコストは0円

    • おまけに、検索から入ってこられる静的ページが413ページ

    画像

    題材選び:実装量より「ネタの強度」で勝負が決まる

    実装をAIに任せられる前提だと、勝負はほぼ題材選びで決まります。
    市外局番が強い理由は3つ。

    • 全員が1つは知っているのに、2つ目から急に怪しい(難易度がちょうどいい)

    • データが総務省の公表資料にある(ライセンスと精度の心配がない)

    • 答えが一意に決まる(採点がブレない)

    個人開発で一番詰まるのは実装ではなくデータの調達で、そこが最初から解決している題材でした。 (結局原典のママでは使えず意外と手間がかかったが…)

    人間が決めたこと① 設計の型

    最初に作ったのは、コンポーネントでもデータでもなく規約ファイルです。

    • コンポーネントは Atomic Design(atoms / molecules / organisms / templates)

    • それ以外はオニオンアーキテクチャ(domain / application / infrastructure)

    • スタイルは CSS Modules + BEM

    • 各コンポーネントは index.tsx(ロジック)/ [Name].tsx(見た目)/ [Name].module.css の3点セット

    • enum は使わず as const オブジェクト + 派生型

    • import 順は 外部ライブラリ → コンポーネント → スタイル

    AIに書かせるときに一番コストが高いのは、毎回ちがう設計で出てくることです。10個目のコンポーネントあたりで統一感が崩れて、直す時間のほうが長くなる。
    先にテンプレートになる実装を1つ置いておくと、出力が毎回同じ形で返ってきて、レビューが「実装として正しいか」ではなく「意図と合っているか」だけになります。

    規約はカスタムインストラクションでAIに渡しますが、基本的には使いまわしでOKなので、一度作ってしまえば以降はコスト0で使い回せます。

    人間が決めたこと② データの意味

    一番時間を使ったのはUIではなくデータでした。
    総務省の一覧をもとにした静的マスタ(10,427行のJSON)から、ビルド時に365局番・940市区町村ぶんの出題データを生成しています。

    ただ、原典をそのまま使うと成立しません。元データはこう書き分けられています。

    • 大半の区域を持つ行 →「〇〇市(△△を除く。)」

    • 飛び地 →「〇〇市□□地区(〜に限る。)」

    つまり「を除く」と書いてあるほうが本体で、「に限る」は飛び地。ここを読んで、市区町村ごとの代表局番を推定します。青森市なら017が代表で、飛び地の浪岡地区0172はサブ扱い。

    const primaryScore = (rawName, cleanedName, municipality, code) => {
      let score = 0;
      if (cleanedName === municipality) score += 100; // 一部地域を指す行ではない
      if (/に限る/.test(rawName)) score -= 50;
      else score += 50;     // 「を除く」または注記なし
      score -= code.length; // 同点なら短い局番(=広域)を主とする
      return score;
    };

    この100点・50点の配点は、AIが勝手に決めたら困るところです。データの意味をどう解釈するかは人間が決めて、その解釈を実装するスクリプトをAIに書かせる。この分担が一番効果的でした。

    あわせて、広く知られている91件の局番・自治体と突き合わせる検証スクリプトも書かせています。

    $ npm run verify:data
    ✅ 既知データ 91 件と一致 / 局番 365件・市区町村 940件の整合性OK

    生成をAIに任せる以上、正しさを機械で確かめられない状態は怖い。逆に言えば一度確認したスクリプトで機械的に生成、検証できる機構さえあれば、再現性を持たせることができます。

    AIに投げたもの: 残り全部

    UI、状態管理、共有テキスト、SEOページ、OGP画像の生成、Storybookのストーリー。
    ここは全部書かせました。そのなかで詰まった1点。

    • OGP画像はビルド時生成で日本語フォントを積んでいない。画像内の文言は英字のみ。しかも拡張子なしで書き出されるので MIME の指定も要る

    この手のメモは、次に同じ構成で作るときの4時間をさらに削ってくれるように、カスタムインストラクションなど適当なファイルに残します。

    人間が決めたこと③ 体験のどこを妥協しないか

    惜しいときは、惜しいと言う

    画像

    自由回答には部分点を入れました。都道府県が合っていれば半分、局番なら同じ県の局番や上2〜3桁が合っていれば半分。「惜しい」を切り捨てると、ただ理不尽なだけのゲームになります。
    しかし、この「感情」に沿った判断は今のAIの苦手分野。ここは人間の判断を介在ささるべきと判断しました。

    また、最高称号だけ、条件を変える。 称号は得点率で決まりますが、最上位の SSS だけは全問正解が条件。25,000点を出すと「電話帳の化身」、添えられるのは「全問正解。もう総務省の人では?」。

    画像

    ドヤり易い仕組みを作る

    ゲームで一番スクリーンショットされるのは結果画面です。ゲーム拡散のためにもシェアは必須なため、確実に実装していきます。

    スコアカード(1200x630)を Canvas でその場に生成して、視覚的に伝わるような仕組みを構築。
    ただし、LINE は共有URLに画像を添付できない仕様ないため、そこはテキストだけを送る、という割り切りも大切。(時間をかけて作り込む手もあるかもしれないがコスパとの相談。)

    画像

    サーバーに送らない

    ベストスコアと直近30件の履歴は localStorage だけ。運用コストが0になり、個人開発でユーザーデータを預かるリスクも0になります。

    公開したあと:検索から入れる413ページ

    2日目にやったのは、ゲームではなく導線づくりでした。「市外局番を調べたい」人向けの静的ページを413ページ生成しています。

    これは、純粋に「市外局番を調べたい」人向けというわけではなく、SEOを意識した実装。
    コンテンツの有用性を高めつつ、これまでの実装リソースを最大限に活用できる内容を作り込む。

    • /area-code/ … 地方→都道府県で辿るハブ(1ページ)

    • /prefecture/[ローマ字]/ … 県ごとの全局番と逆引き(47ページ)

    • /area-code/[局番]/ … 局番1件の詳細(365ページ)

    画像

    全部ゲーム本体と同じ生成データから作っているので、データを更新すれば自動で増減します。どのページも Server Component だけで構成していて、ゲーム本体のJSは読み込みません。

    「0774 市外局番」で検索した人が、答えを見たついでにゲームに入る。遊びと検索需要が同じデータの上に乗っていて、追加の運用コストはゼロ。SSG で作った副産物としてはかなり効率がいいと思っています。

    広告(AdSense)の枠も置きましたが、プレイ画面には入れていません。回答ボタンの近くの広告は誤クリックを誘発し、ポリシー違反になりうるからです。短期の収益より、アカウントを飛ばさないほうが大事。

    4時間の正体

    244ファイル・約11,000行は、1人で4時間に書ける量ではありません。速さの理由は単純で、自分の仕事を「決めること」に寄せたからです。

    人間が決めたのは4つだけ。

    • 何を作るか(市外局番ゲーム)

    • どういう形で作るか(Atomic Design+オニオンアーキテクチャ)

    • データの意味をどう読むか(「を除く」と「に限る」)

    • 体験のどこを妥協しないか(部分点・称号・色に頼らない共有・広告位置)

    この4つを決めずに投げると、出てきたものを直す時間で半日が溶けます。逆にここさえ決まっていれば、実装は待っているだけでいい。

    AI駆動開発で本当に変わったのは実装速度ではなく、思いつきの賞味期限が切れる前に公開まで行けることだと思います。市外局番でゲームを作るなんてアイデア、1週間温めていたらたぶん作っていません。

    あなたの街の局番、言えますか

    ちなみに冒頭の 0774 は京都府・宇治市。「知らないけど当たった」が1問でもあれば、それがこのゲームのたのしいところです。

    やってみた人はスコアを貼ってください。25,000点(SSS・電話帳の化身)が出たら、正直ちょっと悔しい。

    #個人開発 #AI駆動開発 #ClaudeCode #Nextjs #Webサービス


     
     
    エンジニア。 SIerを7年経験後独立。「AI」とか「技術」とか「個人開発」の成功談、失敗談などをふんわり書いています。

    あなたへのおすすめ