diff --git a/src/lib.rs b/src/lib.rs index 04b4ac8..42d7909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ mod utils; +use std::fmt; + use wasm_bindgen::prelude::*; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global @@ -31,3 +33,124 @@ pub fn greet(s: &str) { pub fn info(s: &str) { log(&format!("{s}: from rust !!! hot reload? 6 ?")); } + +#[wasm_bindgen] +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Cell { + Dead = 0, + Alive = 1, +} +#[wasm_bindgen] +pub struct Universe { + width: u32, + height: u32, + cells: Vec, +} + +#[wasm_bindgen] +impl Universe { + fn get_index(&self, row: u32, column: u32) -> usize { + (row * self.width + column) as usize + } + + fn live_neighbor_count(&self, row: u32, column: u32) -> u8 { + let mut count = 0; + for delta_row in [self.height - 1, 0, 1].iter().cloned() { + for delta_col in [self.width - 1, 0, 1].iter().cloned() { + if delta_row == 0 && delta_col == 0 { + continue; + } + + let neighbor_row = (row + delta_row) % self.height; + let neighbor_col = (column + delta_col) % self.width; + let idx = self.get_index(neighbor_row, neighbor_col); + count += self.cells[idx] as u8; + } + } + count + } + + pub fn tick(&mut self) { + let mut next = self.cells.clone(); + + for row in 0..self.height { + for col in 0..self.width { + let idx = self.get_index(row, col); + let cell = self.cells[idx]; + let live_neighbors = self.live_neighbor_count(row, col); + + let next_cell = match (cell, live_neighbors) { + // Rule 1: Any live cell with fewer than two live neighbours + // dies, as if caused by underpopulation. + (Cell::Alive, x) if x < 2 => Cell::Dead, + // Rule 2: Any live cell with two or three live neighbours + // lives on to the next generation. + (Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive, + // Rule 3: Any live cell with more than three live + // neighbours dies, as if by overpopulation. + (Cell::Alive, x) if x > 3 => Cell::Dead, + // Rule 4: Any dead cell with exactly three live neighbours + // becomes a live cell, as if by reproduction. + (Cell::Dead, 3) => Cell::Alive, + // All other cells remain in the same state. + (otherwise, _) => otherwise, + }; + + next[idx] = next_cell; + } + } + + self.cells = next; + } + + pub fn new() -> Universe { + let width = 64; + let height = 64; + + let cells = (0..width * height) + .map(|i| { + if i % 2 == 0 || i % 7 == 0 { + Cell::Alive + } else { + Cell::Dead + } + }) + .collect(); + + Universe { + width, + height, + cells, + } + } + + pub fn render(&self) -> String { + self.to_string() + } + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn cells(&self) -> *const Cell { + self.cells.as_ptr() + } +} + +impl fmt::Display for Universe { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for line in self.cells.as_slice().chunks(self.width as usize) { + for &cell in line { + let symbol = if cell == Cell::Dead { '◻' } else { '◼' }; + write!(f, "{}", symbol)?; + } + write!(f, "\n")?; + } + + Ok(()) + } +} diff --git a/www/index.html b/www/index.html index ca5615c..bae9043 100644 --- a/www/index.html +++ b/www/index.html @@ -6,6 +6,8 @@ +

+    
     
   
 
diff --git a/www/index.js b/www/index.js
index 3c783bf..b76be3f 100644
--- a/www/index.js
+++ b/www/index.js
@@ -1,10 +1,96 @@
 // import * as wasm from "hello-wasm-pack";
 import * as wasm from "wasm-game-of-life";
+import { Universe, Cell } from "wasm-game-of-life";
+import { memory } from "wasm-game-of-life/wasm_game_of_life_bg.wasm";
 
 var x = 3;
 wasm.info("hello" + x);
+const CELL_SIZE = 5; // px
+const GRID_COLOR = "#CCCCCC";
+const DEAD_COLOR = "#FFFFFF";
+const ALIVE_COLOR = "#000000";
 
 
 // wasm.greet("you" + x);
 
+// const pre = document.getElementById("game-of-life-canvas");
+// const universe = Universe.new();
+// 
+// const renderLoop = () => {
+//   pre.textContent = universe.render();
+//   universe.tick();
+// 
+//   requestAnimationFrame(renderLoop);
+// };
+// requestAnimationFrame(renderLoop);
+// 
 
+
+const canvas = document.getElementById("game-of-life-canvas");
+canvas.height = (CELL_SIZE + 1) * height + 1;
+canvas.width = (CELL_SIZE + 1) * width + 1;
+
+const ctx = canvas.getContext('2d');
+
+const renderLoop = () => {
+  universe.tick();
+
+  drawGrid();
+  drawCells();
+
+  requestAnimationFrame(renderLoop);
+};
+
+const drawGrid = () => {
+  ctx.beginPath();
+  ctx.strokeStyle = GRID_COLOR;
+
+  // Vertical lines.
+  for (let i = 0; i <= width; i++) {
+    ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
+    ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
+  }
+
+  // Horizontal lines.
+  for (let j = 0; j <= height; j++) {
+    ctx.moveTo(0,                           j * (CELL_SIZE + 1) + 1);
+    ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
+  }
+
+  ctx.stroke();
+};
+
+
+const getIndex = (row, column) => {
+  return row * width + column;
+};
+
+const drawCells = () => {
+  const cellsPtr = universe.cells();
+  const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);
+
+  ctx.beginPath();
+
+  for (let row = 0; row < height; row++) {
+    for (let col = 0; col < width; col++) {
+      const idx = getIndex(row, col);
+
+      ctx.fillStyle = cells[idx] === Cell.Dead
+        ? DEAD_COLOR
+        : ALIVE_COLOR;
+
+      ctx.fillRect(
+        col * (CELL_SIZE + 1) + 1,
+        row * (CELL_SIZE + 1) + 1,
+        CELL_SIZE,
+        CELL_SIZE
+      );
+    }
+  }
+
+  ctx.stroke();
+};
+
+drawGrid();
+drawCells();
+requestAnimationFrame(renderLoop);