use std::{ io, io::Read }; mod minesweeper { use std::convert::TryFrom; use std::fmt::Display; use rand::{ thread_rng, Rng, distributions::Uniform }; const HIDDEN_BIT: u8 = 1 << 7; 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)] = &[ (-1,-1),(0,-1),(1,-1), (-1, 0), (1, 0), (-1, 1),(0, 1),(1, 1), ]; pub struct Board { pub data: Vec, pub width: usize, pub height: usize, } pub struct Move(pub Board, pub bool); impl Board { pub fn new(w: usize, h: usize, mine_count: usize) -> Board { let mut b = Board { data: [HIDDEN_BIT].repeat(w*h), width: w, height: h, }; let mut rng = thread_rng(); let mut c = mine_count; 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 } else { b.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| { 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]; if *c != HIDDEN_BIT | MINE_VAL { *c += 1; } Ok(0) }) }); } } } b } pub fn pos_to_off(&self, x: usize, y: usize) -> usize { x + y * 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); let c = &mut self.data[off]; if *c & HIDDEN_BIT > 0 { *c &= !HIDDEN_BIT; 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.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 c = &mut self.data[off]; if *c == HIDDEN_BIT { self.flood_reveal(nx, ny); } Ok(0) }) }); } } } 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); let c = self.data[off]; Move { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL } // Kaboom } } impl Display for Board { 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)]; 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")?, _ => write!(f, "?")?, } } writeln!(f, "\r")?; } Ok(()) } } } fn main() -> Result<(), io::Error> { let mut board = minesweeper::Board::new(10,10,100/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 } } Ok(()) } fn playerctrl(mut board: minesweeper::Board, cursor: &mut (usize, usize)) -> 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 }) }