need to commit more

This commit is contained in:
2026-05-02 23:25:02 -07:00
parent 2690d538f4
commit 0ea67f62e2
168 changed files with 5600 additions and 65 deletions

View File

@@ -1,5 +1,7 @@
let playerId = localStorage.getItem('playerId') || '';
let currentGameId = localStorage.getItem('gameId') || '';
/** Avoid duplicate tabs when toggleReady and lobby poll both notice the new game */
let openedVisualGameTabForGameId = '';
let lastGameState = null;
let lastRenderedGameLogKey = null;
let finalizeRollInFlight = false;
@@ -115,6 +117,14 @@ let playerId = localStorage.getItem('playerId') || '';
wireDiceRigUi();
wireDebugStartingResourcesUi();
wireAutoHarvestUi();
function openVisualGameClientTab(gameId) {
if (!gameId || !playerId) return;
if (openedVisualGameTabForGameId === gameId) return;
openedVisualGameTabForGameId = gameId;
const q = new URLSearchParams({ game_id: gameId, player_id: playerId });
window.open(`${location.origin}/?${q}`, '_blank', 'noopener,noreferrer');
}
async function joinLobby() {
const name = document.getElementById('playerName').value;
@@ -156,6 +166,7 @@ let playerId = localStorage.getItem('playerId') || '';
if (data.in_game) {
html += `<p><strong>You are in game: ${data.game_id}</strong></p>`;
if (data.game_id && data.game_id !== currentGameId) {
openVisualGameClientTab(data.game_id);
currentGameId = data.game_id;
localStorage.setItem('gameId', currentGameId);
// Fetch immediately when we first learn the game id
@@ -190,7 +201,7 @@ let playerId = localStorage.getItem('playerId') || '';
if (data.game_id) {
currentGameId = data.game_id;
localStorage.setItem('gameId', currentGameId);
alert('Game started! Game ID: ' + data.game_id);
openVisualGameClientTab(data.game_id);
// Immediately fetch state so the Game section fills in
getGameState(false);
}
@@ -428,7 +439,9 @@ let playerId = localStorage.getItem('playerId') || '';
boardBtn.textContent = 'Board';
boardBtn.style.left = '50%';
boardBtn.style.top = '50%';
boardBtn.onclick = () => { openBoardTableau(); };
boardBtn.onclick = () => {
window.open(`/?game_id=${currentGameId}&player_id=${playerId}`, '_blank');
};
wrap.appendChild(boardBtn);
const cleanPlayers = players.filter(p => p && p.player_id);
@@ -555,7 +568,7 @@ let playerId = localStorage.getItem('playerId') || '';
if (reqAction === 'bonus_resource_choice') return true;
const trimmed = reqAction.trim();
if (trimmed.startsWith('choose ')) return true;
if (trimmed === 'choose_player' || trimmed === 'choose_monster_strength' || trimmed === 'domain_self_convert') return true;
if (trimmed === 'choose_player' || trimmed === 'choose_monster_strength' || trimmed === 'domain_self_convert' || trimmed === 'harvest_optional_exchange') return true;
if (reqAction === 'standard_action' && (gameState.phase || '') === 'action') return true;
return false;
}
@@ -1226,6 +1239,10 @@ let playerId = localStorage.getItem('playerId') || '';
return renderDomainSelfConvertPrompt(gameState);
}
if (reqAction === 'harvest_optional_exchange') {
return renderHarvestOptionalExchangePrompt(gameState);
}
if (reqAction === 'choose_player') {
return renderDomainChoosePlayer(gameState);
}
@@ -1655,6 +1672,79 @@ let playerId = localStorage.getItem('playerId') || '';
}
}
function harvestExchangeExplain(command) {
const parts = (command || '').trim().split(/\s+/);
if (parts.length < 5 || parts[0].toLowerCase() !== 'exchange') return (command || '').trim() || 'Optional harvest exchange.';
const pay = parts[1].toLowerCase();
const payN = parts[2];
const gain = parts[3].toLowerCase();
const gainN = parts[4];
const labels = { g: 'gold', s: 'strength', m: 'magic', v: 'victory points' };
return `Pay ${payN} ${labels[pay] || pay}, gain ${gainN} ${labels[gain] || gain}.`;
}
function renderHarvestOptionalExchangePrompt(gameState) {
const panel = document.getElementById('choicePanel');
if (!panel) return;
const req = gameState?.action_required || {};
const reqId = (req?.id || '').toString();
const isYou = (playerId && reqId === playerId);
const prc = gameState?.pending_required_choice || null;
const cmd = (prc?.command || '').toString();
const explain = harvestExchangeExplain(cmd);
if (!isYou) {
panel.innerHTML = `<div style="padding:8px;border:1px solid #ddd;border-radius:8px;background:#fff6d8;">
Waiting on <code>${escapeHtml(reqId)}</code> — optional citizen harvest exchange.
</div>`;
return;
}
panel.innerHTML = `
<div style="padding:10px;border:1px solid #ddd;border-radius:8px;background:#eef7ff;">
<div style="font-weight:700;margin-bottom:8px;">Harvest: optional exchange</div>
<div class="mini" style="margin-bottom:8px;color:#333;">${escapeHtml(explain)}</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<button type="button" onclick="sendHarvestExchangeConfirm()">Take exchange</button>
<button type="button" onclick="sendHarvestExchangeSkip()">Skip (keep resources)</button>
</div>
</div>`;
}
async function sendHarvestExchangeConfirm() {
if (!playerId || !currentGameId) return;
try {
await fetch(`/api/game/${currentGameId}/action`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
player_id: playerId,
action_type: 'act_on_required_action',
action: 'confirm_harvest_exchange'
})
});
getGameState(false);
} catch (e) {
console.error(e);
}
}
async function sendHarvestExchangeSkip() {
if (!playerId || !currentGameId) return;
try {
await fetch(`/api/game/${currentGameId}/action`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
player_id: playerId,
action_type: 'act_on_required_action',
action: 'skip_harvest_exchange'
})
});
getGameState(false);
} catch (e) {
console.error(e);
}
}
function renderDomainChoosePlayer(gameState) {
const panel = document.getElementById('choicePanel');
if (!panel) return;

3095
static/game/game.js Normal file

File diff suppressed because it is too large Load Diff

51
static/game/index.html Normal file
View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Valeria Card Kingdoms</title>
<link rel="stylesheet" href="/static/game/style.css">
</head>
<body>
<a href="/" class="game-lobby-btn">Lobby</a>
<div class="board" id="board">
<div class="seat seat-0" id="seat-0"></div>
<div class="seat seat-1" id="seat-1"></div>
<div class="seat seat-2" id="seat-2"></div>
<div class="seat seat-3" id="seat-3"></div>
<div class="seat seat-4" id="seat-4"></div>
<div class="center-board" id="zone-center"></div>
</div>
<div id="conn-status" class="conn-status"></div>
<div id="lobby-overlay" class="lobby-overlay" aria-hidden="true">
<div class="lobby-sheet" role="dialog" aria-modal="true" aria-labelledby="lobby-title">
<div class="lobby-brand">
<p class="lobby-kicker">Table</p>
<h1 id="lobby-title" class="lobby-title">Valeria Card Kingdoms</h1>
<p class="lobby-tagline">Join the lobby, ready up when your group is here.</p>
</div>
<div id="lobby-error" class="lobby-error lobby-hidden" role="alert"></div>
<div id="lobby-step-join" class="lobby-step">
<label class="lobby-label" for="lobby-display-name">Display name</label>
<div class="lobby-join-row">
<input id="lobby-display-name" class="lobby-input" type="text" maxlength="40" placeholder="Your name" autocomplete="nickname" />
<button type="button" id="lobby-join-btn" class="lobby-btn lobby-btn-primary">Join lobby</button>
</div>
</div>
<div id="lobby-step-wait" class="lobby-step lobby-hidden">
<p class="lobby-hint">Need at least two players. Everyone taps ready to start.</p>
<ul id="lobby-player-list" class="lobby-player-list"></ul>
<div class="lobby-actions">
<button type="button" id="lobby-ready-btn" class="lobby-btn lobby-btn-ready">Ready</button>
<button type="button" id="lobby-leave-btn" class="lobby-btn lobby-btn-ghost">Leave</button>
</div>
</div>
<div class="lobby-footer">
<span id="lobby-live" class="lobby-live lobby-live--warn" aria-live="polite">Connecting…</span>
<p id="lobby-meta" class="lobby-meta"></p>
</div>
</div>
</div>
<script src="/static/game/game.js"></script>
</body>
</html>

1574
static/game/style.css Normal file

File diff suppressed because it is too large Load Diff