diff --git a/README.md b/README.md index 959d822..28fc595 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ # Wyvern&Castle + +## Sujet et tache Ceci est un projet de **modèle MCP** ([Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro)) pour le cours de **Natural Language Programming** de l'année 2025-2026. L'objectif du projet a été de concevoir une **version simplifié du jeu Donjon & Dragon** avec un LLM capable de générer des parties et des scénarios qui n'ont de limite que votre imagination (et celles du LLM aussi!). -Le projet contient un serveur (`server.py`) qui intéragit avec notre API de jeu (`game.py`). +Le projet contient un serveur (`server.py`) qui intéragit avec notre API de jeu (`game.py`) via un système d'outils (MCP tooling) asynchrone afin de pouvoir enchainer les outils sans trop de délais d'interruption pour les appels long. ![Création d'un joueur](images/create_player.gif) -## Initialisation du projet -Pour lancer une partie de notre jeu, nous vous conseillons d'installer **[Claude Desktop](https://claude.com/fr-fr/download)** (disponible sur Windows -et Mac) qui va servir de LLM. Nous n'avons pas essayé mais il serait aussi possible d'utiliser la **version Desktop de ChatGPT** +## Installation des dépendances +Pour lancer une partie de notre jeu, il faudra installer **l'utilitaire python `UV`** qui est similaire à `pip` ainsi qu'une **application qui contient un LLM** de bureau. -1. Installer l'utilitaire python UV : +Nous vous conseillons d'installer **[Claude Desktop](https://claude.com/fr-fr/download)** (disponible sur Windows +et Mac) qui va servir de LLM de base. Nous n'avons pas essayé pour ce projet mais il serait aussi possible d'utiliser la **version Desktop de ChatGPT**. + +**1. Installation de UV :** ```bash # Mac/Linux curl -LsSf https://astral.sh/uv/install.sh | sh @@ -19,27 +23,30 @@ curl -LsSf https://astral.sh/uv/install.sh | sh powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` -2. Initialiser le dossier pour l'installation +**2. Initialiser le dossier pour l'installation** Creer un dossier et cloner le projet : ```bash mkdir wyvern_castle cd wyvern_castle git clone "https://gitea.galaxynoliro.fr/KuMiShi/Wyvern-Castle.git" ``` -3. Creation de l'environnement virtuel +**3. Creation de l'environnement virtuel** ```bash +# Cela est nécessaire afin de pouvoir télécharger les librairies du projet uv venv ``` Mac/Linux ```bash +# Activation du venv source .venv/bin/activate ``` Windows ```Powershell +# Activation du venv .\.venv\Scripts\activate ``` -4. Installation des dependances/requirements +**4. Installation des dependances/requirements python** ```bash # Synchronise l'environnement virtuel du dossier avec les dependances du projet uv pip sync pyproject.toml @@ -50,9 +57,13 @@ uv pip compile --upgrade pyproject.toml -o uv.lock uv pip sync uv.lock ``` -5. Changement de la config de Claude Desktop -Modifier le fichier `claude_desktop_config.json` +**5. Configuration de Claude Desktop** +Il faut d'abord se rendre dans les **Paramètres > Développeur** en cliquant sur l'icône du profil en bas à gauche de la fenêtre. +
+ +
+Une fois dans la partie dev, il faut rajouter la configuration suivante en modifier le fichier `claude_desktop_config.json` par le json ci-dessous: ```json { "mcpServers": { @@ -68,10 +79,24 @@ Modifier le fichier `claude_desktop_config.json` } } ``` +Si la configuration a bien été effectuée, vous devriez voir le tag `running` à côté du modèle. Autrement, il faudra sûrement **redémarrer l'application**. Si jamais ca ne fonctionne toujours pas, vous pouvez +aller **regarder les logs** ("journaux") pour diagnostiquer l'erreur. ## Utilisation -Le projet est assez simple d'utilisation car une fois le serveur lancé, il vous suffit d'écrire des prompts à l'aide de votre application Desktop de LLM. Il est aussi possible d'avoir accès à une aide de génération de prompt intégrée. +
+ +
+Une fois les configurations terminées et que notre modèle tourne bien en local, vous pouvez vous rendre dans **Discussion** et verifier dans l'icône **(+) -> Connecteurs** que votre modèle wyvern_castle est bien coché: + +
+ +
+ +Si cela est bien coché, alors il ne vous reste plus qu'à écrire votre prompt et laisser l'IA utilise nos outils MCP: +
+ +
## Documentation ### Tools / Outils diff --git a/events/event.py b/events/event.py index 83880e4..52ce8fc 100644 --- a/events/event.py +++ b/events/event.py @@ -21,6 +21,10 @@ class Event(Serializable): def remove_entity(self, entity_id:str): if entity_id in self.entities: self.entities.remove(entity_id) + + def add_entity(self, entity_id:str): + if not entity_id in self.entities: + self.entities.append(entity_id) def get_current_turn(self): idx = len(self.turns) - 1 diff --git a/images/connector.png b/images/connector.png new file mode 100644 index 0000000..81bd8f4 Binary files /dev/null and b/images/connector.png differ diff --git a/images/game_party_state.gif b/images/game_party_state.gif new file mode 100644 index 0000000..cb3c1f2 Binary files /dev/null and b/images/game_party_state.gif differ diff --git a/images/parameter_claude.PNG b/images/parameter_claude.PNG new file mode 100644 index 0000000..8b8cf7e Binary files /dev/null and b/images/parameter_claude.PNG differ diff --git a/server.py b/server.py index 852497a..eed8d5b 100644 --- a/server.py +++ b/server.py @@ -304,7 +304,7 @@ async def toss_coin(): } @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_id:str): +async def create_player(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item_id:str, add_to_event:bool=True): """Create a new player. Need all the stats to function properly. Throw a d20 for every stats you don't have, and a d50 for the armor and a d100 for speed. @@ -319,22 +319,18 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence: armor: Armor class of the player speed: Speed of the player item: Item carried by the player + add_to_event: Boolean deciding whether or not to add the entity to the current event """ logging.info(f"Creating player named {name}") try: - player_id = game.create_player(name=name, - strength=strength, - dexterity=dexterity, - intelligence=intelligence, - wisdom=wisdom, - charisma=charisma, - hp=20 + hp, - armor=50 + armor, - speed= speed, - equipped_item=game.get_item(item_id)) # Check if item exists + item = game.get_item(item_id) # Check if item exists + player_id = game.create_player(name=name, strength=strength, dexterity=dexterity, intelligence=intelligence, wisdom=wisdom, charisma=charisma, hp=20 + hp, armor=50 + armor, speed=speed, equipped_item=item) game.add_item_to_entity(item_id=item_id, entity_id=player_id) player_dict = game.get_player(player_id=player_id).serialize() logging.info(f"Creation of player successful") + if add_to_event: + game.add_entity_to_event(player_id) + logging.info(f"Player #{player_id} added to current event!") return { "success": True, "player_properties": player_dict @@ -347,7 +343,7 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence: } @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_id:str): +async def create_npc(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item_id:str, add_to_event:bool=True): """Create a new NPC. Need all the stats to function properly. Throw a d20 for every stats you don't have, and a d50 for the armor and a d100 for speed. @@ -362,14 +358,18 @@ async def create_npc(name: str, strength: int, dexterity: int, intelligence: int armor: Armor class of the NPC speed: Speed of the NPC item: Item carried by the NPC + add_to_event: Boolean deciding whether or not to add the entity to the current event """ logging.info(f"Creating NPC named {name}") try: item = game.get_item(item_id) # Check if item exists - npc_id = game.create_npc(name=name, strength=strength, dexterity=dexterity, intelligence=intelligence, wisdom=wisdom, charisma=charisma, hp=20 + hp, armor=50 + armor, speed= speed, equipped_item=item) + npc_id = game.create_npc(name=name, strength=strength, dexterity=dexterity, intelligence=intelligence, wisdom=wisdom, charisma=charisma, hp=20 + hp, armor=50 + armor, speed=speed, equipped_item=item) game.add_item_to_entity(item_id=item_id, entity_id=npc_id) npc_dict = game.get_npc(npc_id=npc_id).serialize() logging.info(f"Creation of NPC successful") + if add_to_event: + game.add_entity_to_event(npc_id) + logging.info(f"Player #{npc_id} added to current event!") return { "success": True, "npc_properties": npc_dict diff --git a/utils/game.py b/utils/game.py index 6fba99e..95bccb3 100644 --- a/utils/game.py +++ b/utils/game.py @@ -100,7 +100,10 @@ class Game(Serializable): def add_event(self, new_event:Event): self.events.append(new_event) self.update_turn_order() - self.turn_idx = 0 + self.turn_idx = 0 + + def add_entity_to_event(self, entity_id:str): + self.get_current_event().add_entity(entity_id=entity_id) def check_turn_ended(self): if self.turn_idx == len(self.turn_order)-1: