Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tron Light Cycles</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); | |
| body { | |
| font-family: 'Orbitron', sans-serif; | |
| background-color: #0f172a; | |
| color: #00ff41; | |
| overflow: hidden; | |
| user-select: none; | |
| } | |
| #gameCanvas { | |
| border: 2px solid #00ff41; | |
| box-shadow: 0 0 20px #00ff41; | |
| background-color: #111827; | |
| } | |
| .glow { | |
| text-shadow: 0 0 10px #00ff41; | |
| } | |
| .trail-1 { | |
| background-color: #3b82f6; | |
| box-shadow: 0 0 10px #3b82f6; | |
| } | |
| .trail-2 { | |
| background-color: #ef4444; | |
| box-shadow: 0 0 10px #ef4444; | |
| } | |
| .power-up { | |
| background-color: #f59e0b; | |
| box-shadow: 0 0 15px #f59e0b; | |
| animation: pulse 1.5s infinite; | |
| border-radius: 50%; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(0.95); opacity: 0.8; } | |
| 50% { transform: scale(1.05); opacity: 1; } | |
| 100% { transform: scale(0.95); opacity: 0.8; } | |
| } | |
| .modal { | |
| background-color: rgba(15, 23, 42, 0.9); | |
| border: 2px solid #00ff41; | |
| box-shadow: 0 0 30px #00ff41; | |
| } | |
| .btn { | |
| transition: all 0.3s; | |
| } | |
| .btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 0 15px #00ff41; | |
| } | |
| .btn:active { | |
| transform: scale(0.95); | |
| } | |
| @keyframes crash { | |
| 0% { transform: scale(1); opacity: 1; } | |
| 50% { transform: scale(1.5); opacity: 0.7; } | |
| 100% { transform: scale(2); opacity: 0; } | |
| } | |
| .crash-animation { | |
| animation: crash 0.5s forwards; | |
| } | |
| .countdown { | |
| font-size: 10rem; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| z-index: 100; | |
| opacity: 0; | |
| animation: countdownFade 1s; | |
| } | |
| @keyframes countdownFade { | |
| 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); } | |
| 50% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); } | |
| 100% { opacity: 0; transform: translate(-50%, -50%) scale(1.5); } | |
| } | |
| </style> | |
| </head> | |
| <body class="flex flex-col items-center justify-center min-h-screen p-4"> | |
| <h1 class="text-4xl md:text-6xl font-bold mb-4 glow">TRON LIGHT CYCLES</h1> | |
| <div class="relative"> | |
| <canvas id="gameCanvas" width="800" height="600" class="mb-4"></canvas> | |
| <div id="countdown" class="countdown absolute hidden"></div> | |
| <div id="startScreen" class="modal absolute inset-0 flex flex-col items-center justify-center p-8"> | |
| <h2 class="text-3xl font-bold mb-6 glow">WELCOME TO THE GRID</h2> | |
| <div class="flex gap-4 mb-8"> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold mb-2">PLAYER 1</h3> | |
| <div class="flex flex-col items-center"> | |
| <div class="w-8 h-8 bg-blue-500 mb-2"></div> | |
| <p>WASD to move</p> | |
| </div> | |
| </div> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold mb-2">PLAYER 2</h3> | |
| <div class="flex flex-col items-center"> | |
| <div class="w-8 h-8 bg-red-500 mb-2"></div> | |
| <p>Arrow keys to move</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex gap-4"> | |
| <button id="startBtn" class="btn px-6 py-3 bg-transparent border-2 border-green-500 text-green-500 font-bold rounded hover:bg-green-500 hover:text-black"> | |
| START GAME | |
| </button> | |
| <button id="aiBtn" class="btn px-6 py-3 bg-transparent border-2 border-yellow-500 text-yellow-500 font-bold rounded hover:bg-yellow-500 hover:text-black"> | |
| VS COMPUTER | |
| </button> | |
| </div> | |
| </div> | |
| <div id="gameOverScreen" class="modal absolute inset-0 hidden flex-col items-center justify-center p-8"> | |
| <h2 id="winnerText" class="text-4xl font-bold mb-6 glow"></h2> | |
| <p id="crashReason" class="text-xl mb-6 text-center max-w-md"></p> | |
| <div class="flex gap-4"> | |
| <button id="restartBtn" class="btn px-6 py-3 bg-transparent border-2 border-green-500 text-green-500 font-bold rounded hover:bg-green-500 hover:text-black"> | |
| PLAY AGAIN | |
| </button> | |
| <button id="menuBtn" class="btn px-6 py-3 bg-transparent border-2 border-blue-500 text-blue-500 font-bold rounded hover:bg-blue-500 hover:text-black"> | |
| MAIN MENU | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex gap-8 mt-4"> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold mb-2">PLAYER 1</h3> | |
| <p id="p1Score" class="text-2xl">0</p> | |
| </div> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold mb-2">PLAYER 2</h3> | |
| <p id="p2Score" class="text-2xl">0</p> | |
| </div> | |
| </div> | |
| <div class="mt-6 text-sm text-gray-400"> | |
| <p>Collect yellow power-ups for temporary speed boost!</p> | |
| </div> | |
| <script> | |
| // Game constants | |
| const GRID_SIZE = 20; | |
| const CELL_SIZE = 20; | |
| const GAME_WIDTH = 800; | |
| const GAME_HEIGHT = 600; | |
| const GRID_COLS = GAME_WIDTH / GRID_SIZE; | |
| const GRID_ROWS = GAME_HEIGHT / GRID_SIZE; | |
| // Game variables | |
| let gameRunning = false; | |
| let gameLoop; | |
| let isAIEnabled = false; | |
| let scores = [0, 0]; | |
| let crashPosition = null; | |
| let crashColor = null; | |
| let crashFrame = 0; | |
| let crashReason = ""; | |
| // Players | |
| const players = [ | |
| { | |
| id: 0, | |
| x: 5, | |
| y: Math.floor(GRID_ROWS / 2), | |
| dx: 1, | |
| dy: 0, | |
| color: '#3b82f6', | |
| trail: [], | |
| speed: 3, // Slower initial speed | |
| boostTimer: 0, | |
| controls: { | |
| up: 'w', | |
| down: 's', | |
| left: 'a', | |
| right: 'd' | |
| }, | |
| keys: { | |
| up: false, | |
| down: false, | |
| left: false, | |
| right: false | |
| } | |
| }, | |
| { | |
| id: 1, | |
| x: GRID_COLS - 5, | |
| y: Math.floor(GRID_ROWS / 2), | |
| dx: -1, | |
| dy: 0, | |
| color: '#ef4444', | |
| trail: [], | |
| speed: 3, // Slower initial speed | |
| boostTimer: 0, | |
| controls: { | |
| up: 'ArrowUp', | |
| down: 'ArrowDown', | |
| left: 'ArrowLeft', | |
| right: 'ArrowRight' | |
| }, | |
| keys: { | |
| up: false, | |
| down: false, | |
| left: false, | |
| right: false | |
| } | |
| } | |
| ]; | |
| // Power-ups | |
| let powerUps = []; | |
| const POWER_UP_DURATION = 300; // frames | |
| const POWER_UP_BOOST = 2; // Reduced boost effect | |
| // DOM elements | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const startScreen = document.getElementById('startScreen'); | |
| const gameOverScreen = document.getElementById('gameOverScreen'); | |
| const winnerText = document.getElementById('winnerText'); | |
| const crashReasonElement = document.getElementById('crashReason'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const aiBtn = document.getElementById('aiBtn'); | |
| const restartBtn = document.getElementById('restartBtn'); | |
| const menuBtn = document.getElementById('menuBtn'); | |
| const p1Score = document.getElementById('p1Score'); | |
| const p2Score = document.getElementById('p2Score'); | |
| const countdownElement = document.getElementById('countdown'); | |
| // Event listeners | |
| startBtn.addEventListener('click', startGame); | |
| aiBtn.addEventListener('click', startAI); | |
| restartBtn.addEventListener('click', restartGame); | |
| menuBtn.addEventListener('click', showMenu); | |
| document.addEventListener('keydown', handleKeyDown); | |
| document.addEventListener('keyup', handleKeyUp); | |
| // Initialize game | |
| function initGame() { | |
| // Reset players | |
| players[0] = { | |
| ...players[0], | |
| x: 5, | |
| y: Math.floor(GRID_ROWS / 2), | |
| dx: 1, | |
| dy: 0, | |
| trail: [], | |
| speed: 3, | |
| boostTimer: 0 | |
| }; | |
| players[1] = { | |
| ...players[1], | |
| x: GRID_COLS - 5, | |
| y: Math.floor(GRID_ROWS / 2), | |
| dx: -1, | |
| dy: 0, | |
| trail: [], | |
| speed: 3, | |
| boostTimer: 0 | |
| }; | |
| // Clear power-ups | |
| powerUps = []; | |
| crashPosition = null; | |
| crashColor = null; | |
| crashFrame = 0; | |
| // Generate initial trails | |
| for (let i = 0; i < 5; i++) { | |
| players[0].trail.push({ | |
| x: players[0].x - i * players[0].dx, | |
| y: players[0].y - i * players[0].dy | |
| }); | |
| players[1].trail.push({ | |
| x: players[1].x - i * players[1].dx, | |
| y: players[1].y - i * players[1].dy | |
| }); | |
| } | |
| // Spawn initial power-ups | |
| spawnPowerUp(); | |
| spawnPowerUp(); | |
| // Draw initial state | |
| draw(); | |
| } | |
| // Start countdown | |
| function startCountdown() { | |
| countdownElement.classList.remove('hidden'); | |
| let count = 3; | |
| countdownElement.textContent = count; | |
| const countdownInterval = setInterval(() => { | |
| count--; | |
| if (count > 0) { | |
| countdownElement.textContent = count; | |
| countdownElement.style.animation = 'none'; | |
| countdownElement.offsetHeight; // Trigger reflow | |
| countdownElement.style.animation = 'countdownFade 1s'; | |
| } else { | |
| countdownElement.textContent = 'GO!'; | |
| countdownElement.style.animation = 'none'; | |
| countdownElement.offsetHeight; // Trigger reflow | |
| countdownElement.style.animation = 'countdownFade 0.5s'; | |
| setTimeout(() => { | |
| countdownElement.classList.add('hidden'); | |
| clearInterval(countdownInterval); | |
| gameRunning = true; | |
| }, 500); | |
| } | |
| }, 1000); | |
| } | |
| // Start game | |
| function startGame() { | |
| isAIEnabled = false; | |
| startScreen.classList.add('hidden'); | |
| initGame(); | |
| // Start countdown | |
| startCountdown(); | |
| // Start game loop | |
| if (gameLoop) clearInterval(gameLoop); | |
| gameLoop = setInterval(update, 1000 / 60); // 60 FPS | |
| } | |
| // Start game vs AI | |
| function startAI() { | |
| isAIEnabled = true; | |
| startScreen.classList.add('hidden'); | |
| initGame(); | |
| // Start countdown | |
| startCountdown(); | |
| // Start game loop | |
| if (gameLoop) clearInterval(gameLoop); | |
| gameLoop = setInterval(update, 1000 / 60); // 60 FPS | |
| } | |
| // Restart game | |
| function restartGame() { | |
| gameOverScreen.classList.add('hidden'); | |
| initGame(); | |
| // Start countdown | |
| startCountdown(); | |
| // Start game loop | |
| if (gameLoop) clearInterval(gameLoop); | |
| gameLoop = setInterval(update, 1000 / 60); // 60 FPS | |
| } | |
| // Show menu | |
| function showMenu() { | |
| gameOverScreen.classList.add('hidden'); | |
| startScreen.classList.remove('hidden'); | |
| gameRunning = false; | |
| if (gameLoop) { | |
| clearInterval(gameLoop); | |
| gameLoop = null; | |
| } | |
| } | |
| // Game over | |
| function gameOver(winner, reason) { | |
| gameRunning = false; | |
| clearInterval(gameLoop); | |
| crashReason = reason; | |
| // Update scores | |
| if (winner === 0) { | |
| scores[0]++; | |
| p1Score.textContent = scores[0]; | |
| winnerText.textContent = 'PLAYER 1 WINS!'; | |
| winnerText.className = 'text-4xl font-bold mb-6 glow text-blue-500'; | |
| } else { | |
| scores[1]++; | |
| p2Score.textContent = scores[1]; | |
| winnerText.textContent = isAIEnabled ? 'COMPUTER WINS!' : 'PLAYER 2 WINS!'; | |
| winnerText.className = 'text-4xl font-bold mb-6 glow text-red-500'; | |
| } | |
| crashReasonElement.textContent = crashReason; | |
| gameOverScreen.classList.remove('hidden'); | |
| } | |
| // Update game state | |
| function update() { | |
| if (!gameRunning) { | |
| draw(); | |
| return; | |
| } | |
| // Move players | |
| players.forEach((player, index) => { | |
| // Handle AI movement for player 2 if AI is enabled | |
| if (isAIEnabled && index === 1) { | |
| aiMovement(player); | |
| } | |
| // Handle player movement | |
| handlePlayerMovement(player); | |
| // Move player | |
| if (frameCount % (15 - player.speed) === 0) { // Adjusted speed calculation | |
| player.x += player.dx; | |
| player.y += player.dy; | |
| // Add new position to trail | |
| player.trail.unshift({ x: player.x, y: player.y }); | |
| // Remove oldest trail segment if not boosting (longer trail when boosting) | |
| if (player.boostTimer <= 0 && player.trail.length > 5) { | |
| player.trail.pop(); | |
| } | |
| // Check collisions | |
| const collision = checkCollisions(player); | |
| if (collision) { | |
| crashPosition = { x: player.x, y: player.y }; | |
| crashColor = player.color; | |
| crashFrame = 0; | |
| gameOver(player.id === 0 ? 1 : 0, collision.reason); | |
| return; | |
| } | |
| // Check power-up collection | |
| checkPowerUps(player); | |
| } | |
| // Update boost timer | |
| if (player.boostTimer > 0) { | |
| player.boostTimer--; | |
| if (player.boostTimer === 0) { | |
| player.speed -= POWER_UP_BOOST; | |
| } | |
| } | |
| }); | |
| // Spawn new power-ups occasionally | |
| if (Math.random() < 0.005 && powerUps.length < 3) { | |
| spawnPowerUp(); | |
| } | |
| // Draw everything | |
| draw(); | |
| frameCount++; | |
| } | |
| let frameCount = 0; | |
| // AI movement logic | |
| function aiMovement(player) { | |
| // Simple AI that avoids walls and tries to trap the player | |
| const directions = [ | |
| { dx: 0, dy: -1 }, // up | |
| { dx: 0, dy: 1 }, // down | |
| { dx: -1, dy: 0 }, // left | |
| { dx: 1, dy: 0 } // right | |
| ]; | |
| // Don't reverse direction | |
| const invalidDirection = { dx: -player.dx, dy: -player.dy }; | |
| // Find safe directions | |
| const safeDirections = directions.filter(dir => { | |
| // Skip invalid direction (reverse) | |
| if (dir.dx === invalidDirection.dx && dir.dy === invalidDirection.dy) { | |
| return false; | |
| } | |
| // Check if next position is safe | |
| const nextX = player.x + dir.dx; | |
| const nextY = player.y + dir.dy; | |
| // Check boundaries | |
| if (nextX < 0 || nextX >= GRID_COLS || nextY < 0 || nextY >= GRID_ROWS) { | |
| return false; | |
| } | |
| // Check trails | |
| for (const p of players) { | |
| for (const segment of p.trail) { | |
| if (segment.x === nextX && segment.y === nextY) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| }); | |
| // If no safe directions, game over is imminent | |
| if (safeDirections.length === 0) return; | |
| // Choose a random safe direction (could be enhanced with more sophisticated AI) | |
| const randomIndex = Math.floor(Math.random() * safeDirections.length); | |
| const newDir = safeDirections[randomIndex]; | |
| player.dx = newDir.dx; | |
| player.dy = newDir.dy; | |
| } | |
| // Handle player movement based on key presses | |
| function handlePlayerMovement(player) { | |
| // Change direction based on key presses, but prevent 180-degree turns | |
| if (player.keys.up && player.dy !== 1) { | |
| player.dx = 0; | |
| player.dy = -1; | |
| } else if (player.keys.down && player.dy !== -1) { | |
| player.dx = 0; | |
| player.dy = 1; | |
| } else if (player.keys.left && player.dx !== 1) { | |
| player.dx = -1; | |
| player.dy = 0; | |
| } else if (player.keys.right && player.dx !== -1) { | |
| player.dx = 1; | |
| player.dy = 0; | |
| } | |
| } | |
| // Check for collisions | |
| function checkCollisions(player) { | |
| // Check wall collisions | |
| if (player.x < 0) { | |
| return { reason: "Player " + (player.id + 1) + " crashed into the left wall!" }; | |
| } | |
| if (player.x >= GRID_COLS) { | |
| return { reason: "Player " + (player.id + 1) + " crashed into the right wall!" }; | |
| } | |
| if (player.y < 0) { | |
| return { reason: "Player " + (player.id + 1) + " crashed into the top wall!" }; | |
| } | |
| if (player.y >= GRID_ROWS) { | |
| return { reason: "Player " + (player.id + 1) + " crashed into the bottom wall!" }; | |
| } | |
| // Check trail collisions (including own trail) | |
| for (const p of players) { | |
| // Skip the head of the current player | |
| const startIndex = p === player ? 1 : 0; | |
| for (let i = startIndex; i < p.trail.length; i++) { | |
| const segment = p.trail[i]; | |
| if (segment.x === player.x && segment.y === player.y) { | |
| if (p === player) { | |
| return { reason: "Player " + (player.id + 1) + " crashed into their own trail!" }; | |
| } else { | |
| return { reason: "Player " + (player.id + 1) + " crashed into " + | |
| (p.id === 0 ? "Player 1" : isAIEnabled ? "the Computer" : "Player 2") + "'s trail!" }; | |
| } | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| // Check power-up collection | |
| function checkPowerUps(player) { | |
| for (let i = powerUps.length - 1; i >= 0; i--) { | |
| const powerUp = powerUps[i]; | |
| if (powerUp.x === player.x && powerUp.y === player.y) { | |
| // Apply speed boost | |
| player.speed += POWER_UP_BOOST; | |
| player.boostTimer = POWER_UP_DURATION; | |
| // Remove power-up | |
| powerUps.splice(i, 1); | |
| // Spawn new power-up | |
| spawnPowerUp(); | |
| } | |
| } | |
| } | |
| // Spawn a new power-up | |
| function spawnPowerUp() { | |
| let x, y; | |
| let validPosition = false; | |
| // Keep trying until we find a valid position | |
| while (!validPosition) { | |
| x = Math.floor(Math.random() * GRID_COLS); | |
| y = Math.floor(Math.random() * GRID_ROWS); | |
| validPosition = true; | |
| // Check if position is on any trail | |
| for (const player of players) { | |
| for (const segment of player.trail) { | |
| if (segment.x === x && segment.y === y) { | |
| validPosition = false; | |
| break; | |
| } | |
| } | |
| if (!validPosition) break; | |
| } | |
| // Check if position is on another power-up | |
| for (const powerUp of powerUps) { | |
| if (powerUp.x === x && powerUp.y === y) { | |
| validPosition = false; | |
| break; | |
| } | |
| } | |
| } | |
| powerUps.push({ x, y }); | |
| } | |
| // Draw game | |
| function draw() { | |
| // Clear canvas | |
| ctx.fillStyle = '#111827'; | |
| ctx.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); | |
| // Draw grid lines | |
| ctx.strokeStyle = '#1e293b'; | |
| ctx.lineWidth = 1; | |
| for (let x = 0; x <= GAME_WIDTH; x += GRID_SIZE) { | |
| ctx.beginPath(); | |
| ctx.moveTo(x, 0); | |
| ctx.lineTo(x, GAME_HEIGHT); | |
| ctx.stroke(); | |
| } | |
| for (let y = 0; y <= GAME_HEIGHT; y += GRID_SIZE) { | |
| ctx.beginPath(); | |
| ctx.moveTo(0, y); | |
| ctx.lineTo(GAME_WIDTH, y); | |
| ctx.stroke(); | |
| } | |
| // Draw power-ups | |
| powerUps.forEach(powerUp => { | |
| ctx.fillStyle = '#f59e0b'; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| powerUp.x * GRID_SIZE + GRID_SIZE / 2, | |
| powerUp.y * GRID_SIZE + GRID_SIZE / 2, | |
| GRID_SIZE / 2 - 2, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fill(); | |
| }); | |
| // Draw trails | |
| players.forEach(player => { | |
| // Draw trail | |
| player.trail.forEach((segment, index) => { | |
| const alpha = index === 0 ? 1 : 0.7 - (index / player.trail.length) * 0.5; | |
| ctx.fillStyle = player.color; | |
| ctx.globalAlpha = alpha; | |
| ctx.fillRect( | |
| segment.x * GRID_SIZE, | |
| segment.y * GRID_SIZE, | |
| GRID_SIZE, | |
| GRID_SIZE | |
| ); | |
| }); | |
| // Reset alpha | |
| ctx.globalAlpha = 1; | |
| // Draw head (with different shape) | |
| ctx.fillStyle = player.color; | |
| ctx.beginPath(); | |
| // Draw a bike-like shape based on direction | |
| const centerX = player.x * GRID_SIZE + GRID_SIZE / 2; | |
| const centerY = player.y * GRID_SIZE + GRID_SIZE / 2; | |
| if (player.dx === 1) { // right | |
| ctx.moveTo(centerX - GRID_SIZE / 3, centerY - GRID_SIZE / 2); | |
| ctx.lineTo(centerX + GRID_SIZE / 2, centerY); | |
| ctx.lineTo(centerX - GRID_SIZE / 3, centerY + GRID_SIZE / 2); | |
| } else if (player.dx === -1) { // left | |
| ctx.moveTo(centerX + GRID_SIZE / 3, centerY - GRID_SIZE / 2); | |
| ctx.lineTo(centerX - GRID_SIZE / 2, centerY); | |
| ctx.lineTo(centerX + GRID_SIZE / 3, centerY + GRID_SIZE / 2); | |
| } else if (player.dy === 1) { // down | |
| ctx.moveTo(centerX - GRID_SIZE / 2, centerY - GRID_SIZE / 3); | |
| ctx.lineTo(centerX, centerY + GRID_SIZE / 2); | |
| ctx.lineTo(centerX + GRID_SIZE / 2, centerY - GRID_SIZE / 3); | |
| } else { // up | |
| ctx.moveTo(centerX - GRID_SIZE / 2, centerY + GRID_SIZE / 3); | |
| ctx.lineTo(centerX, centerY - GRID_SIZE / 2); | |
| ctx.lineTo(centerX + GRID_SIZE / 2, centerY + GRID_SIZE / 3); | |
| } | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Draw boost effect if active | |
| if (player.boostTimer > 0) { | |
| ctx.strokeStyle = player.color; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| centerX, | |
| centerY, | |
| GRID_SIZE / 1.5, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.stroke(); | |
| } | |
| }); | |
| // Draw crash animation if active | |
| if (crashPosition && crashFrame < 30) { | |
| const progress = crashFrame / 30; | |
| const size = GRID_SIZE * (1 + progress * 3); | |
| const alpha = 1 - progress; | |
| ctx.fillStyle = crashColor; | |
| ctx.globalAlpha = alpha; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| crashPosition.x * GRID_SIZE + GRID_SIZE / 2, | |
| crashPosition.y * GRID_SIZE + GRID_SIZE / 2, | |
| size / 2, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fill(); | |
| ctx.globalAlpha = 1; | |
| crashFrame++; | |
| } | |
| } | |
| // Handle key down events | |
| function handleKeyDown(e) { | |
| players.forEach(player => { | |
| if (e.key.toLowerCase() === player.controls.up.toLowerCase()) { | |
| player.keys.up = true; | |
| } | |
| if (e.key.toLowerCase() === player.controls.down.toLowerCase()) { | |
| player.keys.down = true; | |
| } | |
| if (e.key.toLowerCase() === player.controls.left.toLowerCase()) { | |
| player.keys.left = true; | |
| } | |
| if (e.key.toLowerCase() === player.controls.right.toLowerCase()) { | |
| player.keys.right = true; | |
| } | |
| }); | |
| // Space to pause | |
| if (e.key === ' ' && gameRunning) { | |
| gameRunning = !gameRunning; | |
| if (gameRunning) { | |
| gameLoop = setInterval(update, 1000 / 60); | |
| } else { | |
| clearInterval(gameLoop); | |
| } | |
| } | |
| } | |
| // Handle key up events | |
| function handleKeyUp(e) { | |
| players.forEach(player => { | |
| if (e.key.toLowerCase() === player.controls.up.toLowerCase()) { | |
| player.keys.up = false; | |
| } | |
| if (e.key.toLowerCase() === player.controls.down.toLowerCase()) { | |
| player.keys.down = false; | |
| } | |
| if (e.key.toLowerCase() === player.controls.left.toLowerCase()) { | |
| player.keys.left = false; | |
| } | |
| if (e.key.toLowerCase() === player.controls.right.toLowerCase()) { | |
| player.keys.right = false; | |
| } | |
| }); | |
| } | |
| // Initial draw | |
| draw(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Asher18471/games-folder" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |