Rust + WebAssembly 实现康威生命游戏

1. 设计思路

1.1 选择有限的世界

康威生命游戏的世界是 无限二维网格 ,但由于 计算机内存有限,我们可以选择三种有限宇宙方案:

  1. 动态扩展:仅存储"活跃区域",按需扩展(可能无限增长)。
  2. 固定大小,无边界扩展:边界处的细胞会被"消灭"。
  3. 固定大小,环绕宇宙(Toroidal Universe (我们采用此方案)

环绕宇宙 (Toroidal Universe)允许 滑翔机(Gliders) 无限运行,不会被边界限制:

  • 上边界的细胞连接到下边界
  • 左边界的细胞连接到右边界

2. Rust 代码实现

2.1 定义 Cell 结构

rust 复制代码
#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
    Dead = 0,
    Alive = 1,
}

说明:

  • #[repr(u8)]:让 Cell 占用 1 字节,减少内存浪费。
  • Dead = 0, Alive = 1 :使得 Cell 可以直接用整数加法计算邻居数。

2.2 定义 Universe

rust 复制代码
#[wasm_bindgen]
pub struct Universe {
    width: u32,
    height: u32,
    cells: Vec<Cell>,
}

说明:

  • width、height:宇宙的宽度和高度
  • cells: Vec<Cell>:存储所有细胞状态(0=死,1=活)

2.3 计算网格索引

rust 复制代码
impl Universe {
    fn get_index(&self, row: u32, column: u32) -> usize {
        (row * self.width + column) as usize
    }
}
  • 计算二维网格 在一维数组中的索引。

2.4 计算活邻居数量

rust 复制代码
impl Universe {
    fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
        let mut count = 0;
        for delta_row in [self.height - 1, 0, 1].iter().cloned() {
            for delta_col in [self.width - 1, 0, 1].iter().cloned() {
                if delta_row == 0 && delta_col == 0 {
                    continue;
                }

                let neighbor_row = (row + delta_row) % self.height;
                let neighbor_col = (column + delta_col) % self.width;
                let idx = self.get_index(neighbor_row, neighbor_col);
                count += self.cells[idx] as u8;
            }
        }
        count
    }
}

说明:

  • 使用 modulo 计算 环绕宇宙,确保邻居索引不会溢出。
  • 避免 if 语句,减少特殊情况处理,提高性能。

2.5 更新下一代状态

rust 复制代码
#[wasm_bindgen]
impl Universe {
    pub fn tick(&mut self) {
        let mut next = self.cells.clone();

        for row in 0..self.height {
            for col in 0..self.width {
                let idx = self.get_index(row, col);
                let cell = self.cells[idx];
                let live_neighbors = self.live_neighbor_count(row, col);

                let next_cell = match (cell, live_neighbors) {
                    (Cell::Alive, x) if x < 2 => Cell::Dead,
                    (Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
                    (Cell::Alive, x) if x > 3 => Cell::Dead,
                    (Cell::Dead, 3) => Cell::Alive,
                    (otherwise, _) => otherwise,
                };

                next[idx] = next_cell;
            }
        }

        self.cells = next;
    }
}

说明:

  • 规则翻译 :
    • 活细胞 < 2 → 死亡(过少)
    • 活细胞 = 2 or 3 → 存活(繁衍)
    • 活细胞 > 3 → 死亡(过度拥挤)
    • 死细胞 = 3 → 复活(繁殖)

2.6 初始化宇宙

rust 复制代码
#[wasm_bindgen]
impl Universe {
    pub fn new() -> Universe {
        let width = 64;
        let height = 64;

        let cells = (0..width * height)
            .map(|i| if i % 2 == 0 || i % 7 == 0 { Cell::Alive } else { Cell::Dead })
            .collect();

        Universe {
            width,
            height,
            cells,
        }
    }

    pub fn render(&self) -> String {
        self.to_string()
    }
}
  • 初始化 :创建 64x64 网格,细胞 随机分布。
  • 实现 render():在 JavaScript 中调用,返回网格字符串。

3. JavaScript 前端

在 wasm-game-of-life/www/index.js 编写 Canvas 渲染 代码。

3.1 JavaScript 初始化

js 复制代码
import { Universe, Cell, memory } from "wasm-game-of-life";

const CELL_SIZE = 5;
const GRID_COLOR = "#CCCCCC";
const DEAD_COLOR = "#FFFFFF";
const ALIVE_COLOR = "#000000";

const universe = Universe.new();
const width = universe.width();
const height = universe.height();

3.2 绘制网格

js 复制代码
const drawGrid = () => {
  ctx.beginPath();
  ctx.strokeStyle = GRID_COLOR;

  for (let i = 0; i <= width; i++) {
    ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
    ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
  }

  for (let j = 0; j <= height; j++) {
    ctx.moveTo(0, j * (CELL_SIZE + 1) + 1);
    ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
  }

  ctx.stroke();
};

3.3 读取 WebAssembly 内存

js 复制代码
const drawCells = () => {
  const cellsPtr = universe.cells();
  const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);

  ctx.beginPath();

  for (let row = 0; row < height; row++) {
    for (let col = 0; col < width; col++) {
      const idx = row * width + col;
      ctx.fillStyle = cells[idx] === Cell.Dead ? DEAD_COLOR : ALIVE_COLOR;
      ctx.fillRect(col * (CELL_SIZE + 1) + 1, row * (CELL_SIZE + 1) + 1, CELL_SIZE, CELL_SIZE);
    }
  }

  ctx.stroke();
};

3.4 动画渲染

js 复制代码
const renderLoop = () => {
  universe.tick();
  drawGrid();
  drawCells();
  requestAnimationFrame(renderLoop);
};

drawGrid();
drawCells();
requestAnimationFrame(renderLoop);

4.运行项目

sh 复制代码
wasm-pack build
cd www
npm install
npm run start

打开 http://localhost:8080/ ,你会看到 动态演化的生命游戏!

相关推荐
Zenova EdgeOS40 分钟前
Rust TUI 实战:用 ratatui 构建工业边缘终端界面
开发语言·后端·rust·工业网关
梦想平凡1 小时前
棋牌游戏技术实战(十三):缓存对局历史时控制容量,命中以后仍然检查成员权限
游戏·缓存
遇码1 小时前
不用转场引擎做幻灯片转场:相机滑动、黑场淡入与真全屏的完整做法
人工智能·数码相机·ai·rust·大模型·agent
阿阳微客3 小时前
steam游戏搬砖用选品软件照搬就可以么?经验帖
学习·游戏
lpfasd12312 小时前
node-sass 停止维护!老项目迁移 Dart Sass 完整教程
前端·rust·sass
FairGuard手游加固13 小时前
手游加固怎么做?SO、IL2CPP、AssetBundle 与运行时保护详解
安全·游戏·unity
Vex2une13 小时前
GRLP游戏运行库合集:一站式解决DLL缺失与启动报错实战指南 | Game Runtime Libraries Package
游戏·vc++运行库·0xc000007b·游戏运行库·dll缺失修复·directx修复·游戏报错解决
Flynt13 小时前
我把真实的 ES 查询怼到 7059 星的 Rust 搜索引擎上:1366/1369 的兼容承诺,裂了 3 个缝
elasticsearch·搜索引擎·rust
小杍随笔16 小时前
egui 0.37 升级实录:卡顿修复与5倍性能回退
windows·rust·egui
geovindu1 天前
rust: Iterator Pattern
开发语言·后端·设计模式·rust·行为模式·迭代器模