Microbit 游戏编程: 贪心算法也无法让贪吃蛇永生


上一次, 我们介绍了在Microbit上编写最简单的贪吃蛇游戏(Microbit 游戏编程: 不会吃胖的贪食蛇 (自带人工智能)), 不过只是个原形, 因为那只蛇并不会长胖, 有网友说更像是一个男孩爱上一个女孩疯狂的爱情故事.

这一次, 我们将对游戏的关键部分进行修改, 不过考虑到整个 Microbit 的屏幕才25个像素点, 我们加个限制让蛇在长度为10的时候停止生长, 这样的话, 只要你玩得好, 游戏能一直进行下去.

贪吃蛇的身体

由于蛇的身体不再是单个game.LedSprite对象, 我们将需要将蛇的身体块存储在数组中. 我们可以定义一个initSnake函数, 该函数需要传进一组蛇身体坐标并创建相应的精灵数组.

1
2
3
4
5
6
7
function initSnake(arr: Array<number>) {
    let result = [];
    for (let i = 0; i + 1 < arr.length; i += 2) {
        result.push(game.createSprite(arr[i], arr[i + 1]));
    }
    return result;
};
function initSnake(arr: Array<number>) {
    let result = [];
    for (let i = 0; i + 1 < arr.length; i += 2) {
        result.push(game.createSprite(arr[i], arr[i + 1]));
    }
    return result;
};

游戏初始化蛇的身体

1
2
3
4
5
6
let direction = 1; // initial direction is down
let dxOffset = [[1, 0], [0, 1], [-1, 0], [0, -1]];
let snake = initSnake([px, py, px + 1, py]);
//当蛇长到10像素时, 它停止增长
//避免填充LED
const maxLength = 10;
let direction = 1; // initial direction is down
let dxOffset = [[1, 0], [0, 1], [-1, 0], [0, -1]];
let snake = initSnake([px, py, px + 1, py]);
//当蛇长到10像素时, 它停止增长
//避免填充LED
const maxLength = 10;

碰撞检测

蛇的下一个像素点必须不是蛇的身体, 否则蛇就会立刻狗带.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 检查(x, y)是否为蛇体的坐标之一. 
function isOnSnake(x: number, y: number): boolean {
    for (let body of snake) {
        if (body.x() == x && body.y() == y) {
            return true;
        }
    }
    return false;
}
 
function validPixelCoordinate(nx: number, ny: number): boolean {
    return (nx >= 0 && nx <= 4 && 
            ny >= 0 && ny <= 4) && (!isOnSnake(nx, ny));
}
// 检查(x, y)是否为蛇体的坐标之一. 
function isOnSnake(x: number, y: number): boolean {
    for (let body of snake) {
        if (body.x() == x && body.y() == y) {
            return true;
        }
    }
    return false;
}

function validPixelCoordinate(nx: number, ny: number): boolean {
    return (nx >= 0 && nx <= 4 && 
            ny >= 0 && ny <= 4) && (!isOnSnake(nx, ny));
}

同样, 我们需要调整苹果生成函数, 确保新苹果不位于贪吃蛇的身体之一上.

1
2
3
4
5
6
7
8
9
function placeNextApple() {
    let x, y;
    do {
        x = Math.randomRange(0, 4);
        y = Math.randomRange(0, 4);
    } while (isOnSnake(x, y));
    apple.goTo(x, y);
    apple.setBrightness(100);
}
function placeNextApple() {
    let x, y;
    do {
        x = Math.randomRange(0, 4);
        y = Math.randomRange(0, 4);
    } while (isOnSnake(x, y));
    apple.goTo(x, y);
    apple.setBrightness(100);
}

重置游戏

