回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【花雕动手做】Kitronik 可编程游戏开发板基于 ARCADE MakeCode 之康威生命游戏 简单

头像 驴友花雕 2025.09.16 6 0

Kitronik ARCADE 是一款由英国教育科技公司 Kitronik 精心打造的可编程游戏机开发板,专为编程教学与创客实践而设计。该设备原生支持微软的 MakeCode Arcade 平台,用户可通过图形化或 JavaScript 编程方式,轻松创建、下载并运行复古风格的街机游戏。

它集成了彩色 LCD 显示屏、方向控制键、功能按键、蜂鸣器和震动马达等交互组件,提供完整的游戏输入输出体验。无论是初学者进行编程启蒙,还是创客群体开发交互式作品,Kitronik ARCADE 都能作为理想的硬件载体,助力创意实现。

凭借其开源友好、易于上手、兼容性强等特点,该开发板广泛应用于中小学编程课程、创客工作坊、游戏开发教学以及个人项目原型设计,深受教育者与技术爱好者的喜爱。

 

00 (4).jpg
00 (5).jpg

作为学习、练习与尝试,这里创建一个康威生命的小游戏。
打开网页版:https://arcade.makecode.com/,设置项目名称:康威生命

JavaScript 实验代码
 

代码
enum StillLife {
    Block,
    Beehive,
    Loaf,
    Boat,
    Tub
}

enum Oscillator {
    Blinker,
    Toad,
    Beacon,
    Pulsar,
    Pentadecathlon
}

enum Motion {
    Glider,
    LightWeight,
    Gospers,
    Simkins,
    SimkinsDouble,
    Engine,
    BlockLayer
}

enum OddCell {
    RPentomino,
    DieHard,
    Acorn
}

const width = screen.width;
const height = screen.height;
scene.setBackgroundImage(image.create(width, height));

// test1();
test2();
// test3();

function test1() {
    cells.createRandom(4000);
}

function test2() {
    for (let i = 0; i < 15; ++i) {
        for (let j = 0; j < 6; ++j) {
            cells.createOscillator(Oscillator.Pentadecathlon, 8 + 10 * i, 5 + 20 * j);
        }
    }
}
function test3() {
    for (let i = 0; i < 5; ++i) {
        cells.createMotion(Motion.Gospers, 15, 2 + i * 25);
    }
}

// buffers[bufferNum][x][y] corresponds to whether the cell at location (x,y)
// was alive in the given buffer
let buffers: boolean[][][];
let currentBuffer: number;
init();

game.onUpdateInterval(100, nextGeneration);
// game.onUpdate(nextGeneration);

function countNeighbors(src: boolean[][], x: number, y: number): number {
    const lX = x - 1; // left x
    const rX = x + 1; // right x
    const lY = y - 1; // left y
    const rY = y + 1; // right y

    let count = 0;
    if (src[lX][lY])++count;
    if (src[lX][y])++count;
    if (src[lX][rY])++count;
    if (src[x][lY])++count;
    if (src[x][rY])++count;
    if (src[rX][lY])++count;
    if (src[rX][y])++count;
    if (src[rX][rY])++count;
    return count;
}

function nextGeneration() {
    const lastGeneration = buffers[currentBuffer % 2];
    const currGeneration = buffers[++currentBuffer % 2];
    const bkgd = scene.backgroundImage();

    // leave 1 pixel of unused edge on each side
    // to avoid having to deal with oob checking
    for (let x = 1; x < width - 1; ++x) {
        for (let y = 1; y < height - 1; ++y) {
            const neighbors = countNeighbors(lastGeneration, x, y);
            if (lastGeneration[x][y] && (neighbors < 2 || neighbors > 3)) {
                // Previously alive cell has died due to under- or over-population
                currGeneration[x][y] = false;
                bkgd.setPixel(x, y, 0);
            } else if (!lastGeneration[x][y] && neighbors == 3) {
                // Previously empty location has new cell born
                currGeneration[x][y] = true;
                bkgd.setPixel(x, y, randint(1, 0xd));
            } else {
                // State is unchanged
                currGeneration[x][y] = lastGeneration[x][y];
            }
        }
    }
}

