20行js代码实现的贪吃蛇小游戏


Posted in Javascript onJune 20, 2017

前言

最近在csdn上看到一位大神用20行代码就写出了一个贪吃蛇的小游戏,感觉被惊艳到了,就试着读了一下这段代码,阅读过程中不断为作者写法的巧妙而叫绝,其中我发现自己对运算符优先级和一些js的技巧不是很清楚,所以看完之后决定把思路分享出来,方便和我一样的小白学习。

我对代码稍稍做了些修改,并添加了一些注释,方便理解。

示例代码

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>贪吃蛇重构</title>
 <style>
  body {
   display: flex;
   height: 100vh;
   margin: 0;
   padding: 0;
   justify-content: center;
   align-items: center;
  }
 </style>
</head>
<body>
 <canvas id="can" width="400" height="400" style="background-color: black">对不起,您的浏览器不支持canvas</canvas>
 <script>
 
  var snake = [41, 40],  //snake队列表示蛇身,初始节点存在但不显示
   direction = 1,   //1表示向右,-1表示向左,20表示向下,-20表示向上
   food = 43,    //食物的位置
   n,      //与下次移动的位置有关
   box = document.getElementById('can').getContext('2d');
         //从0到399表示box里[0~19]*[0~19]的所有节点,每20px一个节点
  function draw(seat, color) {
   box.fillStyle = color;
   box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);
         //用color填充一个矩形,以前两个参数为x,y坐标,后两个参数为宽和高。
  }
  document.onkeydown = function(evt) { 
         //当键盘上下左右键摁下的时候改变direction
   direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;
  };
  !function() {
   snake.unshift(n = snake[0] + direction); 
         //此时的n为下次蛇头出现的位置,n进入队列
   if(snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || direction == 1 && n % 20 == 0 || direction == -1 && n % 20 == 19) {
         //if语句判断贪吃蛇是否撞到自己或者墙壁,碰到时返回,结束程序
    return alert("GAME OVER!");
   }
   draw(n, "lime");  //画出蛇头下次出现的位置
   if(n == food) {   //如果吃到食物时,产生一个蛇身以外的随机的点,不会去掉蛇尾
    while (snake.indexOf(food = ~~(Math.random() * 400)) > 0);
    draw(food, "yellow");
   } else {    //没有吃到食物时正常移动,蛇尾出队列
    draw(snake.pop(),"black");
   }
   setTimeout(arguments.callee, 150);  
         //每隔0.15秒执行函数一次,可以调节蛇的速度
  }();
 </script>
</body>
</html>

首先,我们要知道做一个贪吃蛇最主要的是什么,是做出蛇活动的场所和如何使蛇动起来。

我们先看蛇活动的场所:

<!-- html -->
<canvas id="can" width="400" height="400" style="background-color: black">
 对不起,您的浏览器不支持canvas
</canvas>
<!-- js -->
box = document.getElementById('can').getContext('2d');

这是一个400px*400px的canvas,思路是以20px*20px为一个方格,组成20行20列的方阵,总共400格,然后绿色填充的格子表示蛇身,用黄色表示食物。这400个格子和数字0~399一一对应,对应的方式就是以20作为基数,n / 20再取整表示第几行,n % 20表示第几列。行数和列数都用0~19表示。

蛇用一个一维数组表示,每个值都是这400个数中的一个,用var snake = [41, 40];初始化这条蛇,索引0为蛇头。food表示食物的位置,direction表示蛇头下一次运动的转向。蛇的运动就用添加和删除数组元素来实现,每次执行绘制蛇头,去掉蛇尾,循环执行使蛇运动。

下边从函数运行的起始处(39行)开始看:

!function() {}();

什么鬼?这其实是立即执行函数IIFE的另一种写法。关于IIFE,这篇文章讲的挺不错的。继续往下看,给蛇头添加一个节点n,其值为当前蛇头的值加direction的值,如此一来就能理解为什么要用20表示向下,-20表示向上了。再下一行是一个if语句,其中值得提醒的是&&的优先级高于||,这个语句就是判断即将出现的蛇头是不是属于蛇身,或者跑到box外边去了。如果没有死亡,就把这个蛇头绘制出来,下边就看看绘制的代码:

function draw(seat, color) {
 box.fillStyle = color;
 box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);
}

填充时填充18*18的像素,留1px边框。 .fillRect()中第一个参数就是要绘制的矩形的x坐标seat % 20 *20 + 1,即先得到所要绘制的矩形块在方阵中的位置:第~~(seat / 20)行,第seat % 20列,再* 20 + 1具体到像素点。可能这个~~有点难理解,我感觉在这里的用处应该和Math.floor()差不多,对一个浮点型的数取反再取反,得到的数就是去掉小数位的整数了。

回到47行,又是一个判断语句,判断下次蛇头出现的位置是不是和当前的食物的位置相同,如果相同,生成下一个食物,食物的位置为一个随机数,但是要判断这个点不是出现在当前的蛇身上,绘制食物。如果没有吃到食物,即蛇在正常运动时,每向前一次,将蛇尾弹出,并利用其返回值将这个点重新绘制为黑色。

最后的setTimeout,循环执行当前函数,设置执行周期来调蛇的移动速度。

到了这里,我们发现这条蛇已经可以动了,加上键盘的操作就完成了:

document.onkeydown = function(evt) { 
 direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;
};

将这个函数绑定到键盘事件上,evt || event用法的原因这里有详细的解释,是为了兼容ie。

三目运算符?前边的判断语句又可分为两部分:

  1. snake[1] - snake[0]的值应该就是-direction,按理说此处写成-direction应该和原来是一个效果,那为什么没有这么做呢,因为如果这样写,玩家可能在一个函数周期中多次改变direction的值,最后使得direction和当前真正的运动方向不一致,导致游戏崩溃。
  2. 在==后边, [-1, -20, 1, 20][(evt || event).keyCode - 37]中前边的[]是一个数组,后边的[]是取索引,左上右下四个键的keyCode分别为37, 38, 39, 40,计算后的索引为0, 1, 2, 3,使方向键与direction的取值对应起来。这里的巧妙之处在于如果按下的按键不是方向键,在数组中将得不到对应的值,返回undefine。此时,由于之后的||运算符,n会取到direction原来的值。

再用三目运算符来判断,如果按键方向不是反方向,就更新direction的值。

以上就是本篇的全部内容啦,虽然都是一些基础的东西,但是感觉还是挺好玩的。要是哪里理解的不对还希望指证出来,共同进步。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript GUID生成器实现代码
Oct 31 Javascript
jquery判断字符输入个数(数字英文长度记为1,中文记为2,超过长度自动截取)
Oct 15 Javascript
初窥JQuery(二) 事件机制(1)
Nov 25 Javascript
js实现按一下删除键删除整个单词附demo
Sep 05 Javascript
jquery实现增加删除行的方法
Feb 03 Javascript
终于实现了!精彩的jquery弹幕效果
Jul 18 Javascript
AngularJS  双向数据绑定详解简单实例
Oct 20 Javascript
原生的强大DOM选择器querySelector介绍
Dec 21 Javascript
Vue2.0表单校验组件vee-validate的使用详解
May 02 Javascript
基于EasyUI的基础之上实现树形功能菜单
Jun 28 Javascript
create-react-app 修改为多入口编译的方法
Aug 01 Javascript
angular 未登录状态拦截路由跳转的方法
Oct 09 Javascript
详解vue-resource promise兼容性问题
Jun 20 #Javascript
ionic2自定义cordova插件开发以及使用(Android)
Jun 19 #Javascript
详解vue2.0 transition 多个元素嵌套使用过渡
Jun 19 #Javascript
vue中如何实现变量和字符串拼接
Jun 19 #Javascript
js实现随机数字字母验证码
Jun 19 #Javascript
解决vue-cli中stylus无法使用的问题方法
Jun 19 #Javascript
AngularJS  ng-repeat遍历输出的用法
Jun 19 #Javascript
You might like
深入理解:XML与对象的序列化与反序列化
2013/06/08 PHP
解析yahoo邮件用phpmailer发送的实例
2013/06/24 PHP
PHP二进制与字符串之间的相互转换教程
2016/10/14 PHP
PHP多进程简单实例小结
2019/11/09 PHP
JavaScript中的History历史对象
2008/01/16 Javascript
jquery 表单下所有元素的隐藏
2009/07/25 Javascript
百度留言本js 大家可以参考下
2009/10/13 Javascript
JavaScript利用正则表达式去除日期中的-
2014/06/09 Javascript
浅谈JavaScript 的执行顺序
2015/08/07 Javascript
每天一篇javascript学习小结(面向对象编程)
2015/11/20 Javascript
JavaScript算法系列之快速排序(Quicksort)算法实例详解
2016/09/04 Javascript
深入理解Javascript中的valueOf与toString
2017/01/04 Javascript
利用nodejs监控文件变化并使用sftp上传到服务器
2017/02/18 NodeJs
js学习总结之dom2级事件基础知识详解
2017/07/27 Javascript
JavaScript控制浏览器全屏显示简单示例
2018/07/05 Javascript
ng-alain表单使用方式详解
2018/07/10 Javascript
详解无限滚动插件vue-infinite-scroll源码解析
2019/05/12 Javascript
layui动态表头的实现代码
2019/08/22 Javascript
vue子组件改变父组件传递的prop值通过sync实现数据双向绑定(DEMO)
2020/02/01 Javascript
js根据后缀判断文件文件类型的代码
2020/05/09 Javascript
[47:12]TFT vs Secret Supermajor小组赛C组 BO3 第三场 6.3
2018/06/04 DOTA
python使用正则表达式检测密码强度源码分享
2014/06/11 Python
python实现计算资源图标crc值的方法
2014/10/05 Python
Python中 传递值 和 传递引用 的区别解析
2018/02/22 Python
Python求解排列中的逆序数个数实例
2020/05/03 Python
Pycharm修改python路径过程图解
2020/05/22 Python
Windows下PyCharm配置Anaconda环境(超详细教程)
2020/07/31 Python
在Python中字典按值排序的实现方法
2020/11/12 Python
termux中matplotlib无法显示中文问题的解决方法
2021/01/11 Python
李维斯牛仔裤英国官方网站:Levi’s英国
2019/10/10 全球购物
投标承诺书范本
2014/03/27 职场文书
体育教师个人总结
2015/02/09 职场文书
后勤个人工作总结
2015/02/28 职场文书
2015年秘书个人工作总结
2015/04/25 职场文书
学校世界艾滋病日宣传活动总结
2015/05/05 职场文书
Feign调用传输文件异常的解决
2021/06/24 Java/Android