Last active
August 15, 2020 19:23
-
-
Save kristopherjohnson/83c6a6b8a1b7c6929ced83e922abccc1 to your computer and use it in GitHub Desktop.
Translation of classic Lunar Lander game from FOCAL to Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Translation of | |
//! <http://www.cs.brandeis.edu/~storer/LunarLander/LunarLander/LunarLanderListing.jpg> | |
//! by Jim Storer from FOCAL to Rust. | |
use std::error::Error; | |
use std::io; | |
use std::io::prelude::*; | |
use std::marker::{Send, Sync}; | |
use std::process; | |
use std::str::FromStr; | |
fn main() { | |
#![allow(clippy::cyclomatic_complexity)] | |
#![allow(non_snake_case)] | |
// Change this to "0_f64" to use double-precision floats | |
let Zero = 0_f32; | |
let mut A = Zero; | |
let mut G = Zero; | |
let mut I = Zero; | |
let mut J = Zero; | |
let mut K = Zero; | |
let mut L = Zero; | |
let mut M = Zero; | |
let mut N = Zero; | |
let mut S = Zero; | |
let mut T = Zero; | |
let mut V = Zero; | |
let mut Z = Zero; | |
// The loop below is a state machine that allows us to implement the | |
// spaghetti control flow of the FOCAL code in Rust. Each arm of the match | |
// is a possible destination of a jump. `next_location` is the next line | |
// number in the FOCAL source to be executed. `return_stack` is a | |
// subroutine return stack. | |
let mut next_location = 104; | |
let mut return_stack: Vec<i32> = Vec::new(); | |
macro_rules! goto { | |
($location:expr) => { | |
next_location = $location; | |
}; | |
} | |
macro_rules! call_subroutine { | |
($sub_location:expr, $return_location:expr) => { | |
next_location = $sub_location; | |
return_stack.push($return_location); | |
}; | |
} | |
macro_rules! return_from_subroutine { | |
() => { | |
next_location = return_stack.pop().unwrap(); | |
}; | |
} | |
loop { | |
match &mut next_location { | |
104 => { | |
println!( | |
"CONTROL CALLING LUNAR MODULE. MANUAL CONTROL IS NECESSARY | |
YOU MAY RESET FUEL RATE K EACH 10 SECS TO 0 OR ANY VALUE | |
BETWEEN 8 & 200 LBS/SEC. YOU'VE 16000 LBS FUEL. ESTIMATED | |
FREE FALL IMPACT TIME-120 SECS. CAPSULE WEIGHT-32500 LBS | |
" | |
); | |
goto!(120); | |
} | |
120 => { | |
println!( | |
"FIRST RADAR CHECK COMING UP | |
COMMENCE LANDING PROCEDURE | |
TIME,SECS ALTITUDE,MILES+FEET VELOCITY,MPG FUEL,LBS FUEL RATE | |
" | |
); | |
A = 120.0; | |
V = 1.0; | |
M = 32500.0; | |
N = 16500.0; | |
G = 0.001; | |
Z = 1.8; | |
L = 0.0; | |
goto!(210); | |
} | |
210 => { | |
print!( | |
"{:7.0}{:16.0}{:7.0}{:15.2}{:12.1} ", | |
L.round(), | |
A.trunc(), | |
(5280.0 * (A - A.trunc())).trunc(), | |
3600.0 * V, | |
M - N | |
); | |
goto!(211); | |
} | |
211 => { | |
print!("K=:"); | |
match accept_value() { | |
Ok(num) => { | |
K = num; | |
T = 10.0; | |
goto!(270); | |
} | |
Err(err) => match err.kind() { | |
io::ErrorKind::InvalidData => goto!(272), | |
io::ErrorKind::UnexpectedEof => { | |
process::exit(-1); | |
} | |
_ => panic!("unable to read input"), | |
}, | |
} | |
} | |
270 => { | |
if (200.0 - K) < 0.0 { | |
goto!(272); | |
} else if (8.0 - K) <= 0.0 { | |
goto!(310); | |
} else if K < 0.0 { | |
goto!(272); | |
} else if K == 0.0 { | |
goto!(310); | |
} else { | |
goto!(272); | |
} | |
} | |
272 => { | |
print!("NOT POSSIBLE"); | |
for _ in 1..=51 { | |
print!(".") | |
} | |
goto!(211); | |
} | |
310 => { | |
if (M - N - 0.001) < 0.0 { | |
goto!(410); | |
} else if (T - 0.001) < 0.0 { | |
goto!(210); | |
} else { | |
S = T; | |
if (N + S * K - M) <= 0.0 { | |
goto!(350); | |
} else { | |
S = (M - N) / K; | |
goto!(350); | |
} | |
} | |
} | |
350 => { | |
call_subroutine!(910, 351); | |
} | |
351 => { | |
if I <= 0.0 { | |
goto!(710); | |
} else if V <= 0.0 { | |
goto!(380); | |
} else if J < 0.0 { | |
goto!(810); | |
} else { | |
goto!(380); | |
} | |
} | |
380 => { | |
call_subroutine!(610, 310); | |
} | |
410 => { | |
println!("FUEL OUT AT {:8.2} SECS", L); | |
S = ((V * V + 2.0 * A * G).sqrt() - V) / G; | |
V += G * S; | |
L += S; | |
goto!(510) | |
} | |
510 => { | |
println!("ON THE MOON AT {:8.2} SECS", L); | |
let W = 3600.0 * V; | |
println!("IMPACT VELOCITY OF {:8.2} M.P.H.", W); | |
println!("FUEL LEFT: {:8.2} LBS", M - N); | |
if W <= 1.0 { | |
println!("PERFECT LANDING !-(LUCKY)"); | |
} else if W <= 10.0 { | |
println!("GOOD LANDING-(COULD BE BETTER"); | |
} else if W <= 22.0 { | |
println!("CONGRATULATIONS ON A POOR LANDING"); | |
} else if W <= 40.0 { | |
println!("CRAFT DAMAGE. GOOD LUCK"); | |
} else if W <= 60.0 { | |
println!("CRASH LANDING-YOU'VE 5 HRS OXYGEN"); | |
} else { | |
println!("SORRY,BUT THERE WERE NO SURVIVORS-YOU BLEW IT!"); | |
println!( | |
"IN FACT YOU BLASTED A NEW LUNAR CRATER {:8.2} FT. DEEP", | |
W * 0.277_777 | |
); | |
} | |
println!("\n\n\n\nTRY AGAIN?"); | |
goto!(592); | |
} | |
592 => { | |
print!("(ANS. YES OR NO):"); | |
match accept_line() { | |
Ok(line) => { | |
let line = line.trim().to_ascii_uppercase(); | |
if line.starts_with('Y') { | |
goto!(120); | |
} else if line.starts_with('N') { | |
goto!(598); | |
} else { | |
goto!(592); | |
} | |
} | |
Err(_) => goto!(598), | |
} | |
} | |
598 => { | |
println!("CONTROL OUT\n\n"); | |
break; | |
} | |
610 => { | |
L += S; | |
T -= S; | |
M -= S * K; | |
A = I; | |
V = J; | |
return_from_subroutine!(); | |
} | |
710 => { | |
if (S - 0.005) < 0.0 { | |
goto!(510); | |
} else { | |
S = 2.0 * A / (V + (V * V + 2.0 * A * (G - Z * K / M)).sqrt()); | |
call_subroutine!(910, 730); | |
} | |
} | |
730 => { | |
call_subroutine!(610, 710); | |
} | |
810 => { | |
let W = (1.0 - (M * G) / (Z * K)) / 2.0; | |
S = M * V / (Z * K * (W + (W * W + V / Z).sqrt())) + 0.05; | |
call_subroutine!(910, 830); | |
} | |
830 => { | |
if I <= 0.0 { | |
goto!(710); | |
} else { | |
call_subroutine!(610, 831); | |
} | |
} | |
831 => { | |
if -J <= 0.0 || V <= 0.0 { | |
goto!(310); | |
} else { | |
goto!(810); | |
} | |
} | |
910 => { | |
let Q = S * K / M; | |
J = V | |
+ G * S | |
+ Z * (-Q | |
- Q.powi(2) / 2.0 | |
- Q.powi(3) / 3.0 | |
- Q.powi(4) / 4.0 | |
- Q.powi(5) / 5.0); | |
I = A - G * S * S / 2.0 - V * S | |
+ Z * S | |
* (Q / 2.0 | |
+ Q.powi(2) / 6.0 | |
+ Q.powi(3) / 12.0 | |
+ Q.powi(4) / 20.0 | |
+ Q.powi(5) / 30.0); | |
return_from_subroutine!(); | |
} | |
line_number => { | |
panic!("destination {} does not exist", line_number); | |
} | |
} | |
} | |
} | |
/// Reads a numeric value from standard input. | |
/// | |
/// The value type `T` is expected to be something like `f32` or `f64`, but it | |
/// could be any type for which `str.parse()` is valid. | |
/// | |
/// Returns an error with kind `std::io::ErrorKind::InvalidData` if text is read | |
/// that cannot be parsed as a numeric value. | |
/// | |
/// Returns an error with kind `std::io::ErrorKind::UnexpectedEof` if | |
/// end-of-file is encountered without any preceding input. | |
fn accept_value<T>() -> io::Result<T> | |
where | |
T: FromStr, | |
<T as FromStr>::Err: 'static + Error + Send + Sync, | |
{ | |
let line = accept_line()?; | |
if line.is_empty() { | |
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "end of file")); | |
} | |
match line.trim().parse() { | |
Ok(num) => Ok(num), | |
Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err)), | |
} | |
} | |
/// Flushes standard output and reads a line from standard input. | |
/// | |
/// On EOF, returns an empty string. | |
fn accept_line() -> io::Result<String> { | |
io::stdout().flush()?; | |
let mut line = String::new(); | |
io::stdin().read_line(&mut line)?; | |
Ok(line) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment