20行代码的贪吃蛇

栏目: Html5 · 发布时间: 7年前

内容简介:20行代码的贪吃蛇

原文阅读更方便

在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*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() 差不多,对一个浮点型的数取反再取反,得到的数就是去掉小数位的整数了。

回到 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 的值。

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Philosophy of Software Design

A Philosophy of Software Design

John Ousterhout / Yaknyam Press / 2018-4-6 / GBP 14.21

This book addresses the topic of software design: how to decompose complex software systems into modules (such as classes and methods) that can be implemented relatively independently. The book first ......一起来看看 《A Philosophy of Software Design》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具