// Help menu toggle document.getElementById('closeHelp').addEventListener('click', () => { document.getElementById('helpMenu').classList.add('hidden'); }); // Game constants const CANVAS_WIDTH = 2000; const CANVAS_HEIGHT = 2000; const PLAYER_SIZE = 30; const BULLET_SIZE = 8; const FOOD_SIZE = 10; const MAX_PLAYERS = 50; const MAX_FOOD = 500; const MAX_AI = 20; // Game state let gameState = { players: {}, bullets: [], food: [], aiTanks: [], playerId: null, score: 0, level: 1, upgrades: { health: 0, damage: 0, reload: 0, movement: 0, bulletSpeed: 0, penetration: 0 }, upgradePoints: 0 }; // Canvas setup const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; // Viewport tracking let viewport = { x: 0, y: 0, width: canvas.width, height: canvas.height, target: { x: 0, y: 0 } }; // Game loop function gameLoop() { update(); render(); requestAnimationFrame(gameLoop); } function update() { // Update viewport to follow player if (gameState.playerId && gameState.players[gameState.playerId]) { const player = gameState.players[gameState.playerId]; viewport.target.x = player.x - viewport.width / 2; viewport.target.y = player.y - viewport.height / 2; // Smooth viewport movement viewport.x += (viewport.target.x - viewport.x) * 0.1; viewport.y += (viewport.target.y - viewport.y) * 0.1; } } function render() { // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw grid background drawGrid(); // Draw all game objects relative to viewport drawFood(); drawBullets(); drawPlayers(); drawAI(); // Draw UI drawUI(); } function drawGrid() { const gridSize = 100; const offsetX = -viewport.x % gridSize; const offsetY = -viewport.y % gridSize; // Draw a subtle grid with slightly more visible lines ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)'; ctx.lineWidth = 1.5; // Vertical lines for (let x = offsetX; x < canvas.width; x += gridSize) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } // Horizontal lines for (let y = offsetY; y < canvas.height; y += gridSize) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } } function drawPlayers() { for (const id in gameState.players) { const player = gameState.players[id]; const isCurrentPlayer = id === gameState.playerId; // Calculate screen position const screenX = player.x - viewport.x; const screenY = player.y - viewport.y; // Draw player tank ctx.save(); ctx.translate(screenX, screenY); ctx.rotate(player.angle); // Tank body ctx.fillStyle = isCurrentPlayer ? '#4F46E5' : player.color; ctx.beginPath(); ctx.rect(-PLAYER_SIZE/2, -PLAYER_SIZE/2, PLAYER_SIZE, PLAYER_SIZE); ctx.fill(); // Tank barrel ctx.fillStyle = '#D1D5DB'; ctx.beginPath(); ctx.rect(PLAYER_SIZE/2 - 5, -3, 20, 6); ctx.fill(); ctx.restore(); // Draw player name and health if (isCurrentPlayer || Math.random() < 0.1) { // Only draw names occasionally for performance ctx.fillStyle = '#FFFFFF'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(player.name, screenX, screenY - PLAYER_SIZE - 5); // Health bar const healthWidth = 30; const healthPercent = player.health / player.maxHealth; ctx.fillStyle = '#FF0000'; ctx.fillRect(screenX - healthWidth/2, screenY - PLAYER_SIZE - 15, healthWidth, 3); ctx.fillStyle = '#00FF00'; ctx.fillRect(screenX - healthWidth/2, screenY - PLAYER_SIZE - 15, healthWidth * healthPercent, 3); } } } function drawBullets() { for (const bullet of gameState.bullets) { const screenX = bullet.x - viewport.x; const screenY = bullet.y - viewport.y; ctx.fillStyle = bullet.color; ctx.beginPath(); ctx.arc(screenX, screenY, BULLET_SIZE, 0, Math.PI * 2); ctx.fill(); } } function drawFood() { ctx.shadowBlur = 5; for (const food of gameState.food) { const screenX = food.x - viewport.x; const screenY = food.y - viewport.y; // Only draw food that's visible in viewport if (screenX > -50 && screenX < canvas.width + 50 && screenY > -50 && screenY < canvas.height + 50) { ctx.shadowColor = food.color; ctx.fillStyle = food.color; ctx.beginPath(); ctx.arc(screenX, screenY, food.size, 0, Math.PI * 2); ctx.fill(); } } ctx.shadowBlur = 0; } function drawAI() { // Similar to drawPlayers but with different styling } function drawUI() { // Draw current player stats if (gameState.playerId && gameState.players[gameState.playerId]) { const player = gameState.players[gameState.playerId]; // Mini stats in top left with better visibility ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; ctx.fillRect(10, 10, 180, 100); ctx.strokeStyle = '#4F46E5'; ctx.lineWidth = 2; ctx.strokeRect(10, 10, 180, 100); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 16px Arial'; ctx.textAlign = 'left'; ctx.fillText(`Player: ${player.name}`, 20, 30); ctx.fillText(`Score: ${gameState.score}`, 20, 55); ctx.fillText(`Level: ${gameState.level}`, 20, 80); ctx.fillText(`Upgrades: ${gameState.upgradePoints}`, 20, 105); // Show help prompt if help menu is hidden if (document.getElementById('helpMenu').classList.contains('hidden')) { ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText('Press H for help', canvas.width/2, 30); } } } // Initialize game function initGame() { // Generate initial food with brighter colors for (let i = 0; i < MAX_FOOD; i++) { gameState.food.push({ x: Math.random() * CANVAS_WIDTH, y: Math.random() * CANVAS_HEIGHT, color: `hsl(${Math.random() * 360}, 90%, 60%)`, // More saturated colors value: 1, size: FOOD_SIZE }); } // Generate AI tanks with distinct colors and positions for (let i = 0; i < MAX_AI; i++) { const aiId = 'ai_' + i; gameState.players[aiId] = { x: Math.random() * CANVAS_WIDTH, y: Math.random() * CANVAS_HEIGHT, angle: Math.random() * Math.PI * 2, health: 100, maxHealth: 100, name: 'AI-' + (i+1), color: `hsl(${i * 36}, 90%, 50%)`, // Distinct colors for each AI score: Math.floor(Math.random() * 500) }; } // Start game loop gameLoop(); // Connect to server (simulated) // Create player immediately gameState.playerId = 'player_' + Math.random().toString(36).substr(2, 9); gameState.players[gameState.playerId] = { x: CANVAS_WIDTH / 2, y: CANVAS_HEIGHT / 2, angle: 0, health: 100, maxHealth: 100, name: 'Player' + Math.floor(Math.random() * 1000), color: '#4F46E5', // Consistent blue color for player score: 0 }; // Show upgrade menu when player reaches level 2 if (gameState.level >= 2) { document.getElementById('upgradeMenu').classList.remove('hidden'); } }, 1000); } // Event listeners window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; viewport.width = canvas.width; viewport.height = canvas.height; }); // Keyboard controls const keys = {}; const movementKeys = ['w', 'a', 's', 'd', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']; window.addEventListener('keydown', (e) => { keys[e.key] = true; // Space to shoot if (e.key === ' ' && gameState.playerId) { shootBullet(); } // H to toggle help if (e.key.toLowerCase() === 'h') { const helpMenu = document.getElementById('helpMenu'); if (helpMenu.classList.contains('hidden')) { helpMenu.classList.remove('hidden'); } else { helpMenu.classList.add('hidden'); } } // Movement handling if (gameState.playerId && movementKeys.includes(e.key.toLowerCase())) { handleMovement(); } }); function handleMovement() { if (!gameState.playerId) return; const player = gameState.players[gameState.playerId]; const moveSpeed = 5 + gameState.upgrades.movement * 0.5; if (keys['w'] || keys['ArrowUp']) player.y -= moveSpeed; if (keys['s'] || keys['ArrowDown']) player.y += moveSpeed; if (keys['a'] || keys['ArrowLeft']) player.x -= moveSpeed; if (keys['d'] || keys['ArrowRight']) player.x += moveSpeed; // Boundary checks player.x = Math.max(PLAYER_SIZE/2, Math.min(CANVAS_WIDTH - PLAYER_SIZE/2, player.x)); player.y = Math.max(PLAYER_SIZE/2, Math.min(CANVAS_HEIGHT - PLAYER_SIZE/2, player.y)); } window.addEventListener('keyup', (e) => { keys[e.key] = false; }); // Game loop update to handle continuous movement function update() { // Update viewport to follow player if (gameState.playerId && gameState.players[gameState.playerId]) { handleMovement(); const player = gameState.players[gameState.playerId]; viewport.target.x = player.x - viewport.width / 2; viewport.target.y = player.y - viewport.height / 2; // Smooth viewport movement viewport.x += (viewport.target.x - viewport.x) * 0.1; viewport.y += (viewport.target.y - viewport.y) * 0.1; } } function shootBullet() { if (!gameState.playerId) return; const player = gameState.players[gameState.playerId]; const bulletSpeed = 10 + gameState.upgrades.bulletSpeed * 2; gameState.bullets.push({ x: player.x + Math.cos(player.angle) * (PLAYER_SIZE/2 + 20), y: player.y + Math.sin(player.angle) * (PLAYER_SIZE/2 + 20), dx: Math.cos(player.angle) * bulletSpeed, dy: Math.sin(player.angle) * bulletSpeed, color: player.color, damage: 10 + gameState.upgrades.damage * 2, owner: gameState.playerId, penetration: 1 + gameState.upgrades.penetration }); } // Mouse controls canvas.addEventListener('mousemove', (e) => { if (!gameState.playerId) return; const player = gameState.players[gameState.playerId]; const rect = canvas.getBoundingClientRect(); const mouseX = e.clientX - rect.left + viewport.x; const mouseY = e.clientY - rect.top + viewport.y; player.angle = Math.atan2(mouseY - player.y, mouseX - player.x); }); canvas.addEventListener('click', (e) => { if (gameState.playerId) { shootBullet(); } }); // Upgrade buttons document.querySelectorAll('.upgrade-btn').forEach(btn => { btn.addEventListener('click', () => { const stat = btn.dataset.stat; if (gameState.upgradePoints > 0) { gameState.upgrades[stat]++; gameState.upgradePoints--; updatePlayerStats(); } }); }); function updatePlayerStats() { if (!gameState.playerId) return; const player = gameState.players[gameState.playerId]; player.maxHealth = 100 + gameState.upgrades.health * 20; // Apply other stat upgrades... } // Show initial help for 5 seconds then auto-hide setTimeout(() => { document.getElementById('helpMenu').classList.add('hidden'); }, 5000); // Start the game initGame(); // Simulate multiplayer updates setInterval(() => { if (!gameState.playerId) return; // Simulate other players moving and shooting for (const id in gameState.players) { if (id !== gameState.playerId) { const player = gameState.players[id]; const targetPlayer = gameState.players[gameState.playerId]; if (!targetPlayer) continue; // Move toward player with some randomness const angleToPlayer = Math.atan2( targetPlayer.y - player.y, targetPlayer.x - player.x ); const moveSpeed = 2; player.x += Math.cos(angleToPlayer) * moveSpeed + (Math.random() - 0.5); player.y += Math.sin(angleToPlayer) * moveSpeed + (Math.random() - 0.5); player.angle = angleToPlayer; // Shoot more frequently when close const distance = Math.sqrt( Math.pow(targetPlayer.x - player.x, 2) + Math.pow(targetPlayer.y - player.y, 2) ); if (distance < 300 && Math.random() < 0.05) { shootBullet(id); } } } // Update bullets for (let i = gameState.bullets.length - 1; i >= 0; i--) { const bullet = gameState.bullets[i]; bullet.x += bullet.dx; bullet.y += bullet.dy; // Remove bullets that go out of bounds if (bullet.x < 0 || bullet.x > CANVAS_WIDTH || bullet.y < 0 || bullet.y > CANVAS_HEIGHT) { gameState.bullets.splice(i, 1); continue; } // Check for collisions with players for (const id in gameState.players) { const player = gameState.players[id]; if (id !== bullet.owner) { const dx = player.x - bullet.x; const dy = player.y - bullet.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < PLAYER_SIZE/2 + BULLET_SIZE) { player.health -= bullet.damage; bullet.penetration--; if (bullet.penetration <= 0) { gameState.bullets.splice(i, 1); } if (player.health <= 0) { // Player died if (bullet.owner === gameState.playerId) { gameState.score += 100; checkLevelUp(); } delete gameState.players[id]; } break; } } } } // Check for food collection if (gameState.playerId) { const player = gameState.players[gameState.playerId]; for (let i = gameState.food.length - 1; i >= 0; i--) { const food = gameState.food[i]; const dx = player.x - food.x; const dy = player.y - food.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < PLAYER_SIZE/2 + FOOD_SIZE) { gameState.score += food.value; gameState.food.splice(i, 1); checkLevelUp(); // Add new food to maintain count gameState.food.push({ x: Math.random() * CANVAS_WIDTH, y: Math.random() * CANVAS_HEIGHT, color: `hsl(${Math.random() * 360}, 70%, 60%)`, value: 1 }); } } } // Update leaderboard updateLeaderboard(); }, 1000 / 60); function checkLevelUp() { const needed = gameState.level * 1000; if (gameState.score >= needed) { gameState.level++; gameState.upgradePoints++; if (gameState.level >= 2) { document.getElementById('upgradeMenu').classList.remove('hidden'); } } } function updateLeaderboard() { const leaderboardList = document.getElementById('leaderboardList'); leaderboardList.innerHTML = ''; // Sort players by score const players = Object.values(gameState.players); players.sort((a, b) => (b.score || 0) - (a.score || 0)); // Display top 10 players.slice(0, 10).forEach(player => { const li = document.createElement('li'); li.className = 'flex justify-between'; li.innerHTML = ` ${player.name} ${player.score || 0} `; leaderboardList.appendChild(li); }); } // Simulated shoot function for other players function shootBullet(playerId) { const player = gameState.players[playerId]; if (!player) return; const isPlayer = playerId === gameState.playerId; const baseSpeed = isPlayer ? 10 + gameState.upgrades.bulletSpeed * 2 : 8; const baseDamage = isPlayer ? 10 + gameState.upgrades.damage * 2 : 8; gameState.bullets.push({ x: player.x + Math.cos(player.angle) * (PLAYER_SIZE/2 + 20), y: player.y + Math.sin(player.angle) * (PLAYER_SIZE/2 + 20), dx: Math.cos(player.angle) * baseSpeed, dy: Math.sin(player.angle) * baseSpeed, color: player.color, damage: baseDamage, owner: playerId, penetration: isPlayer ? 1 + gameState.upgrades.penetration : 1 }); } // Initialize with more visible AI tanks function initGame() { // Generate initial food for (let i = 0; i < MAX_FOOD; i++) { gameState.food.push({ x: Math.random() * CANVAS_WIDTH, y: Math.random() * CANVAS_HEIGHT, color: '#00FF00', value: 1 }); } // Generate AI tanks with distinct colors for (let i = 0; i < MAX_AI; i++) { const aiId = 'ai_' + i; gameState.players[aiId] = { x: Math.random() * CANVAS_WIDTH, y: Math.random() * CANVAS_HEIGHT, angle: Math.random() * Math.PI * 2, health: 100, maxHealth: 100, name: 'AI-' + i, color: `hsl(${i * 36}, 80%, 50%)`, score: Math.floor(Math.random() * 500) }; }