实验7 贪吃蛇

简介

1976年,Gremlin平台推出了一款经典街机游戏Blockade。游戏中,两名玩家分别控制一个角色在屏幕上移动,所经之处砌起围栏。角色只能向左、右方向90°转弯,游戏的目标是让对方先撞上边界或围栏。听起来有些复杂,其实就是两条每走一步都会长大的贪吃蛇比谁坚持的时间长,玩家要做的就是避免撞上障碍物和越来越长的身体。更多照片、视频可以看 GamesDBase的介绍。

Blockade很受欢迎,类似的游戏先后出现在 Atari 2600、TRS-80、苹果2等早期游戏机、计算机上。但真正让这种游戏形式红遍全球的还是21年后随诺基亚手机走向世界的贪吃蛇游戏——Snake,如图1-24所示。

图1-24 诺基亚贪吃蛇游戏

贪吃蛇算法

首先用Jscex把整个游戏的Loop搭建起来:

        var moveAsync=eval(Jscex.compile("async", function (p) {
        while (true) {
        //...
        //code here
        //...
                  $await(Jscex.Async.sleep(100));
                }
            }));

然后把相关的算法加进去。首先是蛇的移动算法,采用每一个时间间隔去掉尾部一个节点,并且向蛇的头部运动方向增加一个节点的方法。

        if (direction=="right") {
                      p.push({
                        x: p[p.length-1].x+1,
                        y: p[p.length-1].y
                      });
                  }
        if (direction=="left") {
                      p.push({
                        x: p[p.length-1].x-1,
                        y: p[p.length-1].y
                      });
                  }
        if (direction=="up") {
                      p.push({
                        x: p[p.length-1].x,
                        y: p[p.length-1].y-1
                      });
                  }
        if (direction=="down") {
                      p.push({
                        x: p[p.length-1].x,
                        y: p[p.length-1].y+1
                      });
                  }

蛇的头部运动方向共有4个,蛇在运动时都是往头部运动方向增加一个节点,因为当蛇吃到食物时节点增加一个,所以不是每次都会去掉尾部节点,在判断到蛇没有吃到食物时去掉尾部的一个节点:

        if (p[p.length-1].x==foodPositions.x && p[p.length-1].y==foodPositions.y) {
                      foodPositions.x=-1;
                      foodPositions.y=-1;
                  }
        else {
                      clearColor(p[0]);
                      p.shift();
                  }

然后是随机生成蛇的食物(这里可以加一个递归,如果生成的位置和蛇的位置重叠,则重新生成,一直递归下去,直到没有重叠)。

        var MR=Math.random;
        function drawSnakeAndFood(p) {
        if (foodPositions.x==-1 && foodPositions.y==-1) {
                  foodPositions.x=MR() * 40 | 0;
                  foodPositions.y=MR() * 40 | 0;
                }
                cxt.fillStyle=randomColor();
                cxt.fillRect(foodPositions.x * width, foodPositions.y * height, height, width);
        for (i=0; i < p.length; i++) {
                  cxt.fillStyle="#D1EEEE";
                  cxt.fillRect(p[i].x * width, p[i].y * height, height, width);
                }
            }

GameOver的判定:

        if (p[p.length-1].x < 0 || p[p.length-1].x > 39 || p[p.length-1].y < 0 || p[p.length-1].y > 39) {
                      gameOver=true;
                  }
        if (isInSnake(p, { x: p[p.length-1].x, y: p[p.length-1].y })) {
                      gameOver=true;
                  }

GameOver分两种情况,一种是撞到墙壁和障碍物,一种是撞到自己的身体,代码如下:

        var moveAsync=eval(Jscex.compile("async", function (p) {
        while (true) {
        if (p[p.length-1].x < 0 || p[p.length-1].x > 39 || p[p.length-1].y < 0 || p[p.length-1].y > 39) {
                      gameOver=true;
                  }
        if (p[p.length-1].x==foodPositions.x && p[p.length-1].y==foodPositions.y) {
                      foodPositions.x=-1;
                      foodPositions.y=-1;
                  }
        else {
                      clearColor(p[0]);
                      p.shift();
                  }
        if (direction=="right") {
                      p.push({
                        x: p[p.length-1].x+1,
                        y: p[p.length-1].y
                      });
                  }
        if (direction=="left") {
                      p.push({
                        x: p[p.length-1].x-1,
                        y: p[p.length-1].y
                      });
                  }
        if (direction=="up") {
                      p.push({
                        x: p[p.length-1].x,
                        y: p[p.length-1].y-1
                      });
                  }
        if (direction=="down") {
                      p.push({
                        x: p[p.length-1].x,
                        y: p[p.length-1].y+1
                      });
                  }
                  drawSnakeAndFood(p);
        if (gameOver) {
                      drawGameOver();
                      document.getElementById("btnReset").disabled=";
        break;
                  }
                  $await(Jscex.Async.sleep(100));
                }
            }));

运行效果如图1-25所示。

图1-25 Canvas中的贪吃蛇游戏