【JS基础案例】——贪吃蛇demo
做个小练习,用JS做个贪吃蛇demo~
思路:
①点击按钮开始,开始时要对UI和数据进行重置(清除上一次的游戏情况)
②蛇的位置用一个XY坐标数组表示
③蛇的身体其实就是个由多个XY坐标数组组成的大数组,蛇长大会把最新坐标不断往数组加入元素,因此蛇尾就是数组第一个元素
④爱心/星星 通过随机可以获得,设置一个定时器让爱心定时随机,其位置也是一个XY坐标数组
⑤蛇移动其实也是个定时器,移动时会把蛇尾位置去去除(蛇吃星星长度+1不用去除),也就是删除蛇身体数组第一个元素;
⑥判断蛇头和星星位置是否相等就是是否吃星星
⑦蛇触边、吃自己身体即死,也就是判断是否在格子内
⑧设置一个监听事件用来监听用户按键用以操作蛇移动方向
CSS
* {
box-sizing: border-box;
}
/* :root {
height: 100%;
} */
body {
margin: 0;
}
body>div {
border: 1px solid red;
background-color: bisque;
}
.parent {
margin: 300px auto;
width: 305px;
position: relative;
}
h1 {
text-align: center;
font-family: cursive;
color: #a919a1;
}
.gameBox {
width: 100%;
height: 305px;
border-collapse: collapse;
margin-bottom: 10px;
position: relative;
background-color: white;
}
.gameBox>tbody>tr>td {
border: 1px solid transparent;
}
#state,
#btnStart {
padding: 8px;
}
.control {
width: 305px;
position: relative;
}
#record {
outline: none;
width: 140px;
position: absolute;
right: 0;
left: auto;
font-size: 27px;
font-weight: bold;
text-align: center;
color: crimson;
}
#state {
user-select: none;
visibility: hidden;
outline: none;
}
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="./css/viperstyle.css">
</head>
<body>
<div>
<div class="parent">
<h1>贪吃蛇蛇</h1>
<!-- 游戏格子,使用了table表格 -->
<table class="gameBox">
<!-- 格子11*11,table补全即可 -->
tr*11>td*11
</table>
<div class="control">
<input id="btnStart" type="button" value="开始" placeholder="" />
<span id="state">游戏状态</span>
<input id="record" type="text" readonly="readonly" placeholder="计分板" value="0">
</div>
</div>
</div>
<body>
</html>
部分以table为例
JS
// 游戏结束?
let over = false;
// 获取按钮元素对象
let btn = document.getElementById("btnStart");
// 获取单元格DOM元素对象
let gBox = document.getElementsByClassName("gameBox")[0];
// 开始按钮点击事件
btn.onclick = () => {
btn.disabled = true;
state.innerHTML = "游戏已开始";
state.style.backgroundColor = "green";
state.style.visibility = 'visible';
/**** 界面重置代码 ****/
// 防止下次开始游戏时遗留上次游戏的数据,先对数据进行重置
resetGames();
// 鼠标失去焦点,表格gBox获得焦点
btn.blur()
gBox.focus();
// 开始~
play();
};
// 重置游戏函数,UI、Data
function resetGames() {
/**** 界面重置代码 ****/
_arrHeart = [];
_arrViper = [];
headX = 6;
headY = 6;
first = false;
_keyCode = 0;
over = false;
press = true;
record.value = '0';
for (let i = 0; i < rangeXY; i++) {
for (let j = 0; j < rangeXY; j++) {
gBox.rows[i].cells[j].style.backgroundColor = 'white';
}
}
}
//方格大小,X、Y最大都是11
let rangeXY = 11;
// 蛇初始坐标
let headX = 6,
headY = 6;
// 记录蛇的身体数据坐标的数组
let _arrViper = [];
// 记录爱心坐标的数组
let _arrHeart = [];
// 玩~
function play() {
// 生成蛇XY坐标数组,初始位置6,6
let arrStart = [headX, headY];
// 生成蛇
createPoint(arrStart, 'indigo');
// 保存到蛇身体记录数组中
_arrViper.push(arrStart);
// 调用随机生成爱心函数生成爱心
randomHeart();
// 蛇移动,调用点移动函数
viperMove();
}
// 用来在指定范围内随机的函数,返回一个随机数
function randNum(start, end) {
return Math.floor(start + Math.random() * (end - start - 10));
}
// 给表格中指定坐标单元格进行颜色设置,arrXY指定必须传入一个XY坐标数组
function createPoint(arrXY, strColor) {
// 因为表格其实就是数组,下标默认从0开始,所以要目标的坐标都要-1 后续代码中所有看到有要-1差不多都是这个情况
// arrXY[0] X
// arrXY[1] Y
let cell = gBox.rows[arrXY[0] - 1].cells[arrXY[1] - 1];
cell.style.backgroundColor = strColor;
}
// 生成爱心位置函数
function randomHeart() {
// 定时器,每隔10秒重新生成一次,10000毫秒=10秒
let timerHeart = setInterval(() => {
// 判断游戏是否game over
if (over) {
clearInterval(timerHeart);
return;
}
// 每次生成前移除之前的爱心点
// (也就是当爱心记录数组不为空时)
if (_arrHeart.length !== 0) {
//消除前还得考虑,如果这个爱心已经被蛇吃掉了,那么就不要消除,也就是格子是印度紫色的话
removePoint(_arrHeart, 'green');
}
// 将随机的坐标保存到爱心坐标数组
// 但随机之前要先判断随机的点是否是蛇的身体————思路:判断格子是否是印度紫色即可
_arrHeart = checkHeart(rangeXY, 'indigo');
// 生成爱心,爱心是绿色
createPoint(_arrHeart, "green");
}, 7000); //7000毫秒
}
// 移除爱心位置的函数,传入的参数为爱心XY坐标数组,colorIF:如果格子是这个颜色的话就消掉
function removePoint(arrXY, colorIF) {
let cell = gBox.rows[arrXY[0] - 1].cells[arrXY[1] - 1];
if (cell.style.backgroundColor === colorIF) {
// 消除颜色,也就是设为白色即可
cell.style.backgroundColor = 'white';
}
}
// 给爱心的位置进行随机并检查是否在蛇的身体内
function checkHeart(arrXY, color) {
let x = 0;
let y = 0;
let cell = null;
do {
x = randNum(arrXY, arrXY);
y = randNum(arrXY, arrXY);
cell = gBox.rows[x - 1].cells[y - 1];
}
// 判断格子颜色是否为印度紫色即可
while (cell.style.backgroundColor === color)
return [x, y];
}
// 第一次按下按键,false 未,true 已
let first = false;
// 记录按键代码的变量,37上,38左,39下,40右
let _keyCode = 0;
// press时候允许用户按下按键
let press = true;
// 事件监听用户按下按键
window.addEventListener("keydown", (e) => {
// JS是没有枚举类型的,因此使用了switch
if (btnStart.disabled) {
// press:是否接收用户按下按键?true按下的按键代码会被记录下来,false:按下没卵用~
// 这个主要是为了防止界面还未处理完,用户再次按下按键然后_keyCode被改变,导致后续界面处理的时候触发了bug
if (press) {
switch (e.keyCode) {
case 37:
// 蛇不能向反方向移动
if (_keyCode !== 39) { //下
_keyCode = 37; // 上
}
break;
case 38:
// 蛇不能向反方向移动
if (_keyCode !== 40) { //右
_keyCode = 38; // 左
}
break;
case 39:
// 蛇不能向反方向移动
if (_keyCode !== 37) { //上
_keyCode = 39; // 下
}
break;
case 40:
// 蛇不能向反方向移动
if (_keyCode !== 38) { //左
_keyCode = 40; // 右
}
break;
default:
return; // ****这里按了别的按键直接就返回了,后面的就不再判断了****
}
// 第一次按的时候会更改first为true;后面再按的时候,不会重复进行 first = true
if (_keyCode !== 0 && !first) {
first = !first;
}
// 当用户按下了按键,我们把接收用户按下按键设为false,也就是用户再次按下按键无效
// 只有当界面处理完成时才会将press 设为true,然后用户继续按下的按键就能被记录了
press = false;
}
}
});
// 蛇移动函数
function viperMove() {
// 只有当点击了开始,并且用户按下第一次指定的按键才生效
if (btnStart.disabled) {
// 创建一个定时器,用来做蛇移动
let timerMove = setInterval(() => {
switch (_keyCode) {
case 37:
headY -= 1; // 上 坐标x - 1
break;
case 38:
headX -= 1; // 左 坐标y - 1
break;
case 39:
headY += 1; // 下 坐标x + 1
break;
case 40:
headX += 1; // 右 坐标y + 1
break;
default:
return;
}
// 判断是否触边,触边直接Game Over...
// 也就是会干掉定时器
//直接try{}catch{}拉闸了
try {
// 发现格子是印度紫色的话,就意味着吃到自己的身体了,直接挂掉~
let cell = gBox.rows[headX - 1].cells[headY - 1];
if (cell.style.backgroundColor === 'indigo') {
throw err;
}
//判断是否吃掉爱心,吃掉长度+1,也就是不做清除;判断方法:对比坐标,坐标是数组嘛~那也就是对比数组是否相等咯
if ([headX, headY].join() !== _arrHeart.join()) {
// 先移除蛇尾,移除蛇尾前先把界面改了,再动记录蛇身体的数组,不然界面没法改
removePoint(_arrViper[0], 'indigo');
// 弹出蛇尾巴,也就是数组第一个元素
_arrViper.shift();
} else {
// 吃到爱心,计分板+1
let score = parseInt(record.value);
record.value = `${++score}`;
}
// 将移动位置记录到蛇身体数组中
createPoint([headX, headY], 'indigo');
_arrViper.push([headX, headY]);
// 当界面处理完成,就允许用户继续按键了;这里把press设为true即可~
press = true;
} catch {
/****** 游戏结束时要做的事 ******/
// Game Over~~~提示
state.innerHTML = "游戏结束!";
state.style.backgroundColor = 'crimson';
alert('Game over~~~');
// over = true,告诉前边的生成爱心的定时器,游戏结束撩~~~
over = true;
// 恢复按钮,以及一些提示
btn.disabled = false;
// 定时器消除
clearInterval(timerMove);
return;
}
}, 300); //300毫秒
}
}
...其实后续还可以封装成一个类~