fix bugs and useful usage info
This commit is contained in:
@@ -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"
|
||||
|
||||
21
release.sh
Executable file
21
release.sh
Executable file
@@ -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
|
||||
209
src/main.rs
209
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<Vec<Cell>>);
|
||||
|
||||
#[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<Position> {
|
||||
fn test_new_pos(pos: &Position, (x, y): (i8, i8), difficulty: &Difficulty) -> Option<Position> {
|
||||
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<Position> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Difficulty> {
|
||||
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, <DIFFICULTY>, accepted values: [["1", "b", "beginner"], ["2", "i", "intermediate"], ["3", "e", "expert"]]
|
||||
-c, --custom set custom size and mine count, <WIDTH> <HEIGHT> <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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user