美文网首页
JavaScript - 20行代码实现贪吃蛇[解析]

JavaScript - 20行代码实现贪吃蛇[解析]

作者: _zk | 来源:发表于2019-04-17 22:56 被阅读0次

首先声明,代码不是我写的,是我从网上看到大佬写的(侵权私我),感到有兴趣,所以拿来研究了下。。。
原帖点这里啊

变量说明

  • snake
    存储蛇的数组,之后可用于判断前进方向、是否撞到身体等。。
  • bean
    用于存储豆子的位置
  • direction
    控制蛇的方向
  • head
    蛇头当前的位置

思路

  • canvas 绘制蛇行走的面板(网格思路)
  • 使用数组存储蛇
  • draw 函数用于绘制蛇、豆子
  • 向 direction 所指方向绘制蛇头
  • 未吃到豆子则删除最后一个身体,让其变为黑色

理解面板

首先绘制 400*400 大小的 canvas 面板,蛇的身体每一节、果实都分别占 20*20 大小的区域(即每个大小为 20*20),所以整体网格可以分为 20*20个小的网格,这样就可以抽象出以下网格。


贪吃蛇网格

面板左上角从 0 开始编号,第一行为 0~19 第二行为 20~39,以此类推,最后一行为 380~399。至此,面板上的每一个格都代表一个位置(蛇、果实的位置)。

理解豆子、蛇的位置表示

首先贴出代码。。。。

// 创建蛇数组,存放蛇身的绘图位置
var snake = [42, 41];
// 创建豆子,存放豆子初始出现的位置
var bean = 43;

snake 数组表示蛇身体所占的网格位置,bean 表示豆子出现的位置,上面理解了网格面板,现在可以找找蛇和豆子的位置大概在哪里了,密集恐惧的话就算了,问题不大。。。

理解 draw 函数

首先贴出代码。。。。

function draw(location, color) {
  ctx.fillStyle = color;
  // location % 20 * 20 + 1 取位置的个位乘格的宽度
  ctx.fillRect(location % 20 * 20 + 1, parseInt(location / 20) * 20 + 1, 18, 18);
}

要理解 draw,先要知道 fillStyle、fillRect 是怎么个意思,不用百度了,我给你大概说下。。。

fillStyle 设置画笔的填充颜色
fillRect(x, y, width, height) 从 (x, y) 点开始画个矩形宽 width 高 height 的矩形

好了接下来就可以理解 draw 函数在干嘛了,就是设置填充颜色,然后画个矩形。
fillRect 函数里参数一大堆可能觉得很蒙蔽,其实挺简单的哈,跟着我理解下。

location 表示要在哪个编号上画矩形,例如:43 号,画矩形的起点为 (x, y)。编号除 20 取余可知在第几列,然后乘宽度就是横坐标了(x = location % 20 * 20),纵坐标的求法类似,除 20 取整,可知在第几行,然后乘 20(y = parseInt(location / 20) * 20)

至于为什么加一,还要设置宽高为 18(说好的 20),是为了让蛇身体间有点空隙,看下图,绿色是蛇占的区域,外层红框是一个 20*20 区域,这样可留出 1 的空白。


每格留1距离空白

理解键盘监视 onkeydown

简单来说,就是当你键盘按下去的时候,浏览器会接受一个 keyCode,可以利用 keyCode 区别按下了哪个键。
上代码。。。。

document.onkeydown = function (e) {
  e = e || event;
  /**
  * 1.获取方向键
  *      -1  向左退一格
  *      1   向右前进一格
  *      20  向下走一格
  *      -20 向上走一格
  *      左、上、右、下的 keycode 分别对应了 37 38 39 40
  * 2.(snake[1] - snake[0]) 可以获取蛇当前前进的反方向
  *      当前进方向一致或相反时,direction 不变
  */
  if(e.keyCode < 37 || e.keyCode > 40 || (snake[1] - snake[0] == [-1, -20, 1, 20][e.keyCode - 37])){
    direction = direction;
  }
  else {
    direction = [-1, -20, 1, 20][e.keyCode - 37];
  }
// direction = (snake[1] - snake[0] == (head = [-1, -20, 1, 20][(e || event).keyCode - 37] || direction)) ? direction : head;
};

-1、1、20、-20 所代表方向,按网格理解(一行 20 个),不理解自己画画,其他注释挺清楚,不做解释了。

理解碰撞检测

代码。。。

if (snake.indexOf(head, 1) > 0 || head < 0 || head > 399 || (direction == -1 && head % 20 == 19) || (direction == 1 && head % 20 == 0)){
  return alert("GAME OVER");
 }

一下一下来啊。

