Skip to content

Instantly share code, notes, and snippets.

@straker
Last active April 15, 2026 08:51
Show Gist options
  • Select an option

  • Save straker/ff00b4b49669ad3dec890306d348adc4 to your computer and use it in GitHub Desktop.

Select an option

Save straker/ff00b4b49669ad3dec890306d348adc4 to your computer and use it in GitHub Desktop.
Basic Snake HTML and JavaScript Game

ICE Out; Abolish ICE

Basic Snake HTML and JavaScript Game

Snake is a fun game to make as it doesn't require a lot of code (less than 100 lines with all comments removed). This is a basic implementation of the snake game, but it's missing a few things intentionally and they're left as further exploration for the reader.

Further Exploration

  • Score
    • When the snake eats an apple, the score should increase by one. Use context.fillText() to display the score to the screen
  • Mobile and touchscreen support
  • Better apple spawning
    • Currently the apple spawns in any random grid in the game, even if the snake is already on that spot. Improve it so it only spawns in empty grid locations

Important note: I will answer questions about the code but will not add more features or answer questions about adding more features. This series is meant to give a basic outline of the game but nothing more.

License

(CC0 1.0 Universal) You're free to use this game and code in any project, personal or commercial. There's no need to ask permission before using these. Giving attribution is not required, but appreciated.

Other Basic Games

Support

Basic HTML Games are made possible by users like you. When you become a Patron, you get access to behind the scenes development logs, the ability to vote on which games I work on next, and early access to the next Basic HTML Game.

Top Patrons

  • Karar Al-Remahy
  • UnbrandedTech
  • Innkeeper Games
  • Nezteb
<!DOCTYPE html>
<html>
<head>
<title>Basic Snake HTML Game</title>
<meta charset="UTF-8">
<style>
html, body {
height: 100%;
margin: 0;
}
body {
background: black;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
border: 1px solid white;
}
</style>
</head>
<body>
<canvas width="400" height="400" id="game"></canvas>
<script>
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
// the canvas width & height, snake x & y, and the apple x & y, all need to be a multiples of the grid size in order for collision detection to work
// (e.g. 16 * 25 = 400)
var grid = 16;
var count = 0;
var snake = {
x: 160,
y: 160,
// snake velocity. moves one grid length every frame in either the x or y direction
dx: grid,
dy: 0,
// keep track of all grids the snake body occupies
cells: [],
// length of the snake. grows when eating an apple
maxCells: 4
};
var apple = {
x: 320,
y: 320
};
// get random whole numbers in a specific range
// @see https://stackoverflow.com/a/1527820/2124254
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
// game loop
function loop() {
requestAnimationFrame(loop);
// slow game loop to 15 fps instead of 60 (60/15 = 4)
if (++count < 4) {
return;
}
count = 0;
context.clearRect(0,0,canvas.width,canvas.height);
// move snake by it's velocity
snake.x += snake.dx;
snake.y += snake.dy;
// wrap snake position horizontally on edge of screen
if (snake.x < 0) {
snake.x = canvas.width - grid;
}
else if (snake.x >= canvas.width) {
snake.x = 0;
}
// wrap snake position vertically on edge of screen
if (snake.y < 0) {
snake.y = canvas.height - grid;
}
else if (snake.y >= canvas.height) {
snake.y = 0;
}
// keep track of where snake has been. front of the array is always the head
snake.cells.unshift({x: snake.x, y: snake.y});
// remove cells as we move away from them
if (snake.cells.length > snake.maxCells) {
snake.cells.pop();
}
// draw apple
context.fillStyle = 'red';
context.fillRect(apple.x, apple.y, grid-1, grid-1);
// draw snake one cell at a time
context.fillStyle = 'green';
snake.cells.forEach(function(cell, index) {
// drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is
context.fillRect(cell.x, cell.y, grid-1, grid-1);
// snake ate apple
if (cell.x === apple.x && cell.y === apple.y) {
snake.maxCells++;
// canvas is 400x400 which is 25x25 grids
apple.x = getRandomInt(0, 25) * grid;
apple.y = getRandomInt(0, 25) * grid;
}
// check collision with all cells after this one (modified bubble sort)
for (var i = index + 1; i < snake.cells.length; i++) {
// snake occupies same space as a body part. reset game
if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) {
snake.x = 160;
snake.y = 160;
snake.cells = [];
snake.maxCells = 4;
snake.dx = grid;
snake.dy = 0;
apple.x = getRandomInt(0, 25) * grid;
apple.y = getRandomInt(0, 25) * grid;
}
}
});
}
// listen to keyboard events to move the snake
document.addEventListener('keydown', function(e) {
// prevent snake from backtracking on itself by checking that it's
// not already moving on the same axis (pressing left while moving
// left won't do anything, and pressing right while moving left
// shouldn't let you collide with your own body)
// left arrow key
if (e.which === 37 && snake.dx === 0) {
snake.dx = -grid;
snake.dy = 0;
}
// up arrow key
else if (e.which === 38 && snake.dy === 0) {
snake.dy = -grid;
snake.dx = 0;
}
// right arrow key
else if (e.which === 39 && snake.dx === 0) {
snake.dx = grid;
snake.dy = 0;
}
// down arrow key
else if (e.which === 40 && snake.dy === 0) {
snake.dy = grid;
snake.dx = 0;
}
});
// start the game
requestAnimationFrame(loop);
</script>
</body>
</html>
@Musaddik41
Copy link
Copy Markdown

// Game constants
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 400;
const GRAVITY = 0.5;
const JUMP_FORCE = -10;

// Game class
class ScootyRacingGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = CANVAS_WIDTH;
this.canvas.height = CANVAS_HEIGHT;

    this.player = new Player(50, CANVAS_HEIGHT - 50, 30, 50);
    this.level = 1;
    this.distance = 0;
    this.petrol = 100;
    this.coins = 0;
    this.score = 0;
    
    this.obstacles = [];
    this.powerUps = [];
    
    this.keys = {};
    
    this.setupEventListeners();
}

setupEventListeners() {
    document.addEventListener('keydown', (e) => this.keys[e.code] = true);
    document.addEventListener('keyup', (e) => this.keys[e.code] = false);
}

update() {
    this.player.update(this.keys);
    this.updateObstacles();
    this.updatePowerUps();
    this.checkCollisions();
    this.updateGameState();
}

draw() {
    this.ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    this.player.draw(this.ctx);
    this.drawObstacles();
    this.drawPowerUps();
    this.drawUI();
}

updateObstacles() {
    // Add logic to update obstacles
}

updatePowerUps() {
    // Add logic to update power-ups
}

checkCollisions() {
    // Add collision detection logic
}

updateGameState() {
    this.distance += this.player.speed;
    this.petrol -= 0.1;
    this.score = this.distance + this.coins * 10;
    
    if (this.petrol <= 0) {
        this.gameOver();
    }
    
    if (this.distance >= this.getLevelDistance()) {
        this.levelUp();
    }
}

drawObstacles() {
    // Add logic to draw obstacles
}

drawPowerUps() {
    // Add logic to draw power-ups
}

drawUI() {
    this.ctx.fillStyle = 'black';
    this.ctx.font = '16px Arial';
    this.ctx.fillText(`Level: ${this.level}`, 10, 20);
    this.ctx.fillText(`Distance: ${Math.floor(this.distance)}m`, 10, 40);
    this.ctx.fillText(`Petrol: ${Math.floor(this.petrol)}%`, 10, 60);
    this.ctx.fillText(`Coins: ${this.coins}`, 10, 80);
    this.ctx.fillText(`Score: ${this.score}`, 10, 100);
}

gameOver() {
    console.log('Game Over');
    // Add game over logic
}

levelUp() {
    this.level++;
    console.log(`Level up! Now on level ${this.level}`);
    // Add level up logic
}

getLevelDistance() {
    return this.level * 500; // 500m, 1000m, 1500m, etc.
}

