4
\$\begingroup\$

Here is my code about our final project in uni called 'Mars Lander'. The thing is that my code seems to be all over the place (more specifically in the 'main' function at the end of the code...) therefore I would like to get some help with putting things in place! It's my first time making a game using the 'pygame' library and this one was definitely worth the effort!

import pygame
import sys
from random import uniform, randint # used for the random starting velocity of the lander, random clock time etc.
from time import clock # to show the time elapsed
import math # used to calculate the magnitude of the gravity force applied on the lander
WIDTH = 1200 # width of the game window
HEIGHT = 750 # height of the game window
FPS = 20 # frames per second
pause = False # variable which is used to determine whether the game is paused or not
# Initialise pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock_game = pygame.time.Clock()
pygame.font.init() # you have to call this at the start if you want to use this module.
myfont = pygame.font.SysFont('Comic Sans MS', 15)
alert_large = pygame.font.SysFont("Comic Sans MS", 18)
large_text = pygame.font.SysFont("Comic Sans MS", 50)
class Background(pygame.sprite.Sprite): # class for the background image
 def __init__(self, image_file, location):
 pygame.sprite.Sprite.__init__(self) # call Sprite initializer
 self.image = pygame.image.load(image_file)
 self.rect = self.image.get_rect()
 self.rect.left, self.rect.top = location # the location of the image should be inputted as a tuple (x,y)
 # where x is the left side position of the image whereas y is the top side position
# obstacles landing pad meteors classes
class Lander(pygame.sprite.Sprite): # class for the lander image
 def __init__(self, image_file, location):
 pygame.sprite.Sprite.__init__(self)
 self.image = pygame.image.load(image_file)
 self.rect = self.image.get_rect()
 self.rect.left, self.rect.top = location # the location of the image should be inputted as a tuple (x,y)
 # where x is the left side position of the image whereas y is the top side position
 self.rot_image = self.image
 self.angle = 0
 self.veloc_y = uniform(0.0, 1.0)
 self.veloc_x = uniform(-1.0, 1.0)
 self.fuel = 500
 self.altitude = 0
 self.damage = 0
 def free_fall(self):
 self.rect.y += self.veloc_y
 self.rect.x += self.veloc_x
 self.veloc_y += 0.1
 def reset_stats(self):
 self.rect.top = 0
 self.veloc_y = uniform(0.0, 1.0)
 self.veloc_x = uniform(-1.0, 1.0)
 self.fuel = 500
 self.angle = 0
 self.damage = 0
 self.rot_image = pygame.transform.rotate(self.image, self.angle)
 def check_boundaries(self):
 if self.rect.top < 0:
 self.rect.top = 0
 self.veloc_y = uniform(0.0, 1.0)
 if self.rect.right < 0:
 self.rect.left = WIDTH
 if self.rect.left > WIDTH:
 self.rect.right = 0
 if self.rect.bottom > HEIGHT:
 self.reset_stats()
 self.rect.left = randint(0, 1123)
 return True
 else:
 return False
 def get_fuel(self):
 return self.fuel
 def burn_fuel(self): # decreases the fuel when 'space' key is pressed
 self.fuel -= 5
 def start_engine(self):
 self.burn_fuel()
 self.veloc_x = self.veloc_x + 0.33 * math.sin(math.radians(-self.angle))
 self.veloc_y = self.veloc_y - 0.33 * math.cos(math.radians(self.angle))
 def rotate_left(self):
 self.angle += 1 % 360
 self.rot_image = pygame.transform.rotate(self.image, self.angle)
 def rotate_right(self):
 self.angle -= 1 % 360
 self.rot_image = pygame.transform.rotate(self.image, self.angle)
 def to_ground(self):
 self.altitude = 1000 - self.rect.top*1.436
 return self.altitude
 def get_damage(self):
 return self.damage
 def check_landing(self, pad):
 check_velocity_y = [True if self.veloc_y < 5 else False]
 check_velocity_x = [True if -5 < self.veloc_x < 5 else False]
 check_angle = [True if -7 <= self.angle <= 7 else False]
 check_above_pad = [True if (self.rect.left > pad.rect.left and self.rect.right < pad.rect.right) else False]
 check_touch = [True if (self.rect.bottom == pad.rect.top) else False]
 if check_above_pad[0] and check_angle[0] and check_velocity_x[0] and check_velocity_y[0] and check_touch[0]:
 return True
 else:
 return False
class EngineThrust(pygame.sprite.Sprite): # class for the thrust image
 def __init__(self, image_file, location):
 pygame.sprite.Sprite.__init__(self) # call Sprite initializer
 self.image = pygame.image.load(image_file)
 self.rot_image = self.image
 self.rect = self.image.get_rect()
 self.rect.left, self.rect.top = location # the location of the image should be inputted as a tuple (x,y)
 # where x is the left side position of the image whereas y is the top side position
 self.thrst_angle = lndr.angle
 def rotate_thrust(self):
 self.rot_image = pygame.transform.rotate(self.image, self.thrst_angle)
class LandingPad(pygame.sprite.Sprite): # class for the landing pad image
 def __init__(self, image_file, location):
 pygame.sprite.Sprite.__init__(self) # call Sprite initializer
 self.image = pygame.image.load(image_file)
 self.rect = self.image.get_rect()
 self.rect.left, self.rect.top = location # the location of the image should be inputted as a tuple (x,y)
 # where x is the left side position of the image whereas y is the top side position
class GameScore: # class for the score of the game
 def __init__(self):
 self.score = 0
 def successful_land(self):
 self.score += 50
 def get_score(self): # Returns game score
 return self.score
class Lives: # class for the lives of the player
 def __init__(self, lives):
 self.lives = lives # Holds lives left
 def crashed(self): # Decrement lives by 1
 self.lives -= 1
 def get_lives(self): # Return lives number
 return self.lives
 def game_over(self): # Check if there are no lives left
 return self.lives == 0
class SysFailure: # class for the lander system errors
 def __init__(self):
 self.random_alert = 0 # Will carry alert time
 self.random_key = 0 # Will holds key value
 def get_alert(self): # Set a new alert time and return it
 self.random_alert = randint(int(clock()+5), int(clock() + 15))
 return self.random_alert
 def get_key(self): # Randomize and return key value
 self.random_key = randint(1, 3)
 return self.random_key
class Obstacle(pygame.sprite.Sprite): # class for the obstacle images
 def __init__(self, image_file, location):
 pygame.sprite.Sprite.__init__(self) # call Sprite initializer
 self.image = pygame.image.load(image_file)
 self.rect = self.image.get_rect()
 self.rect.left, self.rect.top = location # the location of the image should be inputted as a tuple (x,y)
 # where x is the left side position of the image whereas y is the top side position
 self.destroyed = False
 def get_status(self): # Return the status of the obstacle
 return self.destroyed
 def obstacle_collision(self, lander): # Increment lander damage by 10 % if the meteor collides with the lander
 if lander.rect.colliderect(self.rect):
 lander.damage += 10
 return True
 else:
 return False
class Meteor(pygame.sprite.Sprite): # Class for the meteor images
 def __init__(self, image_file, location):
 pygame.sprite.Sprite.__init__(self) # Call Sprite initializer
 self.image = pygame.image.load(image_file)
 self.rect = self.image.get_rect()
 self.rect.left, self.rect.bottom = location # The location of the image should be inputted as a tuple (x,y)
 # where x is the left side position of the image whereas y is the top side position
 self.speed_y = uniform(5, 10)
 self.speed_x = uniform(-2, 2)
 self.destroyed = False
 def storm_fall(self): # Set y and x-axis speed of the meteor
 self.rect.x += self.speed_x
 self.rect.y += self.speed_y
 def meteor_collision(self, lander): # Increment lander damage by 25 % if the meteor collides with the lander
 if lander.rect.colliderect(self.rect):
 lander.damage += 25
 return True
 else:
 return False
 def get_status(self): # Return the status of the meteor
 return self.destroyed
 def reset_stats(self): # Set the bottom of the sprite to its initial value
 self.rect.bottom = 0
class Storm: # Class for the meteor storms
 def __init__(self):
 self.random_storm = 0 # Holds random storm time
 def storm_time(self): # Randomize and return storm time
 self.random_storm = randint(int(clock()+3), int(clock() + 12))
 return self.random_storm
def resume(): # Resume the game
 global pause
 pause = False
def paused(): # Pause the game
 global game_status
 crash_msg = large_text.render('You Have Crashed!', False, (255, 0, 0))
 screen.blit(crash_msg, (420, 300)) # Display crash message in the middle of the screen
 while pause:
 for event in pygame.event.get():
 if event.type == pygame.QUIT: # Quit the game if the 'X' button is clicked
 sys.exit()
 if event.type == pygame.KEYDOWN: # Wait for a key to be pressed and if so resumes the game
 resume()
 pygame.display.update()
 clock_game.tick(FPS)
obstacles = pygame.sprite.Group() # Create obstacle sprite group
meteors = pygame.sprite.Group() # Create meteor sprite group
bckgd = Background('mars_background_instr.png', [0, 0])
lndr = Lander('lander.png', [randint(0, 1123), 0])
pad_1 = LandingPad('pad.png', [randint(858, 1042), 732])
pad_2 = LandingPad('pad_tall.png', [randint(458, 700), 620])
pad_3 = LandingPad('pad.png', [randint(0, 300), 650])
"""
Create 5 obstacles each being placed on a fixed location
on the background image!
"""
obstacle_1 = Obstacle('pipe_ramp_NE.png', [90, 540])
obstacle_2 = Obstacle('building_dome.png', [420, 575])
obstacle_3 = Obstacle('satellite_SW.png', [1150, 435])
obstacle_4 = Obstacle('rocks_ore_SW.png', [1080, 620])
obstacle_5 = Obstacle('building_station_SW.png', [850, 640])
# Add to the sprite group 'obstacles'
obstacles.add(obstacle_1, obstacle_2, obstacle_3, obstacle_4, obstacle_5)
""" 
Create 10 meteors using the Meteor class which are placed
at random x-axis locations starting with the bottom of the image rectangle 
lying at 0 on the y-axis! 
"""
meteor1 = Meteor('spaceMeteors_1.png', [randint(300, 900), 0])
meteor2 = Meteor('spaceMeteors_2.png', [randint(300, 900), 0])
meteor3 = Meteor('spaceMeteors_3.png', [randint(300, 900), 0])
meteor4 = Meteor('spaceMeteors_4.png', [randint(300, 900), 0])
meteor5 = Meteor('spaceMeteors_1.png', [randint(300, 900), 0])
meteor6 = Meteor('spaceMeteors_2.png', [randint(300, 900), 0])
meteor7 = Meteor('spaceMeteors_1.png', [randint(300, 900), 0])
meteor8 = Meteor('spaceMeteors_4.png', [randint(300, 900), 0])
meteor9 = Meteor('spaceMeteors_1.png', [randint(300, 900), 0])
meteor10 = Meteor('spaceMeteors_3.png', [randint(300, 900), 0])
# Add to the sprite group assigned to the 'meteors' variable
meteors.add(meteor1, meteor2, meteor3, meteor4, meteor5, meteor6, meteor7, meteor8, meteor9, meteor10)
storm = Storm() # Storm variable
lives_left = Lives(3) # Each time the player starts with 3 lives
lander_score = GameScore() # Holds the score of the game
alert_signal = SysFailure() # Holds the lander system failure causes
game_status = True # Holds the status of the game
def main(): # The main function which runs the game
 global game_status, pause # change name
 random_signal = alert_signal.get_alert() # Holds the randomized alert signal time
 random_key = alert_signal.get_key() # Carries the randomized key used to decide which control failure will occur
 # during the alert signal
 random_storm = storm.storm_time() # Random meteor storm time
 meteor_storm = False # Set to True whenever a storm should occur
 meteor_shower = False # Set to True whenever a storm should occur
 print(random_storm)
 meteor_number = randint(1, 10) # Determines the number of meteors the storm will contain
 print(meteor_number)
 while game_status: # main game loop
 clock_game.tick(FPS)
 screen.fill([255, 255, 255]) # Fill the empty spaces with white color
 screen.blit(bckgd.image, bckgd.rect) # Place the background image
 screen.blit(pad_1.image, pad_1.rect) # Put the first landing pad on the background
 screen.blit(pad_2.image, pad_2.rect) # Put the second landing pad on the background
 screen.blit(pad_3.image, pad_3.rect) # Put the last landing pad on the background
 for obstacle in obstacles: # draw every one of the obstacles
 # if a collision occurs the obstacle gets destroyed and it is no longer shown
 if not obstacle.get_status():
 screen.blit(obstacle.image, obstacle.rect)
 if obstacle.obstacle_collision(lndr):
 obstacle.destroyed = True
 # Waits for an event
 for event in pygame.event.get(): # If the user clicks the 'X' button on the window it quits the program
 if event.type == pygame.QUIT:
 sys.exit()
 pressed_key = pygame.key.get_pressed() # Take pressed key value
 if not meteor_storm: # As soon as the clock passes the random storm time it causes meteor rain
 if clock() > random_storm:
 meteor_storm = True
 meteor_shower = True
 if meteor_shower:
 delay = 0 # Each meteor is drawn with 1 second delay
 count = 0 # Counts the meteors number
 for meteor in meteors:
 if count < meteor_number:
 delay += 1
 if clock() > random_storm + delay:
 if not meteor.get_status(): # Draw every one of the meteors
 # if a collision occurs the meteor gets destroyed and it is no longer shown
 meteor.storm_fall() # Give x-axis and y-axis velocity to the meteors
 screen.blit(meteor.image, meteor.rect)
 if meteor.meteor_collision(lndr):
 meteor.destroyed = True
 count += 1
 if pressed_key[pygame.K_ESCAPE]: # Stop game if the 'Esc' button is pressed
 game_status = False
 elif lives_left.game_over(): # Terminate program if the player has no lives left
 game_status = False
 elif lndr.get_fuel() <= 0: # Remove lander controls if it is out of fuel
 screen.blit(lndr.rot_image, lndr.rect)
 else:
 if not random_signal < clock() < random_signal+2: # While the clock is not in the 2 sec alert time
 if lndr.get_damage() < 100: # While the lander hasn't sustained 100% damage
 if pressed_key[pygame.K_SPACE]: # Show thrust image when 'space' is pressed
 thrst = EngineThrust('thrust.png', [lndr.rect.left+31, lndr.rect.bottom-10]) # Create thrust
 # sprite
 lndr.start_engine() # Call 'start engine' function (Lander)
 thrst.rotate_thrust() # Call 'rotate_engine' which rotates the thrust along with the lander
 screen.blit(thrst.rot_image, thrst.rect)
 pygame.display.update()
 if pressed_key[pygame.K_LEFT]: # Rotate lander anticlockwise when 'left' is pressed
 lndr.rotate_left()
 if pressed_key[pygame.K_RIGHT]: # Rotate lander clockwise when 'left' is pressed
 lndr.rotate_right()
 if lndr.check_landing(pad_1) or lndr.check_landing(pad_2) or lndr.check_landing(pad_3): # Call
 # 'check_landing' method on each pad sprite which checks whether the lander has landed on
 # the landing pad
 lander_score.successful_land() # Increment score with 50 pts
 for meteor in meteors:
 meteor.destroyed = False
 meteor.reset_stats()
 random_signal = alert_signal.get_alert()
 random_key = alert_signal.get_key()
 random_storm = storm.storm_time()
 meteor_number = randint(1, 10)
 print(random_storm)
 print(meteor_number)
 meteor_shower = False
 meteor_storm = False
 for obstacle in obstacles:
 obstacle.destroyed = False
 lndr.reset_stats()
 else:
 lndr.damage = 100 # Stop lander damage at 100 %
 else:
 alert_msg = alert_large.render('*ALERT*', False, (0, 0, 255))
 screen.blit(alert_msg, (190, 80)) # Display alert message
 if random_key == 1:
 if pressed_key[pygame.K_SPACE]: # Show thrust image when 'space' is pressed
 thrst = EngineThrust('thrust.png', [lndr.rect.left + 31, lndr.rect.bottom - 10])
 lndr.start_engine()
 thrst.rotate_thrust()
 screen.blit(thrst.rot_image, thrst.rect)
 pygame.display.update()
 if pressed_key[pygame.K_LEFT]: # Rotate lander anticlockwise when 'left' is pressed
 lndr.rotate_left()
 elif random_key == 2:
 if pressed_key[pygame.K_LEFT]: # Rotate lander anticlockwise when 'left' is pressed
 lndr.rotate_left()
 if pressed_key[pygame.K_RIGHT]: # Rotate lander clockwise when 'right' is pressed
 lndr.rotate_right()
 else:
 if pressed_key[pygame.K_SPACE]: # Show thrust image when 'space' is pressed
 thrst = EngineThrust('thrust.png', [lndr.rect.left + 31, lndr.rect.bottom - 10])
 lndr.start_engine()
 thrst.rotate_thrust()
 screen.blit(thrst.rot_image, thrst.rect)
 pygame.display.update()
 if pressed_key[pygame.K_RIGHT]: # Rotate lander clockwise when 'right' is pressed
 lndr.rotate_right()
 screen.blit(lndr.rot_image, lndr.rect)
 time_passed = myfont.render('{:.1f} s'.format(clock()), False, (255, 0, 0))
 screen.blit(time_passed, (72, 10)) # Display clock in seconds
 velocity_y = myfont.render('{:.1f} m/s'.format(lndr.veloc_y), False, (255, 0, 0))
 screen.blit(velocity_y, (280, 56)) # Display y-axis velocity (downward, meters per second)
 velocity_x = myfont.render('{:.1f} m/s'.format(lndr.veloc_x), False, (255, 0, 0))
 screen.blit(velocity_x, (280, 33)) # Display x-axis velocity (sideways, meters per second)
 fuel_remaining = myfont.render('{:d} kg'.format(lndr.fuel), False, (255, 0, 0))
 screen.blit(fuel_remaining, (72, 33)) # Display remaining fuel in kg
 altitude = myfont.render('{:.0f} m'.format(lndr.to_ground()), False, (255, 0, 0))
 screen.blit(altitude, (280, 10)) # Display altitude in meters
 lander_damage = myfont.render('{} %'.format(lndr.get_damage()), False, (255, 0, 0))
 screen.blit(lander_damage, (95, 56)) # Display damage suffered by the mars lander
 game_score = myfont.render('{:.0f} pts'.format(lander_score.get_score()), False, (255, 0, 0))
 screen.blit(game_score, (77, 82)) # Display altitude in meters
 lndr.free_fall() # Call 'free_fall' method in class 'Lander'
 if lndr.check_boundaries(): # Call 'check_boundaries' method located in 'Lander' class
 for meteor in meteors:
 meteor.destroyed = False
 meteor.reset_stats()
 random_signal = alert_signal.get_alert() # Get a new random time for the alert
 random_key = alert_signal.get_key() # Get a new random key for the lander control failure
 random_storm = storm.storm_time() # Get a new random time for the storm
 meteor_number = randint(1, 10) # Get a new random number for the meteors
 lives_left.crashed() # Reduce lives with 1
 print(random_storm)
 print(meteor_number)
 meteor_shower = False
 meteor_storm = False
 for obstacle in obstacles: # Reset all obstacles and make them visible
 obstacle.destroyed = False
 pause = True # Set 'pause' to True so the game pauses when 'paused' method is called
 paused() # Call 'paused'
 pygame.display.update() # Refresh (update) display
 pygame.quit() # quit game if game_status = False
