When I run the game, it works for a bit but then when the first asteroid

gets to the bottom I get this:

Traceback (most recent call last):
  File "/root/making_with_code/mwc1/unit3/lab_retro/nav_game.py", line 14, in <module>
    game.play()
  File "/root/making_with_code/mwc1/unit3/lab_retro/retro/game.py", line 80, in play
    agent.play_turn(self)
  File "/root/making_with_code/mwc1/unit3/lab_retro/asteroid.py", line 16, in play_turn
    game.remove_agent_by_name(self.name)
AttributeError: 'Asteroid' object has no attribute 'name'
This commit is contained in:
root
2024-12-12 16:42:57 -05:00
parent 2ce382cfb6
commit 52c1128ed4
82 changed files with 5972 additions and 0 deletions

Binary file not shown.

6
retro/examples/debug.py Normal file
View File

@@ -0,0 +1,6 @@
from retro.game import Game
from retro.agent import ArrowKeyAgent
game = Game([ArrowKeyAgent()], {}, debug=True)
game.play()

View File

139
retro/examples/nav.py Normal file
View File

@@ -0,0 +1,139 @@
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()

View File

7
retro/examples/simple.py Normal file
View File

@@ -0,0 +1,7 @@
from retro.game import Game
from retro.agent import ArrowKeyAgent
agent = ArrowKeyAgent()
state = {}
game = Game([agent], state)
game.play()

View File

198
retro/examples/snake.py Normal file
View File

@@ -0,0 +1,198 @@
from random import randint
from retro.game import Game
class Apple:
"""An agent representing the Apple.
Note how Apple doesn't have ``play_turn`` or
``handle_keystroke`` methods: the Apple doesn't need to do
anything in this game. It just sits there waiting to get
eaten.
Attributes:
name: "Apple"
character: '@'
color: "red_on_black" (`Here's documentation on how colors
work <https://blessed.readthedocs.io/en/latest/colors.html>`_
position: (0, 0). The Apple will choose a random position
as soon as the game starts, but it needs an initial
position to be assigned.
"""
name = "Apple"
character = '@'
color = "red_on_black"
position = (0, 0)
def relocate(self, game):
"""Sets position to a random empty position. This method is
called whenever the snake's head touches the apple.
Arguments:
game (Game): The current game.
"""
self.position = self.random_empty_position(game)
def random_empty_position(self, game):
"""Returns a randomly-selected empty position. Uses a very
simple algorithm: Get the game's board size, choose a
random x-value between 0 and the board width, and choose
a random y-value between 0 and the board height. Now use
the game to check whether any Agents are occupying this
position. If so, keep randomly choosing a new position
until the position is empty.
"""
bw, bh = game.board_size
occupied_positions = game.get_agents_by_position()
while True:
position = (randint(0, bw-1), randint(0, bh-1))
if position not in occupied_positions:
return position
class SnakeHead:
"""An Agent representing the snake's head. When the game starts, you control
the snake head using the arrow keys. The SnakeHead always has a direction, and
will keep moving in that direction every turn. When you press an arrow key,
you change the SnakeHead's direction.
Attributes:
name: "Snake head"
position: (0,0)
character: ``'v'`` Depending on the snake head's direction, its character
changes to ``'<'``, ``'^'``, ``'>'``, or ``'v'``.
next_segment: Initially ``None``, this is a reference to a SnakeBodySegment.
growing: When set to True, the snake will grow a new segment on its next move.
"""
RIGHT = (1, 0)
UP = (0, -1)
LEFT = (-1, 0)
DOWN = (0, 1)
name = "Snake head"
position = (0, 0)
direction = DOWN
character = 'v'
next_segment = None
growing = False
def play_turn(self, game):
"""On each turn, the snake head uses its position and direction to figure out
its next position. If the snake head is able to move there (it's on the board and
not occuppied by part of the snake's body), it moves.
Then, if the snake head is on the Apple, the Apple moves to a new random position
and ``growing`` is set to True.
Now we need to deal with two situations. First, if ``next_segment`` is not None, there is
a SnakeBodySegment attached to the head. We need the body to follow the head,
so we call ``self.next_segment.move``, passing the head's old position
(this will be the body's new position), a reference to the game, and a value for
``growing``. If the snake needs to grow, we need to pass this information along
the body until it reaches the tail--this is where the next segment will be attached.
If there is no ``next_segment`` but ``self.growing`` is True, it's time to add
a body! We set ``self.next_segment`` to a new SnakeBodySegment, set its
position to the head's old position, and add it to the game. We also add 1 to the
game's score.
"""
x, y = self.position
dx, dy = self.direction
if self.can_move((x+dx, y+dy), game):
self.position = (x+dx, y+dy)
if self.is_on_apple(self.position, game):
apple = game.get_agent_by_name("Apple")
apple.relocate(game)
self.growing = True
if self.next_segment:
self.next_segment.move((x, y), game, growing=self.growing)
elif self.growing:
self.next_segment = SnakeBodySegment(1, (x, y))
game.add_agent(self.next_segment)
game.state['score'] += 1
self.growing = False
def handle_keystroke(self, keystroke, game):
"""Checks whether one of the arrow keys has been pressed.
If so, sets the SnakeHead's direction and character.
"""
x, y = self.position
if keystroke.name == "KEY_RIGHT":
self.direction = self.RIGHT
self.character = '>'
elif keystroke.name == "KEY_UP":
self.direction = self.UP
self.character = '^'
elif keystroke.name == "KEY_LEFT":
self.direction = self.LEFT
self.character = '<'
elif keystroke.name == "KEY_DOWN":
self.direction = self.DOWN
self.character = 'v'
def can_move(self, position, game):
on_board = game.on_board(position)
empty = game.is_empty(position)
on_apple = self.is_on_apple(position, game)
return on_board and (empty or on_apple)
def is_on_apple(self, position, game):
apple = game.get_agent_by_name("Apple")
return apple.position == position
class SnakeBodySegment:
"""Finally, we need an Agent for the snake's body segments.
SnakeBodySegment doesn't have ``play_turn`` or ``handle_keystroke`` methods because
it never does anything on its own. It only moves when the SnakeHead, or the previous
segment, tells it to move.
Arguments:
segment_id (int): Keeps track of how far back this segment is from the head.
This is used to give the segment a unique name, and also to keep track
of how many points the player earns for eating the next apple.
position (int, int): The initial position.
Attributes:
character: '*'
next_segment: Initially ``None``, this is a reference to a SnakeBodySegment
when this segment is not the last one in the snake's body.
"""
character = '*'
next_segment = None
def __init__(self, segment_id, position):
self.segment_id = segment_id
self.name = f"Snake body segment {segment_id}"
self.position = position
def move(self, new_position, game, growing=False):
"""When SnakeHead moves, it sets off a chain reaction, moving all its
body segments. Whenever the head or a body segment has another segment
(``next_segment``), it calls that segment's ``move`` method.
This method updates the SnakeBodySegment's position. Then, if
``self.next_segment`` is not None, calls that segment's ``move`` method.
If there is no next segment and ``growing`` is True, then we set
``self.next_segment`` to a new SnakeBodySegment in this segment's old
position, and update the game's score.
Arguments:
new_position (int, int): The new position.
game (Game): A reference to the current game.
growing (bool): (Default False) When True, the snake needs to
add a new segment.
"""
old_position = self.position
self.position = new_position
if self.next_segment:
self.next_segment.move(old_position, game, growing=growing)
elif growing:
self.next_segment = SnakeBodySegment(self.segment_id + 1, old_position)
game.add_agent(self.next_segment)
game.state['score'] += self.segment_id + 1
if __name__ == '__main__':
head = SnakeHead()
apple = Apple()
game = Game([head, apple], {'score': 0}, board_size=(32, 16), framerate=12)
apple.relocate(game)
game.play()

View File