Refactor CLI/interface: init command, factory field, remove extras_size
- Rename `retro-gamer create` to `retro-gamer init` with positional args (GAME OUTPUT) instead of --game/--output flags - Add [tool.retro-gamer].factory = "module:attr" to declare the game factory function from pyproject.toml instead of relying on a hard-coded create_game attribute - Remove extras_size from user-facing config; it is now measured automatically from a sample observation and never declared - Update docs throughout: create→init, runs/→training/, add factory field documentation, clarify [model] vs [training] hyperparameter sections, remove stale version-history explanations - Bump version to 0.3.0; require retro-games>=2.5.0
This commit is contained in:
@@ -12,15 +12,18 @@ class GameMetadata:
|
||||
"""Describes a retro game for training purposes.
|
||||
|
||||
Required fields: actions, reward.
|
||||
Optional fields: character_set, spatial, observation_function.
|
||||
Optional fields: character_set, spatial, observation_function, factory.
|
||||
Discovered fields: board_size (from game.board_size), extras_size
|
||||
(computed by DQNTrainer from one sampled observation — never set in a
|
||||
game's own pyproject.toml).
|
||||
(measured by DQNTrainer from one sampled observation — never declared).
|
||||
|
||||
observation_function, if set, is a "module:attr" string naming a function
|
||||
``f(game) -> Any`` that fully replaces the built-in board/observe_state
|
||||
encoding. It is mutually exclusive with the [preprocessing] observe_state
|
||||
option. See GameEnvironment for how the two paths are selected.
|
||||
|
||||
factory, if set, is a "module:attr" string naming the create_game function.
|
||||
Read from [tool.retro-gamer].factory in pyproject.toml. When absent,
|
||||
the loader falls back to looking for create_game on the game module.
|
||||
"""
|
||||
actions: list[str]
|
||||
reward: str
|
||||
@@ -29,6 +32,7 @@ class GameMetadata:
|
||||
board: bool = True
|
||||
board_size: tuple[int, int] | None = None
|
||||
observation_function: str | None = None
|
||||
factory: str | None = None
|
||||
extras_size: int = 0
|
||||
|
||||
def validate(self):
|
||||
@@ -76,6 +80,41 @@ class GameMetadata:
|
||||
"This should name a function f(game) -> observation that fully\n"
|
||||
"describes what your agent observes each turn."
|
||||
)
|
||||
if self.factory is not None:
|
||||
if not isinstance(self.factory, str) or ':' not in self.factory:
|
||||
raise ValueError(
|
||||
f"'factory' must be a string of the form 'module:attr', "
|
||||
f"but got: {self.factory!r}\n"
|
||||
"Example: factory = \"my_game:create_game\"\n"
|
||||
"This should name a function that takes no arguments and returns a Game."
|
||||
)
|
||||
|
||||
def resolve_factory(self) -> Callable | None:
|
||||
"""Import and return the factory function named by the factory field, or None if unset."""
|
||||
if self.factory is None:
|
||||
return None
|
||||
if ':' not in self.factory:
|
||||
raise ValueError(
|
||||
f"'factory' must be of the form 'module:attr', but got "
|
||||
f"{self.factory!r} (no ':' found).\n"
|
||||
"Example: factory = \"my_game:create_game\""
|
||||
)
|
||||
module_name, attr_name = self.factory.split(':', 1)
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except ImportError as e:
|
||||
raise ValueError(
|
||||
f"Could not import module {module_name!r} for factory "
|
||||
f"{self.factory!r}: {e}"
|
||||
) from e
|
||||
try:
|
||||
return getattr(module, attr_name)
|
||||
except AttributeError:
|
||||
raise ValueError(
|
||||
f"Module {module_name!r} has no attribute {attr_name!r} "
|
||||
f"(from factory = {self.factory!r}).\n"
|
||||
f"Define a function named '{attr_name}' in {module_name}."
|
||||
) from None
|
||||
|
||||
def resolve_observation_function(self) -> Callable | None:
|
||||
"""Import and return the function named by observation_function, or None if unset.
|
||||
@@ -153,14 +192,13 @@ class GameMetadata:
|
||||
spatial=d.get('spatial', False),
|
||||
board_size=board_size,
|
||||
observation_function=d.get('observation_function'),
|
||||
extras_size=d.get('extras_size', 0),
|
||||
factory=d.get('factory'),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = {
|
||||
'actions': self.actions,
|
||||
'reward': self.reward,
|
||||
'extras_size': self.extras_size,
|
||||
}
|
||||
if self.board_size is not None:
|
||||
d['board_size'] = list(self.board_size)
|
||||
|
||||
Reference in New Issue
Block a user