Snake Game AI Coding Test

Snake Game

Use arrow keys to control the snake. Eat the red food to grow and earn points. Avoid walls and yourself!
Score: 0
High Score: 0
Game Over!
Use Arrow Keys (↑, ↓, ←, →) to control direction

Source Code

<div class="snake-game-wrapper">
  <style>
    .snake-game-wrapper {
      max-width: 600px;
      margin: 0 auto;
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
      text-align: center;
      color: #333;
    }
    
    .game-header {
      margin-bottom: 15px;
    }
    
    .instructions {
      background: #f8f9fa;
      padding: 12px;
      border-radius: 8px;
      margin-bottom: 15px;
      font-size: 14px;
      color: #666;
      border: 1px solid #e9ecef;
    }
    
    .score-container {
      display: flex;
      justify-content: center;
      gap: 30px;
      margin-bottom: 15px;
      font-weight: bold;
      font-size: 18px;
    }
    
    .current-score {
      color: #4CAF50;
    }
    
    .high-score {
      color: #2196F3;
    }
    
    .game-container {
      position: relative;
      margin-bottom: 20px;
    }
    
    .game-canvas {
      background-color: #1a1a1a;
      border-radius: 8px;
      border: 2px solid #333;
      display: block;
      margin: 0 auto;
    }
    
    .game-button {
      background: #4CAF50;
      color: white;
      border: none;
      padding: 12px 30px;
      font-size: 16px;
      font-weight: bold;
      border-radius: 6px;
      cursor: pointer;
      transition: all 0.2s;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    }
    
    .game-button:hover {
      background: #45a049;
      transform: translateY(-2px);
      box-shadow: 0 6px 8px rgba(0,0,0,0.15);
    }
    
    .game-button:active {
      transform: translateY(0);
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    
    .game-over {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: rgba(0, 0, 0, 0.85);
      color: white;
      padding: 20px 30px;
      border-radius: 8px;
      font-size: 24px;
      font-weight: bold;
      display: none;
      box-shadow: 0 8px 16px rgba(0,0,0,0.3);
    }
    
    .controls-info {
      font-size: 13px;
      color: #777;
      margin-top: 15px;
    }
  </style>

  <div class="game-header">
    <h2>Snake Game</h2>
    <div class="instructions">
      Use arrow keys to control the snake. Eat the red food to grow and earn points. Avoid walls and yourself!
    </div>
  </div>
  
  <div class="score-container">
    <div class="current-score">Score: <span id="currentScore">0</span></div>
    <div class="high-score">High Score: <span id="highScore">0</span></div>
  </div>
  
  <div class="game-container">
    <canvas class="game-canvas" width="400" height="400"></canvas>
    <div class="game-over" id="gameOverText">Game Over!</div>
  </div>
  
  <button class="game-button" id="startButton">Start Game</button>
  <div class="controls-info">Use Arrow Keys (↑, ↓, ←, →) to control direction</div>

  <script>
    // Game state and configuration
    const gameState = {
      canvas: null,
      ctx: null,
      snake: [],
      food: { x: 0, y: 0 },
      direction: { x: 1, y: 0 },
      nextDirection: { x: 1, y: 0 },
      gridSize: 20,
      gameLoopId: null,
      score: 0,
      highScore: 0,
      isRunning: false,
      speed: 100 // milliseconds per move
    };

    // Initialize game
    function initGame() {
      gameState.canvas = document.querySelector('.game-canvas');
      gameState.ctx = gameState.canvas.getContext('2d');
      
      // Load high score from localStorage
      gameState.highScore = parseInt(localStorage.getItem('snakeHighScore')) || 0;
      document.getElementById('highScore').textContent = gameState.highScore;
      
      // Set up event listeners
      document.addEventListener('keydown', handleKeyPress);
      document.getElementById('startButton').addEventListener('click', startGame);
      
      // Draw initial static state
      drawStatic();
    }

    // Draw static elements (border, initial text)
    function drawStatic() {
      gameState.ctx.fillStyle = '#1a1a1a';
      gameState.ctx.fillRect(0, 0, gameState.canvas.width, gameState.canvas.height);
      
      // Draw grid pattern (subtle)
      gameState.ctx.strokeStyle = '#222';
      gameState.ctx.lineWidth = 0.5;
      for (let x = 0; x <= gameState.canvas.width; x += gameState.gridSize) {
        gameState.ctx.beginPath();
        gameState.ctx.moveTo(x, 0);
        gameState.ctx.lineTo(x, gameState.canvas.height);
        gameState.ctx.stroke();
      }
      for (let y = 0; y <= gameState.canvas.height; y += gameState.gridSize) {
        gameState.ctx.beginPath();
        gameState.ctx.moveTo(0, y);
        gameState.ctx.lineTo(gameState.canvas.width, y);
        gameState.ctx.stroke();
      }
      
      // Initial instruction text
      gameState.ctx.fillStyle = '#666';
      gameState.ctx.font = '16px Arial';
      gameState.ctx.textAlign = 'center';
      gameState.ctx.fillText('Click "Start Game" to begin', 
                            gameState.canvas.width/2, 
                            gameState.canvas.height/2);
    }

    // Start or restart the game
    function startGame() {
      if (gameState.isRunning) {
        clearInterval(gameState.gameLoopId);
      }
      
      // Reset game state
      gameState.snake = [
        { x: 10, y: 10 },
        { x: 9, y: 10 },
        { x: 8, y: 10 }
      ];
      gameState.direction = { x: 1, y: 0 };
      gameState.nextDirection = { x: 1, y: 0 };
      gameState.score = 0;
      gameState.isRunning = true;
      
      // Update UI
      document.getElementById('currentScore').textContent = '0';
      document.getElementById('gameOverText').style.display = 'none';
      document.getElementById('startButton').textContent = 'Restart Game';
      
      // Generate initial food
      generateFood();
      
      // Start game loop
      gameState.gameLoopId = setInterval(gameLoop, gameState.speed);
      
      // Draw initial frame
      draw();
    }

    // Main game loop
    function gameLoop() {
      // Update direction from queued input
      gameState.direction = { ...gameState.nextDirection };
      
      // Calculate new head position
      const head = { ...gameState.snake[0] };
      head.x += gameState.direction.x;
      head.y += gameState.direction.y;
      
      // Check for collisions
      if (isCollision(head)) {
        gameOver();
        return;
      }
      
      // Add new head
      gameState.snake.unshift(head);
      
      // Check if food is eaten
      if (head.x === gameState.food.x && head.y === gameState.food.y) {
        // Increase score
        gameState.score += 10;
        document.getElementById('currentScore').textContent = gameState.score;
        
        // Update high score if needed
        if (gameState.score > gameState.highScore) {
          gameState.highScore = gameState.score;
          document.getElementById('highScore').textContent = gameState.highScore;
          localStorage.setItem('snakeHighScore', gameState.highScore.toString());
        }
        
        // Generate new food
        generateFood();
      } else {
        // Remove tail if no food eaten
        gameState.snake.pop();
      }
      
      // Draw updated game state
      draw();
    }

    // Check for collisions
    function isCollision(head) {
      // Wall collision
      if (head.x < 0 || head.x >= gameState.canvas.width / gameState.gridSize ||
          head.y < 0 || head.y >= gameState.canvas.height / gameState.gridSize) {
        return true;
      }
      
      // Self collision (skip head itself)
      for (let i = 1; i < gameState.snake.length; i++) {
        if (gameState.snake[i].x === head.x && gameState.snake[i].y === head.y) {
          return true;
        }
      }
      
      return false;
    }

    // Generate food at random location (not on snake)
    function generateFood() {
      let newFood;
      let foodOnSnake;
      
      do {
        foodOnSnake = false;
        newFood = {
          x: Math.floor(Math.random() * (gameState.canvas.width / gameState.gridSize)),
          y: Math.floor(Math.random() * (gameState.canvas.height / gameState.gridSize))
        };
        
        // Check if food would spawn on snake
        for (const segment of gameState.snake) {
          if (segment.x === newFood.x && segment.y === newFood.y) {
            foodOnSnake = true;
            break;
          }
        }
      } while (foodOnSnake);
      
      gameState.food = newFood;
    }

    // Draw everything on canvas
    function draw() {
      // Clear canvas
      gameState.ctx.fillStyle = '#1a1a1a';
      gameState.ctx.fillRect(0, 0, gameState.canvas.width, gameState.canvas.height);
      
      // Draw grid (subtle)
      gameState.ctx.strokeStyle = '#222';
      gameState.ctx.lineWidth = 0.5;
      for (let x = 0; x <= gameState.canvas.width; x += gameState.gridSize) {
        gameState.ctx.beginPath();
        gameState.ctx.moveTo(x, 0);
        gameState.ctx.lineTo(x, gameState.canvas.height);
        gameState.ctx.stroke();
      }
      for (let y = 0; y <= gameState.canvas.height; y += gameState.gridSize) {
        gameState.ctx.beginPath();
        gameState.ctx.moveTo(0, y);
        gameState.ctx.lineTo(gameState.canvas.width, y);
        gameState.ctx.stroke();
      }
      
      // Draw snake
      gameState.snake.forEach((segment, index) => {
        const x = segment.x * gameState.gridSize;
        const y = segment.y * gameState.gridSize;
        
        // Head is slightly brighter
        if (index === 0) {
          gameState.ctx.fillStyle = '#66BB6A';
        } else {
          gameState.ctx.fillStyle = '#4CAF50';
        }
        
        // Draw rounded segments
        gameState.ctx.beginPath();
        gameState.ctx.roundRect(x, y, gameState.gridSize, gameState.gridSize, 4);
        gameState.ctx.fill();
        
        // Add subtle border
        gameState.ctx.strokeStyle = '#388E3C';
        gameState.ctx.lineWidth = 1;
        gameState.ctx.stroke();
      });
      
      // Draw food
      const foodX = gameState.food.x * gameState.gridSize;
      const foodY = gameState.food.y * gameState.gridSize;
      
      gameState.ctx.fillStyle = '#f44336';
      gameState.ctx.beginPath();
      gameState.ctx.arc(foodX + gameState.gridSize/2, 
                       foodY + gameState.gridSize/2, 
                       gameState.gridSize/2 - 1, 0, Math.PI * 2);
      gameState.ctx.fill();
      
      // Add shine effect to food
      gameState.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
      gameState.ctx.beginPath();
      gameState.ctx.arc(foodX + gameState.gridSize/2 - 3, 
                       foodY + gameState.gridSize/2 - 3, 
                       gameState.gridSize/6, 0, Math.PI * 2);
      gameState.ctx.fill();
    }

    // Handle keyboard input
    function handleKeyPress(event) {
      if (!gameState.isRunning) return;
      
      // Prevent default behavior for arrow keys to avoid page scrolling
      if ([37, 38, 39, 40].includes(event.keyCode)) {
        event.preventDefault();
      }
      
      let newDirection = { ...gameState.nextDirection };
      
      // Update direction based on key press, prevent opposite direction
      switch (event.keyCode) {
        case 37: // Left arrow
          if (gameState.direction.x !== 1) newDirection = { x: -1, y: 0 };
          break;
        case 38: // Up arrow
          if (gameState.direction.y !== 1) newDirection = { x: 0, y: -1 };
          break;
        case 39: // Right arrow
          if (gameState.direction.x !== -1) newDirection = { x: 1, y: 0 };
          break;
        case 40: // Down arrow
          if (gameState.direction.y !== -1) newDirection = { x: 0, y: 1 };
          break;
      }
      
      // Only update if direction actually changed
      if (newDirection.x !== gameState.nextDirection.x || 
          newDirection.y !== gameState.nextDirection.y) {
        gameState.nextDirection = newDirection;
      }
    }

    // Handle game over
    function gameOver() {
      gameState.isRunning = false;
      clearInterval(gameState.gameLoopId);
      
      // Show game over text
      const gameOverElement = document.getElementById('gameOverText');
      gameOverElement.textContent = `Game Over! Score: ${gameState.score}`;
      gameOverElement.style.display = 'block';
    }

    // Initialize game when page loads
    window.addEventListener('DOMContentLoaded', initGame);
  </script>
</div>
</div>

Source Prompt (Used on DeepSeek

Create a fully functional Snake game as a SINGLE DIV suitable for embedding in a Blogger post. Critical requirements:

STRUCTURE:
- Start with <div class="snake-game-wrapper"> and end with </div>
- NO <!DOCTYPE>, <html>, <head>, or <body> tags
- Include <style> tag inside the div with scoped CSS
- Include <script> tag inside the div
- All code must be self-contained with no external dependencies

FUNCTIONALITY:
- Canvas-based Snake game (400x400px recommended)
- Arrow keys control direction (prevent opposite-direction moves)
- Snake starts at center, length 3, moving right automatically on game start
- Food spawns randomly (never on snake body)
- Snake grows one segment per food eaten
- Score increments with each food
- High score persists using localStorage
- Game over on wall collision or self-collision
- Clear "Start Game" button to begin/restart
- Display current score and high score

GAMEPLAY REQUIREMENTS:
- Game must start moving immediately when Start is clicked (don't wait for arrow key)
- Movement must be smooth and continuous at ~100ms intervals
- Direction changes must queue properly (no skipped inputs)
- Game loop must stop cleanly on game over

STYLE:
- Container max-width: 600px, centered
- Dark canvas background (#1a1a1a)
- Green snake (#4CAF50), red food (#f44336)
- Clean, modern UI with good contrast
- Brief instructions above canvas
- Professional button styling

CODE QUALITY:
- Clear comments explaining game logic
- Defensive coding (check boundaries, prevent bugs)
- Use const/let appropriately
- Test that the game actually works before returning

The game must be playable immediately when embedded - no debugging required.

Comments

Popular posts from this blog

AI is Climbing the Wall -- Fast

Core Rights Draft

Javascript webp to png converter