main()
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Apr 15, 2018 at 17:43
\$\endgroup\$
2
  • \$\begingroup\$ Could you upload the images somewhere, please? We can't run the code without the assets. \$\endgroup\$ Commented Apr 17, 2018 at 9:47
  • \$\begingroup\$ Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2 \$\endgroup\$ Commented Apr 19, 2018 at 12:04

1 Answer 1

2
\$\begingroup\$

You try to extract behaviour into classes, which is a good idea, but you failed at recognising patterns that could be abstracted by a single class and created way too much classes for the same thing: a Sprite set at certain coordinates; heck, even your LandingPad and Background classes are exactly the same, that should have raised a red flag.

The same goes with your Lives and GameScore classes which could be a simple integer as they add nothing more.

You should also avoid globals and code at top-level. They, however, are better in an __init__ of some class or other. Thus I’d create a MarsLanding class to hold that and the main function. This would help for refactoring, also.

Lastly, you perform drawing, sprites updates and collision detection manually but pygame allows to automate it through groups. See for instance the spritecollide function or the Group.draw method.

Proposed improvements follows:

import math
from time import clock
from random import uniform, randint, choice
import pygame
def init():
 pygame.init()
 pygame.font.init()
class MarsLander:
 def __init__(self, fps=20, width=1200, height=750):
 self.screen = pygame.display.set_mode((width, height))
 self.clock = pygame.time.Clock()
 self.FPS = fps
 self.regular_font = pygame.font.SysFont('Comic Sans MS', 15)
 self.alert_font = pygame.font.SysFont('Comic Sans MS', 18)
 self.large_font = pygame.font.SysFont('Comic Sans MS', 50)
 self.score = 0
 self.lives = 3
 self.obstacles = pygame.sprite.Group()
 self.meteors = pygame.sprite.Group()
 self.landing_pads = pygame.sprite.Group()
 self.background = Sprite('mars_background_instr.png', 0, 0)
 self.lander = Lander(width)
 self.height = height
 # Create sprites for landing pads and add them to the pads group
 # TODO have coordinates dependent on actual width and height
 Sprite('pad.png', 732, randint(858, 1042)).add(self.landing_pads)
 Sprite('pad_tall.png', 620, randint(458, 700)).add(self.landing_pads)
 Sprite('pad.png', 650, randint(0, 300)).add(self.landing_pads)
 self.reset_obstacles()
 self.create_new_storm()
 self.create_new_alert()
 @property
 def game_over(self):
 return self.lives < 1
 def reset_obstacles(self):
 """Create obstacles at a fixed location and add the to the obstacles group"""
 # TODO have coordinates dependent on actual width and height
 self.obstacles.empty()
 Sprite('pipe_ramp_NE.png', 540, 90).add(self.obstacles)
 Sprite('building_dome.png', 575, 420).add(self.obstacles)
 Sprite('satellite_SW.png', 435, 1150).add(self.obstacles)
 Sprite('rocks_ore_SW.png', 620, 1080).add(self.obstacles)
 Sprite('building_station_SW.png', 640, 850).add(self.obstacles)
 def create_new_storm(self, number_of_images=4):
 """Create meteors and add the to the meteors group"""
 # TODO have coordinates dependent on actual width and height
 now = int(clock())
 self.random_storm = randint(now + 3, now + 12)
 self.meteors.empty()
 for i in range(randint(1, 10)):
 image_name = 'spaceMeteors_{}.png'.format(randint(1, number_of_images))
 Meteor(image_name, -2 * i * self.FPS, randint(300, 900)).add(self.meteors)
 def create_new_alert(self):
 self.random_alert = randint(int(clock() + 5), int(clock() + 15))
 self.alert_key = choice((pygame.K_SPACE, pygame.K_LEFT, pygame.K_RIGHT))
 def draw_text(self, message, position, color=(255, 0, 0)):
 text = self.regular_font.render(message, False, color)
 self.screen.blit(text, position)
 def run(self):
 meteor_storm = False # Set to True whenever a storm should occur
 while not self.game_over:
 self.clock.tick(self.FPS)
 # If the user clicks the 'X' button on the window it quits the program
 if any(event.type == pygame.QUIT for event in pygame.event.get()):
 return
 self.screen.fill([255, 255, 255]) # Fill the empty spaces with white color
 self.screen.blit(self.background.image, self.background.rect) # Place the background image
 self.landing_pads.draw(self.screen)
 self.obstacles.draw(self.screen)
 # Check for collisions with obstacles and remove hit ones
 obstacles_hit = pygame.sprite.spritecollide(self.lander, self.obstacles, True)
 self.lander.damage += 10 * len(obstacles_hit)
 pressed_key = pygame.key.get_pressed() # Take pressed key value
 if not meteor_storm and clock() > self.random_storm:
 # As soon as the clock passes the random storm time it causes meteor rain
 meteor_storm = True
 if meteor_storm:
 self.meteors.update()
 self.meteors.draw(self.screen)
 # Check for collisions with meteors and remove hit ones
 meteors_hit = pygame.sprite.spritecollide(self.lander, self.meteors, True)
 self.lander.damage += 25 * len(meteors_hit)
 if pressed_key[pygame.K_ESCAPE]: # Stop game if the 'Esc' button is pressed
 return
 if self.random_alert < clock() < self.random_alert + 2:
 alert_msg = self.large_font.render('*ALERT*', False, (0, 0, 255))
 self.screen.blit(alert_msg, (190, 80))
 thrust = self.lander.handle_inputs(pressed_key, self.alert_key)
 else:
 thrust = self.lander.handle_inputs(pressed_key)
 if thrust:
 self.screen.blit(thrust.rot_image, thrust.rect)
 self.screen.blit(self.lander.rot_image, self.lander.rect)
 self.draw_text('{:1.f} s'.format(clock()), (72, 10))
 self.draw_text('{:.1f} m/s'.format(self.lander.veloc_y), (280, 56))
 self.draw_text('{:.1f} m/s'.format(self.lander.veloc_x), (280, 33))
 self.draw_text('{:d} kg'.format(self.lander.fuel), (72, 33))
 self.draw_text('{:.0f} m'.format(self.lander.altitude), (280, 10))
 self.draw_text('{} %'.format(self.lander.damage), (95, 56))
 self.draw_text('{:.0f} pts'.format(self.score), (77, 82))
 self.lander.free_fall()
 pygame.display.update()
 landing_pad_reached = pygame.sprite.spritecollideany(self.lander, self.landing_pads)
 if landing_pad_reached or self.lander.rect.bottom > self.height:
 self.create_new_alert()
 self.create_new_storm()
 self.reset_obstacles()
 meteor_storm = False
 if landing_pad_reached and self.lander.has_landing_position():
 self.score += 50
 else:
 self.lives -= 1
 should_exit = self.show_crash()
 if should_exit:
 return
 self.lander.reset_stats()
 def show_crash(self):
 """Display crash message in the middle of the screen and wait for a key press"""
 crash_msg = self.large_font.render('You Have Crashed!', False, (255, 0, 0))
 self.screen.blit(crash_msg, (420, 300))
 while True:
 for event in pygame.event.get():
 if event.type == pygame.QUIT:
 # Quit the game if the 'X' button is clicked
 return True
 if event.type == pygame.KEYDOWN:
 # Wait for a key to be pressed and if so resumes the game
 return False
 pygame.display.update()
 self.clock.tick(self.FPS)
class Sprite(pygame.sprite.Sprite):
 def __init__(self, image_file, top, left):
 super().__init__()
 self.image = pygame.image.load(image_file)
 self.rect = self.image.get_rect()
 self.rect.top = top
 self.rect.left = left
class EngineThrust(Sprite): # class for the thrust image
 def __init__(self, lander_rect, lander_angle):
 super().__init__('thrust.png', lander_rect.bottom - 10, lander_rect.left + 31)
 self.rot_image = pygame.transform.rotate(self.image, lander_angle)
class Meteor(Sprite):
 def __init__(self, image_file, top, left):
 super().__init__(image_file, top, left)
 self.speed_y = uniform(5, 10)
 self.speed_x = uniform(-2, 2)
 def update(self):
 self.rect.x += self.speed_x
 self.rect.y += self.speed_y
class Lander(Sprite):
 def __init__(self, width):
 super().__init__('lander.png', 0, 0)
 self.width = width
 self.reset_stats()
 def reset_stats(self):
 self.rect.top = 0
 self.rect.left = randint(0, self.width - self.rect.width)
 self.veloc_y = uniform(0.0, 1.0)
 self.veloc_x = uniform(-1.0, 1.0)
 self.fuel = 500
 self.angle = 0
 self.damage = 0
 self.rot_image = self.image
 def free_fall(self):
 self.rect.y += self.veloc_y
 self.rect.x += self.veloc_x
 self.veloc_y += 0.1
 if self.rect.top < 0:
 self.rect.top = 0
 self.veloc_y = uniform(0.0, 1.0)
 if self.rect.rigth < 0:
 self.rect.left = self.width
 if self.rect.left > self.width:
 self.rect.right = 0
 def start_engine(self):
 self.fuel -= 5
 self.veloc_x = self.veloc_x + 0.33 * math.sin(math.radians(-self.angle))
 self.veloc_y = self.veloc_y - 0.33 * math.cos(math.radians(self.angle))
 @property
 def altitude(self):
 return 1000 - self.rect.top * 1.436
 @property
 def can_land(self):
 return self.fuel > 0 and self.damage < 100
 def has_landing_position(self):
 return self.can_land and (self.veloc_y < 5) and (-5 < self.veloc_x < 5) and (-7 <= self.angle <= 7)
 def handle_inputs(self, pressed_key, alert_key=None):
 if not self.can_land:
 return
 thrust = None
 rotated = False
 if alert_key != pygame.K_SPACE and pressed_key[pygame.K_SPACE]:
 # Show thrust image when 'space' is pressed
 thrust = EngineThrust(self.rect, self.angle)
 self.start_engine()
 if alert_key != pygame.K_LEFT and pressed_key[pygame.K_LEFT]:
 # Rotate lander anticlockwise when 'left' is pressed
 self.angle += 1
 rotated = True
 if alert_key != pygame.K_RIGHT and pressed_key[pygame.K_RIGHT]:
 # Rotate lander clockwise when 'left' is pressed
 self.angle -= 1
 rotated = True
 if rotated:
 self.angle %= 360
 self.rot_image = pygame.transform.rotate(self.image, self.angle)
 return thrust
if __name__ == '__main__':
 init()
 game = MarsLander()
 game.run()
 pygame.quit()

You may have seen that I changed some constants into parameters with default values, this will allow you to improve the game customization if you need to by integrating argparse for instance.

Other changes may include restarting hazards every once in a while (spritecollideany might be of some help to detect when every meteor have run off the background)

answered Apr 19, 2018 at 21:33
\$\endgroup\$
1
  • \$\begingroup\$ Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions. \$\endgroup\$ Commented Apr 20, 2018 at 16:33

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.