From f43f5795e94e1301ff3fcfe2ab7de7e80af4b4fa Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 1 Oct 2026 19:31:11 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E7=94=9F=E6=88=90:=20A=20classic=20Snake?= =?UTF-8?q?=20game=20built=20with=20HTML,=20CSS,=20and=20JavaScript.=20Con?= =?UTF-8?q?trol=20the=20snake=20to=20eat=20food,=20grow=20longer,=20and=20?= =?UTF-8?q?avoid=20hitting=20walls=20or=20yourself.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 ++++++++++++++ css/style.css | 68 +++++++++++++++++++ index.html | 24 +++++++ js/game.js | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 318 insertions(+) create mode 100644 README.md create mode 100644 css/style.css create mode 100644 index.html create mode 100644 js/game.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5b0e1c --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Snake Game + +A classic Snake game built with pure HTML, CSS, and JavaScript. No dependencies, no build step — just open and play. + +## Features + +- Smooth grid-based snake movement +- Score tracking with persistent high score (localStorage) +- Keyboard controls (Arrow keys or WASD) +- Game over detection (walls & self-collision) +- Restart button +- Responsive, clean retro-style UI + +## Project Structure + +``` +snake-game/ +├── index.html # Main HTML page +├── css/ +│ └── style.css # Game styling +├── js/ +│ └── game.js # Game logic +└── README.md +``` + +## How to Play + +1. Open `index.html` in any modern web browser. +2. Use the **Arrow keys** or **WASD** to control the snake. +3. Eat the red food pellets to grow and earn 10 points each. +4. Avoid hitting the walls or your own body. +5. Click **Restart** to start a new game. + +## Development + +No installation required. Simply clone or download the project and open `index.html`. + +If you prefer a local server: + +```bash +# Python 3 +python -m http.server 8000 + +# Then open http://localhost:8000 +``` + +## License + +MIT diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..7903ed6 --- /dev/null +++ b/css/style.css @@ -0,0 +1,68 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: #1a1a2e; + color: #eee; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +.game-container { + text-align: center; + padding: 20px; +} + +h1 { + margin-bottom: 15px; + color: #4ecca3; + letter-spacing: 2px; +} + +.score-board { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + font-size: 16px; + color: #4ecca3; +} + +canvas { + background: #16213e; + border: 3px solid #4ecca3; + border-radius: 8px; + display: block; + margin: 0 auto; +} + +.controls { + margin-top: 15px; +} + +button { + background: #4ecca3; + border: none; + color: #1a1a2e; + padding: 10px 25px; + font-size: 16px; + font-weight: bold; + border-radius: 5px; + cursor: pointer; + transition: background 0.2s; +} + +button:hover { + background: #45b893; +} + +.hint { + margin-top: 10px; + font-size: 13px; + color: #888; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..44025c8 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + Snake Game + + + +
+

Snake Game

+
+ Score: 0 + High Score: 0 +
+ +
+ +
+

Use arrow keys or WASD to move

