Imported from markusos/llmania (
AGENTS.md). Install upstream withnpx skills add markusos/llmania. Copyright stays with the author.
Agent Development Guide for Text-Based Adventure Game
This document provides guidance for AI agents (like Jules) working on this text-based adventure game codebase.
Linting and Testing
This project uses ruff for code linting and formatting, and pytest for running unit tests. Ensure your changes pass linting checks and that all tests pass before submitting.
-
Ruff (Linting & Formatting):
- To check for linting errors:
ruff check . - To automatically format code:
ruff format . - It's recommended to run these commands from the project root. Configuration for
ruffcan be found inpyproject.toml.
- To check for linting errors:
-
Pytest (Unit Testing):
- To run all unit tests:
pytest - Tests are located in the
tests/directory. - Ensure any new code is accompanied by corresponding tests, and that existing tests are updated if necessary.
- To run all unit tests:
Core Architecture
The game is structured around a central GameEngine (src/game_engine.py) that manages the main game loop, game state, and interactions between various components. Key components include:
GameEngine: Orchestrates the game. It initializes all other major components, runs the game loop, and manages updates to the game state.Player(src/player.py): Represents the player character, including their position, health, and inventory.WorldMap(src/world_map.py): Represents the game world, composed ofTileobjects. It stores the layout of the map, including walls, open spaces, items, and monsters.Tile(src/tile.py): A single unit of theWorldMap. Tiles can be of different types (e.g., wall, floor) and can containItemobjects orMonsterobjects.CommandProcessor(src/command_processor.py): Takes player input (parsed into a verb and optional argument) and dispatches it to the appropriate command object.commands/directory: Contains all player-executable commands. Each command is a class inheriting fromCommand(src/commands/base_command.py).Renderer(src/renderer.py): Handles displaying the game state to the user. It uses thecurseslibrary for terminal-based graphics but can also render to a list of strings for debugging.InputHandler(src/input_handler.py): Captures raw keyboard input from the player.Parser(src/parser.py): Converts raw player input strings into structured command tuples (e.g.,("move", "north")).MessageLog(src/message_log.py): Manages and stores messages to be displayed to the player (e.g., results of actions, descriptions).WorldGenerator(src/world_generator.py): Responsible for procedurally creating the game map.AILogic(src/ai_logic.py): Contains logic for an AI to play the game, making decisions about which commands to execute.main.py(src/main.py): The entry point for the game. It handles command-line arguments (like--debugor--ai) and starts theGameEngine.
Adding New Commands
To add a new command (e.g., "search"):
- Create a new command class:
- In the
src/commands/directory, create a new Python file (e.g.,search_command.py). - Define a class (e.g.,
SearchCommand) that inherits fromCommand(fromsrc/commands/base_command.py). - Implement the
__init__method (callingsuper().__init__(...)) if you need to store the argument or have specific initialization. - Implement the
execute(self) -> Dict[str, Any]method. This method contains the logic for your command.- It should interact with
self.player,self.world_map, andself.message_logas needed. - It must return a dictionary, typically
{"game_over": False}. If the command can end the game, it should return{"game_over": True}.
- It should interact with
- In the
- Register the command:
- Open
src/command_processor.py. - Import your new command class (e.g.,
from src.commands.search_command import SearchCommand). - In the
CommandProcessor.__init__method, add your command to theself._commandsdictionary. The key is the command verb (lowercase string users will type) and the value is the command class itself (e.g.,"search": SearchCommand).
- Open
- Update
src/commands/__init__.py:- Export your new command class by adding a line like
from .search_command import SearchCommand. This makes it easier to import elsewhere.
- Export your new command class by adding a line like
Interacting with Key Components
GameEngine:- Generally, commands don't interact directly with the
GameEngineitself, but rather with components passed to them by theGameEnginevia theCommandProcessor(likeplayer,world_map,message_log). - The
GameEnginehandles the fog of war (_update_fog_of_war_visibility) and manages avisible_mapthat is passed to theRenderer. Commands operate on theworld_map(the true state of the world).
- Generally, commands don't interact directly with the
WorldMap:- Use
world_map.get_tile(x, y)to get aTileobject at specific coordinates. - Use
world_map.is_blocked(x, y)to check if a tile is a wall or occupied by a monster. - Tiles have
itemandmonsterattributes, which can beNoneor an instance ofItemorMonster.
- Use
Player:- Access player's position via
player.x,player.y. - Modify player's health via
player.health. - Manage inventory using
player.inventory(a list ofItemobjects), and methods likeplayer.add_item()andplayer.remove_item().
- Access player's position via
MessageLog:- Use
message_log.add_message("Your message here")to display information to the player. These messages are typically shown by theRenderer.
- Use
Debug Mode
- The game can be run in debug mode by passing the
--debugflag when runningsrc/main.py(e.g.,python src/main.py --debug). - In debug mode:
- The
cursesinterface is not initialized. Game output is printed to the console. - The
GameEngineis initialized withdebug_mode=True. - The
Rendererwill output the game state as a list of strings instead of drawing to the screen. main_debug()insrc/main.pyprovides an example of how to run specific commands and inspect state in this mode. This is useful for testing game logic without UI complexities.
- The
Map Algorithms (src/map_algorithms/)
This directory contains algorithms for map analysis:
connectivity.py: Checks if all floor tiles on a map are reachable from a starting point.density.py: Calculates the proportion of wall tiles on the map.pathfinding.py: Implements A* pathfinding to find routes between points on the map.
These can be used for more advanced world generation, AI decision-making, or game mechanics.
Rendering and curses
- The
Renderer(src/renderer.py) is responsible for all visual output. - When not in
debug_mode, it uses thecurseslibrary to create a terminal-based graphical interface. GameEngine.run()handles the main loop and callsrenderer.render_all()to draw the current game state.- The
Renderertakes care of initializing and cleaning up thecursesenvironment. If you are making changes to rendering or the main game loop, be mindful ofcursesstate management (seerenderer.cleanup_curses()).
Testing
- The
tests/directory contains unit tests for various components. - When adding new features or modifying existing ones, ensure corresponding tests are added or updated.
- You can run tests using a test runner like
pytest(which should be configured viapyproject.tomlanduv).
AI System Architecture
The game includes a Utility-Based AI system that can control the player character.
Running with AI
To run the game with AI:
python src/main.py --ai --seed 42
AI Components (src/ai_logic/)
AILogic(main.py): Main AI controller that orchestrates decision-making using utility-based action selection.AIContext(context.py): Immutable snapshot of game state passed to utility actions for decision-making.UtilityCalculator(utility_calculator.py): Selects and executes the highest-utility action from available actions.actions/directory: Contains utility-based action classes:HealAction: Use healing items when health is lowFleeAction: Escape from monsters when in dangerAttackAction: Attack adjacent monstersUseCombatItemAction: Use combat items (fire potions, etc.)PickupItemAction: Pick up items on current tileEquipAction: Equip better gear from inventoryPathToHealthAction,PathToWeaponAction,PathToArmorAction,PathToQuestAction,PathToPortalAction,PathToLootAction: Path to various targetsExploreAction: Explore unexplored areasRandomMoveAction: Random movement as fallback/loop breaker
Adding New AI Actions (Utility-Based)
-
Create a new action class in
src/ai_logic/actions/that inherits fromAIAction:from .base_action import AIAction class MyNewAction(AIAction): @property def name(self) -> str: return "MyNewAction" def is_available(self, ctx: "AIContext") -> bool: # Return True if this action can be executed return True def calculate_utility(self, ctx: "AIContext") -> float: # Return utility score 0.0-1.0 based on context return 0.5 def execute(self, ctx, ai_logic, message_log): # Execute the action and return command tuple return ("command", "argument") -
Register the action in
utility_calculator.py'screate_default_utility_calculator()function. -
Add tests in
tests/ai_logic/actions/.
Reproducibility
Important: All randomness in the AI must use the seeded Random instance passed through the context (ctx.random) or ai_logic.random. Do NOT use import random directly, as this breaks reproducibility when running with --seed.
Benchmarking
Use benchmark.py to measure AI performance:
python benchmark.py --seeds 500 --timeout 5
By following these guidelines, you can effectively contribute to the development of this game.