summaryrefslogtreecommitdiff
path: root/src/minesweeper.rs
diff options
context:
space:
mode:
authorstale <redkugelblitzin@gmail.com>2022-04-30 04:44:08 -0300
committerstale <redkugelblitzin@gmail.com>2022-04-30 04:44:08 -0300
commit9987d7abf6acd647b4a3a00c20d15f716dce653c (patch)
tree1d87a1024c67c0aa67d83b96414ade735b53cb95 /src/minesweeper.rs
parentf8deba936e62e5c9b4f7487f656c92c253427d42 (diff)
we finally got moving cursors heck yeah
Diffstat (limited to 'src/minesweeper.rs')
-rw-r--r--src/minesweeper.rs203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/minesweeper.rs b/src/minesweeper.rs
new file mode 100644
index 0000000..8c0dc77
--- /dev/null
+++ b/src/minesweeper.rs
@@ -0,0 +1,203 @@
+use std::convert::TryFrom;
+use rand::{ thread_rng, Rng, distributions::Uniform };
+const HIDDEN_BIT: u8 = 1 << 7;
+pub const FLAGGED_BIT: u8 = 1 << 6;
+const CORRECT_BIT: u8 = 1 << 5; // grading for a rightly flagged mine
+const MINE_VAL: u8 = !(HIDDEN_BIT | FLAGGED_BIT | CORRECT_BIT);
+const NEIGH_OFFS: &[(isize,isize)] = &[
+ (-1,-1),(0,-1),(1,-1),
+ (-1, 0), (1, 0),
+ (-1, 1),(0, 1),(1, 1),
+];
+#[derive(PartialEq)]
+pub enum Phase {
+ SafeFirstMove,
+ FirstMoveFail,
+ Run,
+ Die,
+ Win,
+ Leave,
+}
+pub struct Game {
+ pub phase: Phase,
+ pub board: Board,
+ pub mine_count: usize,
+}
+pub struct Board {
+ pub data: Vec<u8>,
+ pub width: usize,
+ pub height: usize,
+ pub hidden_tiles: usize,
+}
+#[derive(Debug)]
+pub enum MoveType {
+ Reveal,
+ ToggleFlag,
+}
+#[derive(Debug)]
+pub struct Move {
+ pub t: MoveType,
+ pub pos: (usize,usize),
+}
+pub struct MoveResult(pub Board, pub bool);
+impl Game {
+ pub fn new(board: Board, mine_count: usize) -> Self {
+ let mut g = Game { phase: Phase::SafeFirstMove, board, mine_count };
+ g.board = g.board.spread_mines(mine_count);
+ g
+ }
+ pub fn act(mut self, m: Move) -> Self {
+ let lost_phase = | phase | {
+ match phase {
+ Phase::SafeFirstMove => Phase::FirstMoveFail,
+ Phase::Run => Phase::Die,
+ _ => unreachable!(),
+ }
+ };
+
+ match m.t {
+ MoveType::Reveal => {
+ let kaboom: bool;
+ self.board = {
+ let mr = self.board.reveal(m.pos);
+ kaboom = mr.1;
+ mr.0
+ };
+ if kaboom { self.phase = lost_phase(self.phase) }
+ if self.phase == Phase::SafeFirstMove { self.phase = Phase::Run }
+ },
+ MoveType::ToggleFlag => self.board = self.board.flag(m.pos).0,
+ };
+
+ if self.board.hidden_tiles == self.mine_count {
+ self.phase = Phase::Win;
+ }
+ if self.phase == Phase::FirstMoveFail {
+ self.board = Board::new(self.board.width, self.board.height);
+ self.mine_count -= 1;
+ self.board = self.board.spread_mines(self.mine_count);
+ self.phase = Phase::SafeFirstMove;
+ self = self.act(m);
+ }
+ self
+ }
+}
+impl Board {
+ pub fn new(w: usize, h: usize) -> Self {
+ Board {
+ data: [HIDDEN_BIT].repeat(w*h),
+ width: w,
+ height: h,
+ hidden_tiles: w*h,
+ }
+ }
+ pub fn spread_mines(mut self, count: usize) -> Self {
+ let mut rng = thread_rng();
+ let mut c = count;
+ let w = self.width;
+ let h = self.height;
+ while c > 0 {
+ let randpos: (usize, usize) = (rng.sample(Uniform::new(0,w-1)), rng.sample(Uniform::new(0,h-1)));
+ let o = self.pos_to_off(randpos);
+ if self.data[o] == MINE_VAL | HIDDEN_BIT { continue }
+ else {
+ self.data[o] |= MINE_VAL;
+ c -= 1;
+ for (nx,ny) in NEIGH_OFFS {
+ let x = randpos.0;
+ let y = randpos.1;
+ let nxc = usize::try_from(isize::try_from(x).unwrap() + nx);
+ let nyc = usize::try_from(isize::try_from(y).unwrap() + ny);
+ let _ = nxc.and_then(|nx: usize| { nyc.and_then(|ny: usize| {
+ if nx > w - 1 || ny > h - 1 { return usize::try_from(-1) };
+ let off = self.pos_to_off((nx,ny));
+ let c = &mut self.data[off];
+ if *c != HIDDEN_BIT | MINE_VAL {
+ *c += 1;
+ }
+ Ok(0)
+ })
+ });
+ }
+ }
+ }
+ self
+ }
+ pub fn pos_to_off(&self, pos: (usize, usize)) -> usize {
+ pos.0 + pos.1 * self.width
+ }
+ pub fn flood_reveal(&mut self, pos: (usize, usize)) {
+ if pos.0 > self.width || pos.1 > self.height { return; }
+ let off = self.pos_to_off(pos);
+ let c = &mut self.data[off];
+ if *c & HIDDEN_BIT > 0 {
+ *c &= !(HIDDEN_BIT | FLAGGED_BIT);
+ self.hidden_tiles -= 1;
+ if *c > 0 { return }
+ drop(c);
+ for (ox,oy) in NEIGH_OFFS {
+ let nxr = usize::try_from(isize::try_from(pos.0).unwrap() + ox);
+ let nyr = usize::try_from(isize::try_from(pos.1).unwrap() + oy);
+ let _ = nxr.and_then(|nx: usize| { nyr.and_then(|ny: usize| {
+ if nx > self.width - 1 || ny > self.height - 1 { return usize::try_from(-1) };
+ self.flood_reveal((nx, ny));
+ Ok(0)
+ })
+ });
+ }
+ }
+ }
+ pub fn reveal(mut self, pos: (usize, usize)) -> MoveResult {
+ if pos.0 > self.width - 1 || pos.1 > self.height - 1 { panic!("OOB reveal"); }
+ let off = self.pos_to_off(pos);
+ self.flood_reveal(pos);
+ let c = self.data[off];
+ MoveResult { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL } // Kaboom
+ }
+ pub fn grade(mut self) -> Board {
+ for i in &mut self.data {
+ if *i == MINE_VAL | FLAGGED_BIT | HIDDEN_BIT {
+ *i |= CORRECT_BIT;
+ }
+ }
+ self
+ }
+ pub fn flag(mut self, pos: (usize, usize)) -> MoveResult {
+ let off = self.pos_to_off(pos);
+ self.data[off] ^= FLAGGED_BIT;
+ MoveResult { 0: self, 1: false }
+ }
+
+ pub fn render(&self, cursor: Option<(usize,usize)>) -> Vec<u8> {
+ const CYAN: &[u8] = &[27,b'[',b'3',b'6',b'm'];
+ const YELLOW: &[u8] = &[27,b'[',b'3',b'3',b'm'];
+ const GREEN: &[u8] = &[27,b'[',b'3',b'2',b'm'];
+ const RED: &[u8] = &[27,b'[',b'3',b'1',b'm'];
+ const FG: &[u8] = &[27,b'[',b'3',b'9',b'm'];
+ const ULINE: &[u8] = &[27,b'[',b'4',b'm'];
+ const REGULAR: &[u8] = &[27,b'[',b'0',b'm'];
+
+ let mut ret = vec![27];
+ let mut cur_clr = FG;
+ for y in 0..self.height {
+ ret.extend_from_slice(b"<br>");
+ for x in 0..self.width {
+ let c = &self.data[self.pos_to_off((x,y))];
+ let is_cursor = if let Some(cursor) = cursor { cursor.0 == x && cursor.1 == y } else { false };
+ if is_cursor { ret.extend_from_slice(ULINE); }
+ match *c {
+ 0 => ret.push(b' '),
+ _ if *c <= 8 => { if cur_clr != FG { ret.extend_from_slice(FG); cur_clr = FG; } ret.push(b'0' + c); },
+ _ if (*c & CORRECT_BIT) > 0 => { if cur_clr != GREEN { ret.extend_from_slice(GREEN); cur_clr = GREEN; } ret.push(b'F') },
+ _ if (*c & FLAGGED_BIT) > 0 => { if cur_clr != YELLOW { ret.extend_from_slice(YELLOW); cur_clr = YELLOW; } ret.push(b'F'); },
+ _ if (*c & HIDDEN_BIT) > 0 => { if cur_clr != CYAN { ret.extend_from_slice(CYAN); cur_clr = CYAN; } ret.push(35); },
+ _ if *c == MINE_VAL => { if cur_clr != RED { ret.extend_from_slice(RED); cur_clr = RED; } ret.push(b'O'); },
+ _ => ret.push(b'?'),
+ }
+ if is_cursor { ret.extend_from_slice(REGULAR); ret.extend_from_slice(cur_clr); }
+ }
+ }
+ ret
+ }
+}
+