Source code for pyscoundrel.dungeon.card_pool
"""Dungeon card pool loader and manager."""
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional
import yaml
from ..models import CardType
[docs]
@dataclass
class CardDefinition:
"""Definition of a card in the dungeon pool."""
id: str
name: str
card_type: CardType
value: int
count: int
description: Optional[str] = None
[docs]
@staticmethod
def from_dict(data: Dict) -> "CardDefinition":
"""Create a CardDefinition from a dictionary."""
# Map string type to CardType enum
type_map = {
"monster": CardType.MONSTER,
"weapon": CardType.WEAPON,
"health_potion": CardType.HEALTH_POTION,
}
card_type_str = data["type"].lower()
if card_type_str not in type_map:
raise ValueError(f"Invalid card type: {data['type']}")
return CardDefinition(
id=data["id"],
name=data["name"],
card_type=type_map[card_type_str],
value=data["value"],
count=data["count"],
description=data.get("description"),
)
def __repr__(self) -> str:
return (
f"CardDefinition(id={self.id!r}, name={self.name!r}, "
f"type={self.card_type.value}, value={self.value}, count={self.count})"
)
[docs]
class Dungeon:
"""
Dungeon card pool loaded from YAML configuration.
The dungeon represents all possible cards that can appear in the game,
not the shuffled deck used during play.
"""
def __init__(self, config_path: Optional[Path] = None):
"""
Initialize dungeon from YAML configuration.
Args:
config_path: Path to dungeon YAML file. If None, uses default.
"""
self.config_path = config_path or self._get_default_config_path()
self.version: str = "1.0"
self.card_definitions: List[CardDefinition] = []
self._load()
def _get_default_config_path(self) -> Path:
"""Get path to default dungeon configuration."""
return Path(__file__).parent / "default_dungeon.yaml"
def _load(self) -> None:
"""Load dungeon configuration from YAML file."""
if not self.config_path.exists():
raise FileNotFoundError(f"Dungeon config not found: {self.config_path}")
with open(self.config_path, "r") as f:
data = yaml.safe_load(f)
self.version = data.get("version", "1.0")
# Load card definitions
self.card_definitions = []
for card_data in data.get("cards", []):
card_def = CardDefinition.from_dict(card_data)
self.card_definitions.append(card_def)
[docs]
def get_total_cards(self) -> int:
"""Get total number of cards in the dungeon pool."""
return sum(card.count for card in self.card_definitions)
[docs]
def get_cards_by_type(self, card_type: CardType) -> List[CardDefinition]:
"""Get all card definitions of a specific type."""
return [card for card in self.card_definitions if card.card_type == card_type]
[docs]
def get_card_by_id(self, card_id: str) -> Optional[CardDefinition]:
"""Get a card definition by its ID."""
for card in self.card_definitions:
if card.id == card_id:
return card
return None
[docs]
def validate(self) -> List[str]:
"""
Validate the dungeon configuration.
Returns:
List of validation error messages (empty if valid)
"""
errors = []
# Check for duplicate IDs
ids = [card.id for card in self.card_definitions]
duplicates = set([id for id in ids if ids.count(id) > 1])
if duplicates:
errors.append(f"Duplicate card IDs: {duplicates}")
# Check that all cards have positive values
for card in self.card_definitions:
if card.value <= 0:
errors.append(f"Card '{card.id}' has non-positive value: {card.value}")
if card.count <= 0:
errors.append(f"Card '{card.id}' has non-positive count: {card.count}")
# Check minimum card counts
total = self.get_total_cards()
if total < 20:
errors.append(f"Dungeon has too few cards: {total} (minimum 20 recommended)")
return errors
def __repr__(self) -> str:
return f"Dungeon(cards={len(self.card_definitions)}, total={self.get_total_cards()})"