use termion::{clear, cursor, color, style}; use termion::raw::IntoRawMode; use termion::input::TermRead; use termion::event::Key; use std::env; use std::io::{self, Read, Write}; use std::process; pub struct Randomizer { state: u64, } impl Randomizer { /// Create a new randomizer from a seed. pub fn new(seed: u64) -> Randomizer { Randomizer { state: seed.wrapping_add(0xDEADBEEFDEADBEEF), } } /// Write a buffer into the randomizer (as entropy). pub fn write(&mut self, buf: &[u8]) { for &i in buf { self.write_u8(i); } } /// Read random bytes to a buffer. pub fn read(&mut self, buf: &mut [u8]) { for i in buf { *i = self.read_u8(); } } /// Read a byte from the randomizer. pub fn read_u8(&mut self) -> u8 { self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1); (self.state.wrapping_mul(1152921504735157271).rotate_right(2) ^ 0xFAB00105C0DE) as u8 } /// Write a byte into the randomizer. /// /// This is used for collecting entropy to the randomizer. pub fn write_u8(&mut self, b: u8) { self.state ^= b as u64; self.read_u8(); } } /// A cell in the grid. #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] struct Cell { /// Does it contain a mine? mine: bool, /// Is it revealed? /// /// That is, is it showed or chosen previously by the player? revealed: bool, /// Is this cell observed? /// /// That is, is the state of this cell determined, or is it pending for randomization. observed: bool, /// Does this flag contain a flag? flagged: bool, } /// The string printed for flagged cells. const FLAGGED: &'static str = "F"; /// The string printed for mines in the game over revealing. const MINE: &'static str = "*"; /// The string printed for concealed cells. const CONCEALED: &'static str = "▒"; /// The game over screen. const GAME_OVER: &'static str = "╔═════════════════╗\n\r\ ║───┬Game over────║\n\r\ ║ r ┆ replay ║\n\r\ ║ q ┆ quit ║\n\r\ ╚═══╧═════════════╝"; /// The upper and lower boundary char. const HORZ_BOUNDARY: &'static str = "─"; /// The left and right boundary char. const VERT_BOUNDARY: &'static str = "│"; /// The top-left corner const TOP_LEFT_CORNER: &'static str = "┌"; /// The top-right corner const TOP_RIGHT_CORNER: &'static str = "┐"; /// The bottom-left corner const BOTTOM_LEFT_CORNER: &'static str = "└"; /// The bottom-right corner const BOTTOM_RIGHT_CORNER: &'static str = "┘"; /// The help page. const HELP: &'static str = r#" minesweeper ~ a simple minesweeper implementation. rules: Select a cell to reveal, printing the number of adjacent cells holding a mine. If no adjacent cells hold a mine, the cell is called free. Free cell will recursively reveal their neighboring cells. If a mine is revealed, you loose. The grid wraps. flags: -r | --height N ~ set the height of the grid. -c | --width N ~ set the width of the grid. -h | --help ~ this help page. -b ~ beginner mode. -i ~ intermediate mode. -a ~ advanced mode. -g ~ god mode. controls: ---selection-------------------- space ~ reveal the current cell. ---movement--------------------- h | a ~ move left. j | s ~ move down. k | w ~ move up. l | d ~ move right. ---flags------------------------ f ~ set flag. F ~ remove flag. ---control---------------------- q ~ quit game. r ~ restart game. author: ticki. "#; /// The game state. struct Game { /// Width of the grid. width: u16, /// The grid. /// /// The cells are enumerated like you would read a book. Left to right, until you reach the /// line ending. grid: Box<[Cell]>, /// The difficulty of the game. /// /// The lower, the easier. difficulty: u8, /// The x coordinate. x: u16, /// The y coordinate. y: u16, /// The randomizer. rand: Randomizer, /// Points. /// /// That is, revealed fields. points: u16, /// Standard output. stdout: W, /// Standard input. stdin: R, } /// Initialize the game. fn init(mut stdout: W, stdin: R, difficulty: u8, w: u16, h: u16) { write!(stdout, "{}", clear::All).unwrap(); // Set the initial game state. let mut game = Game { x: 0, y: 0, rand: Randomizer::new(0), width: w, grid: vec![Cell { mine: false, revealed: false, observed: false, flagged: false, }; w as usize * h as usize].into_boxed_slice(), points: 0, stdin: stdin.keys(), stdout: stdout, difficulty: difficulty, }; // Reset that game. game.reset(); // Start the event loop. game.start(); } impl Drop for Game { fn drop(&mut self) { // When done, restore the defaults to avoid messing with the terminal. write!(self.stdout, "{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1)).unwrap(); } } impl>, W: Write> Game { /// Get the grid position of a given coordinate. fn pos(&self, x: u16, y: u16) -> usize { y as usize * self.width as usize + x as usize } /// Read cell, randomizing it if it is unobserved. fn read_cell(&mut self, c: usize) { if !self.grid[c].observed { self.grid[c].mine = self.rand.read_u8() % self.difficulty == 0; self.grid[c].observed = true; } } /// Get the cell at (x, y). fn get(&mut self, x: u16, y: u16) -> Cell { let pos = self.pos(x, y); self.read_cell(pos); self.grid[pos] } /// Get a mutable reference to the cell at (x, y). fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell { let pos = self.pos(x, y); self.read_cell(pos); &mut self.grid[pos] } /// Start the game loop. /// /// This will listen to events and do the appropriate actions. fn start(&mut self) { let mut first_click = true; loop { // Read a single byte from stdin. let b = self.stdin.next().unwrap().unwrap(); use termion::event::Key::*; if let Char(c) = b { // Collect it as entropy. self.rand.write_u8(c as u8); } match b { Char('h') | Char('a') | Left => self.x = self.left(self.x), Char('j') | Char('s') | Down => self.y = self.down(self.y), Char('k') | Char('w') | Up => self.y = self.up(self.y), Char('l') | Char('d') | Right => self.x = self.right(self.x), Char(' ') => { // Check if it was a mine. let (x, y) = (self.x, self.y); if first_click { // This is the player's first turn; clear all cells of // mines around the cursor. for &(x, y) in self.adjacent(x, y).iter() { self.get_mut(x, y).mine = false; } self.get_mut(x, y).mine = false; first_click = false; } if self.get(x, y).mine { self.reveal_all(); // Make the background colour of the mine we just // landed on red, and the foreground black. write!(self.stdout, "{}{}{}{}{}", cursor::Goto(x + 2, y + 2), color::Bg(color::Red), color::Fg(color::Black), MINE, style::Reset).unwrap(); self.game_over(); return; } if !self.get(x, y).revealed { self.points += 1; } // Reveal the cell. self.reveal(x, y); self.print_points(); }, Char('f') => { let (x, y) = (self.x, self.y); self.toggle_flag(x, y); } Char('r') => { self.restart(); return; } Char('q') => return, _ => {}, } // Make sure the cursor is placed on the current position. write!(self.stdout, "{}", cursor::Goto(self.x + 2, self.y + 2)).unwrap(); self.stdout.flush().unwrap(); } } /// Set a flag on cell. fn set_flag(&mut self, x: u16, y: u16) { if !self.get(x, y).revealed { self.stdout.write(FLAGGED.as_bytes()).unwrap(); self.get_mut(x, y).flagged = true; } } /// Remove a flag on cell. fn remove_flag(&mut self, x: u16, y: u16) { self.stdout.write(CONCEALED.as_bytes()).unwrap(); self.get_mut(x, y).flagged = false; } /// Place a flag on cell if unflagged, or remove it if present. fn toggle_flag(&mut self, x: u16, y: u16) { if !self.get(x, y).flagged { self.set_flag(x, y); } else { self.remove_flag(x, y); } } /// Reset the game. /// /// This will display the starting grid, and fill the old grid with random mines. fn reset(&mut self) { // Reset the cursor. write!(self.stdout, "{}", cursor::Goto(1, 1)).unwrap(); // Write the upper part of the frame. self.stdout.write(TOP_LEFT_CORNER.as_bytes()).unwrap(); for _ in 0..self.width { self.stdout.write(HORZ_BOUNDARY.as_bytes()).unwrap(); } self.stdout.write(TOP_RIGHT_CORNER.as_bytes()).unwrap(); self.stdout.write(b"\n\r").unwrap(); // Conceal all the cells. for _ in 0..self.height() { // The left part of the frame self.stdout.write(VERT_BOUNDARY.as_bytes()).unwrap(); for _ in 0..self.width { self.stdout.write_all(CONCEALED.as_bytes()).unwrap(); } // The right part of the frame. self.stdout.write(VERT_BOUNDARY.as_bytes()).unwrap(); self.stdout.write(b"\n\r").unwrap(); } // Write the lower part of the frame. self.stdout.write(BOTTOM_LEFT_CORNER.as_bytes()).unwrap(); for _ in 0..self.width { self.stdout.write(HORZ_BOUNDARY.as_bytes()).unwrap(); } self.stdout.write(BOTTOM_RIGHT_CORNER.as_bytes()).unwrap(); write!(self.stdout, "{}", cursor::Goto(self.x + 2, self.y + 2)).unwrap(); self.stdout.flush().unwrap(); // Reset the grid. for i in 0..self.grid.len() { // Fill it with random, concealed fields. self.grid[i] = Cell { mine: false, revealed: false, observed: false, flagged: false, }; self.points = 0; } } /// Get the value of a cell. /// /// The value represent the sum of adjacent cells containing mines. A cell of value, 0, is /// called "free". fn val(&mut self, x: u16, y: u16) -> u8 { // To avoid nightly version, we manually sum the adjacent mines. let mut res = 0; for &(x, y) in self.adjacent(x, y).iter() { res += self.get(x, y).mine as u8; } res } /// Reveal the cell, _c_. /// /// This will recursively reveal free cells, until non-free cell is reached, terminating the /// current recursion descendant. fn reveal(&mut self, x: u16, y: u16) { let v = self.val(x, y); self.get_mut(x, y).revealed = true; write!(self.stdout, "{}", cursor::Goto(x + 2, y + 2)).unwrap(); if v == 0 { // If the cell is free, simply put a space on the position. self.stdout.write(b" ").unwrap(); // Recursively reveal adjacent cells until a non-free cel is reached. for &(x, y) in self.adjacent(x, y).iter() { if !self.get(x, y).revealed && !self.get(x, y).mine { self.reveal(x, y); } } } else { // Aww. The cell was not free. Print the value instead. self.stdout.write(&[b'0' + v]).unwrap(); } } /// Print the point count. fn print_points(&mut self) { let height = self.height(); write!(self.stdout, "{}", cursor::Goto(3, height + 2)).unwrap(); self.stdout.write(self.points.to_string().as_bytes()).unwrap(); } /// Reveal all the fields, printing where the mines were. fn reveal_all(&mut self) { write!(self.stdout, "{}", cursor::Goto(1, 1)).unwrap(); for y in 0..self.height() { for x in 0..self.width { write!(self.stdout, "{}", cursor::Goto(x + 2, y + 2)).unwrap(); if self.get(x, y).mine { self.stdout.write(MINE.as_bytes()).unwrap(); } } } } /// Game over! fn game_over(&mut self) { //Goto top left corner write!(self.stdout, "{}", cursor::Goto(1, 1)).unwrap(); self.stdout.write(GAME_OVER.as_bytes()).unwrap(); self.stdout.flush().unwrap(); loop { // Repeatedly read a single byte. match self.stdin.next().unwrap().unwrap() { Key::Char('r') => { // Replay! self.restart(); return; }, Key::Char('q') => return, _ => {}, } } } /// Restart (replay) the game. fn restart(&mut self) { self.reset(); self.start(); } /// Calculate the adjacent cells. fn adjacent(&self, x: u16, y: u16) -> [(u16, u16); 8] { let left = self.left(x); let right = self.right(x); let up = self.up(y); let down = self.down(y); [ // Left-up (left, up), // Up (x, up), // Right-up (right, up), // Left (left, y), // Right (right, y), // Left-down (left, down), // Down (x, down), // Right-down (right, down) ] } /// Calculate the height (number of rows) of the grid. fn height(&self) -> u16 { (self.grid.len() / self.width as usize) as u16 } /// Calculate the y coordinate of the cell "above" a given y coordinate. /// /// This wraps when _y = 0_. fn up(&self, y: u16) -> u16 { if y == 0 { // Upper bound reached. Wrap around. self.height() - 1 } else { y - 1 } } /// Calculate the y coordinate of the cell "below" a given y coordinate. /// /// This wraps when _y = h - 1_. fn down(&self, y: u16) -> u16 { if y + 1 == self.height() { // Lower bound reached. Wrap around. 0 } else { y + 1 } } /// Calculate the x coordinate of the cell "left to" a given x coordinate. /// /// This wraps when _x = 0_. fn left(&self, x: u16) -> u16 { if x == 0 { // Lower bound reached. Wrap around. self.width - 1 } else { x - 1 } } /// Calculate the x coordinate of the cell "left to" a given x coordinate. /// /// This wraps when _x = w - 1_. fn right(&self, x: u16) -> u16 { if x + 1 == self.width { // Upper bound reached. Wrap around. 0 } else { x + 1 } } } fn main() { let mut args = env::args().skip(1); let mut width = None; let mut height = None; let mut diff = 6; // Get and lock the stdios. let stdout = io::stdout(); let mut stdout = stdout.lock(); let stdin = io::stdin(); let stdin = stdin.lock(); let stderr = io::stderr(); let mut stderr = stderr.lock(); loop { // Read the arguments. // Does not use a for loop because each argument may have second parameter. let arg = if let Some(x) = args.next() { x } else { break; }; match arg.as_str() { "-r" | "--height" => if height.is_none() { height = Some(args.next().unwrap_or_else(|| { stderr.write(b"no height given.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); }).parse().unwrap_or_else(|_| { stderr.write(b"invalid integer given.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); })); } else { stderr.write(b"you may only input one height.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); }, "-c" | "--width" => if width.is_none() { width = Some(args.next().unwrap_or_else(|| { stderr.write(b"no width given.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); }).parse().unwrap_or_else(|_| { stderr.write(b"invalid integer given.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); })); } else { stderr.write(b"you may only input one width.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); }, "-h" | "--help" => { // Print the help page. stdout.write(HELP.as_bytes()).unwrap(); stdout.flush().unwrap(); process::exit(0); }, "-g" => diff = 2, "-a" => diff = 4, "-i" => diff = 6, "-b" => diff = 10, _ => { stderr.write(b"Unknown argument.\n").unwrap(); stderr.flush().unwrap(); process::exit(1); } } } // We go to raw mode to make the control over the terminal more fine-grained. let stdout = stdout.into_raw_mode().unwrap(); let termsize = termion::terminal_size().ok(); let termwidth = termsize.map(|(w,_)| w - 2); let termheight = termsize.map(|(_,h)| h - 2); // Initialize the game! init(stdout, stdin, diff, width.or(termwidth).unwrap_or(70), height.or(termheight).unwrap_or(40)); }