initial
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
290
src/main.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user