Files
lab_reinforcement_learning/forager/__init__.py
Chris Proctor 3d42de7b12 Give forager an energy mechanic so episodes terminate naturally
Previously the game ran indefinitely and relied entirely on
max_turns_per_episode to end an episode. Add start/food energy values
so an episode ends on its own when energy runs out, and mention
retro-gamer plot in the lab's training log instructions.
2026-06-24 07:50:14 -04:00

112 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Forager: an 8×8 grid game where an agent collects food.
The agent moves in four directions collecting food that respawns on collection.
The agent starts with 100 energy. Each step costs 1 energy. Collecting food
restores 40 energy. The episode ends when energy reaches 0.
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
START_ENERGY = 100
FOOD_ENERGY = 40
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['energy'] -= 1
game.state['reward'] -= 0.01
food = game.get_agent_by_name("Food")
if self.position == food.position:
food.relocate(game)
game.state['energy'] += FOOD_ENERGY
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
if game.state['energy'] <= 0:
game.end()
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, 'energy': START_ENERGY, '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()