Linux Format

Coding Lunar Lander

Calvin Robinson uses Python to create three examples of the legendary Lunar Lander game – text-based, vector-based and complete with GUI.

-

Calvin Robinson uses Python to create three examples of the legendary Lunar

Lander game – text-based, vector-based and complete with GUI.

In this new Python series we’re going to be developing classic video games using contempora­ry techniques. This issue we’re kicking things off with the legendary Lunar Lander.

Lunar Lander games are a genre originatin­g from the original Atari back in 1979, and are one of the oldest video game genres. The player controls a lander spacecraft and attempts to land the spacecraft by controllin­g the thrusters, while monitoring forces and fuel levels, with the game round ending in either a crash or a successful landing, most commonly the former. Points can be given for time and precision of landing.

The game world is black and white with vector graphics, displaying the environmen­t and lander module in a 2D environmen­t. We need to set up measuremen­ts for our lander’s fuel levels, speed and altitude. Our player will need a way of controllin­g the thrusters in upwards and left and right directions to steer the module.

Before we program the graphics it’s a good idea to get our head around the maths. For that reason, we’re going to program a text-based Lunar Lander first.

Let’s start by setting up some variables for our approach speed, gravity level, amount of fuel, altitude above the surface of the Moon, and initial burn rate. speed = 30;gravity = 1.622;fuel = 1500;altitude = 1000;burn = 0 Now we’ll ask the user for burn rates and calculate the speed and altitude accordingl­y, not forgetting to take into considerat­ion gravity. If our pilot burns all the fuel the rocket will gravitate towards the Moon. while altitude > 0: if speed <= 0:

impact = 1000 else:

impact = altitude / speed print(“altitude={:8.3f} Speed={:6.3f} Fuel={:8.3f} Impact={:6.3f} Previous burn={:6.3f}”.format(altitude,sp eed,fuel,impact,burn))

burn = float(input(“enter an amount of fuel to burn between 0 and 50: “)) if burn < 0:

burn = 0 if burn > 50:

burn = 50 if burn > fuel:

burn = fuel altitude -= speed speed += gravity - burn/10 fuel -= burn

Specifying .3f and using .format{} enables us to be more precise with our output, sticking to three decimal places. Integers provide no decimal places, so they wouldn’t be helpful for this use case, while floats are generally inefficien­t for this level of calculatio­n. We might see two decimal places for one number and three for another. To avoid inconsiste­ncies we’ll set all printed data to display three numbers after the decimal point.

We’re running on a loop to get constant updates from our user and updating the flight informatio­n. We also have protection­s to ensure that the rate of fuel burning can’t exceed the amount of fuel remaining. This loop can conclude with two possible outcomes; either we crash our module or successful­ly land on the Moon. print(“altitude={:8.3f} Speed={:6.3f} Fuel={:8.3f} Last burn={:6.3f}”.format(altitude,speed,fuel,burn)) if altitude <- 5 or speed > 5:

print(“you have crashed.“) else:

print(“you have successful­ly landed.“)

We’ve set the altitude limit to 5, as our pilot should control the speed to below 5m/s before landing. If the module hits the surface with a speed greater than that, it’s classed as a crash. Likewise, if the lunar module attempts to land 5m or more below the surface, it will count as a crash. Anything else is considered a success.

Our text-based Lunar Lander is fun, but it’s time to take things to the next level by introducin­g some 2D vector graphics. Before we do, we’ll need to ensure our Python setup includes a few modules. Python can be a little tricky, so make sure you’ve only got one version of Python 3 installed, or set up a virtual environmen­t with

virtualenv. Install Pygame and Pyaudio:

$ Pip3 install pygame

$ Pip3 install pyaudio

If you experience difficulti­es installing Pyaudio, as we did, you may have to install it manually. First install portaudio, then grab the source code for Pyaudio:

$ sudo apt-get install portaudio1­9-dev

$ wget Pyaudio-0.2.11.tar.gz

$ tar -zxvf Pyaudio-0.2.11.tar.gz

$ cd Pyaudio

$ sudo python3 setup.py install

Assuming everything is working, we may begin by including these modules in our new Python file:

import pygame, sys, pyaudio, array from random import * from pygame import * from math import *

Next, initiate the variables similar to the text-based version. We’ll need a game loop to keep things running, and set up events for each key press. We’ll use the Left and Right arrow keys for the appropriat­e side thrusters and Down to control the vertical thruster. We’ll keep our display strings short (Fuel, Alt, Vertical and Horizontal Speed) in line with the small window size.

for i in range(mn+1):

mx.append(z*i);my.append(int(randint(mh,0)+am*(4-sin((i+ph)/5.)))-fs) mx.append(s);my.append(randint(smh,s));mx[pl]=mx[pl-1]+lp;my[pl]=my[pl-1] while dn == False: for event in pygame.event.get(): if event.type==quit:dn=true if event.type==keydown: if event.key==k_escape:dn=true if event. key==k_r:x=randint(z,z);y=z;u=v=0;r=5;cg=w;wi=n;f=s;gs=c if event.key==k_down and f>0:v=v-a;f=f-5;cl=w;cr =w;ss=se if event.key==k_left and f>0:u=u+a;f=f5;cl=w;ss=se if event.key==k_right and f>0:u=u-a;f=f5;cr=w;ss=se if gs==c and (x<0 or x>s):x=x-(abs(x)/x)*s if gs==c:v=v+1;x=(10*x+u)/10;y=(10*y+v)/10 if (y+8)>=my[pl] and x>mx[pl-1] and x=x and (my[i]<=y or my[i+1]<=y):

... //see code file TXT=’FUEL %3d ALT %3d VERT SPD %3d HORZ

SPD %3d’%(f,s-y,v,u) sp=ft.render(txt,0,w);sc.blit(sp,(0,s12));cl=b;cr=b;stream.write(ss) for i in range(mn):draw.line(sc,w,(mx[i],my[i]),(mx[i+ 1],my[i+1])) sp=ft.render(gs,0,w);sc.blit(sp,(s/3,s/2));display. flip();clk.tick(5);ss=c

Don’t forget to close everything off at the end with a terminatio­n, quit and close:

pygame.quit();stream.close();pa.terminate()

Having created a text-based lunar lander, followed by a basic visual game with Pygame, let’s design one with a graphical user interface. This time we have far fewer modules to import, but we need to initiate our variables:

from tkinter import * import time

GRAVITY = 0.0005

ENGINE_POWER = -0.00003 THRUSTER_POWER = 0.00001 MAXIMUM_ENGINE_POWER = -0.002 MAXIMUM_THRUSTER_POWER = 0.0001

We’re using tkinter as our GUI module, as it’s quite accessible as far as Python GUIS go. To use tkinter properly we’ll need to develop this game using objectorie­nted programmin­g. OOP provides a class/object structure that makes adapting and updating the game so much easier in the long run.

class Game: def __init__(self): self.tk = Tk() self.tk.title(“lander”) self.tk.resizable(0, 0) self.tk.wm_attributes(“-topmost”, 1) self.canvas = Canvas(self.tk, width=700, height=500, highlightt­hickness=0) self.canvas.pack() self.canvas.focus_set() self.tk.update() self.canvas_height = 500 self.canvas_width = 1000 self.sprites = [] self.running = True def mainloop(self): while 1: if self.running == True: for sprite in self.sprites:

sprite.move() self.tk.update_idletasks() self.tk.update() time.sleep(0.01)

Our primary class Game has two functions, __init__ and mainloop. The former initiates everything we’ll need to use setting up our game window; Title is the window title, canvas the variables for the dimensions of that window. Pack is a tkinter command to create the window, and focus_set makes sure this window takes focus. The latter function, mainloop, is the main loop that ensures everything keeps running. Here, it continuous­ly displays our sprites. Create a class for our

Lunar Lander, with an init function to boot: class Lander: def __init__(self, game): self.canvas = game.canvas self.id = self.canvas.create_rectangle(340, 0, 360, 20) self.time = time.time() self.x = 0 self.y = 0.01 self.engine_y = 0 self.engine_x = 0 self.fuel = 2000 self.down = False self.left = False self.right = False game.canvas.bind_all(‘’, self. engine_down) game.canvas.bind_all(‘’, self. engine_left) game.canvas.bind_all(‘’, self. engine_right) self.engine_y_text = self.canvas.create_text(0, 10, text=’main Engine: off’, anchor = ‘nw’) self.engine_x_text = self.canvas.create_text(0, 30, text=’thrusters: off’, anchor = ‘nw’) self.engine_fuel_text = self.canvas.create_text(0, 50, text=’fuel: %s’ % self.fuel, anchor = ‘nw’)

This is our first class with an argument parameter (self, game) , meaning we can assign an object of our Game class. We’ve set up some object variables, too. Canvas stores the game’s canvas details we set up; ID

is the unique identifier of our lunar lander module, which in this case is a rectangle for the sake of simplicity; Time will monitor the current time, once we’ve set it up; X and Y are movement values to control our rectangle; Engine_x and Engine_y will control the horizontal and vertical engine power respective­ly; Down, Left and Right will be toggles for our engine, so we know if the thrusters are active or not, depending on when the keys are pressed. We could have just as easily used WASD. The _text options display the current variables on our display so that we know how the engine and thrusters are currently toggled, and how much fuel remains. To do this, we set up a function for each: def engine_down(self, evt): if self.down: self.down = False self.engine_y = 0 self.canvas.itemconfig(self.engine_y_text, text=’main Engine: off’) else: self.down = True self.canvas.itemconfig(self.engine_y_text, text=’main Engine: on’) def engine_left(self, evt): if self.right or self.left: self.left = False self.right = False self.engine_x = 0 self.canvas.itemconfig(self.engine_x_text, text=’thrusters: off’) else: self.left = True self.canvas.itemconfig(self.engine_x_text, text=’thrusters: left’)

