From 3d39868b8218f1f02ee97dfb6fa4109cff4dd393 Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Thu, 9 May 2024 16:26:25 +0200 Subject: [PATCH] fix bugs and useful usage info --- Cargo.toml | 7 ++ release.sh | 21 ++++++ src/main.rs | 209 ++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 182 insertions(+), 55 deletions(-) create mode 100755 release.sh diff --git a/Cargo.toml b/Cargo.toml index c557ba0..65f4751 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,13 @@ name = "term_minesweeper" version = "0.1.0" edition = "2021" +[profile.release] +strip = true +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" + [dependencies] crossterm = "0.27.0" rand = "0.8.5" diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..acb610e --- /dev/null +++ b/release.sh @@ -0,0 +1,21 @@ +#!/bin/sh +name=term_minesweeper +linux="x86_64-unknown-linux-gnu" +windows="x86_64-pc-windows-gnu" +platform=$linux +echo $platform + +if [ $platform != $windows ]; then + RUSTFLAGS="-Zlocation-detail=none" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \ + --target $platform --release +else + cargo build --target $platform --release +fi + +if [ $platform != $windows ]; then + upx --best --lzma target/$platform/release/$name + cp target/$platform/release/$name $name +else + upx --best --lzma target/$platform/release/$name.exe + cp target/$platform/release/$name.exe $name.exe +fi diff --git a/src/main.rs b/src/main.rs index ae3faea..c87e83d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,21 @@ use crossterm::{ use rand::prelude::*; use std::io; -const WIDTH: usize = 20; -const HEIGHT: usize = 20; -const MINE_COUNT: usize = 50; +const BEGINNER_DIFFICULTY: Difficulty = Difficulty { + width: 8, + height: 8, + mine_count: 10, +}; +const INTERMEDIATE_DIFFICULTY: Difficulty = Difficulty { + width: 16, + height: 16, + mine_count: 40, +}; +const EXPERT_DIFFICULTY: Difficulty = Difficulty { + width: 30, + height: 16, + mine_count: 99, +}; const TEST_POS: [(i8, i8); 8] = [ (-1, 1), (0, 1), @@ -28,46 +40,63 @@ 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); + let difficulty = match args() { + Some(v) => v, + None => { + println!("Invalid or missing argument."); + print_usage(); + exit(); + } + }; + let mut board = Board::new(&difficulty); let mut stdout = io::stdout(); let mut rng = rand::thread_rng(); + print!("\x1b[?25l"); board.render(); - board.calculate(); + board.calculate(&difficulty); - if MINE_COUNT != WIDTH * HEIGHT { + if difficulty.mine_count != difficulty.width * difficulty.height { loop { - let input = input(&mut stdout); + let input = input(&mut stdout, &difficulty); if input.button != MouseButton::Left - || !validate_pos(input.pos.x as i8, input.pos.y as i8) + || !validate_pos(input.pos.x as i8, input.pos.y as i8, &difficulty) { continue; } let cell = &mut board.0[input.pos.y][input.pos.x]; if cell.kind != CellKind::Mine { - handle_input(&input, &mut board); + handle_input(&input, &mut board, &difficulty); break; } // Since mine placement is updated, recalculation is needed. - board.spawn_mine(&mut rng, 0..WIDTH * HEIGHT - MINE_COUNT); + board.spawn_mine( + &mut rng, + 0..difficulty.width * difficulty.height - difficulty.mine_count, + ); board.0[input.pos.y][input.pos.x].kind = CellKind::MineCount(0); - board.calculate(); - handle_input(&input, &mut board); + board.calculate(&difficulty); + handle_input(&input, &mut board, &difficulty); break; } } - board.calculate(); + board.calculate(&difficulty); loop { board.render(); - let input = input(&mut stdout); - handle_input(&input, &mut board); + let input = input(&mut stdout, &difficulty); + handle_input(&input, &mut board, &difficulty); } } -struct Board([[Cell; WIDTH]; HEIGHT]); +struct Difficulty { + width: usize, + height: usize, + mine_count: usize, +} + +struct Board(Vec>); #[derive(Clone, Copy, Debug)] struct Cell { @@ -100,16 +129,20 @@ struct Position { } impl Board { - fn new(mine_count: usize) -> Self { + fn new(difficulty: &Difficulty) -> Self { let mut rng = rand::thread_rng(); - let mut board = Board( - [[Cell { - kind: CellKind::MineCount(0), - visibility: Visibility::Hidden, - }; WIDTH]; HEIGHT], - ); - for i in 0..mine_count { - board.spawn_mine(&mut rng, 0..WIDTH * HEIGHT - i); + let mut board = Board(vec![ + vec![ + Cell { + kind: CellKind::MineCount(0), + visibility: Visibility::Hidden, + }; + difficulty.width + ]; + difficulty.height + ]); + for i in 0..difficulty.mine_count { + board.spawn_mine(&mut rng, 0..difficulty.width * difficulty.height - i); } board } @@ -129,14 +162,16 @@ impl Board { } } - fn calculate(&mut self) { + fn calculate(&mut self, difficulty: &Difficulty) { 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 let Some(pos) = + test_new_pos(&Position { x, y }, (test_x, test_y), difficulty) + { if self.0[pos.y][pos.x].kind == CellKind::Mine { n += 1; } @@ -153,21 +188,23 @@ impl Board { } } - fn inside(&self, x: i8, y: i8) -> bool { - if validate_pos(x, y) { + fn inside(&self, x: i8, y: i8, difficulty: &Difficulty) -> bool { + if validate_pos(x, y, difficulty) { let cell = self.0[y as usize][x as usize]; return cell.visibility == Visibility::Hidden && cell.kind == CellKind::MineCount(0); } false } - fn pad_fill(&mut self) { - for y in 0..HEIGHT { - for x in 0..WIDTH { + fn pad_fill(&mut self, difficulty: &Difficulty) { + for y in 0..difficulty.height { + for x in 0..difficulty.width { let cell = self.0[y][x]; if cell.visibility == Visibility::Visible && 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 Some(pos) = + test_new_pos(&Position { x, y }, (test_x, test_y), difficulty) + { if let CellKind::MineCount(_) = self.0[pos.y][pos.x].kind { self.0[pos.y][pos.x].visibility = Visibility::Visible; } @@ -178,8 +215,8 @@ impl Board { } } - fn fill(&mut self, x: i8, y: i8) { - if !self.inside(x, y) { + fn fill(&mut self, x: i8, y: i8, difficulty: &Difficulty) { + if !self.inside(x, y, difficulty) { return; } let mut s = vec![]; @@ -188,8 +225,8 @@ impl Board { while let Some((mut x1, x2, y, dy)) = s.pop() { let mut x = x1; - if self.inside(x, y) { - while self.inside(x - 1, y) { + if self.inside(x, y, difficulty) { + while self.inside(x - 1, y, difficulty) { self.0[y as usize][x as usize - 1].visibility = Visibility::Visible; x -= 1; } @@ -198,7 +235,7 @@ impl Board { } } while x1 <= x2 { - while self.inside(x1, y) { + while self.inside(x1, y, difficulty) { self.0[y as usize][x1 as usize].visibility = Visibility::Visible; x1 += 1; } @@ -209,7 +246,7 @@ impl Board { s.push((x2 + 1, x1 - 1, y - dy, -dy)); } x1 += 1; - while x1 < x2 && !self.inside(x1, y) { + while x1 < x2 && !self.inside(x1, y, difficulty) { x1 += 1; } x = x1; @@ -269,7 +306,7 @@ impl Board { } } -fn input(stdout: &mut io::Stdout) -> Input { +fn input(stdout: &mut io::Stdout, difficulty: &Difficulty) -> Input { stdout.execute(EnableMouseCapture).unwrap(); loop { if poll(std::time::Duration::from_secs(5)).unwrap() { @@ -277,8 +314,17 @@ fn input(stdout: &mut io::Stdout) -> Input { 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 / 2) as usize); + if difficulty + .height + .checked_sub(event.row as usize + 1) + .is_none() + { + continue; + } + let (y, x) = ( + difficulty.height - event.row as usize - 1, + (event.column / 2) as usize, + ); stdout.execute(DisableMouseCapture).unwrap(); return Input { button, @@ -296,19 +342,19 @@ fn input(stdout: &mut io::Stdout) -> Input { } } -fn handle_input(input: &Input, board: &mut Board) { - if validate_pos(input.pos.x as i8, input.pos.y as i8) { +fn handle_input(input: &Input, board: &mut Board, difficulty: &Difficulty) { + if validate_pos(input.pos.x as i8, input.pos.y as i8, difficulty) { if input.button == MouseButton::Left { - board.fill(input.pos.x as i8, input.pos.y as i8); - board.pad_fill(); + board.fill(input.pos.x as i8, input.pos.y as i8, difficulty); + board.pad_fill(difficulty); let cell = &mut board.0[input.pos.y][input.pos.x]; if cell.visibility == Visibility::Hidden { cell.visibility = Visibility::Visible; + if cell.kind == CellKind::Mine { + board.game_over(); + } + board.win_condition(); } - 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 { @@ -320,9 +366,9 @@ fn handle_input(input: &Input, board: &mut Board) { } } -fn test_new_pos(pos: &Position, (x, y): (i8, i8)) -> Option { +fn test_new_pos(pos: &Position, (x, y): (i8, i8), difficulty: &Difficulty) -> Option { let (p_x, p_y) = (pos.x as i8, pos.y as i8); - if validate_pos(p_x + x, p_y + y) { + if validate_pos(p_x + x, p_y + y, difficulty) { Some(Position { x: (p_x + x) as usize, y: (p_y + y) as usize, @@ -332,11 +378,64 @@ fn test_new_pos(pos: &Position, (x, y): (i8, i8)) -> Option { } } -fn validate_pos(x: i8, y: i8) -> bool { - x >= 0 && x < WIDTH as i8 && y >= 0 && y < HEIGHT as i8 +fn validate_pos(x: i8, y: i8, difficulty: &Difficulty) -> bool { + x >= 0 && x < difficulty.width as i8 && y >= 0 && y < difficulty.height as i8 +} + +fn args() -> Option { + let mut args = std::env::args().skip(1); + match &args.next()?[..] { + "-d" | "--difficulty" => match &args.next()?[..] { + "1" | "b" | "beginner" => Some(BEGINNER_DIFFICULTY), + "2" | "i" | "intermediate" => Some(INTERMEDIATE_DIFFICULTY), + "3" | "e" | "expert" => Some(EXPERT_DIFFICULTY), + _ => None, + }, + "-c" | "--custom" => { + let (width, height, mine_count) = ( + args.next()?.parse().ok()?, + args.next()?.parse().ok()?, + args.next()?.parse().ok()?, + ); + Some(Difficulty { + width, + height, + mine_count, + }) + } + _ => None, + } +} + +fn print_usage() { + println!( + r#" +USAGE: + minesweeper OPTIONS VALUES + +ARGUMENTS: + -d, --difficulty set the difficulty, , accepted values: [["1", "b", "beginner"], ["2", "i", "intermediate"], ["3", "e", "expert"]] + -c, --custom set custom size and mine count, + +DIFFICULTY LEVELS: + WIDTH HEIGHT MINE_COUNT + Beginner: 8 8 10 + Intermediate: 16 16 40 + Expert: 30 16 99 + +EXAMPLES: + minesweeper -d 1 minesweeper with the beginner difficulty preset + minesweeper -c 16 30 80 minsweeper with a width of 16, a height of 30 and mine count of 80 + minesweeper -d expert minesweeper with the expert difficulty preset + minesweeper -d i minesweeper with the intermediate difficulty preset + +REFERENCES: + https://en.wikipedia.org/wiki/Minesweeper_game#Objective_and_strategy"# + ); } fn exit() -> ! { + print!("\x1b[?25h"); disable_raw_mode().unwrap(); std::process::exit(0) }