mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-04 09:07:41 -08:00
Merge pull request #726 from AnthonyMichaelTDM/rust-port-18_bullseye
rust port of 18_blackjack
This commit is contained in:
9
18_Bullseye/rust/Cargo.toml
Normal file
9
18_Bullseye/rust/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "rust"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
||||
3
18_Bullseye/rust/README.md
Normal file
3
18_Bullseye/rust/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Rust](https://www.rust-lang.org/) by Anthony Rubick [AnthonyMichaelTDM](https://github.com/AnthonyMichaelTDM)
|
||||
223
18_Bullseye/rust/src/lib.rs
Normal file
223
18_Bullseye/rust/src/lib.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
lib.rs contains all the logic of the program
|
||||
*/
|
||||
|
||||
use std::{error::Error, fmt::Display, str::FromStr, io::{self, Write}};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
/// handles setup for the game
|
||||
pub struct Config {
|
||||
players: Vec<Player>,
|
||||
}
|
||||
impl Config {
|
||||
/// creates and returns a new Config from user input
|
||||
pub fn new() -> Result<Config, Box<dyn Error>> {
|
||||
//DATA
|
||||
let mut config: Config = Config { players: Vec::new() };
|
||||
let num_players: usize;
|
||||
|
||||
//get data from user input
|
||||
|
||||
//get num players
|
||||
//input looop
|
||||
num_players = loop {
|
||||
match get_number_from_input("HOW MANY PLAYERS? ", 1, 0) {
|
||||
Ok(num) => break num,
|
||||
Err(e) => {
|
||||
println!("{}",e);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
//get names of all players
|
||||
for id in 1..=num_players {
|
||||
let name = get_string_from_user_input( format!("NAME OF PLAYER#{}: ", id).as_str())?;
|
||||
config.players.push(Player::from(&name));
|
||||
}
|
||||
|
||||
//return new config
|
||||
return Ok(config);
|
||||
}
|
||||
}
|
||||
pub struct Player {
|
||||
name: String,
|
||||
score: usize,
|
||||
}
|
||||
impl Player {
|
||||
fn from(name: &str) -> Player {
|
||||
return Player { name: String::from(name), score: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/// run the program
|
||||
pub fn run(config: &mut Config) -> Result<(), Box<dyn Error>> {
|
||||
//DATA
|
||||
let mut round = 1;
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
//gameloop
|
||||
loop {
|
||||
//print round
|
||||
println!("");
|
||||
println!("");
|
||||
println!("ROUND {}", round);
|
||||
println!("---------");
|
||||
|
||||
//each players play
|
||||
for player in config.players.iter_mut() {
|
||||
//prompt user for players move
|
||||
//input loop
|
||||
let throw = loop {
|
||||
match get_number_from_input( format!("{}'S THROW (input 1, 2, or 3): ", player.name).as_str(), 1, 3) {
|
||||
Ok(t) => break t,
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
//get probabilities of the various outcomes based on the throw
|
||||
let p_bullseye;
|
||||
let p_30_point_zone;
|
||||
let p_20_point_zone;
|
||||
let p_10_point_zone;
|
||||
match throw {
|
||||
1 => {
|
||||
p_bullseye = 0.65;
|
||||
p_30_point_zone = 0.55;
|
||||
p_20_point_zone = 0.5;
|
||||
p_10_point_zone = 0.5;
|
||||
},
|
||||
2 => {
|
||||
p_bullseye = 0.99;
|
||||
p_30_point_zone = 0.77;
|
||||
p_20_point_zone = 0.43;
|
||||
p_10_point_zone = 0.01;
|
||||
},
|
||||
_ => {
|
||||
p_bullseye = 0.95;
|
||||
p_30_point_zone = 0.75;
|
||||
p_20_point_zone = 0.45;
|
||||
p_10_point_zone = 0.05;
|
||||
},
|
||||
}
|
||||
|
||||
//determine results
|
||||
let roll = rng.gen::<f64>();
|
||||
if roll >= p_bullseye{
|
||||
println!("BULLSEYE!! 40 POINTS!");
|
||||
player.score += 40;
|
||||
} else if roll >= p_30_point_zone {
|
||||
println!("30-POINT ZONE!");
|
||||
player.score += 30;
|
||||
} else if roll >= p_20_point_zone {
|
||||
println!("20-POINT ZONE");
|
||||
player.score += 20;
|
||||
} else if roll >= p_10_point_zone {
|
||||
println!("WHEW! 10 POINTS.");
|
||||
player.score += 10;
|
||||
} else {
|
||||
println!("MISSED THE TARGET! TOO BAD.");
|
||||
}
|
||||
|
||||
//print their score
|
||||
println!("TOTAL SCORE = {}", player.score);
|
||||
}
|
||||
|
||||
//stuff to do before next round
|
||||
if config.players.iter().any(|player| player.score >= 200) {
|
||||
//print congradulations and scores
|
||||
println!("\nWE HAVE A WINNER!!\n");
|
||||
for player in config.players.iter() {
|
||||
println!("{} SCORED {} POINTS", player.name, player.score);
|
||||
}
|
||||
//exit loop
|
||||
break;
|
||||
} else {
|
||||
//otherwise do another round
|
||||
round += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//return to main
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// gets a string from user input
|
||||
fn get_string_from_user_input(prompt: &str) -> Result<String, Box<dyn Error>> {
|
||||
//DATA
|
||||
let mut raw_input = String::new();
|
||||
|
||||
//print prompt
|
||||
print!("{}", prompt);
|
||||
//make sure it's printed before getting input
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
//read user input from standard input, and store it to raw_input, then return it or an error as needed
|
||||
raw_input.clear(); //clear input
|
||||
match io::stdin().read_line(&mut raw_input) {
|
||||
Ok(_num_bytes_read) => return Ok(String::from(raw_input.trim())),
|
||||
Err(err) => return Err(format!("ERROR: CANNOT READ INPUT!: {}", err).into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// generic function to get a number from the passed string (user input)
|
||||
/// pass a min lower than the max to have minimun and maximun bounds
|
||||
/// pass a min higher than the max to only have a minumum bound
|
||||
/// pass a min equal to the max to only have a maximun bound
|
||||
///
|
||||
/// Errors:
|
||||
/// no number on user input
|
||||
fn get_number_from_input<T:Display + PartialOrd + FromStr>(prompt: &str, min:T, max:T) -> Result<T, Box<dyn Error>> {
|
||||
//DATA
|
||||
let raw_input: String;
|
||||
let processed_input: String;
|
||||
|
||||
|
||||
//input looop
|
||||
raw_input = loop {
|
||||
match get_string_from_user_input(prompt) {
|
||||
Ok(input) => break input,
|
||||
Err(e) => {
|
||||
eprintln!("{}",e);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
//filter out num-numeric characters from user input
|
||||
processed_input = raw_input.chars().filter(|c| c.is_numeric()).collect();
|
||||
|
||||
//from input, try to read a number
|
||||
match processed_input.trim().parse() {
|
||||
Ok(i) => {
|
||||
//what bounds must the input fall into
|
||||
if min < max { //have a min and max bound: [min,max]
|
||||
if i >= min && i <= max {//is input valid, within bounds
|
||||
return Ok(i); //exit the loop with the value i, returning it
|
||||
} else { //print error message specific to this case
|
||||
return Err(format!("ONLY BETWEEN {} AND {}, PLEASE!", min, max).into());
|
||||
}
|
||||
} else if min > max { //only a min bound: [min, infinity)
|
||||
if i >= min {
|
||||
return Ok(i);
|
||||
} else {
|
||||
return Err(format!("NO LESS THAN {}, PLEASE!", min).into());
|
||||
}
|
||||
} else { //only a max bound: (-infinity, max]
|
||||
if i <= max {
|
||||
return Ok(i);
|
||||
} else {
|
||||
return Err(format!("NO MORE THAN {}, PLEASE!", max).into());
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_e) => return Err(format!("Error: couldn't find a valid number in {}",raw_input).into()),
|
||||
}
|
||||
}
|
||||
54
18_Bullseye/rust/src/main.rs
Normal file
54
18_Bullseye/rust/src/main.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
The responsibilities that remain in the main function after separating concerns
|
||||
should be limited to the following:
|
||||
- Setting up any configuration
|
||||
- Calling a run function in lib.rs
|
||||
- Handling the error if run returns an error
|
||||
*/
|
||||
|
||||
use std::process; //allows for some better error handling
|
||||
|
||||
mod lib;
|
||||
use lib::Config;
|
||||
|
||||
/// main function
|
||||
fn main() {
|
||||
//greet user
|
||||
welcome();
|
||||
|
||||
// set up other configuration
|
||||
let mut config = Config::new().unwrap_or_else(|err| {
|
||||
eprintln!("Problem configuring program: {}", err);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
// run the program
|
||||
if let Err(e) = lib::run(&mut config) {
|
||||
eprintln!("Application Error: {}", e); //use the eprintln! macro to output to standard error
|
||||
process::exit(1); //exit the program with an error code
|
||||
}
|
||||
|
||||
//end of program
|
||||
println!("THANKS FOR PLAYING!");
|
||||
}
|
||||
|
||||
/// prints the welcome/start message
|
||||
fn welcome() {
|
||||
println!("
|
||||
BULLSEYE
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
|
||||
IN THIS GAME, UP TO 20 PLAYERS THROW DARTS AT A TARGET
|
||||
WITH 10, 20, 30, AND 40 POINT ZONES. THE OBJECTIVE IS
|
||||
TO GET 200 POINTS.
|
||||
|
||||
| Throw | Description | Probable Score |
|
||||
|-------|--------------------|---------------------------|
|
||||
| 1 | Fast overarm | Bullseye or complete miss |
|
||||
| 2 | Controlled overarm | 10, 20, or 30 points |
|
||||
| 3 | Underarm | Anything |
|
||||
|
||||
");
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Separation of concerns for Binary Projects
|
||||
|
||||
The organizational problem of allocating responsibility for multiple tasks to the main function is common to many projects. As a result, the Rust community has developed a process to use as a guideline for splitting the separate concerns of a binary program when main starts getting large.
|
||||
|
||||
## The process has the following steps:
|
||||
- Split your program into a main.rs and a lib.rs and move your program’s logic to lib.rs.
|
||||
- As long as your command line logic is small, it can remain in main.rs.
|
||||
- When the command line logic starts getting complicated, extract it from main.rs and move it to lib.rs.
|
||||
|
||||
## The responsibilities that remain in the main function after this process should be limited to the following:
|
||||
- Calling the command line or input parsing logic with the argument values
|
||||
- Setting up any other configuration
|
||||
- Calling a run function in lib.rs
|
||||
- Handling the error if run returns an error
|
||||
|
||||
This pattern is about separating concerns: main.rs handles running the program, and lib.rs handles all the logic of the task at hand. Because you can’t test the main function directly, this structure lets you test all of your program’s logic by moving it into functions in lib.rs. The only code that remains in main.rs will be small enough to verify its correctness by reading it.
|
||||
Reference in New Issue
Block a user