0 Introduction
Video address: https://www.bilibili.com/video/BV1eg411g7c8
Relevant source code: https://github.com/anonymousGiga/Rust-and-Web-Assembly
In this section, we will use WebAssembly to implement a simple game.
1 rules of the game
In a two-dimensional grid, the state of each grid is "life" or "death". Each square corresponds to a cell, and each cell is adjacent to the eight squares around it. During each time lapse, the following transformations occur:
1. Any living cell with less than two living neighbors will die.
2. Any living cell with two or three living neighbors can survive to the next generation.
3. Any living cell with more than three neighbors will die.
4. Any dead cell with three living neighbors will become a living cell.
Consider the initial state:
---------------- | | | | | | ---------------- | | |living| | | ---------------- | | |living| | | ---------------- | | |living| | | ---------------- | | | | | | ----------------
According to the above rules, the next time point will become:
---------------- | | | | | | ---------------- | | | | | | ---------------- | |living|living|living| | ---------------- | | | | | | ---------------- | | | | | | ----------------
2 design
2.1 design rules
2.1.1 design of the universe
The so-called universe is the design of two-dimensional squares. Because the game of life cycle is played in the infinite universe, but we don't have infinite memory and computing power, we can design the whole universe in three ways:
1. An expanding way.
2. Creating a fixed size universe with fewer cells on the edge than in the middle is a pattern with an end.
3. Create a fixed size universe, but the end on the left is the right.
2.1.2 principles of interaction between rust and Js
1. Minimize replication of linear memory in WebAssembly. Unnecessary copies incur unnecessary overhead.
2. Minimum serialization and deserialization. Like replicas, serialization and deserialization incur overhead, and often replication.
Generally speaking, a good JavaScript and web assembly interface design usually implements a large and long-lived data structure as a Rust type residing in the linear memory of the web assembly, and passes it to JavaScript as an opaque handle.
2.1.3 design of interaction between Rust and Js in our game
We can use an array to represent it. In each element, 0 represents dead cells and 1 represents living cells. Therefore, the 4 * 4 universe is like this:
Indices: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ------------------------------------------------- array :| | | | | | | | | | | | | | | | | ------------------------------------------------- Rows: | 0 | 1 | 2 | 3 |
To find the index value of a given row and column in the universe, the formula is as follows:
index(row, column, universe) = row * width(universe) + column
3. Rust implementation
Start modifying wasm game of life / SRC / lib Add code to RS
3.1 defining cell state enumeration types
The code is as follows:
#[wasm_bindgen] #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cell { Dead = 0, Alive = 1, }
In the above code, we defined the state of each cell. 0 means death and 1 means life. The #[repr(u8)] above indicates that the following enumeration types occupy 8 bits of memory.
3.2 defining the universe
Let's define the universe as follows:
#[wasm_bindgen] pub struct Universe { width: u32, height: u32, cells: Vec<Cell>, }
Related methods are defined below:
#[wasm_bindgen] impl Universe { //Get the corresponding index fn get_index(&self, row: u32, column: u32) -> usize { (row*self.width + column) as usize } //Get the number of living neighbors //Adjacent are - 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 } //Calculate the status of the next tick 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; } }
So far, we have basically finished the core logic. However, we want to use black squares to represent living cells and empty squares to represent dead cells. We also need to write the following code:
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(()) } }
Next, let's write the rest of the code, create the code of the universe and fill in the code:
#[wasm_bindgen] impl Universe { //establish 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, } } //fill pub fn render(&self) -> String { self.to_string() } ... }
3.3 compilation
Compile with the following command:
wasm-pack build
4 call code writing
Modify wasm game of life / www / index The html is as follows:
<!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>
Modify index JS code is as follows:
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);
5 call
Enter the www directory and execute the command:
npm run start
Enter the following address in the browser to display the changes of cells:
127.0.0.1:8080