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 渲染游戏,不过还有两点可以进行优化:

  1. 生成细胞的算法是固定,应该采用随机的方式。
  2. 一个细胞占用了一个字节,其实一位就可以表示存活与否了。

引入依赖

[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);
posted @   jiangbo4444  阅读(123)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示