function init() {
    const bkgd = scene.backgroundImage();
    buffers = [[], []];
    for (let x = 0; x < width; x++) {
        buffers[0][x] = [];
        buffers[1][x] = [];
        for (let y = 0; y < height; y++) {
            buffers[0][x][y] = bkgd.getPixel(x, y) != 0;
            buffers[1][x][y] = false;
        }
    }

    currentBuffer = 0;
    // Draw a border around screen, as those pixels are counted as 'not alive'
    for (let x = 0; x < width; x++) {
        buffers[0][x][0] = false;
        buffers[0][x][height - 1] = false;
        bkgd.setPixel(x, 0, 1);
        bkgd.setPixel(x, height - 1, 1);
    }
    for (let y = 0; y < height; y++) {
        buffers[0][0][y] = false;
        buffers[0][width - 1][y] = false;
        bkgd.setPixel(0, y, 1);
        bkgd.setPixel(width - 1, y, 1);
    }
}

namespace cells {
    export function createStillLife(toDisplay: StillLife,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case StillLife.Block: {
                display = img`
                    1 1
                    1 1
                `
                break;
            }
            case StillLife.Beehive: {
                display = img`
                    . 1 1 .
                    1 . . 1
                    . 1 1 .
                `
                break;
            }
            case StillLife.Loaf: {
                display = img`
                    . 1 1 .
                    1 . . 1
                    . 1 . 1
                    . . 1 .
                `
                break;
            }
            case StillLife.Boat: {
                display = img`
                    1 1 .
                    1 . 1
                    . 1 .
                `
                break;
            }
            case StillLife.Block: {
                display = img`
                    1 1
                    1 1
                `
                break;
            }
            case StillLife.Tub: {
                display = img`
                    . 1 .
                    1 . 1
                    . 1 .
                `
                break;
            }
            default: return;
        }
        src.drawImage(display, x, y);
    }

    export function createOscillator(toDisplay: Oscillator,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case Oscillator.Blinker: {
                display = img`
                    1
                    1
                    1
                `
                break;
            }
            case Oscillator.Toad: {
                display = img`
                    . 1 1 1
                    1 1 1 .
                `
                break;
            }
            case Oscillator.Beacon: {
                display = img`
                    1 1 . .
                    1 1 . .
                    . . 1 1
                    . . 1 1
                `
                break;
            }
            case Oscillator.Pulsar: {
                display = img`
                    . . . . 1 . . . . . 1 . . . .
                    . . . . 1 . . . . . 1 . . . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . . . . . . . . . . . . . .
                    1 1 1 . . 1 1 . 1 1 . . 1 1 1
                    . . 1 . 1 . 1 . 1 . 1 . 1 . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . 1 . 1 . 1 . 1 . 1 . 1 . .
                    1 1 1 . . 1 1 . 1 1 . . 1 1 1
                    . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . . . 1 . . . . . 1 . . . .
                    . . . . 1 . . . . . 1 . . . .
                `
                break;
            }
            case Oscillator.Pentadecathlon: {
                display = img`
                    1 1 1
                    . 1 .
                    . 1 .
                    1 1 1
                    . . .
                    1 1 1
                    1 1 1
                    . . .
                    1 1 1
                    . 1 .
                    . 1 .
                    1 1 1
                `
                break;
            }
            default: return;
        }
        src.drawImage(display, x, y);
    }

    export function createMotion(toDisplay: Motion,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case Motion.Glider: {
                display = img`
                    . 1 .
                    . . 1
                    1 1 1
                `
                break;
            }
            case Motion.LightWeight: {
                display = img`
                    1 . . 1 .
                    . . . . 1
                    1 . . . 1
                    . 1 1 1 1
                `
                break;
            }
            case Motion.Gospers: {
                display = img`
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . . . . . . . .
                    . . . . . . . . . 1 . . . . . . . . . . . . . . . 1 1 . 1 . . . . . 1 1
                    . . . . . . . 1 . 1 . . . . 1 1 1 . . . . . . . . 1 . . 1 . . . . . 1 1
                    1 1 . . . . 1 . 1 . . . . . . . . . . . . . . . . 1 1 . 1 . . . . . . .
                    1 1 . . . 1 . . 1 . . . . . . . 1 . . 1 1 . . 1 1 1 . . . . . . . . . .
                    . . . . . . 1 . 1 . . . . . . . 1 . . . 1 . . 1 1 . . . . . . . . . . .
                    . . . . . . . 1 . 1 . . . . . . 1 . . 1 . . . . . . . . . . . . . . . .
                    . . . . . . . . . 1 . . . . . . . . 1 1 . . . . . . . . . . . . . . . .
                `;
                break;
            }
            case Motion.Simkins: {
                display = img`
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . 1 1 . 1 1 . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . 1 . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . 1 . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . 1 . . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . .
                `
                break;
            }
            case Motion.SimkinsDouble: {
                display = img`
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . 1 1 . 1 1 . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . 1 . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . 1 . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . 1 . . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . .
                `
                break;
            }
            case Motion.Engine: {
                display = img`
                    1 1 1 1 1 1 1 1 . 1 1 1 1 1 . . . 1 1 1 . . . . . . 1 1 1 1 1 1 1 . 1 1 1 1 1
                `
                break;
            }
            case Motion.BlockLayer: {
                display = img`
                    1 1 1 . 1
                    1 . . . .
                    . . . 1 1
                    . 1 1 . 1
                    1 . 1 . 1
                `
                break;
            }
            default: return;
        }

        src.drawImage(display, x, y);
    }

    export function createOddCell(toDisplay: OddCell,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case OddCell.RPentomino: {
                display = img`
                    . 1 1
                    1 1 .
                    . 1 .
                `
                break;
            }
            case OddCell.DieHard: {
                display = img`
                    . . . . . . 1 .
                    . . . . . . . .
                    1 1 . . . . 1 .
                    . 1 . . . 1 1 1
                `
                break;
            }
            case OddCell.Acorn: {
                display = img`
                    . 1 . . . . .
                    . . . 1 . . .
                    1 1 . . 1 1 1
                `;
                break;
            }
            default: return;
        }

        src.drawImage(display, x, y);
    }

    export function createRandom(count: number, src?: Image) {
        if (!src) src = scene.backgroundImage();

        for (let i = 0; i < count; ++i)
            src.setPixel(randint(0, width), randint(0, height), 1);
    }
}

