stuff
This commit is contained in:
parent
40ae10437b
commit
a2a468f43c
220
src/main.rs
220
src/main.rs
@ -1,5 +1,7 @@
|
|||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::fmt;
|
use std::collections::HashSet;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::{fmt, io, process};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct Cell {
|
struct Cell {
|
||||||
@ -40,65 +42,205 @@ struct Grid(Vec<Vec<Cell>>);
|
|||||||
|
|
||||||
impl fmt::Display for Grid {
|
impl fmt::Display for Grid {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut y = 'a' as u8;
|
||||||
for row in self.0.iter() {
|
for row in self.0.iter() {
|
||||||
|
write!(f, "{:2} ", y as char)?;
|
||||||
|
y += 1;
|
||||||
|
|
||||||
for cell in row.iter() {
|
for cell in row.iter() {
|
||||||
write!(f, " {} ", cell)?;
|
write!(f, " {} ", cell)?;
|
||||||
}
|
}
|
||||||
write!(f, "\n")?;
|
write!(f, "\n")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write!(f, "{:2} ", "")?;
|
||||||
|
let mut col = 0;
|
||||||
|
for _ in self.0[0].iter() {
|
||||||
|
write!(f, "{:2} ", col)?;
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_neighbors(grid: &mut Grid, x: usize, y: usize) {
|
impl Grid {
|
||||||
for row in y.saturating_sub(1)..=y.saturating_add(1) {
|
fn plant_mines_and_set_neighbors(&mut self, mines: u8) {
|
||||||
for col in x.saturating_sub(1)..=x.saturating_add(1) {
|
let mut rng = rand::thread_rng();
|
||||||
if x == col && y == row {
|
|
||||||
|
let mut mines_left = mines;
|
||||||
|
while mines_left > 0 {
|
||||||
|
let row = rng.gen_range(0..self.0.len());
|
||||||
|
let col = rng.gen_range(0..self.0[0].len());
|
||||||
|
let cell: &mut Cell = &mut self.0[row][col];
|
||||||
|
if cell.mine {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(cell) = grid.0.get_mut(row).and_then(|row| row.get_mut(col)) else {
|
mines_left -= 1;
|
||||||
continue;
|
cell.mine = true;
|
||||||
};
|
self.on_neighbors(col, row, |cell, _| cell.neighbors += 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cell.neighbors += 1;
|
pub fn new(rows: usize, cols: usize, mines: u8) -> Option<Self> {
|
||||||
|
if rows < 1 || cols < 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut grid: Grid = Grid(vec![
|
||||||
|
vec![
|
||||||
|
Cell {
|
||||||
|
neighbors: 0,
|
||||||
|
flagged: false,
|
||||||
|
mine: false,
|
||||||
|
revealed: false,
|
||||||
|
};
|
||||||
|
rows
|
||||||
|
];
|
||||||
|
cols
|
||||||
|
]);
|
||||||
|
grid.plant_mines_and_set_neighbors(mines);
|
||||||
|
|
||||||
|
return Some(grid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GridCommand {
|
||||||
|
REVEAL((usize, usize)),
|
||||||
|
FLAG((usize, usize)),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GameState {
|
||||||
|
grid: Grid,
|
||||||
|
win: bool,
|
||||||
|
turn: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for GameState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "turn({})\n", self.turn)?;
|
||||||
|
write!(f, "{}", self.grid)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_coord(coord: &str) -> Result<(usize, usize), &'static str> {
|
||||||
|
let mut parts = coord.chars();
|
||||||
|
if coord.len() < 2 {
|
||||||
|
return Err("expected at least two characters when parsing coordinate");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut y: usize = 0;
|
||||||
|
let mut x: usize = 0;
|
||||||
|
loop {
|
||||||
|
let Some(next) = parts.next() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
if next >= 'a' && next <= 'z' {
|
||||||
|
y = ((next as u8) - 'a' as u8) as usize;
|
||||||
|
}
|
||||||
|
if next >= '0' && next <= '9' {
|
||||||
|
x = ((next as u8) - '0' as u8) as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((y, x));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_command(cmd: &str) -> Result<GridCommand, &'static str> {
|
||||||
|
let mut parts = cmd.split_whitespace();
|
||||||
|
|
||||||
|
if parts.clone().count() == 1 {
|
||||||
|
if let Some(coord_str) = parts.next() {
|
||||||
|
match parse_coord(coord_str) {
|
||||||
|
Ok(coord) => return Ok(GridCommand::REVEAL(coord)),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err("Can't get coordinate part of command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let command_part = parts.next().ok_or("Can't get command part of input")?;
|
||||||
|
let coord_str = parts.next().ok_or("Can't get coordinate part of command")?;
|
||||||
|
|
||||||
|
let coord = parse_coord(coord_str).or(Err("Invalid coordinates"))?;
|
||||||
|
|
||||||
|
match command_part.to_lowercase().as_str() {
|
||||||
|
"flag" => Ok(GridCommand::FLAG(coord)),
|
||||||
|
"reveal" => Ok(GridCommand::REVEAL(coord)),
|
||||||
|
_ => Err("Unknown command"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reveal_at(game_state: &mut GameState, coord: (usize, usize)) {
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
let mut stack = Vec::<(usize, usize)>::new();
|
||||||
|
stack.push(coord);
|
||||||
|
|
||||||
|
while let Some((y, x)) = stack.pop() {
|
||||||
|
if seen.contains(&coord) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.insert(coord);
|
||||||
|
|
||||||
|
let Some(cell) = game_state.grid.0.get_mut(y).and_then(|row| row.get_mut(x)) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if cell.neighbors > 0 || cell.mine {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.revealed = true;
|
||||||
|
|
||||||
|
for row in y.saturating_sub(1)..=y.saturating_add(1) {
|
||||||
|
for col in x.saturating_sub(1)..=x.saturating_add(1) {
|
||||||
|
if x == col && y == row {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.push((row, col));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plant_mines(grid: &mut Grid, mines: u8) {
|
fn play_game(game_state: &mut GameState) {
|
||||||
let mut rng = rand::thread_rng();
|
if game_state.win {
|
||||||
|
return;
|
||||||
let mut mines_left = mines;
|
|
||||||
while mines_left > 0 {
|
|
||||||
let row = rng.gen_range(0..grid.0.len());
|
|
||||||
let col = rng.gen_range(0..grid.0[0].len());
|
|
||||||
let cell: &mut Cell = &mut grid.0[row][col];
|
|
||||||
|
|
||||||
if !cell.mine {
|
|
||||||
mines_left -= 1;
|
|
||||||
cell.mine = true;
|
|
||||||
|
|
||||||
mark_neighbors(grid, col, row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("{}\n", game_state);
|
||||||
|
print!("> ");
|
||||||
|
let _ = io::stdout().flush();
|
||||||
|
|
||||||
|
let mut buffer = String::new();
|
||||||
|
let stdin = io::stdin();
|
||||||
|
stdin.read_line(&mut buffer);
|
||||||
|
|
||||||
|
let cmd = match parse_command(buffer.as_str()) {
|
||||||
|
Ok(command) => command,
|
||||||
|
Err(e) => {
|
||||||
|
println!("error: {}", e);
|
||||||
|
return play_game(game_state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
game_state.turn += 1;
|
||||||
|
play_game(game_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut grid = Grid(vec![
|
let Some(grid) = Grid::new(9, 9, 10) else {
|
||||||
vec![
|
eprintln!("failed to initialize grid");
|
||||||
Cell {
|
process::exit(1);
|
||||||
neighbors: 0,
|
};
|
||||||
flagged: false,
|
|
||||||
mine: false,
|
|
||||||
revealed: false,
|
|
||||||
};
|
|
||||||
9
|
|
||||||
];
|
|
||||||
9
|
|
||||||
]);
|
|
||||||
|
|
||||||
plant_mines(&mut grid, 10);
|
let mut game_state = GameState {
|
||||||
|
grid,
|
||||||
println!("{}", grid);
|
win: false,
|
||||||
|
turn: 1,
|
||||||
|
};
|
||||||
|
play_game(&mut game_state);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user