+
+ + + diff --git a/js/game.js b/js/game.js new file mode 100644 index 0000000..1ab3e96 --- /dev/null +++ b/js/game.js @@ -0,0 +1,177 @@ +// Snake Game Logic +(function () { + 'use strict'; + + const canvas = document.getElementById('gameCanvas'); + const ctx = canvas.getContext('2d'); + const scoreEl = document.getElementById('score'); + const highScoreEl = document.getElementById('high-score'); + const restartBtn = document.getElementById('restartBtn'); + + const GRID_SIZE = 20; // size of each cell in pixels + const COLS = canvas.width / GRID_SIZE; + const ROWS = canvas.height / GRID_SIZE; + + let snake, direction, nextDirection, food, score, highScore, gameOver, gameLoop; + + highScore = Number(localStorage.getItem('snakeHighScore')) || 0; + highScoreEl.textContent = highScore; + + // Initialize a new game state + function init() { + snake = [ + { x: 10, y: 10 }, + { x: 9, y: 10 }, + { x: 8, y: 10 } + ]; + direction = { x: 1, y: 0 }; + nextDirection = { x: 1, y: 0 }; + score = 0; + gameOver = false; + scoreEl.textContent = score; + placeFood(); + + if (gameLoop) clearInterval(gameLoop); + gameLoop = setInterval(update, 100); + } + + // Place food at a random empty cell + function placeFood() { + let newFood; + do { + newFood = { + x: Math.floor(Math.random() * COLS), + y: Math.floor(Math.random() * ROWS) + }; + } while (snake.some(seg => seg.x === newFood.x && seg.y === newFood.y)); + food = newFood; + } + + // Update game state each tick + function update() { + if (gameOver) return; + + direction = nextDirection; + + const head = { + x: snake[0].x + direction.x, + y: snake[0].y + direction.y + }; + + // Wall collision + if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS) { + return endGame(); + } + + // Self collision + if (snake.some(seg => seg.x === head.x && seg.y === head.y)) { + return endGame(); + } + + snake.unshift(head); + + // Eat food + if (head.x === food.x && head.y === food.y) { + score += 10; + scoreEl.textContent = score; + if (score > highScore) { + highScore = score; + highScoreEl.textContent = highScore; + localStorage.setItem('snakeHighScore', highScore); + } + placeFood(); + } else { + snake.pop(); + } + + draw(); + } + + // Draw everything on the canvas + function draw() { + // Background + ctx.fillStyle = '#16213e'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Grid lines (subtle) + ctx.strokeStyle = 'rgba(78, 204, 163, 0.08)'; + for (let i = 1; i < COLS; i++) { + ctx.beginPath(); + ctx.moveTo(i * GRID_SIZE, 0); + ctx.lineTo(i * GRID_SIZE, canvas.height); + ctx.stroke(); + } + for (let j = 1; j < ROWS; j++) { + ctx.beginPath(); + ctx.moveTo(0, j * GRID_SIZE); + ctx.lineTo(canvas.width, j * GRID_SIZE); + ctx.stroke(); + } + + // Food + ctx.fillStyle = '#e94560'; + ctx.beginPath(); + ctx.arc( + food.x * GRID_SIZE + GRID_SIZE / 2, + food.y * GRID_SIZE + GRID_SIZE / 2, + GRID_SIZE / 2 - 2, + 0, + Math.PI * 2 + ); + ctx.fill(); + + // Snake + snake.forEach((seg, i) => { + ctx.fillStyle = i === 0 ? '#4ecca3' : '#3aa886'; + ctx.fillRect( + seg.x * GRID_SIZE + 1, + seg.y * GRID_SIZE + 1, + GRID_SIZE - 2, + GRID_SIZE - 2 + ); + }); + + // Game over overlay + if (gameOver) { + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#e94560'; + ctx.font = 'bold 32px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Game Over', canvas.width / 2, canvas.height / 2 - 10); + ctx.fillStyle = '#eee'; + ctx.font = '18px sans-serif'; + ctx.fillText('Press Restart to play again', canvas.width / 2, canvas.height / 2 + 30); + } + } + + // End the game + function endGame() { + gameOver = true; + clearInterval(gameLoop); + draw(); + } + + // Keyboard controls + document.addEventListener('keydown', (e) => { + const key = e.key.toLowerCase(); + let newDir = null; + + if (key === 'arrowup' || key === 'w') newDir = { x: 0, y: -1 }; + else if (key === 'arrowdown' || key === 's') newDir = { x: 0, y: 1 }; + else if (key === 'arrowleft' || key === 'a') newDir = { x: -1, y: 0 }; + else if (key === 'arrowright' || key === 'd') newDir = { x: 1, y: 0 }; + + if (!newDir) return; + e.preventDefault(); + + // Prevent reversing direction + if (newDir.x === -direction.x && newDir.y === -direction.y) return; + nextDirection = newDir; + }); + + restartBtn.addEventListener('click', init); + + // Start the game + init(); +})();