Mars Lander pygame

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












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()






share|improve this question





















  • Could you upload the images somewhere, please? We can't run the code without the assets.
    – Gareth Rees
    Apr 17 at 9:47










  • Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2
    – Close Enough
    Apr 19 at 12:04
















up vote
4
down vote

favorite












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()






share|improve this question





















  • Could you upload the images somewhere, please? We can't run the code without the assets.
    – Gareth Rees
    Apr 17 at 9:47










  • Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2
    – Close Enough
    Apr 19 at 12:04












up vote
4
down vote

favorite









up vote
4
down vote

favorite











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()






share|improve this question













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()








share|improve this question












share|improve this question




share|improve this question








edited Apr 15 at 18:08









Jamal♦

30.1k11114225




30.1k11114225









asked Apr 15 at 17:43









Close Enough

263




263











  • Could you upload the images somewhere, please? We can't run the code without the assets.
    – Gareth Rees
    Apr 17 at 9:47










  • Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2
    – Close Enough
    Apr 19 at 12:04
















  • Could you upload the images somewhere, please? We can't run the code without the assets.
    – Gareth Rees
    Apr 17 at 9:47










  • Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2
    – Close Enough
    Apr 19 at 12:04















Could you upload the images somewhere, please? We can't run the code without the assets.
– Gareth Rees
Apr 17 at 9:47




Could you upload the images somewhere, please? We can't run the code without the assets.
– Gareth Rees
Apr 17 at 9:47












Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2
– Close Enough
Apr 19 at 12:04




Here you go: photos.app.goo.gl/y8mNYY9bLvbv9poM2
– Close Enough
Apr 19 at 12:04










1 Answer
1






active

oldest

votes

















up vote
2
down vote



accepted










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)






share|improve this answer





















  • Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
    – Close Enough
    Apr 20 at 16:33










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f192128%2fmars-lander-pygame%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted










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)






share|improve this answer





















  • Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
    – Close Enough
    Apr 20 at 16:33














up vote
2
down vote



accepted










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)






share|improve this answer





















  • Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
    – Close Enough
    Apr 20 at 16:33












up vote
2
down vote



accepted







up vote
2
down vote



accepted






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)






share|improve this answer













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)







share|improve this answer













share|improve this answer



share|improve this answer











answered Apr 19 at 21:33









Mathias Ettinger

21.8k32876




21.8k32876











  • Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
    – Close Enough
    Apr 20 at 16:33
















  • Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
    – Close Enough
    Apr 20 at 16:33















Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
– Close Enough
Apr 20 at 16:33




Thank you for taking time out to answer my question! I am looking forward to implementing many of your suggestions.
– Close Enough
Apr 20 at 16:33












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f192128%2fmars-lander-pygame%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation