Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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<R, W: Write> {
- /// 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<W: Write, R: Read>(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<R, W: Write> Drop for Game<R, W> {
- 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<R: Iterator<Item=Result<Key, std::io::Error>>, W: Write> Game<R, W> {
- /// 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));
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement