如何用不到200行代码实现经典小游戏贪吃蛇,附源代码及详细实现思路
不多废话,直接上链接
链接:https://pan.baidu.com/s/1ZKtVNhzR4fIzNZSGWgFKQw?pwd=zglt
提取码:zglt
有需要的好兄弟们可以直接取用,想要了解一下编程思路的朋友们可以继续往下看,如有任何问题可以在评论区留言。
首先贪吃蛇小游戏主要需要实现一下几个功能:
(1)小蛇不断向前移动
(2)小蛇根据键盘按键改变移动方向
(3)小蛇撞墙或撞到自己后游戏结束
(4)地图内随机生成苹果
(5)小蛇吃到苹果后增加一格
接下来我们逐条实现
首先在页面内生成一个div,划出800*800的区域,并使其居中,将其class名设置为back
back的内容如下:
.back {border: 2px solid black;width: 800px;height: 800px;margin: 0 auto;}
生成效果:
然后在.back中添加两个属性 display: flex;和 flex-wrap: wrap;
.back {border: 2px solid black;width: 800px;height: 800px;margin: 0 auto;display: flex;flex-wrap: wrap;}
这两个属性是弹性盒子的内容,第一个属性设置为flex值将back声明为弹性盒子,第二个属性设置该盒子内容自动换行,设置这两个属性的原因我们接下来讲。
然后我们声明一个css样式名为.box,设置.box的宽高为40,并设置为怪异盒子
.box {width: 40px;height: 40px;box-sizing: border-box;}
box-sizing: border-box;用于将元素设置为怪异盒子,怪异盒子的特殊之处在于其总宽高固定,设置边框不会增加其实际宽高。
左为怪异盒子,右为普通盒子,给予20像素边框右者实际大小为140*140
这时页面中并没有叫.box的元素,接下来我们使用JavaScript代码给大背景添加小格子。
首先获取clas名为back的大背景。
var back = document.querySelector(".back");
接下来写一个执行400次的for循环,每次循环都用.creatElement()函数生成一个新的div标签,并将该标签的class名设置为box,然后使用.appendChild方法添加进大背景中:
for (let i = 0; i < 400; i++) { //添加地图格子var box = document.createElement("div");box.className = "box";back.appendChild(box);}
由于大背景宽高为800,小格子宽高为40,这样大背景中一共可以塞下20*20=400个小格子,由于我们给父元素设置为了弹性盒子且自动换行,这时当第一行被小格子塞满时多余的小格子就自动填充到下一行中(类似于浮动),这样逐行填充后就生成了一个400格的棋盘。
为了方便演示,我们给小格子添加一个边框,这是最终效果:
接下来我们开始画蛇,首先用querySelectAll()方法获取大背景中的400个小格子,并命名为box
var box = document.querySelectorAll(".box"); //获取所有格子
然后给格子添加颜色,需要注意的是,由于游戏开始时蛇头的位置是朝右的,所以蛇头一格并不是box[0],而是box[2],这里我设置蛇头为青色,身体为灰色。
//声明初始蛇样式box[0].style.background = "grey";box[1].style.background = "grey";box[2].style.background = "cyan";
最终效果:
现在我们有了一条三格长的小蛇,接下来就是如何让小蛇动起来,这里当然需要用到计时器
var timer = setInterval(move, 200);
setInterval()为间隔计时器,内部传入两个参数,第一个为调用的函数名,第二个为间隔时间(单位为毫秒),当前该计时器会每隔200毫秒调用一次函数,即让小蛇一秒走五次,如果觉得太慢也可以把这个值调小,这样小蛇会移动得更快。
然后我们需要写让小蛇动起来的函数move(),不过在此之前,先将小蛇的头和尾位置以及整体位置存储一下。
//声明蛇,并以行和列的形式存储蛇每一格的位置var snake = [{hang:0,lie: 0}, {hang:0,lie: 1}, {hang:0,lie: 2}];var head = {}; //声明蛇头head.hang = snake[2].hang;//获取当前蛇头位置head.lie = snake[2].lie;var tail = {}; //声明蛇尾tail.hang = snake[0].hang;//获取当前蛇尾位置tail.lie = snake[0].lie;
接下来我们来写函数move(),首先我们要理解一点,想让小蛇前进一个其实并不需要让小蛇每一段身体都前进,只需要让头部的位置前移一格,让原本头部的位置变为身体,再删除最后一节尾部即可。
这样我们就有了基本的实现思路,接下来就是如何用代码实现。
首先在移动前,我们需要判断蛇头是否撞墙,若撞墙则游戏直接结束无需再进行移动,判断是否撞墙,即判断新的头部位置行和列数值是否大于-1且小于20,不过向右移动时只需要判断右侧即可。
function move() {if (head.lie + 1 < 20) { //判断是否向右撞墙} else {alert("游戏结束"); //撞墙,游戏结束;clearInterval(timer);//清除定时器,让小蛇停止移动};}
由于头部遇到的情况比较复杂(撞墙,吃苹果,撞自己等),所以这里我们将头部移动单独封装成一个函数moveHead(),不过在调用该函数前,先设置好新头部的位置。
function move() {if (head.lie + 1 < 20) { //判断是否向右撞墙head.列 += 1;//向右移动即头部列位置+1moveHead();//调用头部移动函数} else {alert("游戏结束"); //撞墙,游戏结束;clearInterval(timer); //清除定时器,让小蛇停止移动};}function moveHead() {}//声明头部移动函数
接下来正式开始移动小蛇,首先获取到新的头部位置在数组中的下标,由于地图每行为20格,那么位置下标=行数值*20+列数值,获取后将新头部格子刷成青色。
function moveHead() { //声明头部移动函数//获取头部位置在数组中的下标,并储存为positonvar position = head.hang * 20 + head.lie;//利用下标在box数组中定位新的头位置,并将其刷成青色box[position].style.background = "cyan";}
当前效果:
然后我们需要将原蛇头位置的青色变为灰色,原蛇头位置即当前snake数组中的最后一位,即数组长度-1的位置(数组下标从0开始,所以减一)。
function moveHead() { //声明头部移动函数//获取新头部位置在数组中的下标,并储存为positonvar position = head.hang * 20 + head.lie;//利用下标在box数组中定位新的头位置,并将其刷成青色box[position].style.background = "cyan";//获取原有头部位置的下标,并储存为positonposition = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;//将原蛇头位置刷成灰色box[position].style.background = "grey";}
当前效果:
接下来就是删除蛇尾,蛇尾位置即snak[0]存储的位置。
function moveHead() { //声明头部移动函数//获取新头部位置在数组中的下标,并储存为positonvar position = head.hang * 20 + head.lie;//利用下标在box数组中定位新的头位置,并将其刷成青色box[position].style.background = "cyan";//获取原有头部位置的下标,并储存为positonposition = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;//将原蛇头位置刷成青色box[position].style.background = "grey";//获取蛇尾位置position = snake[0].hang * 20 + snake[0].lie;//删除蛇尾颜色box[position].style.background = "";}
最终效果:
这时我们还需要做最后的处理工作,虽然页面中的snake位置已经变化了,但是snake数组中存储的位置并没有改变,所以现在我们要修改snake数组中的内容。
var newhead = { //处理对象浅复制问题"hang": head.hang,"lie": head.lie};snake.push(newhead) //将新的头位置添加进数组末尾snake.shift() //删除数组中尾部位置
由于这里涉及到对象浅复制问题,所以每次添加都需要声明一个新对象,感兴趣的朋友们可以自行了解一下。
最终效果:
这样小蛇的移动就完成了,接下来我们来控制小蛇的移动方向。
首先我们声明一个变量,设置其数值为ArrowRight,为什么是这个值我们接下来讲。
var ahead = "ArrowRight"; //声明蛇的前进方向
然后我们给页面添加一个监听事件,监听鼠标按下,并传入一个参数e。
var ahead = 39; //声明蛇的前进方向document.addEventListener("keydown", (e) => {})//添加键盘按下的监听事件并传入e
这里传入的e可以简单理解为事件本身,其内部存储了触发该事件时的一系列数据,这里我们需要用到其中的一个数据.key,该属性存储了触发事件的按键名称。
打印e.key时分别点击小键盘上下左右四个键时返回的名称:
这就是我们设置ahead的值为ArrowRight的原因,在JavaScript中不同的按键有不同的名称,我们只需要根据名称就可以利用分支语句决定小蛇前进的方向,同时我们还可以添加一个判断语句,防止小蛇出现180度调头的情况(这里使用了JavaScript的三目运算,感兴趣的朋友们可以自行了解)。
document.addEventListener("keydown", (e) => { //添加键盘按下的监听事件并传入eswitch (e.key) { //添加分支语句,设置移动方向case "ArrowUp": //判断e.key是否等于该关键字ahead != "ArrowDown" ? ahead = e.key : ""; //判断并给ahead赋新值break; //结束Switch执行,防止下方的代码影响结果case "ArrowRight":ahead != "ArrowLeft" ? ahead = e.key : ""; //判断并给ahead赋新值break;case "ArrowDown":ahead != "ArrowUp" ? ahead = e.key : ""; //判断并给ahead赋新值break;case "ArrowLeft":ahead != "ArrowRight" ? ahead = e.key : ""; //判断并给ahead赋新值break;}console.log(e.key);})
然后再给前面的move()函数也添加一个Switch分支语句,通过ahead的值判断头部向哪个方向移动,向上移动时头部行位置减一,向下移动时头部行位置加一,向左和向右则为头部列位置减一和加一。
function move() {switch (ahead) { //通过ahead判断移动方向case "ArrowUp": //是否向上if (head.hang - 1 > -1) {head.hang -= 1; //向上移动即头部行位置-1moveHead();} else {alert("游戏结束");clearInterval(timer);};break;case "ArrowDown": //是否向下if (head.hang + 1 < 20) {head.hang += 1; //向下移动即头部行位置-1moveHead();} else {alert("游戏结束");clearInterval(timer);};break;case "ArrowLeft": //是否向左if (head.lie - 1 > -1) {head.lie -= 1; //向左移动即头部列位置-1moveHead();} else {alert("游戏结束");clearInterval(timer);};break;case "ArrowRight": //是否向右if (head.lie + 1 < 20) {head.lie += 1; //向右移动即头部列位置+1moveHead();} else {alert("游戏结束");clearInterval(timer);};break;}}
由于目前我们可以通过按键来改变ahead的值,而move()函数的移动方向由ahead决定,所以我们就间接实现了通过按键控制小蛇的移动方向,效果如下:
下一步就是声明苹果了,首先我们创建函数addApple(),并声明对象apple存储苹果的位置
var apple = {};function addapple() {}
接下来我们需要用到一个数学方法Math.random(),该方法的作用是声明一个0到1之间的随机浮点数,由于我们的地图大小为20*20格,所以我们将生成的数字乘20并取整,这样就得到了一个0到20之间的随机整数(向下取整所以不含20)。
function addapple() { //随机生成苹果函数apple.hang = parseInt(Math.random() * 20); //随机生成行apple.lie = parseInt(Math.random() * 20); //随机生成列var position = apple.lie + apple.hang * 20 //通过行和列随机生成苹果box[position].style.background = "red"; //在随机确定的位置刷红色}
这时我们调用函数,苹果就会出现在地图中
不过由于目前苹果的位置完全随机,所以有可能会直接生成在小蛇身上,这是我们需要避免的问题,即当随机生成的苹果位置有颜色时重新生成,这里我们可以通过使用一个简单的递归来解决该问题。
var apple = {}; //声明对象存储苹果位置function addapple() { //随机生成苹果函数apple.hang = parseInt(Math.random() * 20); //随机生成行apple.lie = parseInt(Math.random() * 20); //随机生成列var position = apple.lie + apple.hang * 20 //通过行和列随机生成苹果if (box[position].style.background != "") { //判断随机生成的苹果位置是否有颜色addapple(); //若有颜色重新调用该函数生成} else {box[position].style.background = "red"; //若无颜色则刷红}}
这时当生成的随机位置有颜色时该函数就会重新被调用,生成一个新的苹果位置,若没有颜色则结束递归,并在该位置刷红色。
然后就是最后一步,当小蛇吃到苹果时变长一格,同时生成新的苹果位置,其实这一功能很好实现,我们在移动小蛇时会删除小蛇的尾部,现在只需要让小蛇头部碰到苹果时尾部不删除即可增加一格长度,所以我们需要对movehead()函数进行修改:
function moveHead() {//获取新头部位置在数组中的下标,并储存为positonvar position = head.hang * 20 + head.lie;if (box[position].style.background == "red") { //判断新的头部位置是否为红色snake.unshift({"hang": snake[0].hang,"lie": snake[0].lie}); //若为红色则复制一份蛇尾,删除时便会保留蛇尾addapple(); //调用函数,生成新苹果}box[position].style.background = "cyan";position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;box[position].style.background = "grey";position = snake[0].hang * 20 + snake[0].lie;box[position].style.background = "";var newhead = {"hang": head.hang,"lie": head.lie};snake.push(newhead)snake.shift() //删除数组中尾部位置}
同时这里我们还可以添加一个判断,若新蛇头位置为灰色,则说明小蛇撞到了自己,这时可以直接结束游戏:
function moveHead() {//获取新头部位置在数组中的下标,并储存为positonvar position = head.hang * 20 + head.lie;if (box[position].style.background == "red") { //判断新的头部位置是否为红色snake.unshift({"hang": snake[0].hang,"lie": snake[0].lie}); //若为红色则复制一份蛇尾,删除时便会保留蛇尾addapple(); //调用函数,生成新苹果} else if (box[position].style.background == "grey") {//判断新的头部位置是否为灰色alert("游戏结束");//若为灰色则直接结束游戏clearInterval(timer);}box[position].style.background = "cyan";position = snake[snake.length - 1].hang * 20 + snake[snake.length - 1].lie;box[position].style.background = "grey";position = snake[0].hang * 20 + snake[0].lie;box[position].style.background = "";var newhead = {"hang": head.hang,"lie": head.lie};snake.push(newhead)snake.shift() //删除数组中尾部位置}
到这里一款可以正常游玩的贪吃蛇小游戏就算基本完工了,删除注释的话总代码量可能都不到150行,虽然有很多细节有待优化;
那么本期的内容就到这里,如有任何问题欢迎在评论区留言,up都会尽量解答。
----------------------------------------------分割线-----------------------------------------------
后续我又对代码进行了一点优化,首先是添加了分数和规则
规则不难写,分数的话只需要在全局声明一个变量number 来存储分数,每次撞到苹果后该数值加一即可。
var p = document.querySelector("p");//获取第一行,分数行
var number = 0; //存储当前分数function moveHead() { //声明头部移动函数.....if (box[position].style.background == "red") { //判断新的头部位置是否为红色snake.unshift({"hang": snake[0].hang,"lie": snake[0].lie}); //若为红色则复制一份蛇尾,删除时便会保留蛇尾addapple(); //调用函数,生成新苹果number++; //number加一分p.innerHTML = number + "分"; //改变p标签内容} else if (box[position].style.background == "grey") { //判断新的头部位置是否为灰色alert("游戏结束"); //若为灰色则直接结束游戏clearInterval(timer);}.....}
然后是暂停功能,只需要在addEventListener监听事件的Switch分支中添加一个空格分支,单击空格时弹出一个窗口即可暂停,当浏览器显示窗口时计时器是停止运行的。
document.addEventListener("keydown", (e) => { //添加键盘按下的监听事件并传入eswitch (e.key) { //添加分支语句,设置移动方向.....case " ":alert("暂停中。。。");}console.log(e.key);});
最后我把蛇和苹果的颜色放到了代码最上方,这样可以直接修改所有代码中的颜色。
如何用不到200行代码实现经典小游戏贪吃蛇,附源代码及详细实现思路相关推荐
- Android 300行代码实现经典小游戏贪吃蛇
前言 贪吃蛇算是一个非常经典的小游戏了,本人00后,初次游玩是在小时候用诺基亚手机进行游玩的.这次算是复刻一下经典hhh,贪吃蛇算是一个制作起来非常简单的小游戏,本文使用Kotlin语言进行开发,用J ...
- Java控制台游戏~600行代码实现打怪小游戏
Java控制台游戏~600行代码实现打怪小游戏(多图预警) 一,先放个启动界面(一些英雄,怪物技能介绍跟装备属性都写在里边): 二,在这个简单的小游戏里,你可以体验到: 1.打怪: 2.随机玩法寻宝: ...
- Python命令行小游戏—贪吃蛇
Python命令行小游戏-贪吃蛇 前言 一.贪吃蛇游戏初始界面及地图 1.游戏初始界面 2.游戏地图 二.命令符的设置.输出刷新和按键检测 1.库支持 2.c语言代码 3.Python代码(变量初始化 ...
- c++编写手机小游戏代码_经典小游戏大集合(C++ 源码)
[实例简介] 五子棋 贪吃蛇 俄罗斯方块 黑白棋 连连看 推箱子 扫雷等7个小游戏 C++源码 VC6.0 下编译运行. [实例截图] [核心代码] 经典小游戏大集合(C源码) └── 经典小游戏大集 ...
- python猜拳小游戏代码200行左右_python简单小游戏代码,python简单小游戏代码200行...
如何看懂python杨辉三角代码? 第一步先找规律,抽象化问题.首先我们观察到,第一行为[1],我们直接赋给一个变量:初始化数列 p = [1].核心点是这个除去首位两个 [1] 的中间部分:[p[0 ...
- c++编写手机小游戏代码_玩过自己开发的贪吃蛇吗?点这里,教你用Python写一个贪吃蛇小游戏!(附源代码)...
后台回复'0816',加入Python交流群~ 往日回顾:Python必读好书,这9本份量十足~ 本文代码的实现效果,获取源代码,请直接滑到文末~都说Python除了生孩子,什么都能干.咱们今天,就用 ...
- python界面小游戏贪吃蛇_用Python实现童年贪吃蛇小游戏功能的实例代码
贪吃蛇作为一款经典小游戏,早在 1976 年就面世了,我最早接触它还是在家长的诺基亚手机中. 尽管贪吃蛇的历史相对比较久远,但它却有着十分顽强的生命力,保持经久不衰,其中很重要的原因便是游戏厂家不断的 ...
- 面向对象编程java小游戏_JavaScript面向对象编程小游戏---贪吃蛇代码实例
1 面向对象编程思想在程序项目中有着非常明显的优势: 1- 1 代码可读性高.由于继承的存在,即使改变需求,那么维护也只是在局部模块 1-2 维护非常方便并且成本较低. 2 这个demo是采用了面向 ...
- python小游戏代码200行左右,python编程小游戏代码
大家好,本文将围绕python小游戏代码200行左右展开说明,小游戏程序代码python是一个很多人都想弄明白的事情,想搞清楚python编程小游戏代码需要先了解以下几个事情. 1.python简单小 ...
最新文章
- PyQt5 简易计算器
- 福建省计算机应用考试成绩,福建省高等学校非计算机专业学生计算机应用水平等级考试成绩查询...
- 六年级上学期计算机上册教案,六年级上册数学全册教案
- eclipse中svn和TortoiseSVN更改账号的方法
- .NET6之MiniAPI(十):基于策略的身份验证和授权
- flutter调用api_如何在Flutter(REST API)中进行API调用
- CreateChildControls 里动态生成的控件 PostBack 之后不能保持状态
- 软考网络工程师考试大纲
- WSO2 ESB 5.0.0 配置消息存储
- 魔兽世界插件开发:Beginning Lua with World of Warcraft Add-ons 中文翻译及学习 (1.1)
- gmail设置双重验证后,第三工具无法登陆解决
- 微服务系列之单体架构
- 简单好用、且永久免费的内网穿透工具
- 国外有哪些知名的游戏资讯网站或博客?
- 学习数据数据结构的意义
- Cycle3-Group1
- Netgear wndr3700v2 路由器刷OpenWrt打造全能服务器(一)序章
- GC8837 DFN8 12V直流电机驱动芯片 完美替代TI DRV8837
- TP-LINK 150M无线宽带路由器TL-WR740N 路由器当无线AP(交换机用)
- qt qled_OLED与QLED:有什么区别?
热门文章
- 选择软件测试,你后悔吗?
- LDO与三端稳压器详解
- Android 上实现非root的 Traceroute -- 非Root权限下移植可执行二进制文件 脚本文件
- 云计算安全测评:云原生安全
- bread 块设备读取函数解析(1)
- 运用Python+Pygame开发坦克大战游戏_版本V1.01
- 【bzoj3295】动态逆序对
- streaing-kafka
- IDEA java编译中出现了Exception in thread “main java.lang.UnsupportedClassVersionError
- 金属箔式应变片性能—单臂电桥