Server & Game refactor basics

This commit is contained in:
KuMiShi
2026-01-29 16:55:45 +01:00
parent 50d2b16224
commit e655de8f54
23 changed files with 189 additions and 121 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,6 @@
.python-version .python-version
__pycache__/ __pycache__/
uv.lock uv.lock
# Save/Load files for testing
*.json *.json

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

12
dice.py
View File

@@ -1,13 +1,13 @@
# Native imports
import random as rd import random as rd
class Dice(): class Dice():
def __init__(self, num_faces:int=20): @staticmethod
self.num_faces = num_faces def roll(num_faces=20):
return rd.randrange(start=1, stop=num_faces+1, step=1)
def roll(self): @staticmethod
return rd.randrange(start=1, stop=self.num_faces+1, step=1) def head_or_tails():
def head_or_tails(self):
result = rd.randint(0,1) result = rd.randint(0,1)
if result: # true if result: # true
return "head" # face return "head" # face

22
entities/entity.py Normal file
View File

@@ -0,0 +1,22 @@
# Game imports
from serializable import Serializable
# Native imports
import uuid
class Entity(Serializable):
def __init__(self, name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item=None):
self.id = str(uuid.uuid4())
self.name = name
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.wisdom = wisdom
self.charisma = charisma
self.hp = hp
self.armor = armor
self.speed = speed
self.equipped_item = equipped_item
def get_id(self):
return self.id

View File

@@ -1,5 +1,5 @@
from entity import Entity from entity import Entity
class NPC(Entity): class NPC(Entity):
def __init__(self, name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, ): def __init__(self, name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed):
super().__init__(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed) super().__init__(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed)

View File

@@ -1,30 +0,0 @@
from serializable import Serializable
from enum import Enum, IntEnum
class Action(Enum):
ATTACK = 'strength' # Physical Battle action
FORCE = 'strength' # Actions that requires physical effort
SPELL = 'intelligence' # Many kind of spell (battle or not)
SCAN = 'wisdom' # Danger in environment or NPC's lies
SPEECH = 'charisma' # To persuade or deceive
AGILE = 'dexterity' # Avoid traps or incoming attacks & spell
class BonusAction(IntEnum):
EQUIP_ITEM = 0
USE_CONSUMMABLE = 1
class Entity(Serializable):
def __init__(self, name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item=None):
self.name =name
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.wisdom = wisdom
self.charisma = charisma
self.hp =hp
self.armor = armor
self.speed = speed
self.equipped_item = equipped_item
def make_a_turn(self, action:Action, bonus_action:BonusAction):
pass

View File

@@ -1,26 +0,0 @@
from serializable import Serializable
from entity import Action, BonusAction, Entity
import json
class Event(Serializable):
def __init__(self, location:str, entities_objectives:dict[str,str]):
super().__init__()
self.location = location
self.entities_objectives = entities_objectives
self.description = ""
def add_turn_event(self, event_text:str, action:Action, bonus:BonusAction):
event_dict = {}
event_dict['text'] = event_text
event_dict['action'] = action
event_dict['bonus'] = bonus
self.description += json.dumps(event_dict)
def update_objectives(self, entities:list[Entity], objectives:list[str]):
assert len(entities) == len(objectives), f"Number of entities & objectives are different: {len(entities)} (entities) and {len(objectives)} (objectives) !"
n = len(entities)
for i in range(n):
entity_name = entities[i].name
objective = objectives[i]
self.entities_objectives[entity_name] = objective

14
events/event.py Normal file
View File

@@ -0,0 +1,14 @@
# Game imports
from serializable import Serializable
from entities.entity import Entity
# Native imports
import json
import uuid
class Event(Serializable):
def __init__(self, location:str):
super().__init__()
self.id = str(uuid.uuid4())
self.location = location
self.description = ""

14
events/turn.py Normal file
View File

@@ -0,0 +1,14 @@
# Native imports
from enum import Enum, IntEnum
class Action(Enum):
ATTACK = 'strength' # Physical Battle action
FORCE = 'strength' # Actions that requires physical effort
SPELL = 'intelligence' # Many kind of spell (battle or not)
SCAN = 'wisdom' # Danger in environment or NPC's lies
SPEECH = 'charisma' # To persuade or deceive
AGILE = 'dexterity' # Avoid traps or incoming attacks & spell
class BonusAction(IntEnum):
EQUIP_ITEM = 0
USE_CONSUMMABLE = 1

41
game.py
View File

@@ -1,10 +1,43 @@
from serializable import Serializable from serializable import Serializable
from dice import Dice from dice import Dice
from events.event import Event
from entities.player import Player
from entities.npc import NPC
from items.item import Item
class Game(Serializable): class Game(Serializable):
def __init__(self, seed:int=42): def __init__(self, seed:int=42):
self.players = [] self.active_players:list[Player] = []
self.npcs = [] self.active_npcs:list[NPC] = []
self.items = [] self.active_items:list[Item] = []
self.events:list[Event] = []
def get_player(self, player_id:str):
for player in self.active_players:
if player.id == player_id:
return player
raise ReferenceError(f"The player #{player_id} doesn't exist!")
def get_npc(self, npc_id:str):
for npc in self.active_npcs:
if npc.id == npc_id:
return npc
raise ReferenceError(f"The npc #{npc_id} doesn't exist!")
def get_item(self, item_id:str):
for item in self.active_items:
if item.id == item_id:
return item
raise ReferenceError(f"The item #{item_id} doesn't exist!")
def get_current_event(self):
idx = len(self.events) - 1
if idx < 0:
raise IndexError("There is no event yet, you should create one!")
return self.events[idx]
def add_event(self, new_event:Event):
self.events.append(new_event)
#TODO: Add State Summary as Resource?

View File

@@ -1,39 +0,0 @@
from serializable import Serializable
from item import Item
class Inventory(Serializable):
def __init__(self, max_capacity:float = 50.0):
super().__init__()
self.items:list[Item] = []
self.max_capacity = max_capacity # Weight (kg)
def current_capacity(self):
return sum([item.weight for item in self.items])
def list_items(self):
s_items = ''
for item in self.items:
s_items += item.__str__() + '; '
return s_items
def add_item(self, added_item:Item):
if added_item.weight + self.current_capacity() > self.max_capacity:
return f'{added_item.name} is too heavy to fit inside the inventory: {added_item.weight + self.current_capacity() - self.max_capacity}kg in surplus!'
else:
self.items.append(added_item)
return f'{added_item.name} added to inventory. Current items load: {self.current_capacity()}kg'
def remove_item(self, r_item):
if r_item in self.items:
self.items.remove(r_item)
return f'{r_item.name} was removed from the inventory'
else:
return f'{r_item.name} is not present within the inventory'
def get_item(self, item_name:str):
for item in self.items:
if item.name == item_name:
return item
# The item was not found
return None

37
items/inventory.py Normal file
View File

@@ -0,0 +1,37 @@
from serializable import Serializable
from items.item import Item
class Inventory(Serializable):
def __init__(self, max_capacity:int = 5):
super().__init__()
self.items:list[Item] = []
self.max_capacity = max_capacity # Maximum umber of items
def current_capacity(self):
return len(self.items)
def list_items(self):
s_items = ''
for item in self.items:
s_items += item.__str__() + '; '
return s_items
def add_item(self, added_item:Item):
if self.current_capacity() == self.max_capacity:
return f'The inventory is full!'
else:
self.items.append(added_item)
return f'{added_item.name} added to inventory. Current number of items: {self.current_capacity()}/{self.max_capacity}'
def remove_item(self, item_id:str):
searched_item = self.get_item(item_id=item_id)
if searched_item:
return f'{searched_item.name} was removed from the inventory.'
raise ValueError(f'Item #{item_id} is not present within the inventory!')
def get_item(self, item_id:str):
for item in self.items:
if item.id == item_id:
return item
# The item was not found
return None

View File

@@ -1,19 +1,21 @@
from serializable import Serializable from serializable import Serializable
import uuid
class Item(Serializable): class Item(Serializable):
def __init__(self,name:str, description:str, stat_modifier:str, weight:float = 10.0): def __init__(self,name:str, description:str, stat_modifier:str):
super().__init__() super().__init__()
self.id = str(uuid.uuid4())
self.name = name self.name = name
self.description = description self.description = description
self.stat_modifier = stat_modifier self.stat_modifier = stat_modifier
self.weight = weight
def __str__(self): def __str__(self):
return f"{self.name} ({self.weight} kg): {self.description}" return f"{self.name}: {self.description}"
class Equippable(Item): class Equippable(Item):
def __init__(self, name, description, stat_modifier, equipped:bool, weight = 10.0): def __init__(self, name, description, stat_modifier, equipped:bool):
super().__init__(name, description, stat_modifier, weight) super().__init__(name, description, stat_modifier)
self.equipped = equipped self.equipped = equipped
def equip(self): def equip(self):
@@ -23,8 +25,8 @@ class Equippable(Item):
self.equipped = False self.equipped = False
class Consummable(Item): class Consummable(Item):
def __init__(self, name, description, stat_modifier, nb_of_uses:int, weight = 10.0): def __init__(self, name, description, stat_modifier, nb_of_uses:int):
super().__init__(name, description, stat_modifier, weight) super().__init__(name, description, stat_modifier)
self.nb_of_uses = nb_of_uses self.nb_of_uses = nb_of_uses
def consumme(self): def consumme(self):

View File

@@ -1,6 +0,0 @@
def main():
print("Hello from mcp-nlp!")
if __name__ == "__main__":
main()

View File

@@ -11,7 +11,7 @@ from serializable import Serializable
import json import json
import os import os
mcp = FastMCP("wyvern-castle") mcp = FastMCP("wyvern-castle")
game = Game() game: Game = None
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@@ -21,8 +21,53 @@ logging.basicConfig(
] ]
) )
# History file path # Constants
HISTORY_FILE = "game_history.json" HISTORY_FILE = "game_history.json"
SAVE_PATH = "save_"
@mcp.tool()
async def load_game(slot:int):
"""Loads an already existing game.
Args:
slot: Integer id of the save slot.
"""
global game
path = SAVE_PATH + str(slot) + ".json"
try:
with open(path, "r", encoding="utf-8") as f:
game.deserialize(f.read())
return {
"success": True,
"msg": f"{path} as been successfully loaded!"
}
except OSError as e:
return {
"success": False,
"error": str(e)
}
@mcp.tool()
async def save_game(slot:int):
"""Saves the current game to the given slot.
Args:
slot: Integer id of the save slot.
"""
global game
path = SAVE_PATH + str(slot) + ".json"
try:
with open(path, "w", encoding="utf-8") as f:
f.write(game.serialize())
return {
"success": True,
"msg": f"{path} as been successfully saved!"
}
except OSError as e:
return {
"success": False,
"error": str(e)
}
def append_to_history(event: Dict[str, Any]): def append_to_history(event: Dict[str, Any]):
"""Append a game event to the history file.""" """Append a game event to the history file."""