Make the game
This commit is contained in:
parent
e947cb4464
commit
003757b36c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
84
Cargo.lock
generated
Normal file
84
Cargo.lock
generated
Normal file
@ -0,0 +1,84 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustsweeper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "rustsweeper"
|
||||
version = "0.1.0"
|
||||
authors = ["ElementG9 <garentyler@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.7.3"
|
397
src/lib.rs
Normal file
397
src/lib.rs
Normal file
@ -0,0 +1,397 @@
|
||||
extern crate rand;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn run() {
|
||||
println!("Welcome to Rustsweeper!");
|
||||
let mut width = 10;
|
||||
let mut height = 10;
|
||||
match prompt("\nChoose your size:\n1. 10 x 10\n2. 20 x 10\n3. 20 x 20\n4. 40 x 20\n> ", 1, 4) {
|
||||
1 => {
|
||||
width = 10;
|
||||
height = 10;
|
||||
},
|
||||
2 => {
|
||||
width = 20;
|
||||
height = 10;
|
||||
},
|
||||
3 => {
|
||||
width = 20;
|
||||
height = 20;
|
||||
},
|
||||
4 => {
|
||||
width = 40;
|
||||
height = 20;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
let mut difficulty = Difficulty::Easy;
|
||||
match prompt("\nChoose your difficulty:\n1. Easy - 10% bombs\n2. Medium - 20% bombs\n3. Hard - 40% bombs\n> ", 1, 3) {
|
||||
1 => {
|
||||
difficulty = Difficulty::Easy;
|
||||
},
|
||||
2 => {
|
||||
difficulty = Difficulty::Medium;
|
||||
},
|
||||
3 => {
|
||||
difficulty = Difficulty::Hard;
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
|
||||
print!("\n");
|
||||
let mut game = Game::new(width, height, difficulty);
|
||||
while game.running {
|
||||
println!("{}", game.get_board());
|
||||
match prompt("\nWhat would you like to do?\n1. Uncover a tile\n2. Flag a tile\n> ", 1, 2) {
|
||||
1 => {
|
||||
let pos = prompt_coords(0, game.board.width - 1, 0, game.board.height - 1);
|
||||
println!("Uncovering ({}, {})", pos.x, pos.y);
|
||||
game.uncover(pos.x as usize, pos.y as usize);
|
||||
},
|
||||
2 => {
|
||||
let pos = prompt_coords(0, game.board.width - 1, 0, game.board.height - 1);
|
||||
println!("Flagging ({}, {})", pos.x, pos.y);
|
||||
game.flag(pos.x as usize, pos.y as usize);
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Cell {
|
||||
Bomb,
|
||||
Empty,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Difficulty {
|
||||
Easy,
|
||||
Medium,
|
||||
Hard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Coord {
|
||||
pub x: isize,
|
||||
pub y: isize,
|
||||
pub valid: bool
|
||||
}
|
||||
impl Coord {
|
||||
pub fn new(x: isize, y: isize) -> Coord {
|
||||
Coord {
|
||||
x,
|
||||
y,
|
||||
valid: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Board {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub values: Vec<Vec<Cell>>,
|
||||
}
|
||||
impl Board {
|
||||
pub fn new(width: usize, height: usize) -> Board {
|
||||
let mut values = Vec::new();
|
||||
for _y in 0..height {
|
||||
let mut row = Vec::new();
|
||||
for _x in 0..width {
|
||||
row.push(Cell::Empty);
|
||||
}
|
||||
values.push(row);
|
||||
}
|
||||
let values = values;
|
||||
Board {
|
||||
width,
|
||||
height,
|
||||
values,
|
||||
}
|
||||
}
|
||||
pub fn get<'a>(&'a self, x: usize, y: usize) -> &'a Cell {
|
||||
&self.values[y][x]
|
||||
}
|
||||
pub fn set(&mut self, x: usize, y: usize, value: Cell) {
|
||||
self.values[y][x] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Game {
|
||||
pub running: bool,
|
||||
pub board: Board,
|
||||
pub difficulty: Difficulty,
|
||||
pub uncovered: Vec<Vec<bool>>,
|
||||
pub flags: Vec<(usize, usize)>
|
||||
}
|
||||
impl Game {
|
||||
pub fn new(width: usize, height: usize, difficulty: Difficulty) -> Game {
|
||||
let mut board = Board::new(width, height);
|
||||
let mut uncovered = Vec::new();
|
||||
for _y in 0..height {
|
||||
let mut row = Vec::new();
|
||||
for _x in 0..width {
|
||||
row.push(false);
|
||||
}
|
||||
uncovered.push(row);
|
||||
}
|
||||
let num_bombs = difficulty_lookup(width, height, difficulty.clone());
|
||||
for _ in 0..num_bombs {
|
||||
loop {
|
||||
let x = random(width);
|
||||
let y = random(height);
|
||||
if board.get(x, y) == &Cell::Empty {
|
||||
board.set(x, y, Cell::Bomb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Game {
|
||||
running: true,
|
||||
board,
|
||||
difficulty,
|
||||
uncovered,
|
||||
flags: Vec::new()
|
||||
}
|
||||
}
|
||||
pub fn get_valid_neighbors(&self, x: usize, y: usize) -> Vec<Coord> {
|
||||
let x = x as isize;
|
||||
let y = y as isize;
|
||||
let mut cells_to_check = vec![
|
||||
Coord::new(x - 1, y - 1),
|
||||
Coord::new(x, y - 1),
|
||||
Coord::new(x + 1, y - 1),
|
||||
Coord::new(x - 1, y ),
|
||||
Coord::new(x, y ),
|
||||
Coord::new(x + 1, y ),
|
||||
Coord::new(x - 1, y + 1),
|
||||
Coord::new(x, y + 1),
|
||||
Coord::new(x + 1, y + 1)
|
||||
];
|
||||
let mut valid_cells = Vec::new();
|
||||
for i in 0..cells_to_check.len() {
|
||||
if cells_to_check[i].x < 0 || cells_to_check[i].y < 0 {
|
||||
cells_to_check[i].valid = false;
|
||||
}
|
||||
if cells_to_check[i].x >= self.board.width as isize || cells_to_check[i].y >= self.board.height as isize {
|
||||
cells_to_check[i].valid = false;
|
||||
}
|
||||
if cells_to_check[i].valid {
|
||||
valid_cells.push(cells_to_check[i]);
|
||||
}
|
||||
}
|
||||
valid_cells
|
||||
}
|
||||
pub fn get_bomb_count(&self, x: usize, y: usize) -> usize {
|
||||
let mut bomb_count = 0;
|
||||
for cell in self.get_valid_neighbors(x, y) {
|
||||
if self.board.get(cell.x as usize, cell.y as usize) == &Cell::Bomb {
|
||||
bomb_count += 1;
|
||||
}
|
||||
}
|
||||
bomb_count
|
||||
}
|
||||
pub fn check_uncovered(&self, x: usize, y: usize) -> bool {
|
||||
self.uncovered[y][x]
|
||||
}
|
||||
pub fn uncover(&mut self, x: usize, y: usize) {
|
||||
if !self.check_uncovered(x, y) || !self.check_flagged(x, y) {
|
||||
self.uncovered[y][x] = true;
|
||||
if self.get_bomb_count(x, y) == 0 {
|
||||
for neighbor in self.get_valid_neighbors(x, y) {
|
||||
if !self.check_uncovered(neighbor.x as usize, neighbor.y as usize) {
|
||||
self.uncover(neighbor.x as usize, neighbor.y as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.board.get(x, y) == &Cell::Bomb {
|
||||
println!("Uncovered a bomb!");
|
||||
self.uncover_all();
|
||||
println!("{}", self.get_board());
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn uncover_all(&mut self) {
|
||||
let mut uncovered = Vec::new();
|
||||
for _y in 0..self.board.height {
|
||||
let mut row = Vec::new();
|
||||
for _x in 0..self.board.width {
|
||||
row.push(true);
|
||||
}
|
||||
uncovered.push(row);
|
||||
}
|
||||
self.uncovered = uncovered;
|
||||
}
|
||||
pub fn check_flagged(&self, x: usize, y: usize) -> bool {
|
||||
for flag in &self.flags {
|
||||
if *flag == (x, y) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub fn flag(&mut self, x: usize, y: usize) {
|
||||
if !self.check_flagged(x, y) {
|
||||
self.flags.push((x, y));
|
||||
} else {
|
||||
for i in 0..self.flags.len() {
|
||||
if self.flags[i] == (x, y) {
|
||||
self.flags.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_board(&self) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(" ");
|
||||
if self.board.height > 10 {
|
||||
out.push_str(" ");
|
||||
}
|
||||
for x in 0..self.board.width {
|
||||
out.push_str(&format!("{} ", x));
|
||||
if x < 10 {
|
||||
out.push_str(" ");
|
||||
}
|
||||
}
|
||||
out.push_str("\n");
|
||||
if self.board.height > 10 {
|
||||
out.push_str(" ");
|
||||
}
|
||||
out.push_str(" +");
|
||||
for _ in 0..self.board.width {
|
||||
out.push_str("---");
|
||||
}
|
||||
out.push_str("\n");
|
||||
for y in 0..self.board.height {
|
||||
let mut temp = String::new();
|
||||
temp.push_str(&format!("{}", y));
|
||||
if y < 10 && self.board.height > 10 {
|
||||
temp.push_str(" ");
|
||||
}
|
||||
temp.push_str("|");
|
||||
out.push_str(&temp);
|
||||
for x in 0..self.board.width {
|
||||
if self.check_uncovered(x, y) {
|
||||
let bomb_count = self.get_bomb_count(x, y);
|
||||
let count = &format!("{}", bomb_count);
|
||||
out.push_str(match self.board.get(x, y) {
|
||||
Cell::Empty => if bomb_count == 0 { "." } else { count },
|
||||
Cell::Bomb => "@",
|
||||
});
|
||||
} else {
|
||||
if self.check_flagged(x, y) {
|
||||
out.push_str("F");
|
||||
} else {
|
||||
out.push_str("_");
|
||||
}
|
||||
}
|
||||
out.push_str(" ");
|
||||
}
|
||||
out.push_str("\n");
|
||||
}
|
||||
out.pop(); // Remove trailing newline.
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub fn difficulty_lookup(width: usize, height: usize, difficulty: Difficulty) -> usize {
|
||||
let area = (width * height) as f32;
|
||||
let num_bombs = match difficulty {
|
||||
Difficulty::Easy => 0.1 * area,
|
||||
Difficulty::Medium => 0.2 * area,
|
||||
Difficulty::Hard => 0.4 * area,
|
||||
};
|
||||
num_bombs as usize
|
||||
}
|
||||
pub fn random(max: usize) -> usize {
|
||||
let rand_amt: f64 = rand::random();
|
||||
((max as f64) * rand_amt) as usize
|
||||
}
|
||||
pub fn read_num() -> isize {
|
||||
std::io::stdout().flush().unwrap();
|
||||
let mut input = String::new();
|
||||
match std::io::stdin().read_line(&mut input) {
|
||||
Ok(_bytes_read) => {
|
||||
input.pop(); // Remove trailing newline
|
||||
std::io::stdout().flush().unwrap();
|
||||
return input.parse::<isize>().expect("input was not isize");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error reading input: {}", err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn read() -> String {
|
||||
std::io::stdout().flush().unwrap();
|
||||
let mut input = String::new();
|
||||
match std::io::stdin().read_line(&mut input) {
|
||||
Ok(_bytes_read) => {
|
||||
input.pop(); // Remove trailing newline
|
||||
std::io::stdout().flush().unwrap();
|
||||
return input;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error reading input: {}", err);
|
||||
return String::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn prompt(options: &str, min: isize, max: isize) -> usize {
|
||||
loop {
|
||||
print!("{}", options);
|
||||
let num = read_num();
|
||||
if num >= min && num <= max {
|
||||
return num as usize;
|
||||
} else {
|
||||
println!("Please choose between {} and {}", min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn prompt_coords(xmin: usize, xmax: usize, ymin: usize, ymax: usize) -> Coord {
|
||||
loop {
|
||||
print!("Which coordinates? ");
|
||||
let s = read();
|
||||
let mut current = 0;
|
||||
while char_at(&s, current) != ' ' && char_at(&s, current) != '\0' {
|
||||
current += 1;
|
||||
}
|
||||
if current >= str_len(&s) {
|
||||
println!("X and Y must be separated by a space.");
|
||||
continue;
|
||||
}
|
||||
let x = substring(&s, 0, current).parse::<usize>().expect("input was not usize");
|
||||
let y = substring(&s, current + 1, str_len(&s)).parse::<usize>().expect("input was not usize");
|
||||
if x < xmin || x > xmax || y < ymin || y > ymax {
|
||||
println!("X and Y must be within range.");
|
||||
continue;
|
||||
}
|
||||
return Coord::new(x as isize, y as isize);
|
||||
}
|
||||
}
|
||||
fn str_len(s: &String) -> usize {
|
||||
s.chars().count()
|
||||
}
|
||||
fn char_at(s: &String, pos: usize) -> char {
|
||||
if pos >= str_len(&s) {
|
||||
'\0'
|
||||
} else {
|
||||
s.chars().skip(pos).next().unwrap()
|
||||
}
|
||||
}
|
||||
fn substring(s: &String, start: usize, mut end: usize) -> String {
|
||||
if start > end || start >= str_len(&s) {
|
||||
"".to_owned()
|
||||
} else {
|
||||
if end > str_len(&s) {
|
||||
end = str_len(&s);
|
||||
}
|
||||
let text: String = s.chars().skip(start).take(end - start).collect();
|
||||
text
|
||||
}
|
||||
}
|
3
src/main.rs
Normal file
3
src/main.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
rustsweeper::run();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user