Event modifs and README update
This commit is contained in:
47
README.md
47
README.md
@@ -1,17 +1,21 @@
|
|||||||
# Wyvern&Castle
|
# 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.
|
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!).
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Initialisation du projet
|
## Installation des dépendances
|
||||||
Pour lancer une partie de notre jeu, nous vous conseillons d'installer **[Claude Desktop](https://claude.com/fr-fr/download)** (disponible sur Windows
|
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.
|
||||||
et Mac) qui va servir de LLM. Nous n'avons pas essayé mais il serait aussi possible d'utiliser la **version Desktop de ChatGPT**
|
|
||||||
|
|
||||||
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
|
```bash
|
||||||
# Mac/Linux
|
# Mac/Linux
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
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"
|
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 :
|
Creer un dossier et cloner le projet :
|
||||||
```bash
|
```bash
|
||||||
mkdir wyvern_castle
|
mkdir wyvern_castle
|
||||||
cd wyvern_castle
|
cd wyvern_castle
|
||||||
git clone "https://gitea.galaxynoliro.fr/KuMiShi/Wyvern-Castle.git"
|
git clone "https://gitea.galaxynoliro.fr/KuMiShi/Wyvern-Castle.git"
|
||||||
```
|
```
|
||||||
3. Creation de l'environnement virtuel
|
**3. Creation de l'environnement virtuel**
|
||||||
```bash
|
```bash
|
||||||
|
# Cela est nécessaire afin de pouvoir télécharger les librairies du projet
|
||||||
uv venv
|
uv venv
|
||||||
```
|
```
|
||||||
Mac/Linux
|
Mac/Linux
|
||||||
```bash
|
```bash
|
||||||
|
# Activation du venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
```
|
```
|
||||||
Windows
|
Windows
|
||||||
```Powershell
|
```Powershell
|
||||||
|
# Activation du venv
|
||||||
.\.venv\Scripts\activate
|
.\.venv\Scripts\activate
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Installation des dependances/requirements
|
**4. Installation des dependances/requirements python**
|
||||||
```bash
|
```bash
|
||||||
# Synchronise l'environnement virtuel du dossier avec les dependances du projet
|
# Synchronise l'environnement virtuel du dossier avec les dependances du projet
|
||||||
uv pip sync pyproject.toml
|
uv pip sync pyproject.toml
|
||||||
@@ -50,9 +57,13 @@ uv pip compile --upgrade pyproject.toml -o uv.lock
|
|||||||
uv pip sync uv.lock
|
uv pip sync uv.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Changement de la config de Claude Desktop
|
**5. Configuration de Claude Desktop**
|
||||||
Modifier le fichier `claude_desktop_config.json`
|
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.
|
||||||
|
<div align="center">
|
||||||
|
<img src="images/parameter_claude.PNG"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"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
|
## 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.
|
<div align="center">
|
||||||
|
<img src="images/create_player.gif"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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é:
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="images/connector.png"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Si cela est bien coché, alors il ne vous reste plus qu'à écrire votre prompt et laisser l'IA utilise nos outils MCP:
|
||||||
|
<div align="center">
|
||||||
|
<img src="images/game_party_state.gif"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
### Tools / Outils
|
### Tools / Outils
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class Event(Serializable):
|
|||||||
def remove_entity(self, entity_id:str):
|
def remove_entity(self, entity_id:str):
|
||||||
if entity_id in self.entities:
|
if entity_id in self.entities:
|
||||||
self.entities.remove(entity_id)
|
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):
|
def get_current_turn(self):
|
||||||
idx = len(self.turns) - 1
|
idx = len(self.turns) - 1
|
||||||
|
|||||||
BIN
images/connector.png
Normal file
BIN
images/connector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
images/game_party_state.gif
Normal file
BIN
images/game_party_state.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 MiB |
BIN
images/parameter_claude.PNG
Normal file
BIN
images/parameter_claude.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
26
server.py
26
server.py
@@ -304,7 +304,7 @@ async def toss_coin():
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mcp.tool()
|
@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,
|
"""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.
|
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
|
armor: Armor class of the player
|
||||||
speed: Speed of the player
|
speed: Speed of the player
|
||||||
item: Item carried by 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}")
|
logging.info(f"Creating player named {name}")
|
||||||
try:
|
try:
|
||||||
player_id = game.create_player(name=name,
|
item = game.get_item(item_id) # Check if item exists
|
||||||
strength=strength,
|
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)
|
||||||
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
|
|
||||||
game.add_item_to_entity(item_id=item_id, entity_id=player_id)
|
game.add_item_to_entity(item_id=item_id, entity_id=player_id)
|
||||||
player_dict = game.get_player(player_id=player_id).serialize()
|
player_dict = game.get_player(player_id=player_id).serialize()
|
||||||
logging.info(f"Creation of player successful")
|
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 {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"player_properties": player_dict
|
"player_properties": player_dict
|
||||||
@@ -347,7 +343,7 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mcp.tool()
|
@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,
|
"""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.
|
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
|
armor: Armor class of the NPC
|
||||||
speed: Speed of the NPC
|
speed: Speed of the NPC
|
||||||
item: Item carried by 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}")
|
logging.info(f"Creating NPC named {name}")
|
||||||
try:
|
try:
|
||||||
item = game.get_item(item_id) # Check if item exists
|
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)
|
game.add_item_to_entity(item_id=item_id, entity_id=npc_id)
|
||||||
npc_dict = game.get_npc(npc_id=npc_id).serialize()
|
npc_dict = game.get_npc(npc_id=npc_id).serialize()
|
||||||
logging.info(f"Creation of NPC successful")
|
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 {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"npc_properties": npc_dict
|
"npc_properties": npc_dict
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ class Game(Serializable):
|
|||||||
def add_event(self, new_event:Event):
|
def add_event(self, new_event:Event):
|
||||||
self.events.append(new_event)
|
self.events.append(new_event)
|
||||||
self.update_turn_order()
|
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):
|
def check_turn_ended(self):
|
||||||
if self.turn_idx == len(self.turn_order)-1:
|
if self.turn_idx == len(self.turn_order)-1:
|
||||||
|
|||||||
Reference in New Issue
Block a user