Dave finishes the script for his Martian lander game and offers suggestions on how you can make improvements on your own.
In my last few articles, I've been building a variant on the classic video game Lunar Lander, with a few simplifications and one big change: Martian gravity instead of lunar gravity. The moon is 1/6th of Earth's gravity; whereas Mars is about 1/3 of Earth's gravity, which makes flying a lander in for a soft descent a bit more exciting.
The tricky one might be to simulate a black hole, but that's easy to do by having a really, really big gravitational value, but not so easy to land safely. It's not hugely interesting, actually, unless you're working on the script to Interstellar 2 perhaps.
The starting parameters of the game have Martian gravity set to 3.722 meters/sec/sec, and the spaceship enters the atmosphere at an altitude of 500 meters (about 1/3 mile). Do the math, and that means players have just more than 15 seconds to avoid crashing onto the Martian surface.
My last article (in the October 2016 issue of LJ) ended with a demonstration of code that offered second-by-second data on what was basically free fall through the Martian atmosphere:
1 seconds: speed: -3.722 m/s altitude: 496.278 meters. 2 seconds: speed: -7.444 m/s altitude: 488.834 meters. 3 seconds: speed: -11.166 m/s altitude: 477.668 meters. 4 seconds: speed: -14.888 m/s altitude: 462.780 meters. 5 seconds: speed: -18.610 m/s altitude: 444.170 meters.
That's not a great way to land unless you're in a really, really well padded couch. My first stab at adding an interesting interface is to stop each second and offer users the chance to specify whether they want to fire their retro-rockets and how much fuel to burn for the subsequent second.
Burn your fuel too early, and you could end up shooting off into space or level out just to plummet to the surface anyway. In this first version, however, the user will have unlimited fuel (though in real life it'd be limited, and the vessel would lighten up, decreasing gravitational pull, as the fuel was burned).
Here's the core of the code:
echo "$time seconds into flight: speed: $speed m/s \ and altitude: $altitude meters." echo -n "Fire retro rockets? (burn rate: 0-100): " read thrust if [ -z "$thrust" ] ; then thrust=0 fi
The last few lines allow the player simply to press Enter and have that be the equivalent of a zero—easy enough. Now let's try to land the darn spaceship:
Time: 1: Speed: -3.722 m/s and altitude: 496.278 meters. Fire retro rockets? (burn rate: 0-100): Time: 2: Speed: -7.444 m/s and altitude: 488.834 meters. Fire retro rockets? (burn rate: 0-100): Time: 3: Speed: -11.166 m/s and altitude: 477.668 meters. Fire retro rockets? (burn rate: 0-100): Time: 4: Speed: -14.888 m/s and altitude: 462.780 meters. Fire retro rockets? (burn rate: 0-100): 15 Time: 5: Speed: -3.610 m/s and altitude: 459.170 meters. Fire retro rockets? (burn rate: 0-100): Time: 6: Speed: -7.332 m/s and altitude: 451.838 meters. Fire retro rockets? (burn rate: 0-100): Time: 7: Speed: -11.054 m/s and altitude: 440.784 meters. Fire retro rockets? (burn rate: 0-100): Time: 8: Speed: -14.776 m/s and altitude: 426.008 meters. Fire retro rockets? (burn rate: 0-100): 15 Time: 9: Speed: -3.498 m/s and altitude: 422.510 meters. Fire retro rockets? (burn rate: 0-100): Time: 10: Speed: -7.220 m/s and altitude: 415.290 meters. Fire retro rockets? (burn rate: 0-100): Time: 11: Speed: -10.942 m/s and altitude: 404.348 meters. Fire retro rockets? (burn rate: 0-100): Time: 12: Speed: -14.664 m/s and altitude: 389.684 meters. Fire retro rockets? (burn rate: 0-100): 15 Time: 13: Speed: -3.386 m/s and altitude: 386.298 meters. Fire retro rockets? (burn rate: 0-100):
Notice here that I am being conservative with the fuel, firing the thrusters at 4, 8 and 12 seconds. This allows me to be 13 seconds into the descent and have a speed of only 3.386 m/s, while dropping from 500 meters to 386 meters. If the fuel holds out, this isn't a bad landing strategy!
Now, what if I add some basic constraints? What about limited fuel? And what about a cap on the maximum possible thrust so that you don't just decide to drop like a stone to the surface and apply huge amounts of thrust at the last second to pop up for a gentle landing?
In real life, of course, that'd likely produce more g-force than a human could survive, but I'm not going to worry about people falling unconscious while playing Martian Lander. It'd probably be bad for reviews anyway!
Still, starting with 100 fuel and a max thrust of 30 should make things interesting. This can be captured in a couple variables at the top of the script (and then could be changed with starting parameters, as desired).
The main mathematics of the script are captured in five lines, making it easy enough to understand:
speed=$( $bc <<< "scale=3; $speed + $gravity + $thrust" ) thrust=0 # rocket fires second by second altitude=$( $bc <<< "scale=3; $altitude + $speed" ) alt=$( echo "$altitude" | cut -d\. -f1 ) time=$(( $time + 1 ))
Notice the $alt variable. That's the integer portion of the altitude for display. The script actually keeps a more accurate value as $altitude.
As a reminder, I'm using the bc binary calculator, and in the Linux world, you need to specify how many digits of accuracy you want after the decimal point. The default is zero, making bc work like expr.
The math is straightforward once sufficiently simplified (one of the few times it's good not to work at NASA!), so most of the code deals with user interaction.
Before going there, however, let's start with a full listing of all starting parameters for the script:
speed=0 # > 0 is climbing, < 0 is falling gravity="-3.722" # gravity pulls ya down accel=0 # start with zero acceleration height=500 # Note that 1609 meters = 1 mile fuel=100 # limited fuel maxthrust=30 # the ship, she canna handle greater! maxheight=$(( 2 * $height )) # above you shoot into space altitude=$height # current altitude above the surface alt=$altitude # integer value of altitude thrust=0 outoffuel=0 # not yet true
With these values all specified, the main input loop of the game is captured in about 20 lines:
if [ $alt -gt $maxheight ] ; then echo "Well heck. You used too much thrust and have \ escaped the gravitational pull of Mars. You're \ doomed to float off into space and die. Bummer." exit 1 elif [ $alt -gt 0 ] ; then echo "Time: ${time}: Speed: $speed m/s and \ altitude: $altitude meters." if [ $fuel -gt 0 ] ; then echo -n "Fire retro rockets? (burn: 0-${maxthrust}): " read thrust echo "" if [ -z "$thrust" ] ; then thrust=0 elif [ $thrust -gt $maxthrust ] ; then echo "** Ya canna handle that much thrust! \ Reset to $maxthrust" ; echo "" thrust=$maxthrust fi fuel=$(( $fuel - $thrust )) # burn, baby else if [ $outoffuel -eq 0 ] ; then echo "( out of fuel )" outoffuel=1 fi fi fi
Notice that if the user specifies too much thrust, the program just resets it to $maxthrust—safety, you know. Otherwise, the code above should be pretty easy to understand (and do note also that long, mnemonic variable names always make code more readable!)
And finally, let's give it a whirl:
Time: 1: Speed: -3.722 m/s and altitude: 496.278 meters. Fire retro rockets? (burn rate: 0-30): 50 ** Ya canna handle that much thrust! Reset to 30 Time: 2: Speed: 22.556 m/s and altitude: 518.834 meters. Fire retro rockets? (burn rate: 0-30): 30 Time: 3: Speed: 48.834 m/s and altitude: 567.668 meters. Fire retro rockets? (burn rate: 0-30): 30 Time: 4: Speed: 75.112 m/s and altitude: 642.780 meters. Fire retro rockets? (burn rate: 0-30): 30 Time: 5: Speed: 101.390 m/s and altitude: 744.170 meters. ( out of fuel ) Time: 6: Speed: 97.668 m/s and altitude: 841.838 meters. Time: 7: Speed: 93.946 m/s and altitude: 935.784 meters. Well heck. You used too much thrust and have escaped the gravitational pull of Mars. You're doomed to float off into space and die. Bummer.
Oops—way too much retro rocket, and I ran out of fuel only five seconds into the descent! Bummer indeed.
I encourage you to work on this script yourself to see what you can do with it that's interesting. One possibility is a simple input script that lets users specify times and burns and have them all applied automatically (this could be as easy as time:burn time:burn as a starting parameter).
For realism, you also could go back and calculate gravitational pull as a function of the weight of the ship + fuel, so that as you burn fuel, the pull of the Martian surface diminishes. Or, that might be too much physics!
Another possibility: make gravity an optional starting parameter so that you can create Venusian Lander, Neptune Lander and so on too. While you're at it, you could let the player specify max thrust and fuel from the command line too.
In any case, good luck with your Martian Lander. In my next article, I'll move off in a completely new direction—which might possibly still involve the moon.