Jofthomas's picture
Upload 12 files
a303b0f verified
let miniMap, guessMarker, currentRounds = [], currentRoundIdx = 0, aiEventSource = null;
function initIndexApp() {
hydrateAuth();
document.getElementById('start-btn').addEventListener('click', async () => {
const difficulty = document.getElementById('difficulty-select-lobby').value;
const url = new URL(window.location.origin + '/game');
url.searchParams.set('difficulty', difficulty);
window.location.href = url.toString();
});
}
async function initGameApp() {
const params = new URLSearchParams(window.location.search);
const difficulty = params.get('difficulty') || 'easy';
const res = await fetch('/api/start', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ difficulty })
});
const data = await res.json();
if (!res.ok) { alert(data.error || 'Failed to start game'); window.location.href = '/'; return; }
currentRounds = data.rounds;
currentRoundIdx = 0;
document.getElementById('game-container').style.display = 'block';
document.getElementById('chat-container').style.display = 'none';
document.getElementById('validate-btn').addEventListener('click', validateGuess);
document.getElementById('next-round-btn').addEventListener('click', nextRound);
const wrapEl = document.getElementById('mini-map-wrap');
document.getElementById('map-size-plus').addEventListener('click', (e) => { e.stopPropagation(); resizeMiniMap(wrapEl, 1); });
document.getElementById('map-size-minus').addEventListener('click', (e) => { e.stopPropagation(); resizeMiniMap(wrapEl, -1); });
showRound();
}
async function hydrateAuth() {
const res = await fetch('/api/me');
const me = await res.json();
const signedin = document.getElementById('signedin');
const signinBtn = document.getElementById('signin-btn');
const startBtn = document.getElementById('start-btn');
const limitMsg = document.getElementById('limit-msg');
if (me.authenticated) {
signinBtn.style.display = 'none';
signedin.style.display = 'block';
document.getElementById('username').textContent = me.username;
if (me.can_play_today) {
startBtn.disabled = false;
limitMsg.style.display = 'none';
} else {
startBtn.disabled = true;
limitMsg.style.display = 'block';
limitMsg.textContent = `You already played today. Try again in ${formatCountdown(me.seconds_until_midnight)}.`;
startCountdown(limitMsg, me.seconds_until_midnight);
}
} else {
signinBtn.style.display = 'inline-block';
signedin.style.display = 'none';
startBtn.disabled = true;
}
}
function formatCountdown(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${pad(h)}:${pad(m)}:${pad(s)}`;
}
function pad(n) { return n.toString().padStart(2, '0'); }
function startCountdown(el, seconds) {
let remaining = seconds;
const id = setInterval(() => {
remaining -= 1;
if (remaining <= 0) { clearInterval(id); location.reload(); return; }
el.textContent = `You already played today. Try again in ${formatCountdown(remaining)}.`;
}, 1000);
}
async function startGame() {
const difficulty = document.getElementById('difficulty-select-lobby').value;
const res = await fetch('/api/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ difficulty })
});
const data = await res.json();
if (!res.ok) {
alert(data.error || 'Failed to start game');
return;
}
currentRounds = data.rounds;
currentRoundIdx = 0;
document.getElementById('game-container').style.display = 'block';
document.getElementById('chat-container').style.display = 'none';
showRound();
}
function showRound() {
const round = currentRounds[currentRoundIdx];
const img = document.getElementById('street-image');
img.src = round.image_url;
document.getElementById('validate-btn').style.display = 'none';
initMiniMap();
}
function initMiniMap() {
const miniEl = document.getElementById('mini-map');
miniMap = new google.maps.Map(miniEl, {
center: { lat: 0, lng: 0 },
zoom: 1,
streetViewControl: false,
mapTypeControl: false,
fullscreenControl: false,
});
miniMap.addListener('click', (e) => {
if (miniMap._locked) return;
placeGuessMarker(e.latLng);
document.getElementById('validate-btn').style.display = 'inline-block';
});
}
function resizeMiniMap(el, delta) {
const sizes = ['size-small', 'size-medium', 'size-large'];
let idx = sizes.findIndex(c => el.classList.contains(c));
if (idx === -1) idx = 1;
idx = Math.min(2, Math.max(0, idx + (delta > 0 ? 1 : -1)));
sizes.forEach(c => el.classList.remove(c));
el.classList.add(sizes[idx]);
if (miniMap) {
const miniEl = document.getElementById('mini-map');
google.maps.event.trigger(miniMap, 'resize');
const center = miniMap.getCenter();
miniMap.setCenter(center);
}
}
function placeGuessMarker(latLng) {
if (guessMarker) guessMarker.setMap(null);
guessMarker = new google.maps.Marker({ position: latLng, map: miniMap });
miniMap.setCenter(latLng);
}
async function validateGuess() {
if (!guessMarker) { alert('Click on the mini-map to pick your guess.'); return; }
const round = currentRounds[currentRoundIdx];
const pos = guessMarker.getPosition();
const payload = { round_id: round.id, lat: pos.lat(), lng: pos.lng() };
const res = await fetch('/api/guess', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
const result = await res.json();
if (!res.ok) { alert(result.error || 'Guess failed'); return; }
miniMap._locked = true;
document.getElementById('validate-btn').style.display = 'none';
document.getElementById('chat-container').style.display = 'block';
const chat = document.getElementById('chat-log');
chat.textContent = '';
chat.textContent += 'Analyzing image...\n';
const aiRes = await fetch('/api/ai_analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ round_id: round.id }) });
const aiData = await aiRes.json();
if (!aiRes.ok) {
chat.textContent += `[Error] ${aiData.error || 'AI failed'}\n`;
document.getElementById('next-round-btn').disabled = false;
openResultsPopup({ actual: result.actual_location, human: result.guess_location });
return;
}
chat.textContent += (aiData.text || '') + '\n';
openResultsPopup({ actual: result.actual_location, human: result.guess_location });
if (aiData.ai_guess) renderAIGuessOnPopup(aiData.ai_guess);
document.getElementById('next-round-btn').disabled = false;
}
function openResultsPopup(points) {
const overlay = document.getElementById('popup-overlay');
overlay.style.display = 'flex';
const popupMap = new google.maps.Map(document.getElementById('popup-map'), { zoom: 3, center: points.actual });
new google.maps.Marker({ position: points.actual, map: popupMap, label: 'A' });
new google.maps.Marker({ position: points.human, map: popupMap, label: 'H' });
new google.maps.Polyline({ path: [points.actual, points.human], geodesic: true, strokeColor: '#F97316', strokeOpacity: 1.0, strokeWeight: 2, map: popupMap });
document.getElementById('next-round-btn').disabled = true;
}
function appendChatToken(t) {
const el = document.getElementById('chat-log');
const span = document.createElement('span');
span.textContent = t;
el.appendChild(span);
el.scrollTop = el.scrollHeight;
}
function renderAIGuessOnPopup(ai) {
const mapEl = document.getElementById('popup-map');
const popupMap = mapEl._map || new google.maps.Map(mapEl, { zoom: 3, center: ai });
mapEl._map = popupMap;
new google.maps.Marker({ position: ai, map: popupMap, label: 'AI' });
}
async function nextRound() {
if (aiEventSource) { try { aiEventSource.close(); } catch (e) {} }
currentRoundIdx += 1;
if (currentRoundIdx >= currentRounds.length) {
await showFinalResults();
return;
}
document.getElementById('popup-overlay').style.display = 'none';
showRound();
}
async function showFinalResults() {
const res = await fetch('/api/session_summary');
const data = await res.json();
document.getElementById('popup-overlay').style.display = 'none';
document.getElementById('game-container').style.display = 'none';
const fin = document.getElementById('final-results');
fin.style.display = 'block';
const total = data.total_score?.toFixed ? data.total_score.toFixed(0) : data.total_score;
document.getElementById('final-summary').textContent = `Your total score: ${total} / 15000`;
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }