Skip to content

Instantly share code, notes, and snippets.

@danmindru
Created May 22, 2025 18:43
Show Gist options
  • Save danmindru/932ec39666ab56fbb6a3a7f52efed12a to your computer and use it in GitHub Desktop.
Save danmindru/932ec39666ab56fbb6a3a7f52efed12a to your computer and use it in GitHub Desktop.
Tank AI game with Claude Sonnet 4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tank Battle</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background-color: #222;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: "Courier New", monospace;
touch-action: none;
}
canvas {
display: block;
background-color: #333;
}
#ui-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.menu {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
color: white;
text-align: center;
pointer-events: auto;
}
button {
background-color: #4caf50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 10px 2px;
cursor: pointer;
border-radius: 5px;
font-family: inherit;
}
.color-option {
display: inline-block;
width: 30px;
height: 30px;
margin: 5px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
}
.color-option.selected {
border: 2px solid white;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-container"></div>
<script>
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
const uiContainer = document.getElementById("ui-container");
const GRID_SIZE = 36;
let TILE_SIZE;
const PLAYER_COLORS = [
"#3498db",
"#e74c3c",
"#2ecc71",
"#f1c40f",
"#9b59b6",
];
const ENEMY_COLORS = {
AGGRESSIVE: "#e74c3c",
DEFENSIVE: "#2ecc71",
PATROL: "#f1c40f",
};
let gameState = "menu";
let level = 1;
let score = 0;
let highScore = localStorage.getItem("tankBattleHighScore") || 0;
let startTime;
let playerColor = PLAYER_COLORS[0];
let playerLives = 3;
let enemyCount = 3;
let enemies = [];
let projectiles = [];
let explosions = [];
let powerUps = [];
let lastShootTime = 0;
let killedEnemies = 0;
let player = {
x: 1,
y: 1,
direction: "right",
color: playerColor,
projectileColor: "#fff",
specialAmmo: false,
moving: false,
speedModifier: 1,
destroyed: false,
lastMove: 0,
};
let terrain = [];
let touchStartX = 0;
let touchStartY = 0;
let isTouching = false;
function resizeCanvas() {
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const size = Math.min(windowWidth, windowHeight) * 0.9;
canvas.width = size;
canvas.height = size;
TILE_SIZE = size / GRID_SIZE;
}
function init() {
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
canvas.addEventListener("touchstart", handleTouchStart);
canvas.addEventListener("touchmove", handleTouchMove);
canvas.addEventListener("touchend", handleTouchEnd);
showMainMenu();
requestAnimationFrame(gameLoop);
}
function showMainMenu() {
uiContainer.innerHTML = `
<div class="menu">
<h1>TANK BATTLE</h1>
<h2>High Score: ${highScore}</h2>
<div>
<h3>Select Tank Color</h3>
<div id="color-selector">
${PLAYER_COLORS.map(
(color, index) =>
`<div class="color-option ${
color === playerColor ? "selected" : ""
}"
style="background-color: ${color};"
data-color="${color}"></div>`
).join("")}
</div>
</div>
<div>
<h3>Enemy Tanks: <span id="enemy-count">${enemyCount}</span></h3>
<button id="decrease-enemies">-</button>
<button id="increase-enemies">+</button>
</div>
<button id="start-game">Start Game</button>
</div>
`;
document.querySelectorAll(".color-option").forEach((option) => {
option.addEventListener("click", () => {
playerColor = option.dataset.color;
document
.querySelectorAll(".color-option")
.forEach((o) => o.classList.remove("selected"));
option.classList.add("selected");
player.color = playerColor;
});
});
document
.getElementById("decrease-enemies")
.addEventListener("click", () => {
if (enemyCount > 1) {
enemyCount--;
document.getElementById("enemy-count").textContent = enemyCount;
}
});
document
.getElementById("increase-enemies")
.addEventListener("click", () => {
if (enemyCount < 10) {
enemyCount++;
document.getElementById("enemy-count").textContent = enemyCount;
}
});
document
.getElementById("start-game")
.addEventListener("click", startGame);
}
function startGame() {
gameState = "game";
uiContainer.innerHTML = "";
player = {
x: 1,
y: 1,
direction: "right",
color: playerColor,
projectileColor: "#fff",
specialAmmo: false,
moving: false,
speedModifier: 1,
destroyed: false,
lastMove: 0,
};
playerLives = 3;
level = 1;
score = 0;
killedEnemies = 0;
projectiles = [];
explosions = [];
powerUps = [];
startTime = Date.now();
generateMap();
spawnEnemies();
}
function generateMap() {
terrain = Array(GRID_SIZE)
.fill()
.map(() => Array(GRID_SIZE).fill("open"));
for (let i = 0; i < GRID_SIZE; i++) {
terrain[0][i] = "solid";
terrain[GRID_SIZE - 1][i] = "solid";
terrain[i][0] = "solid";
terrain[i][GRID_SIZE - 1] = "solid";
}
for (let i = 2; i < GRID_SIZE - 2; i++) {
for (let j = 2; j < GRID_SIZE - 2; j++) {
const rand = Math.random();
if (i <= 3 && j <= 3) continue;
if (rand < 0.05) {
terrain[i][j] = "solid";
} else if (rand < 0.15) {
terrain[i][j] = "breakable";
} else if (rand < 0.2) {
terrain[i][j] = "water";
} else if (rand < 0.25) {
terrain[i][j] = "mud";
}
}
}
createPaths();
}
function createPaths() {
for (let i = 4; i < GRID_SIZE - 4; i += 4) {
for (let j = 0; j < GRID_SIZE; j++) {
if (terrain[i][j] !== "solid") {
terrain[i][j] = "open";
}
if (terrain[j][i] !== "solid") {
terrain[j][i] = "open";
}
}
}
}
function spawnEnemies() {
enemies = [];
for (let i = 0; i < enemyCount; i++) {
let x, y;
let validPosition = false;
while (!validPosition) {
x = Math.floor(Math.random() * (GRID_SIZE - 6)) + 3;
y = Math.floor(Math.random() * (GRID_SIZE - 6)) + 3;
if (
terrain[x][y] === "open" &&
(Math.abs(x - player.x) > 5 || Math.abs(y - player.y) > 5)
) {
validPosition = true;
}
}
const behaviorTypes = ["aggressive", "defensive", "patrol"];
const behavior =
behaviorTypes[Math.floor(Math.random() * behaviorTypes.length)];
enemies.push({
x: x,
y: y,
direction: ["up", "right", "down", "left"][
Math.floor(Math.random() * 4)
],
color: ENEMY_COLORS[behavior.toUpperCase()],
behavior: behavior,
lastMove: Date.now(),
lastShoot: Date.now(),
moveDelay: 500 - level * 20,
specialAmmo: false,
patrolDirection: Math.random() < 0.5 ? "horizontal" : "vertical",
patrolReverse: false,
});
}
}
function handleKeyDown(e) {
if (gameState !== "game" || player.destroyed) return;
switch (e.key) {
case "ArrowUp":
player.direction = "up";
player.moving = true;
break;
case "ArrowRight":
player.direction = "right";
player.moving = true;
break;
case "ArrowDown":
player.direction = "down";
player.moving = true;
break;
case "ArrowLeft":
player.direction = "left";
player.moving = true;
break;
case " ":
playerShoot();
break;
}
}
function handleKeyUp(e) {
if (gameState !== "game") return;
if (
["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"].includes(e.key)
) {
player.moving = false;
}
}
function handleTouchStart(e) {
e.preventDefault();
if (gameState !== "game" || player.destroyed) return;
const touch = e.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
isTouching = true;
setTimeout(() => {
if (isTouching) {
playerShoot();
}
}, 200);
}
function handleTouchMove(e) {
e.preventDefault();
if (!isTouching || gameState !== "game" || player.destroyed) return;
const touch = e.touches[0];
const dx = touch.clientX - touchStartX;
const dy = touch.clientY - touchStartY;
if (Math.abs(dx) > 20 || Math.abs(dy) > 20) {
if (Math.abs(dx) > Math.abs(dy)) {
player.direction = dx > 0 ? "right" : "left";
} else {
player.direction = dy > 0 ? "down" : "up";
}
player.moving = true;
touchStartX = touch.clientX;
touchStartY = touch.clientY;
}
}
function handleTouchEnd(e) {
e.preventDefault();
isTouching = false;
player.moving = false;
}
function playerShoot() {
const currentTime = Date.now();
if (currentTime - lastShootTime < 2000) return;
lastShootTime = currentTime;
const projectile = {
x: player.x,
y: player.y,
direction: player.direction,
color: player.projectileColor,
speed: 0.2,
isPlayerProjectile: true,
special: player.specialAmmo,
lastMove: Date.now(),
};
projectiles.push(projectile);
}
function enemyShoot(enemy) {
const currentTime = Date.now();
if (currentTime - enemy.lastShoot < 2000) return;
enemy.lastShoot = currentTime;
const projectile = {
x: enemy.x,
y: enemy.y,
direction: enemy.direction,
color: "#ff4d4d",
speed: 0.15,
isPlayerProjectile: false,
special: enemy.specialAmmo,
lastMove: Date.now(),
};
projectiles.push(projectile);
}
function movePlayer() {
if (!player.moving || player.destroyed) return;
const currentTime = Date.now();
let moveDelay = 200 / player.speedModifier;
if (currentTime - (player.lastMove || 0) < moveDelay) return;
player.lastMove = currentTime;
let newX = player.x;
let newY = player.y;
switch (player.direction) {
case "up":
newY--;
break;
case "right":
newX++;
break;
case "down":
newY++;
break;
case "left":
newX--;
break;
}
if (isValidMove(newX, newY)) {
player.x = newX;
player.y = newY;
player.speedModifier = terrain[newX][newY] === "mud" ? 0.5 : 1;
checkPowerUpCollision();
}
}
function moveEnemies() {
const currentTime = Date.now();
enemies.forEach((enemy) => {
if (currentTime - enemy.lastMove < enemy.moveDelay) return;
enemy.lastMove = currentTime;
switch (enemy.behavior) {
case "aggressive":
moveAggressiveEnemy(enemy);
break;
case "defensive":
moveDefensiveEnemy(enemy);
break;
case "patrol":
movePatrolEnemy(enemy);
break;
}
const shouldShoot = Math.random() < 0.2;
if (shouldShoot) {
enemyShoot(enemy);
}
});
}
function moveAggressiveEnemy(enemy) {
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
if (Math.abs(dx) > Math.abs(dy)) {
enemy.direction = dx > 0 ? "right" : "left";
} else {
enemy.direction = dy > 0 ? "down" : "up";
}
let newX = enemy.x;
let newY = enemy.y;
switch (enemy.direction) {
case "up":
newY--;
break;
case "right":
newX++;
break;
case "down":
newY++;
break;
case "left":
newX--;
break;
}
if (isValidMove(newX, newY) && !isEnemyAtPosition(newX, newY)) {
enemy.x = newX;
enemy.y = newY;
} else {
const directions = ["up", "right", "down", "left"];
const currentIndex = directions.indexOf(enemy.direction);
directions.splice(currentIndex, 1);
enemy.direction = directions[Math.floor(Math.random() * 3)];
}
}
function moveDefensiveEnemy(enemy) {
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
const distanceToPlayer = Math.sqrt(dx * dx + dy * dy);
if (distanceToPlayer < 6) {
if (Math.abs(dx) > Math.abs(dy)) {
enemy.direction = dx > 0 ? "right" : "left";
} else {
enemy.direction = dy > 0 ? "down" : "up";
}
} else {
if (Math.random() < 0.3) {
enemy.direction = ["up", "right", "down", "left"][
Math.floor(Math.random() * 4)
];
}
}
let newX = enemy.x;
let newY = enemy.y;
switch (enemy.direction) {
case "up":
newY--;
break;
case "right":
newX++;
break;
case "down":
newY++;
break;
case "left":
newX--;
break;
}
if (isValidMove(newX, newY) && !isEnemyAtPosition(newX, newY)) {
enemy.x = newX;
enemy.y = newY;
}
const shootDx = player.x - enemy.x;
const shootDy = player.y - enemy.y;
if (Math.abs(shootDx) > Math.abs(shootDy)) {
enemy.direction = shootDx > 0 ? "right" : "left";
} else {
enemy.direction = shootDy > 0 ? "down" : "up";
}
}
function movePatrolEnemy(enemy) {
if (enemy.patrolDirection === "horizontal") {
enemy.direction = enemy.patrolReverse ? "left" : "right";
} else {
enemy.direction = enemy.patrolReverse ? "up" : "down";
}
let newX = enemy.x;
let newY = enemy.y;
switch (enemy.direction) {
case "up":
newY--;
break;
case "right":
newX++;
break;
case "down":
newY++;
break;
case "left":
newX--;
break;
}
if (isValidMove(newX, newY) && !isEnemyAtPosition(newX, newY)) {
enemy.x = newX;
enemy.y = newY;
} else {
enemy.patrolReverse = !enemy.patrolReverse;
}
if (Math.random() < 0.05) {
enemy.patrolDirection =
enemy.patrolDirection === "horizontal" ? "vertical" : "horizontal";
}
}
function isValidMove(x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) {
return false;
}
const terrainType = terrain[x][y];
return (
terrainType !== "solid" &&
terrainType !== "breakable" &&
terrainType !== "water"
);
}
function isEnemyAtPosition(x, y) {
return enemies.some((enemy) => enemy.x === x && enemy.y === y);
}
function moveProjectiles() {
const currentTime = Date.now();
for (let i = projectiles.length - 1; i >= 0; i--) {
const projectile = projectiles[i];
if (
currentTime - projectile.lastMove <
1000 / (projectile.speed * 10)
) {
continue;
}
projectile.lastMove = currentTime;
let newX = projectile.x;
let newY = projectile.y;
switch (projectile.direction) {
case "up":
newY -= 0.5;
break;
case "right":
newX += 0.5;
break;
case "down":
newY += 0.5;
break;
case "left":
newX -= 0.5;
break;
}
projectile.x = newX;
projectile.y = newY;
if (checkProjectileCollision(projectile, i)) {
continue;
}
if (newX < 0 || newX >= GRID_SIZE || newY < 0 || newY >= GRID_SIZE) {
projectiles.splice(i, 1);
}
}
}
function checkProjectileCollision(projectile, index) {
const gridX = Math.floor(projectile.x);
const gridY = Math.floor(projectile.y);
if (
gridX >= 0 &&
gridX < GRID_SIZE &&
gridY >= 0 &&
gridY < GRID_SIZE
) {
const terrainType = terrain[gridX][gridY];
if (terrainType === "solid") {
projectiles.splice(index, 1);
return true;
} else if (terrainType === "breakable") {
terrain[gridX][gridY] = "open";
createExplosion(gridX, gridY, projectile.special);
projectiles.splice(index, 1);
return true;
}
}
if (!projectile.isPlayerProjectile) {
const playerGridX = Math.floor(player.x);
const playerGridY = Math.floor(player.y);
if (
gridX === playerGridX &&
gridY === playerGridY &&
!player.destroyed
) {
playerHit();
createExplosion(playerGridX, playerGridY, projectile.special);
projectiles.splice(index, 1);
return true;
}
} else {
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
const enemyGridX = Math.floor(enemy.x);
const enemyGridY = Math.floor(enemy.y);
if (gridX === enemyGridX && gridY === enemyGridY) {
enemyHit(i);
createExplosion(enemyGridX, enemyGridY, projectile.special);
projectiles.splice(index, 1);
return true;
}
}
}
return false;
}
function createExplosion(x, y, isSpecial) {
const radius = isSpecial ? 2 : 1;
explosions.push({
x: x,
y: y,
radius: radius,
life: 10,
lastUpdate: Date.now(),
});
if (isSpecial) {
for (let i = x - radius; i <= x + radius; i++) {
for (let j = y - radius; j <= y + radius; j++) {
if (i < 0 || i >= GRID_SIZE || j < 0 || j >= GRID_SIZE) continue;
if (terrain[i][j] === "breakable") {
terrain[i][j] = "open";
}
for (let e = enemies.length - 1; e >= 0; e--) {
const enemy = enemies[e];
const enemyGridX = Math.floor(enemy.x);
const enemyGridY = Math.floor(enemy.y);
if (i === enemyGridX && j === enemyGridY) {
enemyHit(e);
}
}
if (i === Math.floor(player.x) && j === Math.floor(player.y)) {
playerHit();
}
}
}
}
}
function updateExplosions() {
const currentTime = Date.now();
for (let i = explosions.length - 1; i >= 0; i--) {
const explosion = explosions[i];
if (currentTime - explosion.lastUpdate > 100) {
explosion.lastUpdate = currentTime;
explosion.life--;
if (explosion.life <= 0) {
explosions.splice(i, 1);
}
}
}
}
function playerHit() {
playerLives--;
player.specialAmmo = false;
if (playerLives <= 0) {
player.destroyed = true;
setTimeout(gameOver, 2000);
}
}
function enemyHit(enemyIndex) {
const enemy = enemies[enemyIndex];
enemies.splice(enemyIndex, 1);
score += 100;
killedEnemies++;
if (Math.random() < 0.3) {
powerUps.push({
x: enemy.x,
y: enemy.y,
type: "specialAmmo",
collected: false,
});
}
if (enemies.length === 0) {
setTimeout(nextLevel, 2000);
}
}
function nextLevel() {
level++;
projectiles = [];
explosions = [];
player.specialAmmo = false;
const timeBonus = Math.floor((30000 / (Date.now() - startTime)) * 1000);
score += timeBonus;
enemyCount = Math.min(enemyCount + 1, 10);
generateMap();
spawnEnemies();
startTime = Date.now();
}
function gameOver() {
gameState = "gameOver";
if (score > highScore) {
highScore = score;
localStorage.setItem("tankBattleHighScore", highScore);
}
uiContainer.innerHTML = `
<div class="menu">
<h1>GAME OVER</h1>
<h2>Score: ${score}</h2>
<h2>High Score: ${highScore}</h2>
<h3>Enemies Defeated: ${killedEnemies}</h3>
<button id="play-again">Play Again</button>
<button id="return-menu">Main Menu</button>
</div>
`;
document
.getElementById("play-again")
.addEventListener("click", startGame);
document.getElementById("return-menu").addEventListener("click", () => {
gameState = "menu";
showMainMenu();
});
}
function checkPowerUpCollision() {
for (let i = powerUps.length - 1; i >= 0; i--) {
const powerUp = powerUps[i];
if (
Math.floor(player.x) === Math.floor(powerUp.x) &&
Math.floor(player.y) === Math.floor(powerUp.y)
) {
if (powerUp.type === "specialAmmo") {
player.specialAmmo = true;
}
powerUps.splice(i, 1);
}
for (let j = 0; j < enemies.length; j++) {
const enemy = enemies[j];
if (
Math.floor(enemy.x) === Math.floor(powerUp.x) &&
Math.floor(enemy.y) === Math.floor(powerUp.y)
) {
if (powerUp.type === "specialAmmo") {
enemy.specialAmmo = true;
}
powerUps.splice(i, 1);
break;
}
}
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (gameState === "game") {
drawTerrain();
drawPowerUps();
if (!player.destroyed) {
drawTank(player);
}
enemies.forEach((enemy) => drawTank(enemy));
projectiles.forEach((projectile) => drawProjectile(projectile));
explosions.forEach((explosion) => drawExplosion(explosion));
drawHUD();
} else {
ctx.fillStyle = "#7d916c";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 20; i++) {
for (let j = 0; j < 20; j++) {
if ((i + j) % 2 === 0) {
ctx.fillStyle = "#8da370";
ctx.fillRect(
i * (canvas.width / 20),
j * (canvas.height / 20),
canvas.width / 20,
canvas.height / 20
);
}
}
}
}
}
function drawTerrain() {
for (let i = 0; i < GRID_SIZE; i++) {
for (let j = 0; j < GRID_SIZE; j++) {
const terrainType = terrain[i][j];
const x = i * TILE_SIZE;
const y = j * TILE_SIZE;
switch (terrainType) {
case "solid":
ctx.fillStyle = "#555";
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
ctx.strokeStyle = "#444";
ctx.lineWidth = 1;
ctx.strokeRect(x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4);
break;
case "breakable":
ctx.fillStyle = "#a57164";
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
ctx.strokeStyle = "#8c5b4a";
ctx.lineWidth = 1;
for (let bi = 0; bi < 2; bi++) {
for (let bj = 0; bj < 2; bj++) {
ctx.strokeRect(
x + bi * (TILE_SIZE / 2),
y + bj * (TILE_SIZE / 2),
TILE_SIZE / 2,
TILE_SIZE / 2
);
}
}
break;
case "water":
ctx.fillStyle = "#4a90e2";
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
ctx.fillStyle = "#67a5e5";
for (let w = 0; w < 3; w++) {
ctx.beginPath();
ctx.arc(
x + TILE_SIZE / 2,
y + TILE_SIZE * (0.3 + w * 0.2),
TILE_SIZE / 3,
0,
Math.PI
);
ctx.fill();
}
break;
case "mud":
ctx.fillStyle = "#8b6b4c";
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
ctx.fillStyle = "#7a5c3d";
for (let m = 0; m < 5; m++) {
ctx.beginPath();
ctx.arc(
x + Math.random() * TILE_SIZE,
y + Math.random() * TILE_SIZE,
TILE_SIZE / 8,
0,
Math.PI * 2
);
ctx.fill();
}
break;
case "open":
ctx.fillStyle = "#7d916c";
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
break;
}
}
}
}
function drawTank(tank) {
const x = tank.x * TILE_SIZE;
const y = tank.y * TILE_SIZE;
const size = TILE_SIZE * 0.8;
ctx.fillStyle = tank.color;
ctx.fillRect(x + TILE_SIZE * 0.1, y + TILE_SIZE * 0.1, size, size);
ctx.fillStyle = "#333";
switch (tank.direction) {
case "up":
ctx.fillRect(
x + TILE_SIZE * 0.4,
y,
TILE_SIZE * 0.2,
TILE_SIZE * 0.4
);
break;
case "right":
ctx.fillRect(
x + TILE_SIZE * 0.6,
y + TILE_SIZE * 0.4,
TILE_SIZE * 0.4,
TILE_SIZE * 0.2
);
break;
case "down":
ctx.fillRect(
x + TILE_SIZE * 0.4,
y + TILE_SIZE * 0.6,
TILE_SIZE * 0.2,
TILE_SIZE * 0.4
);
break;
case "left":
ctx.fillRect(
x,
y + TILE_SIZE * 0.4,
TILE_SIZE * 0.4,
TILE_SIZE * 0.2
);
break;
}
ctx.fillStyle = "#333";
ctx.fillRect(
x + TILE_SIZE * 0.1,
y + TILE_SIZE * 0.1,
TILE_SIZE * 0.15,
size
);
ctx.fillRect(
x + TILE_SIZE * 0.75,
y + TILE_SIZE * 0.1,
TILE_SIZE * 0.15,
size
);
if (tank.specialAmmo) {
ctx.fillStyle = "#ff0";
ctx.beginPath();
ctx.arc(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 6,
0,
Math.PI * 2
);
ctx.fill();
}
}
function drawProjectile(projectile) {
const x = projectile.x * TILE_SIZE;
const y = projectile.y * TILE_SIZE;
ctx.fillStyle = projectile.color;
if (projectile.special) {
ctx.beginPath();
ctx.arc(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 4,
0,
Math.PI * 2
);
ctx.fill();
ctx.beginPath();
const gradient = ctx.createRadialGradient(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 8,
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 2
);
gradient.addColorStop(0, "rgba(255, 255, 0, 0.8)");
gradient.addColorStop(1, "rgba(255, 255, 0, 0)");
ctx.fillStyle = gradient;
ctx.arc(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 2,
0,
Math.PI * 2
);
ctx.fill();
} else {
ctx.beginPath();
ctx.arc(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 6,
0,
Math.PI * 2
);
ctx.fill();
}
}
function drawExplosion(explosion) {
const x = explosion.x * TILE_SIZE;
const y = explosion.y * TILE_SIZE;
const radius = explosion.radius * TILE_SIZE;
const gradient = ctx.createRadialGradient(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
0,
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
radius
);
gradient.addColorStop(
0,
"rgba(255, 255, 255, " + explosion.life / 10 + ")"
);
gradient.addColorStop(
0.4,
"rgba(255, 200, 0, " + explosion.life / 12 + ")"
);
gradient.addColorStop(
0.6,
"rgba(255, 100, 0, " + explosion.life / 15 + ")"
);
gradient.addColorStop(1, "rgba(255, 0, 0, 0)");
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(x + TILE_SIZE / 2, y + TILE_SIZE / 2, radius, 0, Math.PI * 2);
ctx.fill();
}
function drawPowerUps() {
powerUps.forEach((powerUp) => {
const x = powerUp.x * TILE_SIZE;
const y = powerUp.y * TILE_SIZE;
if (powerUp.type === "specialAmmo") {
ctx.fillStyle = "#ffcc00";
ctx.beginPath();
ctx.arc(
x + TILE_SIZE / 2,
y + TILE_SIZE / 2,
TILE_SIZE / 3,
0,
Math.PI * 2
);
ctx.fill();
ctx.fillStyle = "#ff9900";
const starPoints = 5;
const outerRadius = TILE_SIZE / 4;
const innerRadius = TILE_SIZE / 8;
ctx.beginPath();
for (let i = 0; i < starPoints * 2; i++) {
const radius = i % 2 === 0 ? outerRadius : innerRadius;
const angle = (Math.PI * 2 * i) / (starPoints * 2);
const xPos = x + TILE_SIZE / 2 + radius * Math.cos(angle);
const yPos = y + TILE_SIZE / 2 + radius * Math.sin(angle);
if (i === 0) {
ctx.moveTo(xPos, yPos);
} else {
ctx.lineTo(xPos, yPos);
}
}
ctx.closePath();
ctx.fill();
}
});
}
function drawHUD() {
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
ctx.fillRect(0, 0, canvas.width, TILE_SIZE * 1.5);
ctx.fillStyle = "#fff";
ctx.font = `${TILE_SIZE / 2}px 'Courier New', monospace`;
ctx.fillText(`Lives: ${playerLives}`, TILE_SIZE, TILE_SIZE);
ctx.fillText(`Score: ${score}`, TILE_SIZE * 10, TILE_SIZE);
ctx.fillText(`Level: ${level}`, TILE_SIZE * 20, TILE_SIZE);
if (player.specialAmmo) {
ctx.fillStyle = "#ffcc00";
ctx.fillText("SPECIAL AMMO", TILE_SIZE * 28, TILE_SIZE);
}
}
function gameLoop() {
if (gameState === "game") {
movePlayer();
moveEnemies();
moveProjectiles();
updateExplosions();
}
draw();
requestAnimationFrame(gameLoop);
}
init();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment