LJ Archive

Work the Shell

Wrapping Up the Mars Lander

Dave Taylor

Issue #271, November 2016

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.

Creating the Interface

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!

Adding Some Limits and Constraints

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.

Mods and Improvements

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.

Dave Taylor has been hacking shell scripts on UNIX and Linux systems for a really long time. He's the author of Learning Unix for Mac OS X and the popular shell scripting book Wicked Cool Shell Scripts. He can be found on Twitter as @DaveTaylor, and you can reach him through his tech Q&A site: www.AskDaveTaylor.com.

LJ Archive