Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Desert Road Runner</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| font-family: 'Press Start 2P', cursive; | |
| background-color: #f7f7f7; | |
| } | |
| #game-container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| background: linear-gradient(to bottom, #87CEEB 0%, #E6E6FA 50%, #F5DEB3 100%); | |
| overflow: hidden; | |
| } | |
| #road { | |
| position: absolute; | |
| bottom: 0; | |
| width: 100%; | |
| height: 120px; | |
| background-color: #333; | |
| z-index: 5; | |
| } | |
| .road-marking { | |
| position: absolute; | |
| bottom: 45px; | |
| width: 50px; | |
| height: 10px; | |
| background-color: #fff; | |
| z-index: 6; | |
| } | |
| #car { | |
| position: absolute; | |
| bottom: 0px; | |
| left: 40px; | |
| width: 200px; | |
| height: 200px; | |
| background-image: url('https://www.chennaipainter.in/wp-content/uploads/2025/04/car-1024x434.png'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| z-index: 15; | |
| transform: scale(0.7); | |
| } | |
| .obstacle { | |
| position: absolute; | |
| bottom: 80px; | |
| width: 80px; | |
| height: 70px; | |
| background-image: url('https://www.chennaipainter.in/wp-content/uploads/2025/04/obstacle-150x150.png'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| z-index: 5; | |
| } | |
| .cloud { | |
| position: absolute; | |
| width: 100px; | |
| height: 60px; | |
| background-image: url('https://www.chennaipainter.in/wp-content/uploads/2025/04/cloud-300x300.png'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| opacity: 0.8; | |
| z-index: 2; | |
| } | |
| .cactus { | |
| position: absolute; | |
| bottom: 118px; | |
| width: 200px; | |
| height: 500px; | |
| background-image: url('https://www.chennaipainter.in/wp-content/uploads/2025/04/tree-150x150.png'); | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| z-index: 4; | |
| } | |
| #score { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| font-size: 20px; | |
| color: #333; | |
| z-index: 20; | |
| background-color: rgba(255, 255, 255, 0.7); | |
| padding: 10px 15px; | |
| border-radius: 10px; | |
| } | |
| #game-over { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 40px; | |
| color: #ff3333; | |
| text-align: center; | |
| display: none; | |
| z-index: 20; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| padding: 30px; | |
| border-radius: 20px; | |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); | |
| } | |
| #start-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(240, 230, 210, 0.9); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 30; | |
| text-align: center; | |
| } | |
| #start-button { | |
| margin-top: 20px; | |
| padding: 15px 40px; | |
| font-size: 20px; | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| font-family: 'Press Start 2P', cursive; | |
| transition: all 0.3s; | |
| box-shadow: 0 5px 0 #2E7D32; | |
| } | |
| #start-button:hover { | |
| background-color: #45a049; | |
| transform: translateY(-2px); | |
| } | |
| #start-button:active { | |
| transform: translateY(3px); | |
| box-shadow: 0 2px 0 #2E7D32; | |
| } | |
| .controls { | |
| margin-top: 30px; | |
| display: flex; | |
| gap: 20px; | |
| } | |
| .control-key { | |
| background-color: #333; | |
| color: white; | |
| padding: 10px 15px; | |
| border-radius: 5px; | |
| font-size: 16px; | |
| } | |
| .sun { | |
| position: absolute; | |
| width: 80px; | |
| height: 80px; | |
| background: radial-gradient(circle, #FFD700 30%, #FFA500 70%); | |
| border-radius: 50%; | |
| box-shadow: 0 0 40px #FFD700; | |
| z-index: 1; | |
| } | |
| @keyframes birdFly { | |
| 0% { transform: translateX(0) translateY(0); } | |
| 50% { transform: translateX(0) translateY(-10px); } | |
| 100% { transform: translateX(0) translateY(0); } | |
| } | |
| .bird { | |
| animation: birdFly 1s infinite ease-in-out; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div class="sun" style="top: 50px; right: 100px;"></div> | |
| <div id="road"></div> | |
| <div id="score">0</div> | |
| <div id="game-over"> | |
| GAME OVER<br> | |
| <span id="final-score">0</span><br> | |
| <div class="mt-4 text-xl">Press Space to Restart</div> | |
| <div class="mt-4 text-sm">Or tap the screen on mobile</div> | |
| </div> | |
| <div id="start-screen"> | |
| <h1 class="text-4xl md:text-6xl mb-6 text-orange-600">play with Viateur AI</h1> | |
| <p class="mb-8 text-lg md:text-xl max-w-2xl px-4">Avoid the obstacles and survive as long as you can in this endless desert adventure!</p> | |
| <button id="start-button">START GAME to Viateur AI</button> | |
| <div class="controls mt-8"> | |
| <div class="control-key"><i class="fas fa-arrow-up"></i> JUMP</div> | |
| </div> | |
| <div class="mt-8 text-sm text-gray-600"> | |
| <i class="fas fa-mobile-alt"></i> Tap the screen to jump on mobile | |
| </div> | |
| </div> | |
| <div id="car"></div> | |
| </div> | |
| <script> | |
| const gameContainer = document.getElementById('game-container'); | |
| const car = document.getElementById('car'); | |
| const scoreEl = document.getElementById('score'); | |
| const gameOverEl = document.getElementById('game-over'); | |
| const finalScoreEl = document.getElementById('final-score'); | |
| const startScreen = document.getElementById('start-screen'); | |
| const startButton = document.getElementById('start-button'); | |
| let gameSpeed = 5; | |
| let score = 0; | |
| let isJumping = false; | |
| let isGameOver = false; | |
| let gameStarted = false; | |
| let jumpStart = 0; | |
| let animationId; | |
| const obstacles = []; | |
| const clouds = []; | |
| const birds = []; | |
| const cacti = []; | |
| const mountains = []; | |
| const roadMarkings = []; | |
| // Create initial road markings | |
| for (let i = 0; i < 10; i++) { | |
| const mark = document.createElement('div'); | |
| mark.className = 'road-marking'; | |
| mark.style.left = (i * 200) + 'px'; | |
| gameContainer.appendChild(mark); | |
| roadMarkings.push({ el: mark, x: i * 200 }); | |
| } | |
| // Create initial clouds | |
| for (let i = 0; i < 5; i++) { | |
| createCloud(Math.random() * window.innerWidth, Math.random() * 200); | |
| } | |
| // Create initial desert scenery | |
| for (let i = 0; i < 3; i++) { | |
| createCactus(window.innerWidth + (i * 400), Math.random() * 50 + 50); | |
| } | |
| for (let i = 0; i < 2; i++) { | |
| createMountain(window.innerWidth + (i * 600), Math.random() * 100); | |
| } | |
| function startGame() { | |
| gameStarted = true; | |
| isGameOver = false; | |
| score = 0; | |
| gameSpeed = 5; | |
| scoreEl.textContent = '0'; | |
| startScreen.style.display = 'none'; | |
| gameOverEl.style.display = 'none'; | |
| // Clear all game elements | |
| obstacles.forEach(o => o.el.remove()); | |
| clouds.forEach(c => c.el.remove()); | |
| birds.forEach(b => b.el.remove()); | |
| cacti.forEach(c => c.el.remove()); | |
| mountains.forEach(m => m.el.remove()); | |
| obstacles.length = clouds.length = birds.length = cacti.length = mountains.length = 0; | |
| // Create initial elements | |
| for (let i = 0; i < 5; i++) { | |
| createCloud(Math.random() * window.innerWidth, Math.random() * 200); | |
| } | |
| for (let i = 0; i < 3; i++) { | |
| createCactus(window.innerWidth + (i * 400), Math.random() * 50 + 50); | |
| } | |
| for (let i = 0; i < 2; i++) { | |
| createMountain(window.innerWidth + (i * 600), Math.random() * 100); | |
| } | |
| animationId = requestAnimationFrame(gameLoop); | |
| } | |
| function gameLoop() { | |
| if (isGameOver) return cancelAnimationFrame(animationId); | |
| score += 0.1; | |
| scoreEl.textContent = Math.floor(score); | |
| if (Math.floor(score) % 500 === 0) gameSpeed += 0.5; | |
| updateCarPosition(); | |
| updateObstacles(); | |
| updateClouds(); | |
| updateBirds(); | |
| updateCacti(); | |
| updateMountains(); | |
| updateRoadMarks(); | |
| detectCollisions(); | |
| animationId = requestAnimationFrame(gameLoop); | |
| } | |
| function updateCarPosition() { | |
| if (!isJumping) return; | |
| const jumpDuration = 700; | |
| const t = (Date.now() - jumpStart) / jumpDuration; | |
| if (t >= 1) { | |
| car.style.bottom = '0px'; | |
| isJumping = false; | |
| } else { | |
| const height = Math.round(220 * Math.sin(Math.PI * t)); | |
| car.style.bottom = height + 'px'; | |
| } | |
| } | |
| function updateObstacles() { | |
| if (Math.random() < 0.0025 * gameSpeed) { | |
| if (obstacles.length === 0 || obstacles[obstacles.length - 1].x < window.innerWidth - 400) { | |
| createObstacle(); | |
| } | |
| } | |
| obstacles.forEach((obj, idx) => { | |
| obj.x -= gameSpeed; | |
| obj.el.style.left = obj.x + 'px'; | |
| if (obj.x < -200) { | |
| obj.el.remove(); | |
| obstacles.splice(idx, 1); | |
| } | |
| }); | |
| } | |
| function updateClouds() { | |
| if (Math.random() < 0.002) createCloud(window.innerWidth, Math.random() * 200); | |
| clouds.forEach((c, idx) => { | |
| c.x -= gameSpeed * 0.5; | |
| c.el.style.left = c.x + 'px'; | |
| if (c.x < -100) { | |
| c.el.remove(); | |
| clouds.splice(idx, 1); | |
| } | |
| }); | |
| } | |
| function updateBirds() { | |
| if (Math.random() < 0.0005 * gameSpeed) createBird(window.innerWidth, Math.random() * 150 + 50); | |
| birds.forEach((b, idx) => { | |
| b.x -= gameSpeed * 0.8; | |
| b.el.style.left = b.x + 'px'; | |
| if (b.x < -100) { | |
| b.el.remove(); | |
| birds.splice(idx, 1); | |
| } | |
| }); | |
| } | |
| function updateCacti() { | |
| if (Math.random() < 0.0008 * gameSpeed) createCactus(window.innerWidth, Math.random() * 50 + 50); | |
| cacti.forEach((c, idx) => { | |
| c.x -= gameSpeed * 0.3; | |
| c.el.style.left = c.x + 'px'; | |
| if (c.x < -100) { | |
| c.el.remove(); | |
| cacti.splice(idx, 1); | |
| } | |
| }); | |
| } | |
| function updateMountains() { | |
| if (mountains.length < 2 || mountains[mountains.length - 1].x < window.innerWidth - 400) { | |
| createMountain(window.innerWidth, Math.random() * 100); | |
| } | |
| mountains.forEach((m, idx) => { | |
| m.x -= gameSpeed * 0.2; | |
| m.el.style.left = m.x + 'px'; | |
| if (m.x < -300) { | |
| m.el.remove(); | |
| mountains.splice(idx, 1); | |
| } | |
| }); | |
| } | |
| function updateRoadMarks() { | |
| roadMarkings.forEach(mark => { | |
| mark.x -= gameSpeed; | |
| if (mark.x < -50) mark.x = window.innerWidth; | |
| mark.el.style.left = mark.x + 'px'; | |
| }); | |
| } | |
| function detectCollisions() { | |
| const carRect = car.getBoundingClientRect(); | |
| // Fine-tuned shrink values | |
| const shrinkLeft = 30; | |
| const shrinkRight = 30; | |
| const shrinkTop = 10; | |
| const shrinkBottom = 10; | |
| const carBox = { | |
| left: carRect.left + shrinkLeft, | |
| right: carRect.right - shrinkRight, | |
| top: carRect.top + shrinkTop, | |
| bottom: carRect.bottom - shrinkBottom, | |
| }; | |
| for (let obj of obstacles) { | |
| const r = obj.el.getBoundingClientRect(); | |
| const obstacleBox = { | |
| left: r.left + 20, | |
| right: r.right - 20, | |
| top: r.top + 20, | |
| bottom: r.bottom - 20, | |
| }; | |
| if ( | |
| carBox.left < obstacleBox.right && | |
| carBox.right > obstacleBox.left && | |
| carBox.top < obstacleBox.bottom && | |
| carBox.bottom > obstacleBox.top | |
| ) { | |
| return triggerGameOver(); | |
| } | |
| } | |
| } | |
| function triggerGameOver() { | |
| isGameOver = true; | |
| finalScoreEl.textContent = Math.floor(score); | |
| gameOverEl.style.display = 'block'; | |
| } | |
| function createObstacle() { | |
| const el = document.createElement('div'); | |
| el.className = 'obstacle'; | |
| el.style.left = window.innerWidth + 'px'; | |
| gameContainer.appendChild(el); | |
| obstacles.push({ el, x: window.innerWidth }); | |
| } | |
| function createCloud(x, y) { | |
| const el = document.createElement('div'); | |
| el.className = 'cloud'; | |
| el.style.left = x + 'px'; | |
| el.style.top = y + 'px'; | |
| gameContainer.appendChild(el); | |
| clouds.push({ el, x }); | |
| } | |
| function createBird(x, y) { | |
| const el = document.createElement('div'); | |
| el.className = 'bird'; | |
| el.style.left = x + 'px'; | |
| el.style.top = y + 'px'; | |
| gameContainer.appendChild(el); | |
| birds.push({ el, x }); | |
| } | |
| function createCactus(x, height) { | |
| const el = document.createElement('div'); | |
| el.className = 'cactus'; | |
| el.style.left = x + 'px'; | |
| el.style.height = height + 'px'; | |
| gameContainer.appendChild(el); | |
| cacti.push({ el, x }); | |
| } | |
| function createMountain(x, height) { | |
| const el = document.createElement('div'); | |
| el.className = 'mountain'; | |
| el.style.left = x + 'px'; | |
| el.style.height = (150 + height) + 'px'; | |
| gameContainer.appendChild(el); | |
| mountains.push({ el, x }); | |
| } | |
| function jump() { | |
| if (!gameStarted || isGameOver || isJumping) return; | |
| isJumping = true; | |
| jumpStart = Date.now(); | |
| } | |
| document.addEventListener('keydown', e => { | |
| if ((e.code === 'Space' || e.key === 'ArrowUp') && !gameStarted) return startGame(); | |
| if ((e.code === 'Space' || e.key === 'ArrowUp') && isGameOver) return startGame(); | |
| if ((e.code === 'Space' || e.key === 'ArrowUp')) return jump(); | |
| }); | |
| startButton.addEventListener('click', startGame); | |
| document.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| if (!gameStarted || isGameOver) return startGame(); | |
| jump(); | |
| }); | |
| // Prevent scrolling on mobile when touching the game area | |
| document.addEventListener('touchmove', (e) => { | |
| if (gameStarted) e.preventDefault(); | |
| }, { passive: false }); | |
| </script> | |
| </body> | |
| </html> |