Untitled

 avatar
unknown
plain_text
9 months ago
5.5 kB
9
Indexable
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Simple Snake Game</title>
  <style>
    body { display:flex; align-items:center; justify-content:center; height:100vh; margin:0;
           background:#111; color:#eee; font-family:Arial, sans-serif; }
    canvas { background:#0b3d0b; border:6px solid #072; box-shadow:0 6px 20px rgba(0,0,0,0.7); }
    .info { position:fixed; top:12px; left:12px; font-size:14px; opacity:0.9; }
  </style>
</head>
<body>
  <div class="info">Use arrow keys to move • Space = pause • R = restart</div>
  <canvas id="game" width="480" height="480"></canvas>

<script>
(() => {
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');

  const TILE = 20;                // size of one cell
  const COLS = canvas.width / TILE;
  const ROWS = canvas.height / TILE;
  let speed = 8;                  // frames per second (snake speed)
  let tick = 0;

  // Game state
  let snake = [{x: Math.floor(COLS/2), y: Math.floor(ROWS/2)}];
  let dir = {x: 1, y: 0};         // initial direction: right
  let nextDir = {...dir};
  let food = spawnFood();
  let score = 0;
  let running = true;
  let gameOver = false;

  // Main loop using requestAnimationFrame with fixed-step logic
  let lastTime = 0;
  function loop(time) {
    requestAnimationFrame(loop);
    if (!lastTime) lastTime = time;
    const delta = (time - lastTime) / 1000;
    lastTime = time;
    tick += delta * speed;
    if (tick >= 1) {
      tick = 0;
      update();
      draw();
    }
  }

  function update() {
    if (!running) return;
    // apply buffered direction (prevents reversing instantly)
    if ((nextDir.x !== -dir.x || nextDir.y !== -dir.y)) {
      dir = {...nextDir};
    }
    const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };

    // wrap-around (optional) OR collide with walls — we'll wrap
    if (head.x < 0) head.x = COLS - 1;
    if (head.x >= COLS) head.x = 0;
    if (head.y < 0) head.y = ROWS - 1;
    if (head.y >= ROWS) head.y = 0;

    // check self-collision
    if (snake.some(s => s.x === head.x && s.y === head.y)) {
      gameOver = true;
      running = false;
      return;
    }

    snake.unshift(head);

    // eat food?
    if (head.x === food.x && head.y === food.y) {
      score += 1;
      // slight speed up every 5 points
      if (score % 5 === 0) speed = Math.min(speed + 1, 20);
      food = spawnFood();
    } else {
      snake.pop(); // move forward (remove tail)
    }
  }

  function draw() {
    // background
    ctx.fillStyle = '#082208';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // draw food
    drawRect(food.x, food.y, '#e53935');

    // draw snake with gradient
    for (let i = 0; i < snake.length; i++) {
      const s = snake[i];
      // head brighter
      if (i === 0) drawRect(s.x, s.y, '#b7ffb7');
      else {
        const shade = Math.floor(60 + 150 * (i / snake.length));
        drawRect(s.x, s.y, `rgb(${shade/2}, ${shade}, ${shade/2})`);
      }
    }

    // HUD
    ctx.fillStyle = 'rgba(0,0,0,0.4)';
    ctx.fillRect(6, 6, 120, 34);
    ctx.fillStyle = '#fff';
    ctx.font = '16px Arial';
    ctx.fillText('Score: ' + score, 12, 30);

    if (!running && !gameOver) {
      ctx.fillStyle = 'rgba(0,0,0,0.6)';
      ctx.fillRect(0, canvas.height/2 - 40, canvas.width, 80);
      ctx.fillStyle = '#fff';
      ctx.textAlign = 'center';
      ctx.font = '24px Arial';
      ctx.fillText('Paused — press Space to resume', canvas.width/2, canvas.height/2 + 8);
      ctx.textAlign = 'start';
    }

    if (gameOver) {
      ctx.fillStyle = 'rgba(0,0,0,0.7)';
      ctx.fillRect(0, canvas.height/2 - 60, canvas.width, 120);
      ctx.fillStyle = '#fff';
      ctx.textAlign = 'center';
      ctx.font = '32px Arial';
      ctx.fillText('Game Over', canvas.width/2, canvas.height/2 - 6);
      ctx.font = '18px Arial';
      ctx.fillText('Score: ' + score + '  •  Click or press R to restart', canvas.width/2, canvas.height/2 + 24);
      ctx.textAlign = 'start';
    }
  }

  function drawRect(cx, cy, color) {
    ctx.fillStyle = color;
    ctx.fillRect(cx * TILE + 1, cy * TILE + 1, TILE - 2, TILE - 2);
  }

  function spawnFood() {
    while (true) {
      const pos = { x: randInt(0, COLS-1), y: randInt(0, ROWS-1) };
      if (!snake.some(s => s.x === pos.x && s.y === pos.y)) return pos;
    }
  }

  function randInt(a, b) { return Math.floor(Math.random()*(b-a+1)) + a; }

  // input
  window.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowUp') nextDir = {x:0, y:-1};
    else if (e.key === 'ArrowDown') nextDir = {x:0, y:1};
    else if (e.key === 'ArrowLeft') nextDir = {x:-1, y:0};
    else if (e.key === 'ArrowRight') nextDir = {x:1, y:0};
    else if (e.key === ' ') { running = !running; e.preventDefault(); } // space = pause
    else if (e.key.toLowerCase() === 'r') restart();
  });

  // mouse restart when game over
  canvas.addEventListener('click', () => { if (gameOver) restart(); });

  function restart() {
    snake = [{x: Math.floor(COLS/2), y: Math.floor(ROWS/2)}];
    dir = {x:1,y:0}; nextDir = {...dir};
    food = spawnFood();
    score = 0; speed = 8; running = true; gameOver = false;
  }

  // start
  draw();
  requestAnimationFrame(loop);

})();
</script>
</body>
</html>
Editor is loading...
Leave a Comment