注释详解:用20行代码编程的js贪吃蛇动画游戏!javascript贪吃蛇。

偶然看到的,

对看不懂JS的偶来说,的确很牛!大神级的崇拜。

静静的看了一天,爽!

似懂非懂。  ^_^


20行JS代码编写的贪吃蛇,

代码粘贴如下:

<!doctype html>  
<html>  
<body>  
    <canvas id="can" width="400" height="400" style="background: black"></canvas>  
    <script>  
        var sn = [ 42, 41 ], dz = 43, fx = 1, n, ctx = document.getElementById("can").getContext("2d");  
        function draw(t, c) {  
            ctx.fillStyle = c;  
            ctx.fillRect(t % 20 * 20 + 1, ~~(t / 20) * 20 + 1, 18, 18);  
        }  
        document.onkeydown = function(e) {  
            fx = sn[1] - sn[0] == (n = [ -1, -20, 1, 20 ][(e || event).keyCode - 37] || fx) ? fx : n  
        };  
        !function() {  
            sn.unshift(n = sn[0] + fx);  
            if (sn.indexOf(n, 1) > 0 || n<0||n>399 || fx == 1 && n % 20 == 0 || fx == -1 && n % 20 == 19)  
                return alert("GAME OVER");
            draw(n, "Lime");  
            if (n == dz) {  
                while (sn.indexOf(dz = ~~(Math.random() * 400)) >= 0);  
                draw(dz, "Yellow");  
            } else  
                draw(sn.pop(), "Black");  
                setTimeout(arguments.callee, 130);  
        }();  
    </script>  
</body>  
</html>



下面来解析一下:(被吹成)20行js代码实现的贪吃蛇小游戏(我数了又数,的确JS部分就20行!呵呵)


我不会代码,但据会代码的人说:搞清代码在做什么很容易,搞清代码实现了什么很难!  PS:反正我不懂  ^_^||


要搞清楚这个贪吃蛇,是什么原理实现的,其实很简单!

你把画布<canvas>的底色改成红色再运行看看!

懂了吧?

原来黑色下面有阴谋!障眼法嘛!晕!

一切的玄机都在你看不见的地方! ^_^

20行代码贪吃蛇大揭密:前面画一个(青色),后面改一个(改成与底色相同),动画就出来了。


==== 以下是 ctrl+C配合ctrl+V “生成”的内容 ==== 


注悉详解贪吃蛇:

var sn = [42, 41], // 存放贪食蛇的坐标, 第一个是头, 最后一个是尾, 初始的蛇长度是2, 坐标[2, 2], [1, 2]
  dz = 43, // 食物的坐标, 初始[3, 2]
  fx = 1, // 蛇前进的方向
  n, // 蛇头位置
  ctx = document.getElementById("can").getContext("2d"); // 画布// 在画布上画背景或者贪食蛇方格// 第一个参数是坐标信息, 第二个是颜色信息(用来区分背景和蛇)function draw(t, c) {
  ctx.fillStyle = c;  // fillReact的签名是: fillRect(x: number, y: number, w: number, h: number): void;
  // 所以画的方格的宽高都是18, 加上默认的lineWidth=1, 所以每个方格的长宽都是20
  // 参数t用一个标量标志二维坐标信息, t % 20即t/20的余数部分表示横坐标是第几个方格
  // <int>(t/20) 即t/20的商部分表示纵坐标是第几个方格
  // 这意味着游戏画布宽度固定为20, 坐标为[0, 19]
  // 需要注意的是纵坐标的方向向下
  // 所以初始的贪食蛇`[42, 41]`的头和尾的实际坐标分别是[2, 2], [1, 2], 也就是在画布的右上方
  // 所以最初的运动方向是向右
  // 所以前进方向`fx`的含义是: -1向左, -20右上, 1向右, 20向下
  ctx.fillRect(t % 20 * 20, ~~(t / 20) * 20, 18, 18);
}// 监听用户按键, 按键时改变下一次paint时蛇的方向document.onkeydown = function (e) {  // 方向键 左, 上, 右, 下的keyCode分别为37, 38, 39, 40
  // 如果用户按下这几个键, 会改变蛇头下一次的位置到对应的方向上
  // 如果不是这几个键, 则不会变动
  n = [-1, -20, 1, 20][(e || event).keyCode - 37] || fx  // 如果按键的方向和蛇当前前进的方向相反, 则不改动
  fx = sn[1] - sn[0] == n ? fx : n
};// 根据蛇的位置及运动方向更新画布!function update() {  // 更新蛇头的位置
  n = sn[0] + fx;  // 蛇每前进一步, 只需要把蛇尾放到蛇头处即可
  // 这里增加了蛇头
  sn.unshift(n);  // 边界判断
  if (
    sn.indexOf(n, 1) > 0 // 蛇头碰到了自身
    || n < 0 // 或者纵坐标小于0
    || n > 399 // 或者纵坐标大于等于20
    || fx == 1 && n % 20 == 0 // 或者蛇向右运动并且蛇头横坐标是0(注意是更新过的坐标)
    || fx == -1 && n % 20 == 19 // 或者蛇头向左运动并且蛇头横坐标是19, 也就是在最右边
  ) {    // 碰到边界时结束游戏, 边界是
    // x in [0, 20] & y in [0, 20]
    // 并且不可碰到蛇身
    return alert("GAME OVER");
  }  // 更新蛇头
  draw(n, "Lime");  if (n == dz) {    // 如果蛇头位置和食物的位置相同, 则更新食物的位置
    // 新的食物的位置不能在蛇身上
    while (sn.indexOf(dz = ~~(Math.random() * 400)) >= 0);    // 更新食物
    draw(dz, "Yellow");
  } else
  // 去掉蛇尾(画成背景)
    draw(sn.pop(), "Black");  // 每隔150ms更新
    setTimeout(update, 150);
}();



其实,超短的代码是为了耍酷,可读性很差!

更改如下:

<!DOCTYPE html>
<html>
<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*400pxcanvas,思路是以20px*20px为一个方格,组成2020列的方阵,总共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()差不多,可以用来截除小数。这两者具体还有一些区别,可以去看《你不知道的js(中卷)》62页。
  回到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的值。

qoogle按:据说代码很普通、很基础,但原创,应该是神级的!^_^


如果文章对您有帮助|有启发|有共鸣|有“爽了”,请随意打赏。您的支持是我的动力和价值的体现。

----------我不是街头卖艺者,但我渴望被打赏,博客要生存下去不容易。
----------我不是酒店服务生,但我渴望被打赏,博主也有妻儿子女一家子人。
----------我不是乞丐叫花子,但我渴望被打赏,渴望被打赏的价值认同感。
----------我不是建筑搬运工,但我渴望被打赏,我是蜜蜂我阅读/挑选/整合/转载/传播。
----------我不是作家或教授,但我渴望被打赏,咱中国人也能主动为知识/为受益而付费。

感谢土豪的打赏!


一码支持:微信/支付宝

打赏qoogle的二维码


分享,也是您对我的热力支持。谢谢!

内容版权声明:除转载分类下的文章来源网络(直转或整合而成),其它皆为本站原创文章。

转载注明出处:http://qoogle.cn/?id=50

发表评论(不用注册哟!)

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。