diff --git a/81_Splat/rust/Cargo.toml b/81_Splat/rust/Cargo.toml new file mode 100644 index 00000000..3b1d02f5 --- /dev/null +++ b/81_Splat/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/81_Splat/rust/src/celestial_body.rs b/81_Splat/rust/src/celestial_body.rs new file mode 100644 index 00000000..594a3bcb --- /dev/null +++ b/81_Splat/rust/src/celestial_body.rs @@ -0,0 +1,64 @@ +use rand::{prelude::SliceRandom, Rng}; + +#[derive(Debug)] +pub enum CelestialBody { + MERCURY, + VENUS, + EARTH, + MOON, + MARS, + JUPITER, + SATURN, + URANUS, + NEPTUNE, + SUN, +} + +impl CelestialBody { + pub fn get_acceleration(&self) -> f32 { + use CelestialBody::*; + + match self { + MERCURY => 12.2, + VENUS => 28.3, + EARTH => 32.16, + MOON => 5.12, + MARS => 12.5, + JUPITER => 85.2, + SATURN => 37.6, + URANUS => 33.8, + NEPTUNE => 39.6, + SUN => 896., + } + } + + pub fn print_acceleration_message(&self) { + let messages: [&str; 3] = ["Fine,", "All right,", "Then"]; + let m = messages.choose(&mut rand::thread_rng()).unwrap(); + + println!( + "{} YOU'RE ON {:?}. ACCELERATION = {} FT/SEC/SEC.", + m.to_uppercase(), + self, + self.get_acceleration() + ); + } +} + +pub fn random_celestial_body() -> Option { + use CelestialBody::*; + + match rand::thread_rng().gen_range(0..10) { + 0 => Some(MERCURY), + 1 => Some(VENUS), + 2 => Some(EARTH), + 3 => Some(MOON), + 4 => Some(MARS), + 5 => Some(JUPITER), + 6 => Some(SATURN), + 7 => Some(URANUS), + 8 => Some(NEPTUNE), + 9 => Some(SUN), + _ => None, + } +} diff --git a/81_Splat/rust/src/game.rs b/81_Splat/rust/src/game.rs new file mode 100644 index 00000000..8b3bba18 --- /dev/null +++ b/81_Splat/rust/src/game.rs @@ -0,0 +1,105 @@ +use crate::utility; + +pub struct Game { + altitude: f32, + terminal_velocity: f32, + acceleration: f32, + interval: f32, +} + +impl Game { + pub fn new() -> Game { + let altitude = utility::get_altitude(); + + let terminal_velocity = utility::get_terminal_velocity( + "SELECT YOUR OWN TERMINAL VELOCITY", + "WHAT TERMINAL VELOCITY (MI/HR)?", + ); + + let acceleration = utility::get_acceleration( + "WANT TO SELECT ACCELERATION DUE TO GRAVITY", + "WHAT ACCELERATION (FT/SEC/SEC)?", + ); + + println!(""); + println!(" ALTITUDE = {} FT", altitude); + println!(" TERM. VELOCITY = {} FT/SEC +-5%", terminal_velocity); + println!(" ACCELERATION = {} FT/SEC/SEC +-5%", acceleration); + + let seconds = + utility::prompt_numeric("\nSET THE TIMER FOR YOUR FREEFALL.\nHOW MANY SECONDS?"); + + println!("\nHERE WE GO.\n"); + + println!("TIME (SEC)\tDIST TO FALL (FT)"); + println!("==========\t================="); + + Game { + altitude, + terminal_velocity, + acceleration, + interval: seconds / 8., + } + } + + pub fn tick(&mut self) -> f32 { + let mut splat = false; + let mut terminal_velocity_reached = false; + + let (v, a) = (self.terminal_velocity, self.acceleration); + let terminal_velocity_time = v / a; + + let initial_altitude = self.altitude; + + for i in 0..=8 { + let dt = i as f32 * self.interval; + + if dt >= terminal_velocity_time { + if !terminal_velocity_reached { + println!( + "TERMINAL VELOCITY REACHED AT T PLUS {} SECONDS.", + terminal_velocity_time + ); + terminal_velocity_reached = true; + } + + let d1 = v.powi(2) / (2. * a); + let d2 = v * (dt - (terminal_velocity_time)); + self.altitude = initial_altitude - (d1 + d2); + + if self.altitude <= 0. { + let t = (initial_altitude - d1) / v; + utility::print_splat(t + terminal_velocity_time); + + splat = true; + break; + } + } else { + let d1 = (a * 0.5) * (dt.powi(2)); + self.altitude = initial_altitude - d1; + + if self.altitude <= 0. { + let t = (2. * initial_altitude / a).sqrt(); + utility::print_splat(t); + + splat = true; + break; + } + } + + println!("{}\t\t{}", dt, self.altitude); + + std::thread::sleep(std::time::Duration::from_secs(1)); + } + + let mut a = -1.; + + if !splat { + println!("\nCHUTE OPEN\n"); + + a = self.altitude; + } + + a + } +} diff --git a/81_Splat/rust/src/main.rs b/81_Splat/rust/src/main.rs new file mode 100644 index 00000000..eec8bfe6 --- /dev/null +++ b/81_Splat/rust/src/main.rs @@ -0,0 +1,39 @@ +use crate::{game::Game, stats::Stats}; + +mod celestial_body; +mod game; +mod stats; +mod utility; + +fn main() { + println!("\n\n\n SPLAT"); + println!(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n"); + + println!("WELCOME TO 'SPLAT' -- THE GAME THAT SIMULATES"); + println!("A PARACHUTE JUMP. TRY OPEN YOUR CHUTE AT THE"); + println!("LAST POSSIBLE MOMENT WITHOUT GOING SPLAT.\n"); + + let mut stats = Stats::new(); + + loop { + let mut game = Game::new(); + + let latest_altitude = game.tick(); + + if latest_altitude > 0. { + if let Some(s) = &mut stats { + s.add_altitude(latest_altitude); + } + } + + use utility::prompt_bool; + if !prompt_bool("DO YOU WANT TO PLAY AGAIN?", true) { + if !prompt_bool("PLEASE?", false) { + if !prompt_bool("YES OR NO PLEASE?", false) { + println!("SSSSSSSSSS."); + break; + } + } + } + } +} diff --git a/81_Splat/rust/src/stats.rs b/81_Splat/rust/src/stats.rs new file mode 100644 index 00000000..c86b83ae --- /dev/null +++ b/81_Splat/rust/src/stats.rs @@ -0,0 +1,88 @@ +use std::{ + fs::{self, File}, + io::Write, +}; + +use crate::utility; + +pub struct Stats { + altitudes: Vec, +} + +impl Stats { + pub fn new() -> Option { + if utility::prompt_bool("WOULD YOU LIKE TO LOAD PREVIOUS GAME DATA?", false) { + let path = "src/stats.txt"; + let mut altitudes = Vec::new(); + + if let Ok(stats) = fs::read_to_string(path) { + if stats.is_empty() { + return Some(Stats { + altitudes: Vec::new(), + }); + } + + let stats: Vec<&str> = stats.trim().split(",").collect(); + + for s in stats { + if s.is_empty() { + continue; + } + + let s = s.parse::().expect("Corrupt stats file!"); + altitudes.push(s); + } + + return Some(Stats { altitudes }); + } else { + println!("PREVIOUS GAME DATA NOT FOUND!"); + + if !utility::prompt_bool("WOULD YOU LIKE TO CREATE ONE?", false) { + return None; + } else { + let mut file = File::create(path).expect("Invalid file path!"); + file.write_all("".as_bytes()) + .expect("Could not create file!"); + + return Some(Stats { + altitudes: Vec::new(), + }); + } + } + } + + println!("\nRESULTS OF THIS SESSION WILL NOT BE SAVED."); + None + } + + pub fn add_altitude(&mut self, a: f32) { + let all_jumps = self.altitudes.len() + 1; + let mut placement = all_jumps; + + for (i, altitude) in self.altitudes.iter().enumerate() { + if a <= *altitude { + placement = i + 1; + break; + } + } + + utility::print_win(all_jumps, placement); + + self.altitudes.push(a); + self.altitudes.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + self.write(); + } + + fn write(&self) { + let mut file = File::create("src/stats.txt").expect("Error loading stats data!"); + + let mut altitudes = String::new(); + + for a in &self.altitudes { + altitudes.push_str(format!("{},", a).as_str()); + } + + write!(&mut file, "{}", altitudes.trim()).expect("ERROR WRITING Stats FILE!"); + } +} diff --git a/81_Splat/rust/src/utility.rs b/81_Splat/rust/src/utility.rs new file mode 100644 index 00000000..c37a2af3 --- /dev/null +++ b/81_Splat/rust/src/utility.rs @@ -0,0 +1,168 @@ +use crate::celestial_body; +use rand::Rng; +use std::io; + +const DEATH_MESSAGES: [&str; 10] = [ + "REQUIESCAT IN PACE.", + "MAY THE ANGEL OF HEAVEN LEAD YOU INTO PARADISE.", + "REST IN PEACE.", + "SON-OF-A-GUN.", + "#$%&&%!$", + "A KICK IN THE PANTS IS A BOOST IF YOU'RE HEADED RIGHT.", + "HMMM. SHOULD HAVE PICKED A SHORTER TIME.", + "MUTTER. MUTTER. MUTTER.", + "PUSHING UP DAISIES.", + "EASY COME, EASY GO.", +]; + +pub fn read_line() -> String { + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read line."); + input.trim().to_uppercase() +} + +pub fn prompt_bool(msg: &str, template: bool) -> bool { + if template { + println!("{} (YES OR NO)?", msg); + } else { + println!("{}", msg); + } + + loop { + let response = read_line(); + + match response.as_str() { + "YES" => return true, + "NO" => return false, + _ => println!("PLEASE ENTER YES OR NO."), + } + } +} + +pub fn prompt_numeric(msg: &str) -> f32 { + println!("{}", msg); + + loop { + let response = read_line(); + + if let Some(_) = response.chars().find(|c| !c.is_numeric()) { + println!("PLEASE ENTER A NUMBER."); + } else { + return response.parse::().unwrap(); + } + } +} + +pub fn get_altitude() -> f32 { + 9001. * rand::random::() + 1000. +} + +pub fn get_terminal_velocity(bool_msg: &str, num_msg: &str) -> f32 { + let mut _num = 0.0; + + if prompt_bool(bool_msg, true) { + _num = prompt_numeric(num_msg); + } else { + _num = get_random_float(0., 1000.); + println!("OK. TERMINAL VELOCTY = {} MI/HR", _num); + } + + (_num * ((5280 / 3600) as f32)) * get_random_float(0.95, 1.05) +} + +pub fn get_acceleration(bool_msg: &str, num_msg: &str) -> f32 { + let mut _num = 0.0; + + if prompt_bool(bool_msg, true) { + _num = prompt_numeric(num_msg); + } else { + let b = + celestial_body::random_celestial_body().expect("Fatal Error: Invalid Celestial Body!"); + + _num = b.get_acceleration(); + b.print_acceleration_message(); + } + + _num * get_random_float(0.95, 1.05) +} + +fn get_random_float(min: f32, max: f32) -> f32 { + rand::thread_rng().gen_range(min..=max) +} + +pub fn print_splat(t: f32) { + print_random(format!("{}\t\tSPLAT!", t).as_str(), &DEATH_MESSAGES); + print!("I'LL GIVE YOU ANOTHER CHANCE.\n"); +} + +fn print_random(msg: &str, choices: &[&str]) { + use rand::seq::SliceRandom; + use rand::thread_rng; + + let mut rng = thread_rng(); + + println!("{}", msg); + println!("\n{}\n", choices.choose(&mut rng).unwrap()); +} + +pub fn print_win(total: usize, placement: usize) { + if total <= 3 { + let order; + + match total { + 1 => order = "1ST", + 2 => order = "2ND", + 3 => order = "3RD", + _ => order = "#INVALID#", + } + + println!("AMAZING!!! NOT BAD FOR YOUR {} SUCCESSFUL JUMP!!!", order); + return; + } + + let (total, placement) = (total as f32, placement as f32); + + let betters = placement - 1.; + let p: f32 = (total - betters) / total; + + println!("{}", p); + + println!( + "placement: {}, total jumps: {}, percent is: {}", + placement, total, p + ); + + if p < 0.1 { + println!( + "HEY! YOU PULLED THE RIP CORD MUCH TOO SOON. {} SUCCESSFUL\nJUMPS BEFORE YOURS AND YOU CAME IN NUMBER {}! GET WITH IT!", + total, placement + ); + } else if p < 0.25 { + println!( + "HUMPH! DON'T YOU HAVE ANY SPORTING BLOOD? THERE WERE\n{} SUCCESSFUL JUMPS BEFORE YOURS AND YOU CAME IN {} JUMPS\nBETTER THAN THE WORST. SHAPE UP!!!", + total, placement + ); + } else if p < 0.5 { + println!( + "CONSERVATIVE, AREN'T YOU? YOU RANKED ONLY {} IN THE\n{} SUCCESSFUL JUMPS BEFORE YOURS.", + placement, total + ); + } else if p < 0.75 { + println!( + "NOT BAD. THERE HAVE BEEN {} SUCCESSFUL JUMPS BEFORE YOURS.\nYOU WERE BEATEN OUT BY {} OF THEM.", + total, betters + ); + } else if p < 0.9 { + println!( + "PRETTY GOOD! {} SUCCESSFUL JUMPS PRECEDED YOURS AND ONLY\n{} OF THEM GOT LOWER THAN YOU DID BEFORE THEIR CHUTES\nOPENED.", + total, betters + ) + } else { + println!( + "WOW! THAT'S SOME JUMPING. OF THE {} SUCCESSFUL JUMPS\nBEFORE YOURS, ONLY {} OPENED THEIR CHUTES LOWER THAN\nYOU DID.", + total, betters + ) + } +}