generated from mwc/lab_retro
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:
BIN
retro/examples/__pycache__/debug.cpython-310.pyc
Normal file
BIN
retro/examples/__pycache__/debug.cpython-310.pyc
Normal file
Binary file not shown.
6
retro/examples/debug.py
Normal file
6
retro/examples/debug.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from retro.game import Game
|
||||
from retro.agent import ArrowKeyAgent
|
||||
|
||||
game = Game([ArrowKeyAgent()], {}, debug=True)
|
||||
game.play()
|
||||
|
||||
0
retro/examples/debug.py:Zone.Identifier
Normal file
0
retro/examples/debug.py:Zone.Identifier
Normal file
139
retro/examples/nav.py
Normal file
139
retro/examples/nav.py
Normal 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()
|
||||
|
||||
0
retro/examples/nav.py:Zone.Identifier
Normal file
0
retro/examples/nav.py:Zone.Identifier
Normal file
7
retro/examples/simple.py
Normal file
7
retro/examples/simple.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from retro.game import Game
|
||||
from retro.agent import ArrowKeyAgent
|
||||
|
||||
agent = ArrowKeyAgent()
|
||||
state = {}
|
||||
game = Game([agent], state)
|
||||
game.play()
|
||||
0
retro/examples/simple.py:Zone.Identifier
Normal file
0
retro/examples/simple.py:Zone.Identifier
Normal file
198
retro/examples/snake.py
Normal file
198
retro/examples/snake.py
Normal 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()
|
||||
|
||||
0
retro/examples/snake.py:Zone.Identifier
Normal file
0
retro/examples/snake.py:Zone.Identifier
Normal file
Reference in New Issue
Block a user