Skip to content

Commit e55b885

Browse files
committed
feat: add logic to load/save strategy manifest
1 parent 14978aa commit e55b885

2 files changed

Lines changed: 90 additions & 17 deletions

File tree

src/balatrollm/bot.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
)
2020
from openai.types.chat import ChatCompletion
2121

22-
from .config import Config, load_model_config
22+
from .config import Config, StrategyManifest, load_model_config
2323
from .data_collection import ChatCompletionError, ChatCompletionResponse, StatsCollector
2424
from .strategies import StrategyManager
2525

@@ -390,6 +390,13 @@ async def _init_game(self, base_dir: Path = Path.cwd()) -> dict[str, Any]:
390390
"""
391391
self.data_collector = StatsCollector(self.config, base_dir)
392392
self.config.to_config_file(self.data_collector.run_dir / "config.json")
393+
394+
# Load and save strategy manifest
395+
strategy_manifest = StrategyManifest.from_manifest_file(self.config.strategy)
396+
strategy_manifest.to_strategy_file(
397+
self.data_collector.run_dir / "strategy.json"
398+
)
399+
393400
logger.info(f"Run data will be saved to: {self.data_collector.run_dir}")
394401

395402
# Set up logging to write to run.log file

src/balatrollm/config.py

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Configuration management for BalatroLLM."""
22

33
import json
4-
from dataclasses import asdict, dataclass, field
4+
from dataclasses import asdict, dataclass
55
from pathlib import Path
66
from typing import Any
77

@@ -62,12 +62,91 @@ def load_model_config(
6262
return final_config
6363

6464

65+
@dataclass
66+
class StrategyManifest:
67+
"""Strategy metadata from manifest.json.
68+
69+
Stores all metadata for a game strategy including name, description,
70+
author, version, and tags.
71+
72+
Attributes:
73+
name: Human-readable strategy name (e.g., 'Default', 'Aggressive').
74+
description: Strategy description.
75+
author: Strategy author identifier.
76+
version: Strategy version (distinct from BalatroLLM version).
77+
tags: List of tags for categorization.
78+
"""
79+
80+
name: str
81+
description: str
82+
author: str
83+
version: str
84+
tags: list[str]
85+
86+
@classmethod
87+
def from_manifest_file(
88+
cls, strategy: str, strategies_dir: Path = Path("src/balatrollm/strategies")
89+
) -> "StrategyManifest":
90+
"""Load strategy metadata from manifest.json.
91+
92+
Reads the manifest.json file for a given strategy to retrieve metadata.
93+
94+
Args:
95+
strategy: Strategy name (e.g., 'default', 'aggressive')
96+
strategies_dir: Base directory containing strategy folders
97+
98+
Returns:
99+
StrategyManifest instance loaded from the JSON file.
100+
101+
Raises:
102+
FileNotFoundError: If manifest.json doesn't exist for the strategy
103+
json.JSONDecodeError: If manifest.json is malformed
104+
ValueError: If manifest.json is missing required fields
105+
"""
106+
manifest_path = strategies_dir / strategy / "manifest.json"
107+
if not manifest_path.exists():
108+
raise FileNotFoundError(
109+
f"Manifest not found for strategy '{strategy}': {manifest_path}"
110+
)
111+
112+
with manifest_path.open() as f:
113+
data = json.load(f)
114+
115+
# Validate required fields
116+
required_fields = {"name", "description", "author", "version", "tags"}
117+
missing_fields = required_fields - set(data.keys())
118+
if missing_fields:
119+
raise ValueError(
120+
f"Manifest for strategy '{strategy}' missing required fields: {missing_fields}"
121+
)
122+
123+
return cls(**data)
124+
125+
def to_strategy_file(self, strategy_path: Path) -> None:
126+
"""Write strategy metadata to a JSON file.
127+
128+
Saves the current strategy manifest to a JSON file with proper
129+
formatting.
130+
131+
Args:
132+
strategy_path: Path to the strategy file to write.
133+
"""
134+
# Convert dataclass to dictionary
135+
strategy_data = asdict(self)
136+
137+
# Create parent directory if it doesn't exist
138+
strategy_path.parent.mkdir(parents=True, exist_ok=True)
139+
140+
with strategy_path.open("w") as f:
141+
json.dump(strategy_data, f, indent=2)
142+
143+
65144
@dataclass
66145
class Config:
67146
"""Configuration for LLMBot.
68147
69148
Stores all configuration parameters for bot execution including
70-
model settings, game parameters, and metadata.
149+
model settings and game parameters.
71150
72151
Attributes:
73152
model: LLM model identifier (e.g., 'openai/gpt-oss-20b').
@@ -78,11 +157,7 @@ class Config:
78157
challenge: Optional challenge mode identifier.
79158
take_screenshots: Whether to take screenshots during gameplay (default: True).
80159
use_default_paths: Whether to use BalatroBot's default storage paths (default: False).
81-
version: Software version string.
82-
name: Human-readable configuration name.
83-
description: Configuration description.
84-
author: Configuration author identifier.
85-
tags: List of tags for categorization.
160+
version: BalatroLLM version string (separate from strategy version).
86161
"""
87162

88163
model: str
@@ -94,10 +169,6 @@ class Config:
94169
take_screenshots: bool = True
95170
use_default_paths: bool = False
96171
version: str = __version__
97-
name: str = "Unknown Name"
98-
description: str = "Unknown Description"
99-
author: str = "BalatroBench"
100-
tags: list[str] = field(default_factory=list)
101172

102173
@classmethod
103174
def from_defaults(cls) -> "Config":
@@ -115,11 +186,6 @@ def from_defaults(cls) -> "Config":
115186
challenge=None,
116187
take_screenshots=True,
117188
use_default_paths=False,
118-
version="",
119-
name="Unknown Name",
120-
description="Unknown Description",
121-
author="BalatroBench",
122-
tags=[],
123189
)
124190

125191
@classmethod

0 commit comments

Comments
 (0)