Server & Game refactor basics
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,4 +3,6 @@
|
||||
.python-version
|
||||
__pycache__/
|
||||
uv.lock
|
||||
|
||||
# Save/Load files for testing
|
||||
*.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.
Binary file not shown.
Binary file not shown.
12
dice.py
12
dice.py
@@ -1,13 +1,13 @@
|
||||
# Native imports
|
||||
import random as rd
|
||||
|
||||
class Dice():
|
||||
def __init__(self, num_faces:int=20):
|
||||
self.num_faces = num_faces
|
||||
@staticmethod
|
||||
def roll(num_faces=20):
|
||||
return rd.randrange(start=1, stop=num_faces+1, step=1)
|
||||
|
||||
def roll(self):
|
||||
return rd.randrange(start=1, stop=self.num_faces+1, step=1)
|
||||
|
||||
def head_or_tails(self):
|
||||
@staticmethod
|
||||
def head_or_tails():
|
||||
result = rd.randint(0,1)
|
||||
if result: # true
|
||||
return "head" # face
|
||||
|
||||
22
entities/entity.py
Normal file
22
entities/entity.py
Normal 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
|
||||
@@ -1,5 +1,5 @@
|
||||
from entity import 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)
|
||||
30
entity.py
30
entity.py
@@ -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
|
||||
26
event.py
26
event.py
@@ -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
14
events/event.py
Normal 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
14
events/turn.py
Normal 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
41
game.py
@@ -1,10 +1,43 @@
|
||||
from serializable import Serializable
|
||||
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):
|
||||
def __init__(self, seed:int=42):
|
||||
self.players = []
|
||||
self.npcs = []
|
||||
self.items = []
|
||||
self.active_players:list[Player] = []
|
||||
self.active_npcs:list[NPC] = []
|
||||
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?
|
||||
39
inventory.py
39
inventory.py
@@ -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
37
items/inventory.py
Normal 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
|
||||
@@ -1,19 +1,21 @@
|
||||
from serializable import Serializable
|
||||
|
||||
import uuid
|
||||
|
||||
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__()
|
||||
self.id = str(uuid.uuid4())
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.stat_modifier = stat_modifier
|
||||
self.weight = weight
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.weight} kg): {self.description}"
|
||||
return f"{self.name}: {self.description}"
|
||||
|
||||
class Equippable(Item):
|
||||
def __init__(self, name, description, stat_modifier, equipped:bool, weight = 10.0):
|
||||
super().__init__(name, description, stat_modifier, weight)
|
||||
def __init__(self, name, description, stat_modifier, equipped:bool):
|
||||
super().__init__(name, description, stat_modifier)
|
||||
self.equipped = equipped
|
||||
|
||||
def equip(self):
|
||||
@@ -23,8 +25,8 @@ class Equippable(Item):
|
||||
self.equipped = False
|
||||
|
||||
class Consummable(Item):
|
||||
def __init__(self, name, description, stat_modifier, nb_of_uses:int, weight = 10.0):
|
||||
super().__init__(name, description, stat_modifier, weight)
|
||||
def __init__(self, name, description, stat_modifier, nb_of_uses:int):
|
||||
super().__init__(name, description, stat_modifier)
|
||||
self.nb_of_uses = nb_of_uses
|
||||
|
||||
def consumme(self):
|
||||
6
main.py
6
main.py
@@ -1,6 +0,0 @@
|
||||
def main():
|
||||
print("Hello from mcp-nlp!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
49
server.py
49
server.py
@@ -11,7 +11,7 @@ from serializable import Serializable
|
||||
import json
|
||||
import os
|
||||
mcp = FastMCP("wyvern-castle")
|
||||
game = Game()
|
||||
game: Game = None
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -21,8 +21,53 @@ logging.basicConfig(
|
||||
]
|
||||
)
|
||||
|
||||
# History file path
|
||||
# Constants
|
||||
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]):
|
||||
"""Append a game event to the history file."""
|
||||
|
||||
Reference in New Issue
Block a user