ClaudeCodeを利用してなんとなく開発はしてみたい、でも、何を開発すればいいか思いつかない、といった悩みはありませんでしょうか。
今回は"反アキネーター"こと、自分の気持ちを2択で回答していくことで深層的な理想像を炙り出すような、Claudeとのインタラクティブでゲームアプリを作ってもらうことにします。
このような問答はソクラテス式問答というみたいです。
(ソクラネーターではちょっとしっくりこなかった)
前提
1.敢えて具体的な "作りたいもの" は考えない
2.Planモードで進め、内容が煮詰まったら一気にゲームアプリを作成する
3.プログラム言語ヨクワカラナイのでhtmlだけで実装する
4.トークン枯渇しないようにClaude Sonnet 4.6を使う
はじめかた
シンプルな3ステップに絞ります。
ステップ1:ClaudeCodeを起動します。
$ claude
ステップ2:Planモードにします。
> /plan
ステップ3:進め方を指示します。
❯ html言語を利用して、ブラウザで利用可能なゲームを作成します。
私の要望が明確になるように、3択(はい、いいえ、わからない)で選択肢を提示し続け、
要件が具体的になってきたら整理をして作成するゲームを示してください。
これだけ!
やってみた
Slay the Spireのようなログライク要素を取り入れるはどちらでもいいで。
この時点で私がSlay the Spireが好きなことがバレていますが、
照れ隠しでどちらでもいいにしています
カードのテーマについて、ここだけはType somethingを選択し、レガシーシステムをモダンなシステムへリニューアルするシステム開発がテーマと打ち込んでいます。
(ここの問答はYes/Noだけで進めるとが非常に長くなる気がしたため)


