Spaces:
Running
Running
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ラブカ Solo | ゲームボード</title> | |
| <link rel="icon" href="img/icon_blade.png"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="css/main.css"> | |
| </head> | |
| <body> | |
| <header class="header"> | |
| <div class="header-main-row"> | |
| <h1>🎵 ラブカ Solo | |
| <span id="room-display" | |
| style="font-size:0.55em; opacity:1; font-weight:normal; margin-left:15px; background: rgba(255,255,255,0.15); padding: 5px 12px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.3); display: inline-flex; align-items: center; gap: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);"> | |
| <span | |
| style="color: #fff; font-weight: bold; letter-spacing: 0.5px; text-shadow: 0 1px 2px rgba(0,0,0,0.5);">ROOM: | |
| <span id="room-code-header" style="color: var(--accent-gold);">---</span></span> | |
| <button class="btn" onclick="leaveRoom()" | |
| style="font-size: 0.75rem; padding: 3px 10px; height: auto; line-height: 1; background: var(--accent-pink); color: white; border: none; margin: 0; font-weight: bold; box-shadow: 0 1px 3px rgba(0,0,0,0.3);" | |
| title="Exit to Lobby">🚪 Exit</button> | |
| </span> | |
| </h1> | |
| <div class="info-bar"> | |
| <span class="stat" title="Connection"><span class="stat-icon">📡</span> <strong id="header-debug-info" | |
| style="border:1px solid #555; padding:0 4px; border-radius:3px;">PvE</strong></span> | |
| <span class="stat" title="Turn"><span class="stat-icon">🕒</span> <span class="stat-label" | |
| data-i18n="turn">ターン</span>: <strong id="turn">1</strong></span> | |
| <span class="stat" title="Phase"><span class="stat-icon">🎮</span> <span class="stat-label" | |
| data-i18n="phase">フェーズ</span>: <strong id="phase">MAIN</strong></span> | |
| <span class="stat" title="Score"><span class="stat-icon">🏆</span> <span class="stat-label" | |
| data-i18n="score">スコア</span>: <strong id="score">0 - 0</strong></span> | |
| <span class="stat" title="Energy"><span class="stat-icon" style="color:var(--accent-gold);">●</span> | |
| <span class="stat-label" data-i18n="energy">エネルギー</span>: <strong id="header-energy">0 / | |
| 0</strong></span> | |
| </div> | |
| <div class="controls"> | |
| <button id="report-btn-header" class="btn btn-primary" onclick="openReportModal()" data-i18n="report" | |
| title="Report Issue">🐛 <span class="btn-text">バグ報告</span></button> | |
| <button class="btn btn-settings" onclick="openSettingsModal()" title="Settings">⚙️ <span | |
| class="btn-text" data-i18n="settings">設定</span></button> | |
| </div> | |
| </div> | |
| <div class="replay-controls" id="replay-controls" | |
| style="display:none; flex-wrap:wrap; gap:8px; padding:4px; background:rgba(0,0,0,0.3); border-radius:6px; margin-top:4px; align-items:center; width:100%; justify-content: center;"> | |
| <button class="btn" onclick="document.getElementById('replay-file-input').click()" | |
| style="font-size:0.8rem; padding:2px 8px;">📂 Open</button> | |
| <input type="file" id="replay-file-input" accept=".json" style="display:none" | |
| onchange="loadReplayFromFile(this)"> | |
| <button class="btn" onclick="openPasteReplayModal()" style="font-size:0.8rem; padding:2px 8px;">📋 | |
| Paste</button> | |
| <button class="btn" onclick="loadReplay()" style="font-size:0.8rem; padding:2px 8px;">☁️ Load</button> | |
| <input type="text" id="replay-file" value="ai_match.json" placeholder="ai_match.json" | |
| style="width:120px; padding:2px 6px; background:rgba(0,0,0,0.5); border:1px solid #444; color:white; border-radius:4px; font-size: 0.8rem;"> | |
| <div style="width:1px; height:20px; background:#555; margin:0 4px;"></div> | |
| <button class="btn" onclick="replayPrevTurn()" | |
| style="font-size:0.75rem; padding:2px 6px; background:#444;">⏮ Turn</button> | |
| <button class="btn" onclick="replayPrevPhase()" | |
| style="font-size:0.75rem; padding:2px 6px; background:#555;">⏪ Phase</button> | |
| <button class="btn" onclick="replayPrev()" style="font-size:0.8rem; padding:2px 10px;">◀</button> | |
| <button class="btn" id="play-btn" onclick="togglePlay()" | |
| style="font-size:0.8rem; padding:2px 10px; min-width:60px; background:#4a9eff;">▶ Play</button> | |
| <button class="btn" onclick="replayNext()" style="font-size:0.8rem; padding:2px 10px;">▶</button> | |
| <button class="btn" onclick="replayNextPhase()" | |
| style="font-size:0.75rem; padding:2px 6px; background:#555;">Phase ⏩</button> | |
| <button class="btn" onclick="replayNextTurn()" | |
| style="font-size:0.75rem; padding:2px 6px; background:#444;">Turn ⏭</button> | |
| <span style="color:#aaa; font-size:0.8rem; margin-left:8px;">Fr: <span id="frame-num">0</span>/<span | |
| id="total-frames">0</span></span> | |
| <input type="number" id="jump-frame" placeholder="#" | |
| style="width:50px; padding:2px 4px; background:rgba(0,0,0,0.5); border:1px solid #444; color:white; border-radius:4px; font-size:0.8rem;" | |
| onchange="window.jumpToFrame(this.value)"> | |
| </div> | |
| </header> | |
| <div id="phase-stepper" class="phase-stepper"></div> | |
| <div class="game-container" id="game-container"> | |
| <!-- LEFT SIDEBAR (Rule Log) --> | |
| <div class="sidebar-left" id="sidebar-left"> | |
| <div class="sidebar-section" style="height: 100%; display: flex; flex-direction: column; gap: 10px;"> | |
| <div id="card-desc-panel" class="card-desc-panel" style="display: none; flex-shrink: 0;"> | |
| <h3 style="color: var(--accent-pink); margin-bottom: 5px;">🎴 カード能力</h3> | |
| <div id="card-desc-content" class="card-desc-content" | |
| style="font-size: 0.85rem; line-height: 1.4; color: #eee;"></div> | |
| </div> | |
| <div id="active-abilities-panel" class="active-abilities-panel" | |
| style="display: none; flex-shrink: 0; max-height: 30%; overflow-y: auto; background: rgba(0,0,0,0.2); border-left: 3px solid var(--accent-gold); padding: 5px 10px; border-radius: 4px;"> | |
| <h3 style="color: var(--accent-gold); margin-bottom: 5px; font-size: 0.9rem;">✨ 適用中の効果</h3> | |
| <div id="active-abilities-list" style="font-size: 0.8rem; line-height: 1.3; color: #ddd;"></div> | |
| </div> | |
| <div style="flex: 1; display: flex; flex-direction: column; min-height: 0;"> | |
| <h3 data-i18n="rule_log">📜 ルールログ</h3> | |
| <div class="rule-log-list" id="rule-log" style="flex: 1;"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Resizer Left --> | |
| <div class="resizer" id="resizer-left"></div> | |
| <!-- CENTER GAME BOARD --> | |
| <div class="game-board"> | |
| <!-- OPPONENT SECTION --> | |
| <div class="player-section opponent"> | |
| <div class="player-label"> | |
| <span>👤 対戦相手 <span id="opp-agent-name" | |
| style="font-size:0.8rem; opacity:0.6; font-weight:normal;">(Player 2)</span></span> | |
| <span class="score-badge" id="opp-score">0 ライブ</span> | |
| </div> | |
| <div class="board-inner"> | |
| <!-- TOP: Hand Row --> | |
| <div class="hand-row"> | |
| <div class="hand-group"> | |
| <span class="area-count" id="opp-hand-count">0</span> | |
| <div class="card-area hand" id="opp-hand"></div> | |
| </div> | |
| </div> | |
| <!-- MID: Energy Row (Horizontal) --> | |
| <div class="energy-row"> | |
| <div class="energy-area-container horizontal"> | |
| <span class="area-count" id="opp-energy-count">0</span> | |
| <div class="card-area energy" id="opp-energy"></div> | |
| </div> | |
| </div> | |
| <!-- BOTTOM: Field Row --> | |
| <div class="field-row"> | |
| <!-- Left Column: Success --> | |
| <div class="side-column left"> | |
| <div class="successful-live-area-container"> | |
| <span class="count-badge" id="opp-score-badge" style="display:none;">0</span> | |
| <div class="card-area" id="opp-success"></div> | |
| </div> | |
| </div> | |
| <!-- Center Column: Live & Stage --> | |
| <div class="center-field"> | |
| <div class="live-group"> | |
| <div class="card-area live-zone-cards" id="opp-live"></div> | |
| </div> | |
| <div class="stage-group"> | |
| <div class="card-area stage" id="opp-stage"></div> | |
| </div> | |
| </div> | |
| <!-- Right Column: Decks --> | |
| <div class="side-column right"> | |
| <div class="deck-discard-group"> | |
| <div class="card-area deck" id="opp-deck" style="width: 50px; height: 70px;">0</div> | |
| <div class="card-area energy-deck" id="opp-energy-deck" | |
| style="width: 50px; height: 70px;">0</div> | |
| <span class="area-count" id="opp-discard-count" style="display:none;">0</span> | |
| <div class="card-area discard" id="opp-discard"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- PLAYER SECTION --> | |
| <div class="player-section"> | |
| <div class="player-label"> | |
| <span>⭐ あなた <span id="my-agent-name" | |
| style="font-size:0.8rem; opacity:0.6; font-weight:normal;">(Player 1)</span></span> | |
| <span class="score-badge" id="my-score">0 ライブ</span> | |
| </div> | |
| <div class="board-inner"> | |
| <!-- TOP: Field Row --> | |
| <div class="field-row"> | |
| <!-- Left Column: Success Only --> | |
| <div class="side-column left"> | |
| <div class="successful-live-area-container"> | |
| <span class="count-badge" id="my-score-badge" style="display:none;">0</span> | |
| <div class="card-area" id="my-success"></div> | |
| </div> | |
| </div> | |
| <!-- Center Column: Live (Top) & Stage (Bottom) --> | |
| <div class="center-field"> | |
| <div class="live-group"> | |
| <div class="card-area live-zone-cards" id="my-live"></div> | |
| </div> | |
| <div class="stage-group"> | |
| <div class="card-area stage" id="my-stage"></div> | |
| </div> | |
| </div> | |
| <!-- Right Column: Decks --> | |
| <div class="side-column right"> | |
| <div class="deck-discard-group"> | |
| <div class="card-area deck" id="my-deck" style="width: 50px; height: 70px;">0</div> | |
| <div class="card-area energy-deck" id="my-energy-deck" | |
| style="width: 50px; height: 70px;">0</div> | |
| <span class="area-count" id="my-discard-count" style="display:none;">0</span> | |
| <div class="card-area discard" id="my-discard"></div> | |
| <span class="area-count" id="my-exclude-count" style="display:none;">0</span> | |
| <div class="card-area exclude" id="my-exclude"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- MID: Energy Row (Horizontal) --> | |
| <div class="energy-row"> | |
| <div class="energy-area-container horizontal"> | |
| <span class="area-count" id="my-energy-count">0</span> | |
| <div class="card-area energy" id="my-energy"></div> | |
| </div> | |
| </div> | |
| <!-- BOTTOM: Hand Row --> | |
| <div class="hand-row"> | |
| <div class="hand-group"> | |
| <span class="area-count" id="my-hand-count">0</span> | |
| <div class="card-area hand" id="my-hand"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Resizer Right --> | |
| <div class="resizer" id="resizer-right"></div> | |
| <!-- RIGHT SIDEBAR (Actions) --> | |
| <div class="sidebar sidebar-right" id="sidebar-right"> | |
| <div class="sidebar-section"> | |
| <h3>☰ アクション</h3> | |
| <div style="margin-bottom: 10px;"> | |
| <button class="btn btn-primary" onclick="showLastPerformance()" | |
| style="width: 100%; font-size: 0.85rem; padding: 10px; background: linear-gradient(135deg, #f59e0b, #d97706); display: flex; align-items: center; justify-content: center; gap: 8px;"> | |
| <span>📊</span> パフォーマンス確認 | |
| </button> | |
| </div> | |
| <div class="action-list" id="actions"></div> | |
| </div> | |
| <div class="sidebar-section diagnostic-section" id="diagnostic-panel" | |
| style="display:none; border: 1px solid var(--accent-blue); background: rgba(0,0,0,0.2);"> | |
| <h3 style="color: var(--accent-blue);">🔍 診断情報 (Diag)</h3> | |
| <div id="diag-content" style="font-size: 0.75rem; padding: 5px; color: #ccc;"> | |
| <div>Phase: <span id="diag-phase">-</span></div> | |
| <div>Player: <span id="diag-player">-</span></div> | |
| <div>Legal Actions: <span id="diag-actions">-</span></div> | |
| <div>Game Over: <span id="diag-gameover">-</span></div> | |
| <div style="margin-top: 5px;"> | |
| <button class="action-btn" onclick="forceAdvance()" | |
| style="font-size:0.7rem; padding: 2px 8px; background: var(--accent-blue);">Force | |
| Advance</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="sidebar-section performance-guide" id="perf-guide-panel" | |
| style="display:none; border: 1px solid var(--accent-gold); background: rgba(0,0,0,0.2);"> | |
| <h3 style="color: var(--accent-gold);" data-i18n="live_guide">🌟 ライブガイド</h3> | |
| <div id="perf-guide-content" style="font-size: 0.8rem; padding: 5px; color: #ddd;"> | |
| <!-- Content populated by JS --> | |
| </div> | |
| </div> | |
| <div class="sidebar-section looked-cards-panel" id="looked-cards-panel" | |
| style="display:none; border: 1px solid var(--accent-pink); background: rgba(255,100,180,0.1);"> | |
| <h3 style="color: var(--accent-pink);" data-i18n="looked_cards">👁️ 確認したカード</h3> | |
| <div id="looked-cards-content" style="display:flex; flex-wrap:wrap; gap:5px; justify-content:center;"> | |
| <!-- Populated by JS --> | |
| </div> | |
| </div> | |
| <!-- Rule Log Removed from here --> | |
| <div class="sidebar-section log-section"> | |
| <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;"> | |
| <h3 style="margin:0" data-i18n="logs">📜 ログ</h3> | |
| <div> | |
| <button class="perf-review-btn" id="view-last-perf-btn" onclick="showLastPerformance()" | |
| data-i18n="last_perf">前のライブ結果</button> | |
| </div> | |
| </div> | |
| <div class="log-list" id="log"></div> | |
| </div> | |
| <div class="sidebar-section ai-analysis" id="ai-analysis-panel" | |
| style="border: 1px solid var(--accent-pink); background: rgba(255,100,180,0.1); display: none;"> | |
| <h3 style="color: var(--accent-pink);">🤖 AI 分析 (MCTS) | |
| <span id="ai-thinking-dots" style="font-size: 0.8rem; font-weight: normal;">...</span> | |
| </h3> | |
| <div id="ai-suggestions-list" style="font-size: 0.8rem; padding: 5px; color: #eee;"> | |
| <!-- Top moves will be here --> | |
| </div> | |
| <div style="padding: 5px; border-top: 1px solid rgba(255,255,255,0.1);"> | |
| <button class="btn" style="font-size: 0.7rem; padding: 2px 8px;" onclick="runAiAnalysis()">分析実行 (Run | |
| Analysis)</button> | |
| </div> | |
| </div> | |
| <div class="sidebar-section god-mode"> | |
| <h3 data-i18n="god_mode">🔧 ゴッドモード</h3> | |
| <div style="margin-bottom: 8px;"> | |
| <label style="font-size:0.8rem; color: #ccc;">🤖 AI Mode:</label> | |
| <select id="ai-selector" onchange="changeAI()" | |
| style="width:100%; background: #222; color: #fff; border: 1px solid #444; padding: 4px; border-radius: 4px;"> | |
| <option value="random">Random</option> | |
| <option value="smart" selected>Smart</option> | |
| <!-- <option value="super">Super (Minimax)</option> --> | |
| </select> | |
| </div> | |
| <input type="number" id="force-id" placeholder="Action ID"> | |
| <button class="btn" onclick="forceAction()" data-i18n="force">強制実行</button> | |
| <textarea id="god-code" rows="2" placeholder="p.hand.append(p.main_deck.pop())"></textarea> | |
| <button class="btn" onclick="execCode()" data-i18n="exec">コード実行</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Modal --> | |
| <div id="settings-modal" class="modal-overlay" style="z-index: 2000;"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <span class="modal-title">⚙️ <span data-i18n="settings">設定</span></span> | |
| <button class="close-btn" onclick="closeSettingsModal()">×</button> | |
| </div> | |
| <div class="modal-body settings-grid"> | |
| <div class="settings-group"> | |
| <label>Main Actions</label> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;"> | |
| <button class="btn" onclick="fetchState()" title="Update State">🔄 <span class="btn-text" | |
| data-i18n="update">更新</span></button> | |
| <button class="btn btn-primary" onclick="resetGame()" title="Reset Game">🔄 <span | |
| class="btn-text" data-i18n="reset">リセット</span></button> | |
| </div> | |
| </div> | |
| <div class="settings-group"> | |
| <label>Game Controls</label> | |
| <button class="btn" onclick="location.href='/deck_builder.html'" | |
| style="background: linear-gradient(135deg, #9b59b6, #8e44ad);" data-i18n="deck_creator">🛠 | |
| デッキ作成</button> | |
| <button class="btn" onclick="openDeckModal()" | |
| style="background: linear-gradient(135deg, #4a9eff, #2a7edd);" data-i18n="set_deck">🎴 | |
| デッキ設定</button> | |
| <button id="pvp-btn" class="btn" onclick="toggleHotseat()" | |
| style="background: linear-gradient(135deg, #444, #222); border-color: #777;">Shared Screen: | |
| OFF</button> | |
| <button id="switch-btn" class="btn" onclick="togglePerspective()" | |
| style="background: linear-gradient(135deg, #6366f1, #4338ca);">Switch View (P1/P2)</button> | |
| </div> | |
| <div class="settings-group"> | |
| <label>View & Support</label> | |
| <button id="live-watch-btn" class="btn" | |
| style="background: linear-gradient(135deg, #cc4444, #882222); border-color: #ff6666;" | |
| onclick="toggleLiveWatch()" data-i18n="live_watch">🔴 ライブ監視: OFF</button> | |
| <button id="friendly-abilities-btn" class="btn" | |
| style="background: linear-gradient(135deg, #2ecc71, #27ae60); border-color: #76d672;" | |
| onclick="toggleFriendlyAbilities()" data-i18n="friendly_abilities">読みやすい能力テキスト: ON</button> | |
| <button id="lang-btn" class="btn" onclick="toggleLang()" | |
| style="border:1px solid #aaa;">English</button> | |
| <button class="btn" onclick="openHelpModal()" | |
| style="border:1px solid var(--accent-gold); color:var(--accent-gold);">❓ ヘルプ</button> | |
| </div> | |
| <div class="settings-group"> | |
| <label>Tools</label> | |
| <button class="btn" onclick="toggleReplayMode()" data-i18n="replay_mode">🎬 リプレイモード Toggle</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Performance Result Modal --> | |
| <div id="performance-modal" class="modal-overlay" style="display:none; z-index: 3000;"> | |
| <div class="modal-content" style="max-width: 600px;"> | |
| <div class="modal-header"> | |
| <div class="modal-title" id="perf-title">ライブ結果</div> | |
| <button class="btn" onclick="dismissPerformanceModal()">✕</button> | |
| </div> | |
| <div class="modal-body" id="perf-content" style="max-height: 70vh; overflow-y: auto;"> | |
| <!-- Performance results will be rendered here --> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn btn-primary" onclick="dismissPerformanceModal()">閉じる</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Selection Modal (for deck search, discard pick, etc) --> | |
| <div id="selection-modal" class="modal-overlay" style="display:none; z-index: 4000;"> | |
| <div class="modal-content" style="max-width: 800px; width: 90%;"> | |
| <div class="modal-header"> | |
| <div class="modal-title" id="selection-title">カード選択</div> | |
| <button class="btn" onclick="document.getElementById('selection-modal').style.display='none'">✕</button> | |
| </div> | |
| <div id="selection-description" style="padding:0 20px 10px; font-size:0.9rem; opacity:0.8;"></div> | |
| <div class="modal-body" id="selection-content" | |
| style="display:grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap:15px; padding:10px;"> | |
| <!-- Choice cards will be rendered here --> | |
| </div> | |
| <div class="modal-footer" id="selection-footer"> | |
| <!-- Cancel button if optional --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Reporting Modal --> | |
| <div id="report-modal" class="modal-overlay" style="display:none; z-index: 3000;"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <div class="modal-title">Report Issue / Save State</div> | |
| <button class="btn" onclick="closeReportModal()">✕</button> | |
| </div> | |
| <div class="modal-body"> | |
| <p style="font-size: 0.85rem; color: var(--text-dim);"> | |
| Describe what happened or the failure point. The current game state and your last few actions will | |
| be saved for debugging. | |
| </p> | |
| <textarea id="report-explanation" class="report-textarea" | |
| placeholder="Explain the issue here..."></textarea> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn" onclick="downloadReport()" | |
| style="margin-right:auto; border:1px solid var(--accent-blue); color:var(--text);">💾 Download | |
| JSON</button> | |
| <button class="btn" onclick="closeReportModal()">Cancel</button> | |
| <button class="btn btn-primary" onclick="submitReport()">Submit to Server</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Help / FAQ Modal --> | |
| <div id="help-modal" class="modal-overlay" style="display:none; z-index: 3000;"> | |
| <div class="modal-content" style="max-width: 700px;"> | |
| <div class="modal-header"> | |
| <span class="modal-title">❓ ヘルプ & FAQ (Help)</span> | |
| <button onclick="closeHelpModal()" | |
| style="background:none;border:none;color:white;font-size:1.5rem;cursor:pointer;">×</button> | |
| </div> | |
| <div class="modal-body" style="line-height: 1.6; font-size: 0.9rem;"> | |
| <h3 style="border-bottom:1px solid #444; padding-bottom:5px; margin-top:0;">🃏 デッキのセット方法 (Deck Setup) | |
| </h3> | |
| <ul style="padding-left:20px; color:#ddd;"> | |
| <li><b>Deck Log:</b> Bushiroad Deck Log からHTMLをコピーして「Option 2」に貼り付けます。</li> | |
| <li><b>Test Deck:</b> 「🧪 Load Test Deck」ボタンで、テスト用(2倍量)デッキを読み込みます。</li> | |
| <li><b>Random Deck:</b> 「🎲 Generate Random Deck」ボタンで、ランダムな有効デッキを生成します。</li> | |
| </ul> | |
| <h3 style="border-bottom:1px solid #444; padding-bottom:5px; margin-top:15px;">📱 モバイル操作 (Mobile)</h3> | |
| <ul style="padding-left:20px; color:#ddd;"> | |
| <li>画面右下の <b>☰ ボタン</b> でサイドバー(アクション履歴、ログ)を開閉できます。</li> | |
| <li>手札やステージなどのゾーンは横スクロール可能です。</li> | |
| </ul> | |
| <h3 style="border-bottom:1px solid #444; padding-bottom:5px; margin-top:15px;">🎮 ゲーム操作 (Controls)</h3> | |
| <ul style="padding-left:20px; color:#ddd;"> | |
| <li><b>更新 (Update):</b> ゲームの状態を再取得します。</li> | |
| <li><b>リセット (Reset):</b> ゲームを初期状態の最初からやり直します。</li> | |
| <li>カードをクリックして使用・移動します。</li> | |
| <li>アクションを選択したら、サイドバーの「Do Action」等は自動で進む場合があります。</li> | |
| </ul> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn btn-primary" onclick="closeHelpModal()">閉じる</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Deck Setup Modal --> | |
| <div id="deck-modal" class="modal-overlay" style="display: none; z-index: 3000;"> | |
| <div class="modal-content" style="width: 600px;"> | |
| <div class="modal-header"> | |
| <span class="modal-title">🎴 デッキセット (Set Deck)</span> | |
| <button onclick="closeDeckModal()" | |
| style="background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer;">×</button> | |
| </div> | |
| <div class="modal-body"> | |
| <label style="display: block; margin-bottom: 8px;">プレイヤー選択:</label> | |
| <select id="deck-player-select" | |
| style="width: 100%; padding: 8px; margin-bottom: 15px; background: var(--bg-zone); border: 1px solid var(--border); color: white; border-radius: 5px;"> | |
| <option value="0">プレイヤー1 (あなた)</option> | |
| <option value="1">プレイヤー2 (AI)</option> | |
| <option value="both">両方 (Both)</option> | |
| </select> | |
| <div | |
| style="background: rgba(74, 158, 255, 0.1); border: 1px solid var(--accent-blue); padding: 10px; border-radius: 8px; margin-bottom: 15px; font-size: 0.8rem;"> | |
| <div style="color: var(--accent-blue); font-weight: bold; margin-bottom: 5px;">📍 デッキデータの取得方法:</div> | |
| <ol style="padding-left: 20px; color: #ccc;"> | |
| <li><a href="https://decklog.bushiroad.com/search?c=11" target="_blank" | |
| style="color: var(--accent-pink);">デッキログ (ラブライブ!Solo)</a> にログイン。</li> | |
| <li>デッキを選択し、虫眼鏡アイコン 🔍 をクリック。</li> | |
| <li><b>F12</b> キーを押して開発者ツールを開く。</li> | |
| <li>In the <b>Elements</b> tab, right-click the top <code><html></code> tag.</li> | |
| <li>Select <b>Copy</b> → <b>Copy outerHTML</b>.</li> | |
| <li>Paste the result below.</li> | |
| </ol> | |
| </div> | |
| <div | |
| style="margin-bottom: 15px; background: rgba(255, 255, 255, 0.05); padding: 10px; border-radius: 5px;"> | |
| <label style="display: block; margin-bottom: 5px; font-weight: bold;">Option 1: Upload File</label> | |
| <input type="file" id="deck-file-input" accept=".html,.txt,.json" | |
| style="width: 100%; font-size: 0.9rem;"> | |
| <button class="btn" | |
| style="width:100%; margin-top:10px; background:linear-gradient(135deg, #a855f7, #6b21a8);" | |
| onclick="loadTestDeck()"> | |
| 🧪 Load Test Deck (Doubled) | |
| </button> | |
| <button class="btn" | |
| style="width:100%; margin-top:10px; background:linear-gradient(135deg, #f59e0b, #d97706);" | |
| onclick="loadRandomDeck()"> | |
| 🎲 Generate Random Deck | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px;">Option 2: Paste Deck HTML:</label> | |
| <textarea id="deck-html-input" class="report-textarea" style="height: 200px;" | |
| placeholder="Paste the HTML content from a deck page here..."></textarea> | |
| <div id="deck-preview" style="margin-top: 10px; font-size: 0.8rem; color: var(--text-dim);"></div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn" onclick="closeDeckModal()">Cancel</button> | |
| <button class="btn btn-primary" onclick="submitDeck()">Apply Deck</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Paste Replay Modal --> | |
| <div id="paste-replay-modal" class="modal-overlay" style="display: none;"> | |
| <div class="modal-content" style="width: 600px;"> | |
| <div class="modal-header"> | |
| <span class="modal-title">📋 Paste Replay JSON</span> | |
| <button onclick="closePasteReplayModal()" | |
| style="background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer;">×</button> | |
| </div> | |
| <div class="modal-body"> | |
| <p style="color:#ccc; font-size:0.8rem; margin-bottom:8px;">Paste the full JSON content of a replay file | |
| here.</p> | |
| <textarea id="paste-replay-input" class="report-textarea" style="height: 300px; font-family:monospace;" | |
| placeholder='{"game_id": 123, "states": [...]}'></textarea> | |
| </div> | |
| <div class="modal-footer"> | |
| <button class="btn" onclick="closePasteReplayModal()">Cancel</button> | |
| <button class="btn btn-primary" onclick="submitPasteReplay()">Load Replay</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Game Setup Modal --> | |
| <div id="setup-modal" class="modal-overlay" style="display: none; background: rgba(0,0,0,0.95); z-index: 10000;"> | |
| <div class="modal-content" style="width: 700px; max-height: 90vh; overflow-y: auto;"> | |
| <div class="modal-header"> | |
| <span class="modal-title">⚔️ Game Setup</span> | |
| <button onclick="closeSetupModal()" | |
| style="background:none;border:none;color:white;font-size:1.5rem;cursor:pointer;">×</button> | |
| </div> | |
| <div class="modal-body"> | |
| <div id="setup-mode-display" | |
| style="text-align: center; margin-bottom: 15px; font-size: 1.1rem; font-weight: bold; color: var(--accent-gold);"> | |
| Mode: Solo (PvE) | |
| </div> | |
| <!-- Deck Selection Grid --> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;"> | |
| <!-- Player 1 (You) --> | |
| <div class="setup-column" | |
| style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px;"> | |
| <h4 style="margin-top: 0; color: var(--accent-blue);">👤 Player 1 (You)</h4> | |
| <label style="display:block; margin-bottom:5px; font-size:0.9rem;">Select Deck:</label> | |
| <select id="p0-deck-select" onchange="onDeckSelectChange(0)" | |
| style="width: 100%; padding: 8px; background: #222; color: white; border: 1px solid #555; margin-bottom: 10px;"> | |
| <option value="random">🎲 Random Deck</option> | |
| <option value="paste">📋 Paste Deck List...</option> | |
| </select> | |
| <div id="p0-paste-area" style="display:none;"> | |
| <textarea id="p0-deck-paste" placeholder="Paste deck list here..." | |
| style="width: 100%; height: 100px; background: #111; color: #ddd; border: 1px solid #444; font-size: 0.8rem;"></textarea> | |
| </div> | |
| <div id="p0-deck-preview" style="font-size: 0.8rem; color: #aaa; margin-top: 5px;"></div> | |
| </div> | |
| <!-- Player 2 (Opponent) --> | |
| <div id="p2-setup-column" class="setup-column" | |
| style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px;"> | |
| <h4 style="margin-top: 0; color: var(--accent-pink);">🤖 Player 2 (AI)</h4> | |
| <label style="display:block; margin-bottom:5px; font-size:0.9rem;">Select Deck:</label> | |
| <select id="p1-deck-select" onchange="onDeckSelectChange(1)" | |
| style="width: 100%; padding: 8px; background: #222; color: white; border: 1px solid #555; margin-bottom: 10px;"> | |
| <option value="random">🎲 Random Deck</option> | |
| <option value="paste">📋 Paste Deck List...</option> | |
| </select> | |
| <div id="p1-paste-area" style="display:none;"> | |
| <textarea id="p1-deck-paste" placeholder="Paste deck list here..." | |
| style="width: 100%; height: 100px; background: #111; color: #ddd; border: 1px solid #444; font-size: 0.8rem;"></textarea> | |
| </div> | |
| <div id="p1-deck-preview" style="font-size: 0.8rem; color: #aaa; margin-top: 5px;"></div> | |
| </div> | |
| </div> | |
| <div style="margin-top: 20px; text-align: center;"> | |
| <button class="btn" onclick="submitGameSetup()" | |
| style="padding: 10px 30px; font-size: 1.1rem; background: linear-gradient(135deg, #2ecc71, #27ae60); font-weight: bold; width: 100%;"> | |
| 🚀 Start Game | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Room Selection Modal --> | |
| <div id="room-modal" class="modal-overlay" style="display: flex; background: rgba(0,0,0,0.9); z-index: 9999;"> | |
| <div class="modal-content" style="width: 400px; text-align: center;"> | |
| <div class="modal-header" style="justify-content: center;"> | |
| <span class="modal-title">🏠 マルチプレイ ロビー (Lobby)</span> | |
| </div> | |
| <div class="modal-body"> | |
| <p style="color:#ccc; margin-bottom: 20px;">新しいルームを作成するか、既存のルームに参加してください。</p> | |
| <div style="margin-bottom: 10px; text-align: left; margin-left: 5px;"> | |
| <label | |
| style="color: #ddd; font-size: 0.9rem; cursor: pointer; display: inline-flex; align-items: center;"> | |
| <input type="checkbox" id="public-room-check" style="margin-right: 8px; transform: scale(1.2);"> | |
| Public Room (Show in Lobby) | |
| </label> | |
| </div> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px;"> | |
| <button type="button" class="btn" onclick="openGameSetup('pve')" | |
| style="padding: 12px 5px; font-size: 0.9rem; background: linear-gradient(135deg, #2ecc71, #27ae60); font-weight: bold;"> | |
| Solo (vs AI) | |
| </button> | |
| <button type="button" class="btn" onclick="openGameSetup('pvp')" | |
| style="padding: 12px 5px; font-size: 0.9rem; background: linear-gradient(135deg, #a855f7, #6b21a8); font-weight: bold;"> | |
| PvP (vs Human) | |
| </button> | |
| </div> | |
| <div style="font-size: 0.8rem; color: #888; margin-top: -10px; margin-bottom: 20px;">対戦相手を選択してルームを作成 | |
| </div> | |
| <div style="height: 1px; background: #444; margin: 20px 0;"></div> | |
| <!-- Public Rooms Section --> | |
| <div style="margin-bottom: 20px;"> | |
| <div | |
| style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> | |
| <span style="color: #aaa; font-size: 0.9rem; font-weight: bold;">🌍 Public Rooms</span> | |
| <button class="btn" onclick="fetchPublicRooms()" | |
| style="font-size: 0.7rem; padding: 2px 8px;">Refresh</button> | |
| </div> | |
| <div id="public-rooms-list" | |
| style="background: rgba(0,0,0,0.3); border: 1px solid #444; border-radius: 4px; height: 120px; overflow-y: auto; padding: 5px; text-align: left;"> | |
| <div style="color: #666; font-size: 0.8rem; text-align: center; padding-top: 20px;">Loading... | |
| </div> | |
| </div> | |
| </div> | |
| <div style="height: 1px; background: #444; margin: 20px 0;"></div> | |
| <div> | |
| <input type="text" id="room-code-input" placeholder="Enter Room Code (e.g. ABCD)" | |
| style="width: 100%; padding: 10px; font-size: 1.2rem; text-align: center; text-transform: uppercase; margin-bottom: 10px; border-radius: 4px; border: 1px solid #555; background: #222; color: white;" | |
| maxlength="4" oninput="this.value = this.value.toUpperCase()"> | |
| <button type="button" class="btn" onclick="joinRoom()" | |
| style="width: 100%; padding: 10px; font-size: 1rem; background: linear-gradient(135deg, #3498db, #2980b9);"> | |
| 🚪 Join Room | |
| </button> | |
| </div> | |
| <!-- Offline Mode for Testing --> | |
| <div style="margin-top: 30px;"> | |
| <button type="button" class="btn" onclick="startOffline()" | |
| style="font-size: 0.9rem; padding: 8px 16px; background: linear-gradient(135deg, #f59e0b, #d97706); opacity: 1; font-weight: bold; width: 100%;"> | |
| 🚀 Start Offline (WASM) | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Perspective Selection Modal --> | |
| <div id="perspective-modal" class="modal-overlay" style="z-index: 3000;"> | |
| <div class="modal-content" style="max-width:400px; text-align:center;"> | |
| <div class="modal-header"> | |
| <span class="modal-title">Select Your Seat</span> | |
| </div> | |
| <div class="modal-body"> | |
| <p style="color:#ccc; margin-bottom:20px;">Which player are you?</p> | |
| <div style="display:grid; grid-template-columns:1fr 1fr; gap:15px;"> | |
| <button class="btn" onclick="setPerspective(0)" | |
| style="background: linear-gradient(135deg, #3b82f6, #1d4ed8);"> | |
| Player 1 (Host) | |
| </button> | |
| <button class="btn" onclick="setPerspective(1)" | |
| style="background: linear-gradient(135deg, #ef4444, #b91c1c);"> | |
| Player 2 (Joiner) | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="mobile-action-bar"></div> | |
| <button id="mobile-sidebar-toggle" onclick="toggleSidebar()">☰</button> | |
| <script> | |
| let currentAiSims = 0; | |
| let analysisInterval = null; | |
| async function runAiAnalysis(sims = 10) { | |
| const roomCode = localStorage.getItem("roomCode"); | |
| const panel = document.getElementById("ai-analysis-panel"); | |
| panel.style.display = "block"; | |
| const dots = document.getElementById("ai-thinking-dots"); | |
| dots.textContent = "考え中..."; | |
| try { | |
| const res = await fetch("/api/ai_suggest", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", "X-Room-Code": roomCode }, | |
| body: JSON.stringify({ sims: sims }) | |
| }); | |
| const data = await res.json(); | |
| if (data.success) { | |
| renderSuggestions(data.suggestions); | |
| currentAiSims += sims; | |
| dots.textContent = `(${currentAiSims} sims)`; | |
| } | |
| } catch (e) { | |
| console.error("AI suggest failed", e); | |
| } | |
| } | |
| function renderSuggestions(suggestions) { | |
| const list = document.getElementById("ai-suggestions-list"); | |
| list.innerHTML = ""; | |
| // Show top 5 | |
| suggestions.slice(0, 5).forEach(s => { | |
| const item = document.createElement("div"); | |
| item.style.marginBottom = "5px"; | |
| item.style.display = "flex"; | |
| item.style.justifyContent = "space-between"; | |
| const winRate = (s.value * 100).toFixed(1); | |
| const color = s.value > 0.6 ? "#2ecc71" : s.value < 0.4 ? "#e74c3c" : "#f1c40f"; | |
| item.innerHTML = ` | |
| <span>${s.desc}</span> | |
| <span style="color: ${color}; font-weight: bold;">${winRate}% (${s.visits})</span> | |
| `; | |
| list.appendChild(item); | |
| }); | |
| } | |
| // Polling logic: if it's player's turn, run analysis occasionally | |
| setInterval(() => { | |
| const phase = document.getElementById("phase").textContent; | |
| if (phase && phase !== "TERMINAL" && phase !== "SETUP") { | |
| // Auto run small analysis if panel is visible | |
| if (document.getElementById("ai-analysis-panel").style.display === "block") { | |
| runAiAnalysis(10); | |
| } | |
| } else { | |
| currentAiSims = 0; | |
| } | |
| }, 5000); | |
| </script> | |
| <script src="js/layout.js"></script> | |
| <script src="js/ability_translator.js"></script> | |
| <script src="js/main.js?v=7"></script> | |
| </body> | |
| </html> |