简介
贪吃蛇是经典街机游戏,玩家控制蛇的方向吃食物,蛇身不断增长,碰到墙壁或自身则游戏结束。游戏厅版本使用 Canvas 渲染,支持方向键控制和最高分记录。
数据结构
蛇身表示
蛇身使用数组存储,每个元素是 {x, y} 坐标对。数组头部为蛇头,尾部为蛇尾:
1var snake = [
2 {x: 10, y: 10}, // 蛇头
3 {x: 9, y: 10}, // 蛇身
4 {x: 8, y: 10} // 蛇尾
5];
6var direction = {x: 1, y: 0}; // 初始向右移动
网格系统
游戏区域划分为 20x20 的网格:
1var GRID_SIZE = 20;
2var CELL_SIZE = 20; // 每格像素大小
3var canvasW = GRID_SIZE * CELL_SIZE;
4var canvasH = GRID_SIZE * CELL_SIZE;
核心逻辑
移动与增长
每次移动时,在蛇头前方插入新头部,如果没吃到食物则移除尾部:
1function move() {
2 // 计算新蛇头位置
3 var head = snake[0];
4 var newHead = {
5 x: head.x + direction.x,
6 y: head.y + direction.y
7 };
8
9 // 穿墙:从另一边出现
10 if (newHead.x < 0) newHead.x = GRID_SIZE - 1;
11 if (newHead.x >= GRID_SIZE) newHead.x = 0;
12 if (newHead.y < 0) newHead.y = GRID_SIZE - 1;
13 if (newHead.y >= GRID_SIZE) newHead.y = 0;
14
15 snake.unshift(newHead);
16
17 // 检查是否吃到食物
18 if (newHead.x === food.x && newHead.y === food.y) {
19 score += 10;
20 spawnFood();
21 // 不删除尾部 = 蛇身增长
22 } else {
23 snake.pop(); // 删除尾部 = 保持长度
24 }
25}
碰撞检测
蛇头碰到自身任意部位则游戏结束:
1function checkCollision() {
2 var head = snake[0];
3 for (var i = 1; i < snake.length; i++) {
4 if (head.x === snake[i].x && head.y === snake[i].y) {
5 return true;
6 }
7 }
8 return false;
9}
食物生成
在空白位置随机生成食物,避免生成在蛇身上:
1function spawnFood() {
2 var emptyCells = [];
3 for (var x = 0; x < GRID_SIZE; x++)
4 for (var y = 0; y < GRID_SIZE; y++) {
5 if (snake.some(function(seg) { return seg.x === x && seg.y === y; })) continue;
6 emptyCells.push({x: x, y: y});
7 }
8 if (emptyCells.length === 0) {
9 // 胜利!蛇已占满整个网格
10 winGame();
11 return;
12 }
13 var idx = Math.floor(Math.random() * emptyCells.length);
14 food = emptyCells[idx];
15}
方向控制
使用方向键改变蛇的移动方向,禁止反向移动(如向右时不能直接向左):
1document.addEventListener('keydown', function(e) {
2 if (!isActive()) return;
3 switch(e.key) {
4 case 'ArrowUp': if (direction.y === 0) direction = {x: 0, y: -1}; break;
5 case 'ArrowDown': if (direction.y === 0) direction = {x: 0, y: 1}; break;
6 case 'ArrowLeft': if (direction.x === 0) direction = {x: -1, y: 0}; break;
7 case 'ArrowRight': if (direction.x === 0) direction = {x: 1, y: 0}; break;
8 }
9 e.preventDefault();
10});
Canvas 渲染
绘制蛇身
蛇头使用较深的绿色,蛇身使用渐变色:
1function drawSnake() {
2 snake.forEach(function(seg, i) {
3 var x = seg.x * CELL_SIZE, y = seg.y * CELL_SIZE;
4 if (i === 0) {
5 // 蛇头:深绿色
6 ctx.fillStyle = '#166534';
7 ctx.fillRect(x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2);
8 // 眼睛
9 ctx.fillStyle = '#fff';
10 ctx.fillRect(x + 5, y + 5, 4, 4);
11 ctx.fillRect(x + 11, y + 5, 4, 4);
12 } else {
13 // 蛇身:绿色渐变
14 ctx.fillStyle = '#22c55e';
15 ctx.fillRect(x + 1, y + 1, CELL_SIZE - 2, CELL_SIZE - 2);
16 }
17 });
18}
绘制食物
食物使用红色圆形,带发光效果:
1function drawFood() {
2 var cx = food.x * CELL_SIZE + CELL_SIZE / 2;
3 var cy = food.y * CELL_SIZE + CELL_SIZE / 2;
4 // 光晕
5 ctx.fillStyle = 'rgba(239, 68, 68, 0.3)';
6 ctx.beginPath();
7 ctx.arc(cx, cy, CELL_SIZE / 2 + 2, 0, Math.PI * 2);
8 ctx.fill();
9 // 食物本体
10 ctx.fillStyle = '#ef4444';
11 ctx.beginPath();
12 ctx.arc(cx, cy, CELL_SIZE / 2 - 2, 0, Math.PI * 2);
13 ctx.fill();
14}
游戏循环
使用 requestAnimationFrame 实现游戏循环,通过帧计数器控制蛇的移动速度:
1var frameCount = 0;
2var SPEED = 8; // 每 8 帧移动一次
3
4function gameLoop() {
5 if (!playing) return;
6 frameCount++;
7 if (frameCount >= SPEED) {
8 frameCount = 0;
9 move();
10 if (checkCollision()) {
11 gameOver();
12 return;
13 }
14 }
15 ctx.clearRect(0, 0, canvasW, canvasH);
16 drawGrid();
17 drawFood();
18 drawSnake();
19 animFrameId = requestAnimationFrame(gameLoop);
20}
速度递增
随着分数提高,蛇的移动速度逐渐加快:
1function getSpeed() {
2 if (score >= 200) return 3;
3 if (score >= 150) return 4;
4 if (score >= 100) return 5;
5 if (score >= 50) return 6;
6 return 8;
7}
最高分持久化
1function saveBest() {
2 var best = localStorage.getItem('arcade-snake-best');
3 if (!best || score > parseInt(best)) {
4 localStorage.setItem('arcade-snake-best', score);
5 }
6}
总结
贪吃蛇的核心在于数组模拟蛇身移动(头部插入 + 尾部删除)和碰撞检测。Canvas 渲染提供了流畅的动画效果,帧计数器控制移动速度实现了速度递增的难度曲线。
留言评论
期待你的想法评论加载中