影片地址:www.bilibili.com/video/BV1eg411g7c...
相關原始碼:github.com/anonymousGiga/Rust-and-...
本節,我們就用WebAssembly實現一個簡單的遊戲。
在一個二維方格中,每個方格的狀態都為“生”或者“死”。每個方格對應的就是一個細胞,每個細胞和它的周圍的八個方格相鄰。在每個時間推移過程中,都會發生以下轉換:
1、 任何少於兩個活鄰居的活細胞都會死亡。
2、 任何有兩個或三個活鄰居的活細胞都能存活到下一代。
3、 任何一個有三個以上鄰居的活細胞都會死亡。
4、 任何一個有三個活鄰居的死細胞都會變成一個活細胞。
考慮初始狀態:
----------------
| | | | | |
----------------
| | |生| | |
----------------
| | |生| | |
----------------
| | |生| | |
----------------
| | | | | |
----------------
按照上面的規則,下一個時間點,將會變成:
----------------
| | | | | |
----------------
| | | | | |
----------------
| |生|生|生| |
----------------
| | | | | |
----------------
| | | | | |
----------------
2.1 設計規則
2.1.1 宇宙的設計
所謂宇宙,也就是二維的方格的設計。因為生命週期的遊戲是在無限的宇宙中進行的,但是我們沒有無限的記憶和計算能力,所以我們對整個宇宙可以由三種設計方式:
1、不斷擴充套件的方式。
2、建立固定大小的宇宙,其中邊緣上的細胞比中間的細胞少,是一種有盡頭的模式。
3、建立一個固定大小的宇宙,但是左邊的盡頭就是右邊。
2.1.2 Rust和Js互動的原則
1、最小化對WebAssembly線性記憶體的複製。不必要的複製會帶來不必要的開銷。
2、最小序列化和反序列化。與副本類似,序列化與反序列化也會帶來開銷,而且通常也帶來複制。
一般來講,一個好的javascript和WebAssembly介面設計通常是將大的、長壽麵的資料結構實現為駐留在WebAssembly線性記憶體中的Rust型別,並將其作為不透明控制程式碼傳遞給JavaScript。
2.1.3 在我們遊戲中Rust和Js互動的設計
我們可以用一個陣列表示,每個元素裡面0表示死細胞,1表示活細胞,因此,4*4的宇宙是這樣的:
Indices: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-------------------------------------------------
array :| | | | | | | | | | | | | | | | |
-------------------------------------------------
Rows: | 0 | 1 | 2 | 3 |
要在宇宙中找出給定行和列的索引值,公式如下:
index(row, column, universe) = row * width(universe) + column
開始修改wasm-game-of-life/src/lib.rs中新增程式碼
3.1 定義細胞狀態列舉型別
程式碼如下:
#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
Dead = 0,
Alive = 1,
}
在上面的程式碼中,我們定義了每個細胞的狀態,0表示死,1表示生。上面的#[repr(u8)]是表示下面的列舉型別佔用記憶體8個位元。
3.2 定義宇宙
下面我們定義宇宙,程式碼如下:
#[wasm_bindgen]
pub struct Universe {
width: u32,
height: u32,
cells: Vec<Cell>,
}
下面定義相關的方法:
#[wasm_bindgen]
impl Universe {
//獲取到對應的索引
fn get_index(&self, row: u32, column: u32) -> usize {
(row*self.width + column) as usize
}
//獲取活著的鄰居個數
//相鄰的都是-1, 0, 1
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
}
//計算下一個滴答的狀態
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;
}
}
至此,我們基本上把核心的邏輯寫完了,不過,我們想用黑色方格表示生的細胞,用空的方格表示死的細胞,我們還需要寫如下程式碼:
use std::fmt;
impl fmt::Display for Universe {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for line in self.cells.as_slice().chunks(self.width as usize) {
for &cell in line {
let symbol = if cell == Cell::Dead {
'◻'
} else {
'◼'
};
write!(f, "{}", symbol)?;
}
write!(f, "\n")?;
}
Ok(())
}
}
接下來,我們寫剩餘的程式碼,建立宇宙的程式碼和填充的程式碼:
#[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()
}
...
}
3.3 編譯
使用如下命令編譯:
wasm-pack build
修改wasm-game-of-life/www/index.html如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>game-of-life-canvas</title>
<style>
body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<pre id="game-of-life-canvas"></pre>
<script src="./bootstrap.js"></script>
</body>
</html>
修改index.js的程式碼如下:
import { Universe } from "wasm-game-of-life";
const pre = document.getElementById("game-of-life-canvas");
const universe = Universe.new();
//alert("+++++++++++");
function renderLoop() {
pre.textContent = universe.render();
universe.tick();
window.requestAnimationFrame(renderLoop);
}
window.requestAnimationFrame(renderLoop);
進到www目錄下,執行命令:
npm run start
在瀏覽器中輸入以下地址,顯示細胞的變化:
127.0.0.1:8080
本作品採用《CC 協議》,轉載必須註明作者和本文連結