gameLoop() {
    this.update();
    this.draw();
    requestAnimationFrame(() => this.gameLoop());
}

start() {
    this.gameLoop();
}

}

// Player class
class Player {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = 0;
this.maxSpeed = 5;
this.acceleration = 0.1;
this.deceleration = 0.05;
this.isJumping = false;
this.jumpVelocity = 0;
}

update(keys) {
    if (keys['ArrowRight']) {
        this.speed = Math.min(this.speed + this.acceleration, this.maxSpeed);
    } else if (keys['ArrowLeft']) {
        this.speed = Math.max(this.speed - this.acceleration, 0);
    } else {
        this.speed = Math.max(this.speed - this.deceleration, 0);
    }
    
    if (keys['Space'] && !this.isJumping) {
        this.jump();
    }
    
    this.x += this.speed;
    
    if (this.isJumping) {
        this.y += this.jumpVelocity;
        this.jumpVelocity += GRAVITY;
        
        if (this.y >= CANVAS_HEIGHT - this.height) {
            this.y = CANVAS_HEIGHT - this.height;
            this.isJumping = false;
            this.jumpVelocity = 0;
        }
    }
}

jump() {
    this.isJumping = true;
    this.jumpVelocity = JUMP_FORCE;
}

draw(ctx) {
    ctx.fillStyle = 'blue';
    ctx.fillRect(this.x, this.y, this.width, this.height);
}

}

// Initialize and start the game
const game = new ScootyRacingGame();
game.start();
Uploading Screenshot_2024_0902_110713.jpg…

@yt1d
Copy link
Copy Markdown

yt1d commented Sep 23, 2024

Thank you for sharing this information, I'm also this in my website and it's work.

Thank you for sharing this. This website, yt1d.online, is also using this script.

@Hudamuneeb
Copy link
Copy Markdown

Hudamuneeb commented Oct 1, 2024

Thank you for sharing this, I am also using it on my website and its work.

@NothingButTyler
Copy link
Copy Markdown

