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();
|
||||
}
|
||||
|
||||
|
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(())
|
||||
|
Loading…
x
Reference in New Issue
Block a user