use rand::Rng; use std::collections::HashSet; use std::io::Write; use std::{fmt, io, process}; #[derive(Clone, Copy)] struct Cell { neighbors: u8, revealed: bool, flagged: bool, mine: bool, } impl fmt::Display for Cell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.flagged { write!(f, "F")?; } if !self.revealed { write!(f, "+")?; return Ok(()); } if self.mine { write!(f, "M")?; return Ok(()); } if self.neighbors > 0 { write!(f, "{}", self.neighbors)?; } else { write!(f, "_")?; } Ok(()) } } #[derive(Clone)] struct Grid(Vec>); 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(()) } } 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; } mines_left -= 1; cell.mine = true; self.on_neighbors(col, row, |cell, _| cell.neighbors += 1); } } pub fn new(rows: usize, cols: usize, mines: u8) -> Option { 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 { 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 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 Some(grid) = Grid::new(9, 9, 10) else { eprintln!("failed to initialize grid"); process::exit(1); }; let mut game_state = GameState { grid, win: false, turn: 1, }; play_game(&mut game_state); }