mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-21 23:00:43 -08:00
9
81_Splat/rust/Cargo.toml
Normal file
9
81_Splat/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"
|
||||
64
81_Splat/rust/src/celestial_body.rs
Normal file
64
81_Splat/rust/src/celestial_body.rs
Normal file
@@ -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<CelestialBody> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
105
81_Splat/rust/src/game.rs
Normal file
105
81_Splat/rust/src/game.rs
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
39
81_Splat/rust/src/main.rs
Normal file
39
81_Splat/rust/src/main.rs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
81_Splat/rust/src/stats.rs
Normal file
88
81_Splat/rust/src/stats.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
use crate::utility;
|
||||
|
||||
pub struct Stats {
|
||||
altitudes: Vec<f32>,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn new() -> Option<Self> {
|
||||
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::<f32>().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!");
|
||||
}
|
||||
}
|
||||
168
81_Splat/rust/src/utility.rs
Normal file
168
81_Splat/rust/src/utility.rs
Normal file
@@ -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::<f32>().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_altitude() -> f32 {
|
||||
9001. * rand::random::<f32>() + 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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user