Geogussr / static /script.js
Jofthomas's picture
Upload 24 files
1ac84c3 verified
let map, panorama, guessMarker, gameId, googleMapsApiKey;
let startLocation;
let onFirstLinksLoaded; // Promise that resolves when the first panorama links are loaded
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();
//runFakeAgent();
});
}
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); // isReplay = 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, // Ensure links are enabled
clickToGo: true, // Ensure click-to-go is enabled
}
);
const linksChangedListener = panorama.addListener('links_changed', () => {
console.log("links_changed event fired for the first time.");
// We got links, resolve the promise, and remove the one-time listener.
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;
}
// --- Fake LLM Agent Logic ---
async function runFakeAgent() {
addChatMessage('Agent', 'Initializing...');
console.log('runFakeAgent: Waiting for initial Street View data...');
// Wait for the first links_changed event to ensure panorama is ready
await onFirstLinksLoaded;
await sleep(1000); // Wait a bit for the panorama to be fully rendered
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);
// Let the agent look around
await takeActionWithScreenshot(`Let me check my surroundings...`);
await sleep(1000);
await turnCamera(-90); // Look 90 degrees left
await sleep(1500);
await turnCamera(180); // Look 180 degrees right (which is 90 deg right from origin)
await sleep(1500);
await turnCamera(-90); // Turn back to the front
await sleep(1000);
// The agent will always try to move forward in this simplified logic
moved = await agentMove('forward');
console.log(`runFakeAgent: Move ${i + 1} result (moved):`, moved);
if (!moved) {
break; // Stop if we hit a dead end
}
// After moving, we need to wait for the new panorama to load its links
await new Promise(resolve => {
const listener = panorama.addListener('links_changed', () => {
google.maps.event.removeListener(listener);
resolve();
});
});
await sleep(2000); // Pause for the user to see the new location
}
addChatMessage('Agent', `I'm done moving. Now I will make a guess.`);
console.log('runFakeAgent: Finished moving, now making a guess.');
await makeEducatedGuess();
}
/**
* Moves the Street View agent in a specified direction.
* @param {('forward'|'backward'|'left'|'right'|number)} direction - The direction to move.
* @returns {Promise<boolean>} - True if the move was successful, false otherwise.
*/
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; // Find the shortest angle
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 {
// This case should be rare if there are any links at all
addChatMessage('Agent', "Couldn't find a suitable path in that direction.");
console.log('agentMove: No suitable link found.');
return false;
}
}
/**
* Turns the camera view relative to the current heading.
* @param {number} angle - The angle in degrees to turn. Negative values turn left, positive values turn right.
*/
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); // Simulate action time
}
async function makeEducatedGuess() {
// 2. Guess (a bit off from the start location to simulate a guess)
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); // Fallback if key is not available
console.warn("Google Maps API key not available for screenshot.");
return;
}
await sleep(500); // Give panorama a moment to update image
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' // Actual
});
new google.maps.Marker({
position: result.guess_location,
map: resultMap,
label: 'G' // Guess
});
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));
}