diff --git a/50_Horserace/rust/Cargo.toml b/50_Horserace/rust/Cargo.toml new file mode 100644 index 00000000..3b1d02f5 --- /dev/null +++ b/50_Horserace/rust/Cargo.toml @@ -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" diff --git a/50_Horserace/rust/src/game.rs b/50_Horserace/rust/src/game.rs new file mode 100644 index 00000000..079e397e --- /dev/null +++ b/50_Horserace/rust/src/game.rs @@ -0,0 +1,57 @@ +use std::{thread, time::Duration}; + +use crate::{horses::Horses, players::Players}; + +pub struct Game { + horses: Horses, + players: Players, +} + +impl Game { + pub fn new() -> Self { + Game { + horses: Horses::new(), + players: Players::new(), + } + } + + pub fn play(&mut self) -> bool { + self.horses.randomize_odds(); + self.horses.print_table(); + + self.players.make_bets(); + + println!("\n1 2 3 4 5 6 7 8"); + + for _ in 1..=7 { + self.horses.advance(); + self.draw(); + thread::sleep(Duration::from_secs(1)); + } + + let winner = self.horses.print_placements(); + self.players.process_winner(winner); + + return self.players.prompt_next_round(); + } + + pub fn draw(&self) { + println!("============="); + println!("XXXXSTARTXXXX"); + for row in 1..=28 { + let neighbors = self.horses.get_at(row); + + match neighbors.len() { + 0 => println!(), + 1 => println!("{}", neighbors[0].no), + _ => { + for h in neighbors { + print!("{} ", h.no); + } + println!(); + } + } + } + println!("XXXXFINISHXXXX"); + } +} diff --git a/50_Horserace/rust/src/horses.rs b/50_Horserace/rust/src/horses.rs new file mode 100644 index 00000000..825042aa --- /dev/null +++ b/50_Horserace/rust/src/horses.rs @@ -0,0 +1,114 @@ +use rand::Rng; + +pub struct Horse { + pub name: String, + pub no: u8, + pub odd: f32, + pub position: u8, +} + +impl Horse { + fn new(name: &str, no: u8) -> Self { + Horse { + name: name.to_string(), + no, + odd: 0., + position: 0, + } + } +} + +pub struct Horses { + horses: [Horse; 8], +} + +impl Horses { + pub fn new() -> Self { + Horses { + horses: [ + Horse::new("JOE MAW", 1), + Horse::new("L.B.J.", 2), + Horse::new("MR.WASHBURN", 3), + Horse::new("MISS KAREN", 4), + Horse::new("JOLLY", 5), + Horse::new("HORSE", 6), + Horse::new("JELLY DO NOT", 7), + Horse::new("MIDNIGHT", 8), + ], + } + } + + pub fn randomize_odds(&mut self) { + let mut odds = Vec::new(); + + for _ in 1..=8 { + odds.push(rand::thread_rng().gen_range(1.0..=10.)); + } + + let total: f32 = odds.iter().sum(); + + for (i, o) in odds.iter().enumerate() { + let o = total / o; + self.horses[i].odd = o; + } + } + + pub fn advance(&mut self) { + for h in self.horses.iter_mut() { + let distance = rand::thread_rng().gen_range(1..=100); + let scale = h.odd.ceil() as i32; + + let dt = if distance < 10 { + 1 + } else if distance < scale + 17 { + 2 + } else if distance < scale + 37 { + 3 + } else if distance < scale + 57 { + 4 + } else if distance < scale + 77 { + 5 + } else if distance < scale + 92 { + 6 + } else { + 7 + }; + + h.position += dt as u8; + } + } + + pub fn get_at(&self, row: usize) -> Vec<&Horse> { + self.horses + .iter() + .filter(|h| h.position == row as u8) + .collect() + } + + pub fn print_table(&self) { + println!("HORSE\t\tNUMBER\t\tODDS\t\t\n"); + for horse in self.horses.iter() { + let (h, n, o) = (horse.name.clone(), horse.no, horse.odd); + + if h.len() > 7 { + println!("{}\t{}\t\t{:.2} :1", h, n, o); + } else { + println!("{}\t\t{}\t\t{:.2} :1", h, n, o); + } + } + println!("-----------------------------------------\n") + } + + pub fn print_placements(&mut self) -> u8 { + self.horses.sort_by(|a, b| b.position.cmp(&a.position)); + + println!("\nTHE RACE RESULTS ARE:\n"); + + for (i, h) in self.horses.iter_mut().enumerate() { + println!("{} PLACE HORSE NO. {}\t\tAT {:.2} :1", i + 1, h.no, h.odd); + h.position = 0; + } + + self.horses[0].no + } +} diff --git a/50_Horserace/rust/src/main.rs b/50_Horserace/rust/src/main.rs new file mode 100644 index 00000000..fc8a5d1a --- /dev/null +++ b/50_Horserace/rust/src/main.rs @@ -0,0 +1,28 @@ +use crate::{game::Game, util::PromptResult}; + +mod game; +mod horses; +mod players; +mod util; + +fn main() { + println!("\n\n\t\tHORSERACE"); + println!("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); + println!("WELCOME TO SOUTH PORTLAND HIGH RACETRACK\n\t\t...OWNED BY LAURIE CHEVALIER"); + + if let PromptResult::YesNo(yes) = util::prompt(Some(false), "DO YOU WANT DIRECTIONS?") { + if yes { + println!("UP TO 10 MAY PLAY. A TABLE OF ODDS WILL BE PRINTED. YOU"); + println!("MAY BET ANY AMOUNT UNDER $100,000 ON ONE HORSE."); + println!("DURING THE RACE, A HORSE WILL BE SHOWN BY ITS"); + println!("NUMBER. THE HORSES RACE DOWN THE PAPER!\n"); + } + } + + let mut game = Game::new(); + let mut again = true; + + while again { + again = game.play(); + } +} diff --git a/50_Horserace/rust/src/players.rs b/50_Horserace/rust/src/players.rs new file mode 100644 index 00000000..5aa891b7 --- /dev/null +++ b/50_Horserace/rust/src/players.rs @@ -0,0 +1,156 @@ +use crate::util::{self, PromptResult}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub money: u32, + pub playing: bool, + pub horse_no: u8, + pub bet: u32, +} + +impl Player { + fn new(name: String) -> Self { + Player { + name, + money: 100000, + playing: true, + horse_no: 0, + bet: 0, + } + } +} + +#[derive(Debug)] +pub struct Players { + players: Vec, +} + +impl Players { + pub fn new() -> Self { + let players; + + loop { + if let PromptResult::Numeric(n) = util::prompt(Some(true), "HOW MANY WANT TO BET?") { + if n <= 0 { + println!("THERE CAN'T BE (LESS THAN) ZERO PLAYERS!"); + } else if n > 10 { + println!("THERE CAN'T BE MORE THAN TEN PLAYERS!"); + } else { + println!("WHEN ? APPEARS, TYPE NAME"); + players = Players::generate_players(n); + break; + } + } + } + + Players { players } + } + + pub fn make_bets(&mut self) { + println!("PLACE YOUR BETS...HORSE # THEN AMOUNT"); + + for p in self.players.iter_mut() { + if !p.playing { + continue; + } + + let name = format!("{}?", p.name); + + 'prompt: loop { + if let PromptResult::Normal(response) = util::prompt(None, name.as_str()) { + let response: Vec<&str> = response.trim().split(",").collect(); + + for (i, n) in response.iter().enumerate() { + if let Ok(n) = n.parse::() { + if n.is_negative() { + println!("YOU CAN'T ENTER A NEGATIVE NUMBER!") + } else { + match i { + 0 => { + if n > 8 { + println!("INVALID HORSE #") + } else { + p.horse_no = n as u8; + } + } + 1 => { + if n == 0 { + println!("YOU CAN'T BET NOTHING!"); + } else if n > p.money as i32 { + println!("YOU DON'T HAVE ENOUGH MONEY!") + } else { + p.bet = n as u32; + break 'prompt; + } + } + _ => println!("YOU CAN'T ENTER MORE THAN 2 NUMBERS!"), + } + } + } else { + println!("ONLY ENTER NUMBERS PLEASE!"); + } + } + } + } + } + } + + fn generate_players(n: i32) -> Vec { + let mut players: Vec = Vec::new(); + + for _ in 0..n { + loop { + if let PromptResult::Normal(name) = util::prompt(None, "?") { + let name = name.trim().to_uppercase(); + + if name.is_empty() { + println!("NAME CAN'T BE EMPTY!"); + } else if let Some(_) = players.iter().find(|p| p.name == name) { + println!("THERE IS ALREADY A PLAYER WITH THAT NAME!"); + } else { + players.push(Player::new(name)); + break; + } + } + } + } + + players + } + + pub fn process_winner(&mut self, no: u8) { + println!(); + for p in self.players.iter_mut() { + if !p.playing { + continue; + } + + if p.horse_no == no { + p.money += p.bet; + println!("{} WON ${}! THEY HAVE ${}.", p.name, p.bet, p.money); + } else { + p.money -= p.bet; + println!("{} LOST ${}. THEY HAVE ${} LEFT.", p.name, p.bet, p.money); + } + p.bet = 0; + p.horse_no = 0; + } + println!(); + } + + pub fn prompt_next_round(&mut self) -> bool { + for p in self.players.iter_mut() { + let msg = format!("{}, DO YOU WANT TO BET ON THE NEXT RACE?", p.name); + if let PromptResult::YesNo(yes) = util::prompt(Some(false), msg.as_str()) { + p.playing = yes; + } + } + + if let None = self.players.iter().find(|p| p.playing) { + return false; + } + + true + } +} diff --git a/50_Horserace/rust/src/util.rs b/50_Horserace/rust/src/util.rs new file mode 100644 index 00000000..f1944bad --- /dev/null +++ b/50_Horserace/rust/src/util.rs @@ -0,0 +1,40 @@ +use std::io; + +pub enum PromptResult { + Normal(String), + YesNo(bool), + Numeric(i32), +} + +pub fn prompt(is_numeric: Option, msg: &str) -> PromptResult { + use PromptResult::*; + + println!("{msg}"); + + loop { + let mut input = String::new(); + + io::stdin() + .read_line(&mut input) + .expect("Failed to read input."); + + if let Some(is_numeric) = is_numeric { + let input = input.trim(); + + if is_numeric { + if let Ok(n) = input.parse::() { + return Numeric(n); + } + println!("PLEASE ENTER A VALID NUMBER!"); + } else { + match input.to_uppercase().as_str() { + "YES" | "Y" => return YesNo(true), + "NO" | "N" => return YesNo(false), + _ => println!("PLEASE ENTER (Y)ES OR (N)O."), + } + } + } else { + return Normal(input); + } + } +}