mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-24 20:10:15 -08:00
finish rust port of 68_orbit
This commit is contained in:
9
68_Orbit/rust/Cargo.toml
Normal file
9
68_Orbit/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
68_Orbit/rust/README.md
Normal file
3
68_Orbit/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)
|
||||
215
68_Orbit/rust/src/lib.rs
Normal file
215
68_Orbit/rust/src/lib.rs
Normal file
@@ -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<Config, Box<dyn Error>> {
|
||||
//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<dyn Error>> {
|
||||
|
||||
//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<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() || 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()),
|
||||
}
|
||||
}
|
||||
103
68_Orbit/rust/src/main.rs
Normal file
103
68_Orbit/rust/src/main.rs
Normal file
@@ -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.
|
||||
");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user