This commit is contained in:
2024-05-03 23:21:24 +02:00
commit e11197cdf4
3 changed files with 300 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "term_minesweeper"
version = "0.1.0"
edition = "2021"
[dependencies]
crossterm = "0.27.0"
rand = "0.8.5"

290
src/main.rs Normal file
View File

@@ -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<Position> {
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)
}