initial commit
This commit is contained in:
75
Cargo.lock
generated
Normal file
75
Cargo.lock
generated
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.153"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusty-sweep"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "rusty-sweep"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8.5"
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 juls0730
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
19
README.md
Normal file
19
README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# RustySweep
|
||||||
|
A somewhat faithful to the original winmine minesweeper clone made entirely in rust designed to be run on the CLI. Might add a cursor interface later if I feel like it.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. **Clone the Repository:**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/juls0730/RustySweep.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run the Game:**
|
||||||
|
```bash
|
||||||
|
cd RustySweep
|
||||||
|
cargo run --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preview:
|
||||||
|

|
||||||
|
|
||||||
BIN
resources/9x9 example.png
Normal file
BIN
resources/9x9 example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 586 KiB |
606
src/main.rs
Normal file
606
src/main.rs
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
use rand::{self, Rng};
|
||||||
|
use std::io;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
static GLOBAL_STATE: RwLock<GameState> = RwLock::new(GameState::new());
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct GameState {
|
||||||
|
picked_tile: bool,
|
||||||
|
game_ended: bool,
|
||||||
|
game_won: bool,
|
||||||
|
game_ending_tile: Option<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameState {
|
||||||
|
const fn new() -> Self {
|
||||||
|
return Self {
|
||||||
|
picked_tile: false,
|
||||||
|
game_ended: false,
|
||||||
|
game_won: false,
|
||||||
|
game_ending_tile: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart(&mut self) {
|
||||||
|
*self = Self::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Board {
|
||||||
|
rows: usize,
|
||||||
|
cols: usize,
|
||||||
|
mines_count: usize,
|
||||||
|
tiles: Vec<Tile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board {
|
||||||
|
fn new(rows: usize, cols: usize, mines: usize) -> Self {
|
||||||
|
assert!((rows * cols) >= mines);
|
||||||
|
|
||||||
|
let mut tiles = Vec::with_capacity(rows * cols);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
tiles.set_len(tiles.capacity());
|
||||||
|
};
|
||||||
|
|
||||||
|
tiles.fill(Tile::new());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
rows,
|
||||||
|
cols,
|
||||||
|
mines_count: mines,
|
||||||
|
tiles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reveals mines and adjacent mines if the revealed mine is not surrounded by any mines
|
||||||
|
pub fn reveal(&mut self, x: usize, y: usize) {
|
||||||
|
let tile_idx = (y * self.cols) + x;
|
||||||
|
|
||||||
|
if !GLOBAL_STATE.read().unwrap().picked_tile {
|
||||||
|
if self.tiles[tile_idx].has_mine {
|
||||||
|
println!(
|
||||||
|
"mine_count: {}",
|
||||||
|
self.tiles.iter().filter(|tile| tile.has_mine).count()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Todo: move mine
|
||||||
|
let (mut new_x, mut new_y) = self.get_new_pos();
|
||||||
|
while new_x == x && new_y == y {
|
||||||
|
(new_x, new_y) = self.get_new_pos();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tile_a = self.tiles[(new_y * self.cols) + new_x];
|
||||||
|
let tile_b = self.tiles[tile_idx];
|
||||||
|
|
||||||
|
self.tiles[tile_idx] = tile_a;
|
||||||
|
self.tiles[(new_y * self.cols) + new_x] = tile_b;
|
||||||
|
|
||||||
|
return self.reveal(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLOBAL_STATE.write().unwrap().picked_tile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tile = &mut self.tiles[tile_idx];
|
||||||
|
|
||||||
|
if tile.revealed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.reveal();
|
||||||
|
|
||||||
|
if GLOBAL_STATE.read().unwrap().game_ended {
|
||||||
|
GLOBAL_STATE.write().unwrap().game_ending_tile = Some((x, y));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let neighbors_pos = self.get_neighboring_tiles(x, y);
|
||||||
|
|
||||||
|
// if any neighboring tiles have a mine, stop the recursive reveal
|
||||||
|
if neighbors_pos
|
||||||
|
.iter()
|
||||||
|
.filter(|(tile_x, tile_y)| {
|
||||||
|
let tile = &self.tiles[(tile_y * self.cols) + tile_x];
|
||||||
|
tile.has_mine
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
> 0
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tile_x, tile_y) in neighbors_pos {
|
||||||
|
self.reveal(tile_x, tile_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flag(&mut self, x: usize, y: usize) {
|
||||||
|
let tile_idx = (y * self.cols) + x;
|
||||||
|
let tile = &mut self.tiles[tile_idx];
|
||||||
|
|
||||||
|
tile.flag();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: gave save us all from these horrible names
|
||||||
|
fn get_neighboring_tiles(&self, x: usize, y: usize) -> Vec<(usize, usize)> {
|
||||||
|
let mut neighbors: Vec<(usize, usize)> = Vec::new();
|
||||||
|
// dumb solution, yes? Do I care, not really.
|
||||||
|
let signed_x = x as isize;
|
||||||
|
let signed_y = y as isize;
|
||||||
|
let tl_x = signed_x - 1;
|
||||||
|
let tl_y = signed_y - 1;
|
||||||
|
|
||||||
|
for tile_x in 0..3 {
|
||||||
|
for tile_y in 0..3 {
|
||||||
|
let check_x = tl_x + tile_x;
|
||||||
|
let check_y = tl_y + tile_y;
|
||||||
|
|
||||||
|
if (check_x < 0 || check_y < 0)
|
||||||
|
|| (check_x > self.cols as isize - 1 || check_y > self.rows as isize - 1)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_x as usize == x && check_y as usize == y {
|
||||||
|
// Don't include outselfs in the check obv
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
neighbors.push((check_x as usize, check_y as usize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self) {
|
||||||
|
println!(" ┌{}┐", "─".repeat(self.cols));
|
||||||
|
|
||||||
|
for row in 0..self.rows {
|
||||||
|
for col in 0..self.cols {
|
||||||
|
if col == 0 {
|
||||||
|
print!("{row:<02} │");
|
||||||
|
}
|
||||||
|
|
||||||
|
let tile = self.tiles[(row * self.cols) + col];
|
||||||
|
|
||||||
|
if GLOBAL_STATE.read().unwrap().game_ended && tile.has_mine {
|
||||||
|
// Highlight the game ending mine in red, and the flagged mines in green
|
||||||
|
print!(
|
||||||
|
"{}*\x1B[0m",
|
||||||
|
if tile.has_flag {
|
||||||
|
"\x1B[92m"
|
||||||
|
} else if {
|
||||||
|
let game_ending_tile =
|
||||||
|
GLOBAL_STATE.read().unwrap().game_ending_tile.unwrap();
|
||||||
|
game_ending_tile.0 == col && game_ending_tile.1 == row
|
||||||
|
} {
|
||||||
|
"\x1B[91m"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if col == self.cols - 1 {
|
||||||
|
print!("│");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if tile.revealed {
|
||||||
|
let nearby_mines = self
|
||||||
|
.get_neighboring_tiles(col, row)
|
||||||
|
.iter()
|
||||||
|
.filter(|(tile_x, tile_y)| {
|
||||||
|
let tile = &self.tiles[(tile_y * self.cols) + tile_x];
|
||||||
|
tile.has_mine
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if nearby_mines == 0 {
|
||||||
|
print!(" ");
|
||||||
|
} else {
|
||||||
|
print!(
|
||||||
|
"{}{nearby_mines}\x1B[0m",
|
||||||
|
match nearby_mines {
|
||||||
|
1 => "\x1B[32m",
|
||||||
|
2 => "\x1B[37m",
|
||||||
|
3 => "\x1B[96m",
|
||||||
|
4 => "\x1B[33m",
|
||||||
|
5 => "\x1B[34m",
|
||||||
|
6 => "\x1B[35m",
|
||||||
|
7 => "\x1B[31m",
|
||||||
|
8 => "\x1B[97m",
|
||||||
|
_ => panic!("More than 8 nearby mines!"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if tile.has_flag {
|
||||||
|
print!("^");
|
||||||
|
} else {
|
||||||
|
print!("#");
|
||||||
|
}
|
||||||
|
|
||||||
|
if col == self.cols - 1 {
|
||||||
|
print!("│");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(" └{}┘", "─".repeat(self.cols));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_mines(&mut self) {
|
||||||
|
for _ in 0..self.mines_count {
|
||||||
|
let pos = self.get_new_pos();
|
||||||
|
|
||||||
|
let idx = (pos.1 * self.cols) + pos.0;
|
||||||
|
|
||||||
|
self.tiles[idx].has_mine = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_new_pos(&self) -> (usize, usize) {
|
||||||
|
let mut y = rng(self.rows);
|
||||||
|
let mut x = rng(self.cols);
|
||||||
|
let mut idx = (y * self.cols) + x;
|
||||||
|
|
||||||
|
while self.tiles[idx].has_mine != false {
|
||||||
|
y = rng(self.rows);
|
||||||
|
x = rng(self.cols);
|
||||||
|
|
||||||
|
idx = (y * self.cols) + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct Tile {
|
||||||
|
pub revealed: bool,
|
||||||
|
pub has_mine: bool,
|
||||||
|
pub has_flag: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tile {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
revealed: false,
|
||||||
|
has_mine: false,
|
||||||
|
has_flag: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reveal(&mut self) {
|
||||||
|
if self.has_mine {
|
||||||
|
GLOBAL_STATE.write().unwrap().game_ended = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.has_flag = false;
|
||||||
|
self.revealed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flag(&mut self) {
|
||||||
|
println!("{}", self.has_flag);
|
||||||
|
self.has_flag = !self.has_flag;
|
||||||
|
println!("{}", self.has_flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> io::Result<()> {
|
||||||
|
print!("\x1B[2J\x1B[1;1H");
|
||||||
|
println!("Starting RustySweep... (bad name ik)");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
GLOBAL_STATE.write().unwrap().restart();
|
||||||
|
|
||||||
|
game_loop()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_loop() -> io::Result<()> {
|
||||||
|
let mut stdin = String::new();
|
||||||
|
|
||||||
|
println!("Please select board size");
|
||||||
|
gith
|
||||||
|
let boards = [
|
||||||
|
Board::new(9, 9, 10),
|
||||||
|
Board::new(16, 16, 40),
|
||||||
|
Board::new(16, 30, 99),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (i, board) in boards.iter().enumerate() {
|
||||||
|
println!(
|
||||||
|
"[{}]: {:<02}x{:<02} {:<02}",
|
||||||
|
i + 1,
|
||||||
|
board.rows,
|
||||||
|
board.cols,
|
||||||
|
board.mines_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("[{}]: Custom board", boards.len() + 1);
|
||||||
|
|
||||||
|
io::stdin().read_line(&mut stdin)?;
|
||||||
|
|
||||||
|
// easier if we want to make custom sized boards a thing later
|
||||||
|
let mut board = match usize::from_str(&stdin.trim()) {
|
||||||
|
Err(_) => {
|
||||||
|
println!("Selected board is invalid!");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(idx) => {
|
||||||
|
if idx == boards.len() + 1 {
|
||||||
|
// custom board
|
||||||
|
let custom_board = make_custom_board();
|
||||||
|
|
||||||
|
let board = match custom_board {
|
||||||
|
Ok(board) => board,
|
||||||
|
Err(board_err) => match board_err {
|
||||||
|
CustomBoardError::Cancel => return Ok(()),
|
||||||
|
CustomBoardError::Error => {
|
||||||
|
println!("Invalid custom board!");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
board
|
||||||
|
} else {
|
||||||
|
if idx > boards.len() {
|
||||||
|
println!("Selected board is invalid!");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
boards[idx - 1].clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
drop(stdin);
|
||||||
|
|
||||||
|
board.generate_mines();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
print!("\x1B[2J\x1B[1;1H");
|
||||||
|
|
||||||
|
if board
|
||||||
|
.tiles
|
||||||
|
.iter()
|
||||||
|
.filter(|tile| !tile.revealed && !tile.has_flag)
|
||||||
|
.count()
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
GLOBAL_STATE.write().unwrap().game_won = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
board.draw();
|
||||||
|
|
||||||
|
if GLOBAL_STATE.read().unwrap().game_won {
|
||||||
|
println!("You won!");
|
||||||
|
enter_to_continue();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if GLOBAL_STATE.read().unwrap().game_ended {
|
||||||
|
println!("Game over!");
|
||||||
|
enter_to_continue();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = get_input_num("Select an X position", None);
|
||||||
|
|
||||||
|
if x.is_cancel() || x.is_invalid() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.get_num() >= board.cols {
|
||||||
|
println!("X position is invalid!");
|
||||||
|
enter_to_continue();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = get_input_num("Select a Y position", None);
|
||||||
|
|
||||||
|
if y.is_cancel() || x.is_invalid() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if y.get_num() >= board.rows {
|
||||||
|
println!("Y position is invalid!");
|
||||||
|
enter_to_continue();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let action = get_input_num(
|
||||||
|
"What would you like to do",
|
||||||
|
Some(&["Reveal the tile", "Place or remove a flag"]),
|
||||||
|
);
|
||||||
|
|
||||||
|
if action.is_cancel() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let action: Action = ((action.get_num() as u8) - 1).into();
|
||||||
|
|
||||||
|
println!("User action: {action:?}ing {} {}", x.get_num(), y.get_num());
|
||||||
|
|
||||||
|
match action {
|
||||||
|
Action::Reveal => board.reveal(x.get_num(), y.get_num()),
|
||||||
|
Action::Flag => board.flag(x.get_num(), y.get_num()),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Tile at {} {} is now: {:?}",
|
||||||
|
x.get_num(),
|
||||||
|
y.get_num(),
|
||||||
|
board.tiles[(y.get_num() * board.cols) + x.get_num()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_to_continue() {
|
||||||
|
println!("Press Return to continue!");
|
||||||
|
let _ = io::stdin().read_line(&mut String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CustomBoardError {
|
||||||
|
Cancel,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_custom_board() -> Result<Board, CustomBoardError> {
|
||||||
|
let mut cols = get_input_num("Board width", None);
|
||||||
|
|
||||||
|
if cols.is_cancel() {
|
||||||
|
return Err(CustomBoardError::Cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cols.is_invalid() {
|
||||||
|
return Err(CustomBoardError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rows = get_input_num("Board height", None);
|
||||||
|
|
||||||
|
if rows.is_cancel() {
|
||||||
|
return Err(CustomBoardError::Cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.is_invalid() {
|
||||||
|
return Err(CustomBoardError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mines = get_input_num("Number of mines", None);
|
||||||
|
|
||||||
|
if mines.is_cancel() {
|
||||||
|
return Err(CustomBoardError::Cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mines.is_invalid() {
|
||||||
|
return Err(CustomBoardError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mines.get_num() == (cols.get_num() * rows.get_num())
|
||||||
|
|| (cols.get_num() * rows.get_num()) < mines.get_num()
|
||||||
|
{
|
||||||
|
return Err(CustomBoardError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// winmine minimums at least in the winmine from archive.org
|
||||||
|
if cols.get_num() < 8 {
|
||||||
|
cols = Input::Num(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.get_num() < 8 {
|
||||||
|
rows = Input::Num(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mines.get_num() < 10 {
|
||||||
|
mines = Input::Num(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Board::new(rows.get_num(), cols.get_num(), mines.get_num()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum Action {
|
||||||
|
Reveal,
|
||||||
|
Flag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Action {
|
||||||
|
fn from(other: u8) -> Self {
|
||||||
|
match other {
|
||||||
|
0 => Self::Reveal,
|
||||||
|
1 => Self::Flag,
|
||||||
|
_ => panic!("Invalid Action {other}!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Input {
|
||||||
|
Num(usize),
|
||||||
|
Cancel,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
fn is_cancel(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Input::Cancel => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_num(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Input::Num(num) => return num.clone(),
|
||||||
|
_ => panic!("tried to unwrap a non-Num value!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_invalid(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Input::Invalid => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_input_num(message: &str, options: Option<&[&str]>) -> Input {
|
||||||
|
println!("{message} (c to cancel):");
|
||||||
|
|
||||||
|
if options.is_some() {
|
||||||
|
for (i, option) in options.unwrap().iter().enumerate() {
|
||||||
|
println!("[{}]: {option}", i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stdin = String::new();
|
||||||
|
|
||||||
|
io::stdin().read_line(&mut stdin).unwrap();
|
||||||
|
if stdin.as_bytes()[0] == b'c' {
|
||||||
|
return Input::Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = match usize::from_str(&stdin.trim()) {
|
||||||
|
Err(_) => {
|
||||||
|
return Input::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(idx) => {
|
||||||
|
if options.is_some() {
|
||||||
|
if idx > options.unwrap().len() {
|
||||||
|
return Input::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Input::Num(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rng(max: usize) -> usize {
|
||||||
|
rand::thread_rng().gen_range(0..max)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user