...

Since we’re toggling the engine thrusters on/off this time, we’ll need to use Boolean logic, setting variables to True/false to control the movement of our lander module. We’ll set the variable to True when the appropriat­e function is active and False when it’s not. Pressing the buttons we assigned in the previous function will activate/deactivate these Booleans. We’ll also change the canvas text we set up earlier to reflect the new status of our thrusters and/or engine.

Finally, we need to design a function to actually implement the movement, following these button presses and activation­s:

def move(self): if self.canvas.coords(self.id)[3] >= 500: if self.y > 0.5:

print(’ You have crashed.‘) else:

print(’ You have successful­ly landed.‘) return now = time.time() time_since_last = now - self.time if time_since_last > 0.1:

if self.down and self.engine_y > MAXIMUM_

ENGINE_POWER: self.engine_y += ENGINE_POWER if self.left and self.engine_x < MAXIMUM_

THRUSTER_POWER: self.engine_x += THRUSTER_POWER elif self.right and self.engine_x > -MAXIMUM_

THRUSTER_POWER:

self.engine_x -= THRUSTER_POWER if self.down:

self.fuel -= 2 if self.left or self.right:

self.fuel -= 1 if self.fuel < 0:

self.fuel = 0 self.canvas.itemconfig(self.engine_fuel_text, text=’fuel: %s’ % self.fuel) if self.fuel <= 0: self.engine_y = 0 self.engine_x = 0 self.y = self.y + (time_since_last * GRAVITY) + (time_since_last * self.engine_y) self.x = self.x + (time_since_last * self.engine_x) self.canvas.move(self.id, self.x, self.y)

We’ve implemente­d some very basic collision detection. Like the last version of our game, if you come in too fast you’re going to crash, but if you land on the target with a speed of 5m/s or less you’ll win the game.

We store the time in a variable called now. We can use this to calculate the time difference between actions taking place. We can subtract now from time and see how long something took. We have an if

function set up to see if time is moving (by measuring if it has changed by more than a tenth of a second).

Here we monitor key presses and work out our engine power. When our engine reaches the maximum engine power or maximum thruster power we add or subtract to it, accordingl­y. For example, if the value in our engine_y variable is greater than maximum_ engine_power we’ll add engine_power to engine_y.

That means if we launch the game and tap the Right key, our program will need to add a value to engine_y as

soon as Right becomes True, because engine_y started on 0. That’s how we get some movement started with Down, Left or Right. The same would be the case with engine_x and maximum_thruster_power.

As soon as we begin the movement calculatio­ns we’ll also need to take into account the amount of fuel being burnt. The moment Down, Left, Right are pressed we’re giving the engine more power and emptying the fuel reserves. We’re subtractin­g by two each time. Throughout all of this we’re updating our on-screen text to display the latest values from our variables.

Towards the end of the move function we’ve put a logic safeguard in place. If the fuel ever reaches a number below zero we reset it back to zero. If/when the fuel tank teaches zero we turn our engines and thrusters off, switching engine_y and engine_x to 0. Then we work out the new position of the lunar lander. To find our x value we take our time_since_last value to calculate the time taken since the function was last run and multiply it against engine_x, before adding it to our x

value. This gives the illusion of momentum, as our lunar lander will move even after shutting off the power. For our y value we also need to take gravity into considerat­ion, which is why we also multiply Gravity by time_since_last before adding it to y. After each calculatio­n we update our display with canvas.move to put these changes into effect. Our final class is the Platform. cwlass Platform: def __init__(self, game): self.canvas = game.canvas self.id = self.canvas.create_rectangle(600, 480, 650, 490)

Finally, create new instances of our objects to spawn in our game world and display as sprites on our screen. g = Game() lander = Lander(g) platform = Platform(g) g.sprites.append(lander) g.mainloop()

Experiment, extend and generally play with the code!

 ??  ?? An exciting crash in text-based Lunar Lander.
An exciting crash in text-based Lunar Lander.
 ??  ?? OUR EXPERT Calvin Robinson is a computer science teacher, former deputy headteache­r and currently consulting as a subject matter expert for the National Centre for Computing Education.
OUR EXPERT Calvin Robinson is a computer science teacher, former deputy headteache­r and currently consulting as a subject matter expert for the National Centre for Computing Education.
 ??  ?? The game will end with either a crash or a successful landing, depending upon the lander’s speed at impact.
The game will end with either a crash or a successful landing, depending upon the lander’s speed at impact.
 ??  ?? Making a landing within the game.
Making a landing within the game.
 ??  ?? Our text-based Lunar Lander.
Our text-based Lunar Lander.

Newspapers in English

Newspapers from Australia