## LunarLander 2

Many of the games in the book are very simplified versions of classic computer games. LunarLander is an example of that. The original Lunar Lander arcade game was a “vector-graphics” game, where you have to land a lunar module on the moon.There is rocky lunar terrain with a few safe landing spots.You control the rotation and thrust of the lander to make a safe landing.Here’s a screen shot: The LunarLander in “Hello World!” only moves vertically, not sideways.This was done to show a gravity simulation while keeping the code as simple as possible.After all, the point of the games in the book is to learn, not to make an accurate copy of an old arcade game.We encourage readers to take the games in the book as starting points and extend them in whatever way they like.

I thought it would be fun to take the first step in that direction with LunarLander.So, I made LunarLander2D.This turns LunarLander from a 1-D simulation (vertical only) to a 2-D simulation (vertical and horizontal), more like the arcade version.

To do that, there are two major things that needed to change:

• Need to keep track of both vertical and horizontal speed
• Need a way to rotate the lander, so you can thrust in different directions to steer

Of course, once you start doing rotation and applying thrust at different angles, you need some trigonometry to do the calculations.That’s the main reason we didn’t make LunarLander a 2-D simulation in the first place.We didn’t want to make trigonometry a requirement for the book.

I had to dust the cobwebs off my knowledge of trig.I found the trickiest part was figuring out how to draw the rocket flames at an angle when the lander is rotated.The code to do that is written in an “expanded” form.That is, the code could be made much more compact, but I’m trying to show step-by-step how I’m doing the calculation.

Here are the main differences from LunarLander to LunarLander2:

• The window is wider, to make room for the sideways motion.
• The lander is now a sprite, which makes it easier to do the rotation (using pygame.transform.rotate)
• The thrust is controlled by pressing/holding the spacebar instead of using the mouse to drag a slider (There are no longer any mouse events.)
• The rotation is controlled by the left and right arrow keys
• A good landing now depends on both speed and angle

The code is shown below, and you can easily cut-and-paste it into your favourite Python editor to try it out.(I use SPE.) Don’t forget to put a copy of the image file (lunarlander.png) in the same folder as the code when you try to run it.

Here’s a screenshot: This is still a very simplified version.Here are some ways it could be enhanced:

• Add some animation or different sprites when the lander lands roughly or crashes
• Add different messages depending on how the landing was
• Add more terrain and multiple landing spots
• Have multiple turns, where it gets more difficult each time (steeper terrain, smaller landing spots, less fuel, etc.)

Enjoy!

```# LunarLander2d.py

# LunarLander 2D
# simulation game of landing a spacecraft

"""The original LunarLander in "Hello World! Computer Programmingfor Kids and Other Beginners"was a very simple 1-D (y-axis, vertical motion only)version of Lunar Lander, just to demonstrate a simplegravity simulaiton using Pygame.

LunarLander 2D adds the x-axis (sideways motion).You can now rotate the lander usingthe right/left arrow keys, more likethe original arcade version.It also changes the thrust control from amouse-based (dragging the slider) to key-based(press/hold the spacebar).

"""

# initialize - get everything ready
import pygame, sys
from math import *
pygame.init()
#wider screen than LunarLander, since we have sideways motion
screen = pygame.display.set_mode([800,600])
screen.fill([0, 0, 0])
ground  = 540   #landing pad is ay
y = 540
start = 90      # starting location 90 pixels from top of window
clock = pygame.time.Clock()
ship_mass = 5000.0
fuel = 5000.0
fps = 30
#defaultx_loc = 20  # x-location in meters = dist from center of landing pad
y_loc = 40  # y-location in meters = height above the landing pad

x_offset = 40    # offsets so that when ship location is (0,0), ship is
y_offset = -108  #   just touching landing pad, and centered.

x_speed = 2000.0   #pixels/framey_speed = -800.0

x_velocity = x_speed/(10.0*fps)  #m/s
y_velocity = y_speed/(10.0*fps)

gravity = 1.5
thrust = 0d
elta_v = 0
scale = 10   #scale factor from pixels to meters
throttle_down = False
left_down = False
right_down = False
#held_down = False
count = 0
x_pos = 0
y_pos = 0

"""x_pos, y_pos in pixels - (0,0) is top leftx_loc, y_loc in meters - (0,0) is center of landing pad

x_speed, y_speed in pixels per frame.x_velocity, y_velocity in m/s

scale factor = 10  (10 pixels = 1 meter)

pos (pixels) to loc (meters) is scaled by: scaleFactor (meters/pixel)speed (pixels/frame) to velocity (m/s) is scaled by:ScaleFactor * FPS (Frames Per Second)

"""

# sprite for the shipclass
ShipClass(pygame.sprite.Sprite):
def __init__(self, image_file, position):

pygame.sprite.Sprite.__init__(self)
self.image = self.imageMaster
self.rect = self.image.get_rect()
self.position = position
self.rect.centerx, self.rect.centery = self.position
self.angle = 0

def update(self):
self.rect.centerx, self.rect.centery = self.position
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imageMaster, self.angle)
self.rect = self.image.get_rect()
self.rect.center = oldCenter

# calcualte position, motion, acceleration, fuel
def calculate_velocity():
global ship, thrust, fuel, x_velocity, y_velocity, x_loc, y_loc
global tot_velocity, scale, x_pos, y_pos, x_speed, y_speed

delta_t = 1/fps

#Calculate thrust based on spacebar being held down
if throttle_down:
thrust = thrust + 100
if thrust > 1000:
thrust = 1000
else:
if thrust > 0:
thrust = thrust - 200
if thrust < 0:
thrust = 0
fuel -= thrust /(10 * fps)  # use up fuel
if fuel < 0:
fuel = 0.0
if fuel < 0.1:
thrust = 0.0
ythrust = thrust * cos(ship.angle*(pi/180))
xthrust = thrust * sin(ship.angle*(pi/180))
y_delta_v = delta_t * (-gravity + 50 * ythrust / (ship_mass + fuel))
y_velocity = y_velocity + y_delta_v
x_delta_v = delta_t * (-50 * xthrust/(ship_mass + fuel))
x_velocity = x_velocity + x_delta_v
x_speed = x_velocity * 10.0/fps   #speed in pixels/frame, velocity in m/s
y_speed = y_velocity * 10.0/fps
y_loc = y_loc + y_velocity/fps    # loc in meters, velocity in m/s
x_loc = x_loc - x_velocity/fps
tot_velocity = sqrt(x_velocity**2 + y_velocity**2)
ship.position = x_pos = screen.get_width()/2 - (scale * x_loc) + x_offset
ship.position = y_pos = screen.get_height() - (scale * y_loc) + y_offset

if right_down:
ship.angle = ship.angle - 2
if left_down:
ship.angle = ship.angle + 2
ship.update()

# display the text with the speed, height, etc.
def display_stats():
vv_str = "vertical speed:  %.1f m/s" % y_velocity    # in m/s
hv_str = "horizontal speed %.1f m/s" % x_velocity    # in m/s
tv_str = "Total Velocity %.1f m/s" % tot_velocity    # in m/s
h_str = "height:   %.1f m" % y_loc                   #in meters
x_str = "position: %.1f m" % x_loc
ang_str = "Angle: %.1f" % ship.angle
t_str = "thrust:   %i  kg" % thrust
a_str = "acceleration: %.1f m/s/s" % (delta_v * fps)
f_str = "fuel:  %i" % fuel

vv_font = pygame.font.Font(None, 26)  #vertical speed  vv_surf = vv_font.render(vv_str, 1, (255, 255, 255))  screen.blit(vv_surf, [10, 50])

hv_font = pygame.font.Font(None, 26)  #horizontal speed  hv_surf = hv_font.render(hv_str, 1, (255, 255, 255))  screen.blit(hv_surf, [10, 70])

hv_font = pygame.font.Font(None, 26)  #horizontal speed  hv_surf = hv_font.render(hv_str, 1, (255, 255, 255))  screen.blit(hv_surf, [10, 90])

h_font = pygame.font.Font(None, 26)   #y-location (height)  h_surf = h_font.render(h_str, 1, (255, 255, 255))  screen.blit(h_surf, [10, 120])

x_font = pygame.font.Font(None, 26)   #x_location  x_surf = x_font.render(x_str, 1, (255, 255, 255))  screen.blit(x_surf, [10, 150])

ang_font = pygame.font.Font(None, 26)  #angle  ang_surf = ang_font.render(ang_str, 1, (255, 255, 255))  screen.blit(ang_surf, [10, 180])

t_font = pygame.font.Font(None, 26)   #thrust  t_surf = t_font.render(t_str, 1, (255, 255, 255))  screen.blit(t_surf, [10, 210])

a_font = pygame.font.Font(None, 26)   #acceleration  a_surf = a_font.render(a_str, 1, (255, 255, 255))  screen.blit(a_surf, [10, 240])

f_font = pygame.font.Font(None, 26)   #fuel  f_surf = f_font.render(f_str, 1, (255, 255, 255))  screen.blit(f_surf, [60, 330])

# display the ship's flames - size depends on the amount of thrust
def display_flames():
fsize = thrust / 30  #flame size
# for rotation, need to do some trig to draw the flame triangles
#   in the correct orientation
# points a-f are the 6 vertices of the 2 flame triangles

# calcualte polar coordinates of un-rotated flames
a_len = sqrt(43*43 + 17*17)
b_len = sqrt( (43+fsize)*(43+fsize) + 13*13)
c_len = sqrt(43*43 + 9*9)
d_len = c_len
e_len = b_len
f_len = a_len
a_ang_orig = atan2(-17.0,-43.0)
b_ang_orig = atan2(-13.0,(-43-fsize))
c_ang_orig = atan2(-9.0,-43.0)
d_ang_orig = atan2(9.0,-43.0)
e_ang_orig = atan2(13.0,(-43.0-fsize))
f_ang_orig = atan2(17.0,-43.0)

# rotate flame by same angle as ship rotation
a_ang_rot = a_ang_orig + (270-ship.angle)*(pi/180)
b_ang_rot = b_ang_orig + (270-ship.angle)*(pi/180)
c_ang_rot = c_ang_orig + (270-ship.angle)*(pi/180)
d_ang_rot = d_ang_orig + (270-ship.angle)*(pi/180)
e_ang_rot = e_ang_orig + (270-ship.angle)*(pi/180)
f_ang_rot = f_ang_orig + (270-ship.angle)*(pi/180)

# calculate x-y coordinates of rotated flames
ax = int(a_len * cos(a_ang_rot))
ay = int(a_len * sin(a_ang_rot))
bx = int(b_len * cos(b_ang_rot))
by = int(b_len * sin(b_ang_rot))
cx = int(c_len * cos(c_ang_rot))
cy = int(c_len * sin(c_ang_rot))
dx = int(d_len * cos(d_ang_rot))
dy = int(d_len * sin(d_ang_rot))
ex = int(e_len * cos(e_ang_rot))
ey = int(e_len * sin(e_ang_rot))
fx = int(f_len * cos(f_ang_rot))
fy = int(f_len * sin(f_ang_rot))

#draw the flames
pygame.draw.polygon(screen, [255, 109, 14],
[(x_pos + ax, y_pos + ay),
(x_pos + bx, y_pos + by),
(x_pos + cx, y_pos + cy)], 0)
pygame.draw.polygon(screen, [255, 109, 14],
[(x_pos + dx, y_pos + dy),
(x_pos + ex, y_pos + ey),
(x_pos + fx, y_pos + fy)], 0)

# display final stats when the game is over
def display_final():
global x_velocity, y_velocity, tot_velocity, ship #, fuelbar
final1 = "Game over"
final2 = "You landed at:"
final3 = "%.1f m/s horizontal" % x_velocity
final4 = "%.1f m/s vertical" % y_velocity
final5 = "%.1f m/s Total" % tot_velocity
final6 = "%.1f degrees" % ship.angle
screen.fill([0, 0, 0])
pygame.draw.rect(screen, [0, 0, 255], [80, 350, 24, 100], 2)
pygame.draw.rect(screen, [0,255,0], [84,448-fuelbar,18, fuelbar], 0)
drawTerrain()
screen.blit(ship.image, ship.rect)

if (abs(tot_velocity) < 2 and abs(ship.angle) < 5):
final7 = "Nice landing!"
final8 = "I hear NASA is hiring!"
elif (abs(tot_velocity) < 55 and  abs(ship.angle < 10)):
final7 = "Ouch!  A bit rough, but you survived."
final8 = "You'll do better next time."
else:
final7 = "Yikes!  You crashed a 30 Billion dollar ship."
final8 = "How are you getting home?"
pygame.draw.rect(screen, [0, 0, 0], [5, 5, 350, 280],0)
f1_font = pygame.font.Font(None, 70)
f1_surf = f1_font.render(final1, 1, (255, 255, 255))
screen.blit(f1_surf, [20, 50])
f2_font = pygame.font.Font(None, 40)
f2_surf = f2_font.render(final2, 1, (255, 255, 255))
screen.blit(f2_surf, [20, 110])
f3_font = pygame.font.Font(None, 26)
f3_surf = f3_font.render(final3, 1, (255, 255, 255))
screen.blit(f3_surf, [20, 150])
f4_font = pygame.font.Font(None, 26)
f4_surf = f4_font.render(final4, 1, (255, 255, 255))
screen.blit(f4_surf, [200, 150])
f5_font = pygame.font.Font(None, 26)
f5_surf = f5_font.render(final5, 1, (255, 255, 255))
screen.blit(f5_surf, [380, 150])
f6_font = pygame.font.Font(None, 26)
f6_surf = f6_font.render(final6, 1, (255, 255, 255))
screen.blit(f6_surf, [20, 180])
f7_font = pygame.font.Font(None, 26)
f7_surf = f7_font.render(final7, 1, (255, 255, 255))
screen.blit(f7_surf, [20, 210])
f8_font = pygame.font.Font(None, 26)
f8_surf = f8_font.render(final8, 1, (255, 255, 255))
screen.blit(f8_surf, [20, 240])
screen.blit(inst1_surf, [50, 550])
screen.blit(inst2_surf, [20, 575])
screen.blit(inst3_surf, [50, 600])
pygame.display.flip()

# note:  This could be made more complex, but just a
#          simple landing pad for now.
def drawTerrain():
pygame.draw.rect(screen, [180, 180, 180], [420, 535, 70, 5],0)

# make an instance of the ship sprite
ship = ShipClass('lunarlander.png', [500, 230])

# main loop
while True:
clock.tick(30)
fps = clock.get_fps()
if fps < 1:
fps = 30
count += 1
if y_loc > 0.01:
calculate_velocity()
screen.fill([0, 0, 0])
display_stats()
pygame.draw.rect(screen, [0, 0, 255], [80, 350, 24, 100], 2)
fuelbar = 96 * fuel / 5000
pygame.draw.rect(screen, [0,255,0], [84,448-fuelbar,18, fuelbar], 0)
drawTerrain()
display_flames()
screen.blit(ship.image, ship.rect)
instruct1 = "Land softly without running out of fuel"
instruct2 = "Good landing: < 5 m/s    Great landing:  < 2m/s"
instruct3 = "And you must be within 10 degrees of vertical"
inst1_font = pygame.font.Font(None, 24)
inst1_surf = inst1_font.render(instruct1, 1, (255, 255, 255))
screen.blit(inst1_surf, [50, 550])
inst2_font = pygame.font.Font(None, 24)
inst2_surf = inst1_font.render(instruct2, 1, (255, 255, 255))
screen.blit(inst2_surf, [20, 575])
inst3_font = pygame.font.Font(None, 24)
inst3_surf = inst3_font.render(instruct3, 1, (255, 255, 255))
screen.blit(inst3_surf, [50, 600])
pygame.display.flip()

else:  #game over - print final score
display_final()

for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
throttle_down = True
if event.key == pygame.K_RIGHT:
right_down = True
if event.key == pygame.K_LEFT:
left_down = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
throttle_down = False
if event.key == pygame.K_RIGHT:
right_down = False
if event.key == pygame.K_LEFT:
left_down = False```