|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Fish.io - Territory Game</title> |
|
|
<style> |
|
|
body { |
|
|
margin: 0; |
|
|
padding: 20px; |
|
|
background: linear-gradient(180deg, #001133 0%, #003366 50%, #004080 100%); |
|
|
font-family: 'Arial', sans-serif; |
|
|
color: white; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.game-container { |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
min-height: 100vh; |
|
|
flex-direction: column; |
|
|
} |
|
|
|
|
|
.ui-panel { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
width: 800px; |
|
|
margin-bottom: 10px; |
|
|
font-size: 18px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.score-info { |
|
|
background: rgba(0, 50, 100, 0.8); |
|
|
padding: 10px 20px; |
|
|
border-radius: 10px; |
|
|
border: 2px solid #00ccff; |
|
|
} |
|
|
|
|
|
#gameCanvas { |
|
|
border: 3px solid #00ccff; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 0 30px rgba(0, 204, 255, 0.5); |
|
|
background: linear-gradient(45deg, #001a33, #002d5a); |
|
|
} |
|
|
|
|
|
.menu-overlay { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: rgba(0, 20, 40, 0.95); |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.menu-title { |
|
|
font-size: 48px; |
|
|
font-weight: bold; |
|
|
color: #00ccff; |
|
|
text-shadow: 0 0 20px #00ccff; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.menu-subtitle { |
|
|
font-size: 20px; |
|
|
margin-bottom: 30px; |
|
|
text-align: center; |
|
|
line-height: 1.5; |
|
|
} |
|
|
|
|
|
.menu-button { |
|
|
background: linear-gradient(45deg, #0066cc, #00ccff); |
|
|
border: none; |
|
|
padding: 15px 40px; |
|
|
font-size: 20px; |
|
|
font-weight: bold; |
|
|
color: white; |
|
|
border-radius: 25px; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
margin: 10px; |
|
|
} |
|
|
|
|
|
.menu-button:hover { |
|
|
transform: scale(1.1); |
|
|
box-shadow: 0 0 20px #00ccff; |
|
|
} |
|
|
|
|
|
.controls { |
|
|
font-size: 16px; |
|
|
margin-top: 20px; |
|
|
text-align: center; |
|
|
opacity: 0.8; |
|
|
} |
|
|
|
|
|
.how-to-play { |
|
|
background: rgba(0, 50, 100, 0.9); |
|
|
border: 2px solid #00ccff; |
|
|
border-radius: 15px; |
|
|
padding: 20px; |
|
|
margin: 20px; |
|
|
max-width: 600px; |
|
|
text-align: left; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
.how-to-play h3 { |
|
|
color: #00ccff; |
|
|
text-align: center; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
|
|
|
.how-to-play ul { |
|
|
margin: 10px 0; |
|
|
padding-left: 20px; |
|
|
} |
|
|
|
|
|
.how-to-play li { |
|
|
margin: 5px 0; |
|
|
} |
|
|
|
|
|
.hidden { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.game-over-stats { |
|
|
font-size: 18px; |
|
|
margin: 20px 0; |
|
|
text-align: center; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="game-container"> |
|
|
<div class="ui-panel"> |
|
|
<div class="score-info"> |
|
|
<div>Territory: <span id="territoryScore">0</span>%</div> |
|
|
</div> |
|
|
<div class="score-info"> |
|
|
<div>Time: <span id="timeScore">0</span>s</div> |
|
|
</div> |
|
|
<div class="score-info"> |
|
|
<div>Enemies: <span id="enemyCount">0</span></div> |
|
|
</div> |
|
|
<div class="score-info"> |
|
|
<div>Kills: <span id="killCount">0</span></div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<canvas id="gameCanvas" width="800" height="600"></canvas> |
|
|
|
|
|
<div id="startMenu" class="menu-overlay"> |
|
|
<div class="menu-title">🐠 FISH.IO 🐠</div> |
|
|
<div class="menu-subtitle"> |
|
|
Control your fish to claim territory and defend yourself! |
|
|
</div> |
|
|
|
|
|
<div class="how-to-play"> |
|
|
<h3>🎯 HOW TO PLAY</h3> |
|
|
<strong>🏊 Movement:</strong> |
|
|
<ul> |
|
|
<li>Use ARROW KEYS or WASD to move your fish</li> |
|
|
<li>Stay in blue territory (safe zone) or venture out to expand</li> |
|
|
</ul> |
|
|
|
|
|
<strong>🏆 Territory Claiming:</strong> |
|
|
<ul> |
|
|
<li>Leave your territory to create a yellow trail</li> |
|
|
<li>Return to your territory to claim the enclosed area</li> |
|
|
<li>Larger territory = higher score</li> |
|
|
</ul> |
|
|
|
|
|
<strong>⚔️ Combat System:</strong> |
|
|
<ul> |
|
|
<li>Your fish auto-fires at nearby enemies</li> |
|
|
<li>Eliminate enemies to reduce threats</li> |
|
|
<li>Each kill doubles the enemy count - be strategic!</li> |
|
|
</ul> |
|
|
|
|
|
<strong>⚠️ Dangers:</strong> |
|
|
<ul> |
|
|
<li>Enemy fish can kill you on contact</li> |
|
|
<li>Your trail is vulnerable when outside territory</li> |
|
|
<li>More enemies spawn as you progress</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<button class="menu-button" onclick="startGame()">START GAME</button> |
|
|
</div> |
|
|
|
|
|
<div id="gameOverMenu" class="menu-overlay hidden"> |
|
|
<div class="menu-title">GAME OVER</div> |
|
|
<div class="game-over-stats" id="finalStats"></div> |
|
|
<button class="menu-button" onclick="restartGame()">🔄 RESTART GAME</button> |
|
|
<button class="menu-button" onclick="showStartMenu()">🏠 MAIN MENU</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const canvas = document.getElementById('gameCanvas'); |
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
|
|
|
let audioContext; |
|
|
let backgroundMusic; |
|
|
let gameRunning = false; |
|
|
|
|
|
|
|
|
let player = { |
|
|
x: 100, |
|
|
y: 300, |
|
|
size: 15, |
|
|
speed: 3, |
|
|
color: '#00ccff', |
|
|
trail: [], |
|
|
inTerritory: true, |
|
|
fireRate: 500, |
|
|
lastShot: 0, |
|
|
range: 100 |
|
|
}; |
|
|
|
|
|
let enemies = []; |
|
|
let bullets = []; |
|
|
let territory = new Set(); |
|
|
let claimedArea = new Set(); |
|
|
let gameTime = 0; |
|
|
let level = 1; |
|
|
let lastTime = 0; |
|
|
let killCount = 0; |
|
|
|
|
|
|
|
|
for(let x = 80; x <= 120; x += 2) { |
|
|
for(let y = 280; y <= 320; y += 2) { |
|
|
territory.add(`${x},${y}`); |
|
|
claimedArea.add(`${x},${y}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const keys = {}; |
|
|
document.addEventListener('keydown', (e) => { |
|
|
keys[e.key.toLowerCase()] = true; |
|
|
}); |
|
|
document.addEventListener('keyup', (e) => { |
|
|
keys[e.key.toLowerCase()] = false; |
|
|
}); |
|
|
|
|
|
|
|
|
function initAudio() { |
|
|
if (!audioContext) { |
|
|
audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
|
|
createBackgroundMusic(); |
|
|
} |
|
|
} |
|
|
|
|
|
function createBackgroundMusic() { |
|
|
|
|
|
const oscillator1 = audioContext.createOscillator(); |
|
|
const oscillator2 = audioContext.createOscillator(); |
|
|
const gainNode = audioContext.createGain(); |
|
|
const filter = audioContext.createBiquadFilter(); |
|
|
|
|
|
oscillator1.type = 'sine'; |
|
|
oscillator2.type = 'sine'; |
|
|
oscillator1.frequency.setValueAtTime(220, audioContext.currentTime); |
|
|
oscillator2.frequency.setValueAtTime(165, audioContext.currentTime); |
|
|
|
|
|
filter.type = 'lowpass'; |
|
|
filter.frequency.setValueAtTime(800, audioContext.currentTime); |
|
|
|
|
|
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); |
|
|
|
|
|
oscillator1.connect(filter); |
|
|
oscillator2.connect(filter); |
|
|
filter.connect(gainNode); |
|
|
gainNode.connect(audioContext.destination); |
|
|
|
|
|
|
|
|
setInterval(() => { |
|
|
if (gameRunning && audioContext) { |
|
|
const freq1 = 220 + Math.sin(Date.now() * 0.001) * 30; |
|
|
const freq2 = 165 + Math.cos(Date.now() * 0.0007) * 20; |
|
|
oscillator1.frequency.setValueAtTime(freq1, audioContext.currentTime); |
|
|
oscillator2.frequency.setValueAtTime(freq2, audioContext.currentTime); |
|
|
} |
|
|
}, 100); |
|
|
|
|
|
oscillator1.start(); |
|
|
oscillator2.start(); |
|
|
backgroundMusic = { oscillator1, oscillator2, gainNode }; |
|
|
} |
|
|
|
|
|
function startGame() { |
|
|
document.getElementById('startMenu').classList.add('hidden'); |
|
|
gameRunning = true; |
|
|
initAudio(); |
|
|
resetGame(); |
|
|
gameLoop(); |
|
|
} |
|
|
|
|
|
function resetGame() { |
|
|
player = { |
|
|
x: 100, |
|
|
y: 300, |
|
|
size: 15, |
|
|
speed: 3, |
|
|
color: '#00ccff', |
|
|
trail: [], |
|
|
inTerritory: true, |
|
|
fireRate: 500, |
|
|
lastShot: 0, |
|
|
range: 100 |
|
|
}; |
|
|
|
|
|
enemies = []; |
|
|
bullets = []; |
|
|
territory = new Set(); |
|
|
claimedArea = new Set(); |
|
|
gameTime = 0; |
|
|
level = 1; |
|
|
killCount = 0; |
|
|
|
|
|
|
|
|
for(let x = 80; x <= 120; x += 2) { |
|
|
for(let y = 280; y <= 320; y += 2) { |
|
|
territory.add(`${x},${y}`); |
|
|
claimedArea.add(`${x},${y}`); |
|
|
} |
|
|
} |
|
|
|
|
|
spawnEnemies(); |
|
|
} |
|
|
|
|
|
function spawnEnemies() { |
|
|
|
|
|
const newEnemyCount = Math.max(1, 2 + Math.floor(level * 0.5)); |
|
|
|
|
|
for(let i = 0; i < newEnemyCount; i++) { |
|
|
let x, y; |
|
|
do { |
|
|
x = Math.random() * (canvas.width - 60) + 30; |
|
|
y = Math.random() * (canvas.height - 60) + 30; |
|
|
} while(Math.abs(x - player.x) < 150 || Math.abs(y - player.y) < 150); |
|
|
|
|
|
enemies.push({ |
|
|
x: x, |
|
|
y: y, |
|
|
size: 12 + Math.random() * 8, |
|
|
speed: 1.5 + Math.random() * 1.5 + level * 0.2, |
|
|
color: `hsl(${Math.random() * 360}, 70%, 60%)`, |
|
|
direction: Math.random() * Math.PI * 2, |
|
|
changeDirection: 0, |
|
|
health: 1 |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
function gameOver() { |
|
|
gameRunning = false; |
|
|
if (backgroundMusic && audioContext) { |
|
|
backgroundMusic.gainNode.gain.setValueAtTime(0, audioContext.currentTime); |
|
|
} |
|
|
|
|
|
const territoryPercent = Math.floor((claimedArea.size / (canvas.width * canvas.height / 4)) * 100); |
|
|
document.getElementById('finalStats').innerHTML = ` |
|
|
Territory Claimed: ${territoryPercent}%<br> |
|
|
Time Survived: ${gameTime}s<br> |
|
|
Enemies Defeated: ${killCount}<br> |
|
|
Level Reached: ${level} |
|
|
`; |
|
|
document.getElementById('gameOverMenu').classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function restartGame() { |
|
|
document.getElementById('gameOverMenu').classList.add('hidden'); |
|
|
gameRunning = true; |
|
|
if (backgroundMusic && audioContext) { |
|
|
backgroundMusic.gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); |
|
|
} |
|
|
resetGame(); |
|
|
gameLoop(); |
|
|
} |
|
|
|
|
|
function showStartMenu() { |
|
|
document.getElementById('gameOverMenu').classList.add('hidden'); |
|
|
document.getElementById('startMenu').classList.remove('hidden'); |
|
|
if (backgroundMusic && audioContext) { |
|
|
backgroundMusic.gainNode.gain.setValueAtTime(0, audioContext.currentTime); |
|
|
} |
|
|
} |
|
|
|
|
|
function updatePlayer() { |
|
|
|
|
|
if (keys['arrowup'] || keys['w']) player.y -= player.speed; |
|
|
if (keys['arrowdown'] || keys['s']) player.y += player.speed; |
|
|
if (keys['arrowleft'] || keys['a']) player.x -= player.speed; |
|
|
if (keys['arrowright'] || keys['d']) player.x += player.speed; |
|
|
|
|
|
|
|
|
player.x = Math.max(player.size, Math.min(canvas.width - player.size, player.x)); |
|
|
player.y = Math.max(player.size, Math.min(canvas.height - player.size, player.y)); |
|
|
|
|
|
|
|
|
const gridX = Math.floor(player.x / 2) * 2; |
|
|
const gridY = Math.floor(player.y / 2) * 2; |
|
|
player.inTerritory = territory.has(`${gridX},${gridY}`); |
|
|
|
|
|
|
|
|
if (!player.inTerritory) { |
|
|
player.trail.push({x: player.x, y: player.y}); |
|
|
if (player.trail.length > 100) { |
|
|
player.trail.shift(); |
|
|
} |
|
|
} else if (player.trail.length > 0) { |
|
|
|
|
|
claimTerritory(); |
|
|
player.trail = []; |
|
|
} |
|
|
|
|
|
|
|
|
autoFire(); |
|
|
} |
|
|
|
|
|
function autoFire() { |
|
|
const currentTime = Date.now(); |
|
|
if (currentTime - player.lastShot < player.fireRate) return; |
|
|
|
|
|
|
|
|
let nearestEnemy = null; |
|
|
let nearestDistance = player.range; |
|
|
|
|
|
for(let enemy of enemies) { |
|
|
const dx = enemy.x - player.x; |
|
|
const dy = enemy.y - player.y; |
|
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
|
if (distance < nearestDistance) { |
|
|
nearestEnemy = enemy; |
|
|
nearestDistance = distance; |
|
|
} |
|
|
} |
|
|
|
|
|
if (nearestEnemy) { |
|
|
|
|
|
const dx = nearestEnemy.x - player.x; |
|
|
const dy = nearestEnemy.y - player.y; |
|
|
const angle = Math.atan2(dy, dx); |
|
|
|
|
|
|
|
|
bullets.push({ |
|
|
x: player.x, |
|
|
y: player.y, |
|
|
dx: Math.cos(angle) * 8, |
|
|
dy: Math.sin(angle) * 8, |
|
|
size: 3, |
|
|
life: 60 |
|
|
}); |
|
|
|
|
|
player.lastShot = currentTime; |
|
|
} |
|
|
} |
|
|
|
|
|
function updateBullets() { |
|
|
for(let i = bullets.length - 1; i >= 0; i--) { |
|
|
const bullet = bullets[i]; |
|
|
bullet.x += bullet.dx; |
|
|
bullet.y += bullet.dy; |
|
|
bullet.life--; |
|
|
|
|
|
|
|
|
if (bullet.x < 0 || bullet.x > canvas.width || |
|
|
bullet.y < 0 || bullet.y > canvas.height || |
|
|
bullet.life <= 0) { |
|
|
bullets.splice(i, 1); |
|
|
continue; |
|
|
} |
|
|
|
|
|
|
|
|
for(let j = enemies.length - 1; j >= 0; j--) { |
|
|
const enemy = enemies[j]; |
|
|
const dx = bullet.x - enemy.x; |
|
|
const dy = bullet.y - enemy.y; |
|
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
|
if (distance < bullet.size + enemy.size) { |
|
|
|
|
|
enemy.health--; |
|
|
bullets.splice(i, 1); |
|
|
|
|
|
if (enemy.health <= 0) { |
|
|
|
|
|
killCount++; |
|
|
const defeatedEnemy = enemies.splice(j, 1)[0]; |
|
|
|
|
|
|
|
|
for(let k = 0; k < 2; k++) { |
|
|
let x, y; |
|
|
do { |
|
|
x = Math.random() * (canvas.width - 60) + 30; |
|
|
y = Math.random() * (canvas.height - 60) + 30; |
|
|
} while(Math.abs(x - player.x) < 100); |
|
|
|
|
|
enemies.push({ |
|
|
x: x, |
|
|
y: y, |
|
|
size: 10 + Math.random() * 6, |
|
|
speed: 1.2 + Math.random() * 1.8 + level * 0.15, |
|
|
color: `hsl(${Math.random() * 360}, 70%, 60%)`, |
|
|
direction: Math.random() * Math.PI * 2, |
|
|
changeDirection: 0, |
|
|
health: 1 |
|
|
}); |
|
|
} |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function claimTerritory() { |
|
|
|
|
|
const newClaimed = new Set(); |
|
|
|
|
|
|
|
|
for(let point of player.trail) { |
|
|
const gridX = Math.floor(point.x / 2) * 2; |
|
|
const gridY = Math.floor(point.y / 2) * 2; |
|
|
territory.add(`${gridX},${gridY}`); |
|
|
newClaimed.add(`${gridX},${gridY}`); |
|
|
} |
|
|
|
|
|
|
|
|
for(let point of player.trail) { |
|
|
const startX = Math.floor(point.x / 2) * 2; |
|
|
const startY = Math.floor(point.y / 2) * 2; |
|
|
|
|
|
|
|
|
for(let dx = -20; dx <= 20; dx += 2) { |
|
|
for(let dy = -20; dy <= 20; dy += 2) { |
|
|
const x = startX + dx; |
|
|
const y = startY + dy; |
|
|
if (x >= 0 && x < canvas.width && y >= 0 && y < canvas.height) { |
|
|
territory.add(`${x},${y}`); |
|
|
claimedArea.add(`${x},${y}`); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function updateEnemies() { |
|
|
for(let enemy of enemies) { |
|
|
|
|
|
enemy.changeDirection--; |
|
|
if (enemy.changeDirection <= 0) { |
|
|
enemy.direction = Math.random() * Math.PI * 2; |
|
|
enemy.changeDirection = 60 + Math.random() * 120; |
|
|
} |
|
|
|
|
|
enemy.x += Math.cos(enemy.direction) * enemy.speed; |
|
|
enemy.y += Math.sin(enemy.direction) * enemy.speed; |
|
|
|
|
|
|
|
|
if (enemy.x <= 0 || enemy.x >= canvas.width) { |
|
|
enemy.direction = Math.PI - enemy.direction; |
|
|
enemy.x = Math.max(0, Math.min(canvas.width, enemy.x)); |
|
|
} |
|
|
if (enemy.y <= 0 || enemy.y >= canvas.height) { |
|
|
enemy.direction = -enemy.direction; |
|
|
enemy.y = Math.max(0, Math.min(canvas.height, enemy.y)); |
|
|
} |
|
|
|
|
|
|
|
|
const dx = enemy.x - player.x; |
|
|
const dy = enemy.y - player.y; |
|
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
|
if (distance < enemy.size + player.size) { |
|
|
gameOver(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
for(let point of player.trail) { |
|
|
const trailDx = enemy.x - point.x; |
|
|
const trailDy = enemy.y - point.y; |
|
|
const trailDistance = Math.sqrt(trailDx * trailDx + trailDy * trailDy); |
|
|
if (trailDistance < enemy.size + 5) { |
|
|
gameOver(); |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function render() { |
|
|
|
|
|
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); |
|
|
gradient.addColorStop(0, '#001a33'); |
|
|
gradient.addColorStop(1, '#002d5a'); |
|
|
ctx.fillStyle = gradient; |
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
|
for(let i = 0; i < 20; i++) { |
|
|
const x = (Date.now() * 0.01 + i * 50) % canvas.width; |
|
|
const y = (Date.now() * 0.02 + i * 30) % canvas.height; |
|
|
ctx.fillStyle = 'rgba(100, 200, 255, 0.1)'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(x, y, 3 + Math.sin(Date.now() * 0.01 + i) * 2, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} |
|
|
|
|
|
|
|
|
ctx.fillStyle = 'rgba(0, 204, 255, 0.3)'; |
|
|
for(let pos of claimedArea) { |
|
|
const [x, y] = pos.split(',').map(Number); |
|
|
ctx.fillRect(x, y, 2, 2); |
|
|
} |
|
|
|
|
|
|
|
|
if (player.trail.length > 0) { |
|
|
ctx.strokeStyle = '#ffff00'; |
|
|
ctx.lineWidth = 3; |
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(player.trail[0].x, player.trail[0].y); |
|
|
for(let i = 1; i < player.trail.length; i++) { |
|
|
ctx.lineTo(player.trail[i].x, player.trail[i].y); |
|
|
} |
|
|
ctx.stroke(); |
|
|
} |
|
|
|
|
|
|
|
|
ctx.fillStyle = '#ffff00'; |
|
|
for(let bullet of bullets) { |
|
|
ctx.beginPath(); |
|
|
ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.shadowColor = '#ffff00'; |
|
|
ctx.shadowBlur = 10; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(bullet.x, bullet.y, bullet.size, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
ctx.shadowBlur = 0; |
|
|
} |
|
|
for(let enemy of enemies) { |
|
|
ctx.fillStyle = enemy.color; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(enemy.x, enemy.y, enemy.size, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.fillStyle = 'white'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(enemy.x - enemy.size * 0.3, enemy.y - enemy.size * 0.3, enemy.size * 0.2, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
} |
|
|
|
|
|
|
|
|
ctx.fillStyle = player.inTerritory ? player.color : '#ffff00'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(player.x, player.y, player.size, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.fillStyle = 'white'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(player.x - player.size * 0.3, player.y - player.size * 0.3, player.size * 0.3, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
ctx.fillStyle = 'black'; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(player.x - player.size * 0.3, player.y - player.size * 0.3, player.size * 0.15, 0, Math.PI * 2); |
|
|
ctx.fill(); |
|
|
|
|
|
|
|
|
ctx.strokeStyle = 'rgba(255, 255, 0, 0.2)'; |
|
|
ctx.lineWidth = 2; |
|
|
ctx.beginPath(); |
|
|
ctx.arc(player.x, player.y, player.range, 0, Math.PI * 2); |
|
|
ctx.stroke(); |
|
|
} |
|
|
|
|
|
function updateUI() { |
|
|
const territoryPercent = Math.floor((claimedArea.size / (canvas.width * canvas.height / 4)) * 100); |
|
|
document.getElementById('territoryScore').textContent = territoryPercent; |
|
|
document.getElementById('timeScore').textContent = Math.floor(gameTime); |
|
|
document.getElementById('enemyCount').textContent = enemies.length; |
|
|
document.getElementById('killCount').textContent = killCount; |
|
|
|
|
|
|
|
|
if (territoryPercent >= level * 15) { |
|
|
level++; |
|
|
spawnEnemies(); |
|
|
} |
|
|
} |
|
|
|
|
|
function gameLoop(currentTime) { |
|
|
if (!gameRunning) return; |
|
|
|
|
|
const deltaTime = currentTime - lastTime; |
|
|
lastTime = currentTime; |
|
|
gameTime += deltaTime / 1000; |
|
|
|
|
|
updatePlayer(); |
|
|
updateBullets(); |
|
|
updateEnemies(); |
|
|
render(); |
|
|
updateUI(); |
|
|
|
|
|
requestAnimationFrame(gameLoop); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |