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 +    } +} + | 
