This commit is contained in:
Elizabeth Hunt 2024-05-16 18:03:18 -07:00
parent 40ae10437b
commit a2a468f43c
Signed by: simponic
GPG Key ID: 52B3774857EB24B1

View File

@ -1,5 +1,7 @@
use rand::Rng;
use std::fmt;
use std::collections::HashSet;
use std::io::Write;
use std::{fmt, io, process};
#[derive(Clone, Copy)]
struct Cell {
@ -40,65 +42,205 @@ struct Grid(Vec<Vec<Cell>>);
impl fmt::Display for Grid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut y = 'a' as u8;
for row in self.0.iter() {
write!(f, "{:2} ", y as char)?;
y += 1;
for cell in row.iter() {
write!(f, " {} ", cell)?;
}
write!(f, "\n")?;
}
write!(f, "{:2} ", "")?;
let mut col = 0;
for _ in self.0[0].iter() {
write!(f, "{:2} ", col)?;
col += 1;
}
Ok(())
}
}
fn mark_neighbors(grid: &mut Grid, x: usize, y: usize) {
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 {
impl Grid {
fn plant_mines_and_set_neighbors(&mut self, mines: u8) {
let mut rng = rand::thread_rng();
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;
}
let Some(cell) = grid.0.get_mut(row).and_then(|row| row.get_mut(col)) else {
continue;
};
mines_left -= 1;
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) {
let mut rng = rand::thread_rng();
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);
}
fn play_game(game_state: &mut GameState) {
if game_state.win {
return;
}
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() {
let mut grid = Grid(vec![
vec![
Cell {
neighbors: 0,
flagged: false,
mine: false,
revealed: false,
};
9
];
9
]);
let Some(grid) = Grid::new(9, 9, 10) else {
eprintln!("failed to initialize grid");
process::exit(1);
};
plant_mines(&mut grid, 10);
println!("{}", grid);
let mut game_state = GameState {
grid,
win: false,
turn: 1,
};
play_game(&mut game_state);
}