結果
反アキネーターの結果として、以下のゲームに関する提案がなされました。
●ありがとうございます!テーマが決まりました。要件が揃ったので整理してゲーム概要を提示します。
---
作成するゲーム: "Legacy Burst"
ゲーム概要
レガシーシステムをモダン化するプロジェクトマネージャーとして、
毎スプリント(ターン)カードを引いてプレイし、
技術的負債と障害に対処しながら近代化を進めるターン制カードシミュレーション。
ゲームプレイの流れ
[ドロー]
→ [カードプレイ(数字キー)]
→ [チーム効果]
→ [レガシーAI行動]
→ [スプリント終了(Space)]
→ [ドラフト(新カード追加)]
主な要素
┌────────────┬──────────────────────────────────────────────────────────────┐
│ 要素 │ 内容 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 操作 │ キーボードのみ(1〜5でカード、Space終了、Enter確定) │
├────────────┼──────────────────────────────────────────────────────────────┤
│ カード種 │ 🔧リファクタ / ☁クラウド移行 / 🧪品質改善 / │
│ │ 👥エンジニア / 🚨インシデント対応 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 育成 │ エンジニアカードをチームに配置 → │
│ │ スプリントごとにXP獲得・Lv1〜3に成長 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ 収集 │ スプリント後に3枚から1枚ドラフトでデッキに追加 │
├────────────┼──────────────────────────────────────────────────────────────┤
│ レアリティ │ Common / Rare / Epic(色で視覚的に区別) │
├────────────┼──────────────────────────────────────────────────────────────┤
│ ゲーム │ System Health が0になったら終了(再スタート可) │
│ オーバー │ │
├────────────┼──────────────────────────────────────────────────────────────┤
│ ビジュアル │ テキスト・絵文字・数字のみのシンプルなUI │
└────────────┴──────────────────────────────────────────────────────────────┘
カードの一例
- コードリファクタ(Common) → 技術的負債を -2
- SREエンジニア(Rare / 育成) → 毎ターンHealthを自動回復(Lv3で効果2倍)
- マイクロサービス化(Epic) → 大幅なモダン化スコア獲得
- 緊急パッチ(Common) → インシデントを即時解決
---
Plan: Legacy Renewal - ターン制カードシミュレーションゲーム
Context
ブラウザで動作するHTML単体ファイルのゲームを作成する。
ユーザーの要望を3択Q&Aで収集した結果、以下の要件が確定した。
確定した要件
┌──────────┬──────────────────────────────────────────────────────┐
│ 項目 │ 内容 │
├──────────┼──────────────────────────────────────────────────────┤
│ ジャンル │ ターン制カードゲーム + シミュレーション │
├──────────┼──────────────────────────────────────────────────────┤
│ テーマ │ レガシーシステムをモダンなシステムへリニューアルする │
│ │ システム開発 │
├──────────┼──────────────────────────────────────────────────────┤
│ 操作 │ キーボード中心 │
│ │ (数字キーでカード選択、Enterで確定、 │
│ │ Spaceでターン終了) │
├──────────┼──────────────────────────────────────────────────────┤
│ プレイ数 │ 1人(vsレガシーシステムAI) │
├──────────┼──────────────────────────────────────────────────────┤
│ ビジュアル│ シンプル(テキスト・アイコン・数字のみ) │
├──────────┼──────────────────────────────────────────────────────┤
│ 育成要素 │ エンジニアカードをチームに配置し、 │
│ │ スプリントごとにXP獲得・レベルアップ │
├──────────┼──────────────────────────────────────────────────────┤
│ カード収集│ レアリティあり(Common/Rare/Epic)、 │
│ │ スプリント後にドラフトで追加 │
├──────────┼──────────────────────────────────────────────────────┤
│ クリア条件│ なし(継続シミュレーション形式、 │
│ │ モダン化スコアを上げ続ける) │
├──────────┼──────────────────────────────────────────────────────┤
│ 勝敗 │ 「システム障害レベル」が最大になるとゲームオーバー │
└──────────┴──────────────────────────────────────────────────────┘
---
ゲーム概要: "Legacy Burst"
世界観
あなたはプロジェクトマネージャー。巨大なレガシーシステムを抱えた企業で、
モダン化プロジェクトを進めながら日々の障害に対処する。
レガシーシステムは毎ターン「技術的負債」と「インシデント」を生み出し続ける。
ターン構造(1ターン = 1スプリント)
1. ドロー : デッキから5枚引く
2. プレイ : Budgetを消費してカードを使用(数字キー1〜5)
3. チーム効果 : 配置済みエンジニアのパッシブ効果を適用
4. レガシーAI : 技術的負債・インシデントを自動生成
5. スプリント終了(Space)
6. ドラフト : 3枚から1枚選んでデッキに追加
リソース
┌────────────────────┬─────────────────────────────┐
│ リソース │ 説明 │
├────────────────────┼─────────────────────────────┤
│ Budget 💰 │ 毎ターン補充。カードのプレイコスト │
├────────────────────┼─────────────────────────────┤
│ Tech Debt 🪙 │ 蓄積すると障害レベルが上がる │
├────────────────────┼─────────────────────────────┤
│ Modernization Score│ 高ければ高いほど良い。 │
│ ★ │ ゲームの進捗指標 │
├────────────────────┼─────────────────────────────┤
│ System Health 🛡 │ 0になるとゲームオーバー │
└────────────────────┴─────────────────────────────┘
カード種別(全20種程度)
┌────────────┬──────────────────────────────┬──────────────────────────┐
│ カテゴリ │ カード例 │ 効果 │
├────────────┼──────────────────────────────┼──────────────────────────┤
│ 🔧 Refactor│ コードリファクタ / テスト追加 │ Tech Debt 削減 │
├────────────┼──────────────────────────────┼──────────────────────────┤
│ ☁ Cloud │ API化 / コンテナ移行 / │ Modernization Score ↑ │
│ │ CDN導入 │ │
├────────────┼──────────────────────────────┼──────────────────────────┤
│ 🧪 Quality │ コードレビュー / CI/CD構築 │ Health ↑, Debt ↓ │
├────────────┼──────────────────────────────┼──────────────────────────┤
│ 👥 Engineer│ フロントエンジニア / SRE / │ チームに配置、 │
│ │ アーキテクト │ パッシブ効果 │
├────────────┼──────────────────────────────┼──────────────────────────┤
│ 🚨 Incident│ 緊急パッチ / ロールバック │ インシデント即時解決 │
└────────────┴──────────────────────────────┴──────────────────────────┘
育成(エンジニアカード)
- 手札から「チーム」に置く(最大3枠)
- 毎スプリント自動でXP獲得
- Lv1 → Lv2 → Lv3でパッシブ効果が強化される
- 例:
SRE Lv1 「毎ターンHealth +1」
→ Lv3 「毎ターンHealth +2 かつインシデント確率 -20%」
レアリティ
- Common(灰) : 基本カード、序盤から入手可
- Rare(青) : 強力な効果、ドラフトで低確率
- Epic(紫) : 特殊な複合効果、ゲーム後半のみ出現
レガシーAI の動作
毎ターン以下をランダムに実行:
- Tech Debt +1〜3
- ランダムなインシデント発生(放置するとHealth減少)
- スプリント数が増えるほど強化
---
実装計画
ファイル構成
単一HTMLファイル(legacy_burst.html)にすべて含める。
CSS・JavaScript をインラインで記述。
外部ライブラリ一切なし。
実装ステップ
1. HTML構造
- ゲーム画面レイアウト
(手札エリア、チームゾーン、ステータスバー、ログ)
2. ゲームステート管理
- JavaScriptオブジェクトで全状態を管理
3. カードデータ定義
- 20種のカード定義
(名前、コスト、レアリティ、効果関数)
4. デッキ・ドロー処理
- シャッフル
- ドロー
- 手札枯れ時のリシャッフル
5. ターンエンジン
- プレイヤー → チーム効果 → AI → スプリント終了
の流れ
6. 育成システム
- チームゾーン管理
- XP計算
- レベルアップ
7. ドラフトUI
- スプリント後の3択カード選択
8. キーボード操作
- 1〜5でカード選択
- Enter確定
- Space終了
- Escキャンセル
9. ゲームオーバー判定
- System Health = 0 の検出とリスタート
10. UI仕上げ
- ログ表示
- アニメーション(CSSのみ)
- レアリティ色分け
こちらを承認して出来上がったものが、以下のブラウザゲーム、LEGACY BURST になります。
限られた予算で行動を打ち、技術的負債(Tech Debt)とインシデントを解消しながら、モダン化を進めつつスプリントを進めるといったゲームになります。
ゲーム画面
ゲーム概要
ゲーム概要: "Legacy Burst"
世界観
あなたはプロジェクトマネージャー。巨大なレガシーシステムを抱えた企業で、
モダン化プロジェクトを進めながら日々の障害に対処する。
レガシーシステムは毎ターン「技術的負債(Tech Debt)」と「インシデント」を生み出し続ける。
ターン構造(1ターン = 1スプリント)
1. ドロー : デッキから5枚引く
2. プレイ : Budgetを消費してカードを使用(数字キー1〜5)
3. チーム効果 : 配置済みエンジニアのパッシブ効果を適用
4. レガシーAI : 技術的負債(Tech Debt)・インシデントを自動生成
5. スプリント終了(Space)
6. ドラフト : 3枚から1枚選んでデッキに追加
さっそく遊んでみたところ、ゲームシステムは意外なほどにしっかりしており、またシステム開発という点でのエスプリも効いていて、下手にスペック駆動開発するより全然いいものができてしまった感がありました。
(絶対にゲームクリアができず、システム開発失敗して終了するというところが一番好み)
そしてリファクタリング
ゲームシステム自体は良かったものの、無限にゲームが終わらない(続けられてしまう)といった難易度に関する課題や、やっぱりマウス操作もやりたい、などといった要望が出てきました。
プレイ(人間)&コメント(人間)とリファクタリング(Claude)を進め、とりあえずマトモっぽくなった版の画面イメージとソースコードが以下になります。
ゲーム画面
リファクタリング内容
リファクタ内容一覧
●以下が初期版(r1)から現在版への改善点の全リストです。
---
機能改善ポイント一覧
ゲームバランス調整(難易度強化)
- Health 初期値変更: 30 → 12(大幅に緊張感を高める)
- 初期Tech Debt: 0 →13(ゲーム開始直後から負債プレッシャーが存在)
- Tech Debt 自動発生量増加: +1〜3/sprint →+2〜5/sprint(後半はさらに増加)
- Tech Debt 複利発生: Debt が 15 を超えると毎スプリント利子が加算
- Tech Debt フロア導入: floor(sprint/3) を下限とし、後半は削りきれなくなる
- intensityキャップ引き上げ: sprint12 頭打ち →sprint20 まで増加し続ける
- インシデントのダメージ引き上げ: COMMON 1→2、RARE2→3、EPIC2〜3→3〜4
- インシデント発生確率上昇: baseP 0.30 →0.45、第2ロール base 0.10 →0.20
- Sprint1から脅威あり: initGame() で legacyAI() を先行実行、初手からDebt・インシデントが存在
インシデント拡充
- インシデント種類を倍増: 8件 → 31件(COMMON×11、RARE×11、EPIC×9)
- Debt発生インシデントを大量追加: 設計書なし・コーディング規約違反・アーキテクチャ腐敗・負債連鎖など15件
- レアリティ別色分け表示: COMMON/RARE/EPICでインシデントカードの見た目を分離
- RARE出現タイミング早期化: sprint5 →sprint3
- EPIC出現タイミング早期化: sprint10 → sprint7
- インシデント最大保有数拡大: 4件 →5件
- 第3ロール追加: sprint8以降、連鎖インシデントが追加発生する可能性
- エスカレーション機能: 放置インシデントはage増加し、age2でダメージ+1、age3でDebt+3追加(⬆AGE/⬆⬆ESC表示)
カード調整
- patch カード強化: インシデント解決時に Budget +1 を付与
- rollback カード調整: 全件消去 →2件のみ消去に制限、回復効果(Health+2)を削除
- container / cicd / cdn カード変更: Health回復効果を廃止 →Tech Debt削減に変換(HP回復源を絞ることで緊張感を演出)
- SREエンジニアパッシブ変更: Health +lv/sprint(最大+3)→固定 +1/sprint(過剰な回復を制限)
- 初期デッキ調整: refactor 5枚 →3枚、test_add 3枚 →4枚、docs 1枚追加
Budget システム変更
- Budget持越し制導入: 毎スプリントリセットから +3加算・持越し方式に変更
- Budget上限撤廃: maxBudgetの概念を廃止、上限なしに蓄積可能
- 表示変更: Budget: X/Y →Budget: X(シンプルに)
初期状態改善
- 初期手札にエンジニア1名を確定配置: ゲーム開始時から戦略的選択が発生
UI・操作性改善
- マウスクリック操作の追加(キーボードは引き続き使用可)
- Actionカード: 1クリック選択 →2クリック目で実行
- Engineerカード: 選択後にTeam Zoneのスロットをクリックで配置
- Card Draft: 1クリック選択 →2クリック目または「入手する」ボタンで確定
- スプリント終了ボタン: 大きな緑のボタンを手札下に追加、フェーズ外は自動でグレーアウト
- ドラフト確定・スキップボタン追加: マウスのみでドラフト操作が完結
- Team Zoneスロットのクリック対応: エンジニア選択中はスロットが黄色ハイライト&クリック可能
- デッキ一覧オーバーレイ追加: [L]キーで全保有カードを一覧表示
- ヘルプオーバーレイ追加: [H]キーでゲームルールを全画面表示
- キーボード操作拡充: [←][→]でカード移動選択、[7][8][9]でスロット直接指定、エンジニア入れ替え対応
- エンジニアスロット入れ替え機能: Team Zone満員でも[7][8][9]で既存メンバーと入れ替え可能
- インシデント詳細表示強化: レアリティ・副次効果・エスカレーション状態を表示
ゲームオーバー画面の改善
- タイトル変更: 「SYSTEM DOWN」→「PROJECT COLLAPSE」
- 5段階ランク評価追加: Sprint数・Modernization Scoreそれぞれの閾値の低いほうで S/A/B/C/D を判定
- ランク別カラー表示: S=金、A=青、B=緑、C=橙、D=赤
Legacy Burst
htmlファイルはこちら
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Legacy Burst — System Renewal Simulation</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#080c18;color:#c8d8e8;font-family:'Courier New',Courier,monospace;font-size:13px;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:16px}
#app{width:920px;max-width:100%}
/* HEADER */
#hdr{display:flex;justify-content:space-between;align-items:center;background:#0b1222;border:1px solid #1e3a5f;padding:8px 14px;margin-bottom:6px;border-top:2px solid #4fc3f7}
#title{color:#4fc3f7;font-size:18px;font-weight:bold;letter-spacing:3px}
#stats{display:flex;gap:14px}
.st{display:flex;flex-direction:column;align-items:center;gap:1px}
.sl{color:#3a5a78;font-size:9px;text-transform:uppercase;letter-spacing:.5px}
.sv{font-weight:bold;font-size:15px}
.sv.g{color:#4caf50}.sv.r{color:#ef5350}.sv.y{color:#ffd54f}.sv.p{color:#ce93d8}.sv.b{color:#4fc3f7}.sv.d{color:#607d8b}
/* HEALTH BAR */
.hbw{width:70px;height:3px;background:#1e3a5f;border-radius:2px;margin-top:2px}
.hb{height:100%;border-radius:2px;transition:width .3s}
.hb.g{background:#4caf50}.hb.y{background:#ff9800}.hb.r{background:#ef5350}
/* SECTION */
.sec{background:#0b1222;border:1px solid #1e3a5f;padding:8px 12px;margin-bottom:6px}
.sec-h{color:#3a5a78;font-size:9px;text-transform:uppercase;letter-spacing:1px;margin-bottom:7px;border-bottom:1px solid #1a2e45;padding-bottom:3px}
/* INCIDENTS */
#inc-list{display:flex;gap:8px;flex-wrap:wrap;min-height:24px;align-items:center}
.inc{background:#2e1010;border:1px solid #7a1a1a;padding:3px 8px;border-radius:3px;color:#ff7070;font-size:11px}
.inc.rare{background:#2a1840;border-color:#8030c0;color:#d090f0}
.inc.epic{background:#3a0505;border-color:#cc1a00;color:#ff6040;font-weight:bold}
.inc-rb{display:inline-block;font-size:8px;padding:0 3px;border-radius:2px;margin-right:4px;vertical-align:middle}
.inc-rb.common{background:#3a1a1a;color:#ff7070}
.inc-rb.rare{background:#3a1a50;color:#d090f0}
.inc-rb.epic{background:#4a0a0a;color:#ff6040}
.no-inc{color:#1e3a20;font-size:11px}
/* TEAM */
#team{display:flex;gap:8px}
.ts{flex:1;min-height:72px;border:1px dashed #1a2e45;border-radius:3px;padding:7px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;transition:border-color .15s,background .15s}
.ts.occ{border-style:solid}
.ts.occ.common{border-color:#455a64;background:#0a1520}
.ts.occ.rare{border-color:#1565c0;background:#0b1624}
.ts.occ.epic{border-color:#6a1b9a;background:#120b20}
.ts.swap-tgt{border-color:#ffd54f!important;background:#161400!important}
.eng-name{font-size:11px;font-weight:bold;color:#90caf9;margin-bottom:2px}
.eng-lv{font-size:11px;color:#ffd54f}
.eng-xp{font-size:10px;color:#3a5a78;margin-top:1px}
.eng-pas{font-size:9px;color:#80cbc4;margin-top:3px;line-height:1.3}
.ts-empty{color:#1e3050;font-size:11px}
.ts-slot-key{font-size:11px;color:#ffd54f;font-weight:bold;margin-bottom:4px}
/* HAND */
#hand{display:flex;gap:7px;flex-wrap:wrap;min-height:110px;align-items:flex-start}
.card{width:155px;border:1px solid #1a2e45;border-radius:3px;padding:7px;cursor:pointer;background:#090f1c;transition:border-color .12s,background .12s;position:relative;flex-shrink:0}
.card:hover{border-color:#3a7aaa}
.card.sel{border-color:#ffd54f!important;background:#161400;box-shadow:0 0 10px rgba(255,213,79,.35)}
.card.dim{opacity:.35}
.card.common{border-left:3px solid #455a64}
.card.rare{border-left:3px solid #1565c0}
.card.epic{border-left:3px solid #6a1b9a}
.c-key{position:absolute;top:4px;right:6px;color:#3a5a78;font-size:10px}
.c-rb{display:inline-block;font-size:8px;padding:1px 3px;border-radius:2px;margin-bottom:3px}
.c-rb.common{background:#263238;color:#90a4ae}
.c-rb.rare{background:#0d2a6e;color:#90caf9}
.c-rb.epic{background:#36006e;color:#ce93d8}
.c-name{font-size:12px;font-weight:bold;color:#c8d8e8;margin-bottom:3px;padding-right:16px}
.c-cost{font-size:11px;color:#ffd54f;margin-bottom:2px}
.c-type{font-size:9px;color:#3a5a78;margin-bottom:3px}
.c-desc{font-size:9px;color:#607d8b;line-height:1.4}
.c-hint{font-size:9px;color:#ffd54f;margin-top:4px}
.hand-empty{color:#1e3050;font-size:11px;align-self:center;margin:auto}
/* CONTROLS */
#ctrl{color:#2e4a62;font-size:11px;padding:5px 12px;text-align:center;background:#080c18;border:1px solid #111d2e;margin-bottom:6px}
#ctrl b{color:#4fc3f7}
/* LOG */
#log{height:75px;overflow-y:auto;font-size:11px;line-height:1.6}
.le{border-bottom:1px solid #0b1222;padding:0 1px}
.le.imp{color:#c8d8e8}.le.suc{color:#4caf50}.le.war{color:#ff9800}.le.dan{color:#ef5350}.le.inf{color:#4fc3f7}.le.def{color:#3a5a78}
/* PHASE */
#phase{text-align:center;font-size:10px;color:#2e4a62;margin-bottom:4px}
#phase .act{color:#ffd54f}
/* DRAFT OVERLAY */
#draft-ov{display:none;position:fixed;inset:0;background:rgba(0,0,0,.88);justify-content:center;align-items:center;z-index:100}
#draft-ov.on{display:flex}
#draft-panel{background:#0b1222;border:2px solid #1e3a5f;padding:22px;width:620px;max-width:95vw;border-top:2px solid #4fc3f7}
.draft-title{color:#4fc3f7;font-size:15px;margin-bottom:4px;letter-spacing:1px}
.draft-sub{color:#3a5a78;font-size:11px;margin-bottom:14px}
#draft-cards{display:flex;gap:10px}
.dc{flex:1;border:1px solid #1a2e45;border-radius:3px;padding:10px;cursor:pointer;background:#090f1c;transition:border-color .12s,background .12s}
.dc:hover,.dc.sel{border-color:#ffd54f;background:#161400}
.dc.common{border-left:3px solid #455a64}
.dc.rare{border-left:3px solid #1565c0}
.dc.epic{border-left:3px solid #6a1b9a}
.dc-key{color:#ffd54f;font-size:13px;font-weight:bold;margin-bottom:5px}
/* DECK VIEW OVERLAY */
#deck-ov{display:none;position:fixed;inset:0;background:rgba(0,0,0,.93);z-index:150;overflow-y:auto;padding:20px}
#deck-ov.on{display:block}
#deck-panel{max-width:840px;margin:0 auto;background:#0b1222;border:2px solid #1e3a5f;padding:22px;border-top:2px solid #ce93d8}
.deck-hdr{color:#ce93d8;font-size:15px;margin-bottom:4px;letter-spacing:1px}
.deck-sub{color:#3a5a78;font-size:11px;margin-bottom:14px}
#deck-cards{display:flex;flex-wrap:wrap;gap:7px}
#deck-cards .card{cursor:default}
/* HELP OVERLAY */
#help-ov{display:none;position:fixed;inset:0;background:#080c18;z-index:160;overflow-y:auto;padding:24px}
#help-ov.on{display:block}
#help-panel{max-width:820px;margin:0 auto}
.help-title{color:#4fc3f7;font-size:20px;letter-spacing:3px;margin-bottom:16px;border-bottom:1px solid #1e3a5f;padding-bottom:8px}
.help-sec{margin-bottom:18px}
.help-sh{color:#ffd54f;font-size:11px;font-weight:bold;margin-bottom:8px;text-transform:uppercase;letter-spacing:1px}
.help-p{color:#607d8b;font-size:11px;line-height:1.9;margin-bottom:6px}
.help-table{width:100%;border-collapse:collapse;font-size:11px;margin-bottom:8px}
.help-table th{color:#3a5a78;text-align:left;padding:3px 10px;border-bottom:1px solid #1a2e45;font-weight:normal;font-size:10px;text-transform:uppercase}
.help-table td{color:#607d8b;padding:4px 10px;border-bottom:1px solid #0d1424}
.help-table td:first-child{color:#c8d8e8;white-space:nowrap}
.help-cols{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.help-close{text-align:center;margin-top:24px;color:#2e4a62;font-size:11px;padding-top:12px;border-top:1px solid #1a2e45}
/* SPRINT END BUTTON */
#sprint-wrap{text-align:center;margin-bottom:6px}
#sprint-btn{background:#0d2a1a;border:2px solid #4caf50;color:#4caf50;padding:10px 48px;cursor:pointer;font-family:'Courier New',Courier,monospace;font-size:14px;letter-spacing:3px;transition:background .15s,border-color .15s}
#sprint-btn:hover:not(:disabled){background:#1a3f28;border-color:#81c784}
#sprint-btn:disabled{opacity:.25;cursor:default;border-color:#2e4a62;color:#2e4a62}
/* DRAFT CONFIRM/SKIP BUTTONS */
#draft-confirm{display:block;width:100%;margin-top:14px;background:#0d2240;border:2px solid #4fc3f7;color:#4fc3f7;padding:8px;cursor:pointer;font-family:'Courier New',Courier,monospace;font-size:13px;letter-spacing:2px}
#draft-confirm:hover:not(:disabled){background:#142e50}
#draft-confirm:disabled{opacity:.3;cursor:default}
#draft-skip{background:none;border:none;color:#2e4a62;font-size:11px;font-family:'Courier New',Courier,monospace;cursor:pointer;margin-top:6px;display:block;width:100%;text-align:center}
#draft-skip:hover{color:#4fc3f7}
/* TEAM SLOT clickable */
.ts.clickable{cursor:pointer}
.ts.clickable:hover{border-color:#ffd54f!important;background:#161400!important}
/* GAME OVER */
#go-ov{display:none;position:fixed;inset:0;background:rgba(0,0,0,.92);justify-content:center;align-items:center;z-index:200}
#go-ov.on{display:flex}
#go-panel{background:#0b1222;border:2px solid #7a1a1a;padding:30px;text-align:center;max-width:400px;width:90vw}
#go-title{color:#ef5350;font-size:22px;letter-spacing:3px;margin-bottom:14px}
#go-rank{font-size:40px;font-weight:bold;letter-spacing:6px;margin-bottom:4px}
#go-rank-label{font-size:13px;letter-spacing:3px;margin-bottom:8px;text-transform:uppercase}
#go-rank-msg{font-size:11px;color:#607d8b;margin-bottom:14px;line-height:1.7}
#go-info{color:#3a5a78;line-height:2;margin-bottom:18px;font-size:12px}
#go-info .hi{color:#c8d8e8}
#restart{background:#1e3a5f;border:1px solid #4fc3f7;color:#4fc3f7;padding:7px 22px;cursor:pointer;font-family:monospace;font-size:13px;letter-spacing:1px}
#restart:hover{background:#253e5f}
.rank-s{color:#ffd700}.rank-a{color:#4fc3f7}.rank-b{color:#4caf50}.rank-c{color:#ff9800}.rank-d{color:#ef5350}
</style>
</head>
<body>
<div id="app">
<div id="hdr">
<div id="title">⚙ LEGACY BURST</div>
<div id="stats">
<div class="st"><span class="sl">Sprint</span><span class="sv b" id="s-sprint">1</span></div>
<div class="st"><span class="sl">Budget 💰</span><span class="sv y" id="s-budget">0</span></div>
<div class="st"><span class="sl">Tech Debt 🔴</span><span class="sv r" id="s-debt">0</span></div>
<div class="st"><span class="sl">Modern ⭐</span><span class="sv p" id="s-score">0</span></div>
<div class="st">
<span class="sl">Health 🟢</span>
<span class="sv g" id="s-hp">12/12</span>
<div class="hbw"><div class="hb g" id="hb" style="width:100%"></div></div>
</div>
<div class="st"><span class="sl">Deck</span><span class="sv d" id="s-deck">10</span></div>
</div>
</div>
<div id="phase"><span id="phase-txt">[ <span class="act">プレイフェーズ</span> ]</span></div>
<div class="sec">
<div class="sec-h">⚠ Active Incidents</div>
<div id="inc-list"><span class="no-inc">障害なし — システム安定稼働中</span></div>
</div>
<div class="sec">
<div class="sec-h">👥 Team Zone — エンジニアカードを配置 (max 3 / スロットをクリックまたは [7][8][9] で入れ替え)</div>
<div id="team">
<div class="ts" id="ts0"><span class="ts-empty">[ Empty Slot ]</span></div>
<div class="ts" id="ts1"><span class="ts-empty">[ Empty Slot ]</span></div>
<div class="ts" id="ts2"><span class="ts-empty">[ Empty Slot ]</span></div>
</div>
</div>
<div class="sec">
<div class="sec-h">🃏 Hand — Budget残: <span id="h-budget">3</span></div>
<div id="hand"><span class="hand-empty">カードなし</span></div>
</div>
<div id="sprint-wrap">
<button id="sprint-btn" onclick="endSprint()">▶ SPRINT 終了 [Space]</button>
</div>
<div id="ctrl">
<b>[1-5][←→]</b> 選択 | <b>[Enter]</b> プレイ | <b>[Esc]</b> 解除 | <b>[Space]</b> スプリント終了 | <b>[L]</b> デッキ一覧 | <b>[H]</b> ヘルプ
</div>
<div class="sec">
<div class="sec-h">📋 Log</div>
<div id="log"></div>
</div>
</div>
<!-- DRAFT OVERLAY -->
<div id="draft-ov">
<div id="draft-panel">
<div class="draft-title">⭐ Sprint Complete — Card Draft</div>
<div class="draft-sub">カードをクリックして選択 → もう一度クリックまたは下の「入手する」で追加 | キー [1][2][3] / [Enter]</div>
<div id="draft-cards"></div>
<button id="draft-confirm" onclick="confirmDraft()" disabled>[ 入手する ] ▶</button>
<button id="draft-skip" onclick="skipDraft()">スキップ [Esc]</button>
</div>
</div>
<!-- DECK VIEW OVERLAY -->
<div id="deck-ov">
<div id="deck-panel">
<div class="deck-hdr">📚 Current Deck — 保有カード一覧</div>
<div class="deck-sub">全カード(デッキ + 手札 + 捨て札) | <b>[L]</b> または <b>[Esc]</b> で閉じる | 合計: <span id="deck-count">0</span> 枚</div>
<div id="deck-cards"></div>
</div>
</div>
<!-- HELP OVERLAY -->
<div id="help-ov">
<div id="help-panel">
<div class="help-title">⚙ LEGACY BURST — ゲームガイド</div>
<div class="help-cols">
<div>
<div class="help-sec">
<div class="help-sh">🎯 ゲーム概要</div>
<div class="help-p">あなたはプロジェクトマネージャー。毎スプリント(ターン)カードをプレイしながら、レガシーシステムを近代化する。レガシーシステムは毎ターン「技術的負債」と「インシデント」を生み出し続ける。<br><b style="color:#ef5350">System Health が 0 になるとゲームオーバー。</b><br>Modernization Score を上げ続けることが目標。</div>
</div>
<div class="help-sec">
<div class="help-sh">🔄 ターン構造</div>
<table class="help-table">
<tr><td>1. ドロー</td><td>デッキから5枚引く</td></tr>
<tr><td>2. プレイ</td><td>Budgetを消費してカードを使用</td></tr>
<tr><td>3. スプリント終了</td><td>[Space]を押す</td></tr>
<tr><td>4. チーム効果</td><td>エンジニアのパッシブ発動・XP+1</td></tr>
<tr><td>5. Legacy AI</td><td>技術的負債・インシデントを自動生成</td></tr>
<tr><td>6. ドラフト</td><td>3枚から1枚選んでデッキに追加</td></tr>
</table>
</div>
<div class="help-sec">
<div class="help-sh">📊 リソース</div>
<table class="help-table">
<tr><td>💰 Budget</td><td>毎スプリント +3 加算(持越し可・上限なし)。カードのプレイコスト。インシデントで削られることも</td></tr>
<tr><td>🔴 Tech Debt</td><td>蓄積すると10ごとにHealth削減。リファクタカードで減少</td></tr>
<tr><td>⭐ Modern Score</td><td>ゲームの進捗指標。クラウド・品質カードで増加</td></tr>
<tr><td>🟢 Health</td><td>上限10。0でゲームオーバー。インシデント放置で毎ターン減少</td></tr>
</table>
</div>
<div class="help-sec">
<div class="help-sh">🃏 カード種別</div>
<table class="help-table">
<tr><td>🔧 Refactor</td><td>Tech Debtを削減する</td></tr>
<tr><td>☁️ Cloud/Modern</td><td>Modernization Scoreを増加させる</td></tr>
<tr><td>🚑 Incident</td><td>インシデント解決。解決ごとにBudget+1(上限超は棄却)</td></tr>
<tr><td>👥 Engineer</td><td>チームゾーンに配置。毎スプリントパッシブ発揮</td></tr>
</table>
<table class="help-table">
<tr><th>レアリティ</th><th>説明</th></tr>
<tr><td>COMMON</td><td>基本カード。序盤から出現</td></tr>
<tr><td>RARE</td><td>強力な効果。ドラフトで低確率</td></tr>
<tr><td>EPIC</td><td>特殊な複合効果。Sprint4以降から出現</td></tr>
</table>
</div>
</div>
<div>
<div class="help-sec">
<div class="help-sh">👥 エンジニア育成</div>
<div class="help-p">エンジニアカードをチームゾーン(最大3スロット)に配置すると、毎スプリント自動でXPを獲得しレベルアップ。<br>チームが満員の場合は [7][8][9] でスロット指定して入れ替え可能。古いエンジニアのXP・レベルは消滅。</div>
<table class="help-table">
<tr><th>レベル</th><th>必要XP</th></tr>
<tr><td>Lv1 → Lv2</td><td>3 XP(3スプリント)</td></tr>
<tr><td>Lv2 → Lv3</td><td>5 XP(5スプリント)</td></tr>
</table>
<table class="help-table">
<tr><th>エンジニア</th><th>パッシブ効果</th></tr>
<tr><td>🖥️ フロントエンジニア</td><td>Modern +1/sprint (Lv3: +2)</td></tr>
<tr><td>🛡️ SREエンジニア</td><td>Health +1/sprint(固定), incident率 -10%×lv</td></tr>
<tr><td>📐 アーキテクト</td><td>Modern +2/sprint, Debt -1 (Lv2:+3, Lv3:Debt-2)</td></tr>
<tr><td>⚙️ DevOpsエンジニア</td><td>Debt -lv/sprint</td></tr>
<tr><td>💾 バックエンドエンジニア</td><td>Debt -1/sprint, Lv2:HP+1, Lv3:Modern+2</td></tr>
</table>
</div>
<div class="help-sec">
<div class="help-sh">⌨️ キー操作</div>
<table class="help-table">
<tr><td>[1] ~ [5]</td><td>手札カード選択</td></tr>
<tr><td>[←] [→]</td><td>手札カードを左右に移動選択</td></tr>
<tr><td>[Enter]</td><td>選択カードをプレイ(Engineer: 空きスロットへ配置)</td></tr>
<tr><td>[7][8][9]</td><td>Engineerカード選択中: スロット1/2/3に配置・入れ替え</td></tr>
<tr><td>[Space]</td><td>スプリント終了</td></tr>
<tr><td>[Esc]</td><td>選択解除 / オーバーレイを閉じる</td></tr>
<tr><td>[L]</td><td>保有カード一覧を表示</td></tr>
<tr><td>[H]</td><td>このヘルプを表示</td></tr>
</table>
</div>
<div class="help-sec">
<div class="help-sh">⚠️ Legacy AI の動作</div>
<div class="help-p">毎スプリント終了後に発動。スプリント数が増えるほど強化される。</div>
<table class="help-table">
<tr><td>Tech Debt増加</td><td>毎ターン +2〜9(Sprint20まで増加)。Debt15超えで複利発生。Sprint/3がDebtの下限(削りきれない)</td></tr>
<tr><td>Health削減</td><td>Tech Debt が 10 を超えると floor(Debt/10) のHealth削減</td></tr>
<tr><td>インシデント</td><td>確率でインシデントを追加(最大5件)。COMMON dmg2 / RARE dmg3 / EPIC dmg4。Sprint3でRARE、Sprint7でEPIC出現。SREで軽減可</td></tr>
<tr><td>エスカレーション</td><td>放置インシデントはageカウント増加。age2でダメージ+1(⬆AGE)、age3でTech Debt+3も追加(⬆⬆ESC)</td></tr>
<tr><td>連鎖インシデント</td><td>Sprint8以降、第3ロールで追加インシデントが発生することがある</td></tr>
</table>
</div>
</div>
</div>
<div class="help-close"><b>[H]</b> または <b>[Esc]</b> で閉じる</div>
</div>
</div>
<!-- GAME OVER -->
<div id="go-ov">
<div id="go-panel">
<div id="go-title">💥 PROJECT COLLAPSE</div>
<div id="go-rank"></div>
<div id="go-rank-label"></div>
<div id="go-rank-msg"></div>
<div id="go-info"></div>
<button id="restart" onclick="initGame()">[ RESTART ]</button>
</div>
</div>
<script>
// ========== CARD DEFINITIONS ==========
const XP_UP = [3, 5]; // xp to level from 1->2, 2->3
const ALL_CARDS = [
// --- REFACTOR (common) ---
{ id:'refactor', name:'コードリファクタ', cost:1, rarity:'common', type:'action', icon:'🔧',
desc:'Tech Debt -2',
fx(s){ s.techDebt=Math.max(0,s.techDebt-2); log('🔧 コードリファクタ: Tech Debt -2','suc') } },
{ id:'test_add', name:'テスト追加', cost:1, rarity:'common', type:'action', icon:'🧪',
desc:'Tech Debt -1, Health +1',
fx(s){ s.techDebt=Math.max(0,s.techDebt-1); s.hp=Math.min(s.maxHp,s.hp+1); log('🧪 テスト追加: Tech Debt -1, Health +1','suc') } },
{ id:'debt_pay', name:'技術的負債返済', cost:2, rarity:'common', type:'action', icon:'💳',
desc:'Tech Debt -5',
fx(s){ s.techDebt=Math.max(0,s.techDebt-5); log('💳 技術的負債返済: Tech Debt -5','suc') } },
{ id:'deadcode', name:'デッドコード削除', cost:1, rarity:'common', type:'action', icon:'🗑️',
desc:'Tech Debt -1, +1 Draw',
fx(s){ s.techDebt=Math.max(0,s.techDebt-1); drawN(1); log('🗑️ デッドコード削除: Tech Debt -1, +1 Draw','suc') } },
{ id:'review', name:'コードレビュー', cost:1, rarity:'common', type:'action', icon:'👁️',
desc:'Tech Debt -1, Modern +1',
fx(s){ s.techDebt=Math.max(0,s.techDebt-1); s.score+=1; log('👁️ コードレビュー: Tech Debt -1, Modern +1','suc') } },
{ id:'pair', name:'ペアプログラミング', cost:1, rarity:'common', type:'action', icon:'👬',
desc:'Tech Debt -1, Modern +1, +1 Draw',
fx(s){ s.techDebt=Math.max(0,s.techDebt-1); s.score+=1; drawN(1); log('👬 ペアプログラミング: Debt -1, Modern +1, +1 Draw','suc') } },
{ id:'patch', name:'緊急パッチ', cost:1, rarity:'common', type:'action', icon:'🚑',
desc:'インシデント1件解決, Budget +1',
fx(s){
if(s.incs.length>0){
const i=s.incs.shift();
s.budget+=1;
log(`🚑 緊急パッチ: "${i.name}" 解決 +1 Budget`, 'suc');
} else { log('🚑 緊急パッチ: インシデントなし(不発)','war') }
} },
{ id:'rollback', name:'ロールバック', cost:2, rarity:'common', type:'action', icon:'⏪',
desc:'インシデント2件解決, Budget +1/件',
fx(s){
const resolved=s.incs.splice(0,2);
const n=resolved.length;
s.budget+=n;
log(`⏪ ロールバック: ${n}件解決, +${n} Budget`,'suc');
} },
{ id:'docs', name:'ドキュメント整備', cost:1, rarity:'common', type:'action', icon:'📝',
desc:'Tech Debt -1, 次スプリントインシデント確率 -10%',
fx(s){ s.techDebt=Math.max(0,s.techDebt-1); s.docBonus=(s.docBonus||0)+1; log('📝 ドキュメント整備: Debt -1, インシデント率↓','suc') } },
// --- CLOUD / MODERN (rare/epic) ---
{ id:'api', name:'API化', cost:2, rarity:'rare', type:'action', icon:'🔌',
desc:'Modern +4, Tech Debt -1',
fx(s){ s.score+=4; s.techDebt=Math.max(0,s.techDebt-1); log('🔌 API化: Modern +4, Tech Debt -1','inf') } },
{ id:'container', name:'コンテナ移行', cost:3, rarity:'rare', type:'action', icon:'📦',
desc:'Modern +6, Tech Debt -4',
fx(s){ s.score+=6; s.techDebt=Math.max(0,s.techDebt-4); log('📦 コンテナ移行: Modern +6, Tech Debt -4','inf') } },
{ id:'cicd', name:'CI/CD構築', cost:2, rarity:'rare', type:'action', icon:'🔄',
desc:'Modern +4, Tech Debt -3',
fx(s){ s.score+=4; s.techDebt=Math.max(0,s.techDebt-3); log('🔄 CI/CD構築: Modern +4, Tech Debt -3','suc') } },
{ id:'cloud', name:'クラウド移行', cost:3, rarity:'epic', type:'action', icon:'☁️',
desc:'Modern +8, Tech Debt -2',
fx(s){ s.score+=8; s.techDebt=Math.max(0,s.techDebt-2); log('☁️ クラウド移行: Modern +8, Tech Debt -2','inf') } },
{ id:'micro', name:'マイクロサービス化', cost:4, rarity:'epic', type:'action', icon:'🏗️',
desc:'Modern +12, Tech Debt -4',
fx(s){ s.score+=12; s.techDebt=Math.max(0,s.techDebt-4); log('🏗️ マイクロサービス化: Modern +12, Debt -4','inf') } },
{ id:'cdn', name:'CDN導入', cost:2, rarity:'rare', type:'action', icon:'🌐',
desc:'Modern +3, Tech Debt -3',
fx(s){ s.score+=3; s.techDebt=Math.max(0,s.techDebt-3); log('🌐 CDN導入: Modern +3, Tech Debt -3','inf') } },
// --- ENGINEERS ---
{ id:'e_front', name:'フロントエンジニア', cost:2, rarity:'rare', type:'engineer', icon:'🖥️',
desc:'パッシブ: Modern +1/sprint (Lv3: +2)',
pas(s,lv){ const g=lv>=3?2:1; s.score+=g; log(`🖥️ フロントエンジニア Lv${lv}: Modern +${g}`,'suc') } },
{ id:'e_sre', name:'SREエンジニア', cost:2, rarity:'rare', type:'engineer', icon:'🛡️',
desc:'パッシブ: Health +1/sprint, インシデント率 -10%×lv',
pas(s,lv){ s.hp=Math.min(s.maxHp,s.hp+1); log(`🛡️ SREエンジニア Lv${lv}: Health +1`,'suc') },
incRed(lv){ return lv*0.10 } },
{ id:'e_arch', name:'アーキテクト', cost:3, rarity:'epic', type:'engineer', icon:'📐',
desc:'パッシブ: Modern +2/sprint, Tech Debt -1/sprint (Lv3: Debt -2)',
pas(s,lv){ const m=lv>=2?3:2,d=lv>=3?2:1; s.score+=m; s.techDebt=Math.max(0,s.techDebt-d); log(`📐 アーキテクト Lv${lv}: Modern +${m}, Debt -${d}`,'suc') } },
{ id:'e_devops', name:'DevOpsエンジニア', cost:2, rarity:'rare', type:'engineer', icon:'⚙️',
desc:'パッシブ: Tech Debt -lv/sprint',
pas(s,lv){ s.techDebt=Math.max(0,s.techDebt-lv); log(`⚙️ DevOpsエンジニア Lv${lv}: Tech Debt -${lv}`,'suc') } },
{ id:'e_back', name:'バックエンドエンジニア', cost:2, rarity:'rare', type:'engineer', icon:'💾',
desc:'パッシブ: Tech Debt -1/sprint, Lv2: Health +1, Lv3: Modern +2',
pas(s,lv){ s.techDebt=Math.max(0,s.techDebt-1); if(lv>=2) s.hp=Math.min(s.maxHp,s.hp+1); if(lv>=3) s.score+=2; log(`💾 バックエンドエンジニア Lv${lv}: Debt -1${lv>=2?', Health +1':''}${lv>=3?', Modern +2':''}`,'suc') } },
];
const INC_POOL = [
// COMMON — dmg 2
{name:'DBクエリ遅延', dmg:2, icon:'🐢', rarity:'common'},
{name:'APIタイムアウト', dmg:2, icon:'⏱️', rarity:'common'},
{name:'バッチ処理失敗', dmg:2, icon:'❌', rarity:'common'},
{name:'ログ肥大化', dmg:2, icon:'📋', rarity:'common'},
{name:'SSL証明書期限切れ', dmg:2, icon:'🔒', rarity:'common'},
{name:'デプロイ失敗', dmg:2, icon:'🚫', rarity:'common'},
{name:'キャッシュ汚染', dmg:2, icon:'🗄️', rarity:'common'},
{name:'設定ファイル破損', dmg:2, icon:'📄', rarity:'common'},
{name:'設計書なし', dmg:2, icon:'📐', rarity:'common',
extra:'Debt +2', fx(s){s.techDebt+=2} },
{name:'コーディング規約違反', dmg:2, icon:'📏', rarity:'common',
extra:'Debt +2', fx(s){s.techDebt+=2} },
{name:'テストコード不足', dmg:2, icon:'🧪', rarity:'common',
extra:'Debt +3', fx(s){s.techDebt+=3} },
// RARE — dmg 3
{name:'メモリリーク', dmg:3, icon:'💧', rarity:'rare'},
{name:'ディスク枯渇', dmg:3, icon:'💿', rarity:'rare'},
{name:'ネットワーク断絶', dmg:3, icon:'📵', rarity:'rare'},
{name:'セキュリティ脆弱性', dmg:3, icon:'🔓', rarity:'rare',
extra:'Debt +3', fx(s){s.techDebt+=3} },
{name:'DBデッドロック', dmg:3, icon:'🔗', rarity:'rare',
extra:'Debt +2', fx(s){s.techDebt+=2} },
{name:'CPU過負荷', dmg:3, icon:'🔥', rarity:'rare',
extra:'Budget -1', fx(s){s.budget=Math.max(0,s.budget-1)} },
{name:'依存ライブラリ脆弱性', dmg:3, icon:'📦', rarity:'rare',
extra:'Debt +2', fx(s){s.techDebt+=2} },
{name:'認証システム障害', dmg:3, icon:'🚪', rarity:'rare'},
{name:'アーキテクチャ腐敗', dmg:3, icon:'🏚️', rarity:'rare',
extra:'Debt +4', fx(s){s.techDebt+=4} },
{name:'スパゲッティコード', dmg:3, icon:'🍝', rarity:'rare',
extra:'Debt +3', fx(s){s.techDebt+=3} },
{name:'依存関係地獄', dmg:3, icon:'🕸️', rarity:'rare',
extra:'Debt +5', fx(s){s.techDebt+=5} },
// EPIC — dmg 3-4
{name:'サービス停止', dmg:4, icon:'🔴', rarity:'epic'},
{name:'データ漏洩', dmg:3, icon:'💀', rarity:'epic',
extra:'Debt +5', fx(s){s.techDebt+=5} },
{name:'システムクラッシュ', dmg:4, icon:'💣', rarity:'epic',
extra:'Budget -2', fx(s){s.budget=Math.max(0,s.budget-2)} },
{name:'ランサムウェア感染', dmg:4, icon:'💰', rarity:'epic',
extra:'Budget -3, Debt +5', fx(s){s.budget=Math.max(0,s.budget-3);s.techDebt+=5} },
{name:'本番DB誤削除', dmg:4, icon:'🗃️', rarity:'epic',
extra:'Debt +8', fx(s){s.techDebt+=8} },
{name:'DDoS攻撃', dmg:3, icon:'🌊', rarity:'epic',
extra:'Budget -2', fx(s){s.budget=Math.max(0,s.budget-2)} },
{name:'レガシーコード崩壊', dmg:4, icon:'💥', rarity:'epic',
extra:'Debt +10', fx(s){s.techDebt+=10} },
{name:'モノリス崩壊', dmg:4, icon:'🗿', rarity:'epic',
extra:'Debt +8', fx(s){s.techDebt+=8} },
{name:'負債連鎖', dmg:4, icon:'⛓️', rarity:'epic',
extra:'Debt +12', fx(s){s.techDebt+=12} },
];
function buildIncPool(sprint){
const pool=[];
INC_POOL.filter(i=>i.rarity==='common').forEach(i=>{pool.push(i);pool.push(i);pool.push(i)});
if(sprint>=3) INC_POOL.filter(i=>i.rarity==='rare').forEach(i=>{pool.push(i);pool.push(i)});
if(sprint>=7) INC_POOL.filter(i=>i.rarity==='epic').forEach(i=>pool.push(i));
return pool;
}
// ========== STATE ==========
let G = {};
let draftQ = [], draftSel = -1, cardSel = -1;
let deckViewOpen = false, helpOpen = false;
function initGame(){
G = {
deck:[], hand:[], disc:[],
team:[null,null,null], // fixed 3 slots
budget:0,
techDebt:13, score:0,
hp:12, maxHp:12,
sprint:1,
incs:[],
docBonus:0,
phase:'play',
over:false,
};
const start = [
'refactor','refactor','refactor',
'test_add','test_add','test_add','test_add',
'patch','patch',
'docs',
];
G.deck = start.map(id=>({...cardById(id)}));
shuffle(G.deck);
const engCards=ALL_CARDS.filter(c=>c.type==='engineer');
G.hand.push({...engCards[Math.floor(Math.random()*engCards.length)]});
cardSel = -1;
closeDeckView(); closeHelp();
document.getElementById('draft-ov').classList.remove('on');
document.getElementById('go-ov').classList.remove('on');
clearLog();
log('⚙ Legacy Burst 開始 — レガシーシステムのモダン化プロジェクト発足','imp');
log('🎯 目標: System Health を維持しつつ、Modernization Score を上げ続けろ','inf');
log('💡 [H]キーでルール確認。インシデントを放置すると毎スプリント Health が削れる','def');
log('⚠ レガシーシステム稼働中 — すでに問題が山積している...','war');
legacyAI();
startSprint();
}
function cardById(id){ return ALL_CARDS.find(c=>c.id===id) }
function shuffle(a){ for(let i=a.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]] } }
function drawN(n){
for(let i=0;i<n;i++){
if(G.deck.length===0){
if(G.disc.length===0) break;
G.deck=[...G.disc]; G.disc=[]; shuffle(G.deck);
log('♻️ デッキ枯渇 — 捨て札をシャッフルして再生成','war');
}
if(G.hand.length<8) G.hand.push({...G.deck.pop()});
}
}
// ========== SPRINT ==========
function startSprint(){
G.phase='play'; G.budget+=3; cardSel=-1;
log(`─── Sprint ${G.sprint} 開始 (Budget +3 → ${G.budget}) ───`,'imp');
const need=Math.max(0,5-G.hand.length); drawN(need);
render();
}
function endSprint(){
if(G.phase!=='play') return;
// incident damage + escalation
G.incs.forEach(i=>{
G.hp=Math.max(0,G.hp-i.dmg);
const extra=i.extra?` [${i.extra}]`:'';
log(`💥 "${i.name}" [${(i.rarity||'common').toUpperCase()}] ダメージ: Health -${i.dmg}${extra}`,'dan');
if(i.fx) i.fx(G);
i.age=(i.age||0)+1;
if(i.age===2){
i.dmg+=1;
log(`⬆️ "${i.name}" エスカレーション! ダメージ増加 → -${i.dmg}HP/sprint`,'dan');
}
if(i.age===3 && !i.escalated){
i.escalated=true;
const prevFx=i.fx;
i.extra=(i.extra?i.extra+', ':'')+'Debt +3';
i.fx=(s)=>{ if(prevFx) prevFx(s); s.techDebt+=3; };
log(`💀 "${i.name}" 重大エスカレーション! Tech Debt +3 追加`,'dan');
}
});
if(G.hp<=0){ gameOver(); return; }
// team passives + XP
G.team.forEach(m=>{
if(!m) return;
m.card.pas && m.card.pas(G,m.lv);
m.xp+=1;
if(m.lv<3 && m.xp>=XP_UP[m.lv-1]){ m.lv+=1; m.xp=0; log(`🌟 ${m.card.name} レベルアップ! → Lv${m.lv}`,'imp') }
});
if(G.hp<=0){ gameOver(); return; }
// discard hand
G.disc.push(...G.hand); G.hand=[];
// tech debt floor — prevents full clearing in late game
const debtFloor=Math.floor(G.sprint/3);
if(G.techDebt<debtFloor){
G.techDebt=debtFloor;
log(`🔒 技術的負債フロア: Debt を ${debtFloor} に強制設定 (Sprint ${G.sprint})`,'war');
}
// legacy AI
legacyAI();
if(G.hp<=0){ gameOver(); return; }
G.sprint++;
startDraft();
}
function legacyAI(){
const intensity=Math.min(G.sprint,20);
const debt=2+Math.floor(Math.random()*2)+Math.floor(intensity/4);
G.techDebt+=debt;
log(`👾 Legacy AI: Tech Debt +${debt}`,'dan');
if(G.techDebt>=15){ const interest=Math.floor(G.techDebt/15); G.techDebt+=interest; log(`📈 技術的負債 複利発生: Debt +${interest} (累計: ${G.techDebt})`,'dan') }
if(G.techDebt>=10){ const d=Math.floor(G.techDebt/10); G.hp=Math.max(0,G.hp-d); log(`🔴 高い技術的負債 (${G.techDebt}): Health -${d}`,'dan') }
// incident generation
const sreRed=G.team.filter(m=>m&&m.card.incRed).reduce((a,m)=>a+m.card.incRed(m.lv),0);
const docRed=(G.docBonus||0)*0.10; G.docBonus=0;
const baseP=0.45+(intensity*0.04);
const p=Math.max(0,baseP-sreRed-docRed);
const pool=buildIncPool(G.sprint);
if(Math.random()<p && G.incs.length<5){
const i={...pool[Math.floor(Math.random()*pool.length)],age:0};
log(`🚨 新規インシデント: [${i.rarity.toUpperCase()}] "${i.name}" (-${i.dmg}HP/sprint${i.extra?', '+i.extra:''})`,'war');
G.incs.push(i);
}
if(Math.random()<0.20+(intensity*0.02) && G.incs.length<5){
const i={...pool[Math.floor(Math.random()*pool.length)],age:0};
log(`🚨 追加インシデント: [${i.rarity.toUpperCase()}] "${i.name}"${i.extra?' — '+i.extra:''}`,'war');
G.incs.push(i);
}
if(intensity>=8 && Math.random()<0.10+(intensity-8)*0.025 && G.incs.length<5){
const i={...pool[Math.floor(Math.random()*pool.length)],age:0};
log(`🚨 連鎖インシデント: [${i.rarity.toUpperCase()}] "${i.name}"${i.extra?' — '+i.extra:''}`,'war');
G.incs.push(i);
}
}
// ========== DRAFT ==========
function startDraft(){
G.phase='draft'; draftSel=-1;
const sp=G.sprint;
let pool=[...ALL_CARDS];
if(sp<4) pool=pool.filter(c=>c.rarity!=='epic');
shuffle(pool);
const picks=[], used=new Set();
for(const c of pool){
if(picks.length>=3) break;
if(used.has(c.id)) continue;
if(c.rarity==='epic' && Math.random()>Math.min(.85,sp/10)) continue;
if(c.rarity==='rare' && Math.random()>Math.min(.90,0.30+sp*0.07)) continue;
picks.push({...c}); used.add(c.id);
}
for(const c of pool){
if(picks.length>=3) break;
if(!used.has(c.id) && c.rarity==='common'){ picks.push({...c}); used.add(c.id) }
}
draftQ=picks;
renderDraft();
document.getElementById('draft-ov').classList.add('on');
}
function selDraft(i){
if(draftSel===i){ confirmDraft(); return; }
draftSel=i; renderDraft();
}
function teamSlotClick(slotIdx){
if(G.phase!=='play'||!isEngSelecting()) return;
placeEngineerInSlot(slotIdx);
}
function confirmDraft(){
if(draftSel<0||draftSel>=draftQ.length) return;
const c=draftQ[draftSel];
G.deck.push({...c});
log(`📥 ドラフト取得: ${c.icon} ${c.name} [${c.rarity}]`,'inf');
closeDraft();
}
function skipDraft(){ log('⏭️ ドラフトスキップ','def'); closeDraft() }
function closeDraft(){
document.getElementById('draft-ov').classList.remove('on');
draftQ=[]; draftSel=-1;
startSprint();
}
// ========== CARD PLAY ==========
function selCard(i){
if(G.phase!=='play') return;
if(cardSel===i){
if(G.hand[i] && G.hand[i].type!=='engineer'){ playCard(); return; }
cardSel=-1;
} else {
cardSel=i;
}
render();
}
function isEngSelecting(){
return cardSel>=0 && cardSel<G.hand.length && G.hand[cardSel] && G.hand[cardSel].type==='engineer';
}
function playCard(){
if(G.phase!=='play'||cardSel<0||cardSel>=G.hand.length) return;
const c=G.hand[cardSel];
if(G.budget<c.cost){ log(`❌ Budget不足: ${c.name} (cost ${c.cost}, 残 ${G.budget})`,'war'); return }
G.budget-=c.cost;
if(c.type==='engineer'){
const emptyIdx=G.team.indexOf(null);
if(emptyIdx===-1){
log('⚠ チームゾーン満員 — [7][8][9]キーでスロット指定して入れ替え','war');
G.budget+=c.cost; return;
}
G.team[emptyIdx]={card:{...c},xp:0,lv:1};
log(`👥 ${c.icon} ${c.name} をスロット${emptyIdx+1}に配置`,'inf');
} else {
c.fx(G);
}
G.disc.push(G.hand.splice(cardSel,1)[0]);
cardSel=-1;
render();
}
function placeEngineerInSlot(slotIdx){
if(G.phase!=='play') return;
if(cardSel<0||cardSel>=G.hand.length) return;
const c=G.hand[cardSel];
if(c.type!=='engineer') return;
if(G.budget<c.cost){ log(`❌ Budget不足: ${c.name} (cost ${c.cost}, 残 ${G.budget})`,'war'); return }
G.budget-=c.cost;
const old=G.team[slotIdx];
if(old){
G.disc.push({...old.card});
log(`🔄 ${old.card.icon} ${old.card.name} (Lv${old.lv}) をチームから外した`,'war');
G.team[slotIdx]={card:{...c},xp:0,lv:1};
log(`👥 ${c.icon} ${c.name} をスロット${slotIdx+1}に入れ替え配置`,'inf');
} else {
G.team[slotIdx]={card:{...c},xp:0,lv:1};
log(`👥 ${c.icon} ${c.name} をスロット${slotIdx+1}に配置`,'inf');
}
G.disc.push(G.hand.splice(cardSel,1)[0]);
cardSel=-1;
render();
}
// ========== GAME OVER ==========
function evalRank(sprint, score){
const RANKS=[
{cls:'rank-d',rank:'D',label:'DISASTER',
msg:'プロジェクトは即座に崩壊した。<br>エンジニアをチームに迎え、インシデントに備えよ。'},
{cls:'rank-c',rank:'C',label:'STRUGGLING',
msg:'技術的負債の波に飲み込まれていった。<br>インシデント対応とモダン化のバランスを見直そう。'},
{cls:'rank-b',rank:'B',label:'AVERAGE',
msg:'及第点の戦いだった。<br>技術的負債との闘いは一進一退で終わった。'},
{cls:'rank-a',rank:'A',label:'EXCELLENT',
msg:'優秀な判断力でシステムを延命させた。<br>もう一歩でレジェンドに手が届く。'},
{cls:'rank-s',rank:'S',label:'LEGENDARY',
msg:'伝説のプロジェクトマネージャー。<br>レガシーの嵐を乗り越え、システムを近代化へ導いた。'},
];
function sprintIdx(s){ return s>=18?4:s>=13?3:s>=9?2:s>=5?1:0; }
function scoreIdx(s){ return s>=60?4:s>=35?3:s>=20?2:s>=8?1:0; }
return RANKS[Math.min(sprintIdx(sprint),scoreIdx(score))];
}
function gameOver(){
G.over=true; G.phase='over';
const ev=evalRank(G.sprint,G.score);
document.getElementById('go-rank').innerHTML=`<span class="${ev.cls}">${ev.rank}</span>`;
document.getElementById('go-rank-label').innerHTML=`<span class="${ev.cls}">${ev.label}</span>`;
document.getElementById('go-rank-msg').innerHTML=ev.msg;
document.getElementById('go-info').innerHTML=
`Sprint: <span class="hi">${G.sprint}</span><br>`+
`Modernization Score: <span class="hi">⭐ ${G.score}</span><br>`+
`Tech Debt: <span class="hi">🔴 ${G.techDebt}</span><br>`+
`チームメンバー: <span class="hi">${G.team.filter(Boolean).length}人</span><br>`+
`デッキ枚数: <span class="hi">${G.deck.length+G.hand.length+G.disc.length}枚</span><br><br>`+
`<span style="color:#ef5350">System Health が 0 になりました。プロジェクト失敗...</span>`;
document.getElementById('go-ov').classList.add('on');
render();
}
// ========== LOG ==========
const LOGS=[];
function log(msg,type='def'){ LOGS.unshift({msg,type}); if(LOGS.length>60) LOGS.pop(); renderLog() }
function clearLog(){ LOGS.length=0; renderLog() }
function renderLog(){
document.getElementById('log').innerHTML=LOGS.map(e=>`<div class="le ${e.type}">${e.msg}</div>`).join('');
}
// ========== DECK VIEW ==========
function openDeckView(){
if(helpOpen) closeHelp();
const allCards=[...G.deck,...G.hand,...G.disc];
const ro={epic:0,rare:1,common:2};
allCards.sort((a,b)=>ro[a.rarity]-ro[b.rarity]||a.name.localeCompare(b.name));
document.getElementById('deck-count').textContent=allCards.length;
document.getElementById('deck-cards').innerHTML=allCards.map(c=>
`<div class="card ${c.rarity}" style="cursor:default">
<div class="c-rb ${c.rarity}">${c.rarity.toUpperCase()}</div>
<div class="c-name">${c.icon} ${c.name}</div>
<div class="c-cost">💰 Cost: ${c.cost}</div>
<div class="c-type">${c.type==='engineer'?'👥 Engineer':'⚡ Action'}</div>
<div class="c-desc">${c.desc}</div>
</div>`
).join('');
document.getElementById('deck-ov').classList.add('on');
deckViewOpen=true;
}
function closeDeckView(){
document.getElementById('deck-ov').classList.remove('on');
deckViewOpen=false;
}
// ========== HELP ==========
function openHelp(){
if(deckViewOpen) closeDeckView();
document.getElementById('help-ov').classList.add('on');
helpOpen=true;
}
function closeHelp(){
document.getElementById('help-ov').classList.remove('on');
helpOpen=false;
}
// ========== RENDER ==========
function render(){
document.getElementById('s-sprint').textContent=G.sprint;
document.getElementById('s-budget').textContent=G.budget;
document.getElementById('s-debt').textContent=G.techDebt;
document.getElementById('s-score').textContent=G.score;
document.getElementById('s-hp').textContent=`${G.hp}/${G.maxHp}`;
document.getElementById('h-budget').textContent=G.budget;
document.getElementById('s-deck').textContent=G.deck.length;
const pct=G.hp/G.maxHp*100;
const hb=document.getElementById('hb');
hb.style.width=pct+'%';
hb.className='hb '+(pct<30?'r':pct<60?'y':'g');
const pt=G.phase==='draft'?'ドラフトフェーズ':G.phase==='over'?'ゲームオーバー':'プレイフェーズ';
document.getElementById('phase-txt').innerHTML=`[ <span class="act">${pt}</span> ]`;
const sb=document.getElementById('sprint-btn');
if(sb) sb.disabled=G.phase!=='play';
renderInc(); renderTeam(); renderHand(); renderControls();
}
function renderInc(){
const el=document.getElementById('inc-list');
el.innerHTML=G.incs.length===0
?'<span class="no-inc">障害なし — システム安定稼働中</span>'
:G.incs.map(i=>{
const r=i.rarity||'common';
const extra=i.extra?`<span style="color:#ff9800;font-size:9px"> ${i.extra}</span>`:'';
const age=i.age||0;
const ageTag=age>=3
?`<span style="color:#ff3030;font-weight:bold;font-size:9px"> ⬆⬆ESC</span>`
:age>=2?`<span style="color:#ff9800;font-size:9px"> ⬆AGE${age}</span>`
:'';
return `<div class="inc ${r}"><span class="inc-rb ${r}">${r.toUpperCase()}</span>${i.icon} ${i.name} <span style="color:#ff3030">-${i.dmg}HP/sprint</span>${ageTag}${extra}</div>`;
}).join('');
}
function renderTeam(){
const pasDesc=(c,lv)=>{
if(c.id==='e_front') return lv>=3?'Modern +2/sprint':'Modern +1/sprint';
if(c.id==='e_sre') return `Health +1/sprint, incident -${lv*10}%`;
if(c.id==='e_arch') return `Modern +${lv>=2?3:2}/sprint, Debt -${lv>=3?2:1}/sprint`;
if(c.id==='e_devops')return `Debt -${lv}/sprint`;
if(c.id==='e_back') return `Debt -1/sprint${lv>=2?', HP +1':''}${lv>=3?', Modern +2':''}`;
return '';
};
const engSel=isEngSelecting();
for(let i=0;i<3;i++){
const m=G.team[i];
const el=document.getElementById(`ts${i}`);
const keyLabel=engSel?`<div class="ts-slot-key">[${i+7}]</div>`:'';
const swapCls=engSel?' swap-tgt':'';
const clickCls=engSel?' clickable':'';
el.onclick=()=>teamSlotClick(i);
if(m){
const xpN=m.lv<3?XP_UP[m.lv-1]:'—';
el.className=`ts occ ${m.card.rarity}${swapCls}${clickCls}`;
el.innerHTML=keyLabel+`<div class="eng-name">${m.card.icon} ${m.card.name}</div><div class="eng-lv">Lv ${m.lv}</div><div class="eng-xp">XP ${m.xp}/${xpN}</div><div class="eng-pas">${pasDesc(m.card,m.lv)}</div>`;
} else {
el.className=`ts${swapCls}${clickCls}`;
el.innerHTML=keyLabel+'<span class="ts-empty">[ Empty Slot ]</span>';
}
}
}
function renderHand(){
const el=document.getElementById('hand');
if(G.hand.length===0){ el.innerHTML='<span class="hand-empty">手札なし — スプリント終了でドロー</span>'; return }
el.innerHTML=G.hand.map((c,i)=>{
const aff=G.budget>=c.cost;
const sel=i===cardSel?' sel':'';
const dim=!aff?' dim':'';
const engHint=(i===cardSel&&c.type==='engineer')?'<div class="c-hint">→ スロットをクリック または [7][8][9]</div>':'';
return `<div class="card ${c.rarity}${sel}${dim}" onclick="selCard(${i})"><span class="c-key">[${i+1}]</span><div class="c-rb ${c.rarity}">${c.rarity.toUpperCase()}</div><div class="c-name">${c.icon} ${c.name}</div><div class="c-cost">💰 Cost: ${c.cost}</div><div class="c-type">${c.type==='engineer'?'👥 Engineer':'⚡ Action'}</div><div class="c-desc">${c.desc}</div>${engHint}</div>`;
}).join('');
}
function renderDraft(){
document.getElementById('draft-cards').innerHTML=draftQ.map((c,i)=>{
const sel=i===draftSel?' sel':'';
return `<div class="dc ${c.rarity}${sel}" onclick="selDraft(${i})"><div class="dc-key">[${i+1}]</div><div class="c-rb ${c.rarity}">${c.rarity.toUpperCase()}</div><div class="c-name">${c.icon} ${c.name}</div><div class="c-cost">💰 Cost: ${c.cost}</div><div class="c-type">${c.type==='engineer'?'👥 Engineer':'⚡ Action'}</div><div class="c-desc">${c.desc}</div></div>`;
}).join('');
const btn=document.getElementById('draft-confirm');
if(btn) btn.disabled=draftSel<0;
}
function renderControls(){
const engSel=isEngSelecting();
const teamFull=G.team.filter(Boolean).length>=3;
let html;
if(G.phase==='draft'){
html=`<b>[1-3]</b> カード選択 | <b>[Enter]</b> デッキに追加 | <b>[Esc]</b> スキップ`;
} else if(engSel&&teamFull){
html=`<b>[7][8][9]</b> スロット指定して入れ替え | <b>[Esc]</b> 選択解除 | <b>[L]</b> デッキ一覧 | <b>[H]</b> ヘルプ`;
} else if(engSel){
html=`<b>[7][8][9]</b> スロット指定配置 | <b>[Enter]</b> 空きスロットへ配置 | <b>[Esc]</b> 選択解除 | <b>[L]</b> デッキ一覧`;
} else {
html=`<b>[1-5][←→]</b> 選択 | <b>[Enter]</b> プレイ | <b>[Esc]</b> 解除 | <b>[Space]</b> スプリント終了 | <b>[L]</b> デッキ一覧 | <b>[H]</b> ヘルプ`;
}
document.getElementById('ctrl').innerHTML=html;
}
// ========== KEYBOARD ==========
document.addEventListener('keydown',e=>{
// L / H always toggle (even after game over)
if(e.key==='l'||e.key==='L'){
if(helpOpen) closeHelp();
deckViewOpen ? closeDeckView() : openDeckView();
e.preventDefault(); return;
}
if(e.key==='h'||e.key==='H'){
if(deckViewOpen) closeDeckView();
helpOpen ? closeHelp() : openHelp();
e.preventDefault(); return;
}
// Esc closes any open overlay first
if(e.key==='Escape'){
if(deckViewOpen||helpOpen){ closeDeckView(); closeHelp(); e.preventDefault(); return; }
}
// Block all other keys when overlay is open
if(deckViewOpen||helpOpen){ e.preventDefault(); return; }
if(G.over) return;
if(G.phase==='draft'){
if(e.key==='1') selDraft(0);
else if(e.key==='2') selDraft(1);
else if(e.key==='3') selDraft(2);
else if(e.key==='Enter'&&draftSel>=0) confirmDraft();
else if(e.key==='Escape') skipDraft();
e.preventDefault(); return;
}
if(G.phase==='play'){
if(e.key==='ArrowRight'){
cardSel=G.hand.length>0?Math.min(G.hand.length-1,cardSel<0?0:cardSel+1):-1;
render(); e.preventDefault();
} else if(e.key==='ArrowLeft'){
cardSel=G.hand.length>0?Math.max(0,cardSel<0?G.hand.length-1:cardSel-1):-1;
render(); e.preventDefault();
} else if(e.key>='7'&&e.key<='9'){
const slotIdx=parseInt(e.key)-7;
if(isEngSelecting()) placeEngineerInSlot(slotIdx);
e.preventDefault();
} else {
const n=parseInt(e.key);
if(n>=1&&n<=5){ selCard(n-1); e.preventDefault() }
else if(e.key==='Enter'){ playCard(); e.preventDefault() }
else if(e.key==='Escape'){ cardSel=-1; render(); e.preventDefault() }
else if(e.key===' '){ endSprint(); e.preventDefault() }
}
}
});
// ========== START ==========
initGame();
</script>
</body>
</html>
最後に
思いつきで始めた内容ですが、思ったより生成AIの質問組み立てや会話コンテキストの統合が上手で、繰り返しの言葉になりますが 下手にスペック駆動開発するより全然いいものができてしまった 感がありました。
ただ、 全面的に何がどうなっているか根本的には何もわからないプログラム実装 となっていて責任が伴う場面では説明を果たせないものになります。
その観点では 「生成AIは利用者の知識/経験を超えるものを作成できない」 といった原理?みたいなものは残ると思うので、(何が言いたいかというと)生成AIも用法・用量を守って正しく使いましょう!ということです。でした。






















