aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/canvas.rs64
-rw-r--r--src/console.rs5
-rw-r--r--src/game.rs31
-rw-r--r--src/game_loop.rs31
-rw-r--r--src/lib.rs26
-rw-r--r--src/state.rs72
6 files changed, 229 insertions, 0 deletions
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::<web_sys::HtmlCanvasElement>()
+ .unwrap();
+
+ let context = canvas
+ .get_context("2d")
+ .unwrap()
+ .unwrap()
+ .dyn_into::<web_sys::CanvasRenderingContext2d>()
+ .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<RefCell<State>>,
+}
+
+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<dyn FnMut(i32)>));
+
+ set_timeout(g.borrow().as_ref().unwrap(), update_period);
+}
+
+fn set_timeout(f: &Closure<dyn FnMut(i32)>, 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::<bool>,
+}
+
+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");
+ }
+ }
+ }
+ }
+}