1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-05-23 10:50:18 +02:00

game-of-life.md

This commit is contained in:
sakex 2023-08-06 23:26:15 +02:00
parent 771e41ccdd
commit 6f94c9e7a4
6 changed files with 267 additions and 5 deletions

View File

@ -23,7 +23,7 @@ extern "C" {
}
#[wasm_bindgen]
pub fn set_panic_hook() {
pub fn setup() {
console_error_panic_hook::set_once();
}
@ -47,4 +47,4 @@ First off let's implement some methods that modify the video live:
4. `(BONUS)` Now feel free to implement other transformations such as `greyscale` or adding `random noise`.
5. Let's now add functionalities to our page. In the `setup` function create a dropdown (`<select>`) that will change which transformation to apply to the image.
6. `(BONUS)` Track moving objects. This can be done by figuring out only the pixels that didn't change between multiple frames. For instance, you could compute the standard deviation of the pixel and black out below a threshold.
While this can be achieved without touching Javascript, I recommend editing it.
While this can be achieved without touching Javascript, I recommend editing it.

View 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.

View 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;
}
```

View File

@ -27,6 +27,7 @@ features = [
'Element',
'ImageData',
'CanvasRenderingContext2d',
'HtmlCanvasElement',
'HtmlSelectElement',
'HtmlElement',
'Node',

View File

@ -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>

View File

@ -13,8 +13,8 @@ pub fn add_a_cat() -> Result<(), JsValue> {
let image = document.create_element("img")?;
// We need a random number to prevent the browser from caching the image
let random_number = js_sys::Math::random();
image.set_attribute("src", &format!("https://cataas.com/cat?random={random_number}"))?;
let random_number = js_sys::Math::random() * 10_000.0;
image.set_attribute("src", &format!("https://picsum.photos/300?random={random_number}"))?;
body.append_child(&image)?;
Ok(())
@ -36,4 +36,4 @@ import init, {set_panic_hook, add_a_cat} from '/wasm/project.js';
add_a_cat();
});
})();
```
```