summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorstale <stale@masba.net>2022-04-27 05:57:00 -0300
committerstale <stale@masba.net>2022-04-27 05:57:00 -0300
commitcdb1e3711f919f22455385cce3587b973270066c (patch)
treeed0ff2c4622b57936c63902a48af1319a1b8434a /src/main.rs
parent892bc0d4bfaeb92ef115ca652a883ea0011563f6 (diff)
mostly done
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs241
1 files changed, 179 insertions, 62 deletions
diff --git a/src/main.rs b/src/main.rs
index a38759d..5970808 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,7 +6,7 @@ mod minesweeper {
use rand::{ thread_rng, Rng, distributions::Uniform };
const HIDDEN_BIT: u8 = 1 << 7;
- const FLAGGED_BIT: u8 = 1 << 6;
+ pub const FLAGGED_BIT: u8 = 1 << 6;
const CORRECT_BIT: u8 = 1 << 5; // grading for a rightly flagged mine
const MINE_VAL: u8 = !(HIDDEN_BIT | FLAGGED_BIT | CORRECT_BIT);
const NEIGH_OFFS: &[(i8,i8)] = &[
@@ -14,39 +14,113 @@ mod minesweeper {
(-1, 0), (1, 0),
(-1, 1),(0, 1),(1, 1),
];
+ #[derive(PartialEq)]
+ pub enum Phase {
+ SafeFirstMove,
+ Run,
+ Die,
+ Win,
+ Leave,
+ }
+ pub struct Game {
+ pub phase: Phase,
+ pub board: Board,
+ pub mine_count: usize,
+ }
+ impl Display for Game {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "{}", self.board)
+ }
+ }
+
pub struct Board {
pub data: Vec<u8>,
pub width: usize,
pub height: usize,
+ pub hidden_tiles: usize,
+ }
+ pub enum MoveType {
+ Reveal,
+ ToggleFlag,
+ }
+ pub struct Move {
+ pub t: MoveType,
+ pub pos: (usize,usize),
+ }
+ pub struct MoveResult(pub Board, pub bool);
+ impl Game {
+ pub fn new(board: Board, mine_count: usize) -> Self {
+ let mut g = Game { phase: Phase::SafeFirstMove, board, mine_count };
+ g.board = g.board.spread_mines(mine_count);
+ g
+ }
+ pub fn act(mut self, m: Move) -> Self {
+ let kaboom = match m.t {
+ MoveType::Reveal => {
+ if self.phase == Phase::SafeFirstMove {
+ self.phase = Phase::Run;
+ }
+ let kaboom: bool;
+ self.board = {
+ let mr = self.board.reveal(m.pos);
+ kaboom = mr.1;
+ mr.0
+ };
+ kaboom
+ },
+ MoveType::ToggleFlag => {
+ let kaboom: bool;
+ self.board = {
+ let mr = self.board.flag(m.pos);
+ kaboom = mr.1;
+ mr.0
+ };
+ kaboom
+ },
+ };
+ if kaboom {
+ self.phase = match self.phase {
+ Phase::SafeFirstMove => unimplemented!(),
+ Phase::Run => Phase::Die,
+ _ => unreachable!(),
+ }
+ }
+ if self.board.hidden_tiles == self.mine_count {
+ self.phase = Phase::Win;
+ }
+ self
+ }
}
-
- pub struct Move(pub Board, pub bool);
-
impl Board {
- pub fn new(w: usize, h: usize, mine_count: usize) -> Board {
- let mut b = Board {
+ pub fn new(w: usize, h: usize) -> Self {
+ Board {
data: [HIDDEN_BIT].repeat(w*h),
width: w,
height: h,
- };
-
+ hidden_tiles: w*h,
+ }
+ }
+ pub fn spread_mines(mut self, count: usize) -> Self {
let mut rng = thread_rng();
- let mut c = mine_count;
+ let mut c = count;
+ let w = self.width;
+ let h = self.height;
while c > 0 {
- let x: usize = rng.sample(Uniform::new(0,w-1));
- let y: usize = rng.sample(Uniform::new(0,h-1));
- let o = b.pos_to_off(x,y);
- if b.data[o] == MINE_VAL | HIDDEN_BIT { continue }
+ let randpos: (usize, usize) = (rng.sample(Uniform::new(0,w-1)), rng.sample(Uniform::new(0,h-1)));
+ let o = self.pos_to_off(randpos);
+ if self.data[o] == MINE_VAL | HIDDEN_BIT { continue }
else {
- b.data[o] |= MINE_VAL;
+ self.data[o] |= MINE_VAL;
c -= 1;
- for (ox,oy) in NEIGH_OFFS {
- let nxr = usize::try_from(x as i8 + ox);
- let nyr = usize::try_from(y as i8 + oy);
- let _ = nxr.and_then(|nx: usize| { nyr.and_then(|ny: usize| {
+ for (nx,ny) in NEIGH_OFFS {
+ let x = randpos.0;
+ let y = randpos.1;
+ let nxc = usize::try_from(x as i8 + nx);
+ let nyc = usize::try_from(y as i8 + ny);
+ let _ = nxc.and_then(|nx: usize| { nyc.and_then(|ny: usize| {
if nx > w - 1 || ny > h - 1 { return usize::try_from(-1) };
- let off = b.pos_to_off(nx,ny);
- let c = &mut b.data[off];
+ let off = self.pos_to_off((nx,ny));
+ let c = &mut self.data[off];
if *c != HIDDEN_BIT | MINE_VAL {
*c += 1;
}
@@ -56,29 +130,29 @@ mod minesweeper {
}
}
}
- b
+ self
}
-
- pub fn pos_to_off(&self, x: usize, y: usize) -> usize {
- x + y * self.width
+ pub fn pos_to_off(&self, pos: (usize, usize)) -> usize {
+ pos.0 + pos.1 * self.width
}
- pub fn flood_reveal(&mut self, x: usize, y: usize) {
- if x > self.width || y > self.height { return; }
- let off = self.pos_to_off(x,y);
+ pub fn flood_reveal(&mut self, pos: (usize, usize)) {
+ if pos.0 > self.width || pos.1 > self.height { return; }
+ let off = self.pos_to_off(pos);
let c = &mut self.data[off];
if *c & HIDDEN_BIT > 0 {
- *c &= !HIDDEN_BIT;
+ *c &= !(HIDDEN_BIT | FLAGGED_BIT);
+ self.hidden_tiles -= 1;
if *c > 0 { return }
drop(c);
for (ox,oy) in NEIGH_OFFS {
- let nxr = usize::try_from(x as i8 + ox);
- let nyr = usize::try_from(y as i8 + oy);
+ let nxr = usize::try_from(pos.0 as i8 + ox);
+ let nyr = usize::try_from(pos.1 as i8 + oy);
let _ = nxr.and_then(|nx: usize| { nyr.and_then(|ny: usize| {
if nx > self.width - 1 || ny > self.height - 1 { return usize::try_from(-1) };
- let off = self.pos_to_off(nx,ny);
+ let off = self.pos_to_off((nx,ny));
let c = &mut self.data[off];
if *c == HIDDEN_BIT {
- self.flood_reveal(nx, ny);
+ self.flood_reveal((nx, ny));
}
Ok(0)
})
@@ -86,12 +160,25 @@ mod minesweeper {
}
}
}
- pub fn reveal(mut self, x: usize, y: usize) -> Move {
- if x > self.width - 1 || y > self.height - 1 { panic!("OOB reveal"); }
- let off = self.pos_to_off(x,y);
- self.flood_reveal(x,y);
+ pub fn reveal(mut self, pos: (usize, usize)) -> MoveResult {
+ if pos.0 > self.width - 1 || pos.1 > self.height - 1 { panic!("OOB reveal"); }
+ let off = self.pos_to_off(pos);
+ self.flood_reveal(pos);
let c = self.data[off];
- Move { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL } // Kaboom
+ MoveResult { 0: self, 1: (c & !(FLAGGED_BIT | CORRECT_BIT)) == MINE_VAL } // Kaboom
+ }
+ pub fn grade(mut self) -> Board {
+ for i in &mut self.data {
+ if *i == MINE_VAL | FLAGGED_BIT | HIDDEN_BIT {
+ *i |= CORRECT_BIT;
+ }
+ }
+ self
+ }
+ pub fn flag(mut self, pos: (usize, usize)) -> MoveResult {
+ let off = self.pos_to_off(pos);
+ self.data[off] ^= FLAGGED_BIT;
+ MoveResult { 0: self, 1: false }
}
}
@@ -99,13 +186,14 @@ mod minesweeper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for y in 0..self.height {
for x in 0..self.width {
- let c = &self.data[self.pos_to_off(x,y)];
+ let c = &self.data[self.pos_to_off((x,y))];
match *c {
0 => write!(f, " ")?,
_ if *c <= 8 => write!(f, "{}", c.to_string())?,
_ if (*c & FLAGGED_BIT) > 0 => write!(f, "F")?,
_ if (*c & HIDDEN_BIT) > 0 => write!(f, "-")?,
_ if (*c & CORRECT_BIT) > 0 => write!(f, "C")?,
+ _ if *c == MINE_VAL => write!(f, "O")?,
_ => write!(f, "?")?,
}
}
@@ -116,37 +204,66 @@ mod minesweeper {
}
}
+use minesweeper::*;
+use std::convert::{ TryFrom, TryInto };
+
fn main() -> Result<(), io::Error> {
- let mut board = minesweeper::Board::new(10,10,100/10);
+ let board = Board::new(10,10);
+ let mut game = Game::new(board, 10);
let mut cursor: (usize, usize) = (5,5);
- let mut gstate = 1;
- while gstate != 0 {
- println!("{}", board);
- let done: bool;
- let m = playerctrl(board, &mut cursor)?;
- (board, done) = (m.0, m.1);
- if done { gstate = 0 }
+ while game.phase == Phase::Run || game.phase == Phase::SafeFirstMove {
+ println!("{}",game);
+ let mm = playerctrl(
+ (cursor.0.try_into().unwrap(), cursor.1.try_into().unwrap()),
+ (game.board.width.try_into().unwrap(), game.board.height.try_into().unwrap()))?;
+ match mm {
+ MetaMove::Move(m) => game = game.act(m),
+ MetaMove::CursorMove(newc) => cursor = newc,
+ MetaMove::Quit => game.phase = Phase::Leave,
+ MetaMove::Noop => (),
+ }
}
+
+ print!("{}", game);
Ok(())
}
-fn playerctrl(mut board: minesweeper::Board, cursor: &mut (usize, usize)) -> io::Result<minesweeper::Move> {
+enum MetaMove {
+ Move(Move),
+ CursorMove((usize, usize)),
+ Noop,
+ Quit,
+}
+fn playerctrl(mut cursor: (isize, isize), bounds: (isize, isize)) -> io::Result<MetaMove> {
let stdin = io::stdin();
let lstdin = stdin.lock();
let mut input = lstdin.bytes();
- let mut kaboom = false;
- match input.next().expect("bad/no input byte")? {
- b'w' => cursor.1 -= 1,
- b's' => cursor.1 += 1,
- b'a' => cursor.0 -= 1,
- b'd' => cursor.0 += 1,
- b'r' => {
- let m = board.reveal(cursor.0, cursor.1);
- (board, kaboom) = (m.0, m.1);
- },
- b'f' => unimplemented!(),
- b'q' => kaboom = true,
- v => println!("{:?}", v),
- };
- Ok(minesweeper::Move { 0: board, 1: kaboom })
+ Ok(match input.next().expect("bad/no input byte")? {
+ b'w' => { cursor.1 -= 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) },
+ b's' => { cursor.1 += 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) },
+ b'a' => { cursor.0 -= 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) },
+ b'd' => { cursor.0 += 1; MetaMove::CursorMove(cursor_bounds(cursor, bounds)) },
+ b'r' => MetaMove::Move(Move { t: MoveType::Reveal, pos: cursor_bounds(cursor, bounds) }),
+ b'f' => MetaMove::Move(Move { t: MoveType::ToggleFlag, pos: cursor_bounds(cursor, bounds) }),
+ b'q' => MetaMove::Quit,
+ v => { println!("{:?}", v); MetaMove::Noop },
+ })
+}
+fn cursor_bounds(mut c: (isize, isize), b: (isize, isize)) -> (usize, usize) {
+ if c.0 > b.0 - 1 {
+ c.0 = 0;
+ }
+ else if c.0 < 0 {
+ c.0 = b.0 - 1;
+ }
+ if c.1 > b.1 - 1 {
+ c.1 = 0;
+ }
+ else if c.1 < 0 {
+ c.1 = b.1 - 1;
+ }
+ (
+ usize::try_from(c.0).unwrap(),
+ usize::try_from(c.1).unwrap()
+ )
}