From e11197cdf4002cca75adc4e9c3fec29e1ed421ab Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Fri, 3 May 2024 23:21:24 +0200 Subject: [PATCH] initial --- .gitignore | 2 + Cargo.toml | 8 ++ src/main.rs | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c557ba0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "term_minesweeper" +version = "0.1.0" +edition = "2021" + +[dependencies] +crossterm = "0.27.0" +rand = "0.8.5" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0e1b055 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,290 @@ +use crossterm::{ + event::{ + poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, + MouseEventKind, + }, + terminal::{disable_raw_mode, enable_raw_mode, size}, + ExecutableCommand, +}; +use rand::prelude::*; +use std::io; + +const WIDTH: usize = 20; +const HEIGHT: usize = 20; +const MINE_COUNT: u32 = 35; +const TEST_POS: [(i8, i8); 8] = [ + (-1, 1), + (0, 1), + (1, 1), + (-1, 0), + (1, 0), + (-1, -1), + (0, -1), + (1, -1), +]; + +fn main() { + print!("\x1b[2J\x1b[H"); + let mut board = Board::new(MINE_COUNT); + board.calculate(); + let size = size().unwrap(); + let mut stdout = io::stdout(); + loop { + board.render(); + stdout.execute(EnableMouseCapture).unwrap(); + let input = input(); + stdout.execute(DisableMouseCapture).unwrap(); + handle_input(&input, &mut board); + } +} + +struct Board([[Cell; 20]; 20]); + +#[derive(Clone, Copy, Debug)] +struct Cell { + kind: CellKind, + visibility: Visibility, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum CellKind { + Mine, + MineCount(u8), +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Visibility { + Visible, + Hidden, + Flag, +} + +struct Input { + button: MouseButton, + pos: Position, +} + +#[derive(Debug)] +struct Position { + x: usize, + y: usize, +} + +impl Board { + fn new(mine_count: u32) -> Self { + let mut rng = rand::thread_rng(); + let mut board = Board( + [[Cell { + kind: CellKind::MineCount(0), + visibility: Visibility::Hidden, + }; 20]; 20], + ); + 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 + } + + fn calculate(&mut self) { + let mut changes = vec![]; + for (y, line) in self.0.iter().enumerate() { + for (x, cell) in line.iter().enumerate() { + if let CellKind::MineCount(_) = cell.kind { + let mut n = 0; + for (test_x, test_y) in TEST_POS { + if let Some(pos) = test_new_pos(&Position { x, y }, (test_x, test_y)) { + if self.0[pos.y][pos.x].kind == CellKind::Mine { + n += 1; + } + } + } + let (x, y) = (x as usize, y as usize); + changes.push((Position { x, y }, n)); + } + } + } + for (pos, n) in changes { + if let CellKind::MineCount(c) = &mut self.0[pos.y as usize][pos.x as usize].kind { + *c = n; + } + } + } + + 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 { + if n == 0 { + return true; + } + } + } + } + false + } + + fn pad_fill(&mut self) { + for y in 0..HEIGHT { + for x in 0..WIDTH { + let cell = self.0[y][x]; + if cell.visibility == Visibility::Visible { + if cell.kind == CellKind::MineCount(0) { + for (test_x, test_y) in TEST_POS { + if let Some(pos) = test_new_pos(&Position { x, y }, (test_x, test_y)) { + if let CellKind::MineCount(_) = self.0[pos.y][pos.x].kind { + self.0[pos.y][pos.x].visibility = Visibility::Visible; + } + } + } + } + } + } + } + } + + fn fill(&mut self, x: i8, y: i8) { + if !self.inside(x, y) { + return; + } + let mut s = vec![]; + s.push((x, x, y, 1)); + s.push((x, x, y - 1, -1)); + + while !s.is_empty() { + let (mut x1, x2, y, dy) = s.pop().unwrap(); + let mut x = x1; + if self.inside(x, y) { + while self.inside(x - 1, y) { + self.0[y as usize][x as usize - 1].visibility = Visibility::Visible; + x -= 1; + } + if x < x1 { + s.push((x, x1 - 1, y - dy, -dy)); + } + } + while x1 <= x2 { + while self.inside(x1, y) { + self.0[y as usize][x1 as usize].visibility = Visibility::Visible; + x1 += 1; + } + if x1 > x { + s.push((x, x1 - 1, y + dy, dy)); + } + if x1 - 1 > x2 { + s.push((x2 + 1, x1 - 1, y - dy, -dy)); + } + x1 += 1; + while x1 < x2 && !self.inside(x1, y) { + x1 += 1; + } + x = x1; + } + } + } + + fn render(&self) { + let mut s = String::new(); + s.push_str("\x1b[2J\x1b[H"); + for line in self.0.iter().rev() { + for cell in line { + match cell.visibility { + Visibility::Visible => match cell.kind { + CellKind::Mine => s.push('*'), + CellKind::MineCount(n) => s.push((n + '0' as u8) as char), + }, + Visibility::Hidden => s.push('#'), + Visibility::Flag => s.push('F'), + } + } + s.push('\n'); + } + disable_raw_mode().unwrap(); + print!("{s}"); + enable_raw_mode().unwrap(); + } +} + +fn input() -> Input { + 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 as usize - event.row as usize - 1, + event.column as usize, + ); + return Input { + button, + pos: Position { x, y }, + }; + } + } + } else if let Event::Key(k) = r { + if k.code == KeyCode::Char('q') { + exit(); + } + } + } + } + } +} + +fn handle_input(input: &Input, board: &mut Board) { + if (input.pos.x as usize) < WIDTH && (input.pos.y as usize) < HEIGHT { + if input.button == MouseButton::Left { + board.fill(input.pos.x as i8, input.pos.y as i8); + board.pad_fill(); + let cell = &mut board.0[input.pos.y as usize][input.pos.x as usize]; + if cell.visibility == Visibility::Hidden { + cell.visibility = Visibility::Visible; + } + if let CellKind::MineCount(n) = cell.kind { + if n != 0 { + return; + } + } + } else if input.button == MouseButton::Right { + let cell = &mut board.0[input.pos.y as usize][input.pos.x as usize]; + if cell.visibility == Visibility::Hidden { + cell.visibility = Visibility::Flag; + } else if cell.visibility == Visibility::Flag { + cell.visibility = Visibility::Hidden; + } + } + } +} + +fn test_new_pos(pos: &Position, (x, y): (i8, i8)) -> Option { + let (p_x, p_y) = (pos.x as i8, pos.y as i8); + if validate_pos(p_x + x, p_y + y) { + Some(Position { + x: (p_x + x) as usize, + y: (p_y + y) as usize, + }) + } else { + None + } +} + +fn validate_pos(x: i8, y: i8) -> bool { + x >= 0 && x < WIDTH as i8 && y >= 0 && y < HEIGHT as i8 +} + +fn exit() -> ! { + disable_raw_mode().unwrap(); + std::process::exit(0) +}