diff --git a/src/main.rs b/src/main.rs index 6ae3486..ae3faea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use std::io; const WIDTH: usize = 20; const HEIGHT: usize = 20; -const MINE_COUNT: u32 = 35; +const MINE_COUNT: usize = 50; const TEST_POS: [(i8, i8); 8] = [ (-1, 1), (0, 1), @@ -22,22 +22,52 @@ const TEST_POS: [(i8, i8); 8] = [ (0, -1), (1, -1), ]; +const MINE: &str = "\x1b[48;2;0;0;0m \x1b[0m"; +const FLAG: &str = "\x1b[48;2;255;0;0m \x1b[0m"; +const EMPTY: &str = "\x1b[48;2;255;255;255m \x1b[0m"; +const HIDDEN: &str = "\x1b[48;2;100;100;100m \x1b[0m"; fn main() { print!("\x1b[2J\x1b[H"); let mut board = Board::new(MINE_COUNT); - board.calculate(); let mut stdout = io::stdout(); + let mut rng = rand::thread_rng(); + + board.render(); + board.calculate(); + + if MINE_COUNT != WIDTH * HEIGHT { + loop { + let input = input(&mut stdout); + if input.button != MouseButton::Left + || !validate_pos(input.pos.x as i8, input.pos.y as i8) + { + continue; + } + let cell = &mut board.0[input.pos.y][input.pos.x]; + if cell.kind != CellKind::Mine { + handle_input(&input, &mut board); + break; + } + // Since mine placement is updated, recalculation is needed. + board.spawn_mine(&mut rng, 0..WIDTH * HEIGHT - MINE_COUNT); + board.0[input.pos.y][input.pos.x].kind = CellKind::MineCount(0); + board.calculate(); + handle_input(&input, &mut board); + break; + } + } + + board.calculate(); + loop { board.render(); - stdout.execute(EnableMouseCapture).unwrap(); - let input = input(); - stdout.execute(DisableMouseCapture).unwrap(); + let input = input(&mut stdout); handle_input(&input, &mut board); } } -struct Board([[Cell; 20]; 20]); +struct Board([[Cell; WIDTH]; HEIGHT]); #[derive(Clone, Copy, Debug)] struct Cell { @@ -70,29 +100,33 @@ struct Position { } impl Board { - fn new(mine_count: u32) -> Self { + fn new(mine_count: usize) -> Self { let mut rng = rand::thread_rng(); let mut board = Board( [[Cell { kind: CellKind::MineCount(0), visibility: Visibility::Hidden, - }; 20]; 20], + }; WIDTH]; HEIGHT], ); for i in 0..mine_count { - let c = rng.gen_range(0..20 * 20 - i); - let mut i = 0; - for line in board.0.iter_mut() { - for cell in line.iter_mut() { - if let CellKind::MineCount(_) = cell.kind { - if i == c { - cell.kind = CellKind::Mine; - } - i += 1; + board.spawn_mine(&mut rng, 0..WIDTH * HEIGHT - i); + } + board + } + + fn spawn_mine(&mut self, rng: &mut ThreadRng, range: std::ops::Range) { + let c = rng.gen_range(range); + let mut i = 0; + for line in self.0.iter_mut() { + for cell in line.iter_mut() { + if cell.kind != CellKind::Mine { + if i == c { + cell.kind = CellKind::Mine; } + i += 1; } } } - board } fn calculate(&mut self) { @@ -122,11 +156,7 @@ impl Board { fn inside(&self, x: i8, y: i8) -> bool { if validate_pos(x, y) { let cell = self.0[y as usize][x as usize]; - if let CellKind::MineCount(n) = cell.kind { - if cell.visibility == Visibility::Hidden && n == 0 { - return true; - } - } + return cell.visibility == Visibility::Hidden && cell.kind == CellKind::MineCount(0); } false } @@ -187,6 +217,33 @@ impl Board { } } + fn game_over(&mut self) { + self.0 + .iter_mut() + .flatten() + .for_each(|c| c.visibility = Visibility::Visible); + self.render(); + disable_raw_mode().unwrap(); + println!("Game Over!"); + exit(); + } + + fn win_condition(&mut self) { + for c in self.0.iter().flatten() { + if c.kind != CellKind::Mine && c.visibility == Visibility::Hidden { + return; + } + } + self.0 + .iter_mut() + .flatten() + .for_each(|c| c.visibility = Visibility::Visible); + self.render(); + disable_raw_mode().unwrap(); + println!("Game Won!"); + exit(); + } + fn render(&self) { let mut s = String::new(); s.push_str("\x1b[2J\x1b[H"); @@ -194,11 +251,14 @@ impl Board { for cell in line { match cell.visibility { Visibility::Visible => match cell.kind { - CellKind::Mine => s.push('*'), - CellKind::MineCount(n) => s.push((n + b'0') as char), + CellKind::Mine => s.push_str(MINE), + CellKind::MineCount(0) => s.push_str(EMPTY), + CellKind::MineCount(n) => s.push_str(&format!( + "\x1b[48;2;255;255;255m\x1b[38;2;0;0;0m{n} \x1b[0m" + )), }, - Visibility::Hidden => s.push('#'), - Visibility::Flag => s.push('F'), + Visibility::Hidden => s.push_str(HIDDEN), + Visibility::Flag => s.push_str(FLAG), } } s.push('\n'); @@ -209,14 +269,17 @@ impl Board { } } -fn input() -> Input { +fn input(stdout: &mut io::Stdout) -> Input { + stdout.execute(EnableMouseCapture).unwrap(); loop { if poll(std::time::Duration::from_secs(5)).unwrap() { while let Ok(r) = read() { if let Event::Mouse(event) = r { if let MouseEventKind::Down(button) = event.kind { if button == MouseButton::Left || button == MouseButton::Right { - let (y, x) = (HEIGHT - event.row as usize - 1, event.column as usize); + let (y, x) = + (HEIGHT - event.row as usize - 1, (event.column / 2) as usize); + stdout.execute(DisableMouseCapture).unwrap(); return Input { button, pos: Position { x, y }, @@ -234,7 +297,7 @@ fn input() -> Input { } fn handle_input(input: &Input, board: &mut Board) { - if (input.pos.x) < WIDTH && (input.pos.y) < HEIGHT { + if validate_pos(input.pos.x as i8, input.pos.y as i8) { if input.button == MouseButton::Left { board.fill(input.pos.x as i8, input.pos.y as i8); board.pad_fill(); @@ -242,6 +305,10 @@ fn handle_input(input: &Input, board: &mut Board) { if cell.visibility == Visibility::Hidden { cell.visibility = Visibility::Visible; } + if cell.kind == CellKind::Mine { + board.game_over(); + } + board.win_condition(); } else if input.button == MouseButton::Right { let cell = &mut board.0[input.pos.y][input.pos.x]; if cell.visibility == Visibility::Hidden {