mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-05-24 03:10:28 +02:00
game-of-life.md
This commit is contained in:
parent
771e41ccdd
commit
6f94c9e7a4
@ -23,7 +23,7 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_panic_hook() {
|
pub fn setup() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
46
src/exercises/webassembly/game-of-life.md
Normal file
46
src/exercises/webassembly/game-of-life.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Game of life exercise
|
||||||
|
|
||||||
|
The goal of this exercise is to implement [John Conway's Game Of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life).
|
||||||
|
|
||||||
|
Serve the web server and navigate to [http://localhost:8000/exercises/game_of_life/](http://localhost:8000/exercises/game_of_life/).
|
||||||
|
|
||||||
|
You will edit [lib.rs](../../rust-wasm-template/lib.rs) as usual.
|
||||||
|
|
||||||
|
Here is the basic code you will need:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate console_error_panic_hook;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
fn alert(s: &str);
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_namespace = console)]
|
||||||
|
pub fn log(s: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_panic_hook() {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn start(canvas: HtmlCanvasElement, width: i32, height: i32) -> Result<(), JsValue> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Let's create a `GameOfLife` struct that segments the canvas into cells of dimensions `10x10` pixels. Cells can be
|
||||||
|
either dead or alive.
|
||||||
|
2. Use the struct to paint every alive cells with and dead cells black. To test this, make every other cell alive.
|
||||||
|
3. Randomly change every cell's status to dead or alive, multiple times per second.
|
||||||
|
4. Using Javascript and Rust; Create a button that triggers the start of the game and allows stopping/restarting it.
|
||||||
|
5. Implement the rules of game of life:
|
||||||
|
1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
|
||||||
|
2. Any live cell with two or three live neighbours lives on to the next generation.
|
||||||
|
3. Any live cell with more than three live neighbours dies, as if by overpopulation.
|
||||||
|
4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
|
||||||
|
6. Create an event listener to manually kill or revive cells on click.
|
190
src/exercises/webassembly/solutions-game-of-life.md
Normal file
190
src/exercises/webassembly/solutions-game-of-life.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# Solutions for the Game Of Life exercise
|
||||||
|
|
||||||
|
1. Let's create a `GameOfLife` struct that segments the canvas into cells of dimensions `10x10` pixels. Cells can be
|
||||||
|
either dead or alive.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct GameOfLife {
|
||||||
|
ctx: CanvasRenderingContext2d,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
// true == alive, false == dead
|
||||||
|
cells: Vec<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameOfLife {
|
||||||
|
fn new(ctx: CanvasRenderingContext2d, width: i32, height: i32) -> Self {
|
||||||
|
let mut cells = vec![false; (width * height) as usize / 10];
|
||||||
|
cells.iter_mut().step_by(2).for_each(|cell| *cell = true);
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
cells,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use the struct to paint every alive cells with and dead cells black. To test this, make every other cell alive.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// In impl GameOfLife
|
||||||
|
fn draw(&self) {
|
||||||
|
self.ctx
|
||||||
|
.clear_rect(0.0, 0.0, self.width as f64, self.height as f64);
|
||||||
|
self.ctx.set_fill_style(&JsValue::from_str("white"));
|
||||||
|
for (i, cell) in self.cells.iter().enumerate() {
|
||||||
|
if *cell {
|
||||||
|
let x = (i % (self.width as usize / 10)) as f64 * 10.0;
|
||||||
|
let y = (i / (self.width as usize / 10)) as f64 * 10.0;
|
||||||
|
self.ctx.fill_rect(x, y, 10.0, 10.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 4 times per second, randomly change every cell's status to dead or alive.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct GameOfLifeHandle {
|
||||||
|
closure: Closure<dyn FnMut()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// In impl GameOfLife
|
||||||
|
fn game_logic(&mut self) {
|
||||||
|
for cell in &mut self.cells {
|
||||||
|
let rand = js_sys::Math::random();
|
||||||
|
if rand > 0.5 {
|
||||||
|
*cell = true;
|
||||||
|
} else {
|
||||||
|
*cell = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(mut self) -> GameOfLifeHandle {
|
||||||
|
let closure = Closure::wrap(Box::new(move || {
|
||||||
|
self.game_logic();
|
||||||
|
self.draw();
|
||||||
|
}) as Box<dyn FnMut()>);
|
||||||
|
setInterval(&closure, 250);
|
||||||
|
GameOfLifeHandle { closure }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Using Javascript and Rust; Create a button that triggers the start of the game and allows stopping it.
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import init, {setup, GameOfLife} from '/wasm/project.js';
|
||||||
|
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Run the init method to initiate the WebAssembly module.
|
||||||
|
await init();
|
||||||
|
setup();
|
||||||
|
const canvas = document.getElementById('game-of-life-canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const game = new GameOfLife(ctx, canvas.width, canvas.height);
|
||||||
|
const startButton = document.createElement('button');
|
||||||
|
document.body.appendChild(startButton);
|
||||||
|
let handle = null;
|
||||||
|
let playing = false;
|
||||||
|
startButton.addEventListener('click', () => {
|
||||||
|
if (handle === null) {
|
||||||
|
handle = game.start();
|
||||||
|
playing = true;
|
||||||
|
} else {
|
||||||
|
handle.stop_or_start();
|
||||||
|
playing = !playing;
|
||||||
|
}
|
||||||
|
startButton.textContent = playing ? 'STOP' : 'START';
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||||
|
fn clearInterval(token: f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct GameOfLifeHandle {
|
||||||
|
closure: Closure<dyn FnMut()>,
|
||||||
|
interval: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl GameOfLifeHandle {
|
||||||
|
pub fn stop_or_start(&mut self) {
|
||||||
|
if let Some(interval) = self.interval.take() {
|
||||||
|
clearInterval(interval);
|
||||||
|
} else {
|
||||||
|
let interval = setInterval(&self.closure, 250);
|
||||||
|
self.interval = Some(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Implement the rules of game of life
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn alive_neighbour_count(&self, idx: i32) -> usize {
|
||||||
|
let mut alive_count = 0;
|
||||||
|
let (width, height) = (self.width as i32 / 10, self.height as i32 / 10);
|
||||||
|
let x = idx % width;
|
||||||
|
let y = idx / width;
|
||||||
|
for i in -1..=1 {
|
||||||
|
for j in -1..=1 {
|
||||||
|
if i == 0 && j == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let x = x as i32 + i;
|
||||||
|
let y = y as i32 + j;
|
||||||
|
if x < 0 || x >= width || y < 0 || y >= height {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let idx = (x + y * self.width) as usize;
|
||||||
|
if self.cells[idx] {
|
||||||
|
alive_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alive_count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_logic(&mut self) {
|
||||||
|
let mut new_cells = self.cells.clone();
|
||||||
|
for idx in 0..self.cells.len() {
|
||||||
|
let alive_count = self.alive_neighbour_count(idx as i32);
|
||||||
|
// Rule 1
|
||||||
|
if self.cells[idx] && alive_count < 2 {
|
||||||
|
new_cells[idx] = false;
|
||||||
|
}
|
||||||
|
// Rule 2
|
||||||
|
if self.cells[idx] && (alive_count == 2 || alive_count == 3) {
|
||||||
|
new_cells[idx] = true;
|
||||||
|
}
|
||||||
|
// Rule 3
|
||||||
|
if self.cells[idx] && alive_count > 3 {
|
||||||
|
new_cells[idx] = false;
|
||||||
|
}
|
||||||
|
// Rule 4
|
||||||
|
if !self.cells[idx] && alive_count == 3 {
|
||||||
|
new_cells[idx] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cells = new_cells;
|
||||||
|
}
|
||||||
|
```
|
@ -27,6 +27,7 @@ features = [
|
|||||||
'Element',
|
'Element',
|
||||||
'ImageData',
|
'ImageData',
|
||||||
'CanvasRenderingContext2d',
|
'CanvasRenderingContext2d',
|
||||||
|
'HtmlCanvasElement',
|
||||||
'HtmlSelectElement',
|
'HtmlSelectElement',
|
||||||
'HtmlElement',
|
'HtmlElement',
|
||||||
'Node',
|
'Node',
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>comprehensive-rust Game Of Life exercise</title>
|
||||||
|
<link rel="stylesheet" href="/index.css">
|
||||||
|
<style>
|
||||||
|
canvas {
|
||||||
|
width: 1000px;
|
||||||
|
height: 800px;
|
||||||
|
display: block;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
comprehensive-rust Game Of Life exercise
|
||||||
|
</h1>
|
||||||
|
<button id="start-button">START</button>
|
||||||
|
|
||||||
|
<canvas id="game-of-life-canvas" width="1000" height="800"></canvas>
|
||||||
|
<script type="module" src="/exercises/game_of_life/index.mjs"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -13,8 +13,8 @@ pub fn add_a_cat() -> Result<(), JsValue> {
|
|||||||
|
|
||||||
let image = document.create_element("img")?;
|
let image = document.create_element("img")?;
|
||||||
// We need a random number to prevent the browser from caching the image
|
// We need a random number to prevent the browser from caching the image
|
||||||
let random_number = js_sys::Math::random();
|
let random_number = js_sys::Math::random() * 10_000.0;
|
||||||
image.set_attribute("src", &format!("https://cataas.com/cat?random={random_number}"))?;
|
image.set_attribute("src", &format!("https://picsum.photos/300?random={random_number}"))?;
|
||||||
body.append_child(&image)?;
|
body.append_child(&image)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user