Dave succeeds at crashing the lander on the Martian surface—and says it's progress!
In my last article, I spent almost the entire piece exploring gravitational physics, of all unlikely topics. The focus was on writing a version of the classic arcade game Lunar Lander, but this time, it would be landing a craft on the red planet Mars rather than that pockmarked lump of rock orbiting the Earth.
Being a shell script, however, it was all about the physics, not about the UI, because vector graphics are a bit tricky to accomplish within Bourne Shell—to say the least!
To make the solution a few dozen lines instead of a few thousand, I simplify the problem to two dimensions and assume safe, flat landing spaces. Then it's a question of forward velocity, which is easy to calculate, and downward velocity, which is tricky because it has the constant pull of gravity as you fire your retro rockets to compensate and thereby avoid crashing onto the planet's surface.
If one were working with Space X or NASA, there would be lots of factors to take into account with a real Martian lander, notably the mass of the spacecraft: as it burns fuel, the mass decreases, a nuance that the gravitational calculations can't ignore.
That's beyond the scope of this project, however, so I'm going to use some highly simplified mathematics instead, starting with the one-dimensional problem of descent:
speed = speed + gravity altitude = altitude - speed
Surprisingly, this works pretty well, particularly when there's negligible atmosphere. Landing on the Earth's surface has lots more complexity with atmospheric drag and weather effects, but looking at Mars, and not during its glory days as Barsoom, it's atmosphere-free.
In my last article, I presented figures using feet as a unit of measure, but it's time to switch to metric, so for the simulation game, I'm using Martian gravity = 3.722 meters/sec/sec. The spaceship will enter the atmosphere at an altitude of 500 meters (about 1/3 mile), and players have just more than 15 seconds to avoid crashing onto the Martian surface, with a terminal velocity of 59m/s.
Since I'm making game out of it, the calculations are performed in one-second increments, meaning that you actually can use the retro rockets at any point to compensate for the tug of gravity and hopefully land, rather than crash into Mars!
The equation changes only a tiny bit:
speed = speed + gravity + thrust
Again, there are complex astro-mechanical formulas to figure out force produced in a retro rocket burn versus fuel expended, but to simplify, I'm assuming that fuel is measured in output force: meters of counter thrust per second.
That is, if you are descending at 25 meters/second, application of 25 units of thrust will fully compensate and get you to zero descent, essentially hovering above the surface—until the inexorable pull of gravity begins to drag you back to the planet's surface, at least!
Gravity diminishes over distance, so too much thrust could break you completely free of the planet's gravitational pull. No bueno. To include that possibility, I'm going to set a ceiling altitude. Fly above that height, and you've broken free and are doomed to float off into space.
Shell scripts make working with integer math quite easy, but any real calculations need to be done with floating-point numbers, which can be tricky in the shell. Therefore, Instead of using the $(( )) notation or expr, I'm going to tap the power of bc, the binary calculator program.
Being in a shell script, it's a bit awkward, so I'm going to use a rather funky notational convenience to constrain each calculation to a single line:
speed=$( $bc <<< "scale=3; $speed + $gravity + $thrust" )
By default, for reasons I don't understand, bc also wants to work with just integer values, so ask it to solve the equation 1/4, and it'll return 0. Indicate how many digits after the decimal place to track with scale, however, and it works a lot better. That's what I'm doing above with scale=3. That gives three digits of precision after the decimal point, enough for the game to function fine.
With that notation in mind, I can finally code the basics of the Martian lander:
while [ $altitude -gt 0 ] do speed=$( $bc <<< "scale=3; $speed + $gravity + $thrust" ) altitude=$( $bc <<< "scale=3; $altitude + $speed" ) time=$(( $time + 1 )) done
Obviously, there are a lot of variables to instantiate with the correct values, including gravity (–3.722), altitude (500 meters), thrust (retro rockets start powered down, so the initial value is 0), and speed and time also should both be set to 0.
Even with this tiny snippet, however, there are some problems. For example, the conditional that controls the while loop tests whether altitude is greater than zero. But altitude is a floating-point number, so the test fails. The easy solution is a second variable that's just the integer portion of altitude:
alt=$( echo $altitude | cut -d\. -f1 )
One problem solved.
Thrust is the force being exerted by the rocket when it's firing, so that'll have to be something the user can enter after each second (the “game” part of the game). But once it's fired, it should shut off again, so thrust needs to be set back to zero after each calculation is complete.
There's also a tricky challenge with positive and negative values here. Gravity should be a negative value, as it's pulling the craft inexorably closer to the center of the planet. Therefore, thrust should be positive, since it's fighting gravity. That means speed will be negative when dropping toward the surface, and positive when shooting upward, potentially escaping the planet's gravity field entirely.
Here's a refinement on the core program loop:
while [ $alt -gt 0 ] do speed=$( $bc <<< "scale=3; $speed + $gravity + $thrust" ) thrust=0 # rocket fires on a per-second basis altitude=$( $bc <<< "scale=3; $altitude + $speed" ) alt=$( echo "$altitude" | cut -d\. -f1 ) time=$(( $time + 1 )) echo "$time seconds: speed: $speed m/s altitude: $altitude meters." done
That works if you just want to plummet to the planet without any rocket firing. It'd look like this:
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. 6 seconds: speed: -22.332 m/s altitude: 421.838 meters. 7 seconds: speed: -26.054 m/s altitude: 395.784 meters. 8 seconds: speed: -29.776 m/s altitude: 366.008 meters. 9 seconds: speed: -33.498 m/s altitude: 332.510 meters. 10 seconds: speed: -37.220 m/s altitude: 295.290 meters. 11 seconds: speed: -40.942 m/s altitude: 254.348 meters. 12 seconds: speed: -44.664 m/s altitude: 209.684 meters. 13 seconds: speed: -48.386 m/s altitude: 161.298 meters. 14 seconds: speed: -52.108 m/s altitude: 109.190 meters. 15 seconds: speed: -55.830 m/s altitude: 53.360 meters.
At this point, the craft is dropping at 55m/s and is only 53 meters above the surface of the planet, so you can count on a big, ugly crash. BOOM!
At second 15, you could apply 55 units of thrust to jerk the craft back to zero speed, but what if you didn't have 55 units of fuel or if the max thrust you could exert at any given unit time was 25 due to rocket design (and passenger survival) constraints?
That's where this gets interesting.
In my next article, I'll dig into those constraints and finally add some interactivity to the program. For now, be careful out there flying this particular space craft. It's your budget that the replacement parts are coming out of, after all!
Props to Joel Garcia and Chris York for their ongoing assistance with all the gravitational formulas. Any errors and glitches are all due to my own rusty physics.