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:
parent
771e41ccdd
commit
6f94c9e7a4
@ -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.
|
||||
|
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',
|
||||
'ImageData',
|
||||
'CanvasRenderingContext2d',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlSelectElement',
|
||||
'HtmlElement',
|
||||
'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")?;
|
||||
// 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();
|
||||
});
|
||||
})();
|
||||
```
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user