diff --git a/Cargo.toml b/Cargo.toml
index 09ccdf3..b9f445d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,10 @@ name = "rust-wasm-minesweeper"
version = "0.1.0"
edition = "2021"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+crate-type=["cdylib"]
[dependencies]
rand = "0.8.5"
+wasm-bindgen = "0.2.80"
+getrandom = { version = "0.2.6", features = ["js"] }
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..ca3094a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+ Minesweeper
+
+
+
+
+
+
+
+
+
diff --git a/src/lib.rs b/src/lib.rs
index 0c9db93..f6fc4ae 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,122 +1,21 @@
-use std::{collections::HashSet, fmt::{Display, Write}};
+#![allow(unused_unsafe)]
+mod minesweeper;
-use rand::Rng;
+use std::cell::RefCell;
-pub type Position = (usize, usize);
+use minesweeper::*;
+use wasm_bindgen::prelude::*;
-#[derive(Debug)]
-pub struct Minesweeper {
- width: usize,
- height: usize,
- open_fields: HashSet,
- mines: HashSet,
- flags: HashSet,
+thread_local! {
+ static MINESWEEPER: RefCell = RefCell::new(Minesweeper::new(10, 10, 10));
}
-impl Display for Minesweeper {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- for y in 0..self.height {
- for x in 0..self.height {
- let pos = (x, y);
- if self.flags.contains(&pos) {
- f.write_str("F ")?;
- } else if !self.open_fields.contains(&pos) {
- f.write_str("# ")?;
- } else if self.mines.contains(&pos) {
- f.write_str("B ")?;
- } else {
- write!(f, "{} ", self.count_mines(pos))?;
- }
- }
- f.write_char('\n')?;
- }
-
- Ok(())
- }
+#[wasm_bindgen(js_name = getState)]
+pub fn get_state() -> String {
+ MINESWEEPER.with(move |ms| ms.borrow().to_string())
}
-pub enum OpenResult {
- Mine,
- NoMine(u8)
-}
-
-impl Minesweeper {
- pub fn new(width: usize, height: usize, mine_count: usize) -> Minesweeper {
- Minesweeper {
- width,
- height,
- open_fields: HashSet::new(),
- mines: generate_mines(width, height, mine_count),
- flags: HashSet::new(),
- }
- }
-
- pub fn iter_neighbours(&self, (x, y): Position) -> impl Iterator- {
- let width = self.width;
- let height = self.height;
-
- (x.max(1) - 1 ..= (x + 1).min(width - 1)).flat_map(
- move |i| (y.max(1) - 1 ..= (y + 1).min(height - 1)).map(move |j| (i, j))
- ).filter(move |&pos| pos != (x, y))
- }
-
- pub fn count_mines(&self, position: Position) -> u8 {
- self.iter_neighbours(position)
- .filter(|pos| self.mines.contains(pos))
- .count() as u8
- }
-
- pub fn open(&mut self, position: Position) -> Option {
- if self.flags.contains(&position) {
- return None;
- }
-
- self.open_fields.insert(position);
- let is_mine = self.mines.contains(&position);
- if is_mine {
- Some(OpenResult::Mine)
- } else {
- Some(OpenResult::NoMine(0))
- }
- }
-
- pub fn toggle_flag(&mut self, position: Position) {
- if self.open_fields.contains(&position) {
- return;
- }
-
- if self.flags.contains(&position) {
- self.flags.remove(&position);
- } else {
- self.flags.insert(position);
- }
- }
-}
-
-fn generate_mines(field_width: usize, field_height: usize, mine_count: usize) -> HashSet {
- let mut mines = HashSet::new();
-
- let mut rng = rand::thread_rng();
- while mines.len() < mine_count {
- let x = rng.gen_range(0..field_width);
- let y = rng.gen_range(0..field_height);
- mines.insert((x, y));
- }
-
- mines
-}
-
-#[cfg(test)]
-mod tests {
- use crate::Minesweeper;
-
- #[test]
- fn test() {
- let mut ms = Minesweeper::new(5, 5, 5);
-
- ms.open((2, 2));
- ms.toggle_flag((3, 3));
-
- println!("{}", ms);
- }
+#[wasm_bindgen(js_name = openField)]
+pub fn open_field(x: usize, y: usize) {
+ MINESWEEPER.with(move |ms| ms.borrow_mut().open((x, y)));
}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index a30eb95..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
- println!("Hello, world!");
-}
diff --git a/src/minesweeper.rs b/src/minesweeper.rs
new file mode 100644
index 0000000..81426c4
--- /dev/null
+++ b/src/minesweeper.rs
@@ -0,0 +1,138 @@
+use std::{collections::HashSet, fmt::{Display, Write}};
+
+use rand::Rng;
+
+pub type Position = (usize, usize);
+
+#[derive(Debug)]
+pub struct Minesweeper {
+ width: usize,
+ height: usize,
+ open_fields: HashSet,
+ mines: HashSet,
+ flags: HashSet,
+ game_over: bool
+}
+
+impl Display for Minesweeper {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for y in 0..self.height {
+ for x in 0..self.height {
+ let pos = (x, y);
+ if self.game_over && self.mines.contains(&pos) {
+ f.write_str("B ")?;
+ } else if self.flags.contains(&pos) {
+ f.write_str("F ")?;
+ } else if !self.open_fields.contains(&pos) {
+ f.write_str("# ")?;
+ } else if self.mines.contains(&pos) {
+ f.write_str("B ")?;
+ } else {
+ let mines = self.count_mines(pos);
+ if mines == 0 {
+ write!(f, "☐ ")?;
+ } else {
+ write!(f, "{} ", mines)?;
+ }
+ }
+ }
+ f.write_char('\n')?;
+ }
+
+ Ok(())
+ }
+}
+
+pub enum OpenResult {
+ Mine,
+ NoMine(u8)
+}
+
+impl Minesweeper {
+ pub fn new(width: usize, height: usize, mine_count: usize) -> Minesweeper {
+ Minesweeper {
+ width,
+ height,
+ open_fields: HashSet::new(),
+ mines: generate_mines(width, height, mine_count),
+ flags: HashSet::new(),
+ game_over: false
+ }
+ }
+
+ pub fn iter_neighbours(&self, (x, y): Position) -> impl Iterator
- {
+ let width = self.width;
+ let height = self.height;
+
+ (x.max(1) - 1 ..= (x + 1).min(width - 1)).flat_map(
+ move |i| (y.max(1) - 1 ..= (y + 1).min(height - 1)).map(move |j| (i, j))
+ ).filter(move |&pos| pos != (x, y))
+ }
+
+ pub fn count_mines(&self, position: Position) -> u8 {
+ self.iter_neighbours(position)
+ .filter(|pos| self.mines.contains(pos))
+ .count() as u8
+ }
+
+ pub fn open(&mut self, position: Position) -> Option {
+ if self.game_over || self.open_fields.contains(&position) || self.flags.contains(&position) {
+ return None;
+ }
+
+ self.open_fields.insert(position);
+ let is_mine = self.mines.contains(&position);
+ if is_mine {
+ self.game_over = true;
+ Some(OpenResult::Mine)
+ } else {
+ let mines = self.count_mines(position);
+ if mines == 0 {
+ for neighbour in self.iter_neighbours(position) {
+ self.open(neighbour);
+ }
+ }
+ Some(OpenResult::NoMine(mines))
+ }
+ }
+
+ pub fn toggle_flag(&mut self, position: Position) {
+ if self.game_over || self.open_fields.contains(&position) {
+ return;
+ }
+
+ if self.flags.contains(&position) {
+ self.flags.remove(&position);
+ } else {
+ self.flags.insert(position);
+ }
+ }
+}
+
+fn generate_mines(field_width: usize, field_height: usize, mine_count: usize) -> HashSet {
+ let mut mines = HashSet::new();
+
+ let mut rng = rand::thread_rng();
+ while mines.len() < mine_count {
+ let x = rng.gen_range(0..field_width);
+ let y = rng.gen_range(0..field_height);
+ mines.insert((x, y));
+ }
+
+ mines
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test() {
+ let mut ms = Minesweeper::new(5, 5, 5);
+
+ ms.open((2, 2));
+ ms.toggle_flag((3, 3));
+
+ println!("{}", ms);
+ }
+}