Spaces:
Build error
Build error
| """ | |
| GameState - Rich state tracking for game configs with logic gates. | |
| Provides comprehensive tracking beyond the basic Player class: | |
| - Inventory, money, knowledge (existing) | |
| - People met, locations discovered | |
| - Missions/quests (active, completed, failed) | |
| - Boolean flags and numeric counters | |
| - Choice history for analytics | |
| JSON-serializable for save/load functionality. | |
| """ | |
| from typing import Dict, List, Set, Any, Optional | |
| from dataclasses import dataclass, field | |
| import json | |
| import time | |
| class GameState: | |
| """ | |
| Comprehensive game state tracking. | |
| All fields are JSON-serializable for save/load. | |
| """ | |
| # ==================== Character Resources (from existing Player) ==================== | |
| inventory: List[str] = field(default_factory=list) | |
| money: int = 0 | |
| knowledge: Dict[str, Any] = field(default_factory=dict) | |
| # ==================== People/NPCs ==================== | |
| people_met: Set[str] = field(default_factory=set) | |
| npc_reputation: Dict[str, int] = field(default_factory=dict) # NPC -> affinity score | |
| # ==================== Locations ==================== | |
| locations_visited: Set[str] = field(default_factory=set) | |
| locations_discovered: Set[str] = field(default_factory=set) # Known but not visited | |
| current_location: str = "" | |
| current_state: str = "" | |
| # ==================== Missions/Quests ==================== | |
| missions_active: Dict[str, Dict] = field(default_factory=dict) # id -> {status, progress, data} | |
| missions_completed: Set[str] = field(default_factory=set) | |
| missions_failed: Set[str] = field(default_factory=set) | |
| # ==================== Flags (boolean switches) ==================== | |
| flags: Dict[str, bool] = field(default_factory=dict) | |
| # ==================== Counters (numeric trackers) ==================== | |
| counters: Dict[str, int] = field(default_factory=dict) | |
| # ==================== History Tracking ==================== | |
| choice_history: List[Dict] = field(default_factory=list) # [{state, choice, timestamp}] | |
| state_visit_counts: Dict[str, int] = field(default_factory=dict) | |
| # ==================== Metadata ==================== | |
| game_start_time: float = field(default_factory=time.time) | |
| total_choices_made: int = 0 | |
| # ==================== Item Methods ==================== | |
| def add_item(self, item: str) -> None: | |
| """Add an item to inventory (no duplicates).""" | |
| if item not in self.inventory: | |
| self.inventory.append(item) | |
| def add_items(self, items: List[str]) -> None: | |
| """Add multiple items to inventory.""" | |
| for item in items: | |
| self.add_item(item) | |
| def remove_item(self, item: str) -> bool: | |
| """Remove an item from inventory. Returns True if successful.""" | |
| if item in self.inventory: | |
| self.inventory.remove(item) | |
| return True | |
| return False | |
| def has_item(self, item: str) -> bool: | |
| """Check if player has an item.""" | |
| return item in self.inventory | |
| def item_count(self, item: str) -> int: | |
| """Count occurrences of an item (for stackable items).""" | |
| return self.inventory.count(item) | |
| # ==================== Money Methods ==================== | |
| def add_money(self, amount: int) -> None: | |
| """Add money to player's balance.""" | |
| self.money += amount | |
| def remove_money(self, amount: int) -> bool: | |
| """Remove money. Returns True if player had enough.""" | |
| if self.money >= amount: | |
| self.money -= amount | |
| return True | |
| return False | |
| def has_money(self, amount: int) -> bool: | |
| """Check if player has at least this much money.""" | |
| return self.money >= amount | |
| # ==================== People Methods ==================== | |
| def meet_person(self, person: str) -> None: | |
| """Mark an NPC as met.""" | |
| self.people_met.add(person) | |
| def has_met(self, person: str) -> bool: | |
| """Check if player has met an NPC.""" | |
| return person in self.people_met | |
| def adjust_reputation(self, npc: str, change: int) -> None: | |
| """Adjust reputation with an NPC.""" | |
| self.npc_reputation[npc] = self.npc_reputation.get(npc, 0) + change | |
| def get_reputation(self, npc: str) -> int: | |
| """Get reputation with an NPC (default 0).""" | |
| return self.npc_reputation.get(npc, 0) | |
| # ==================== Location Methods ==================== | |
| def visit_location(self, location: str) -> None: | |
| """Mark a location as visited.""" | |
| self.locations_visited.add(location) | |
| self.locations_discovered.discard(location) # No longer just discovered | |
| def discover_location(self, location: str) -> None: | |
| """Mark a location as discovered (but not visited).""" | |
| if location not in self.locations_visited: | |
| self.locations_discovered.add(location) | |
| def has_visited(self, location: str) -> bool: | |
| """Check if player has visited a location.""" | |
| return location in self.locations_visited | |
| def has_discovered(self, location: str) -> bool: | |
| """Check if player knows about a location (visited or discovered).""" | |
| return location in self.locations_visited or location in self.locations_discovered | |
| # ==================== Mission Methods ==================== | |
| def start_mission(self, mission_id: str, data: Dict = None) -> None: | |
| """Start a new mission/quest.""" | |
| self.missions_active[mission_id] = data or {"status": "active", "progress": 0} | |
| def update_mission(self, mission_id: str, updates: Dict) -> None: | |
| """Update mission data.""" | |
| if mission_id in self.missions_active: | |
| self.missions_active[mission_id].update(updates) | |
| def complete_mission(self, mission_id: str) -> None: | |
| """Mark a mission as completed.""" | |
| if mission_id in self.missions_active: | |
| del self.missions_active[mission_id] | |
| self.missions_completed.add(mission_id) | |
| def fail_mission(self, mission_id: str) -> None: | |
| """Mark a mission as failed.""" | |
| if mission_id in self.missions_active: | |
| del self.missions_active[mission_id] | |
| self.missions_failed.add(mission_id) | |
| def is_mission_complete(self, mission_id: str) -> bool: | |
| """Check if a mission is completed.""" | |
| return mission_id in self.missions_completed | |
| def is_mission_active(self, mission_id: str) -> bool: | |
| """Check if a mission is currently active.""" | |
| return mission_id in self.missions_active | |
| def is_mission_failed(self, mission_id: str) -> bool: | |
| """Check if a mission has failed.""" | |
| return mission_id in self.missions_failed | |
| def get_mission_data(self, mission_id: str) -> Optional[Dict]: | |
| """Get data for an active mission.""" | |
| return self.missions_active.get(mission_id) | |
| # ==================== Flag Methods ==================== | |
| def set_flag(self, flag: str, value: bool = True) -> None: | |
| """Set a boolean flag.""" | |
| self.flags[flag] = value | |
| def clear_flag(self, flag: str) -> None: | |
| """Clear (set to False) a flag.""" | |
| self.flags[flag] = False | |
| def has_flag(self, flag: str) -> bool: | |
| """Check if a flag is set (True).""" | |
| return self.flags.get(flag, False) | |
| def toggle_flag(self, flag: str) -> bool: | |
| """Toggle a flag and return new value.""" | |
| new_value = not self.flags.get(flag, False) | |
| self.flags[flag] = new_value | |
| return new_value | |
| # ==================== Counter Methods ==================== | |
| def set_counter(self, name: str, value: int) -> None: | |
| """Set a counter to a specific value.""" | |
| self.counters[name] = value | |
| def increment_counter(self, name: str, amount: int = 1) -> int: | |
| """Increment a counter and return new value.""" | |
| self.counters[name] = self.counters.get(name, 0) + amount | |
| return self.counters[name] | |
| def decrement_counter(self, name: str, amount: int = 1) -> int: | |
| """Decrement a counter and return new value.""" | |
| return self.increment_counter(name, -amount) | |
| def get_counter(self, name: str, default: int = 0) -> int: | |
| """Get a counter value.""" | |
| return self.counters.get(name, default) | |
| # ==================== Knowledge Methods ==================== | |
| def update_knowledge(self, key: str, value: Any = True) -> None: | |
| """Add or update knowledge.""" | |
| self.knowledge[key] = value | |
| def has_knowledge(self, key: str) -> bool: | |
| """Check if player has a knowledge entry.""" | |
| return key in self.knowledge | |
| def get_knowledge(self, key: str, default: Any = None) -> Any: | |
| """Get a knowledge value.""" | |
| return self.knowledge.get(key, default) | |
| def remove_knowledge(self, key: str) -> None: | |
| """Remove a knowledge entry.""" | |
| self.knowledge.pop(key, None) | |
| # ==================== History Methods ==================== | |
| def record_choice(self, state: str, choice: str) -> None: | |
| """Record a player choice for history tracking.""" | |
| self.choice_history.append({ | |
| "state": state, | |
| "choice": choice, | |
| "timestamp": time.time() | |
| }) | |
| self.total_choices_made += 1 | |
| self.state_visit_counts[state] = self.state_visit_counts.get(state, 0) + 1 | |
| def get_visit_count(self, state: str) -> int: | |
| """Get number of times a state was visited.""" | |
| return self.state_visit_counts.get(state, 0) | |
| def get_last_choices(self, n: int = 5) -> List[Dict]: | |
| """Get the last N choices made.""" | |
| return self.choice_history[-n:] if self.choice_history else [] | |
| # ==================== Serialization ==================== | |
| def to_dict(self) -> Dict: | |
| """Convert to JSON-serializable dict for saving.""" | |
| return { | |
| "inventory": self.inventory, | |
| "money": self.money, | |
| "knowledge": self.knowledge, | |
| "people_met": list(self.people_met), | |
| "npc_reputation": self.npc_reputation, | |
| "locations_visited": list(self.locations_visited), | |
| "locations_discovered": list(self.locations_discovered), | |
| "current_location": self.current_location, | |
| "current_state": self.current_state, | |
| "missions_active": self.missions_active, | |
| "missions_completed": list(self.missions_completed), | |
| "missions_failed": list(self.missions_failed), | |
| "flags": self.flags, | |
| "counters": self.counters, | |
| "choice_history": self.choice_history, | |
| "state_visit_counts": self.state_visit_counts, | |
| "game_start_time": self.game_start_time, | |
| "total_choices_made": self.total_choices_made | |
| } | |
| def from_dict(cls, data: Dict) -> 'GameState': | |
| """Reconstruct GameState from dict (for loading saves).""" | |
| state = cls() | |
| state.inventory = data.get("inventory", []) | |
| state.money = data.get("money", 0) | |
| state.knowledge = data.get("knowledge", {}) | |
| state.people_met = set(data.get("people_met", [])) | |
| state.npc_reputation = data.get("npc_reputation", {}) | |
| state.locations_visited = set(data.get("locations_visited", [])) | |
| state.locations_discovered = set(data.get("locations_discovered", [])) | |
| state.current_location = data.get("current_location", "") | |
| state.current_state = data.get("current_state", "") | |
| state.missions_active = data.get("missions_active", {}) | |
| state.missions_completed = set(data.get("missions_completed", [])) | |
| state.missions_failed = set(data.get("missions_failed", [])) | |
| state.flags = data.get("flags", {}) | |
| state.counters = data.get("counters", {}) | |
| state.choice_history = data.get("choice_history", []) | |
| state.state_visit_counts = data.get("state_visit_counts", {}) | |
| state.game_start_time = data.get("game_start_time", time.time()) | |
| state.total_choices_made = data.get("total_choices_made", 0) | |
| return state | |
| def to_json(self) -> str: | |
| """Serialize to JSON string.""" | |
| return json.dumps(self.to_dict(), indent=2) | |
| def from_json(cls, json_str: str) -> 'GameState': | |
| """Deserialize from JSON string.""" | |
| return cls.from_dict(json.loads(json_str)) | |
| def reset(self) -> None: | |
| """Reset state to initial values (for new game).""" | |
| self.inventory.clear() | |
| self.money = 0 | |
| self.knowledge.clear() | |
| self.people_met.clear() | |
| self.npc_reputation.clear() | |
| self.locations_visited.clear() | |
| self.locations_discovered.clear() | |
| self.current_location = "" | |
| self.current_state = "" | |
| self.missions_active.clear() | |
| self.missions_completed.clear() | |
| self.missions_failed.clear() | |
| self.flags.clear() | |
| self.counters.clear() | |
| self.choice_history.clear() | |
| self.state_visit_counts.clear() | |
| self.game_start_time = time.time() | |
| self.total_choices_made = 0 | |
| class Player: | |
| """ | |
| Backwards-compatible Player class that wraps GameState. | |
| Existing configs using lambda consequences will continue to work. | |
| """ | |
| def __init__(self, game_state: GameState = None): | |
| self._state = game_state or GameState() | |
| # Existing API (unchanged for backwards compatibility) | |
| def inventory(self) -> List[str]: | |
| return self._state.inventory | |
| def money(self) -> int: | |
| return self._state.money | |
| def money(self, value: int): | |
| self._state.money = value | |
| def knowledge(self) -> Dict[str, Any]: | |
| return self._state.knowledge | |
| def add_item(self, item: str) -> None: | |
| self._state.add_item(item) | |
| def has_item(self, item: str) -> bool: | |
| return self._state.has_item(item) | |
| def update_knowledge(self, topic: str) -> None: | |
| self._state.update_knowledge(topic, True) | |
| # NEW: Expose full state for advanced usage | |
| def game_state(self) -> GameState: | |
| return self._state | |