summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelizabeth.hunt@simponic.xyz <elizabeth.hunt@simponic.xyz>2024-05-16 18:03:18 -0700
committerelizabeth.hunt@simponic.xyz <elizabeth.hunt@simponic.xyz>2024-05-16 18:03:18 -0700
commita2a468f43cb337238dba2332d1532598b0b1586c (patch)
treeb690127ddf0091a72212beaa3373cc1bb02cf5ec
parent40ae10437b7729e22cc40bb5e21507b1ad6edef8 (diff)
downloadcliminesweeper-a2a468f43cb337238dba2332d1532598b0b1586c.tar.gz
climinesweeper-a2a468f43cb337238dba2332d1532598b0b1586c.zip
-rw-r--r--src/main.rs214
1 files changed, 178 insertions, 36 deletions
diff --git a/src/main.rs b/src/main.rs
index c9089fd..b8705c7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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);
}
}
-fn plant_mines(grid: &mut Grid, mines: u8) {
- let mut rng = rand::thread_rng();
+enum GridCommand {
+ REVEAL((usize, usize)),
+ FLAG((usize, usize)),
+}
- 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];
+struct GameState {
+ grid: Grid,
+ win: bool,
+ turn: u32,
+}
- if !cell.mine {
- mines_left -= 1;
- cell.mine = true;
+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));
+}
- mark_neighbors(grid, col, row);
+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 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
- ]);
-
- plant_mines(&mut grid, 10);
-
- println!("{}", grid);
+ 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);
}