文章目录
- 实现方法_1
- 1实现效果
- 2 实现步骤
- 2.1 移动场地
- 2.2 游戏难度
- 2.3 造蛇和食物
- 2.4 蛇的移动
- 2.5 产生食物的随机位置
- 3 全部代码
- 实现方法_2
- 1 实现效果
- 2实现想法
- 2.1 蛇的存储
实现方法_1
1实现效果
2 实现步骤
html部分忽略,布局写的太辣眼了
2.1 移动场地
用的表格td,利用双层for循环加上字符串的拼接,最终利用innnerHTML将布局显示
for (let i = 0; i < height; i++) {
str += '<tr>';
for (let j = 0; j < width; j++) {
if (i == 0) {
str += '<td>' + j + '</td>';
} else if (j == 0) {
str += '<td>' + i + '</td>';
} else {
str += '<td></td>';
}
}
str += '</tr>';
}
document.querySelector('table').innerHTML = str;
2.2 游戏难度
更改定时器的时间(简单)
let difficulty = document.querySelector("#difficulty");
difficulty.onchange = function () {
if (!isPlay) {
time = difficulty.value;
clearInterval(timeId);
difficulty.preventDefault();
}
}
2.3 造蛇和食物
利用一维数组存放蛇,但是因为需要确定蛇头和蛇身的位置需要横纵坐标,所以存放的是键值对形式的数据 {key_x: x_value , key_y: y_value}; ,如何显示蛇呢,道理和js实现动漫拼图1.0版中initEvent函数哪里说的的原理一样。snake一维数组记录的蛇身位置,移动场地是有n个tds组成的,通过snake记录的横纵坐标计算出对应的tds[index]中的index值(index=行×width+列)
,然后将这个td更换背景,来显示蛇。同理显示食物也是如此。
// 初始化蛇
function initSnake() {
let snake = [];
snake[0] = getRandom();
let x = snake[0].x;
let y = snake[0].y;
snake[1] = {
x: x - 1,
y: y
};
snake[2] = {
x: x - 2,
y: y
};
return snake;
}
//显示画面
function show(snake, food) {
// 1 清除所有蛇和食物
tds.forEach(function (item) {
item.style.backgroundColor = '';
});
// 2 显示蛇
snake.forEach(function (item, id) {
let i = parseInt(item.x);
let j = parseInt(item.y);
console.log(i, j);
let tdId = j * width + i;
console.log(tdId);
if (id == 0) {
tds[tdId].style.backgroundColor = 'blue';
} else {
tds[tdId].style.backgroundColor = 'green';
}
});
// 3 显示食物
let tdFoodId = food.y * width + food.x;
tds[tdFoodId].style.backgroundColor = 'red';
}
2.4 蛇的移动
监听键盘的按键按下事件
通过按对应的wasd上左下右,来更改d的值,表示蛇头的移动方向,调用自己写的move函数,实现对应的行向或列向的加加减减实现上下移动,在配合上定时器,便实现了蛇的移动,但这是仅限于单个块的移动(蛇头),那其他块(蛇身)怎么正确随蛇身移动呢?(需要注意越界问题)
这里方法就很巧妙了,我们存放蛇用的是一维数组,而且一维数组有两个方法
pop:移除数组的尾部元素,并返回该值。
unshift:在数组头部添加新的元素。
思路:这里我们先不去考虑越界的情况,假设蛇头的下一个位置都是合法的。
首先,通过我们的按键事件,获得我们预使蛇头向那个方向移动,然后计算出新的蛇头,利用unshift将新蛇头加入snake数组,在利用pop移除最后面的一个(前面加一个,右面移除一个,这样就实现了蛇的移动,我们可以很容易想到整体一条直线的时候确实可以,那要是拐弯呢?也是可以的)
蛇在吃食物时,食物的位置肯定和新蛇头是重合,吃过食物之后蛇的长度应该加一,此时呢就只需要调用unshift函数,加入新蛇头就行了,不用移除后一个,在随机一个食物位置即可。
// 4.1 蛇的移动方向
document.addEventListener("keydown", direction);
function direction(event) {
if (event.keyCode == 65 && d != "RIGHT") {
d = "LEFT";
} else if (event.keyCode == 87 && d != "DOWN") {
d = "UP";
} else if (event.keyCode == 68 && d != "LEFT") {
d = "RIGHT";
} else if (event.keyCode == 83 && d != "UP") {
d = "DOWN";
} else if (event.keyCode == 32) {
// 暂停
suspendGame();
} else if (event.keyCode == 82) {
// 重新开始
restartGame();
} else if (event.keyCode == 66 && !isPlay) {
// 开始
startGame();
}
}
// 4.2 蛇的移动
function move(snake) {
let snakeX = snake[0].x;
let snakeY = snake[0].y;
let isEat = false;
if (d == "LEFT") snakeX -= speed;
if (d == "UP") snakeY -= speed;
if (d == "RIGHT") snakeX += speed;
if (d == "DOWN") snakeY += speed;
if (snakeX == food.x && snakeY == food.y) {
isEat = true;
let tdId = food.y * width + food.x;
tds[tdId].style.backgroundColor = 'lightgray';
score += 5;
scoreSpan.innerHTML = score;
food = getFood(snake);
}
let newHead = {
x: snakeX,
y: snakeY
};
// 检测是否越界,或者自己碰到自己
if (snakeX < 0 || snakeX >= width || snakeY < 0 || snakeY >= height || collision(newHead, snake)) {
endGame();
} else {
// 没有吃到,移除最后一个,在头部加一个,蛇长不变
// 吃到,if不成立,蛇长不减,蛇头加一,整体变长一
if (!isEat) {
snake.pop();
}
snake.unshift(newHead);
console.log("newHead=" + newHead.x + "," + newHead.y);
}
2.5 产生食物的随机位置
食物的随机位置不能在蛇身上
// 产生随机数字(游戏开始时蛇的随机位置和食物的随机位置)
function getRandom() {
// 蛇头的产生范围限定在(2-44)
let ran_x = parseInt(Math.random() * (width - 4)) + 2;
let ran_y = parseInt(Math.random() * height);
return {
x: ran_x,
y: ran_y
};
}
// 检查食物随机产生的位置是否在蛇的身体上
function getFood(snake) {
while (true) {
let food = getRandom();
let flag = true;
for (let i = 0; i < snake.length; i++) {
let item = snake[i];
if (item.x == food.x && item.y == food.y) {
flag = false;
break;
}
}
if (flag) {
return food;
}
}
}
其他就是一些繁琐的获取元素,添加事件,测试逻辑,该bug了
3 全部代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇</title>
<style>
table {
border-collapse: collapse;
}
.body {
display: flex;
height: 600px;
background-color: lightblue;
}
td {
border: 1px solid black;
width: 15px;
height: 15px;
margin-left: 4px;
font-size: 10px;
}
.print {
flex: 70%;
background-color: aliceblue;
border: 1px solid red;
width: 800px;
}
.content {
margin: 0 auto;
margin-top: 30px;
width: 800px;
background-color: lightgray;
}
.option {
margin-top: 40px;
flex: 20%;
}
.text1 {
text-align: center;
}
button {
margin-top: 10px;
margin-bottom: 10px;
width: 100px;
height: 40px;
background-color: orange;
border: none;
border-radius: 20px;
}
button:hover {
background-color: yellow;
}
.snake,
.snake1 {
display: inline-block;
width: 20px;
height: 20px;
background-color: blue;
}
.snake1 {
background-color: green;
}
.food {
display: inline-block;
width: 20px;
height: 20px;
background-color: red;
}
.text2 {
display: inline-block;
width: 170px;
font-size: 14px;
width: 120px;
}
.bt {
padding-left: 30px;
box-sizing: border-box;
}
.score {
text-align: center;
}
.score_span {
display: block;
margin-top: 5px;
color: red;
font-size: 30px;
}
select {
margin-top: 10px;
margin-bottom: 10px;
width: 100px;
height: 40px;
background-color: lightcoral;
border: none;
text-align: center;
border-radius: 20px;
}
.move span{
width: 20px;
height: 20px;
display: inline-block;
border: 1px solid #000;
color: black;
background-color: white;
box-shadow: 2px 2px #000;
}
.w{
margin-bottom: 2px;
margin-right: 14%;
}
</style>
</head>
<body>
<div class="body">
<div class="print">
<div class="content">
<table>
</table>
</div>
</div>
<div class="option">
<div class="text1">
蛇:<span class="snake"></span><span class="snake1"></span><span class="snake1"></span>
食物:<span class="food"></span>
</div>
<div class="bt">
<span class="text2">按B键开始/继续:</span><button>开始游戏</button>
</div>
<div class="bt">
<span class="text2">按空格键暂停:</span> <button>暂停游戏</button>
</div>
<div class="bt">
<span class="text2">按R键重开:</span> <button>重新开始</button>
</div>
<div class="bt">
<span class="text2">游戏难度:</span>
<select id="difficulty">
<option value="500">简单</option>
<option value="300">一般</option>
<option value="100">困难</option>
<option value="50">噩梦</option>
</select>
<p style="color: red;">选择好难度后,按开始游戏(或B键)</p>
</div>
<div class="score">
游戏得分
<span class="score_span">0</span>
<div class="move">
移动:
<span class="w">W</span>
<div>
<span class="A">A</span>
<span class="S">S</span>
<span class="D">D</span>
</div>
</div>
<p style="font-size: small;">
说明:在游戏过程中更换游戏难度无效。<br>
若要更换难度:<br>
可在开始游戏前选择好难度<br>
或者暂停游戏选好难度后,再点击开始<br>
或者点击重新开始,然后选择难度后,再点击开始
</p>
</div>
</div>
</div>
<script>
// 1 初始化场景
let str = '';
let width = 45;
let height = 30;
let isPlay = false;
// 获取分数
let score = 0;
let scoreSpan = document.querySelector('.score_span');
// 蛇的移动速度(定时器的时间)
let timeId = 0;
let time = 300;
const speed = 1;
let d = "RIGHT";
for (let i = 0; i < height; i++) {
str += '<tr>';
for (let j = 0; j < width; j++) {
if (i == 0) {
str += '<td>' + j + '</td>';
} else if (j == 0) {
str += '<td>' + i + '</td>';
} else {
str += '<td></td>';
}
}
str += '</tr>';
}
document.querySelector('table').innerHTML = str;
let difficulty = document.querySelector("#difficulty");
difficulty.onchange = function () {
if (!isPlay) {
time = difficulty.value;
clearInterval(timeId);
difficulty.preventDefault();
}
}
// 2 初始化蛇
let snake = initSnake();
// 3 初始化食物,食物产生的随机位置不要在蛇身上
let food = getFood(snake);
// 获取按钮
let bt = document.querySelectorAll('button');
// 获取所有td
let tds = document.querySelectorAll('td');
bt[0].onclick = startGame;
bt[1].onclick = suspendGame;
bt[2].onclick = restartGame;
// 初始化
show(snake, food);
// 4 蛇的移动
// 4.1 蛇的移动方向
document.addEventListener("keydown", direction);
function direction(event) {
if (event.keyCode == 65 && d != "RIGHT") {
d = "LEFT";
} else if (event.keyCode == 87 && d != "DOWN") {
d = "UP";
} else if (event.keyCode == 68 && d != "LEFT") {
d = "RIGHT";
} else if (event.keyCode == 83 && d != "UP") {
d = "DOWN";
} else if (event.keyCode == 32) {
// 暂停
suspendGame();
} else if (event.keyCode == 82) {
// 重新开始
restartGame();
} else if (event.keyCode == 66 && !isPlay) {
// 开始
startGame();
}
}
// 4.2 蛇的移动
function move(snake) {
let snakeX = snake[0].x;
let snakeY = snake[0].y;
let isEat = false;
if (d == "LEFT") snakeX -= speed;
if (d == "UP") snakeY -= speed;
if (d == "RIGHT") snakeX += speed;
if (d == "DOWN") snakeY += speed;
if (snakeX == food.x && snakeY == food.y) {
isEat = true;
let tdId = food.y * width + food.x;
tds[tdId].style.backgroundColor = 'lightgray';
score += 5;
scoreSpan.innerHTML = score;
food = getFood(snake);
}
let newHead = {
x: snakeX,
y: snakeY
};
// 检测是否越界,或者自己碰到自己
if (snakeX < 0 || snakeX >= width || snakeY < 0 || snakeY >= height || collision(newHead, snake)) {
endGame();
} else {
// 没有吃到,移除最后一个,在头部加一个,蛇长不变
// 吃到,if不成立,蛇长不减,蛇头加一,整体变长一
if (!isEat) {
snake.pop();
}
snake.unshift(newHead);
console.log("newHead=" + newHead.x + "," + newHead.y);
}
}
//显示画面
function show(snake, food) {
// 1 清除所有蛇和食物
tds.forEach(function (item) {
item.style.backgroundColor = '';
});
// 2 显示蛇
snake.forEach(function (item, id) {
let i = parseInt(item.x);
let j = parseInt(item.y);
console.log(i, j);
let tdId = j * width + i;
console.log(tdId);
if (id == 0) {
tds[tdId].style.backgroundColor = 'blue';
} else {
tds[tdId].style.backgroundColor = 'green';
}
});
// 3 显示食物
let tdFoodId = food.y * width + food.x;
tds[tdFoodId].style.backgroundColor = 'red';
}
// TODO 功能按钮的实现
// 开始游戏
function startGame() {
// clearInterval(timeId);
isPlay = true;
time = difficulty.value;
timeId = setInterval(() => {
move(snake);
show(snake, food);
}, time)
}
// 游戏结束
function endGame() {
clearInterval(timeId);
isPlay = false;
alert("游戏结束");
}
// 暂停游戏
function suspendGame() {
if (isPlay) {
clearInterval(timeId);
isPlay = false;
alert("游戏暂停");
}
}
// 重新开始
function restartGame() {
// 关闭之前的定时器
clearInterval(timeId);
score = 0;
scoreSpan.innerHTML = score;
snake = initSnake();
timeId = 0;
time = 300;
d = 'RIGHT';
isPlay = false;
food = getFood(snake);
show(snake, food);
}
// 产生随机数字(游戏开始时蛇的随机位置和食物的随机位置)
function getRandom() {
// 蛇头的产生范围限定在(2-44)
let ran_x = parseInt(Math.random() * (width - 4)) + 2;
let ran_y = parseInt(Math.random() * height);
return {
x: ran_x,
y: ran_y
};
}
// 检查食物随机产生的位置是否在蛇的身体上
function getFood(snake) {
while (true) {
let food = getRandom();
let flag = true;
for (let i = 0; i < snake.length; i++) {
let item = snake[i];
if (item.x == food.x && item.y == food.y) {
flag = false;
break;
}
}
if (flag) {
return food;
}
}
}
// 初始化蛇
function initSnake() {
let snake = [];
snake[0] = getRandom();
let x = snake[0].x;
let y = snake[0].y;
snake[1] = {
x: x - 1,
y: y
};
snake[2] = {
x: x - 2,
y: y
};
return snake;
}
// 检查是否碰到自己
function collision(head, array) {
for (let i = 0; i < array.length; i++) {
if (head.x == array[i].x && head.y == array[i].y) {
return true;
}
}
return false;
}
</script>
</body>
</html>
实现方法_2
完全不同的想法,很考验逻辑思维哟,做好准备
1 实现效果
没有做很好的美化,只是实现功能
2实现想法
2.1 蛇的存储
这里蛇的存储是借助了二维数组,利用二维数组的行列情况来记录位置,达到方式一中的直接在一维数组中记录{x,y}的效果;然后在二维数组里面全部初始化为undefined(其他的<0的值也行,主要是一中初始化标记),为甚小于0呢?因为这里我们给会在后面给二维数组里面赋值(0代表食物,1到n代表蛇,1是蛇头,后面紧邻的2,3,4,…n是蛇身)