Skip to content

Commit

Permalink
basic asteroids gameplay
Browse files Browse the repository at this point in the history
  • Loading branch information
nomnivore committed Mar 7, 2022
1 parent bc8ba67 commit ad3da9c
Show file tree
Hide file tree
Showing 12 changed files with 562 additions and 28 deletions.
11 changes: 11 additions & 0 deletions ast_object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class AstObject:
def wrap_around_screen(self):
"""Wrap around screen."""
if self.pos.x > self.settings.screen_size[0]:
self.pos.x = 0
if self.pos.x < 0:
self.pos.x = self.settings.screen_size[0]
if self.pos.y <= 0:
self.pos.y = self.settings.screen_size[1]
if self.pos.y > self.settings.screen_size[1]:
self.pos.y = 0
156 changes: 153 additions & 3 deletions asteroids.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import sys

from settings import Settings
from game_stats import GameStats
from scoreboard import Scoreboard
from ship import Ship
from space_rock import SpaceRock
from button import Button


class AsteroidsGame:
Expand All @@ -14,32 +18,130 @@ def __init__(self):

pg.init()
self.settings = Settings()
self.stats = GameStats(self)

self.screen = pg.display.set_mode(
self.settings.screen_size)

self.sb = Scoreboard(self)

self.screen_rect = self.screen.get_rect()
pg.display.set_caption("Asteroids")
self.clock = pg.time.Clock()

self.play_button = Button(self, "Play")

self.star_coords = []
self._make_stars()

self.ship = Ship(self)

self.bullets = pg.sprite.Group()
self.rocks = pg.sprite.Group()

# self._prepare_game(reset=True)

def run_game(self):
"""Start the main loop for the game"""
while True:
self._check_events()
if self.stats.game_active:
self.ship.update()
self.bullets.update()
self.rocks.update()
self._check_collisions()

self._update_screen()
self.clock.tick(self.settings.fps)

def _check_events(self):
"""Respond to kwypresses and mouse events"""
"""Respond to keypresses and mouse events"""
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit()
self.ship.update()
elif event.type == pg.KEYDOWN:
self._check_keydown_events(event)
elif event.type == pg.MOUSEBUTTONDOWN:
mouse_pos = pg.mouse.get_pos()
self._check_play_button(mouse_pos)

def _check_play_button(self, mouse_pos):
"""Start game when player clicks Play"""
clicked = self.play_button.rect.collidepoint(mouse_pos)
if clicked and not self.stats.game_active:
self._prepare_game(reset=True)

def _check_keydown_events(self, event):
"""Respond to keypresses"""
if event.key == pg.K_q:
pg.quit()
sys.exit()
elif event.key == pg.K_SPACE:
self.ship.fire()

def _check_collisions(self):
self._check_bullet_collisions()
self._check_rock_collisions()

def _check_bullet_collisions(self):
# check for bullets that hit the ship
# bullet = pg.sprite.spritecollideany(self.ship, self.bullets)
# if bullet:
# self._ship_hit()
# * Actually, the ship's bullets should never be able to hit the ship.
# * Skipping detection for now.

rock_hits = pg.sprite.groupcollide(
self.bullets, self.rocks, True, False)
if rock_hits:
for rocks in rock_hits.values():
for rock in rocks:
print(f"Rock hit! (HP: {rock.hp})")
rock.hp -= self.settings.bullet_dmg

def check_rocks_left(self):
"""Check to see if any rocks are left in the current level"""
if len(self.rocks) == 0:
self._prepare_game() # iterates to next level
pg.time.delay(500)
print(len(self.rocks))

def _check_rock_collisions(self):
# check for rocks that hit the ship
rock = pg.sprite.spritecollideany(self.ship, self.rocks)
if rock and not self.ship.invuln:
self._ship_hit()
rock.hp -= self.settings.bullet_dmg * 4
# basically, destroy the rock that hit the ship

# check for rocks hitting each other
# rock_hits = pg.sprite.groupcollide(
# self.rocks, self.rocks, False, False, self._collide_if_not_self)
# if rock_hits:
# for left_rock, hits in rock_hits.items():
# for rock in hits:
# if not rock.ignore_collisions:
# rock.direction.reflect_ip(left_rock.pos - rock.pos)
# print("reflected")
# rock.ignore_collide()

def _collide_if_not_self(self, left, right):
if left != right:
return pg.sprite.collide_rect(left, right)
return False

def _ship_hit(self):
"""Respond to the ship being hit"""
self.stats.ships_left -= 1
self.sb.prep_ships()
if self.stats.ships_left >= 1:
# move ship back to center of screen
self.ship.center_ship()
else:
# game over!
self.stats.game_active = False
pg.mouse.set_visible(True)

def _update_screen(self):
"""Update images on the screen and flip to the new screen"""
Expand All @@ -48,7 +150,14 @@ def _update_screen(self):
self.screen.fill(self.settings.bg_color)
self._draw_stars()