1.snake.indexOf(head, 1)> 0 说明除首位,还有别的身体部位和占了头一样的位置,前面说了 snake 用数组存储身体所在位置,若 head 的位置在除第一个以外的存在,说明头撞到身体了,game over。。
2.head < 0 || head > 399 头不能冲出网格的界限,否则 game over。。。
3.(direction == -1 && head % 20 == 19) 方向向左时,头部能整除 20,说明头部在最左边了,除 20 取余为 19 说明在最右边了。-1 代表左边,但是和右边界搭配,为啥呢,你试试就知道了,不这样到不了最边边就撞了,试试吧,不做解释了。。。。

理解蛇的爬行

代码不来了,简单说下就是,先让头部加上方向的值,就可以控制上下移动,然后推入 snake 身体数组,之后判断是否碰撞了,撞了就 over,没撞继续爬。。。没吃豆豆的情况下,每一次爬,末尾的身体被销毁,变成黑色(因为没吃豆豆蛇长度不变,头部推入,尾部销毁)。吃了豆豆的画,尾部就不销毁了,表示身体长了一节。

理解自执行

代码来。。。。

!function () {
  // 绘制蛇头
  head = snake[0] + direction;
  snake.unshift(head);
  if (snake.indexOf(head, 1) > 0 || head < 0 || head > 399 || (direction == -1 && head % 20 == 19) || (direction == 1 && head % 20 == 0)){
    // console.log("direction = " + direction + "head = " + head);
    return alert("GAME OVER");
  }
  draw(head, "Lime");
  // 绘制豆子
  if (head == bean) {
    while (snake.indexOf(bean = parseInt(Math.random() * 400)) >= 0);
    draw(bean, "Yellow");
  } else {
    // 没有吃到豆子,则将蛇尾绘制为背景色(黑色)
    draw(snake.pop(), "Black");
  }
  // arguments.callee 指向参数的所属函数,即回调当前函数
  // setTimeout 设置绘图的时间,可改变蛇的速度(游戏难度)
  setTimeout(arguments.callee, 150);
}();

!function () {}(); 匿名函数自动执行。
setTimeout(arguments.callee, 150); 延迟执行函数,过 150 ms 执行一次第一个参数代表的函数。arguments.callee 表示参数所属函数,就是这个函数本身,俗称回调。

结语

基本就这些了。。。。奉上代码,与原版有所改动。。。。。

<!doctype html>
<html>

<body style='overflow:hidden'>
    <canvas id="can" width="400" height="400" style="background:Black;display: block;margin:20px auto;"></canvas>

    <script>
        // 创建蛇数组,存放蛇身的绘图位置
        var snake = [42, 41];
        // 创建豆子,存放豆子初始出现的位置
        var bean = 43;
        // 控制蛇的方向
        var direction = 1;
        var head;
        // 获取画笔
        var ctx = document.getElementById("can").getContext("2d");
        function draw(location, color) {
            ctx.fillStyle = color;
            // location % 20 * 20 + 1 取位置的个位乘格的宽度
            ctx.fillRect(location % 20 * 20 + 1, parseInt(location / 20) * 20 + 1, 18, 18);
        }
        document.onkeydown = function (e) {
            e = e || event;
            /**
             * 1.获取方向键
             *      -1  向左退一格
             *      1   向右前进一格
             *      20  向下走一格
             *      -20 向上走一格
             *      左、上、右、下的 keycode 分别对应了 37 38 39 40
             * 2.(snake[1] - snake[0]) 可以获取蛇当前前进的反方向
             *      当前进方向一致或相反时,direction 不变
             */
             if(e.keyCode < 37 || e.keyCode > 40 || (snake[1] - snake[0] == [-1, -20, 1, 20][e.keyCode - 37])){
                direction = direction;
             }
             else {
                direction = [-1, -20, 1, 20][e.keyCode - 37];
             }
            // direction = (snake[1] - snake[0] == (head = [-1, -20, 1, 20][(e || event).keyCode - 37] || direction)) ? direction : head;
        };
        // 自执行匿名函数
        !function () {
            // 绘制蛇头
            head = snake[0] + direction;
            snake.unshift(head);
            if (snake.indexOf(head, 1) > 0 || head < 0 || head > 399 || (direction == -1 && head % 20 == 19) || (direction == 1 && head % 20 == 0)){
                // console.log("direction = " + direction + "head = " + head);
                return alert("GAME OVER");
            }
            draw(head, "Lime");
            // 绘制豆子
            if (head == bean) {
                while (snake.indexOf(bean = parseInt(Math.random() * 400)) >= 0);
                draw(bean, "Yellow");
            } else {
                // 没有吃到豆子,则将蛇尾绘制为背景色(黑色)
                draw(snake.pop(), "Black");
            }
            // arguments.callee 指向参数的所属函数,即回调当前函数
            // setTimeout 设置绘图的时间,可改变蛇的速度(游戏难度)
            setTimeout(arguments.callee, 150);
        }();
    </script>

</body>

</html>

相关文章

网友评论

      本文标题:JavaScript - 20行代码实现贪吃蛇[解析]

      本文链接:https://www.haomeiwen.com/subject/lhbowqtx.html