diff options
Diffstat (limited to 'src/minesweeper.rs')
-rw-r--r-- | src/minesweeper.rs | 203 |
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 + } +} + |