104 lines
2.8 KiB
Python
104 lines
2.8 KiB
Python
"""Forager: an 8×8 grid game where an agent collects food.
|
||
|
||
The agent moves in four directions collecting food that respawns on collection.
|
||
The game runs indefinitely; retro-gamer's max_turns_per_episode controls episode length.
|
||
|
||
Observation features for retro-gamer:
|
||
food_dx: (food_x - agent_x) / board_width (positive = food is to the right)
|
||
food_dy: (food_y - agent_y) / board_height (positive = food is below)
|
||
"""
|
||
|
||
from random import randint
|
||
from retro.game import Game
|
||
|
||
BOARD_SIZE = 8
|
||
|
||
|
||
class Forager:
|
||
"""The player agent."""
|
||
name = "Forager"
|
||
character = '@'
|
||
color = "green_on_black"
|
||
position = (0, 0)
|
||
|
||
UP = (0, -1)
|
||
DOWN = (0, 1)
|
||
LEFT = (-1, 0)
|
||
RIGHT = (1, 0)
|
||
|
||
def __init__(self):
|
||
self._direction = self.RIGHT
|
||
|
||
def handle_keystroke(self, keystroke, game):
|
||
if keystroke.name == "KEY_RIGHT":
|
||
self._direction = self.RIGHT
|
||
elif keystroke.name == "KEY_UP":
|
||
self._direction = self.UP
|
||
elif keystroke.name == "KEY_LEFT":
|
||
self._direction = self.LEFT
|
||
elif keystroke.name == "KEY_DOWN":
|
||
self._direction = self.DOWN
|
||
|
||
def play_turn(self, game):
|
||
x, y = self.position
|
||
dx, dy = self._direction
|
||
new_pos = (x + dx, y + dy)
|
||
if game.on_board(new_pos):
|
||
self.position = new_pos
|
||
|
||
game.state['reward'] -= 0.01
|
||
|
||
food = game.get_agent_by_name("Food")
|
||
if self.position == food.position:
|
||
food.relocate(game)
|
||
game.state['score'] += 1
|
||
game.state['reward'] += 1.0
|
||
|
||
bw, bh = game.board_size
|
||
ax, ay = self.position
|
||
fx, fy = game.get_agent_by_name("Food").position
|
||
game.state['food_dx'] = (fx - ax) / bw
|
||
game.state['food_dy'] = (fy - ay) / bh
|
||
|
||
|
||
class Food:
|
||
"""The food item. Respawns when collected."""
|
||
name = "Food"
|
||
character = '*'
|
||
color = "yellow_on_black"
|
||
position = (0, 0)
|
||
|
||
def relocate(self, game):
|
||
bw, bh = game.board_size
|
||
forager = game.get_agent_by_name("Forager")
|
||
while True:
|
||
pos = (randint(0, bw - 1), randint(0, bh - 1))
|
||
if pos != forager.position:
|
||
self.position = pos
|
||
return
|
||
|
||
|
||
def create_game():
|
||
"""Return a fresh Forager game."""
|
||
forager = Forager()
|
||
food = Food()
|
||
bw = bh = BOARD_SIZE
|
||
game = Game(
|
||
[forager, food],
|
||
{'score': 0, 'reward': 0.0, 'food_dx': 0.0, 'food_dy': 0.0},
|
||
board_size=(bw, bh),
|
||
framerate=12,
|
||
)
|
||
forager.position = (randint(0, bw - 1), randint(0, bh - 1))
|
||
food.relocate(game)
|
||
bw, bh = game.board_size
|
||
ax, ay = forager.position
|
||
fx, fy = food.position
|
||
game.state['food_dx'] = (fx - ax) / bw
|
||
game.state['food_dy'] = (fy - ay) / bh
|
||
return game
|
||
|
||
|
||
if __name__ == '__main__':
|
||
create_game().play()
|