Skip to content

Commit

Permalink
Added the ability to set ship starting position/angle/lives via Scena…
Browse files Browse the repository at this point in the history
…rio.

ShipSprite tracks its own lives now and can be specified via constructor
  • Loading branch information
brandonmkunkel committed Mar 9, 2021
1 parent b956ba3 commit dd38d82
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 32 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [1.1.3] - 09 - March 2021

- Enabled specification of starting position/angle of the ShipSprite
- Enabled the tracking of lives within the ShipSprite, rather than in the Game environment
- Crash printouts now state crash time and position

## [1.1.2] - 08 - March 2021

- Resolved error where the final ship crash is not printed
Expand Down
2 changes: 1 addition & 1 deletion src/fuzzy_asteroids/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.2"
__version__ = "1.1.3"
25 changes: 11 additions & 14 deletions src/fuzzy_asteroids/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def __init__(self, settings: Dict[str, Any] = None):

# Set up the game instance
self.game_over = None
self.life_count = None
self.player_sprite = None

# Evaluation analytics
Expand Down Expand Up @@ -131,7 +130,6 @@ def start_new_game(self, scenario: Scenario = None, score: Score = None) -> None
self.score.max_asteroids = self.scenario.max_asteroids

# Set trackers used for game over checks
self.life_count = self.lives
self.game_over = StoppingCondition.none

# Sprite lists
Expand All @@ -141,13 +139,13 @@ def start_new_game(self, scenario: Scenario = None, score: Score = None) -> None
self.ship_life_list = arcade.SpriteList()

# Set up the player
self.player_sprite = ShipSprite(self.frequency, self.scenario.game_map.center)
self.player_sprite = ShipSprite(self.frequency, **self.scenario.ship_state)
self.player_sprite_list.append(self.player_sprite)

# Set up the little icons that represent the player lives.
if self.graphics_on:
cur_pos = 50
for i in range(self.life_count):
for i in range(self.player_sprite.lives):
life = arcade.Sprite(":resources:images/space_shooter/playerLife1_orange.png", SCALE)
life.center_x = cur_pos + life.width
life.center_y = life.height + 5
Expand Down Expand Up @@ -249,15 +247,15 @@ def split_asteroid(self, asteroid: AsteroidSprite) -> None:
self.score.asteroids_hit += 1

if asteroid.size == 4:
self.asteroid_list.extend([AsteroidSprite(frequency=self.frequency, parent_asteroid=asteroid) for i in range(3)])
self.asteroid_list.extend([AsteroidSprite(frequency=self.frequency, position=asteroid.position, size=asteroid.size-1) for i in range(3)])
self._play_sound(self.hit_sound1)

elif asteroid.size == 3:
self.asteroid_list.extend([AsteroidSprite(frequency=self.frequency, parent_asteroid=asteroid) for i in range(3)])
self.asteroid_list.extend([AsteroidSprite(frequency=self.frequency, position=asteroid.position, size=asteroid.size-1) for i in range(3)])
self._play_sound(self.hit_sound2)

elif asteroid.size == 2:
self.asteroid_list.extend([AsteroidSprite(frequency=self.frequency, parent_asteroid=asteroid) for i in range(3)])
self.asteroid_list.extend([AsteroidSprite(frequency=self.frequency, position=asteroid.position, size=asteroid.size-1) for i in range(3)])
self._play_sound(self.hit_sound3)

elif asteroid.size == 1:
Expand Down Expand Up @@ -307,11 +305,11 @@ def on_update(self, delta_time: float = 1/60) -> None:

# Check if there are ship-asteroid collisions detected
if len(asteroids) > 0:
self._print_terminal("Crash")
self._print_terminal(f"Crashed at {self.player_sprite.position}, t={self.score.time:.3f} seconds")

if self.life_count > 1:
if self.player_sprite.lives > 1:
self.score.deaths += 1
self.life_count -= 1
self.player_sprite.destroy()
self.player_sprite.respawn(self.scenario.game_map.center)
self.split_asteroid(cast(AsteroidSprite, asteroids[0]))

Expand All @@ -329,10 +327,9 @@ def on_update(self, delta_time: float = 1/60) -> None:
self.score.final_update(environment=self)
self.score.stopping_condition = self.game_over

self._print_terminal("******************************************************")
self._print_terminal(f"Game over ({self.game_over})")
self._print_terminal("******************************************************")
self._print_terminal("Game Score: " + str(self.score))
self._print_terminal("**********************************************************")
self._print_terminal(f"Game over at {self.score.time:.3f} seconds | ({self.game_over}) ")
self._print_terminal("**********************************************************")