self.ship.draw()
if self.stats.game_active:
self.ship.draw()
self.bullets.draw(self.screen)
self.rocks.draw(self.screen)
else:
self.play_button.draw_button()

self.sb.show_score()
# make the most recently drawn screen visible
pg.display.flip()

Expand All @@ -64,6 +173,47 @@ def _draw_stars(self):
pg.draw.circle(self.screen, self.settings.star_color,
coord, self.settings.star_size, 0) # 0 is filled

def _spawn_rocks_fixed(self):

rocks_pos = [
(self.screen_rect.centerx / 2, self.screen_rect.centery / 2),
(self.screen_rect.centerx * 2, self.screen_rect.centery * 2),
(self.screen_rect.centerx / 2, self.screen_rect.centery * 2),
(self.screen_rect.centerx * 2, self.screen_rect.centery / 2)
]
for pos in rocks_pos:
new_rock = SpaceRock(self, pos)
self.rocks.add(new_rock)

def _spawn_rocks_random(self):
for _ in range(self.settings.rocks_per_level):
pos = (
random.randint(0, self.settings.screen_size[0]),
random.randint(0, self.settings.screen_size[1])
)
new_rock = SpaceRock(self, pos)
self.rocks.add(new_rock)

def _prepare_game(self, reset=False):
"""Prepare the game"""
if reset:
self.stats.game_active = True
self.stats.reset_stats()
self.settings.initialize_dynamic_settings()
self.bullets.empty()
self.rocks.empty()
pg.mouse.set_visible(False)
self.sb.prep_score()
self.sb.prep_level()
self.sb.prep_ships()
else:
self.settings.increment_dynamic_settings()
self.stats.level += 1
self.sb.prep_level()

self._spawn_rocks_random()
self.ship.center_ship()


if __name__ == "__main__":
ast = AsteroidsGame()
Expand Down
63 changes: 63 additions & 0 deletions bullet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations
# for ide type hinting
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from asteroids import AsteroidsGame

from ast_object import AstObject
import pygame as pg
from pygame.sprite import Sprite
from pygame.math import Vector2 as vec
from pygame.transform import rotozoom


class Bullet(Sprite, AstObject):
"""A class to manage bullets fired by the ship"""

def __init__(self, game: AsteroidsGame):
"""Create a bullet at the ship's current position"""
super().__init__()
self.screen = game.screen
self.settings = game.settings
self.color = self.settings.bullet_color

# rotate the bullet to the same angle as the ship
self.original_image = pg.Surface(
(self.settings.bullet_width, self.settings.bullet_height))
self.original_image.fill(self.color)

self.image = self.original_image.copy()

self.rect = self.image.get_rect()
self.rect.center = game.ship.pos
self.pos = vec(self.rect.center)
self.direction = game.ship.direction
self.vel = self.direction * self.settings.bullet_speed
self.angle = game.ship.angle

self.image = pg.transform.rotate(self.original_image, self.angle)

self.distance_travelled = 0.0

def update(self):
self.wrap_around_screen()

# self._move()

self.pos += self.vel
self.rect.center = self.pos

# add to ship's total distance travelled
self.distance_travelled += self.vel.length()
self._check_expiration()

def _check_expiration(self):
if (self.distance_travelled >=
self.settings.bullet_speed * self.settings.bullet_lifetime):
self.kill()

def draw(self):
angle = self.direction.angle_to(self.settings.VECTOR_UP)
self.image = rotozoom(self.original_image, angle, 1)
blit_pos = self.pos - vec(self.image.get_size()) * 0.5
self.screen.blit(self.image, blit_pos)
41 changes: 41 additions & 0 deletions button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations
# for ide type hinting
from typing import TYPE_CHECKING, Tuple
if TYPE_CHECKING:
from asteroids import AsteroidsGame

import pygame as pg
from pygame.font import SysFont


class Button:
def __init__(self, game: AsteroidsGame, text):
"""Init button attributes"""

self.game = game
self.screen = game.screen
self.screen_rect = self.screen.get_rect()

# dimensions and properties of the button
self.width = 250
self.height = 50
self.button_color = (107, 208, 255)
self.text_color = (0, 0, 0)
self.font = SysFont(None, 48)

# build the rect
self.rect = pg.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center

self._prep_text(text)

def _prep_text(self, text):
self.text_image = self.font.render(
text, True, self.text_color, self.button_color)
self.text_image_rect = self.text_image.get_rect()
self.text_image_rect.center = self.rect.center

def draw_button(self):
"""Display the button"""
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.text_image, self.text_image_rect)
14 changes: 14 additions & 0 deletions game_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class GameStats:
"""Track statistics for Asteroids."""

def __init__(self, game):
self.game = game
self.reset_stats()

# start in an inactive state
self.game_active = False

def reset_stats(self):
self.score = 0
self.level = 1
self.ships_left = self.game.settings.max_ships
Binary file added media/ast-rock-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/ast-rock-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/ast-rock-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ad3da9c

Please sign in to comment.