蛇的移动:通过定时器实现蛇的自动移动,并通过监听用户的键盘输入来改变蛇的移动方向。
食物的生成:随机在地图上生成食物,并在蛇吃到食物时更新得分和食物的位置。
墙和边界的碰撞判断:
监测蛇是否撞墙(遍历获取墙的位置,检查蛇的边界框是否与当前墙的边界框相交)
有四种情况:
蛇的左边界 小于 墙的右边界,蛇在墙的右侧(蛇从右往左撞墙)
蛇的右边界 大于 墙的左边界,蛇在墙的左侧(蛇从左往右撞墙)
蛇上边界的 小于 墙的下边界,蛇在墙的上侧(蛇从下往上撞墙)
蛇下边界的 大于 墙的上边界,蛇在墙的上侧(蛇从上往下撞墙)
完整源代码如下:
// snake.html<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>snake</title><style>* {margin: 0;padding: 0;}/* 地图 */.map {width: 400px;height: 400px;background-color: black;margin: 0 auto;margin-top: 50px;position: relative;}/* 蛇 */.snake {/* 蛇的宽高 */width: 20px;height: 20px;background-color: red;/* 确保蛇在地图内 */position: absolute;}/* 食物 */.food {/* 食物的宽高 */width: 20px;height: 20px;background-color: green;/* 确保食物在地图内 */position: absolute;}/* 得分 */.score {position: absolute;bottom: 10px;/* 距离底部10px */left: 10px;/* 距离左侧10px */color: white;/* 设置分数颜色为白色 */}/* 墙 */.wall1 {width: 20px;height: 80px;background-color: gray;position: absolute;top: 40px;left: 140px;}.wall2 {width: 20px;height: 80px;background-color: gray;position: absolute;top: 180px;left: 40px;}.wall3 {width: 140px;height: 20px;background-color: gray;position: absolute;top: 300px;left: 140px;}.wall4 {width: 140px;height: 20px;background-color: gray;position: absolute;top: 200px;left: 200px;}.wall5 {width: 140px;height: 20px;background-color: gray;position: absolute;top: 120px;left: 140px;}.wall6 {width: 20px;height: 120px;background-color: gray;position: absolute;top: 240px;left: 120px;}</style>
</head><body><div class="map"><div class="food"></div><div class="snake"></div><div class="score">Score: 0</div><div class="wall1">1</div><div class="wall2">2</div><div class="wall3">3</div><div class="wall4">4</div><div class="wall5">5</div><div class="wall6">6</div></div><script src="./snake.js"></script>
</body></html><!--
重点在于:遍历墙数组,检查蛇是否与墙发生碰撞(在snake.js最后)-->
// snake.jsdocument.addEventListener('DOMContentLoaded', function () {// 获取食物,地图,蛇,得分显示,墙var food = document.querySelector('.food');var map = document.querySelector('.map');var snake = document.querySelector('.snake');var scoredisplay = document.querySelector('.score');var walls = document.querySelectorAll('.wall1, .wall2, .wall3, .wall4, .wall5, .wall6');// 声明变量let snakeX; // 蛇的x坐标let snakeY; // 蛇的y坐标let snakeDirection; // 蛇运动方向let score; // 得分// 重新开始游戏function resetGame() {clearInterval(snakeMoveInterval); // 清除移动定时器snakeX = 160;snakeY = 160;snakeDirection = 'right';score = 0;updateScore();generateFood();startGame();}resetGame();// 更新分数显示function updateScore() {scoredisplay.textContent = '得分: ' + score;}// 生成随机位置的食物var foodX, foodY; // 声明全局变量function generateFood() {foodX = Math.floor(Math.random() * (map.offsetWidth / 20)) * 20;foodY = Math.floor(Math.random() * (map.offsetHeight / 20)) * 20;//虽然/20*20看似没变化,但实际上,如果删掉了这里,食物的位置不会严丝合缝在“格子”上,而是会有所偏移food.style.left = foodX + 'px';food.style.top = foodY + 'px';food.style.display = 'block'; // 让食物作为一个块级元素显示// 一定要让食物显示,因为蛇碰到食物后,食物被隐藏了}// 定时器,持续地让蛇向当前移动方向移动var snakeMoveInterval;// 开始游戏function startGame() {snakeMoveInterval = setInterval(function () {// 根据蛇的移动方向移动蛇的位置// 左上角是原点,向右是 X 轴正方向,向下是 Y 轴正方向if (snakeDirection === 'left' && snakeX > 0) {snakeX -= 20; // 向左移动20px} else if (snakeDirection === 'up' && snakeY > 0) {snakeY -= 20; // 向上移动20px} else if (snakeDirection === 'right' && snakeX < map.offsetWidth - 20) {snakeX += 20; // 向右移动20px} else if (snakeDirection === 'down' && snakeY < map.offsetHeight - 20) {snakeY += 20; // 向下移动20px} else {// 蛇撞到了地图边界,游戏失败var restart = confirm('游戏失败!你的得分是: ' + score + ' 是否重新开始?');if (restart) {resetGame();}return; // 结束函数}// 更新蛇的位置snake.style.left = snakeX + 'px';snake.style.top = snakeY + 'px';// 检查是否吃到了食物if (snakeX === foodX && snakeY === foodY) {food.style.display = 'none'; // 食物消失generateFood(); // 生成新的食物score += 5; // 增加分数updateScore(); // 更新分数显示}// 检查是否撞墙if (isSnakeCollidingWithWall(snakeX, snakeY)) {var restart = confirm('游戏失败!你的得分是: ' + score + ' 是否重新开始?');if (restart) { // 重新开始resetGame();}return; // 不重新开始,则结束函数}}, 500); // 每隔500ms移动一次}// 开始游戏startGame();// 键盘事件监听器,控制蛇的移动方向document.addEventListener('keydown', function (event) {if (event.keyCode === 37 && snakeDirection !== 'right') { // 左箭头键snakeDirection = 'left';} else if (event.keyCode === 38 && snakeDirection !== 'down') { // 上箭头键snakeDirection = 'up';} else if (event.keyCode === 39 && snakeDirection !== 'left') { // 右箭头键snakeDirection = 'right';} else if (event.keyCode === 40 && snakeDirection !== 'up') { // 下箭头键snakeDirection = 'down';}});// 检查是否撞墙function isSnakeCollidingWithWall(snakeX, snakeY) {// 定义蛇的矩形边界框对象,包含蛇头的位置 (x, y) 和蛇的宽度和高度var snakeRect = {x: snakeX, // 蛇的横坐标y: snakeY, // 蛇的纵坐标width: 20, // 蛇的宽度height: 20 // 蛇的高度};// !!!重点!!!// 遍历墙数组,检查蛇是否与墙发生碰撞for (var i = 0; i < walls.length; i++) {// 获取当前墙var wall = walls[i];var wallRect = {// 墙的位置 (x, y) x: wall.offsetLeft,// 墙体元素左侧边缘相对于其最近的定位祖先元素的偏移量,并将其作为墙体的 x 坐标y: wall.offsetTop,// 墙体元素顶部边缘相对于其最近的定位祖先元素的偏移量,并将其作为墙体的 y 坐标// 墙的宽高width: wall.offsetWidth,height: wall.offsetHeight};// 检查蛇的边界框是否与当前墙的边界框相交if (snakeRect.x < wallRect.x + wallRect.width &&snakeRect.x + snakeRect.width > wallRect.x &&snakeRect.y < wallRect.y + wallRect.height &&snakeRect.y + snakeRect.height > wallRect.y) {return true; // 发生碰撞}// 蛇的左边界 小于 墙的右边界,蛇在墙的右侧(蛇从右往左撞墙)// 蛇的右边界 大于 墙的左边界,蛇在墙的左侧(蛇从左往右撞墙)// 蛇上边界的 小于 墙的下边界,蛇在墙的上侧(蛇从下往上撞墙)// 蛇下边界的 大于 墙的上边界,蛇在墙的上侧(蛇从上往下撞墙)}return false; // 未发生碰撞}
});