蛇的移动:通过定时器实现蛇的自动移动,并通过监听用户的键盘输入来改变蛇的移动方向。
食物的生成:随机在地图上生成食物,并在蛇吃到食物时更新得分和食物的位置。
墙和边界的碰撞判断:
监测蛇是否撞墙(遍历获取墙的位置,检查蛇的边界框是否与当前墙的边界框相交)
有四种情况:
蛇的左边界 小于 墙的右边界,蛇在墙的右侧(蛇从右往左撞墙)
蛇的右边界 大于 墙的左边界,蛇在墙的左侧(蛇从左往右撞墙)
蛇上边界的 小于 墙的下边界,蛇在墙的上侧(蛇从下往上撞墙)
蛇下边界的 大于 墙的上边界,蛇在墙的上侧(蛇从上往下撞墙)
完整源代码如下:
// 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.js
document.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; // 未发生碰撞
}
});