I found it just to change the 4. function loop() { requestAnimationFrame(loop); // slow game loop to 15 fps instead of 60 (60/15 = 4) if (++count < 4) { return; }

Thats funny...that was on my birthday 😅.

@3tdsgt3
Copy link
Copy Markdown

3tdsgt3 commented Feb 19, 2025

tks for sharing useful infomation ,check out my website too

@crazysticks420
Copy link
Copy Markdown

hi

@Hudamuneeb
Copy link
Copy Markdown

Great job, i am also using it on my website.

@lijiukun0204
Copy link
Copy Markdown

This game is great. apkyo.comThere are more stress-relieving games available。

@mlsbd247
Copy link
Copy Markdown

mlsbd247 commented Apr 17, 2025

My website is also created with help of HTML, but also used wordpress and custom domain + hosting which is https://mlsbd.io/

@leonqiu2012
Copy link
Copy Markdown

This game is great. I modified the code and add some sound effect, all codes are in the HTML file:
https://www.freejeu.com/snake.html

@its3rdlink
Copy link
Copy Markdown

Great minimalist implementation! Really appreciate the clean structure and open license.
Inspired to tinker with it — cheers from y2down.app!

@arnoldharris111
Copy link
Copy Markdown

Great snippet! Reminds me how valuable lightweight code samples like this are for learners diving into JS animations.
I recently came across a brief guide that explores useful Android tweaks — it's a different topic, but similarly focused on streamlining user experience. Appreciate resources like this that keep things simple and clear!

@agyt7319-commits
Copy link
Copy Markdown

Super op game

@apk1official
Copy link
Copy Markdown

My website is also created with help of HTML, but also used wordpress and custom domain + hosting which is https://apk-1.com/

@discussbetweenchatgpt-ops
Copy link
Copy Markdown

Nice
My Website Has Also Make With this wifi4games

@tubidymp3downloader
Copy link
Copy Markdown

Nice game. check my website Tubidy.

@8171bispgovnews
Copy link
Copy Markdown

i have upload most games recently if you like to watch then visit edusolvia

@sahushubham46224-ui
Copy link
Copy Markdown

<title>Simple Click Game</title> <style> body { text-align: center; font-family: Arial; } #btn { padding: 20px; font-size: 20px; cursor: pointer; } </style>

🎮 Click Game

Score: 0

Click Me! <script> let score = 0; let timeLeft = 10; const scoreDisplay = document.getElementById("score"); const button = document.getElementById("btn"); button.onclick = function() { score++; scoreDisplay.textContent = score; } setInterval(function() { timeLeft--; if(timeLeft <= 0){ alert("Game Over! Your Score: " + score); location.reload(); } }, 1000); </script>

@sahushubham46224-ui
Copy link
Copy Markdown

<title>Simple Click Game</title> <style> body { text-align: center; font-family: Arial; } #btn { padding: 20px; font-size: 20px; cursor: pointer; } </style>

🎮 Click Game

Score: 0

Click Me! <script> let score = 0; let timeLeft = 10; const scoreDisplay = document.getElementById("score"); const button = document.getElementById("btn"); button.onclick = function() { score++; scoreDisplay.textContent = score; } setInterval(function() { timeLeft--; if(timeLeft <= 0){ alert("Game Over! Your Score: " + score); location.reload(); } }, 1000); </script> ![Uploading Screenshot_2026-02-13-19-07-51-782_com.whatsapp.jpg…]()

@sahushubham46224-ui
Copy link
Copy Markdown

<title>Simple Click Game</title> <style> body { text-align: center; font-family: Arial; } #btn { padding: 20px; font-size: 20px; cursor: pointer; } </style>

🎮 Click Game

Score: 0

Click Me! <script> let score = 0; let timeLeft = 10; const scoreDisplay = document.getElementById("score"); const button = document.getElementById("btn"); button.onclick = function() { score++; scoreDisplay.textContent = score; } setInterval(function() { timeLeft--; if(timeLeft <= 0){ alert("Game Over! Your Score: " + score); location.reload(); } }, 1000); </script> ![Uploading Screenshot_2026-02-13-19-07-51-782_com.whatsapp.jpg…]

@landengut0-cmd
Copy link
Copy Markdown

This is good

@exody83-png
Copy link
Copy Markdown

cow game

<title>SAMU: THE FINAL MASTERPIECE</title> <style> :root { --bg: #441030; --text: #aea79f; --orange: #E95420; --ground: #E95420; } .night { --bg: #0d020a; --text: #fff; --ground: #fff; } body { background: #300a24; color: var(--text); font-family: monospace; margin: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; height: 100vh; } #game { position: relative; width: 850px; height: 300px; background: var(--bg); border-bottom: 5px solid var(--ground); transition: background 2s, border-color 2s; overflow: hidden; box-shadow: 0 0 80px rgba(0,0,0,0.6); } #cow { position: absolute; bottom: 0; left: 50px; font-size: 13px; white-space: pre; line-height: 1; z-index: 100; } #score-box { position: absolute; top: 15px; right: 25px; font-size: 28px; font-weight: bold; z-index: 200; text-align: right; } #hi-score { font-size: 14px; opacity: 0.7; } .obs { position: absolute; bottom: 0; font-size: 32px; z-index: 50; } /* CLOUD SIZE INCREASED HERE */ .cloud { position: absolute; font-size: 70px; z-index: 1; color: rgba(255,255,255,0.1); } .star { position: absolute; font-size: 14px; z-index: 1; } #moon { position: absolute; top: -70px; left: 100px; font-size: 50px; transition: top 2.5s cubic-bezier(0.4, 0, 0.2, 1); opacity: 0.7; } .night #moon { top: 40px; } #gameover-msg { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 20px; color: var(--text); } #restart-msg { display: none; position: absolute; top: 60%; left: 50%; transform: translate(-50%, -50%); font-size: 16px; color: var(--orange); } </style>
HI 00000
00000
🌙
^__^ (oo)\_______ (__)\ )\/\ ||----w | || ||
GAME OVER
Press Any Key to Restart
<script> const game = document.getElementById("game"), cow = document.getElementById("cow"), scoreEl = document.getElementById("score"), hiScoreEl = document.getElementById("hi-score"); const gameOverMsg = document.getElementById("gameover-msg"), restartMsg = document.getElementById("restart-msg"); let alive = true, score = 0, gravity = 0.65, jumpForce = -13, velocity = 0, speed = 6.5, nextSpawn = 0, nextCloud = 0, nextStar = 0; let obstacles = [], clouds = [], stars = [], isNight = false, isDucking = false; let highScore = localStorage.getItem("samuHiScore") || 0; hiScoreEl.innerText = "HI " + Math.floor(highScore).toString().padStart(5, '0'); const cowNormal = ` ^__^ (oo)\\_______ (__)\\ )\\/\\ ||----w | || ||`; const cowDuck = ` ^__^ (oo)___ (__) )\\/\\ ||-----w |`; const cowDead = ` ^__^ (**)\\_______ (__)\\ )\\/\\ ||----w | || ||`; // Audio const audio = new (window.AudioContext || window.webkitAudioContext)(); function playSfx(f, t, d, v=0.1) { const o = audio.createOscillator(), g = audio.createGain(); o.type = t; o.frequency.setValueAtTime(f, audio.currentTime); g.gain.value = v; o.connect(g); g.connect(audio.destination); o.start(); o.stop(audio.currentTime + d); } const keys = {}; window.onkeydown = (e) => { if(audio.state==='suspended') audio.resume(); if (!alive) location.reload(); // Restart on ANY key press after death keys[e.code] = true; }; window.onkeyup = (e) => keys[e.code] = false; function loop(time) { if (!alive) return; score += 0.15; scoreEl.innerText = Math.floor(score).toString().padStart(5, '0'); speed += 0.0015; let cycle = Math.floor(score / 700) % 2; if (cycle === 1 && !isNight) { game.classList.add('night'); isNight = true; playSfx(600, 'sine', 0.2); } if (cycle === 0 && isNight) { game.classList.remove('night'); isNight = false; } if ((keys['Space'] || keys['ArrowUp']) && parseInt(cow.style.bottom) <= 0 && !isDucking) { velocity = jumpForce; playSfx(400, 'triangle', 0.1); } if (keys['ArrowDown'] && parseInt(cow.style.bottom) <= 0) { isDucking = true; cow.innerHTML = cowDuck; } else { isDucking = false; if (alive) cow.innerHTML = cowNormal; } let bottom = parseInt(cow.style.bottom) || 0; velocity += gravity; bottom -= velocity; if (bottom < 0) { bottom = 0; velocity = 0; } cow.style.bottom = bottom + "px"; // Parallax Clouds/Stars ✨ if (time > nextCloud) { const item = document.createElement('div'); const isStar = isNight || Math.random() > 0.9; item.className = isStar ? 'star' : 'cloud'; item.innerHTML = isStar ? '✨' : '☁'; item.style.top = Math.random() * 180 + 'px'; item.style.left = '900px'; game.appendChild(item); (isStar ? stars : clouds).push({ el: item, x: 900, s: isStar ? Math.random()*2+1 : Math.random() * 1 + 0.5 }); nextCloud = time + (isStar ? 500 : 2500); } [...clouds, ...stars].forEach((item) => { item.x -= item.s; item.el.style.left = item.x + 'px'; if (item.x < -100) item.el.remove(); }); // Obstacles if (time > nextSpawn) { const o = document.createElement('div'); o.className = 'obs'; o.innerHTML = Math.random() > 0.5 ? '🌵' : '🦅'; o.style.left = '900px'; o.style.bottom = o.innerHTML === '🦅' ? '85px' : '0px'; game.appendChild(o); obstacles.push({ el: o, x: 900 }); nextSpawn = time + (Math.random() * 1000 + (1000 - (speed*10))); } // Collisions obstacles.forEach((o, i) => { o.x -= speed; o.el.style.left = o.x + 'px'; const cR = cow.getBoundingClientRect(), oR = o.el.getBoundingClientRect(); let collision = cR.right > oR.left + 20 && cR.left < oR.right - 20 && cR.bottom > oR.top + 15 && cR.top < oR.bottom - 15; if (collision) die(); if (o.x < -100) o.el.remove(); }); requestAnimationFrame(loop); } function die() { alive = false; playSfx(120, 'sawtooth', 0.8, 0.4); if (score > highScore) localStorage.setItem("samuHiScore", score); cow.innerHTML = cowDead; cow.style.transition = "bottom 1.5s cubic-bezier(0.25, 0.1, 0.25, 1), transform 1.5s"; cow.style.bottom = "800px"; cow.style.transform = "rotate(30deg) scale(1.8)"; // Show Game Over UI gameOverMsg.style.display = 'block'; restartMsg.style.display = 'block'; } cow.style.bottom = "0px"; requestAnimationFrame(loop); </script>

@sureshkewatadar-star
Copy link
Copy Markdown

Tum mere liye mind ka game bnao

@sureshkewatadar-star
Copy link
Copy Markdown

Minecraft game

@sureshkewatadar-star
Copy link
Copy Markdown

Uploading 64072.png…

@FosterUndine
Copy link
Copy Markdown

Simple games like Snake still work because they are clear, fast, and easy to understand, even without complex systems or graphics. Projects like this show how basic logic and small mechanics can stay engaging for a long time. It also reminds me of light chance-based entertainment online, where the focus is on simple interaction and short sessions; for example, I’ve seen similar ideas in sweepstakes-style spaces like Sweep Stars , where everything feels quick and straightforward. Sometimes the simplest concepts are the ones people return to most often.

@itsalexch
Copy link
Copy Markdown

Really clean and elegant implementation 👏
Love how the grid system keeps everything aligned and makes collision detection simple.

One small improvement I’d suggest is preventing apple spawn on the snake body would make gameplay feel more polished. Also adding a score counter with context.fillText() would be a nice touch.

Great minimal example overall, super helpful for beginners getting into canvas games.

Cheers, Y2Down

@DaValet
Copy link
Copy Markdown

DaValet commented Mar 20, 2026

Nice basic snake game implementation, perfect for beginners to learn JavaScript. The further exploration ideas like adding score tracking and better apple spawning are great practice. Clean code under 100 lines is impressive. When I'm not coding or working on game projects, I enjoy some mobile entertainment. I recently downloaded the https://1win.biz.in/app/ and found it really convenient on my phone. The application runs smoothly, loads fast, and deposits are simple. Great selection of games to enjoy during downtime right from my mobile. Perfect for quick gaming sessions on the go. Worth checking out if you're into mobile gaming.

@daardos
Copy link
Copy Markdown

daardos commented Mar 26, 2026

Привет! Посмотрел твой проект — классная работа, мне понравилась. Я тоже недавно пилил змейку и сделал упор на декор/новые функции. Если будет время, зацени мой вариант — интересно узнать мнение коллеги по коду! Вот ссылка: https://daardos.github.io/snake/

@rohanjadhav67
Copy link
Copy Markdown

.

<title>Fire with Gun Game</title> <style> body { margin: 0; padding: 20px; background: linear-gradient(135deg, #1e3c72, #2a5298); font-family: 'Arial', sans-serif; display: flex; flex-direction: column; align-items: center; color: white; } canvas { border: 3px solid #ff6b35; border-radius: 10px; box-shadow: 0 0 20px rgba(255,107,53,0.5); background: linear-gradient(to bottom, #87CEEB 0%, #98D8C8 100%); } .ui { margin: 10px 0; text-align: center; } .score { font-size: 24px; font-weight: bold; color: #ffd700; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); } .instructions { max-width: 600px; text-align: center; margin-bottom: 10px; font-size: 16px; } </style>

🔥 Fire with Gun 🔥

Click to shoot fire targets! Don't let the fire spread too much!
Score: 0 | Level: 1 | Lives: 3
<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const scoreEl = document.getElementById('score');
    const levelEl = document.getElementById('level');
    const livesEl = document.getElementById('lives');

    // Game state
    let score = 0;
    let level = 1;
    let lives = 3;
    let gameRunning = true;

    // Fire targets
    let fires = [];
    let bullets = [];
    let particles = [];
    let gun = {
        x: canvas.width / 2,
        y: canvas.height - 50,
        angle: 0
    };

    // Fire target class
    class FireTarget {
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.radius = 15 + Math.random() * 10;
            this.size = 1;
            this.maxSize = this.radius;
            this.growing = true;
            this.speed = 0.5 + level * 0.2;
            this.hue = 20 + Math.random() * 30;
        }

        update() {
            if (this.growing) {
                this.size += this.speed;
                if (this.size >= this.maxSize) {
                    this.growing = false;
                }
            } else {
                this.size -= this.speed * 0.5;
                if (this.size <= 0) {
                    return false; // Remove this fire
                }
            }
            
            // Spread fire
            if (Math.random() < 0.02 + level * 0.005) {
                spawnNewFire();
            }
            return true;
        }

        draw() {
            // Fire glow
            const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 1.5);
            gradient.addColorStop(0, `hsla(${this.hue}, 100%, 60%, 0.8)`);
            gradient.addColorStop(0.4, `hsla(${this.hue}, 100%, 50%, 0.6)`);
            gradient.addColorStop(1, `hsla(${this.hue}, 100%, 30%, 0)`);
            
            ctx.save();
            ctx.shadowColor = `hsl(${this.hue}, 100%, 50%)`;
            ctx.shadowBlur = 20;
            ctx.fillStyle = gradient;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.size * 1.5, 0, Math.PI * 2);
            ctx.fill();
            ctx.restore();

            // Fire core
            ctx.fillStyle = `hsl(${this.hue}, 100%, 70%)`;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
            ctx.fill();
        }

        hit() {
            // Create explosion particles
            for (let i = 0; i < 15; i++) {
                particles.push(new Particle(this.x, this.y, this.hue));
            }
            score += Math.floor(100 * level * (this.size / this.maxSize));
            return true;
        }
    }

    // Bullet class
    class Bullet {
        constructor(x, y, angle) {
            this.x = x;
            this.y = y;
            this.vx = Math.cos(angle) * 10;
            this.vy = Math.sin(angle) * 10;
            this.life = 60;
        }

        update() {
            this.x += this.vx;
            this.y += this.vy;
            this.life--;
            return this.life > 0 && this.x > 0 && this.x < canvas.width && this.y > 0 && this.y < canvas.height;
        }

        draw() {
            ctx.fillStyle = '#ffd700';
            ctx.shadowColor = '#ffd700';
            ctx.shadowBlur = 10;
            ctx.beginPath();
            ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
            ctx.fill();
            ctx.shadowBlur = 0;
        }
    }

    // Particle class for explosions
    class Particle {
        constructor(x, y, hue) {
            this.x = x;
            this.y = y;
            this.vx = (Math.random() - 0.5) * 8;
            this.vy = (Math.random() - 0.5) * 8;
            this.life = 30;
            this.maxLife = 30;
            this.hue = hue;
        }

        update() {
            this.x += this.vx;
            this.y += this.vy;
            this.vy += 0.1; // gravity
            this.life--;
            return this.life > 0;
        }

        draw() {
            const alpha = this.life / this.maxLife;
            ctx.save();
            ctx.globalAlpha = alpha;
            ctx.fillStyle = `hsl(${this.hue}, 100%, 60%)`;
            ctx.shadowColor = `hsl(${this.hue}, 100%, 60%)`;
            ctx.shadowBlur = 10;
            ctx.beginPath();
            ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
            ctx.fill();
            ctx.restore();
        }
    }

    // Spawn new fire target
    function spawnNewFire() {
        const side = Math.floor(Math.random() * 4);
        let x, y;
        switch(side) {
            case 0: // top
                x = Math.random() * canvas.width;
                y = -30;
                break;
            case 1: // right
                x = canvas.width + 30;
                y = Math.random() * canvas.height;
                break;
            case 2: // bottom
                x = Math.random() * canvas.width;
                y = canvas.height + 30;
                break;
            case 3: // left
                x = -30;
                y = Math.random() * canvas.height;
                break;
        }
        fires.push(new FireTarget(x, y));
    }

    // Mouse handling
    canvas.addEventListener('mousemove', (e) => {
        const rect = canvas.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;
        
        gun.angle = Math.atan2(mouseY - gun.y, mouseX - gun.x);
    });

    canvas.addEventListener('click', (e) => {
        if (!gameRunning) return;
        const rect = canvas.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;
        
        bullets.push(new Bullet(gun.x, gun.y, gun.angle));
    });

    // Collision detection
    function checkCollisions() {
        for (let i = bullets.length - 1; i >= 0; i--) {
            const bullet = bullets[i];
            for (let j = fires.length - 1; j >= 0; j--) {
                const fire = fires[j];
                const dx = bullet.x - fire.x;
                const dy = bullet.y - fire.y;
                const distance = Math.sqrt(dx * dx + dy * dy);
                
                if (distance < fire.size) {
                    if (fire.hit()) {
                        fires.splice(j, 1);
                    }
                    bullets.splice(i, 1);
                    break;
                }
            }
        }
    }

    // Game over check
    function checkGameOver() {
        if (fires.length > 20 + level * 5) {
            lives--;
            if (lives <= 0) {
                gameRunning = false;
                ctx.fillStyle = 'rgba(0,0,0,0.8)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.fillStyle = '#fff';
                ctx.font = '48px Arial';
                ctx.textAlign = 'center';
                ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2);
                ctx.font = '24px Arial';
                ctx.fillText(`Final Score: ${score}`, canvas.width/2, canvas.height/2 + 60);
                ctx.fillText('Refresh to play again', canvas.width/2, canvas.height/2 + 100);
            } else {
                fires = fires.slice(0, 10); // Reduce fires
            }
        }
    }

    // Level up
    function checkLevelUp() {
        if (score > level * 1000) {
            level++;
            lives = Math.min(5, lives + 1); // Bonus life occasionally
        }
    }

    // Update UI
    function updateUI() {
        scoreEl.textContent = score;
        levelEl.textContent = level;
        livesEl.textContent = lives;
    }

    // Main game loop
    function gameLoop() {
        // Clear canvas
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        if (gameRunning) {
            // Update fires
            for (let i = fires.length - 1; i >= 0; i--) {
                if (!fires[i].update()) {
                    fires.splice(i, 1);
                }
            }

            // Spawn new fires
            if (Math.random() < 0.02 + level * 0.01 && fires.length < 15) {
                spawnNewFire();
            }

            // Update bullets
            for (let i = bullets.length - 1; i >= 0; i--) {
                if (!bullets[i].update()) {
                    bullets.splice(i, 1);
                }
            }

            // Update particles
            for (let i = particles.length - 1; i >= 0; i--) {
                if (!particles[i].update()) {
                    particles.splice(i, 1);
                }
            }

            checkCollisions();
            checkGameOver();
            checkLevelUp();
            updateUI();

            // Draw gun
            ctx.save();
            ctx.translate(gun.x, gun.y);
            ctx.rotate(gun.angle);
            ctx.fillStyle = '#333';
            ctx.shadowColor = '#666';
            ctx.shadowBlur = 10;
            ctx.fillRect(-5, -25, 10, 50);
            ctx.fillStyle = '#666';
            ctx.fillRect(-3, -30, 6, 10);
            ctx.restore();
        }

        // Draw everything
        fires.forEach(fire => fire.draw());
        bullets.forEach(bullet => bullet.draw());
        particles.forEach(particle => particle.draw());

        requestAnimationFrame(gameLoop);
    }

    // Start game
    for (let i = 0; i < 3; i++) {
        spawnNewFire();
    }
    gameLoop();
</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment