[原生JS][面向过程]贪吃蛇
您好,本文主要描述如何用原生JS以面向过程的思想来编写一个贪吃蛇小游戏。
1. 准备工作
首先,我们需要新建一个文件夹,文件夹里创建3个文件,分别是 snake.html
来存放html代码,snake.css
存放css样式表代码,snake.js
存放js代码。
1.1 编写snake.html
首先我们在body标签里面建立一个地图div,在地图模型中嵌入蛇模型(包括蛇头与蛇身),并赋予蛇头id名方便我们后面JS操控蛇头div,而一条蛇有很多个蛇身模型div,所以我们给所有蛇身赋予相同的class名,最后还有食物模型div也是内嵌在地图里的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>贪吃蛇</title>
<!-- 引入snake.css文件 -->
<link rel="stylesheet" type="text/css" href="snake.css"/>
</head>
<body>
<!-- 地图 -->
<div id="wrap">
<!-- 蛇 -->
<div id="head"></div>
<div class="body" style="left:25px;top:0;">1</div>
<div class="body" style="left:50px;top:0;">2</div>
<!-- 食物 -->
<div class="food"></div>
</div>
<!-- 引入snake.js文件 -->
<script src="snake.js" type="text/javascript"></script>
</body>
</html>
1.2 编写snake.css
现在我们在浏览器中打开snake.html
文件是一片空白的,因为还没有给各元素赋予样式。那么事不宜迟,赶紧开始css样式表的编写吧!首先,给各元素定位设置,如地图设置相对定位relative,蛇与食物设置绝对定位absolute。由于边幅有限,这里我只设置比较简单的样式,各位有兴趣的话可以给各个元素赋予背景图片,让游戏更真实(笑。
#wrap{
width: 500px;
height: 500px;
border: 1px solid #000;
position: relative;
margin: auto
}
#head{
width: 25px;
height: 25px;
background: red;
position: absolute;
top: 0;
left: 50px;
z-index: 1;
}
.body{
width: 25px;
height: 25px;
position: absolute;
background: forestgreen;
}
.food{
width: 25px;
height: 25px;
background: yellow;
position: absolute;
}
2. 编写snake.js
经过一番准备后,我们现在开始进行js的编写。
2.1 获取所有元素
我们先要获取元素,才能操作元素。
//以id获取地图元素
var wrap = document.getElementById('wrap');
//以id获取蛇头元素
var head = document.getElementById('head');
//以class名获取所有蛇身元素(是一个数组)
var body = document.getElementsByClassName('body');
//以class名获取食物元素(只有一个)
var food = document.getElementsByClassName('food')[0];
2.2 设置所需变量
var box = 25;//蛇的移动步长
var boombol = true;//用于判断是否吃到食物
var timer = null;//让蛇不断移动的定时器
var runbol = true;//判断短时间内的多次按键
// 原始方向为向右
var l = 1;
var t = 0;
2.3 绑定键盘事件
利用键盘方向键改变蛇的移动方向,通过检测keyCode的值来确定按键,如 37代表 ←
、38代表 ↑
、39代表→
、40代表↓
。
需要达到的要求如下:
1️⃣蛇在向右走的时候,按左方向键或按右方向键时依旧往右走,其他三个按键同理
2️⃣多次快速按键无效
document.onkeyup = function(e){
var ee = e||window.event;
// 当移动函数没有执行完毕时按下键盘直接返回,使按键无效
if(!runbol){
return;
}
// 把 l改变为负值 移动时相乘就会为负则向左走
// 同时 t要置为0 不然会斜着走
// 其他按键也是同理
switch(e.keyCode){
case 37:{
// 把移动布尔值置否
runbol = false;
// 如果蛇正在往右走时按下左键 break接着无效
if(l == 1){break;}
l = -1;t = 0;break;
}
case 38:{
runbol = false;
if(t == 1){break;}
l = 0;t = -1;break;
}
case 39:{
runbol = false;
if(l == -1){break;}
l = 1;t = 0;break;
}
case 40:{
runbol = false;
if(t == -1){break;}
l = 0;t = 1;break;
}
}
}
2.4 设置蛇的移动函数
蛇移动有两种情况:
1️⃣蛇吃到食物
2️⃣蛇没有吃到食物
游戏结束判定有两种情况:
1️⃣蛇头撞到墙壁
2️⃣蛇头撞到自己的身体
3️⃣整条蛇的长度等于地图大小的通关庆祝🎉
function move(){
// 获取头部的左和上偏移量
var headX = head.offsetLeft;
var headY = head.offsetTop;
// 直接写蛇头下一个移动位置 用于判断撞墙
var nextx = headX + box * l;
var nexty = headY + box * t;
// 判断撞墙
if(nextx<0||nextx>475 ||nexty<0||nexty>475){
gameOver();
}
// 判断撞到蛇身 利用循环遍历每一个body
// 并判断当头的下一次移动位置为其中一个重合时即跟撞墙一样结束游戏
for(var i = 0;i<body.length;++i){
if(nextx==body[i].offsetLeft && nexty==body[i].offsetTop){
gameOver();
}
}
// 经过判断后蛇才移动
head.style.left = nextx + 'px';
head.style.top = nexty + 'px';
// 如果吃到食物 创造一个蛇身 位置为头部的偏移量
// 数组中的位置为蛇头之后
// 最后把布尔值恢复
if(!boombol){
var div = document.createElement('div');
div.className = 'body';
div.style.left = headX + 'px';
div.style.top = headY + 'px';
wrap.insertBefore(div,body[0]);
boombol = true;
}
// 没有吃到食物
// 移动最后一个蛇身div为头部偏移量 并在数组中把它插入到头部后面
else{
body[body.length-1].style.left = headX + 'px';
body[body.length-1].style.top = headY + 'px';
wrap.insertBefore(body[body.length-1],body[0]);
}
// 判断是否吃到食物
if(head.offsetLeft==food.offsetLeft&&head.offsetTop==food.offsetTop){
// 吃到就改变食物的位置
creatFood();
// 吃到则把布尔值置为否,在下次执行移动函数进入if语句
boombol = false;
}
// 移动完应该把布尔值置为真
// 表示移动函数执行完毕 可以接受下一个按键命令(第一次写错)
runbol = true;
}
function gameOver(){
// 撞到蛇身或墙壁 清除定时器 并弹窗 return
clearInterval(timer);
alert('Game Over');
return;
}
2.5 设置随机食物位置函数
需要达到的要求如下:
1️⃣随机位置不能在蛇的身上
故遍历蛇数组 如食物出现在蛇身上 则再调用一次创造食物函数
则产生函数递归 利用布尔值决定是否移动食物 出口为break
里面移动了一次 外面的就不移动
2️⃣随机位置不能越出地图
因为食物本来就有大小,如果最大值设置500则食物有可能会出现在525,超出了外框,所以位置随机数最大为地图大小减去食物大小
function creatFood(){
var bol = true;
var x = rnd(475,0);
var y = rnd(475,0);
// parseInt(x/25)拿到在随机数相对于25的位数,保证食物在格子上
var foodx = parseInt(x/25)*25;
var foody = parseInt(y/25)*25;
for(var i =0;i<snake.length-1;++i){
//循环判断食物是否出现在蛇身
if(foodx==snake[i].offsetLeft&& foody==snake[i].offsetTop){
creatFood();
bol = false;
break;
}
}
if(bol){
food.style.left = foodx + 'px';
food.style.top = foody + 'px';
}
}
// 随机函数
function rnd(max,min){
return Math.round(Math.random()*(max-min)+min);
}
2.6 最后
1️⃣调用 creatFood()函数
2️⃣启动定时器,定时器内调用函数为蛇移动函数move(),定时器时间间隔设置为200毫秒**
creatFood();
timer = setInterval(move,200);
3 总结
面向过程式的编程如果要加功能或者修改,步骤之间的循序可能都要进行大规模调整,如本例中要加入其他食物,使蛇吃到食物后产生不同的效果(加速,蛇身加两节等)就比较难做到了。
所以我们此时需要学习面向对象编程,因为其结构更加清晰,更容易扩展与良好的封装性。
当我们都两种编程思想都学会了,在面对需求时就可以根据他们的特点选择更好的解决方案啦。(其实还有其他n个编程思想)
最后,感谢您的阅读!