From bbbf2478ef95177bd0d0725fdf2b40f7ef1dd2ab Mon Sep 17 00:00:00 2001 From: AnthonyMichaelTDM <68485672+AnthonyMichaelTDM@users.noreply.github.com> Date: Sat, 14 May 2022 23:39:18 -0700 Subject: [PATCH] finish rust port of 68_orbit --- 68_Orbit/rust/Cargo.toml | 9 ++ 68_Orbit/rust/README.md | 3 + 68_Orbit/rust/src/lib.rs | 215 ++++++++++++++++++++++++++++++++++++++ 68_Orbit/rust/src/main.rs | 103 ++++++++++++++++++ 4 files changed, 330 insertions(+) create mode 100644 68_Orbit/rust/Cargo.toml create mode 100644 68_Orbit/rust/README.md create mode 100644 68_Orbit/rust/src/lib.rs create mode 100644 68_Orbit/rust/src/main.rs diff --git a/68_Orbit/rust/Cargo.toml b/68_Orbit/rust/Cargo.toml new file mode 100644 index 00000000..1c2935d4 --- /dev/null +++ b/68_Orbit/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" \ No newline at end of file diff --git a/68_Orbit/rust/README.md b/68_Orbit/rust/README.md new file mode 100644 index 00000000..7e85f9a1 --- /dev/null +++ b/68_Orbit/rust/README.md @@ -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) diff --git a/68_Orbit/rust/src/lib.rs b/68_Orbit/rust/src/lib.rs new file mode 100644 index 00000000..2075d784 --- /dev/null +++ b/68_Orbit/rust/src/lib.rs @@ -0,0 +1,215 @@ +/* + lib.rs contains all the logic of the program +*/ + +use std::{error::Error, fmt::Display, str::FromStr, io::{self, Write}, f64::consts::PI}; + +use rand::{thread_rng, Rng}; + +/// handles setup for the game +pub struct Config { +} +impl Config { + /// creates and returns a new Config from user input + pub fn new() -> Result> { + //DATA + let config: Config = Config {}; + + //this game doesn't actually need a config + + //return new config + return Ok(config); + } +} + +/// run the program +pub fn run(_config: &Config) -> Result<(), Box> { + + //play game as long as user wants to play it + while play_game() {} + + //return to main + Ok(()) +} + +/// handles playing the game one time +/// after playing, asks the user if they want to play again and ... +/// returns true if user wants to play again, false otherwise +fn play_game() -> bool { + //DATA + let mut rng = thread_rng(); + let mut ship_destroyed:bool = false; + //generate position of the ship + let mut ship:Polar = Polar::new(rng.gen_range(0.0..2.0*PI), rng.gen_range(100.0_f64..300.0_f64).floor()); + let ship_speed:f64 = rng.gen_range((PI/36.0)..(PI/12.0)); //10deg - 30deg, but radians + + //game + // round loop + for hour in 0..7 { + //DATA + let photon_bomb:Polar; + let two_pi = 2.0*PI; + let distance; + + //tell user the hour + println!("THIS IS HOUR {},", hour); + + //get input for angle and distance to send the bomb + photon_bomb = get_bomb_from_user_input(); + + //move ship + ship.angle += ship_speed; + if ship.angle > two_pi { ship.angle -= two_pi} + + //calculate distance of bomb from ship + distance = photon_bomb.distance_from(&ship); + //tell user how far away they were from the target + println!("YOUR PHOTON BOMB EXPLODED {}*10^2 MILES FROM THE ROMULAN SHIP\n\n", distance); + + //did the bomb destroy the ship? + ship_destroyed = distance <= 50.0; + if ship_destroyed {break;} + //otherwise user didn't destroy the ship, run loop again + } + + //print results message depending on whether or not the user destroyed the ship in time + if ship_destroyed { + println!("YOU HAVE SUCCESFULLY COMPLETED YOUR MISSION."); + } else { + println!("YOU HAVE ALLOWED THE ROMULANS TO ESCAPE.") + } + + //prompt user to play again + //do they want to play yes? if they do return true, otherwise false + return match get_string_from_user_input("ANOTHER ROMULAN SHIP HAS GONE INTO ORBIT.\nDO YOU WISH TO TRY TO DESTROY IT?\n(y/n) ") { + Ok(string) => string.chars().any(|c| c.eq_ignore_ascii_case(&'y')), + Err(_e) => false, + }; +} + +/// structure to handle polar coordinates +struct Polar { + angle: f64, + radius: f64, +} +impl Polar { + /// create new polar cordinate with the given angle (in degrees) and radiurd from the origin + fn new(angle:f64, radius:f64) -> Polar { + //if radius is negative, correct it + if radius < 0.0 { + return Polar{ angle: angle + PI/2.0, radius: -radius }; + } + Polar { angle, radius} + } + ///create new polar cordinate with the given angle (in degrees, converted to radians) and radius from the origin + fn new_from_degrees(angle:usize, radius:f64) -> Polar { + let angle_to_rads: f64 = f64::from( (angle % 360) as u16) * PI/180.0; + + Polar::new(angle_to_rads, radius) + } + ///calculates the distance between 2 coordinates using the law of cosines + fn distance_from(&self,other:&Polar) -> f64 { + (other.radius*other.radius + self.radius*self.radius - 2.0*other.radius*self.radius*((other.angle-self.angle).abs().cos())).sqrt().floor() + } +} + +/// gets a Polar with bomb angle and detonation range from user +fn get_bomb_from_user_input() -> Polar { + return loop {//input loop + //DATA + let bomb_angle:usize; + let bomb_altitude:f64; + //get angle + match get_number_from_input("AT WHAT ANGLE DO YOU WISH TO SEND YOUR PHOTON BOMB (0-360): ", 0_usize, 360_usize) { + Ok(angle) => bomb_angle = angle, + Err(err) => { + println!("{}",err); + continue; + }, + } + //get radius + match get_number_from_input("HOW FAR OUT DO YOU WISH TO DETONATE IT (100.0-300.0): ", 100.0, 300.0) { + Ok(radius) => bomb_altitude = radius, + Err(err) => { + println!("{}",err); + continue; + }, + } + println!(""); + //create coordinates from input + break Polar::new_from_degrees( bomb_angle, bomb_altitude); + }; +} + +/// gets a string from user input +fn get_string_from_user_input(prompt: &str) -> Result> { + //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(prompt: &str, min:T, max:T) -> Result> { + //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() || c.eq_ignore_ascii_case(&'.')).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()), + } +} \ No newline at end of file diff --git a/68_Orbit/rust/src/main.rs b/68_Orbit/rust/src/main.rs new file mode 100644 index 00000000..18d61a76 --- /dev/null +++ b/68_Orbit/rust/src/main.rs @@ -0,0 +1,103 @@ +/* + 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!(" + Orbit + CREATIVE COMPUTING MORRISTOWN, NEW JERSEY + + + + SOMEWHERE ABOVE YOUR PLANET IS A ROMULAN SHIP. + + THE SHIP IS IN A CONSTANT POLAR ORBIT. ITS + DISTANCE FROM THE CENTER OF YOUR PLANET IS FROM + 10,000 TO 30,000 MILES AND AT ITS PRESENT VELOCITY CAN + CIRCLE YOUR PLANET ONCE EVERY 12 TO 36 HOURS. + + UNFORTUNATELY, THEY ARE USING A CLOAKING DEVICE SO + YOU ARE UNABLE TO SEE THEM, BUT WITH A SPECIAL + INSTRUMENT YOU CAN TELL HOW NEAR THEIR SHIP YOUR + PHOTON BOMB EXPLODED. YOU HAVE SEVEN HOURS UNTIL THEY + HAVE BUILT UP SUFFICIENT POWER IN ORDER TO ESCAPE + YOUR PLANET'S GRAVITY. + + YOUR PLANET HAS ENOUGH POWER TO FIRE ONE BOMB AN HOUR. + + AT THE BEGINNING OF EACH HOUR YOU WILL BE ASKED TO GIVE AN + ANGLE (BETWEEN 0 AND 360) AND A DISTANCE IN UNITS OF + 100 MILES (BETWEEN 100 AND 300), AFTER WHICH YOUR BOMB'S + DISTANCE FROM THE ENEMY SHIP WILL BE GIVEN. + + AN EXPLOSION WITHIN 5,000 MILES OF THE ROMULAN SHIP + WILL DESTROY IT. + + BELOW IS A DIAGRAM TO HELP YOU VISUALIZE YOUR PLIGHT. + + + 90 + 0000000000000 + 0000000000000000000 + 000000 000000 + 00000 00000 + 00000 XXXXXXXXXXX 00000 + 00000 XXXXXXXXXXXXX 00000 + 0000 XXXXXXXXXXXXXXX 0000 + 0000 XXXXXXXXXXXXXXXXX 0000 + 0000 XXXXXXXXXXXXXXXXXXX 0000 + 180<== 00000 XXXXXXXXXXXXXXXXXXX 00000 ==>0 + 0000 XXXXXXXXXXXXXXXXXXX 0000 + 0000 XXXXXXXXXXXXXXXXX 0000 + 0000 XXXXXXXXXXXXXXX 0000 + 00000 XXXXXXXXXXXXX 00000 + 00000 XXXXXXXXXXX 00000 + 00000 00000 + 000000 000000 + 0000000000000000000 + 0000000000000 + 270 + + X - YOUR PLANET + O - THE ORBIT OF THE ROMULAN SHIP + + ON THE ABOVE DIAGRAM, THE ROMULAN SHIP IS CIRCLING + COUNTERCLOCKWISE AROUND YOUR PLANET. DON'T FORGET THAT + WITHOUT SUFFICIENT POWER THE ROMULAN SHIP'S ALTITUDE + AND ORBITAL RATE WILL REMAIN CONSTANT. + + GOOD LUCK. THE FEDERATION IS COUNTING ON YOU. + "); +} +