百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

写贪吃蛇的过程中遇到的几个问题

作者韩仁光课程贪吃蛇3128次浏览72017-03-06 11:48

游戏的三种玩法我只完成了前两种,在这里记录下遇到的几个问题,和大家分享,同时欢迎指正和点评。

如何让蛇动起来

用 canvas 来写游戏,无非就是设置个定时器,不断清空画布然后重绘。而让蛇动起来,让其身体每一截都往前移动,这显然是非常麻烦的一件事,比如蛇身现在弯弯曲曲,而其每一截都要向爬行方向移动一格,还需要给每一截重新计算下一步的位置。在我看来是十分麻烦的一件事,于是思索一番,发现蛇每次只是移动一格,那么如果我只改变最后一截,让其位置变为爬行方向的下一格不就解决了吗。想到这,我用一个数组来维护每一截的位置对象:

function Snake(args) {
    this.bodyArr = [];
    // 每次移动蛇头x,y值改变量
    this.dx = 0;
    this.dy = 0;
    //...
}
Snake.prototype.setBodyArr = function () {
    var newX = this.bodyArr[0].x + this.dx;
    var newY = this.bodyArr[0].y + this.dy;
    // 移除最后一截
    var temp = this.bodyArr.pop();
    // 将下一次要移动的位置插入到位置数组中
    this.bodyArr.unshift({
        x: newX,
        y: newY
    });
    // ...
};

食物的随机位置如何设置

或许大家觉得这个很简单,不就是产生个随机数,然后避开蛇身的位置就完事了么。我也是这么想的,但是想动手时却犯难了。毕竟每产生一次随机数,就要和蛇身每一截去比较一次,重叠了又要重新产生随机数再全部比较一次… 虽然废不了多少时间,但总觉得可以优化,于是在 github 上找别人的解决方案,发现很多人都是使用一个二维数组来维护地图上每个位置的状态的,觉得不错于是将其用上了:

function Snake(args) {
    // position[x][y] = 0 为空,
    // position[x][y] = 1 为蛇身,
    // position[x][y] = 2 为食物
    this.position = creat2dArr(width, height);

    //...
}
// 产生一个值全为0的二维数组
function creat2dArr(width, height) {
    var arr = new Array(width);
    for(var j = 0; j < width; j++) {
        arr[j] = new Array(height);
        for(var k = 0; k < height; k++) {
            arr[j][k] = 0;
        }
    }
    return arr;
}

每次只需比较 position[x][y] 的值即可知道该位置的状态,所以只要将每次产生的随机数代入比较即可,若为0则成,否则继续。

蛇爬行方向的改变

游戏是通过方向键来控制的,那么如果玩家在两次重绘之间,按下了多次不同的方向键,那么蛇下一次爬行的方向该定为什么?怎么解决这个冲突?我想到的是用一个队列来保存玩家的输入,每次重绘前取出队列的首项,如果该方向合理(即不是与当前方向相同或相反),则修改下一步的方向,通过这种方式,玩家的每一次输入都可以得到响应,只是快与慢的问题罢了:

function Snake(args) {
    // 爬行方向("up": 上,"down": 下,"left": 左,"right": 右)
    this.directionQueue = []; 
    this.nextDirection = "up"; // 定义初始爬行方向为上

    //...
}
// 设置下一次爬行的方向
Snake.prototype.setNextDirection = function () {
    if(this.directionQueue.length === 0) { // 如果用户没有输入下一个方向则返回
        return;
    }
    var temp = "";
    while(this.directionQueue.length !== 0) {
        if((temp = this.directionQueue.shift()) !== this.nextDirection) {
            // 判断方向合理性
            if(this.judgeLegality(temp)) {
                this.nextDirection = temp;
                // 设置dx与dy的值
                if(temp === "up") {
                    this.dx = 0;
                    this.dy = -1;
                } else if(temp === "down") {
                    this.dx = 0;
                    this.dy = 1;
                } else if(temp === "left") {
                    this.dx = -1;
                    this.dy = 0;
                } else {
                    this.dx = 1;
                    this.dy = 0;
                }
                break;
            }
        }
    }
};
// 页面加载完成后
window.onload = function () {
    var snake = new Snake(args);
    // 注册键盘事件
    onEvent(document, "keydown", function (e) {
        e = e || window.event;
        if(e.keyCode === 37) { // left
            snake.directionQueue.push("left");
        } else if(e.keyCode === 38) { // up
            snake.directionQueue.push("up");
        } else if(e.keyCode === 39) { // right
            snake.directionQueue.push("right");
        } else if(e.keyCode === 40) { // down
            snake.directionQueue.push("down");
        }
    });
};

到这也算记录完了,如果有不足之处欢迎指出。

3条评论