这段代码实现了康威生命游戏(Conway's Game of Life),这是一个著名的细胞自动机模拟。
代码结构分析

1. 枚举类型定义
定义了四种模式枚举:
• StillLife: 静态稳定结构(不会变化)
• Oscillator: 振荡器(周期性变化)
• Motion: 移动结构(会在空间中移动)
• OddCell: 特殊细胞结构

2. 初始化设置
• 使用屏幕宽度和高度创建背景图像
• 提供了三种测试模式

3. 核心逻辑
• buffers: 双缓冲区系统,用于存储当前和下一代细胞状态
• countNeighbors(): 计算细胞周围存活邻居数量
• nextGeneration(): 应用生命游戏规则生成下一代
• init(): 初始化缓冲区和边界

4. 细胞模式库
cells命名空间提供了多种预定义模式:
• 静态结构(Block, Beehive, Loaf等)
• 振荡器(Blinker, Toad, Beacon等)
• 移动结构(Glider, LightWeight, Gospers等)
• 特殊结构(RPentomino, DieHard, Acorn)
• 随机生成功能

生命游戏规则实现
代码实现了标准规则:
5. 存活细胞周围少于2个存活邻居 → 死亡(孤独)
6. 存活细胞周围有2或3个存活邻居 → 继续存活
7. 存活细胞周围超过3个存活邻居 → 死亡(过度拥挤)
8. 死亡细胞周围恰好有3个存活邻居 → 新生
9. 
可视化效果
• 使用背景图像的像素表示细胞状态
• 新生的细胞会随机分配颜色(1-13)
• 边界用颜色1标记

可能的改进方向
10. 添加用户交互(点击创建/删除细胞)
11. 增加速度控制
12. 添加更多预定义模式
13. 实现模式保存/加载功能
14. 添加统计信息(代数、细胞数量等)
这个实现充分利用了MakeCode Arcade的图形功能,提供了一个视觉上吸引人的生命游戏模拟。双缓冲区技术确保了平滑的世代过渡,而预定义模式库让用户可以轻松探索生命游戏的各种有趣行为。

通过模拟器,调试与模拟运行

 

00211---0.gif

评论

user-avatar
icon 他的勋章
    展开更多