0017-wasm-康威生命游戏
环境
- Time 2022-05-16
- Rust 1.60.0
- Node 12.22.5
- wasm-pack 0.10.2
前言
说明
参考:https://rustwasm.github.io/docs/book/game-of-life/implementing.html
目标
在上一节的基础上进行,前面实现了 canvas
渲染游戏,不过还有两点可以进行优化:
- 生成细胞的算法是固定,应该采用随机的方式。
- 一个细胞占用了一个字节,其实一位就可以表示存活与否了。
引入依赖
[dependencies]
console_error_panic_hook = {version = "*", optional = true}
fixedbitset = "*"
js-sys = "*"
wasm-bindgen = "*"
wee_alloc = {version = "*", optional = true}
随机算法
impl Universe {
...
pub fn new(width: u32, height: u32) -> Universe {
let size = (width * height) as usize;
let mut cells = FixedBitSet::with_capacity(size);
(0..size).for_each(|i| cells.set(i, Math::random() < 0.5));
Universe {
width,
height,
cells,
}
}
...
}
世界定义
use fixedbitset::FixedBitSet;
...
#[wasm_bindgen]
pub struct Universe {
width: u32,
height: u32,
cells: FixedBitSet,
}
确定下一跳状态
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) {
(true, x) if x < 2 || x > 3 => false,
(true, 2) | (true, 3) | (false, 3) => true,
(otherwise, _) => otherwise,
};
next.set(idx, next_cell);
}
}
self.cells = next;
}
暴露细胞列表地址
pub fn cells(&self) -> *const u32 {
self.cells.as_slice().as_ptr()
}
位地址的细胞状态
const bitIsSet = (n, arr) => {
const byte = Math.floor(n / 8);
const mask = 1 << (n % 8);
return (arr[byte] & mask) === mask;
};
画细胞
const drawCells = () => {
const cellsPtr = universe.cells();
const cells = new Uint8Array(memory.buffer, cellsPtr, WIDTH * HEIGHT / 8);
ctx.beginPath();
for (let row = 0; row < HEIGHT; row++) {
for (let col = 0; col < WIDTH; col++) {
const idx = row * WIDTH + col;
ctx.fillStyle = bitIsSet(idx, cells) ? ALIVE_COLOR : DEAD_COLOR;
ctx.fillRect(col * (CELL_SIZE + 1) + 1,
row * (CELL_SIZE + 1) + 1, CELL_SIZE, CELL_SIZE);
}
}
ctx.stroke();
};
总结
重构了康威生命游戏,生成细胞的算法修改成了随机,并且细胞存储使用位存储。
附录
Cargo.toml
[package]
edition = "2021"
name = "game"
version = "0.1.0"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
console_error_panic_hook = {version = "*", optional = true}
fixedbitset = "*"
js-sys = "*"
wasm-bindgen = "*"
wee_alloc = {version = "*", optional = true}
[dev-dependencies]
wasm-bindgen-test = "*"
[profile.release]
opt-level = "s"
lib.rs
mod utils;
use fixedbitset::FixedBitSet;
use js_sys::Math;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
pub struct Universe {
width: u32,
height: u32,
cells: FixedBitSet,
}
impl Universe {
fn get_index(&self, row: u32, column: u32) -> usize {
(row * self.width + column) as usize
}
fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
let mut count = 0;
for delta_row in [self.height - 1, 0, 1] {
for delta_col in [self.width - 1, 0, 1] {
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
}
}
#[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) {
(true, x) if x < 2 || x > 3 => false,
(true, 2) | (true, 3) | (false, 3) => true,
(otherwise, _) => otherwise,
};
next.set(idx, next_cell);
}
}
self.cells = next;
}
pub fn cells(&self) -> *const u32 {
self.cells.as_slice().as_ptr()
}
pub fn new(width: u32, height: u32) -> Universe {
let size = (width * height) as usize;
let mut cells = FixedBitSet::with_capacity(size);
(0..size).for_each(|i| cells.set(i, Math::random() < 0.5));
Universe {
width,
height,
cells,
}
}
}
index.js
import { Universe } from "game";
import { memory } from "game/game_bg";
const CELL_SIZE = 10;
const GRID_COLOR = "#CCCCCC";
const DEAD_COLOR = "#FFFFFF";
const ALIVE_COLOR = "#000000";
const WIDTH = 64;
const HEIGHT = 64;
const canvas = document.getElementById("game");
canvas.height = (CELL_SIZE + 1) * HEIGHT + 1;
canvas.width = (CELL_SIZE + 1) * WIDTH + 1;
const ctx = canvas.getContext('2d');
const universe = Universe.new(WIDTH, HEIGHT);
const renderLoop = () => {
universe.tick();
drawGrid();
drawCells();
requestAnimationFrame(renderLoop);
};
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();
};
const bitIsSet = (n, arr) => {
const byte = Math.floor(n / 8);
const mask = 1 << (n % 8);
return (arr[byte] & mask) === mask;
};
const drawCells = () => {
const cellsPtr = universe.cells();
const cells = new Uint8Array(memory.buffer, cellsPtr, WIDTH * HEIGHT / 8);
ctx.beginPath();
for (let row = 0; row < HEIGHT; row++) {
for (let col = 0; col < WIDTH; col++) {
const idx = row * WIDTH + col;
ctx.fillStyle = bitIsSet(idx, cells) ? ALIVE_COLOR : DEAD_COLOR;
ctx.fillRect(col * (CELL_SIZE + 1) + 1,
row * (CELL_SIZE + 1) + 1, CELL_SIZE, CELL_SIZE);
}
}
ctx.stroke();
};
drawGrid();
drawCells();
requestAnimationFrame(renderLoop);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!