Driving Game | Google Map
// Game state variables let map; let carMarker; let destinationMarker; let currentPosition; // LatLng object let destinationPosition; let score = 0; let gameActive = true; let snapService; // for road snapping let lastSnapRequest = null; // Movement tuning const MOVE_STEP_METERS = 18; // each tap moves ~18 meters (smooth driving) const SNAP_RADIUS_METERS = 40; // snap to nearest road within 40m // UI elements const scoreEl = document.getElementById('scoreValue'); const distEl = document.getElementById('distValue'); // Helper: meters to readable string function formatDistance(meters) if (meters < 1000) return `$Math.floor(meters) m`; return `$(meters / 1000).toFixed(1) km`; // Update distance display & score (score based on distance covered from start) let totalDrivenMeters = 0; let lastPositionForDistance = null; function updateDistanceAndScore(newPos) !gameActive) return; const delta = google.maps.geometry.spherical.computeDistanceBetween(lastPositionForDistance, newPos); if (delta > 0.5 && delta < 200) // avoid teleport jumps totalDrivenMeters += delta; // Score increments: 1 point per 10 meters driven (feels rewarding) const newScore = Math.floor(totalDrivenMeters / 10); if (newScore > score) score = newScore; scoreEl.innerText = score; lastPositionForDistance = newPos; // Show remaining distance to destination if (destinationPosition) const remaining = google.maps.geometry.spherical.computeDistanceBetween(newPos, destinationPosition); distEl.innerText = formatDistance(remaining); // Win condition: if within 12 meters of destination if (remaining < 12 && gameActive) gameActive = false; setTimeout(() => alert(`🏆 YOU REACHED THE DESTINATION! 🏆\nTotal Score: $score\nDistance driven: $formatDistance(totalDrivenMeters)`); // reset after alert? , 50); // Disable further moves? but we keep gameActive false else distEl.innerText = "---"; // Snap to nearest road using Google Maps Roads API (snapToRoads) OR fallback to geometry library // Since Roads API requires extra key/permissions, we implement a clever snap using the 'geometry' library // and a hidden 'snap-to-road' using Directions API? simpler: we fetch the nearest point on a road segment by using // the `google.maps.geometry.poly.isLocationOnEdge`? Not robust. // Better: use Google Maps Roads API (snapToRoads) but that needs separate enablement. We'll simulate robust snapping // using a DirectionsService intermediate point? Not ideal. Instead implement a clean "clamp to road" by using // the `nearestRoads` trick: we'll call the Distance Matrix? No. // To make game functional and fun without external API key complexities, we implement an elegant "road snap" using // the `google.maps.RoadsService`? Actually Roads API requires additional API. For this demo to work without extra config, // we will use the Places Autocomplete or just rely on the fact the user starts on a road, and we manually adjust by fetching // the nearest road using the `SnapToRoads` from google maps? Simpler: we will use the `directionsService` to get the closest // point along a road? Too heavy. I'll implement a mock road snap that just corrects the car by a tiny offset to stay on // plausible roads — but for real driving game effect, we use a hybrid: On each move, we query the Roads API if available, // but to ensure the demo works out-of-box with a basic API key (Maps JS + Places), we implement a "Safe snap" that uses // the `nearestRoads` using Google's sample method: we can use the `google.maps.RoadsService`? It needs `roads` library. // I decide to build a custom Snap Helper: Using the DirectionsService to "snap" to the nearest point on a drivable path // between two nearby points. However, for simplicity and robust offline-ish feel, I'll implement a "stay on road" mechanism: // We store last valid snapped position. For each move, we compute next point based on heading, then use the `google.maps.geometry.poly` // to check if the point is near any road? That's heavy. // Instead, the most professional: I'll embed a fallback that uses the `PlacesService` to find nearby roads? Not perfect. // Given time, I'll make a function that uses Directions API to get a route from current position to a point ahead, // then extract the nearest polyline point, making the car snap exactly to the route path. That ensures the car follows roads! // This is elegant and works with standard Maps API key without extra Roads API. let directionsService; let currentRoutePath = []; // array of latLng for last computed route let currentHeading = 0; // in degrees // Initialize direction service and get initial road lock function initRoadSnapping() directionsService = new google.maps.DirectionsService(); // Given current position and desired target (next position candidate), snap to nearest road point using DirectionsService // by requesting a short route from current to a point ~50m ahead in desired direction, then extract first point. async function snapToRoad(currentLatLng, desiredNextLatLng) return new Promise((resolve) => // If no directionsService, resolve original if (!directionsService) resolve(desiredNextLatLng); return; // Create a small route request from current to desired point (max distance ~80m) const request = origin: currentLatLng, destination: desiredNextLatLng, travelMode: google.maps.TravelMode.DRIVING, provideRouteAlternatives: false, unitSystem: google.maps.UnitSystem.METRIC ; directionsService.route(request, (result, status) => if (status === google.maps.DirectionsStatus.OK && result.routes[0] && result.routes[0].overview_path.length > 0) const path = result.routes[0].overview_path; // find the point in the route closest to desiredNextLatLng (usually the last or near) let closestPoint = path[path.length-1]; let minDist = google.maps.geometry.spherical.computeDistanceBetween(closestPoint, desiredNextLatLng); for (let i=0; i<path.length; i++) const d = google.maps.geometry.spherical.computeDistanceBetween(path[i], desiredNextLatLng); if (d < minDist) minDist = d; closestPoint = path[i]; // Also store route for potential heading if (path.length >= 2) const idx = Math.min(path.length-1, 2); currentHeading = google.maps.geometry.spherical.computeHeading(currentLatLng, path[idx]); resolve(closestPoint); else // fallback: move straight but ensure no major deviation resolve(desiredNextLatLng); ); ); // Move car in a given direction (forward/backward/steer left/right) // direction: 'forward', 'backward', 'left', 'right' async function moveCar(direction) // Helper: compute point from origin at given heading (degrees) and distance meters function getPointAtDistance(origin, headingDeg, distanceMeters) const radius = 6371000; // Earth radius in meters const lat1 = origin.lat() * Math.PI / 180; const lon1 = origin.lng() * Math.PI / 180; const brng = headingDeg * Math.PI / 180; const d = distanceMeters; const lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/radius) + Math.cos(lat1) * Math.sin(d/radius) * Math.cos(brng)); const lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d/radius) * Math.cos(lat1), Math.cos(d/radius) - Math.sin(lat1) * Math.sin(lat2)); return new google.maps.LatLng(lat2 * 180 / Math.PI, lon2 * 180 / Math.PI); // Execute move: update marker, map view, scoring async function executeMoveUpdate(newPosition) if (!newPosition) return; const oldPos = currentPosition; currentPosition = newPosition; carMarker.setPosition(currentPosition); // Update distance & score updateDistanceAndScore(currentPosition); // Smoothly center map on car (pan) map.panTo(currentPosition); // Rotate car marker icon based on heading const iconSymbol = path: 'M-12,0 L0,-18 L12,0 L0,18 Z', fillColor: '#ff4444', fillOpacity: 1, strokeWeight: 2, strokeColor: '#ffffff', scale: 1.2, rotation: currentHeading ; carMarker.setIcon(iconSymbol); // small haptic feedback on mobile? optional // Reset game: set random destination 0.5-2km away from start along drivable roads async function resetGame() !currentPosition) return; gameActive = true; score = 0; totalDrivenMeters = 0; scoreEl.innerText = "0"; lastPositionForDistance = currentPosition; // Generate random destination ~ 500m to 2500m from current position (drivable) const randomAngle = Math.random() * 360; const randomDistance = 600 + Math.random() * 1800; // meters const rawDest = getPointAtDistance(currentPosition, randomAngle, randomDistance); // snap destination to road using directions const snapDest = await snapToRoad(currentPosition, rawDest); destinationPosition = snapDest; if (destinationMarker) destinationMarker.setMap(null); destinationMarker = new google.maps.Marker( position: destinationPosition, map: map, icon: path: 'M0,-15 L10,10 L0,0 L-10,10 Z', fillColor: '#ffaa33', fillOpacity: 1, strokeWeight: 1, scale: 1, rotation: 0 , title: 'Destination', label: text: '🏁', color: 'white', fontSize: '16px', fontWeight: 'bold' ); // Show distance to dest const distRem = google.maps.geometry.spherical.computeDistanceBetween(currentPosition, destinationPosition); distEl.innerText = formatDistance(distRem); // reset heading to initial north-ish or current direction to destination? const initialHeading = google.maps.geometry.spherical.computeHeading(currentPosition, destinationPosition); currentHeading = initialHeading; // refresh car icon rotation const iconSymbol = path: 'M-12,0 L0,-18 L12,0 L0,18 Z', fillColor: '#ff4444', fillOpacity: 1, strokeWeight: 2, strokeColor: '#ffffff', scale: 1.2, rotation: currentHeading ; carMarker.setIcon(iconSymbol); // Manual drop destination on map click (optional fun) function setupMapClickListener() map.addListener('click', async (e) => if (!gameActive) return; const clicked = e.latLng; // snap clicked point to nearest road via directions from current pos? better simple snap const snapped = await snapToRoad(currentPosition, clicked); if (snapped) destinationPosition = snapped; if (destinationMarker) destinationMarker.setMap(null); destinationMarker = new google.maps.Marker( position: destinationPosition, map: map, icon: path: 'M0,-15 L10,10 L0,0 L-10,10 Z', fillColor: '#ffaa33', fillOpacity: 1, strokeWeight: 1, , label: text: '🎯', color: 'white', fontSize: '14px' ); const distRem = google.maps.geometry.spherical.computeDistanceBetween(currentPosition, destinationPosition); distEl.innerText = formatDistance(distRem); ); // Initialization entry point window.initMap = function() // Default starting location: Central Park, NYC (great roads) const startLocation = lat: 40.7812, lng: -73.9665 ; currentPosition = new google.maps.LatLng(startLocation.lat, startLocation.lng); map = new google.maps.Map(document.getElementById('map'), center: startLocation, zoom: 18, mapTypeId: google.maps.MapTypeId.ROADMAP, tilt: 0, streetViewControl: false, fullscreenControl: true, zoomControl: true, mapTypeControl: false ); // Custom car marker const carIcon = path: 'M-12,0 L0,-18 L12,0 L0,18 Z', fillColor: '#ff4444', fillOpacity: 1, strokeWeight: 2, strokeColor: '#ffffff', scale: 1.2, rotation: 0 ; carMarker = new google.maps.Marker( position: currentPosition, map: map, icon: carIcon, zIndex: 100, title: "Your Car" ); initRoadSnapping(); lastPositionForDistance = currentPosition; currentHeading = 90; // east facing initially // Setup destination after short delay setTimeout(async () => await resetGame(); , 500); setupMapClickListener(); // Add optional driving trail? (nice to have but optional) const trafficLayer = new google.maps.TrafficLayer(); trafficLayer.setMap(map); ; // Event listeners for buttons (and keyboard support) document.addEventListener('DOMContentLoaded', () => // Buttons document.getElementById('btn-fwd').addEventListener('click', () => moveCar('forward')); document.getElementById('btn-left').addEventListener('click', () => moveCar('left')); document.getElementById('btn-right').addEventListener('click', () => moveCar('right')); document.getElementById('btn-back').addEventListener('click', () => moveCar('backward')); document.getElementById('btn-reset').addEventListener('click', async () => if (currentPosition) await resetGame(); ); // Keyboard controls: Arrow keys window.addEventListener('keydown', (e) => if (!gameActive) return; switch(e.key) case 'ArrowUp': e.preventDefault(); moveCar('forward'); break; case 'ArrowDown': e.preventDefault(); moveCar('backward'); break; case 'ArrowLeft': e.preventDefault(); moveCar('left'); break; case 'ArrowRight': e.preventDefault(); moveCar('right'); break; case 'r': case 'R': e.preventDefault(); resetGame(); break; ); ); // Fallback if API key missing: show friendly note window.onload = function() !google.maps) document.body.innerHTML = '<div style="background:black;color:white;padding:40px;text-align:center;height:100vh;"><h2>⚠️ Google Maps API Key Required</h2><p>Please replace <strong>YOUR_API_KEY</strong> in the script src with a valid Google Maps API key.<br>Enable Maps JavaScript API, Places API, and Geometry library.</p><p>Then reload the page to enjoy the driving game!</p></div>'; ; </script> </head> <body> <div class="game-container"> <div id="map"></div> <div class="hud"> <div class="stats-panel"> 🚗 SCORE: <span id="scoreValue">0</span> | 📍 DIST: <span id="distValue">---</span> </div> <div class="controls-card"> <button class="ctrl-btn" id="btn-left" title="Steer Left">◀</button> <button class="ctrl-btn" id="btn-fwd" title="Drive Forward">▲</button> <button class="ctrl-btn" id="btn-back" title="Reverse">▼</button> <button class="ctrl-btn" id="btn-right" title="Steer Right">▶</button> <button class="ctrl-btn reset-btn" id="btn-reset" title="New Destination">⟳</button> </div> <div class="info-text"> 🎮 Arrow keys / Buttons | Click map to set destination 🏁 </div> </div> <div class="instruction"> 🚘 Drive on roads & reach the golden flag! </div> </div> </body> </html>
/* Main game container */ .game-container position: relative; width: 100%; height: 100%; google map driving game
.ctrl-btn background: #2c3e44; border: none; color: white; font-size: 2rem; font-weight: bold; width: 70px; height: 70px; border-radius: 60px; cursor: pointer; transition: all 0.1s ease; box-shadow: 0 4px 0 #0f1a1f; font-family: monospace; display: flex; align-items: center; justify-content: center; touch-action: manipulation; // Game state variables let map; let carMarker;
.instruction position: absolute; top: 15px; right: 15px; background: rgba(0,0,0,0.6); padding: 6px 12px; border-radius: 20px; color: #eee; font-size: 12px; font-family: monospace; z-index: 10; pointer-events: none; but we keep gameActive false else distEl
.stats-panel span color: #ffaa33; font-size: 1.5rem; margin-right: 6px;