if self.graphics_on:
pyglet.app.exit()
Expand Down
47 changes: 31 additions & 16 deletions src/fuzzy_asteroids/sprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ class ShipSprite(arcade.Sprite):
Derives from arcade.Sprite.
"""
def __init__(self, frequency: float, position: Tuple[float, float]):
def __init__(self, frequency: float, position: Tuple[float, float], angle: float = 0.0, lives: int = 3):
"""
Instantiate a ShipSprite
:param frequency: Frequency for rate based update mechanics
:param position: Starting position of the
:param angle: Starting angle of the ShipSprite
:param lives: Number of starting lives
"""
""" Set up the space ship. """

# Call the parent Sprite constructor
Expand All @@ -75,6 +83,9 @@ def __init__(self, frequency: float, position: Tuple[float, float]):
self.max_speed = 240 # Meters per second
self.turn_rate = 0

# Lives
self.lives = lives

# Limitations to controllers
self.thrust_range = (-480.0, 480.0) # m/s^2
self.turn_rate_range = (-180.0, 180.0) # Degrees per second
Expand All @@ -89,7 +100,7 @@ def __init__(self, frequency: float, position: Tuple[float, float]):
self._fire_time = 1 / 10 # seconds

# Mark that we are respawning.
self.respawn(position)
self.respawn(position, angle)

@property
def state(self) -> Dict[str, Any]:
Expand Down Expand Up @@ -117,7 +128,7 @@ def respawn_time(self) -> float:
return self._respawn_time

@property
def can_fire(self):
def can_fire(self) -> bool:
return not self._fire_limiter

@property
Expand All @@ -129,23 +140,29 @@ def fire_wait_time(self) -> float:
return self._fire_limiter

@property
def half_width(self):
def half_width(self) -> float:
return self.width / 2.0

@property
def half_height(self):
def half_height(self) -> float:
return self.height / 2.0

def respawn(self, map_center: Tuple[float, float]):
def destroy(self) -> None:
"""
Destroys the current ship (reducing lives by one)
"""
self.lives -= 1

def respawn(self, position: Tuple[float, float], angle: float = 0.0) -> None:
"""
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 = self._respawn_time
self.center_x, self.center_y = map_center
self.center_x, self.center_y = position
self.speed = 0
self.angle = 0
self.angle = angle

def fire_bullet(self) -> BulletSprite:
# Fire a bullet, starting at this sprite's position/angle
Expand Down Expand Up @@ -224,13 +241,13 @@ def on_update(self, delta_time: float = 1/60):

class AsteroidSprite(arcade.Sprite):
""" Sprite that represents an asteroid. """
def __init__(self, frequency: float, parent_asteroid=None, position: Tuple[float, float] = None,
def __init__(self, frequency: float, position: Tuple[float, float] = None,
speed: float = None, angle: float = None, size: float = None):
"""
Constructor for Asteroid Sprite
:param parent_asteroid: Optional AsteroidSprite which this AsteroidSprite spawns from
:param position: Optional Starting poisition (x, y) position
:param frequency: Operating frequency for rate based model dynamics
:param position: Optional Starting position (x, y) position
:param speed: Optional Starting Speed
:param angle: Optional Starting heading angle (degrees)
:param size: Optional Starting size (1 to 4 inclusive)
Expand All @@ -240,8 +257,6 @@ def __init__(self, frequency: float, parent_asteroid=None, position: Tuple[float
self.size = size
else:
raise ValueError("AsteroidSize can only be between 1 and 4")
elif parent_asteroid:
self.size = parent_asteroid.size - 1
else:
self.size = 4

Expand Down Expand Up @@ -283,7 +298,7 @@ def __init__(self, frequency: float, parent_asteroid=None, position: Tuple[float

# Use parent position as starting point if this asteroid is starting form a parent
# Otherwise use the position given
self.center_x, self.center_y = parent_asteroid.position if parent_asteroid else position
self.center_x, self.center_y = position

@property
def state(self) -> Dict[str, Tuple[float, float]]:
Expand All @@ -296,11 +311,11 @@ def state(self) -> Dict[str, Tuple[float, float]]:
}

@property
def half_width(self):
def half_width(self) -> float:
return self.width / 2.0

@property
def half_height(self):
def half_height(self) -> float:
return self.height / 2.0

def on_update(self, delta_time: float = 1/60):
Expand Down
4 changes: 3 additions & 1 deletion src/fuzzy_asteroids/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def center(self) -> Tuple[float, float]:

class Scenario:
def __init__(self, num_asteroids: int = 0, asteroid_states: List[Dict[str, Any]] = None,
game_map: Map = None, seed: int = None):
ship_state: Dict[str, Any] = None, game_map: Map = None, seed: int = None):
"""
Specify the starting state of the environment, including map dimensions and optional features
Expand All @@ -124,13 +124,15 @@ def __init__(self, num_asteroids: int = 0, asteroid_states: List[Dict[str, Any]]
:param num_asteroids: Optional, Number of asteroids
:param asteroid_states: Optional, Asteroid Starting states
:param ship: Optional, Ship Starting state
:param game_map: Game Map using ``Map`` object
:param seed: Optional seeding value to pass to random.seed() which is called before asteroid creation
"""
self.asteroid_states = list()

# Store Map
self.game_map = game_map if game_map else Map()
self.ship_state = ship_state if ship_state else {"position": self.game_map.center, "angle": 0.0}

# Store seed
self.seed = seed
Expand Down

0 comments on commit dd38d82

Please sign in to comment.