Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # program template for Spaceship
- import simplegui
- import math
- import random
- # UI Globals
- WIDTH = 800
- HEIGHT = 600
- # GAME ATTRIBUTES
- ANGULAR_VEL = 2.5 # Angular velocity of spaceship (rotation)
- ACCEL = 9.81 # approx. 3px/s^2 in the direction of thrust
- FRICTION = ACCEL/(ACCEL**math.exp(1)) # exponential decrease in forward momentum
- # Acceleration adds or subtracts a constant from velocity every tick.
- # Friction is modeled as a decaying exponential, is a multiple of the previous velocity value
- # vel[t] = vel[0] * (1 - FRICTION)
- MISSILE_VEL = 600
- # Image and Art Assets
- # --------------------
- class ImageInfo:
- def __init__(self, center, size, radius = 0, lifespan = None, animated = False):
- self.center = center
- self.size = size
- self.radius = radius
- if lifespan:
- self.lifespan = lifespan
- else:
- self.lifespan = float('inf')
- self.animated = animated
- def get_center(self):
- return self.center
- def get_size(self):
- return self.size
- def get_radius(self):
- return self.radius
- def get_lifespan(self):
- return self.lifespan
- def get_animated(self):
- return self.animated
- # art assets created by Kim Lathrop, may be freely re-used in non-commercial projects, please credit Kim
- # debris images - debris1_brown.png, debris2_brown.png, debris3_brown.png, debris4_brown.png
- # debris1_blue.png, debris2_blue.png, debris3_blue.png, debris4_blue.png, debris_blend.png
- debris_info = ImageInfo([320, 240], [640, 480])
- debris_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/debris2_brown.png")
- # nebula images - nebula_brown.png, nebula_blue.png
- nebula_info = ImageInfo([400, 300], [800, 600])
- nebula_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/nebula_brown.png")
- # splash image
- splash_info = ImageInfo([200, 150], [400, 300])
- splash_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/splash.png")
- # ship image
- ship_info = ImageInfo([45, 45], [90, 90], 35/2)
- ship_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/double_ship.png")
- # missile image - shot1.png, shot2.png, shot3.png
- missile_info = ImageInfo([5,5], [10, 10], 3, 40)
- missile_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/shot1.png")
- # asteroid images - asteroid_blue.png, asteroid_brown.png, asteroid_blend.png
- asteroid_info = ImageInfo([45, 45], [90, 90], 35)
- asteroid_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/asteroid_blue.png")
- # animated explosion - explosion_orange.png, explosion_blue.png, explosion_blue2.png, explosion_alpha.png
- explosion_info = ImageInfo([64, 64], [128, 128], asteroid_info.get_radius(), 24, True)
- explosion_image = simplegui.load_image("http://commondatastorage.googleapis.com/codeskulptor-assets/lathrop/explosion_orange.png")
- # sound assets purchased from sounddogs.com, please do not redistribute
- soundtrack = simplegui.load_sound("http://commondatastorage.googleapis.com/codeskulptor-assets/sounddogs/soundtrack.mp3")
- soundtrack.set_volume(0.5)
- missile_sound = simplegui.load_sound("https://dl.dropboxusercontent.com/s/qt9qyjm86iipiya/missle1.mp3?token_hash=AAGTyTkv1LIebte9VLCnsbHWiAppUaetj_42QarR7Xkukg&dl=1")
- missile_sound.set_volume(0.5)
- ship_thrust_sound = simplegui.load_sound("https://dl.dropboxusercontent.com/s/v3fm1fsnetmwiyt/exhaust.mp3?token_hash=AAEcKwgXlNGfQBuhrG9fehDWtGtJbjtG3-PqZvqN9P7qDg&dl=1")
- ship_thrust_sound.set_volume(0.5)
- explosion_sound = simplegui.load_sound("https://dl.dropboxusercontent.com/s/63k5jci8izxdwk5/explosion4.mp3?token_hash=AAEDpL5hUbsbhTGCKzWpqwG2sioAhVo7TUqYFc78Qf7TiA&dl=1")
- explosion_sound.set_volume(.35)
- # Helper functions to handle transformations
- def deg_to_rad(ang):
- return (ang/180 * math.pi)
- def angle_to_vector(ang):
- return [math.cos(ang), math.sin(ang)]
- def dist(p, q):
- return math.sqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2)
- def rot_vector(vector, ang):
- # Used to rotate points in the xy-Cartesian plane counter-clockwise through an angle
- rot_mat = [[math.cos(ang), -math.sin(ang)], [math.sin(ang), math.cos(ang)]]
- rot_x = rot_mat[0][0] * vector[0] + rot_mat[0][1] * vector[1]
- rot_y = rot_mat[1][0] * vector[0] + rot_mat[1][1] * vector[1]
- return [rot_x, rot_y]
- # GAME Classes
- # -------------
- # Ship class
- class Ship:
- def __init__(self, pos, vel, angle, image, info):
- # codeskulptor has no assert, but explicit assignment will ensure
- # that code will crash if pos is not a tuple/list
- self.pos = [pos[0],pos[1]]
- self.vel = [vel[0],vel[1]] # x, y speeds. No direction information
- self.thrust = False
- self.angle = angle
- self.angle_vel = 0
- self.alive = True
- # image
- self.image = image
- self.image_center = info.get_center()
- self.image_size = info.get_size()
- self.radius = info.get_radius()
- def get_radius(self):
- return self.radius
- def get_pos(self):
- return self.pos
- def get_orientation(self):
- ''' forward vector (direction of orientation) '''
- return angle_to_vector(self.angle)
- def get_missile_spawn_point(self):
- ''' Spawn missiles at the tip of the spaceship '''
- direction = self.get_orientation()
- return [self.pos[0] + self.radius*direction[0], self.pos[1] + self.radius*direction[1]]
- def get_life_status(self):
- return self.alive
- def set_life_status(self, status):
- self.alive = status
- if status:
- self.pos = [WIDTH/2, HEIGHT/2]
- else:
- self.pos[0] *= -1
- self.pos[1] *= -1
- self.vel = [0, 0]
- ship_thrust_sound.rewind()
- def set_thruster(self, thruster_state):
- self.thrust = thruster_state
- def update_angle_vel(self, angle_vel):
- self.angle_vel = deg_to_rad(angle_vel)
- def _update_pos_(self):
- self.pos[0] = (self.pos[0] + self.vel[0]/60) % WIDTH
- self.pos[1] = (self.pos[1] + self.vel[1]/60) % HEIGHT
- def _update_vel_(self):
- # apply friction
- self.vel[0] *= (1 - FRICTION)
- self.vel[1] *= (1 - FRICTION)
- if self.thrust:
- ship_thrust_sound.play()
- direction = angle_to_vector(self.angle) # forward vector (direction of thrust)
- self.vel = [self.vel[0] + direction[0] * ACCEL, self.vel[1] + direction[1] * ACCEL]
- else:
- ship_thrust_sound.rewind()
- def update(self):
- # update orientation
- self.angle += self.angle_vel
- # update position
- self._update_pos_()
- # update velocity
- self._update_vel_()
- def shoot(self):
- direction = angle_to_vector(self.angle) # forward vector (direction of orientation)
- # missiles spawn at the tip of the spaceship
- missile_pos = self.get_missile_spawn_point()
- missile_vel = [MISSILE_VEL * direction[0], MISSILE_VEL * direction[1]]
- # scale the direction by spaceship velocity, to launch missiles
- # along the direction of spaceship's orientation
- if self.thrust:
- missile_vel[0] += self.vel[0] + ACCEL
- missile_vel[1] += self.vel[1] + ACCEL
- missiles.append(Sprite(missile_pos, missile_vel, 0, 0, missile_image, missile_info, missile_sound))
- def draw(self, canvas, draw_size=0.5):
- draw_size = [self.image_size[0] * draw_size, self.image_size[1] * draw_size]
- if self.thrust:
- # draw ship with flames
- image_pos = self.image_center
- image_pos = [image_pos[0] + ship_info.get_size()[0], image_pos[1]]
- canvas.draw_image(self.image, image_pos, self.image_size, self.pos, draw_size, self.angle)
- else:
- canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, draw_size, self.angle)
- # Sprite class
- class Sprite:
- def __init__(self, pos, vel, ang, ang_vel, image, info, sound = None):
- self.pos = [pos[0],pos[1]]
- self.vel = [vel[0],vel[1]]
- self.angle = ang
- self.angle_vel = deg_to_rad(ang_vel)
- self.image = image
- self.image_center = info.get_center()
- self.image_size = info.get_size()
- self.radius = info.get_radius()
- self.lifespan = info.get_lifespan()
- self.animated = info.get_animated()
- self.age = 0
- if sound:
- sound.rewind()
- sound.play()
- def get_radius(self):
- return self.radius
- def get_pos(self):
- return self.pos
- def get_vel(self):
- return self.vel
- def get_angle(self):
- return self.angle
- def get_angle_vel(self):
- return self.angle_vel
- def get_lifespan(self):
- return self.lifespan
- def get_age(self):
- return self.age
- def collide(self, other):
- return dist(self.get_pos(), other.get_pos()) <= (self.get_radius() + other.get_radius())
- def draw(self, canvas):
- canvas.draw_image(self.image, self.image_center, self.image_size, self.pos, [self.radius*2]*2, self.angle)
- # change attributes of sprite that will cause the same image to be drawn differently
- def update(self):
- ''' Return True to indicate sprite's lifespan is over, and the sprite is to be removed
- False otherwise
- '''
- # rotate sprite
- self.angle += self.angle_vel
- # move sprite in space
- self.pos[0] = (self.pos[0] + self.vel[0]/60) % WIDTH
- self.pos[1] = (self.pos[1] + self.vel[1]/60) % HEIGHT
- # update duration for dynamic (limited duration) sprites
- if self.lifespan < float('inf') and self.age < self.lifespan:
- self.age += 1
- if self.animated:
- self.image_center[0] += self.image_size[0]
- return False
- elif self.age >= self.lifespan:
- return True
- # Helpers to handle game logic
- # ----------------------------
- # Spawn 5 rocks in random positions
- def rock_spawner():
- global rocks, level
- initial_speed = 50
- level_speed = initial_speed * math.sqrt(level)
- def rand_sgn():
- return random.randrange(-1, 2, 2)
- def rand_speed():
- return rand_sgn() * random.random()* level_speed
- for i in range(5 + (level // 5)):
- while True:
- pos = [random.randrange(0, WIDTH), random.randrange(0, HEIGHT)]
- safe_zone = 2 * asteroid_info.get_size()[0]
- if dist(pos, [WIDTH/2, HEIGHT/2]) >= safe_zone:
- break
- vel = [rand_speed(), rand_speed()]
- ang_vel = rand_sgn() * random.random() * math.pi * 2
- rocks.append(Sprite(pos, vel, 0, ang_vel, asteroid_image, asteroid_info))
- # Spawn 2 child rocks if parent rock is large enough
- def child_rock_spawner(rock):
- threshold = 20 # child rock size limit, increasing this will yield smaller child rocks
- child_sz_inc = threshold/2 # child size = parent size minus increment, each generation of rocks will be smaller
- if rock.get_radius() > asteroid_info.get_radius() - threshold:
- child_info = ImageInfo([45, 45], [90, 90], int(rock.get_radius() - child_sz_inc))
- rocks.append(Sprite(rock.get_pos(),\
- rot_vector(rock.get_vel(), deg_to_rad(45)),\
- 0, rock.get_angle_vel(), asteroid_image, child_info))
- rocks.append(Sprite(rock.get_pos(),\
- rot_vector(rock.get_vel(), deg_to_rad(-45)),\
- 0, rock.get_angle_vel(), asteroid_image, child_info))
- # Process sprites
- def process_spriteGroup(spriteList, canvas):
- ''' Update and Draw sprites '''
- for sprite in spriteList[:]:
- # if dynamic sprite's lifespan has expired, remove it from list
- if sprite.update():
- spriteList.remove(sprite)
- else:
- sprite.draw(canvas)
- # Detect and handle collisions. Collisions result in explosions
- def group_collide(spriteList, other_obj):
- ''' Checks if given object collides with any member of spriteList
- If collision is detected:
- - colliding object removed from list
- - explosion occurs at the location of other obj
- Returns True
- '''
- for sprite in spriteList:
- if sprite.collide(other_obj):
- explosion_info = ImageInfo([64, 64], [128, 128], (sprite.get_radius() + other_obj.get_radius())*2, 24, True)
- explosions.append(Sprite(other_obj.get_pos(), [0, 0], 0, 0,\
- explosion_image, explosion_info, explosion_sound))
- spriteList.remove(sprite)
- return True
- else:
- return False # either no collisions, or spriteList was empty
- # Detect collisions, create explosions
- def group_group_collide(spList1, spList2):
- destroyed_sprites = []
- for sp2_obj in spList2[:]:
- # Object from list2 collides with someting in list1
- if group_collide(spList1, sp2_obj):
- destroyed_sprites.append(sp2_obj)
- spList2.remove(sp2_obj)
- else:
- # return list of sprites that were destroyed,
- # useful for calculating scores
- return destroyed_sprites
- # Update score
- def update_score(rock):
- ''' multiply scores by level and inverse the size of rock, smaller rocks more points '''
- global score, level, lives
- if level % 5 == 0:
- lives += 1 # grant additional life per 5 levels survived
- score += level * (5 + (asteroid_info.get_radius() - rock.get_radius()))
- # Keyboard controls for Space ship
- def ship_controls():
- # Thruster
- my_ship.set_thruster(keyboard['up'])
- # missiles
- if keyboard['space'] and len (missiles) < 3:
- if len(missiles) == 0:
- my_ship.shoot()
- # space missiles so the sprites don't overlap
- elif dist(my_ship.get_missile_spawn_point(), missiles[-1].get_pos()) >= 25:
- my_ship.shoot()
- # Angle
- if keyboard['left'] and not keyboard['right']:
- my_ship.update_angle_vel(-ANGULAR_VEL)
- elif keyboard['right'] and not keyboard['left']:
- my_ship.update_angle_vel(ANGULAR_VEL)
- else:
- my_ship.update_angle_vel(0.0)
- # Check is level has been cleared
- def is_level_clear():
- # check if level cleared
- return len(rocks) <= 0
- # Check if rendering of dynamic animated sprites has finished
- def finished_rendering():
- return len(missiles) <= 0 and len(explosions) <= 0
- # Play background music
- def play_backgroundMusic():
- soundtrack.play()
- if time % 180*60 == 0:
- soundtrack.rewind()
- # DRAWING
- # --------
- # Draw Intro Splash
- def draw_intro_splash(canvas):
- # order of drawing important because splash needs to be in background
- canvas.draw_image(splash_image, splash_info.get_center(), splash_info.get_size(),\
- [WIDTH / 2, HEIGHT / 2], splash_info.get_size())
- tx_controls = []
- tx_controls.append('UP : Thrust')
- tx_controls.append('<- -> : Rotate')
- tx_controls.append('SPACE : Missile')
- msg_pos = []
- for i in range(len(tx_controls)):
- txSize = frame.get_canvas_textwidth(tx_controls[i], 24)
- canvas.draw_text(tx_controls[i], (WIDTH/2 - txSize/2, HEIGHT/2 + 85 + (i*24)), 20, "White", "monospace")
- # Game Ove Message
- def draw_game_over(canvas):
- msg = "GAME OVER"
- txSize = frame.get_canvas_textwidth(msg, 50)
- canvas.draw_text(msg, (WIDTH/2 - txSize/2, HEIGHT/2 - 85), 50, "White")
- # Draw static elements
- def draw_static_background(canvas):
- # animiate background
- center = debris_info.get_center()
- size = debris_info.get_size()
- wtime = (time / 4) % center[0]
- # static nebula background
- canvas.draw_image(nebula_image, nebula_info.get_center(), nebula_info.get_size(), [WIDTH / 2, HEIGHT / 2], [WIDTH, HEIGHT])
- # slowly moving background debris
- canvas.draw_image(debris_image, [center[0] - wtime, center[1]], [size[0] - 2 * wtime, size[1]],
- [WIDTH / 2 + 1.25 * wtime, HEIGHT / 2], [WIDTH - 2.5 * wtime, HEIGHT])
- # wrap around debris image
- canvas.draw_image(debris_image, [size[0] - wtime, center[1]], [2 * wtime, size[1]],
- [1.25 * wtime, HEIGHT / 2], [2.5 * wtime, HEIGHT])
- # draw/update lives and score
- str_lives = "Lives: " + str(lives)
- canvas.draw_text(str_lives, (50, 50), 30, "White")
- str_score = "Score: " + str(score)
- canvas.draw_text(str_score, (50, 80), 20, "White")
- def draw(canvas):
- global my_ship, rocks, missiles, explosions
- global time, level, lives, score # values
- global start_game, level_clear # flags
- # Static elements
- draw_static_background(canvas)
- # Show Intro splash and Game Over message
- if not start_game:
- """ Show splash screen until mouse click to Start Game """
- draw_intro_splash(canvas)
- # Game Over Message
- if lives <= 0:
- draw_game_over(canvas)
- # Game has been started
- else:
- time += 1
- play_backgroundMusic()
- # Lives > 0 and Level Not Cleared
- if not level_clear:
- # Ship has not been destroyed
- if my_ship.get_life_status():
- ship_controls() # handle user inputs to ship
- my_ship.update() # update position, vel in game
- my_ship.draw(canvas) # draw ship
- # Update and Draw Sprites
- process_spriteGroup(rocks, canvas)
- process_spriteGroup(missiles, canvas)
- process_spriteGroup(explosions, canvas)
- # Handle collisions
- # Missile-Rock collision
- destroyed_rocks = group_group_collide(missiles, rocks)
- for rock in destroyed_rocks:
- # Update score based on size of rock and level
- update_score(rock)
- # if rocks are big enough, spawn child rocks
- child_rock_spawner(rock)
- # Rock-ship collision
- if group_collide(rocks, my_ship):
- my_ship.set_life_status(False) # Ship has been destroyed
- lives -= 1
- score -= 5 * level
- spawn_timer.start()
- # Check is level has been cleared
- if is_level_clear():
- level += 1
- level_clear = True
- my_ship.set_life_status(False)
- level_timer.start()
- # Level cleared, load next level
- if level_clear:
- # Level load message
- msg = "Level " + str(level)
- txSize = frame.get_canvas_textwidth(msg, 50)
- canvas.draw_text(msg, (WIDTH/2 - txSize/2, HEIGHT/2), 50, "White")
- # No lives left, game over
- elif lives <= 0 and finished_rendering():
- start_game = False
- soundtrack.rewind()
- # Timer handlers
- # ---------------
- # Wait 2s...then Spawn a spaceship
- def spawn_ship():
- ship_loc = [WIDTH/2, HEIGHT/2]
- for rock in rocks:
- if dist(rock.get_pos(), ship_loc) >= (rock.get_radius() + ship_info.get_radius()):
- continue
- else:
- break
- else:
- my_ship.set_life_status(True)
- if spawn_timer.is_running():
- spawn_timer.stop()
- # Wait 3s...then load next level
- def load_level():
- global level_clear
- if level_timer.is_running():
- level_timer.stop()
- init_level()
- rock_spawner()
- spawn_ship()
- # INPUT Handlers
- # --------------
- # Start Game on Mouse Click
- def splash_startGame(pos):
- global start_game
- if not start_game:
- init_game()
- rock_spawner()
- start_game = True
- # Keyboard Handler
- def keydown(key):
- global keyboard
- if key == simplegui.KEY_MAP['up']:
- keyboard['up'] = True
- # -> (right arrow) key pressed, increment angular velocity
- if key == simplegui.KEY_MAP['right']:
- keyboard['right'] = True
- # <- (left arrow) key pressed, decrement angular velocity
- if key == simplegui.KEY_MAP['left']:
- keyboard['left'] = True
- if key == simplegui.KEY_MAP['space']:
- keyboard['space'] = True
- def keyup(key):
- global keyboard
- if key == simplegui.KEY_MAP['up']:
- keyboard['up'] = False
- # -> (right arrow) key released, decrement angular velocity
- if key == simplegui.KEY_MAP['right']:
- keyboard['right'] = False
- # <- (left arrow) key released, increment angular velocity
- if key == simplegui.KEY_MAP['left']:
- keyboard['left'] = False
- if key == simplegui.KEY_MAP['space']:
- keyboard['space'] = False
- # Initialize/ Reset Game State Variables
- def init_level():
- ''' Variables that must be reset per level '''
- global level_clear, rocks, missiles, explosions, keyboard
- # Control
- keyboard = {'up' : False,
- 'right' : False,
- 'left' : False,
- 'space' : False,
- }
- # Flags
- level_clear = False
- # Reset Animations
- rocks = []
- missiles = []
- explosions = []
- def init_game():
- ''' Declare globals, and initialize values '''
- global score, lives, time, level
- global keyboard, my_ship, rocks, missiles, explosions
- global level_clear, start_game
- # Game states
- score = 0
- lives = 3
- time = 0.5
- level = 1
- # Control
- keyboard = {'up' : False,
- 'right' : False,
- 'left' : False,
- 'space' : False,
- }
- # Game Objects
- my_ship = Ship([WIDTH / 2, HEIGHT / 2], [0, 0], deg_to_rad(270), ship_image, ship_info)
- rocks = []
- missiles = []
- explosions = []
- # Flags
- level_clear = start_game = False
- init_game() # Initialize and Load Game
- # Initialize frame
- frame = simplegui.create_frame("Asteroids", WIDTH, HEIGHT, 0)
- # Register handlers
- frame.set_draw_handler(draw)
- # Input Handlers
- frame.set_mouseclick_handler(splash_startGame)
- frame.set_keydown_handler(keydown)
- frame.set_keyup_handler(keyup)
- # Timers
- spawn_timer = simplegui.create_timer(2000.0, spawn_ship)
- level_timer = simplegui.create_timer(2000.0, load_level)
- # Get things rolling
- frame.start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement