1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リビングでゴロゴロしながら、チャットだけで数時間でゲームを公開した話

1
Posted at

日曜の夜、リビングでスマホ片手にゴロゴロしていたら、リバーシができていた。開発環境は、スマホ(iPhone)、Claudeモバイルアプリ、GitHub。パソコンを使っていない。

石の代わりに花を置くリバーシで、赤はダリア、青はネモフィラ。置くと花が開き、挟むと花びらを散らしながら順に色が変わる。20時2分にGitHubに足場のコミットが入り、20時58分にゲーム本体、21時6分に「花の庭」(勝ったポイントで椿や牡丹を解放する見た目の機能)、そのあと実機で見つけた不具合の修正と発熱対策が入って、23時28分に最後のPRをマージした。3時間半ほどで、GitHub Pagesで動くゲームとドキュメント一式が揃っていた。私が書いたコードは1行もない。

flower-reversi_board.png

その前日にはブロック崩しを作っていた。こちらはガラスのブロックで、ボールが当たるたびにブロックにひびが走り、最後に音を立てて砕ける。ゲームロジックの最初のコミットが21時48分、ガラス表現とパワーアップとハイスコアが入って23時51分にPagesの準備まで終わった。コア実装はやはり2時間ほどで、翌日にリポジトリの移管とドキュメントの整理をした。

crackout_stage1.png

発案は私、実装はClaude Code。私がやったのはチャットで、それも寝転がったままだ。それで数時間後には人に配れるURLがある。これには驚いた。ただ、分担は思ったより単純ではなかった。

この記事は、地の文が私、Claude: で始まる敬体の段落がClaude、という書き分けにしてある。私がClaudeに何を言い、Claudeが何を考えて何をし、私に何を頼んできたか、が分かるようにしたかった。

先に「自走できる環境」を作った

スマホでお手軽に、とはいえ土台が先だと考えた。ゲームの要件を伝えるより先に、私はClaude Codeに「タスクを渡したら計画・実装・検証・記録・PRまで自分で回る環境」を作ってほしいと頼んだ。1本目のブロック崩しは、その環境の実証として作っている。中身はClaudeが設計した。

Claude: 毎回読む基本ルールを CLAUDE.md に、長い手順を .claude/skills/ に、計画とレビューをサブエージェント(planner / reviewer)に分けました。やってはいけないことは settings.json の権限とhookで機械的に止めます。「終わってよいか」は scripts/verify.sh 1本で判定し、応答を終える直前にStop hookでこれを実行して、失敗したら差し戻します。「テスト通りました」と言いながら実は通っていない、という事故を仕組みで防ぎます。自走の質は、モデルの賢さより「完了の定義」と「検証の自動化」で決まります。

作ってみた結果を先に言うと、よく効いたのはverify.shとStop hookだった。このゲームくらいの規模だと、計画やレビューをサブエージェントに分ける設計はそこまで要らなかった。私がこの環境に手を入れたのも、サブエージェントの報告が英語で返ってきたので「日本語で報告すること」を書き足した、その一か所だけだ。

2本目のリバーシでは、settings.json で npm install を拒否しただけで、hookもスキルもサブエージェントも持ち込んでいない。それでも回った。最小限は「制約を書いたCLAUDE.md」と「verifyコマンド」の二つらしい。

さて、ここから先の方が、話としては楽しい。
いよいよゲームを作り始める。

スマホから開発できた

開発はClaude Code on the web(クラウド上のコンテナでClaude Codeが動く)を使った。1本目のブロック崩しはPCのブラウザから、2本目のリバーシはスマホのブラウザから操作している。

まず1本目。私は「ブラウザで動くブロック崩しを、TypeScriptで、依存なしで」と言っただけで、Claudeがコンテナの中で何に当たっているかは、あとでPROGRESS.mdを読んで知った。

Claude: この環境ではnpmレジストリがネットワークポリシーで403になります。npm install は失敗します。グローバルに入っているtscとPlaywrightだけで完結する構成にしました。Viteは使いません。@types/node も入れられないので、テストの型チェック用に tests/node-shim.d.ts を書いて、node:test と node:assert の最小の型宣言を自分で置きました。package-lock.json はレジストリに届かないため生成できていません。ローカルで npm install したらコミットしてください。

Claude: E2Eが1〜2割の確率で落ちます。最初はPlaywrightの待機が遅れると見立てましたが、ページ内のrAFログを取ったところ、ブロック破壊で自然に落ちていたアイテムが残っていて「全アイテム取得」の判定が遅れていたのが原因でした。テスト用アイテムを置く前に既存のアイテムを消して解消しました。25回中25回成功です。

Claude: 音はヘッドレス環境で聴けません。OfflineAudioContextで描画してスペクトルを確認しました。両端自由の金属棒の固有振動(1 : 2.756 : 5.404 : 8.933)を合成し、基音と2.756倍・5.404倍に強い成分があり、整数倍(2倍・3倍)にはほぼ無いことを確かめました。金属的な非整数倍音になっています。実機で音色の好み(高さ・余韻の長さ)を確認してもらえますか。

私は「鉄琴みたいな音で」と頼んだだけだ。実機で聴くと確かにカーンと鳴る。聴けない状態で音を作って、スペクトルの形で「金属っぽい」を担保していたと知って、少し笑った。

1本目をやってみて、これはモバイルのClaudeだけでも開発できるのではないかと思った。最近のモバイルのClaudeやclaude.aiには、Webで作業コンテナを立ち上げて開発環境にする仕組みがあるからだ。2本目のリバーシは、この経験を最初から織り込んで足場を組んでみた。

Claude: 作業コンテナはサンドボックスに近い制限があります。npmレジストリに到達できない(403)、公開サイト *.github.io にも到達できない、人間側の画面はスマホのみです。デプロイの成否はGitHub Actionsの実行結果で確認し、実画面の確認はユーザーに依頼します。ツールチェーンはNode.js v22の標準機能だけで構成します。テストは node --test、開発サーバー・ビルド・静的検査は依存ゼロの scripts/*.mjs、型はJSDocです。ゲーム本体もビルド不要のプレーンなES Modulesにします。

Claude: 人間の手元にはスマホしかないので、盤面のルールやCPUの思考はユニットテストで確かめ、見た目はコンテナ内のChromiumでスマホ幅のスクリーンショットを撮って共有します。

厳しい環境制約にClaudeはだいぶ苦労していたようだが、制約がそのまま設計方針になった形で、私が指示したわけではない。Claudeのドキュメントには「置き換え表」まであって、Vitest→node --test、Vite→scripts/serve.mjs、ESLint→scripts/check.mjs、TypeScript→JSDoc、と並んでいる。副作用として、制限付きコンテナでもGitHub Actionsでも手元のPCでも同じコマンドで動くし、依存が無いので脆弱性対応も発生しない。

画面も音もドキュメントも丸投げ

私が出した要望は、ブロック崩しでは「ブロックをガラスにしてほしい」「パワーアップがほしい」「ボールが貫通してほしい」、リバーシでは「石の代わりに花を置くリバーシ」くらいの粒度だった。

リバーシの花は、Claudeが仕様書に先に書いた。

Claude: 赤の花はゴージャス(幾重にも重なる花びら・金の花芯・きらめき)、青の花は可憐(小さな5枚花びら・白い花芯・ふんわりした花びらの舞い)にします。花を置く瞬間と裏返る瞬間の演出を、このゲームの一番の見どころにします。効果音は音声ファイルを使わず、Web Audioでその場で合成します。赤は厚みのある和音のチャイム、青は軽く澄んだ単音です。

ブロック崩しの「ボール貫通」は、私の言葉をClaudeが解釈した。

Claude: 「ボール貫通」はパワーアップの1種(P)として実装しました。常時貫通ではありません。貫通中はブロックを壊しても加速させません。一度に何個も壊すと急加速するためです。

ドキュメントの方針はブロック崩しのCLAUDE.mdにClaudeが書いたもので、私がそれをリバーシにも持ち込んだ。

Claude: READMEは遊ぶ人向けに書きます。ゲームの紹介が主題で、Claude Codeで作ったことは副題にします。作業メモはREADMEに足さず docs/ に書きます。calloutはGitHubとObsidianの両方で描画される [!NOTE] / [!TIP] / [!IMPORTANT] だけにしました。READMEのYAMLフロントマターはGitHubで表として描画され外向きには見栄えが悪いので外しました。

花のSVGも、裏返りが波のように広がる演出も、READMEもCLAUDE.mdも、書いたのは全部Claudeだ。

画面のこだわりのほとんどはClaude自身が考えたものだ。ただ、一つだけ、私の側から出した「気づき」が設計に影響した例がある。ブロック崩しのパドルは端で打つほど角度が付くのだが、私は「反射角度を考えるとバーは実は曲面では」と言った。

Claude: 真上から落ちるボールについては、今の方式は「端で30°傾いた円弧(半径 = 幅、盛り上がり = 幅 × 0.134 ≒ 15px)」と等価です。斜め入射では本物の曲面(出射 = 2θ + 入射)と異なり、今の方式は入射角を無視します。選択肢を3つ示します。(1)今のまま (2)見た目だけ曲面にする (3)本物の曲面反射にする。

私は「見た目を曲面にする」を選んだ。物理の細部が分からなくても、違和感を投げれば選択肢に変換してくれる。

公開してから実機で試して、報告して直してもらう

GitHub Pagesに公開してから、私がスマホで遊んで、変な挙動をチャットで伝え、Claudeが直す。この往復が2本目のリバーシでは特にうまく回った。

うまく回った理由は、Claudeが最初から「人間はスマホしか持っていない」前提で、遠隔デバッグの仕組みを入れていたことにある。遠隔と言っても、裏で通信したとかではなく、画面にデバッグログを表示する仕組みを入れ込んだ。

Claude: 予期しないエラーは画面下部に赤い帯で表示します。スクリーンショットを送ってください。公開URLの末尾に ?debug を付けると、手番・合法手・処理中フラグ・タップした位置と触れた要素・デプロイされているコミットが右上に常時表示されます。ゲーム進行中のエラーは recover() で盤面から手番を立て直し、対局を続けられるようにしています。スクリーンショット1枚で原因を追えるようにしてあります。

実際そうだった。スクショはスマホで撮ってそのままモバイルのClaudeに貼れる。私が報告して直った不具合を3つ挙げる。花のSVGがタップを吸ってしまい、セルに届かない。盤面を描き直したときに裏返りアニメーションが残る。そして、遊んでいたらiPhoneが熱くなった。

Claude: 花のSVGが pointer-events を受け取らないようにし、タップが必ずセルに当たるようにしました。盤面を描き直すときに、保留中の裏返りアニメーションのタイマーをキャンセルするようにしました。

Claude: 発熱の対策として、infinite のアニメーションをやめました。待機中も画面が描き直され続けるためです。盤上の要素から filter と clip-path を外し、影はSVGに描き込みました。舞う花びらの同時数に上限(MAX_PARTICLES)を設けました。CLAUDE.md に「発熱・電池に配慮する(iPhoneで実際に熱くなった経緯がある)」の節を追加しました。

発熱は私にしか分からない情報で、私が「熱い」と伝えただけで、Claudeがそれをルールに書き戻した。私はスマホを見ながら、リビングでゴロゴロしているだけだったけれど。

プログラミング知識は、どこで要ったか

ここからは、Claudeに言われて私が手を動かした話になる。GitHubの操作の多くはClaudeが自分でやる。ただ設定画面はClaudeが手を出せない領域で、そのつどClaudeから頼まれた。ブロック崩しの方の話だ。

Claude: デプロイ用のワークフロー pages.yml を書きました。初回のみ、リポジトリのSettings → Pages → Sourceを「GitHub Actions」にしてください。

設定変更は私が行った。ところが初回のデプロイは弾かれた。

Claude: buildジョブは成功しましたが、deployジョブが実行前に弾かれました。原因は、公開用環境 github-pages の「デプロイしてよいブランチ」に main が入っていなかったことです。Settings → Environments → github-pages → Deployment branchesを直してください。

私が main を足し、Actionsから手動で再実行して、公開できた。GitHub側の操作には慣れが要るし、モバイルからやるにはコツもいる。それでもPCを開かず、リビングでゴロゴロしたまま通した。やや意地になっていた。

次に、知り合いに配るなら管理しやすい方がいい、とOrganizationを作って移管するアイデアをClaudeからもらった。個人アカウントからOrganization tinkling-way-games へリポジトリを移管したのは私だった。すると残課題が返ってきた。

Claude: GitHub PagesはOrganizationへの移管で引き継がれず、新URLは404です(2026-09-27 01:50 JSTに確認)。旧URLからのリダイレクトもありません。無料のOrganizationでは、privateリポジトリのPagesは使えません。個人のProと違い、OrganizationはTeamプラン以上が必要です。対応は「リポジトリをpublicにする」か「OrganizationをTeamにする」のどちらかです。

Claude: 新リポジトリのSettings → Pages → Sourceを「GitHub Actions」にし、github-pages 環境のDeployment branchesに main があるか確認し、Actionsタブから「Deploy to GitHub Pages」を手動実行して、新URLで動くことを確認してください。知り合いに配った旧URLは差し替えが必要です。

言われるがままに操作する。私はリポジトリをpublicにし、Pagesの設定をやり直し、手動でデプロイし直した。Claudeは公開サイトに届かないので、新URLで動くことの確認も私がスマホのブラウザでやっている。PROGRESS.mdには「ユーザー側の設定、コードでは解決できない」という見出しで、この6項目がそのまま残っている。

ただ、この初期整備が終わったあとは、リバーシのPR #2から#7まで、全部「スマホでチャット→Claudeがpush→私がマージ」で回った。
リポジトリを立ち上げてPagesへのデプロイが整うところまでを誰かが用意すれば、あとはスマホのチャットで要件を言い、公開URLで動きを見る。この開発は、プログラミングの知識がなくても結構いけるのではないか。

公開するので、セキュリティレビューはやった

知り合いに配るとはいえ公開物なので、レビューをやると決めたのは私で、ここだけはPCを立ち上げた。Claude Code CLIとVSCodeのGitHub Copilot拡張で、それぞれレビューをかけている。まずブロック崩し。

Claude: ソース全体とgit履歴をセキュリティ観点で確認しました。確認したのは、機微情報を含まないこと・個人情報を預からないこと・外部にデータを送らないことの3点です。3点とも問題ありません。秘密情報は現在のファイルにも履歴にもありません。保存するのはハイスコアのscore / stage / dateだけで localStorage 止まりです。src/ に外部と通信する処理はなく、実行時の依存もゼロです。1件だけ個人ドメインのメールアドレスで作られたコミットがあります。

メールアドレスは公開しているものなので、私の判断でそのままにした。リバーシの方では、CSPを入れる提案が来た。

Claude: index.html にContent-Security-Policyのmetaを入れ、同一オリジン以外の読み込みと通信、インラインの <script> / <style> / on*= 属性 / data: URLを止めることを提案します。XSSと外部送信の保険です。

私は「直して」と言っただけで、Claudeが実装した。PCでの作業だったのでコミットは私の名義になっているが、書いたのはClaudeだ。リバーシの npm run check に「外部URLを読み込まない・ルート絶対パスを使わない・localStorage以外に保存しない」の機械的な検査を入れていたのもClaudeで、これは頼んでいない。

そして、ブロック崩しのリポジトリにあった、GitHubのIssueやコメントで @claude と書くとClaudeが動くワークフローについて、警告が来た。

Claude: @claude メンションのワークフローが有効なままpublicにすると、穴になります。publicリポジトリではIssueやコメントの本文を外部の人も書けます。それを書き込み権限つきのClaudeに読ませるのは、入口をひとつ開けておくことになります。

私の判断としては、それに加えて、publicで誰かに @claude を使われると私のクレジットが消費されるのがまずかった。対応はClaudeに頼んだ。

Claude: 無効化ではなく .github/workflows/claude.yml をファイルごと削除しました。それを前提にしていたIssueテンプレートの @claude 行と claude ラベル、docs/AUTONOMOUS_DEV.md の説明も直しました。ユーザー側でSettings → Secrets and variables → Actionsの CLAUDE_CODE_OAUTH_TOKEN を削除し、必要ならClaude GitHub Appをアンインストールしてください。

ClaudeとCopilotの掛け合い

GitHub側にはCopilotの自動レビューを私が入れてあるので、Claudeが直したPRにCopilotがツッコミを入れてくる。これが面白かった。

ブロック崩しの最初のPRでCopilotが出した指摘はMediumが3件で、いずれも実際の不具合だった。私は指摘をClaudeに渡しただけだ。

Claude: 3件とも実際の不具合でした。音は、壁・パドルなどの単発音だけがコンプレッサーを通らずスピーカーに直結していたので、synthTone に切り出して他と同じ出口に通しました。「destinationに直結するのはコンプレッサーだけ」をソースを走査するテストで固定しました。

Claude: ハイスコアは、insertScore が早期returnのとき上限を超えた入力をそのまま返していたので、最初に上限件数で切るようにしました。保存データの stage を数値かどうかしか見ておらず、NaN・0・負数・小数が通っていたので、1以上の整数に限定しました。いずれも修正前に失敗するテストを先に書いて確認しました(89件 → 92件)。

レビュー指摘が再発防止のテストに変わる。Claude側にも自前のレビュー役がいて、PRを出す前に差分を見る。
音響は知識が乏しいので、お任せするしかない。ClaudeとCopilotが勝手に問題を見つけて解決していくのを、体はリビングでゴロゴロしつつ、心の中では正座して拝見していた。

Claude: reviewerの指摘で、ブロックの角をかすめると耐久値が連続で削れるバグを修正しました。入射角と位置の総当たり31137通りで726件 → 0件です。角では向かってきている軸で反射し、両軸から向かってきたときは両方反転します。反転しなかった接触はダメージにしません。

二段のレビューで、人間の私は安心してマージボタンを押している。

おわりに

書き分けてみると、私の地の文は短くて、Claudeの段落の方がずっと長い。私が言ったのはアイデアと違和感と「熱い」で、Claudeが考えたのは設計のほぼ全部で、私が手を動かしたのはGitHubの設定画面だった。リビングでゴロゴロしながらゲームができていく体験は、素直に楽しかった。
それにしても、Claudeは真面目だ。

リポジトリはこちら。READMEは遊ぶ人向けに、作業の記録は docs/ に、どちらもClaudeが書いている。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?