Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tower Defense</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| background: #1a1a1a; | |
| font-family: Arial, sans-serif; | |
| color: white; | |
| } | |
| #gameContainer { | |
| position: relative; | |
| width: 800px; | |
| height: 600px; | |
| background: #2a2a2a; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| #gameCanvas { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| } | |
| .ui-panel { | |
| position: absolute; | |
| right: 0; | |
| top: 0; | |
| width: 200px; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| padding: 10px; | |
| } | |
| .tower-btn { | |
| display: block; | |
| width: 100%; | |
| padding: 10px; | |
| margin: 5px 0; | |
| background: #4a4a4a; | |
| border: none; | |
| border-radius: 5px; | |
| color: white; | |
| cursor: pointer; | |
| transition: background 0.3s; | |
| } | |
| .tower-btn:hover { | |
| background: #5a5a5a; | |
| } | |
| #stats { | |
| position: absolute; | |
| left: 10px; | |
| top: 10px; | |
| font-size: 16px; | |
| text-shadow: 2px 2px 2px rgba(0,0,0,0.5); | |
| z-index: 1; | |
| } | |
| #waveMessage { | |
| position: absolute; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| font-size: 24px; | |
| color: white; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.7); | |
| display: none; | |
| z-index: 2; | |
| } | |
| @keyframes fadeInOut { | |
| 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); } | |
| 10% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); } | |
| 20% { transform: translate(-50%, -50%) scale(1); } | |
| 80% { opacity: 1; } | |
| 100% { opacity: 0; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="gameContainer"> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="stats"> | |
| Lives: <span id="lives">20</span> | |
| Gold: <span id="gold">200</span> | |
| Wave: <span id="wave">1</span> | |
| </div> | |
| <div id="waveMessage"></div> | |
| <div class="ui-panel"> | |
| <button class="tower-btn" data-type="basic">Basic Tower ($100)</button> | |
| <button class="tower-btn" data-type="sniper">Sniper Tower ($200)</button> | |
| <button class="tower-btn" data-type="splash">Splash Tower ($300)</button> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const waveMessage = document.getElementById('waveMessage'); | |
| canvas.width = 600; | |
| canvas.height = 600; | |
| let gameState = { | |
| lives: 20, | |
| gold: 200, | |
| wave: 0, | |
| towers: [], | |
| enemies: [], | |
| path: [], | |
| projectiles: [], | |
| selectedTower: null, | |
| waveInProgress: false, | |
| enemiesSpawned: 0, | |
| totalEnemiesInWave: 10, | |
| spawnInterval: null, | |
| timeBetweenWaves: 5000 | |
| }; | |
| const towerTypes = { | |
| basic: { | |
| cost: 100, | |
| range: 120, | |
| damage: 30, | |
| fireRate: 2, | |
| color: '#4a90e2', | |
| projectileColor: '#4a90e2', | |
| projectileSize: 5 | |
| }, | |
| sniper: { | |
| cost: 200, | |
| range: 250, | |
| damage: 100, | |
| fireRate: 1, | |
| color: '#e24a4a', | |
| projectileColor: '#e24a4a', | |
| projectileSize: 7 | |
| }, | |
| splash: { | |
| cost: 300, | |
| range: 100, | |
| damage: 45, | |
| fireRate: 3, | |
| color: '#4ae24a', | |
| projectileColor: '#4ae24a', | |
| projectileSize: 6 | |
| } | |
| }; | |
| function generatePath() { | |
| const path = []; | |
| let x = 0; | |
| let y = Math.random() * (canvas.height - 200) + 100; | |
| let direction = 1; | |
| while (x < canvas.width - 50) { | |
| path.push({ x, y }); | |
| x += 20; | |
| y += Math.random() * 40 * direction; | |
| direction = -direction; | |
| y = Math.max(50, Math.min(canvas.height - 50, y)); | |
| } | |
| return path; | |
| } | |
| function showWaveMessage(message, duration = 3000) { | |
| waveMessage.textContent = message; | |
| waveMessage.style.display = 'block'; | |
| waveMessage.style.animation = 'fadeInOut 3s ease-in-out'; | |
| setTimeout(() => { | |
| waveMessage.style.display = 'none'; | |
| }, duration); | |
| } | |
| class Enemy { | |
| constructor() { | |
| this.pathIndex = 0; | |
| this.x = gameState.path[0].x; | |
| this.y = gameState.path[0].y; | |
| this.health = 100; | |
| this.maxHealth = 100; | |
| this.speed = 1; | |
| this.value = 20; | |
| } | |
| update() { | |
| if (this.pathIndex < gameState.path.length - 1) { | |
| const target = gameState.path[this.pathIndex + 1]; | |
| const dx = target.x - this.x; | |
| const dy = target.y - this.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < this.speed) { | |
| this.pathIndex++; | |
| } else { | |
| this.x += (dx / distance) * this.speed; | |
| this.y += (dy / distance) * this.speed; | |
| } | |
| } else { | |
| gameState.lives--; | |
| return true; | |
| } | |
| if (this.health <= 0) { | |
| gameState.gold += this.value; | |
| return true; | |
| } | |
| return false; | |
| } | |
| draw() { | |
| ctx.fillStyle = '#ff0000'; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, 10, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#000000'; | |
| ctx.fillRect(this.x - 15, this.y - 20, 30, 5); | |
| ctx.fillStyle = '#00ff00'; | |
| ctx.fillRect(this.x - 15, this.y - 20, (this.health / this.maxHealth) * 30, 5); | |
| } | |
| } | |
| class Projectile { | |
| constructor(x, y, target, damage, color, size, speed = 8) { | |
| this.x = x; | |
| this.y = y; | |
| this.target = target; | |
| this.damage = damage; | |
| this.color = color; | |
| this.size = size; | |
| this.speed = speed; | |
| } | |
| update() { | |
| if (!this.target) return true; | |
| const dx = this.target.x - this.x; | |
| const dy = this.target.y - this.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance < this.speed) { | |
| this.target.health -= this.damage; | |
| return true; | |
| } | |
| this.x += (dx / distance) * this.speed; | |
| this.y += (dy / distance) * this.speed; | |
| return false; | |
| } | |
| draw() { | |
| ctx.fillStyle = this.color; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| class Tower { | |
| constructor(x, y, type) { | |
| this.x = x; | |
| this.y = y; | |
| this.type = type; | |
| this.lastShot = 0; | |
| this.level = 1; | |
| Object.assign(this, towerTypes[type]); | |
| } | |
| update(time) { | |
| if (time - this.lastShot > 1000 / this.fireRate) { | |
| for (let enemy of gameState.enemies) { | |
| const dx = enemy.x - this.x; | |
| const dy = enemy.y - this.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance <= this.range) { | |
| gameState.projectiles.push( | |
| new Projectile( | |
| this.x, | |
| this.y, | |
| enemy, | |
| this.damage * this.level, | |
| this.projectileColor, | |
| this.projectileSize | |
| ) | |
| ); | |
| this.lastShot = time; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| draw() { | |
| ctx.fillStyle = this.color; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, 20, 0, Math.PI * 2); | |
| ctx.fill(); | |
| if (this === gameState.selectedTower) { | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.range, 0, Math.PI * 2); | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| function drawPath() { | |
| ctx.strokeStyle = '#666666'; | |
| ctx.lineWidth = 30; | |
| ctx.lineCap = 'round'; | |
| ctx.lineJoin = 'round'; | |
| ctx.beginPath(); | |
| ctx.moveTo(gameState.path[0].x, gameState.path[0].y); | |
| for (let i = 1; i < gameState.path.length; i++) { | |
| ctx.lineTo(gameState.path[i].x, gameState.path[i].y); | |
| } | |
| ctx.stroke(); | |
| } | |
| function startNewWave() { | |
| gameState.wave++; | |
| gameState.waveInProgress = true; | |
| gameState.enemiesSpawned = 0; | |
| gameState.totalEnemiesInWave = Math.floor(10 + gameState.wave * 2); | |
| gameState.path = generatePath(); | |
| gameState.towers = []; | |
| gameState.projectiles = []; | |
| gameState.enemies = []; | |
| showWaveMessage(`Wave ${gameState.wave} Starting!`); | |
| if(gameState.spawnInterval) clearInterval(gameState.spawnInterval); | |
| setTimeout(() => { | |
| gameState.spawnInterval = setInterval(() => { | |
| if(gameState.enemiesSpawned < gameState.totalEnemiesInWave) { | |
| const enemy = new Enemy(); | |
| enemy.health = 100 + (gameState.wave - 1) * 20; | |
| enemy.maxHealth = enemy.health; | |
| enemy.value = 20 + Math.floor(gameState.wave / 2) * 10; | |
| gameState.enemies.push(enemy); | |
| gameState.enemiesSpawned++; | |
| } else { | |
| clearInterval(gameState.spawnInterval); | |
| } | |
| }, 1000); | |
| }, 2000); | |
| } | |
| function checkWaveStatus() { | |
| if (!gameState.waveInProgress) return; | |
| if (gameState.enemiesSpawned >= gameState.totalEnemiesInWave && | |
| gameState.enemies.length === 0) { | |
| gameState.waveInProgress = false; | |
| const bonus = 100 + gameState.wave * 20; | |
| gameState.gold += bonus; | |
| setTimeout(startNewWave, gameState.timeBetweenWaves); | |
| } | |
| } | |
| function gameLoop(timestamp) { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| drawPath(); | |
| gameState.enemies = gameState.enemies.filter(enemy => !enemy.update()); | |
| gameState.enemies.forEach(enemy => enemy.draw()); | |
| gameState.towers.forEach(tower => { | |
| tower.update(timestamp); | |
| tower.draw(); | |
| }); | |
| gameState.projectiles = gameState.projectiles.filter(proj => !proj.update()); | |
| gameState.projectiles.forEach(proj => proj.draw()); | |
| checkWaveStatus(); | |
| document.getElementById('lives').textContent = gameState.lives; | |
| document.getElementById('gold').textContent = gameState.gold; | |
| document.getElementById('wave').textContent = gameState.wave; | |
| if (gameState.lives <= 0) { | |
| alert('Game Over!'); | |
| return; | |
| } | |
| requestAnimationFrame(gameLoop); | |
| } | |
| canvas.addEventListener('click', (e) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| const x = e.clientX - rect.left; | |
| const y = e.clientY - rect.top; | |
| if (gameState.selectedTower) { | |
| const tower = new Tower(x, y, gameState.selectedTower); | |
| if (gameState.gold >= tower.cost) { | |
| gameState.towers.push(tower); | |
| gameState.gold -= tower.cost; | |
| gameState.selectedTower = null; | |
| } | |
| } | |
| }); | |
| document.querySelectorAll('.tower-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const type = btn.dataset.type; | |
| if (gameState.gold >= towerTypes[type].cost) { | |
| gameState.selectedTower = type; | |
| } | |
| }); | |
| }); | |
| startNewWave(); | |
| requestAnimationFrame(gameLoop); | |
| </script> | |
| </body> | |
| </html> |