Add crafting recipe system
Implements Recipe dataclass, recipe loading from TOML files, and recipe registry. Recipes define ingredients consumed and result produced for item crafting.
This commit is contained in:
parent
9f760bc3af
commit
ec43ead568
2 changed files with 156 additions and 0 deletions
57
src/mudlib/crafting.py
Normal file
57
src/mudlib/crafting.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"""Crafting recipe system."""
|
||||
|
||||
import logging
|
||||
import tomllib
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Recipe:
|
||||
"""A crafting recipe definition."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
ingredients: list[str]
|
||||
result: str
|
||||
|
||||
|
||||
# Module-level registry
|
||||
recipes: dict[str, Recipe] = {}
|
||||
|
||||
|
||||
def load_recipe(path: Path) -> Recipe:
|
||||
"""Load a recipe from TOML.
|
||||
|
||||
Args:
|
||||
path: Path to the recipe TOML file
|
||||
|
||||
Returns:
|
||||
Recipe instance
|
||||
"""
|
||||
with open(path, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
return Recipe(
|
||||
name=data["name"],
|
||||
description=data["description"],
|
||||
ingredients=data["ingredients"],
|
||||
result=data["result"],
|
||||
)
|
||||
|
||||
|
||||
def load_recipes(directory: Path) -> dict[str, Recipe]:
|
||||
"""Load all recipes from a directory.
|
||||
|
||||
Args:
|
||||
directory: Path to directory containing recipe TOML files
|
||||
|
||||
Returns:
|
||||
Dict of recipes keyed by name
|
||||
"""
|
||||
loaded: dict[str, Recipe] = {}
|
||||
for path in sorted(directory.glob("*.toml")):
|
||||
recipe = load_recipe(path)
|
||||
loaded[recipe.name] = recipe
|
||||
return loaded
|
||||
99
tests/test_crafting.py
Normal file
99
tests/test_crafting.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"""Tests for the crafting recipe system."""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from mudlib.crafting import Recipe, load_recipe, load_recipes, recipes
|
||||
from mudlib.things import thing_templates
|
||||
from mudlib.zones import zone_registry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_registries():
|
||||
"""Snapshot and restore registries to prevent test leakage."""
|
||||
saved_zones = dict(zone_registry)
|
||||
saved_templates = dict(thing_templates)
|
||||
saved_recipes = dict(recipes)
|
||||
zone_registry.clear()
|
||||
thing_templates.clear()
|
||||
recipes.clear()
|
||||
yield
|
||||
zone_registry.clear()
|
||||
zone_registry.update(saved_zones)
|
||||
thing_templates.clear()
|
||||
thing_templates.update(saved_templates)
|
||||
recipes.clear()
|
||||
recipes.update(saved_recipes)
|
||||
|
||||
|
||||
def test_load_recipe_from_toml():
|
||||
"""Parse a recipe TOML file."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write("""
|
||||
name = "wooden_table"
|
||||
description = "Craft a sturdy table from planks and nails"
|
||||
ingredients = ["plank", "plank", "plank", "nail", "nail"]
|
||||
result = "table"
|
||||
""")
|
||||
path = Path(f.name)
|
||||
|
||||
try:
|
||||
recipe = load_recipe(path)
|
||||
assert recipe.name == "wooden_table"
|
||||
assert recipe.description == "Craft a sturdy table from planks and nails"
|
||||
assert recipe.ingredients == ["plank", "plank", "plank", "nail", "nail"]
|
||||
assert recipe.result == "table"
|
||||
finally:
|
||||
path.unlink()
|
||||
|
||||
|
||||
def test_load_recipes_directory():
|
||||
"""Load all recipes from a directory."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_path = Path(tmpdir)
|
||||
|
||||
# Create two recipe files
|
||||
(tmp_path / "table.toml").write_text("""
|
||||
name = "wooden_table"
|
||||
description = "Craft a table"
|
||||
ingredients = ["plank", "plank"]
|
||||
result = "table"
|
||||
""")
|
||||
(tmp_path / "chair.toml").write_text("""
|
||||
name = "wooden_chair"
|
||||
description = "Craft a chair"
|
||||
ingredients = ["plank"]
|
||||
result = "chair"
|
||||
""")
|
||||
|
||||
loaded = load_recipes(tmp_path)
|
||||
assert len(loaded) == 2
|
||||
assert "wooden_table" in loaded
|
||||
assert "wooden_chair" in loaded
|
||||
assert loaded["wooden_table"].result == "table"
|
||||
assert loaded["wooden_chair"].result == "chair"
|
||||
|
||||
|
||||
def test_recipe_fields():
|
||||
"""Verify all recipe fields are populated correctly."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write("""
|
||||
name = "test_recipe"
|
||||
description = "A test recipe"
|
||||
ingredients = ["item_a", "item_b", "item_c"]
|
||||
result = "item_result"
|
||||
""")
|
||||
path = Path(f.name)
|
||||
|
||||
try:
|
||||
recipe = load_recipe(path)
|
||||
assert isinstance(recipe, Recipe)
|
||||
assert recipe.name == "test_recipe"
|
||||
assert recipe.description == "A test recipe"
|
||||
assert recipe.ingredients == ["item_a", "item_b", "item_c"]
|
||||
assert recipe.result == "item_result"
|
||||
assert len(recipe.ingredients) == 3
|
||||
finally:
|
||||
path.unlink()
|
||||
Loading…
Reference in a new issue