|
let map, panorama, guessMarker, gameId, googleMapsApiKey; |
|
let startLocation; |
|
let onFirstLinksLoaded; |
|
|
|
function initLobby() { |
|
document.getElementById('new-game-form').addEventListener('submit', (e) => { |
|
e.preventDefault(); |
|
startGame(); |
|
}); |
|
document.getElementById('replay-form').addEventListener('submit', (e) => { |
|
e.preventDefault(); |
|
replayGame(); |
|
}); |
|
document.getElementById('play-again').addEventListener('click', showLobby); |
|
} |
|
|
|
function showLobby() { |
|
document.getElementById('lobby-container').style.display = 'block'; |
|
document.getElementById('game-container').style.display = 'none'; |
|
document.getElementById('result-screen').style.display = 'none'; |
|
} |
|
|
|
function showGame() { |
|
document.getElementById('lobby-container').style.display = 'none'; |
|
document.getElementById('game-container').style.display = 'flex'; |
|
document.getElementById('result-screen').style.display = 'none'; |
|
} |
|
|
|
function startGame() { |
|
showGame(); |
|
const difficulty = document.getElementById('difficulty-select-lobby').value; |
|
fetch('/start_game', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ difficulty: difficulty }) |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.error) { |
|
alert(data.error); |
|
showLobby(); |
|
return; |
|
} |
|
gameId = data.game_id; |
|
startLocation = data.start_location; |
|
googleMapsApiKey = data.google_maps_api_key; |
|
|
|
const chatLog = document.getElementById('chat-log'); |
|
chatLog.innerHTML = ''; |
|
addChatMessage('Agent', `New game started (ID: ${gameId}). Finding my location...`); |
|
|
|
initStreetView(startLocation); |
|
initMap(); |
|
|
|
|
|
}); |
|
} |
|
|
|
function replayGame() { |
|
const replayId = document.getElementById('replay-id-input').value; |
|
if (!replayId) { |
|
alert('Please enter a Game ID to replay.'); |
|
return; |
|
} |
|
|
|
fetch(`/game/${replayId}/state`) |
|
.then(response => { |
|
if (!response.ok) { |
|
throw new Error('Game not found.'); |
|
} |
|
return response.json(); |
|
}) |
|
.then(data => { |
|
if (!data.game_over) { |
|
alert('This game has not finished yet.'); |
|
return; |
|
} |
|
showGame(); |
|
gameId = replayId; |
|
startLocation = data.start_location; |
|
|
|
const chatLog = document.getElementById('chat-log'); |
|
chatLog.innerHTML = ''; |
|
addChatMessage('System', `Replaying game: ${gameId}`); |
|
|
|
initStreetView(startLocation); |
|
initMap(true); |
|
|
|
replayActions(data.actions); |
|
}) |
|
.catch(error => { |
|
alert(error.message); |
|
}); |
|
} |
|
|
|
async function replayActions(actions) { |
|
for (const action of actions) { |
|
await sleep(2000); |
|
if (action.type === 'move') { |
|
addChatMessage('Agent (Replay)', `Moved to: ${action.location.lat.toFixed(4)}, ${action.location.lng.toFixed(4)}`); |
|
panorama.setPosition(action.location); |
|
} else if (action.type === 'guess') { |
|
addChatMessage('Agent (Replay)', `Guessed: ${action.location.lat.toFixed(4)}, ${action.location.lng.toFixed(4)}`); |
|
placeGuessMarker(action.location); |
|
|
|
await sleep(2000); |
|
const resultData = { |
|
guess_location: action.location, |
|
actual_location: startLocation, |
|
distance_km: action.result.distance_km, |
|
score: action.result.score |
|
}; |
|
showResultScreen(resultData); |
|
} |
|
} |
|
} |
|
|
|
function initStreetView(location) { |
|
onFirstLinksLoaded = new Promise(resolve => { |
|
panorama = new google.maps.StreetViewPanorama( |
|
document.getElementById('streetview'), { |
|
position: location, |
|
pov: { heading: 34, pitch: 10 }, |
|
visible: true, |
|
linksControl: true, |
|
clickToGo: true, |
|
} |
|
); |
|
|
|
const linksChangedListener = panorama.addListener('links_changed', () => { |
|
console.log("links_changed event fired for the first time."); |
|
|
|
google.maps.event.removeListener(linksChangedListener); |
|
resolve(); |
|
}); |
|
|
|
panorama.addListener('position_changed', function() { |
|
const newLocation = panorama.getPosition(); |
|
updateAgentLocation(newLocation.lat(), newLocation.lng()); |
|
}); |
|
}); |
|
} |
|
|
|
function initMap(isReplay = false) { |
|
map = new google.maps.Map(document.getElementById('map'), { |
|
center: { lat: 0, lng: 0 }, |
|
zoom: 1, |
|
}); |
|
|
|
if (!isReplay) { |
|
map.addListener('click', function(e) { |
|
placeGuessMarker(e.latLng); |
|
makeGuess(e.latLng.lat(), e.latLng.lng()); |
|
}); |
|
} |
|
} |
|
|
|
function placeGuessMarker(location) { |
|
if (guessMarker) { |
|
guessMarker.setMap(null); |
|
} |
|
guessMarker = new google.maps.Marker({ |
|
position: location, |
|
map: map |
|
}); |
|
map.setCenter(location); |
|
} |
|
|
|
function addChatMessage(sender, message) { |
|
const chatLog = document.getElementById('chat-log'); |
|
const messageElement = document.createElement('div'); |
|
messageElement.innerHTML = `<strong>${sender}:</strong> ${message}`; |
|
chatLog.appendChild(messageElement); |
|
chatLog.scrollTop = chatLog.scrollHeight; |
|
} |
|
|
|
|
|
|
|
async function runFakeAgent() { |
|
addChatMessage('Agent', 'Initializing...'); |
|
console.log('runFakeAgent: Waiting for initial Street View data...'); |
|
|
|
|
|
await onFirstLinksLoaded; |
|
await sleep(1000); |
|
|
|
console.log('runFakeAgent: Initial data loaded. Starting navigation.'); |
|
await takeActionWithScreenshot('I have my bearings. Starting to move.'); |
|
await sleep(2000); |
|
|
|
const numberOfMoves = 3; |
|
let moved = false; |
|
|
|
for (let i = 0; i < numberOfMoves; i++) { |
|
addChatMessage('Agent', `Planning move ${i + 1}/${numberOfMoves}...`); |
|
await sleep(1000); |
|
|
|
|
|
await takeActionWithScreenshot(`Let me check my surroundings...`); |
|
await sleep(1000); |
|
await turnCamera(-90); |
|
await sleep(1500); |
|
await turnCamera(180); |
|
await sleep(1500); |
|
await turnCamera(-90); |
|
await sleep(1000); |
|
|
|
|
|
moved = await agentMove('forward'); |
|
|
|
console.log(`runFakeAgent: Move ${i + 1} result (moved):`, moved); |
|
if (!moved) { |
|
break; |
|
} |
|
|
|
|
|
await new Promise(resolve => { |
|
const listener = panorama.addListener('links_changed', () => { |
|
google.maps.event.removeListener(listener); |
|
resolve(); |
|
}); |
|
}); |
|
await sleep(2000); |
|
} |
|
|
|
addChatMessage('Agent', `I'm done moving. Now I will make a guess.`); |
|
console.log('runFakeAgent: Finished moving, now making a guess.'); |
|
await makeEducatedGuess(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async function agentMove(direction = 'forward') { |
|
await takeActionWithScreenshot(`Trying to move ${direction}...`); |
|
|
|
const links = panorama.getLinks(); |
|
if (!links || links.length === 0) { |
|
addChatMessage('Agent', "I'm at a dead end, can't move from here."); |
|
console.log('agentMove: No links found.'); |
|
return false; |
|
} |
|
|
|
const currentPov = panorama.getPov(); |
|
let targetHeading; |
|
|
|
switch(direction) { |
|
case 'forward': |
|
targetHeading = currentPov.heading; |
|
break; |
|
case 'backward': |
|
targetHeading = (currentPov.heading + 180) % 360; |
|
break; |
|
case 'left': |
|
targetHeading = (currentPov.heading - 90 + 360) % 360; |
|
break; |
|
case 'right': |
|
targetHeading = (currentPov.heading + 90) % 360; |
|
break; |
|
default: |
|
if (typeof direction === 'number' && direction >= 0 && direction < 360) { |
|
targetHeading = direction; |
|
} else { |
|
addChatMessage('Agent', `Unknown direction: ${direction}. Defaulting to forward.`); |
|
targetHeading = currentPov.heading; |
|
} |
|
break; |
|
} |
|
|
|
let bestLink = null; |
|
let minAngleDiff = 360; |
|
|
|
await sleep(1000); |
|
|
|
links.forEach(link => { |
|
let diff = Math.abs(targetHeading - link.heading); |
|
if (diff > 180) diff = 360 - diff; |
|
|
|
if (diff < minAngleDiff) { |
|
minAngleDiff = diff; |
|
bestLink = link; |
|
} |
|
}); |
|
|
|
if (bestLink) { |
|
console.log(`agentMove: Best link found:`, bestLink, `with angle diff ${minAngleDiff}`); |
|
await sleep(1500); |
|
await takeActionWithScreenshot(`Best path is at ${bestLink.heading.toFixed(1)}° (a ${minAngleDiff.toFixed(1)}° turn). Moving...`); |
|
|
|
panorama.setPano(bestLink.pano); |
|
return true; |
|
} else { |
|
|
|
addChatMessage('Agent', "Couldn't find a suitable path in that direction."); |
|
console.log('agentMove: No suitable link found.'); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function turnCamera(angle) { |
|
const currentPov = panorama.getPov(); |
|
const newHeading = (currentPov.heading + angle % 360 + 360) % 360; |
|
|
|
const direction = angle < 0 ? 'left' : 'right'; |
|
await takeActionWithScreenshot(`Turning ${direction} by ${Math.abs(angle)} degrees.`); |
|
|
|
panorama.setPov({ heading: newHeading, pitch: currentPov.pitch }); |
|
|
|
await sleep(1000); |
|
} |
|
|
|
async function makeEducatedGuess() { |
|
|
|
await takeActionWithScreenshot('Okay, I think I have an idea. I will make a guess in 10 seconds...'); |
|
await sleep(10000); |
|
addChatMessage('Agent', 'Making my guess now!'); |
|
|
|
const guessLat = startLocation.lat + (Math.random() - 0.5) * 0.1; |
|
const guessLng = startLocation.lng + (Math.random() - 0.5) * 0.1; |
|
|
|
placeGuessMarker({ lat: guessLat, lng: guessLng }); |
|
makeGuess(guessLat, guessLng); |
|
} |
|
|
|
async function takeActionWithScreenshot(actionMessage) { |
|
if (!googleMapsApiKey) { |
|
addChatMessage('Agent', actionMessage); |
|
console.warn("Google Maps API key not available for screenshot."); |
|
return; |
|
} |
|
|
|
await sleep(500); |
|
|
|
const pov = panorama.getPov(); |
|
const position = panorama.getPosition(); |
|
const location = `${position.lat()},${position.lng()}`; |
|
|
|
const imageUrl = `https://maps.googleapis.com/maps/api/streetview?size=400x250&location=${location}&heading=${pov.heading}&pitch=${pov.pitch}&fov=90&key=${googleMapsApiKey}`; |
|
|
|
const message = ` |
|
${actionMessage}<br> |
|
<img src="${imageUrl}" alt="Agent's view" style="width: 100%; border-radius: 5px; margin-top: 5px; border: 1px solid #ccc;"> |
|
`; |
|
addChatMessage('Agent', message); |
|
console.log(`Action taken: ${actionMessage}`); |
|
} |
|
|
|
async function updateAgentLocation(lat, lng) { |
|
await fetch(`/game/${gameId}/move`, { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ lat: lat, lng: lng }), |
|
}); |
|
} |
|
|
|
async function makeGuess(lat, lng) { |
|
addChatMessage('You', `Guessed: ${lat.toFixed(4)}, ${lng.toFixed(4)}`); |
|
const response = await fetch(`/game/${gameId}/guess`, { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ lat: lat, lng: lng }), |
|
}); |
|
const result = await response.json(); |
|
showResultScreen(result); |
|
} |
|
|
|
function showResultScreen(result) { |
|
document.getElementById('game-container').style.display = 'none'; |
|
document.getElementById('result-screen').style.display = 'block'; |
|
|
|
const resultSummary = document.getElementById('result-summary'); |
|
resultSummary.innerHTML = ` |
|
<p>Your guess was ${result.distance_km.toFixed(2)} km away.</p> |
|
<p>You scored ${result.score.toFixed(0)} points.</p> |
|
`; |
|
|
|
const resultMap = new google.maps.Map(document.getElementById('result-map'), { |
|
zoom: 3, |
|
center: result.actual_location |
|
}); |
|
|
|
new google.maps.Marker({ |
|
position: result.actual_location, |
|
map: resultMap, |
|
label: 'A' |
|
}); |
|
|
|
new google.maps.Marker({ |
|
position: result.guess_location, |
|
map: resultMap, |
|
label: 'G' |
|
}); |
|
|
|
new google.maps.Polyline({ |
|
path: [result.actual_location, result.guess_location], |
|
geodesic: true, |
|
strokeColor: '#F97316', |
|
strokeOpacity: 1.0, |
|
strokeWeight: 2, |
|
map: resultMap |
|
}); |
|
} |
|
|
|
function sleep(ms) { |
|
return new Promise(resolve => setTimeout(resolve, ms)); |
|
} |