diff --git a/.gitignore b/.gitignore index 207a3c8..3dc82e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # UV properties .venv/ -.python-version \ No newline at end of file +.python-version +__pycache__/ +uv.lock +*.json \ No newline at end of file diff --git a/__pycache__/dice.cpython-312.pyc b/__pycache__/dice.cpython-312.pyc index 8ce3fc6..b836c8e 100644 Binary files a/__pycache__/dice.cpython-312.pyc and b/__pycache__/dice.cpython-312.pyc differ diff --git a/__pycache__/game.cpython-312.pyc b/__pycache__/game.cpython-312.pyc index aacd7de..44d7dae 100644 Binary files a/__pycache__/game.cpython-312.pyc and b/__pycache__/game.cpython-312.pyc differ diff --git a/__pycache__/serializable.cpython-312.pyc b/__pycache__/serializable.cpython-312.pyc index 55acc43..b53dcac 100644 Binary files a/__pycache__/serializable.cpython-312.pyc and b/__pycache__/serializable.cpython-312.pyc differ diff --git a/dice.py b/dice.py index fd91518..59cd6f7 100644 --- a/dice.py +++ b/dice.py @@ -1,15 +1,13 @@ import random as rd class Dice(): - def __init__(self): - raise TypeError("Un dé ne peut pas être instanciée!") - - @staticmethod - def roll(self, num_faces=20): - return rd.randrange(start=1, stop=num_faces+1, step=1) + def __init__(self, num_faces:int=20): + self.num_faces = num_faces - @staticmethod - def head_or_tails(): + def roll(self): + return rd.randrange(start=1, stop=self.num_faces+1, step=1) + + def head_or_tails(self): result = rd.randint(0,1) if result: # true return "head" # face diff --git a/game.py b/game.py index d3be329..6913e4b 100644 --- a/game.py +++ b/game.py @@ -3,4 +3,8 @@ from dice import Dice class Game(Serializable): def __init__(self, seed:int=42): - pass \ No newline at end of file + self.players = [] + self.npcs = [] + self.items = [] + + \ No newline at end of file diff --git a/serializable.py b/serializable.py index d6d1ca7..e39d462 100644 --- a/serializable.py +++ b/serializable.py @@ -11,7 +11,7 @@ class Serializable: instance.deserialize_dict(data) return instance - def serialize(self, file) -> str: + def serialize(self, file=None) -> str: """Serializes the object and all nested Serializable objects to JSON.""" def serialize_value(value: Any) -> Any: if isinstance(value, Serializable): @@ -24,7 +24,9 @@ class Serializable: return value attrs = {k: serialize_value(v) for k, v in self.__dict__.items() if not k.startswith('_')} - return json.dumps(attrs, file, ensure_ascii=False, indent=2) + if file: + json.dump(attrs, file, ensure_ascii=False, indent=2) + return json.dumps(attrs, ensure_ascii=False, indent=2) def serialize_dict(self) -> Dict[str, Any]: """Serializes the object to a dictionary (for nested serialization).""" diff --git a/server.py b/server.py index 207e08b..e1d4da4 100644 --- a/server.py +++ b/server.py @@ -7,7 +7,9 @@ from player import Player from item import Item from game import Game from npc import NPC - +from serializable import Serializable +import json +import os mcp = FastMCP("wyvern-castle") game = Game() @@ -19,6 +21,32 @@ logging.basicConfig( ] ) +# History file path +HISTORY_FILE = "game_history.json" + +def append_to_history(event: Dict[str, Any]): + """Append a game event to the history file.""" + history = [] + if os.path.exists(HISTORY_FILE): + with open(HISTORY_FILE, "r", encoding="utf-8") as f: + try: + history = json.load(f) + except json.JSONDecodeError: + history = [] + history.append(event) + with open(HISTORY_FILE, "w", encoding="utf-8") as f: + json.dump(history, f, ensure_ascii=False, indent=2) + +def read_history() -> list: + """Read the game history from the file.""" + if os.path.exists(HISTORY_FILE): + with open(HISTORY_FILE, "r", encoding="utf-8") as f: + try: + return json.load(f) + except json.JSONDecodeError: + return [] + return [] + @mcp.tool() async def throw_a_dice(n_faces: int) -> Any: """Throw a dice with n faces. If n==2 its a coin toss. @@ -27,7 +55,7 @@ async def throw_a_dice(n_faces: int) -> Any: n_faces: Number of faces of the dice """ logging.info(f"Throwing a dice with {n_faces} faces") - dice = Dice() + dice = Dice(n_faces) if n_faces < 1: raise ValueError("Number of faces must be at least 1") @@ -36,36 +64,12 @@ async def throw_a_dice(n_faces: int) -> Any: elif n_faces == 2: return dice.head_or_tails() else: - return dice.roll(n_faces) + return dice.roll() + + @mcp.tool() -async def mult(a: int, b: int) -> int: - """Multiply two numbers. - - Args: - a: First number - b: Second number - """ - logging.info(f"Calling mult with a={a}, b={b}") - result = a * b - logging.info(f"mult result: {result}") - return result - -@mcp.tool() -async def add(a: int, b: int) -> int: - """Add two numbers. - - Args: - a: First number - b: Second number - """ - logging.info(f"Calling add with a={a}, b={b}") - result = a + b - logging.info(f"add result: {result}") - return result - -@mcp.tool() -async def create_player(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item: str = None) -> Dict[str, Any]: +async def create_player(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item: str = "") -> Dict[str, Any]: """Create a new player. Need all the stats to function properly. Throw a d20 for every stats you don't have, and a d6 for hp, armor and speed. @@ -84,10 +88,11 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence: logging.info(f"Creating player with name={name}") player = Player(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed) logging.info(f"Created player: {player}") + game.players.append(player) return player.serialize_dict() @mcp.tool() -async def create_npc(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item: str = None) -> Dict[str, Any]: +async def create_npc(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item: str = "") -> Dict[str, Any]: """Create a new NPC. Need all the stats to function properly. Throw a d20 for every stats you don't have, and a d6 for hp, armor and speed. @@ -106,6 +111,7 @@ async def create_npc(name: str, strength: int, dexterity: int, intelligence: int logging.info(f"Creating NPC with name={name}") npc = NPC(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed) logging.info(f"Created NPC: {npc}") + game.npcs.append(npc) return npc.serialize_dict() @mcp.tool() @@ -120,6 +126,7 @@ async def create_item(name: str, description: str, bonus: str) -> Dict[str, Any] logging.info(f"Creating item with name={name}") item = Item(name, description, bonus) logging.info(f"Created item: {item}") + game.items.append(item) return item.serialize_dict() @mcp.tool() @@ -133,6 +140,39 @@ async def add_item_to_player(player_name: str, item_name: str) -> Dict[str, Any] logging.info(f"Adding item {item_name} to player {player_name}") return {"status": "Item added"} +@mcp.tool() +async def save_game_state() -> str: + """Save the current game state to a persistent storage each time + the game state is modified.""" + logging.info("Saving game state") + with open("game_state.json", "w", encoding="utf-8") as f: + f.write(game.serialize()) + return "Game state saved to game_state.json" + + +# Example MCP tool to add an event to history +@mcp.tool() +async def add_event_to_history(event: Dict[str, Any]) -> str: + """Add a game event to the history resource.""" + append_to_history(event) + return "Event added to history." + +# Example MCP tool to read history +@mcp.tool() +async def get_game_history() -> list: + """Get the full game history.""" + return read_history() + +@mcp.tool() +async def purge_game_history_and_state() -> str: + """Purge the game history and state files when the player is starting a new game.""" + if os.path.exists(HISTORY_FILE): + os.remove(HISTORY_FILE) + if os.path.exists("game_state.json"): + os.remove("game_state.json") + return "Game history and state purged." + + def main(): # Initialize and run the server mcp.run(transport="stdio")