Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Last active August 15, 2020 19:23
Show Gist options
  • Save kristopherjohnson/83c6a6b8a1b7c6929ced83e922abccc1 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/83c6a6b8a1b7c6929ced83e922abccc1 to your computer and use it in GitHub Desktop.
Translation of classic Lunar Lander game from FOCAL to Rust
//! 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