From cdb1e3711f919f22455385cce3587b973270066c Mon Sep 17 00:00:00 2001 From: stale Date: Wed, 27 Apr 2022 05:57:00 -0300 Subject: mostly done --- src/main.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 179 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index a38759d..5970808 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod minesweeper { use rand::{ thread_rng, Rng, distributions::Uniform }; const HIDDEN_BIT: u8 = 1 << 7; - const FLAGGED_BIT: u8 = 1 << 6; + 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: &[(i8,i8)] = &[ @@ -14,39 +14,113 @@ mod minesweeper { (-1, 0), (1, 0), (-1, 1),(0, 1),(1, 1), ]; + #[derive(PartialEq)] + pub enum Phase { + SafeFirstMove, + Run, + Die, + Win, + Leave, + } + pub struct Game { + pub phase: Phase, + pub board: Board, + pub mine_count: usize, + } + impl Display for Game { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.board) + } + } + pub struct Board { pub data: Vec, pub width: usize, pub height: usize, + pub hidden_tiles: usize, + } + pub enum MoveType { + Reveal, + ToggleFlag, + } + 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 kaboom = match m.t { + MoveType::Reveal => { + if self.phase == Phase::SafeFirstMove { + self.phase = Phase::Run; + } + let kaboom: bool; + self.board = { + let mr = self.board.reveal(m.pos); + kaboom = mr.1; + mr.0 + }; + kaboom + }, + MoveType::ToggleFlag => { + let kaboom: bool; + self.board = { + let mr = self.board.flag(m.pos); + kaboom = mr.1; + mr.0 + }; + kaboom + }, + }; + if kaboom { + self.phase = match self.phase { + Phase::SafeFirstMove => unimplemented!(), + Phase::Run => Phase::Die, + _ => unreachable!(), + } + } + if self.board.hidden_tiles == self.mine_count { + self.phase = Phase::Win; + } + self + } } - - pub struct Move(pub Board, pub bool); - impl Board { - pub fn new(w: usize, h: usize, mine_count: usize) -> Board { - let mut b = 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 = mine_count; + let mut c = count; + let w = self.width; + let h = self.height; while c > 0 { - let x: usize = rng.sample(Uniform::new(0,w-1)); - let y: usize = rng.sample(Uniform::new(0,h-1)); - let o = b.pos_to_off(x,y); - if b.data[o] == MINE_VAL | HIDDEN_BIT { continue } + 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 { - b.data[o] |= MINE_VAL; + self.data[o] |= MINE_VAL; c -= 1; - for (ox,oy) in NEIGH_OFFS { - let nxr = usize::try_from(x as i8 + ox); - let nyr = usize::try_from(y as i8 + oy); - let _ = nxr.and_then(|nx: usize| { nyr.and_then(|ny: usize| { + for (nx,ny) in NEIGH_OFFS { + let x = randpos.0; + let y = randpos.1; + let nxc = usize::try_from(x as i8 + nx); + let nyc = usize::try_from(y as i8 + 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 = b.pos_to_off(nx,ny); - let c = &mut b.data[off]; + let off = self.pos_to_off((nx,ny)); + let c = &mut self.data[off]; if *c != HIDDEN_BIT | MINE_VAL { *c += 1; } @@ -56,29 +130,29 @@ mod minesweeper { } } } - b + self } - - pub fn pos_to_off(&self, x: usize, y: usize) -> usize { - x + y * self.width + pub fn pos_to_off(&self, pos: (usize, usize)) -> usize { + pos.0 + pos.1 * self.width } - pub fn flood_reveal(&mut self, x: usize, y: usize) { - if x > self.width || y > self.height { return; } - let off = self.pos_to_off(x,y); + 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; + *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(x as i8 + ox); - let nyr = usize::try_from(y as i8 + oy); + let nxr = usize::try_from(pos.0 as i8 + ox); + let nyr = usize::try_from(pos.1 as i8 + 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) }; - let off = self.pos_to_off(nx,ny); + let off = self.pos_to_off((nx,ny)); let c = &mut self.data[off]; if *c == HIDDEN_BIT { - self.flood_reveal(nx, ny); + self.flood_reveal((nx, ny)); } Ok(0) }) @@ -86,12 +160,25 @@ mod minesweeper { } } } - pub fn reveal(mut self, x: usize, y: usize) -> Move { - if x > self.width - 1 || y > self.height - 1 { panic!("OOB reveal"); } - let off = self.pos_to_off(x,y); - self.flood_reveal(x,y); + 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]; - Move { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL } // Kaboom + 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 } } } @@ -99,13 +186,14 @@ mod minesweeper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for y in 0..self.height { for x in 0..self.width { - let c = &self.data[self.pos_to_off(x,y)]; + let c = &self.data[self.pos_to_off((x,y))]; match *c { 0 => write!(f, " ")?, _ if *c <= 8 => write!(f, "{}", c.to_string())?, _ if (*c & FLAGGED_BIT) > 0 => write!(f, "F")?, _ if (*c & HIDDEN_BIT) > 0 => write!(f, "-")?, _ if (*c & CORRECT_BIT) > 0 => write!(f, "C")?, + _ if *c == MINE_VAL => write!(f, "O")?, _ => write!(f, "?")?, } } @@ -116,37 +204,66 @@ mod minesweeper { } } +use minesweeper::*; +use std::convert::{ TryFrom, TryInto }; + fn main() -> Result<(), io::Error> { - let mut board = minesweeper::Board::new(10,10,100/10); + let board = Board::new(10,10); + let mut game = Game::new(board, 10); let mut cursor: (usize, usize) = (5,5); - let mut gstate = 1; - while gstate != 0 { - println!("{}", board); - let done: bool; - let m = playerctrl(board, &mut cursor)?; - (board, done) = (m.0, m.1); - if done { gstate = 0 } + while game.phase == Phase::Run || game.phase == Phase::SafeFirstMove { + println!("{}",game); + let mm = playerctrl( + (cursor.0.try_into().unwrap(), cursor.1.try_into().unwrap()), + (game.board.width.try_into().unwrap(), game.board.height.try_into().unwrap()))?; + match mm { + MetaMove::Move(m) => game = game.act(m), + MetaMove::CursorMove(newc) => cursor = newc, + MetaMove::Quit => game.phase = Phase::Leave, + MetaMove::Noop => (), + } } + + print!("{}", game); Ok(()) } -fn playerctrl(mut board: minesweeper::Board, cursor: &mut (usize, usize)) -> io::Result { +enum MetaMove { + Move(Move), + CursorMove((usize, usize)), + Noop, + Quit, +} +fn playerctrl(mut cursor: (isize, isize), bounds: (isize, isize)) -> io::Result { let stdin = io::stdin(); let lstdin = stdin.lock(); let mut input = lstdin.bytes(); - let mut kaboom = false; - match input.next().expect("bad/no input byte")? { - b'w' => cursor.1 -= 1, - b's' => cursor.1 += 1, - b'a' => cursor.0 -= 1, - b'd' => cursor.0 += 1, - b'r' => { - let m = board.reveal(cursor.0, cursor.1); - (board, kaboom) = (m.0, m.1); - }, - b'f' => unimplemented!(), - b'q' => kaboom = true, - v => println!("{:?}", v), - }; - Ok(minesweeper::Move { 0: board, 1: kaboom }) + Ok(match input.next().expect("bad/no input byte")? { + b'w' => { cursor.1 -= 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) }, + b's' => { cursor.1 += 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) }, + b'a' => { cursor.0 -= 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) }, + b'd' => { cursor.0 += 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) }, + b'r' => MetaMove::Move(Move { t: MoveType::Reveal, pos: cursor_bounds(cursor, bounds) }), + b'f' => MetaMove::Move(Move { t: MoveType::ToggleFlag, pos: cursor_bounds(cursor, bounds) }), + b'q' => MetaMove::Quit, + v => { println!("{:?}", v); MetaMove::Noop }, + }) +} +fn cursor_bounds(mut c: (isize, isize), b: (isize, isize)) -> (usize, usize) { + if c.0 > b.0 - 1 { + c.0 = 0; + } + else if c.0 < 0 { + c.0 = b.0 - 1; + } + if c.1 > b.1 - 1 { + c.1 = 0; + } + else if c.1 < 0 { + c.1 = b.1 - 1; + } + ( + usize::try_from(c.0).unwrap(), + usize::try_from(c.1).unwrap() + ) } -- cgit v1.2.3