Kitronik ARCADE 是一款由英国教育科技公司 Kitronik 精心打造的可编程游戏机开发板,专为编程教学与创客实践而设计。该设备原生支持微软的 MakeCode Arcade 平台,用户可通过图形化或 JavaScript 编程方式,轻松创建、下载并运行复古风格的街机游戏。
它集成了彩色 LCD 显示屏、方向控制键、功能按键、蜂鸣器和震动马达等交互组件,提供完整的游戏输入输出体验。无论是初学者进行编程启蒙,还是创客群体开发交互式作品,Kitronik ARCADE 都能作为理想的硬件载体,助力创意实现。
凭借其开源友好、易于上手、兼容性强等特点,该开发板广泛应用于中小学编程课程、创客工作坊、游戏开发教学以及个人项目原型设计,深受教育者与技术爱好者的喜爱。


作为学习、练习与尝试,这里创建一个康威生命的小游戏。
打开网页版: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的图形功能,提供了一个视觉上吸引人的生命游戏模拟。双缓冲区技术确保了平滑的世代过渡,而预定义模式库让用户可以轻松探索生命游戏的各种有趣行为。
通过模拟器,调试与模拟运行

评论