Advertisement
NLinker

Console minesweeper in Rust

Jul 10th, 2019
700
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Rust 19.51 KB | None | 0 0
  1. use termion::{clear, cursor, color, style};
  2. use termion::raw::IntoRawMode;
  3. use termion::input::TermRead;
  4. use termion::event::Key;
  5.  
  6. use std::env;
  7. use std::io::{self, Read, Write};
  8. use std::process;
  9.  
  10. pub struct Randomizer {
  11.     state: u64,
  12. }
  13.  
  14. impl Randomizer {
  15.     /// Create a new randomizer from a seed.
  16.     pub fn new(seed: u64) -> Randomizer {
  17.         Randomizer {
  18.             state: seed.wrapping_add(0xDEADBEEFDEADBEEF),
  19.         }
  20.     }
  21.  
  22.     /// Write a buffer into the randomizer (as entropy).
  23.     pub fn write(&mut self, buf: &[u8]) {
  24.         for &i in buf {
  25.             self.write_u8(i);
  26.         }
  27.     }
  28.  
  29.     /// Read random bytes to a buffer.
  30.     pub fn read(&mut self, buf: &mut [u8]) {
  31.         for i in buf {
  32.             *i = self.read_u8();
  33.         }
  34.     }
  35.  
  36.     /// Read a byte from the randomizer.
  37.     pub fn read_u8(&mut self) -> u8 {
  38.         self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1);
  39.         (self.state.wrapping_mul(1152921504735157271).rotate_right(2) ^ 0xFAB00105C0DE) as u8
  40.     }
  41.  
  42.     /// Write a byte into the randomizer.
  43.     ///
  44.     /// This is used for collecting entropy to the randomizer.
  45.     pub fn write_u8(&mut self, b: u8) {
  46.         self.state ^= b as u64;
  47.         self.read_u8();
  48.     }
  49. }
  50.  
  51. /// A cell in the grid.
  52. #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
  53. struct Cell {
  54.     /// Does it contain a mine?
  55.     mine: bool,
  56.     /// Is it revealed?
  57.     ///
  58.     /// That is, is it showed or chosen previously by the player?
  59.     revealed: bool,
  60.     /// Is this cell observed?
  61.     ///
  62.     /// That is, is the state of this cell determined, or is it pending for randomization.
  63.     observed: bool,
  64.     /// Does this flag contain a flag?
  65.     flagged: bool,
  66. }
  67.  
  68. /// The string printed for flagged cells.
  69. const FLAGGED: &'static str = "F";
  70. /// The string printed for mines in the game over revealing.
  71. const MINE: &'static str = "*";
  72. /// The string printed for concealed cells.
  73. const CONCEALED: &'static str = "▒";
  74.  
  75. /// The game over screen.
  76. const GAME_OVER: &'static str = "╔═════════════════╗\n\r\
  77.                                 ║───┬Game over────║\n\r\
  78.                                 ║ r ┆ replay      ║\n\r\
  79.                                 ║ q ┆ quit        ║\n\r\
  80.                                 ╚═══╧═════════════╝";
  81.  
  82. /// The upper and lower boundary char.
  83. const HORZ_BOUNDARY: &'static str = "─";
  84. /// The left and right boundary char.
  85. const VERT_BOUNDARY: &'static str = "│";
  86.  
  87. /// The top-left corner
  88. const TOP_LEFT_CORNER: &'static str = "┌";
  89. /// The top-right corner
  90. const TOP_RIGHT_CORNER: &'static str = "┐";
  91. /// The bottom-left corner
  92. const BOTTOM_LEFT_CORNER: &'static str = "└";
  93. /// The bottom-right corner
  94. const BOTTOM_RIGHT_CORNER: &'static str = "┘";
  95.  
  96. /// The help page.
  97. const HELP: &'static str = r#"
  98. minesweeper ~ a simple minesweeper implementation.
  99. rules:
  100.    Select a cell to reveal, printing the number of adjacent cells holding a mine.
  101.    If no adjacent cells hold a mine, the cell is called free. Free cell will recursively
  102.    reveal their neighboring cells. If a mine is revealed, you loose. The grid wraps.
  103. flags:
  104.    -r | --height N ~ set the height of the grid.
  105.    -c | --width N  ~ set the width of the grid.
  106.    -h | --help     ~ this help page.
  107.    -b              ~ beginner mode.
  108.    -i              ~ intermediate mode.
  109.    -a              ~ advanced mode.
  110.    -g              ~ god mode.
  111. controls:
  112.    ---selection--------------------
  113.    space ~ reveal the current cell.
  114.    ---movement---------------------
  115.    h | a ~ move left.
  116.    j | s ~ move down.
  117.    k | w ~ move up.
  118.    l | d ~ move right.
  119.    ---flags------------------------
  120.    f     ~ set flag.
  121.    F     ~ remove flag.
  122.    ---control----------------------
  123.    q     ~ quit game.
  124.    r     ~ restart game.
  125. author:
  126.    ticki.
  127. "#;
  128.  
  129. /// The game state.
  130. struct Game<R, W: Write> {
  131.    /// Width of the grid.
  132.    width: u16,
  133.    /// The grid.
  134.    ///
  135.    /// The cells are enumerated like you would read a book. Left to right, until you reach the
  136.    /// line ending.
  137.    grid: Box<[Cell]>,
  138.    /// The difficulty of the game.
  139.    ///
  140.    /// The lower, the easier.
  141.    difficulty: u8,
  142.    /// The x coordinate.
  143.    x: u16,
  144.    /// The y coordinate.
  145.    y: u16,
  146.    /// The randomizer.
  147.    rand: Randomizer,
  148.    /// Points.
  149.    ///
  150.    /// That is, revealed fields.
  151.    points: u16,
  152.    /// Standard output.
  153.    stdout: W,
  154.    /// Standard input.
  155.    stdin: R,
  156. }
  157.  
  158. /// Initialize the game.
  159. fn init<W: Write, R: Read>(mut stdout: W, stdin: R, difficulty: u8, w: u16, h: u16) {
  160.    write!(stdout, "{}", clear::All).unwrap();
  161.  
  162.    // Set the initial game state.
  163.    let mut game = Game {
  164.        x: 0,
  165.        y: 0,
  166.        rand: Randomizer::new(0),
  167.        width: w,
  168.        grid: vec![Cell {
  169.            mine: false,
  170.            revealed: false,
  171.            observed: false,
  172.            flagged: false,
  173.        }; w as usize * h as usize].into_boxed_slice(),
  174.        points: 0,
  175.        stdin: stdin.keys(),
  176.        stdout: stdout,
  177.        difficulty: difficulty,
  178.    };
  179.  
  180.    // Reset that game.
  181.    game.reset();
  182.  
  183.    // Start the event loop.
  184.    game.start();
  185. }
  186.  
  187. impl<R, W: Write> Drop for Game<R, W> {
  188.    fn drop(&mut self) {
  189.        // When done, restore the defaults to avoid messing with the terminal.
  190.        write!(self.stdout, "{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1)).unwrap();
  191.    }
  192. }
  193.  
  194. impl<R: Iterator<Item=Result<Key, std::io::Error>>, W: Write> Game<R, W> {
  195.    /// Get the grid position of a given coordinate.
  196.    fn pos(&self, x: u16, y: u16) -> usize {
  197.        y as usize * self.width as usize + x as usize
  198.    }
  199.  
  200.    /// Read cell, randomizing it if it is unobserved.
  201.    fn read_cell(&mut self, c: usize) {
  202.        if !self.grid[c].observed {
  203.            self.grid[c].mine = self.rand.read_u8() % self.difficulty == 0;
  204.            self.grid[c].observed = true;
  205.        }
  206.    }
  207.  
  208.    /// Get the cell at (x, y).
  209.    fn get(&mut self, x: u16, y: u16) -> Cell {
  210.        let pos = self.pos(x, y);
  211.  
  212.        self.read_cell(pos);
  213.        self.grid[pos]
  214.    }
  215.  
  216.    /// Get a mutable reference to the cell at (x, y).
  217.    fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
  218.        let pos = self.pos(x, y);
  219.  
  220.        self.read_cell(pos);
  221.        &mut self.grid[pos]
  222.    }
  223.  
  224.    /// Start the game loop.
  225.    ///
  226.    /// This will listen to events and do the appropriate actions.
  227.    fn start(&mut self) {
  228.        let mut first_click = true;
  229.        loop {
  230.            // Read a single byte from stdin.
  231.            let b = self.stdin.next().unwrap().unwrap();
  232.            use termion::event::Key::*;
  233.            if let Char(c) = b {
  234.                // Collect it as entropy.
  235.                self.rand.write_u8(c as u8);
  236.            }
  237.            match b {
  238.                Char('h') | Char('a') | Left  => self.x = self.left(self.x),
  239.                Char('j') | Char('s') | Down  => self.y = self.down(self.y),
  240.                Char('k') | Char('w') | Up    => self.y = self.up(self.y),
  241.                Char('l') | Char('d') | Right => self.x = self.right(self.x),
  242.                Char(' ') => {
  243.                    // Check if it was a mine.
  244.                    let (x, y) = (self.x, self.y);
  245.  
  246.                    if first_click {
  247.                        // This is the player's first turn; clear all cells of
  248.                         // mines around the cursor.
  249.                         for &(x, y) in self.adjacent(x, y).iter() {
  250.                             self.get_mut(x, y).mine = false;
  251.                         }
  252.                         self.get_mut(x, y).mine = false;
  253.                         first_click = false;
  254.                     }
  255.  
  256.                     if self.get(x, y).mine {
  257.                         self.reveal_all();
  258.                         // Make the background colour of the mine we just
  259.                         // landed on red, and the foreground black.
  260.                         write!(self.stdout, "{}{}{}{}{}",
  261.                                cursor::Goto(x + 2, y + 2),
  262.                                color::Bg(color::Red), color::Fg(color::Black),
  263.                                MINE,
  264.                                style::Reset).unwrap();
  265.                         self.game_over();
  266.                         return;
  267.                     }
  268.  
  269.                     if !self.get(x, y).revealed {
  270.                         self.points += 1;
  271.                     }
  272.  
  273.                     // Reveal the cell.
  274.                     self.reveal(x, y);
  275.  
  276.                     self.print_points();
  277.                 },
  278.                 Char('f') => {
  279.                     let (x, y) = (self.x, self.y);
  280.                     self.toggle_flag(x, y);
  281.                 }
  282.                 Char('r') => {
  283.                     self.restart();
  284.                     return;
  285.                 }
  286.                 Char('q') => return,
  287.                 _ => {},
  288.             }
  289.  
  290.             // Make sure the cursor is placed on the current position.
  291.             write!(self.stdout, "{}", cursor::Goto(self.x + 2, self.y + 2)).unwrap();
  292.             self.stdout.flush().unwrap();
  293.         }
  294.     }
  295.  
  296.     /// Set a flag on cell.
  297.     fn set_flag(&mut self, x: u16, y: u16) {
  298.         if !self.get(x, y).revealed {
  299.             self.stdout.write(FLAGGED.as_bytes()).unwrap();
  300.             self.get_mut(x, y).flagged = true;
  301.         }
  302.     }
  303.     /// Remove a flag on cell.
  304.     fn remove_flag(&mut self, x: u16, y: u16) {
  305.         self.stdout.write(CONCEALED.as_bytes()).unwrap();
  306.         self.get_mut(x, y).flagged = false;
  307.     }
  308.     /// Place a flag on cell if unflagged, or remove it if present.
  309.     fn toggle_flag(&mut self, x: u16, y: u16) {
  310.         if !self.get(x, y).flagged {
  311.             self.set_flag(x, y);
  312.         } else {
  313.             self.remove_flag(x, y);
  314.         }
  315.     }
  316.  
  317.     /// Reset the game.
  318.     ///
  319.     /// This will display the starting grid, and fill the old grid with random mines.
  320.     fn reset(&mut self) {
  321.         // Reset the cursor.
  322.         write!(self.stdout, "{}", cursor::Goto(1, 1)).unwrap();
  323.  
  324.         // Write the upper part of the frame.
  325.         self.stdout.write(TOP_LEFT_CORNER.as_bytes()).unwrap();
  326.         for _ in 0..self.width {
  327.             self.stdout.write(HORZ_BOUNDARY.as_bytes()).unwrap();
  328.         }
  329.         self.stdout.write(TOP_RIGHT_CORNER.as_bytes()).unwrap();
  330.         self.stdout.write(b"\n\r").unwrap();
  331.  
  332.         // Conceal all the cells.
  333.         for _ in 0..self.height() {
  334.             // The left part of the frame
  335.             self.stdout.write(VERT_BOUNDARY.as_bytes()).unwrap();
  336.  
  337.             for _ in 0..self.width {
  338.                 self.stdout.write_all(CONCEALED.as_bytes()).unwrap();
  339.             }
  340.  
  341.             // The right part of the frame.
  342.             self.stdout.write(VERT_BOUNDARY.as_bytes()).unwrap();
  343.             self.stdout.write(b"\n\r").unwrap();
  344.         }
  345.  
  346.         // Write the lower part of the frame.
  347.         self.stdout.write(BOTTOM_LEFT_CORNER.as_bytes()).unwrap();
  348.         for _ in 0..self.width {
  349.             self.stdout.write(HORZ_BOUNDARY.as_bytes()).unwrap();
  350.         }
  351.         self.stdout.write(BOTTOM_RIGHT_CORNER.as_bytes()).unwrap();
  352.  
  353.         write!(self.stdout, "{}", cursor::Goto(self.x + 2, self.y + 2)).unwrap();
  354.         self.stdout.flush().unwrap();
  355.  
  356.         // Reset the grid.
  357.         for i in 0..self.grid.len() {
  358.             // Fill it with random, concealed fields.
  359.             self.grid[i] = Cell {
  360.                 mine: false,
  361.                 revealed: false,
  362.                 observed: false,
  363.                 flagged: false,
  364.             };
  365.  
  366.             self.points = 0;
  367.         }
  368.     }
  369.  
  370.     /// Get the value of a cell.
  371.     ///
  372.     /// The value represent the sum of adjacent cells containing mines. A cell of value, 0, is
  373.     /// called "free".
  374.     fn val(&mut self, x: u16, y: u16) -> u8 {
  375.         // To avoid nightly version, we manually sum the adjacent mines.
  376.         let mut res = 0;
  377.         for &(x, y) in self.adjacent(x, y).iter() {
  378.             res += self.get(x, y).mine as u8;
  379.         }
  380.         res
  381.     }
  382.  
  383.     /// Reveal the cell, _c_.
  384.     ///
  385.     /// This will recursively reveal free cells, until non-free cell is reached, terminating the
  386.     /// current recursion descendant.
  387.     fn reveal(&mut self, x: u16, y: u16) {
  388.         let v = self.val(x, y);
  389.  
  390.         self.get_mut(x, y).revealed = true;
  391.  
  392.         write!(self.stdout, "{}", cursor::Goto(x + 2, y + 2)).unwrap();
  393.  
  394.         if v == 0 {
  395.             // If the cell is free, simply put a space on the position.
  396.             self.stdout.write(b" ").unwrap();
  397.  
  398.             // Recursively reveal adjacent cells until a non-free cel is reached.
  399.             for &(x, y) in self.adjacent(x, y).iter() {
  400.                 if !self.get(x, y).revealed && !self.get(x, y).mine {
  401.                     self.reveal(x, y);
  402.                 }
  403.             }
  404.         } else {
  405.             // Aww. The cell was not free. Print the value instead.
  406.             self.stdout.write(&[b'0' + v]).unwrap();
  407.         }
  408.     }
  409.  
  410.     /// Print the point count.
  411.     fn print_points(&mut self) {
  412.         let height = self.height();
  413.         write!(self.stdout, "{}", cursor::Goto(3, height + 2)).unwrap();
  414.         self.stdout.write(self.points.to_string().as_bytes()).unwrap();
  415.     }
  416.  
  417.     /// Reveal all the fields, printing where the mines were.
  418.     fn reveal_all(&mut self) {
  419.         write!(self.stdout, "{}", cursor::Goto(1, 1)).unwrap();
  420.  
  421.         for y in 0..self.height() {
  422.             for x in 0..self.width {
  423.                 write!(self.stdout, "{}", cursor::Goto(x + 2, y + 2)).unwrap();
  424.                 if self.get(x, y).mine {
  425.                     self.stdout.write(MINE.as_bytes()).unwrap();
  426.                 }
  427.             }
  428.         }
  429.     }
  430.  
  431.     /// Game over!
  432.     fn game_over(&mut self) {
  433.         //Goto top left corner
  434.         write!(self.stdout, "{}", cursor::Goto(1, 1)).unwrap();
  435.  
  436.         self.stdout.write(GAME_OVER.as_bytes()).unwrap();
  437.         self.stdout.flush().unwrap();
  438.  
  439.         loop {
  440.             // Repeatedly read a single byte.
  441.             match self.stdin.next().unwrap().unwrap() {
  442.                 Key::Char('r') => {
  443.                     // Replay!
  444.                     self.restart();
  445.                     return;
  446.                 },
  447.                 Key::Char('q') => return,
  448.                 _ => {},
  449.             }
  450.         }
  451.     }
  452.  
  453.     /// Restart (replay) the game.
  454.     fn restart(&mut self) {
  455.         self.reset();
  456.         self.start();
  457.     }
  458.  
  459.     /// Calculate the adjacent cells.
  460.     fn adjacent(&self, x: u16, y: u16) -> [(u16, u16); 8] {
  461.         let left = self.left(x);
  462.         let right = self.right(x);
  463.         let up = self.up(y);
  464.         let down = self.down(y);
  465.  
  466.         [
  467.             // Left-up
  468.             (left, up),
  469.             // Up
  470.             (x, up),
  471.             // Right-up
  472.             (right, up),
  473.             // Left
  474.             (left, y),
  475.             // Right
  476.             (right, y),
  477.             // Left-down
  478.             (left, down),
  479.             // Down
  480.             (x, down),
  481.             // Right-down
  482.             (right, down)
  483.         ]
  484.     }
  485.  
  486.     /// Calculate the height (number of rows) of the grid.
  487.     fn height(&self) -> u16 {
  488.         (self.grid.len() / self.width as usize) as u16
  489.     }
  490.  
  491.     /// Calculate the y coordinate of the cell "above" a given y coordinate.
  492.     ///
  493.     /// This wraps when _y = 0_.
  494.     fn up(&self, y: u16) -> u16 {
  495.         if y == 0 {
  496.             // Upper bound reached. Wrap around.
  497.             self.height() - 1
  498.         } else {
  499.             y - 1
  500.         }
  501.     }
  502.     /// Calculate the y coordinate of the cell "below" a given y coordinate.
  503.     ///
  504.     /// This wraps when _y = h - 1_.
  505.     fn down(&self, y: u16) -> u16 {
  506.         if y + 1 == self.height() {
  507.             // Lower bound reached. Wrap around.
  508.             0
  509.         } else {
  510.             y + 1
  511.         }
  512.     }
  513.     /// Calculate the x coordinate of the cell "left to" a given x coordinate.
  514.     ///
  515.     /// This wraps when _x = 0_.
  516.     fn left(&self, x: u16) -> u16 {
  517.         if x == 0 {
  518.             // Lower bound reached. Wrap around.
  519.             self.width - 1
  520.         } else {
  521.             x - 1
  522.         }
  523.     }
  524.     /// Calculate the x coordinate of the cell "left to" a given x coordinate.
  525.     ///
  526.     /// This wraps when _x = w - 1_.
  527.     fn right(&self, x: u16) -> u16 {
  528.         if x + 1 == self.width {
  529.             // Upper bound reached. Wrap around.
  530.             0
  531.         } else {
  532.             x + 1
  533.         }
  534.     }
  535. }
  536.  
  537. fn main() {
  538.     let mut args = env::args().skip(1);
  539.     let mut width = None;
  540.     let mut height = None;
  541.     let mut diff = 6;
  542.  
  543.     // Get and lock the stdios.
  544.     let stdout = io::stdout();
  545.     let mut stdout = stdout.lock();
  546.     let stdin = io::stdin();
  547.     let stdin = stdin.lock();
  548.     let stderr = io::stderr();
  549.     let mut stderr = stderr.lock();
  550.  
  551.     loop {
  552.         // Read the arguments.
  553.         // Does not use a for loop because each argument may have second parameter.
  554.  
  555.         let arg = if let Some(x) = args.next() {
  556.             x
  557.         } else {
  558.             break;
  559.         };
  560.  
  561.         match arg.as_str() {
  562.             "-r" | "--height" => if height.is_none() {
  563.                 height = Some(args.next().unwrap_or_else(|| {
  564.                     stderr.write(b"no height given.\n").unwrap();
  565.                     stderr.flush().unwrap();
  566.                     process::exit(1);
  567.                 }).parse().unwrap_or_else(|_| {
  568.                     stderr.write(b"invalid integer given.\n").unwrap();
  569.                     stderr.flush().unwrap();
  570.                     process::exit(1);
  571.                 }));
  572.             } else {
  573.                 stderr.write(b"you may only input one height.\n").unwrap();
  574.                 stderr.flush().unwrap();
  575.                 process::exit(1);
  576.             },
  577.             "-c" | "--width" => if width.is_none() {
  578.                 width = Some(args.next().unwrap_or_else(|| {
  579.                     stderr.write(b"no width given.\n").unwrap();
  580.                     stderr.flush().unwrap();
  581.                     process::exit(1);
  582.                 }).parse().unwrap_or_else(|_| {
  583.                     stderr.write(b"invalid integer given.\n").unwrap();
  584.                     stderr.flush().unwrap();
  585.                     process::exit(1);
  586.                 }));
  587.             } else {
  588.                 stderr.write(b"you may only input one width.\n").unwrap();
  589.                 stderr.flush().unwrap();
  590.                 process::exit(1);
  591.             },
  592.             "-h" | "--help" => {
  593.                 // Print the help page.
  594.                 stdout.write(HELP.as_bytes()).unwrap();
  595.                 stdout.flush().unwrap();
  596.                 process::exit(0);
  597.             },
  598.             "-g" => diff = 2,
  599.             "-a" => diff = 4,
  600.             "-i" => diff = 6,
  601.             "-b" => diff = 10,
  602.             _ => {
  603.                 stderr.write(b"Unknown argument.\n").unwrap();
  604.                 stderr.flush().unwrap();
  605.                 process::exit(1);
  606.             }
  607.         }
  608.     }
  609.  
  610.     // We go to raw mode to make the control over the terminal more fine-grained.
  611.     let stdout = stdout.into_raw_mode().unwrap();
  612.  
  613.     let termsize = termion::terminal_size().ok();
  614.     let termwidth = termsize.map(|(w,_)| w - 2);
  615.     let termheight = termsize.map(|(_,h)| h - 2);
  616.     // Initialize the game!
  617.     init(stdout, stdin, diff, width.or(termwidth).unwrap_or(70),
  618.          height.or(termheight).unwrap_or(40));
  619. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement