From 1b6a7e0d00703e3da2e1620b5a2b2cba027161de Mon Sep 17 00:00:00 2001 From: Joris Date: Tue, 28 Jan 2020 09:55:58 +0100 Subject: Implement game of life --- src/canvas.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ src/console.rs | 5 ++++ src/game.rs | 31 ++++++++++++++++++++++++ src/game_loop.rs | 31 ++++++++++++++++++++++++ src/lib.rs | 26 ++++++++++++++++++++ src/state.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+) create mode 100644 src/canvas.rs create mode 100644 src/console.rs create mode 100644 src/game.rs create mode 100644 src/game_loop.rs create mode 100644 src/lib.rs create mode 100644 src/state.rs (limited to 'src') diff --git a/src/canvas.rs b/src/canvas.rs new file mode 100644 index 0000000..38aef4d --- /dev/null +++ b/src/canvas.rs @@ -0,0 +1,64 @@ +use wasm_bindgen::prelude::JsValue; +use wasm_bindgen::JsCast; +use web_sys::CanvasRenderingContext2d; + +pub struct Canvas { + context: CanvasRenderingContext2d, + width: u32, + height: u32, + scaled_width: u32, + scaled_height: u32, +} + +impl Canvas { + pub fn new(attr_id: &str, width: u32, height: u32) -> Canvas { + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document.get_element_by_id(attr_id).unwrap(); + let canvas: web_sys::HtmlCanvasElement = canvas + .dyn_into::() + .unwrap(); + + let context = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + + let scaled_width = canvas.width() / width; + let scaled_height = canvas.height() / height; + + Canvas { + context, + width, + height, + scaled_width, + scaled_height, + } + } + + pub fn clear(&self) { + self.context.set_fill_style(&JsValue::from_str("white")); + + self.context.fill_rect( + f64::from(0), + f64::from(0), + f64::from(self.width * self.scaled_width), + f64::from(self.height * self.scaled_height) + ); + } + + pub fn draw(&self, x: u32, y: u32, color: &str) { + assert!(x < self.width); + assert!(y < self.height); + + self.context.set_fill_style(&JsValue::from_str(color)); + + self.context.fill_rect( + f64::from(x * self.scaled_width), + f64::from(y * self.scaled_height), + f64::from(self.scaled_width), + f64::from(self.scaled_height) + ); + } +} diff --git a/src/console.rs b/src/console.rs new file mode 100644 index 0000000..4efbd80 --- /dev/null +++ b/src/console.rs @@ -0,0 +1,5 @@ +use wasm_bindgen::prelude::JsValue; + +pub fn log(str: &str) { + web_sys::console::log_1(&JsValue::from_str(str)); +} diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..4b527d2 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,31 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use crate::canvas::Canvas; +use crate::state::State; + +pub struct Game { + canvas: Canvas, + state: Rc>, +} + +impl Game { + pub fn new(canvas_id: &str, width: u32, height: u32) -> Game { + let canvas = Canvas::new(canvas_id, width, height); + let state = Rc::new(RefCell::new(State::new(width, height))); + + Game { + canvas, + state, + } + } + + pub fn update(&self) { + let next_state = self.state.borrow().next(); + *self.state.borrow_mut() = next_state; + } + + pub fn render(&self) { + self.state.borrow().draw(&self.canvas); + } +} diff --git a/src/game_loop.rs b/src/game_loop.rs new file mode 100644 index 0000000..34dd477 --- /dev/null +++ b/src/game_loop.rs @@ -0,0 +1,31 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::prelude::Closure; +use wasm_bindgen::JsCast; + +use crate::game::Game; + +pub fn run(game: Game, update_period: i32) { + game.render(); + + let f = Rc::new(RefCell::new(None)); + let g = f.clone(); + + *g.borrow_mut() = Some(Closure::wrap(Box::new(move |_| { + + game.update(); + game.render(); + + set_timeout(f.borrow().as_ref().unwrap(), update_period); + + }) as Box)); + + set_timeout(g.borrow().as_ref().unwrap(), update_period); +} + +fn set_timeout(f: &Closure, timeout: i32) { + web_sys::window().unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), timeout) + .unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f7fa24c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,26 @@ +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +mod canvas; +mod state; +mod game; +mod game_loop; + +use game::Game; + +#[wasm_bindgen(start)] +pub fn main() -> Result<(), JsValue> { + set_panic_hook(); + game_loop::run(Game::new("canvas", 100, 100), 100); + Ok(()) +} + +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..5d13902 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,72 @@ +use crate::canvas::Canvas; + +#[derive(Debug, Clone)] +pub struct State { + width: u32, + height: u32, + cells: Vec::, +} + +impl State { + pub fn new(width: u32, height: u32) -> State { + let cells = (0..width * height) + .map(|_| js_sys::Math::random() < 0.5) + .collect(); + + State { + width, + height, + cells, + } + } + + pub fn neighbor_count(&self, x: u32, y: u32) -> u32 { + let mut count = 0; + for delta_x in [self.height - 1, 0, 1].iter().clone() { + for delta_y in [self.width - 1, 0, 1].iter().clone() { + let neighbor_x = (x + delta_x) % self.width; + let neighbor_y = (y + delta_y) % self.height; + + if (*delta_x, *delta_y) != (0, 0) && self.is_on(neighbor_x, neighbor_y) { + count += 1; + } + } + } + count + } + + pub fn next(&self) -> State { + let cells = (0..self.width * self.height) + .map(|i| { + let x = i % self.width; + let y = i / self.width; + let neighbor_count = self.neighbor_count(x, y); + neighbor_count == 3 || self.is_on(x, y) && neighbor_count == 2 + }) + .collect(); + + State { + width: self.width, + height: self.height, + cells, + } + } + + fn is_on(&self, x: u32, y: u32) -> bool { + let inside_x = (x + self.width) % self.width; + let inside_y = (y + self.height) % self.height; + self.cells[(inside_x + inside_y * self.width) as usize] + } + + pub fn draw(&self, canvas: &Canvas) { + canvas.clear(); + + for y in 0..self.height { + for x in 0..self.width { + if self.is_on(x, y) { + canvas.draw(x, y, "green"); + } + } + } + } +} -- cgit v1.2.3