generated from mwc/lab_retro
140 lines
5.1 KiB
Python
140 lines
5.1 KiB
Python
from random import randint
|
|
from retro.game import Game
|
|
|
|
HEIGHT = 25
|
|
WIDTH = 25
|
|
|
|
class Spaceship:
|
|
"""A player-controlled agent which moves left and right, dodging asteroids.
|
|
Spaceship is a pretty simple class. The ship's character is ``^``, and
|
|
its position starts at the bottom center of the screen.
|
|
"""
|
|
name = "ship"
|
|
character = '^'
|
|
position = (WIDTH // 2, HEIGHT - 1)
|
|
color = "black_on_skyblue1"
|
|
|
|
def handle_keystroke(self, keystroke, game):
|
|
"""When the
|
|
left or arrow key is pressed, it moves left or right. If the ship's
|
|
new position is empty, it moves to that position. If the new position
|
|
is occupied (by an asteroid!) the game ends.
|
|
"""
|
|
x, y = self.position
|
|
if keystroke.name in ("KEY_LEFT", "KEY_RIGHT"):
|
|
if keystroke.name == "KEY_LEFT":
|
|
new_position = (x - 1, y)
|
|
else:
|
|
new_position = (x + 1, y)
|
|
if game.on_board(new_position):
|
|
if game.is_empty(new_position):
|
|
self.position = new_position
|
|
else:
|
|
self.explode()
|
|
game.end()
|
|
|
|
def explode(self):
|
|
"""Sets the ship's character to ``*`` and its color to red.
|
|
"""
|
|
self.color = "crimson_on_skyblue1"
|
|
self.character = '*'
|
|
|
|
class Asteroid:
|
|
"""When Asteroids are spawned, they fall down the screen until they
|
|
reach the bottom row and are removed.
|
|
An Asteroid's position is set when it is created.
|
|
Whenever an asteroid moves, it
|
|
checks whether it has it the ship.
|
|
"""
|
|
character = 'O'
|
|
color = "deepskyblue1_on_skyblue1"
|
|
|
|
def __init__(self, position):
|
|
self.position = position
|
|
|
|
def play_turn(self, game):
|
|
"""Nothing happens unless
|
|
``game.turn_number`` is divisible by 2. The result is that asteroids
|
|
only move on even-numbered turns. If the asteroid is at the bottom of
|
|
the screen, it has run its course and should be removed from the game.
|
|
Otherwise, the asteroid's new position is one space down from its old
|
|
position. If the asteroid's new position is the same as the ship's
|
|
position, the game ends.
|
|
"""
|
|
if game.turn_number % 2 == 0:
|
|
self.set_color()
|
|
x, y = self.position
|
|
if y == HEIGHT - 1:
|
|
game.remove_agent(self)
|
|
else:
|
|
ship = game.get_agent_by_name('ship')
|
|
new_position = (x, y + 1)
|
|
if new_position == ship.position:
|
|
ship.explode()
|
|
game.end()
|
|
else:
|
|
self.position = new_position
|
|
|
|
def set_color(self):
|
|
"""To add to the game's drama, asteroids gradually become visible as they
|
|
fall down the screen. This method calculates the ratio of the asteroid's
|
|
position compared to the screen height--0 is the top of the screen and 1 is
|
|
the bottom ot the screen. Then sets the asteroid's color depending on the
|
|
ratio. (`Available colors <https://blessed.readthedocs.io/en/latest/colors.html>`_)
|
|
"""
|
|
x, y = self.position
|
|
ratio = y / HEIGHT
|
|
if ratio < 0.2:
|
|
self.color = "deepskyblue1_on_skyblue1"
|
|
elif ratio < 0.4:
|
|
self.color = "deepskyblue2_on_skyblue1"
|
|
elif ratio < 0.6:
|
|
self.color = "deepskyblue3_on_skyblue1"
|
|
else:
|
|
self.color = "deepskyblue4_on_skyblue1"
|
|
|
|
class AsteroidSpawner:
|
|
"""An agent which is not displayed on the board, but which constantly spawns
|
|
asteroids.
|
|
"""
|
|
display = False
|
|
|
|
def play_turn(self, game):
|
|
"""Adds 1 to the game score and then uses
|
|
:py:meth:`~retro.examples.nav.should_spawn_asteroid` to decide whether to
|
|
spawn an asteroid. When :py:meth:`~retro.examples.nav.should_spawn_asteroid`
|
|
comes back ``True``, creates a new instance of
|
|
:py:class:`~retro.examples.nav.Asteroid` at a random position along the
|
|
top of the screen and adds the asteroid to the game.
|
|
"""
|
|
game.state['score'] += 1
|
|
if self.should_spawn_asteroid(game.turn_number):
|
|
asteroid = Asteroid((randint(0, WIDTH - 1), 0))
|
|
game.add_agent(asteroid)
|
|
|
|
def should_spawn_asteroid(self, turn_number):
|
|
"""Decides whether to spawn an asteroid.
|
|
Uses a simple but effective algorithm to make the game get
|
|
progressively more difficult: choose a random number and return
|
|
``True`` if the number is less than the current turn number. At
|
|
the beginning of the game, few asteroids will be spawned. As the
|
|
turn number climbs toward 1000, asteroids are spawned almost
|
|
every turn.
|
|
|
|
Arguments:
|
|
turn_number (int): The current turn in the game.
|
|
"""
|
|
return randint(0, 1000) < turn_number
|
|
|
|
if __name__ == '__main__':
|
|
ship = Spaceship()
|
|
spawner = AsteroidSpawner()
|
|
game = Game(
|
|
[ship, spawner],
|
|
{"score": 0},
|
|
board_size=(WIDTH, HEIGHT),
|
|
color="deepskyblue4_on_skyblue1",
|
|
)
|
|
game.play()
|
|
|