""" Asteroid Smasher Shoot space rocks in this demo program created with Python and the Arcade library. Artwork from https://kenney.nl If Python and Arcade are installed, this example can be run from the command line with: python -m arcade.examples.asteroid_smasher """ import random import math import arcade from typing import cast STARTING_ASTEROID_COUNT = 3 SCALE = 0.5 OFFSCREEN_SPACE = 300 SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Asteroid Smasher" LEFT_LIMIT = -OFFSCREEN_SPACE RIGHT_LIMIT = SCREEN_WIDTH + OFFSCREEN_SPACE BOTTOM_LIMIT = -OFFSCREEN_SPACE TOP_LIMIT = SCREEN_HEIGHT + OFFSCREEN_SPACE class TurningSprite(arcade.Sprite): """ Sprite that sets its angle to the direction it is traveling in. """ def update(self): """ Move the sprite """ super().update() self.angle = math.degrees(math.atan2(self.change_y, self.change_x)) class ShipSprite(arcade.Sprite): """ Sprite that represents our space ship. Derives from arcade.Sprite. """ def __init__(self, filename, scale): """ Set up the space ship. """ # Call the parent Sprite constructor super().__init__(filename, scale) # Info on where we are going. # Angle comes in automatically from the parent class. self.thrust = 0 self.speed = 0 self.max_speed = 4 self.drag = 0.05 self.respawning = 0 # Mark that we are respawning. self.respawn() def respawn(self): """ Called when we die and need to make a new ship. 'respawning' is an invulnerability timer. """ # If we are in the middle of respawning, this is non-zero. self.respawning = 1 self.center_x = SCREEN_WIDTH / 2 self.center_y = SCREEN_HEIGHT / 2 self.angle = 0 def update(self): """ Update our position and other particulars. """ if self.respawning: self.respawning += 1 self.alpha = self.respawning if self.respawning > 250: self.respawning = 0 self.alpha = 255 if self.speed > 0: self.speed -= self.drag if self.speed < 0: self.speed = 0 if self.speed < 0: self.speed += self.drag if self.speed > 0: self.speed = 0 self.speed += self.thrust if self.speed > self.max_speed: self.speed = self.max_speed if self.speed < -self.max_speed: self.speed = -self.max_speed self.change_x = -math.sin(math.radians(self.angle)) * self.speed self.change_y = math.cos(math.radians(self.angle)) * self.speed self.center_x += self.change_x self.center_y += self.change_y # If the ship goes off-screen, move it to the other side of the window if self.right < 0: self.left = SCREEN_WIDTH if self.left > SCREEN_WIDTH: self.right = 0 if self.bottom < 0: self.top = SCREEN_HEIGHT if self.top > SCREEN_HEIGHT: self.bottom = 0 """ Call the parent class. """ super().update() class AsteroidSprite(arcade.Sprite): """ Sprite that represents an asteroid. """ def __init__(self, image_file_name, scale): super().__init__(image_file_name, scale=scale) self.size = 0 def update(self): """ Move the asteroid around. """ super().update() if self.center_x < LEFT_LIMIT: self.center_x = RIGHT_LIMIT if self.center_x > RIGHT_LIMIT: self.center_x = LEFT_LIMIT if self.center_y > TOP_LIMIT: self.center_y = BOTTOM_LIMIT if self.center_y < BOTTOM_LIMIT: self.center_y = TOP_LIMIT class MyGame(arcade.Window): """ Main application class. """ def __init__(self): super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) self.game_over = False # Sprite lists self.player_sprite_list = arcade.SpriteList() self.asteroid_list = arcade.SpriteList() self.bullet_list = arcade.SpriteList() self.ship_life_list = arcade.SpriteList() # Set up the player self.score = 0 self.player_sprite = None self.lives = 3 # Sounds self.laser_sound = arcade.load_sound(":resources:sounds/hurt5.wav") self.hit_sound1 = arcade.load_sound(":resources:sounds/explosion1.wav") self.hit_sound2 = arcade.load_sound(":resources:sounds/explosion2.wav") self.hit_sound3 = arcade.load_sound(":resources:sounds/hit1.wav") self.hit_sound4 = arcade.load_sound(":resources:sounds/hit2.wav") # Text self.text_score = None self.text_asteroid_count = None def start_new_game(self): """ Set up the game and initialize the variables. """ self.game_over = False # Sprite lists self.player_sprite_list = arcade.SpriteList() self.asteroid_list = arcade.SpriteList() self.bullet_list = arcade.SpriteList() self.ship_life_list = arcade.SpriteList() # Set up the player self.score = 0 self.player_sprite = ShipSprite(":resources:images/space_shooter/" "playerShip1_orange.png", SCALE) self.player_sprite_list.append(self.player_sprite) self.lives = 3 # Set up the little icons that represent the player lives. cur_pos = 10 for i in range(self.lives): life = arcade.Sprite(":resources:images/space_shooter/" "playerLife1_orange.png", SCALE) life.center_x = cur_pos + life.width life.center_y = life.height cur_pos += life.width self.ship_life_list.append(life) # Make the asteroids image_list = (":resources:images/space_shooter/meteorGrey_big1.png", ":resources:images/space_shooter/meteorGrey_big2.png", ":resources:images/space_shooter/meteorGrey_big3.png", ":resources:images/space_shooter/meteorGrey_big4.png") for i in range(STARTING_ASTEROID_COUNT): image_no = random.randrange(4) enemy_sprite = AsteroidSprite(image_list[image_no], SCALE) enemy_sprite.guid = "Asteroid" enemy_sprite.center_y = random.randrange(BOTTOM_LIMIT, TOP_LIMIT) enemy_sprite.center_x = random.randrange(LEFT_LIMIT, RIGHT_LIMIT) enemy_sprite.change_x = random.random() * 2 - 1 enemy_sprite.change_y = random.random() * 2 - 1 enemy_sprite.change_angle = (random.random() - 0.5) * 2 enemy_sprite.size = 4 self.asteroid_list.append(enemy_sprite) # Create new text objects with initial values self.text_score = arcade.Text( f"Score: {self.score}", start_x=10, start_y=70, font_size=13, ) self.text_asteroid_count = arcade.Text( f"Asteroid Count: {len(self.asteroid_list)}", start_x=10, start_y=50, font_size=13, ) def on_draw(self): """ Render the screen. """ # This command has to happen before we start drawing self.clear() # Draw all the sprites. self.asteroid_list.draw() self.ship_life_list.draw() self.bullet_list.draw() self.player_sprite_list.draw() # Draw the text self.text_score.draw() self.text_asteroid_count.draw() def on_key_press(self, symbol, modifiers): """ Called whenever a key is pressed. """ # Shoot if the player hit the space bar and we aren't respawning. if not self.player_sprite.respawning and symbol == arcade.key.SPACE: bullet_sprite = TurningSprite(":resources:images/space_shooter/" "laserBlue01.png", SCALE) bullet_sprite.guid = "Bullet" bullet_speed = 13 bullet_sprite.change_y = \ math.cos(math.radians(self.player_sprite.angle)) * bullet_speed bullet_sprite.change_x = \ -math.sin(math.radians(self.player_sprite.angle)) \ * bullet_speed bullet_sprite.center_x = self.player_sprite.center_x bullet_sprite.center_y = self.player_sprite.center_y bullet_sprite.update() self.bullet_list.append(bullet_sprite) arcade.play_sound(self.laser_sound, speed=random.random() * 3 + 0.5) if symbol == arcade.key.LEFT: self.player_sprite.change_angle = 3 elif symbol == arcade.key.RIGHT: self.player_sprite.change_angle = -3 elif symbol == arcade.key.UP: self.player_sprite.thrust = 0.15 elif symbol == arcade.key.DOWN: self.player_sprite.thrust = -.2 def on_key_release(self, symbol, modifiers): """ Called whenever a key is released. """ if symbol == arcade.key.LEFT: self.player_sprite.change_angle = 0 elif symbol == arcade.key.RIGHT: self.player_sprite.change_angle = 0 elif symbol == arcade.key.UP: self.player_sprite.thrust = 0 elif symbol == arcade.key.DOWN: self.player_sprite.thrust = 0 def split_asteroid(self, asteroid: AsteroidSprite): """ Split an asteroid into chunks. """ x = asteroid.center_x y = asteroid.center_y self.score += 1 if asteroid.size == 4: for i in range(3): image_no = random.randrange(2) image_list = [":resources:images/space_shooter/meteorGrey_med1.png", ":resources:images/space_shooter/meteorGrey_med2.png"] enemy_sprite = AsteroidSprite(image_list[image_no], SCALE * 1.5) enemy_sprite.center_y = y enemy_sprite.center_x = x enemy_sprite.change_x = random.random() * 2.5 - 1.25 enemy_sprite.change_y = random.random() * 2.5 - 1.25 enemy_sprite.change_angle = (random.random() - 0.5) * 2 enemy_sprite.size = 3 self.asteroid_list.append(enemy_sprite) self.hit_sound1.play() elif asteroid.size == 3: for i in range(3): image_no = random.randrange(2) image_list = [":resources:images/space_shooter/meteorGrey_small1.png", ":resources:images/space_shooter/meteorGrey_small2.png"] enemy_sprite = AsteroidSprite(image_list[image_no], SCALE * 1.5) enemy_sprite.center_y = y enemy_sprite.center_x = x enemy_sprite.change_x = random.random() * 3 - 1.5 enemy_sprite.change_y = random.random() * 3 - 1.5 enemy_sprite.change_angle = (random.random() - 0.5) * 2 enemy_sprite.size = 2 self.asteroid_list.append(enemy_sprite) self.hit_sound2.play() elif asteroid.size == 2: for i in range(3): image_no = random.randrange(2) image_list = [":resources:images/space_shooter/meteorGrey_tiny1.png", ":resources:images/space_shooter/meteorGrey_tiny2.png"] enemy_sprite = AsteroidSprite(image_list[image_no], SCALE * 1.5) enemy_sprite.center_y = y enemy_sprite.center_x = x enemy_sprite.change_x = random.random() * 3.5 - 1.75 enemy_sprite.change_y = random.random() * 3.5 - 1.75 enemy_sprite.change_angle = (random.random() - 0.5) * 2 enemy_sprite.size = 1 self.asteroid_list.append(enemy_sprite) self.hit_sound3.play() elif asteroid.size == 1: self.hit_sound4.play() def on_update(self, x): """ Move everything """ if not self.game_over: self.asteroid_list.update() self.bullet_list.update() self.player_sprite_list.update() for bullet in self.bullet_list: asteroids = arcade.check_for_collision_with_list(bullet, self.asteroid_list) for asteroid in asteroids: # expected AsteroidSprite, got Sprite instead self.split_asteroid(cast(AsteroidSprite, asteroid)) asteroid.remove_from_sprite_lists() bullet.remove_from_sprite_lists() # Remove bullet if it goes off-screen size = max(bullet.width, bullet.height) if bullet.center_x < 0 - size: bullet.remove_from_sprite_lists() if bullet.center_x > SCREEN_WIDTH + size: bullet.remove_from_sprite_lists() if bullet.center_y < 0 - size: bullet.remove_from_sprite_lists() if bullet.center_y > SCREEN_HEIGHT + size: bullet.remove_from_sprite_lists() if not self.player_sprite.respawning: asteroids = arcade.check_for_collision_with_list(self.player_sprite, self.asteroid_list) if len(asteroids) > 0: if self.lives > 0: self.lives -= 1 self.player_sprite.respawn() self.split_asteroid(cast(AsteroidSprite, asteroids[0])) asteroids[0].remove_from_sprite_lists() self.ship_life_list.pop().remove_from_sprite_lists() print("Crash") else: self.game_over = True print("Game over") # Update the text objects self.text_score.text = f"Score: {self.score}" self.text_asteroid_count.text = f"Asteroid Count: {len(self.asteroid_list)}" def main(): """ Start the game """ window = MyGame() window.start_new_game() arcade.run() if __name__ == "__main__": main()