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 相关文章推荐
用js判断浏览器是否是IE的比较好的办法
May 08 Javascript
JavaScript中的new的使用方法与注意事项
May 16 Javascript
一组JS创建和操作表格的函数集合
May 07 Javascript
php中给js数组赋值方法
Mar 10 Javascript
使用jquery组件qrcode生成二维码及应用指南
Feb 22 Javascript
分享12个非常实用的JavaScript小技巧
May 11 Javascript
大白话讲解JavaScript的Promise
Apr 06 Javascript
详解HTML5 使用video标签实现选择摄像头功能
Oct 25 Javascript
Javascript的console['']常用输入方法汇总
Apr 26 Javascript
Node.js 使用jade模板引擎的示例
May 11 Javascript
解决前后端分离 vue+springboot 跨域 session+cookie失效问题
May 13 Javascript
用webpack4开发小程序的实现方法
Jun 04 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
PHP Smarty生成EXCEL文档的代码
2008/08/23 PHP
解析PHP缓存函数的使用说明
2013/05/10 PHP
PHP实现的购物车类实例
2015/06/17 PHP
php中namespace use用法实例分析
2016/01/22 PHP
jQuery中绑定事件的命名空间详解
2011/04/05 Javascript
Node.js:Windows7下搭建的Node.js服务(来玩玩服务器端的javascript吧,这可不是前端js插件)
2011/06/27 Javascript
为EasyUI的Tab标签添加右键菜单的方法
2012/07/14 Javascript
jquery插件制作教程 txtHover
2012/08/17 Javascript
jquery attr方法获取input的checked属性问题
2014/05/26 Javascript
jQuery学习笔记之toArray()
2014/06/09 Javascript
jQuery解决input超多的表单提交
2015/08/10 Javascript
jquery实现九宫格大转盘抽奖
2015/11/13 Javascript
Node.js编写爬虫的基本思路及抓取百度图片的实例分享
2016/03/12 Javascript
高效利用Angular中内置服务$http、$location等
2016/03/22 Javascript
基于Vue.js实现数字拼图游戏
2016/08/02 Javascript
jQuery EasyUI 获取tabs的实例解析
2016/12/06 Javascript
jQuery实现分页功能(含ajax请求、后台数据、附完整demo)
2017/04/03 jQuery
Angular5中调用第三方js插件的方法
2018/02/26 Javascript
vue数组对象排序的实现代码
2018/06/20 Javascript
swiper.js插件实现pc端文本上下滑动功能示例
2018/12/03 Javascript
微信小程序实现Session功能及无法获取session问题的解决方法
2019/05/07 Javascript
微信小程序Echarts图表组件使用方法详解
2019/06/25 Javascript
解决vue刷新页面以后丢失store的数据问题
2020/08/11 Javascript
Vue实现菜单切换功能
2020/11/08 Javascript
[06:04]DOTA2国际邀请赛纪录片:Just For LGD
2013/08/11 DOTA
[59:08]DOTA2上海特级锦标赛C组小组赛#2 LGD VS Newbee第一局
2016/02/27 DOTA
Python实现PS图像调整之对比度调整功能示例
2018/01/26 Python
python3.X 抓取火车票信息【修正版】
2018/06/19 Python
django 2.2和mysql使用的常见问题
2019/07/18 Python
python使用多线程编写tcp客户端程序
2019/09/02 Python
vue.js刷新当前页面的实例讲解
2020/12/29 Python
html5实现多文件的上传示例代码
2014/02/13 HTML / CSS
C语言笔试题
2014/09/04 面试题
2014离婚协议书范文两篇
2014/09/15 职场文书
导游词之无锡梅园
2019/11/28 职场文书
在Docker容器中部署SQL Server
2022/04/11 Servers