重置游戏resetGame函数的一项更改是在重新初始化新的主体之前, 删除贪吃蛇的身体. 删除精灵将使该精灵对象从内存中删除, 删除后的精灵对象既不会与其他精灵对象进行交互, 也不会显示在LED屏幕上.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function resetGame() {
    game.setScore(0);
    score = 0;
    direction = 0;
    px = 0;
    py = 0;
    // 释放蛇的身体和占用的内存
    for (let s of snake) {
        s.delete();
    }
    snake = initSnake([px, py, px + 1, py]);
    placeNextApple();
    game.resume();
}
function resetGame() {
    game.setScore(0);
    score = 0;
    direction = 0;
    px = 0;
    py = 0;
    // 释放蛇的身体和占用的内存
    for (let s of snake) {
        s.delete();
    }
    snake = initSnake([px, py, px + 1, py]);
    placeNextApple();
    game.resume();
}

蛇向前爬行

当蛇移动时, 我们可以使用Javascript的数组unshift方法将新的像素坐标推到数组的最前面. 然后, 我们需要通过pop()方法删除最后一个元素.

1
2
3
4
5
6
7
8
9
10
11
function moveForward() {
    let dx = dxOffset[direction];
    px += dx[0];
    py += dx[1];
    if (!validPixelCoordinate(px, py)) {
        gameOver();
    }
    snake.unshift(game.createSprite(px, py));
    let last = snake.pop();
    last.delete();
}
function moveForward() {
    let dx = dxOffset[direction];
    px += dx[0];
    py += dx[1];
    if (!validPixelCoordinate(px, py)) {
        gameOver();
    }
    snake.unshift(game.createSprite(px, py));
    let last = snake.pop();
    last.delete();
}

贪吃蛇吃了胖 胖了吃

这部分可以在主游戏循环中处理. 当检测到蛇头和苹果之间发生碰撞时, 我们可以将新像素推到身体的尾部(复制一个尾巴).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
basic.forever(function () {
    if (game.isGameOver()) {
        return;
    }
    let delay = Math.max(100, 1000 - score * 50);
    basic.pause(delay);
    //letComputerPlay();
    moveForward();
    if (snake[0].isTouching(apple)) { // 吃到了苹果
        // 在大于 maxLength 后就不长了
        if (snake.length < maxLength) {
            // 复制一个尾巴
            snake.push(snake[snake.length - 1]);
        }
        score++;
        placeNextApple();
    }
})
basic.forever(function () {
    if (game.isGameOver()) {
        return;
    }
    let delay = Math.max(100, 1000 - score * 50);
    basic.pause(delay);
    //letComputerPlay();
    moveForward();
    if (snake[0].isTouching(apple)) { // 吃到了苹果
        // 在大于 maxLength 后就不长了
        if (snake.length < maxLength) {
            // 复制一个尾巴
            snake.push(snake[snake.length - 1]);
        }
        score++;
        placeNextApple();
    }
})

Microbit 完整的贪吃蛇游戏

完整的源代码和Microbit在这里: https://makecode.microbit.org/_2qyYchHfsDDC

上次的游戏智能AI并不需要修改, 但由于采用的是贪心算法, 所以有可能会陷入死局 导致贪吃蛇无路可走 游戏结束.

带有贪心算法智能游戏 AI的代码和模拟器: https://makecode.microbit.org/_9T89xWaMkRV4

Microbit采用贪心策略玩贪吃蛇的视频:

想立马玩贪吃蛇游戏?

英文: Microbit Programming: Snake Game with Growing Body with Greedy AI

GD Star Rating
loading...
本文一共 669 个汉字, 你数一下对不对.
Microbit 游戏编程: 贪心算法也无法让贪吃蛇永生. (AMP 移动加速版本)
上一篇: 用 Javascript 做一个贪食蛇的游戏
下一篇: 圣诞节 Boxing Day 剑桥华人音乐聚会

扫描二维码,分享本文到微信朋友圈
c6afd1a1cd9d9dc16e079041ae5f4237 Microbit 游戏编程: 贪心算法也无法让贪吃蛇永生 Microbit 编程 游戏 程序设计

评论