diff --git a/.gitignore b/.gitignore index 6884755..cc4f0da 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,5 @@ __pycache__/ .spyproject/ .pydevproject - - +*.egg-info +build diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..825c32f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/app.py b/app.py deleted file mode 100644 index 3cc0d3c..0000000 --- a/app.py +++ /dev/null @@ -1,58 +0,0 @@ -from character_builder import CharacterBuilder -from level_one import Level -import os - - -def action_menu(): - """ Menu Action du niveau """ - print("\n------ Menu Action ------\n") - print("Pièce Suivante :\t 1") - print("Combat : \t\t\t 2") - print("Fuir : \t\t\t\t 3") - print("Quitter :\t\t\t 4") - return int(input("\nChoix : ")) - - -# Exemple d'utilisation -if __name__ == "__main__": - loaded_character = CharacterBuilder.load_character("Jamin") - if loaded_character: - print(loaded_character) - - level = Level(2, CharacterBuilder.load_character("Jamin")) - player = level.player - rooms = level.rooms - number_room = len(rooms) - print(f"Nombre de pièce dans le donjon : {number_room}") - - while level.exit_level: - os.system('cls' if os.name == 'nt' else 'clear') - match action_menu(): - case 1: - if level.current_room_index >= number_room: - print("fin du donjon !") - break - room = rooms[level.current_room_index] - print(f"Pièce actuelle : {level.current_room_index}") - if room is not None: - for mob in room: - print(f"Un {mob.name} est devant toi !") - match action_menu(): - case 2: - level.combat(mob, player) - if player.current_pv <= 0: - print("!!!!!!!!! Joueur mort !!!!!!!!!!") - break - if mob.current_pv == 0: - print(f"Le {mob.name} est mort !!") - case 3: - print("Tu fuis la pièce") - else: - print("\n ------ Pièce vide ------ \n") - player.current_pv = player.max_pv - level.current_room_index += 1 - case 4: - level.exit_level = False - print(f"Au revoir {player.name} !") - case _: - print("Mauvais choix") diff --git a/character_builder.py b/character_builder.py deleted file mode 100644 index fa06a6f..0000000 --- a/character_builder.py +++ /dev/null @@ -1,105 +0,0 @@ -import importlib.util -import json -import os -import sys -from typing import Callable - -from base_mob import BaseMob - - -class CharacterBuilder: - save_character_impl: Callable[[BaseMob], str] - load_character_impl: Callable[[str], None] - - @classmethod - def build_character(cls): - """Demande à l'utilisateur de saisir les attributs du personnage.""" - print("Création d'un nouveau personnage :") - name = input("Nom du personnage : ") - max_pv = int(input("Points de vie maximum : ")) - strength = int(input("Force : ")) - protection = int(input("Protection : ")) - speed = int(input("Vitesse : ")) - mob_type = 0 - return BaseMob(name, max_pv, strength, protection, speed, mob_type) - - @classmethod - def save_character(cls, character: BaseMob) -> None: - """Enregistre un personnage dans un fichier JSON.""" - filename = cls.save_character_impl(character) - print(f"Personnage enregistré dans {filename}.") - - @classmethod - def load_character(cls, name: str) -> BaseMob | None: - """Charge un personnage à partir d'un fichier JSON.""" - try: - return cls.load_character_impl(name) - except Exception as e: - return None - - -def save_character(character: BaseMob) -> str: - """Enregistre un personnage dans un fichier JSON.""" - character_data = { - "name": character.name, - "max_pv": character.max_pv, - "current_pv": character.current_pv, - "strength": character.strength, - "protection": character.protection, - "speed": character.speed, - "mob_type": character.mob_type - } - filename = character.name + ".json" - with open(filename, "w", encoding='UTF-8') as file: - json.dump(character_data, file, indent=4) - return filename - - -def load_character(name: str) -> BaseMob | None: - """Charge un personnage à partir d'un fichier JSON.""" - filename = name + ".json" - if not os.path.exists(filename): - print(f"Erreur : Le fichier {filename} n'existe pas.") - return None - - try: - with open(filename, "r", encoding='UTF-8') as file: - character_data = json.load(file) - - # Vérification des clés essentielles dans le JSON pour éviter les erreurs - required_keys = ["name", "max_pv", "current_pv", - "strength", "protection", "speed", "mob_type"] - if not all(key in character_data for key in required_keys): - print(f"Erreur : Le fichier {filename} ne contient pas toutes les clés nécessaires.") - return None - - # Construction du personnage - return BaseMob( - name=character_data["name"], - max_pv=character_data["max_pv"], - strength=character_data["strength"], - protection=character_data["protection"], - speed=character_data["speed"], - mob_type=character_data["mob_type"] - ) - except json.JSONDecodeError: - print(f"Erreur : Le fichier {filename} n'est pas un JSON valide.") - return None - - -pydantic = None -name = 'pydantic' -if name in sys.modules: - pydantic = sys.modules[name] -elif (spec := importlib.util.find_spec(name)) is not None: - pydantic = importlib.util.module_from_spec(spec) - sys.modules[name] = pydantic - spec.loader.exec_module(pydantic) - -if pydantic and os.environ.get('ELIRON_BUILDER') == 'pydantic': - pydantic_character_builder = importlib.import_module('pydantic_character_builder') - CharacterBuilder.save_character_impl = pydantic_character_builder.save_character - CharacterBuilder.load_character_impl = pydantic_character_builder.load_character -else: - CharacterBuilder.save_character_impl = save_character - CharacterBuilder.load_character_impl = load_character diff --git a/format.sh b/format.sh new file mode 100755 index 0000000..b8f0563 --- /dev/null +++ b/format.sh @@ -0,0 +1,2 @@ +isort src tests +autopep8 src tests diff --git a/level_one.py b/level_one.py deleted file mode 100644 index d3d12ab..0000000 --- a/level_one.py +++ /dev/null @@ -1,71 +0,0 @@ -""" Class Level """ -import random -import mobs_utils -from character_builder import CharacterBuilder - - -class Level(): - """ Class Level """ - - def __init__(self, difficulty, player): - """ Chargement du niveau """ - print("Loading Level") - self.mobs_in_level = mobs_utils.generate_random_mob_list( - random.randint(2, difficulty)) - self.exit_level = True - self.player = player - self.rooms = mobs_utils.generate_random_rooms( - self.mobs_in_level, difficulty) # Liste des pièces du niveau - self.current_room_index = 0 # Index de la pièce actuelle - - print("Level Loaded") - - def combat(self, mob, player): - while (mob.current_pv > 0 and player.current_pv > 0): - if mob.speed > player.speed: - mob.perform_attack(player) - player.perform_attack(mob) - else: - player.perform_attack(mob) - mob.perform_attack(player) - print(f"{player.name} - {player.current_pv} PV") - print(f"{mob.name} - {mob.current_pv} PV") - - -def action_menu(): - """ Menu Action du niveau """ - print("Pièce Suivante :\t1") - print("Quitter :\t\t4") - return int(input("Choix : ")) - -# Exemple d'utilisation -if __name__ == "__main__": - level = Level(2, CharacterBuilder.load_character("Jamin")) - player = level.player - rooms = level.rooms - number_room = len(rooms) - print(f"Nombre de pièce dans le donjon : {number_room}") - - while level.exit_level: - match action_menu(): - case 1: - if level.current_room_index >= number_room: - print("fin du donjon !") - break - room = rooms[level.current_room_index] - print(f"Pièce actuelle : {level.current_room_index}") - if room: - for mob in room: - level.combat(mob, player) - if player.current_pv <= 0: - print("!!!!!!!!! Joueur mort !!!!!!!!!!") - break - else: - print("Pièce vide") - player.current_pv = player.max_pv - level.current_room_index += 1 - case 4: - level.exit_level = False - print(f"Au revoir {player.name} !") - case _: - print("Mauvais choix") diff --git a/menu.py b/menu.py deleted file mode 100644 index ab2ce58..0000000 --- a/menu.py +++ /dev/null @@ -1,26 +0,0 @@ - -# def main_loop(breaker:bool): -# pass - -# def menu (): -# print("Nouvelle partie :\t1") -# print("Charger :\t\t2") -# print("Option :\t\t3") -# print("Quitter :\t\t4") -# return int(input("Choix : ")) - - -# choix = menu() - -# match choix: -# case 1: -# print("Nouvelle partie") -# case 2: -# print("Charger") -# case 3: -# print("Option") -# case 4: -# print("Quitter") -# case _: -# print("Mauvais choix") - diff --git a/mobs_utils.py b/mobs_utils.py deleted file mode 100644 index 76a5044..0000000 --- a/mobs_utils.py +++ /dev/null @@ -1,49 +0,0 @@ -import random -from base_mob import BaseMob - -# Créer des instances de BaseMob -yanaar = BaseMob("Yanaar", 15, 2, 1, 3, 2) -ulnarain = BaseMob("Ulnarain", 20, 3, 3, 2, 2) -skroll = BaseMob("Skroll", 25, 5, 3, 1, 2) - -# Liste d'instances de BaseMob (au lieu d'un ensemble pour autoriser des doublons) -mobs_list = [yanaar, skroll, ulnarain] - - -def generate_random_mob_list(max_mobs): - """ - Génère une liste aléatoire de mobs à partir de la liste des instances disponibles. - - :param max_mobs: Le nombre maximum de mobs dans la liste générée. - :return: Une liste de mobs sélectionnés aléatoirement. - """ - if max_mobs <= 0: - return [] - - num_mobs = random.randint(2, max_mobs) - selected_mobs = [random.choice(mobs_list) for _ in range(num_mobs)] - return selected_mobs - - -def generate_random_rooms(mobs_in_level, num_rooms): - """ - Génère un nombre spécifié de pièces et y distribue les mobs disponibles. - - :param mobs_in_level: Liste des mobs disponibles pour le niveau. - :param num_rooms: Nombre total de pièces à générer. - :return: Une liste de pièces, chaque pièce contenant un ou plusieurs mobs, ou étant vide si aucun mob n'est disponible. - """ - rooms = [] - - for _ in range(num_rooms): - if len(mobs_in_level) > 0: - # Si des mobs sont disponibles, ajouter le premier mob à la pièce - new_room = [] - for x in range(random.randint(1, len(mobs_in_level))): - new_room.append(mobs_in_level.pop(0)) - rooms.append(new_room) - else: - # Ajouter une pièce vide si aucun mob n'est disponible - rooms.append(None) - - return rooms \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d6d66cf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "elironwars-python" +version = "0.0.0" +description = "Lovely Spam! Wonderful Spam!" +readme = "README.md" +keywords = [] +requires-python = ">= 3.14" +dependencies = [ + "pydantic >= 2.13.4", +] +authors = [ + { name = "Raggaroth", email = "b.baudouin@pm.me" }, + { name = "Toucan", email = "anthony.melin@laposte.net" }, +] +maintainers = [ + { name = "Raggaroth", email = "b.baudouin@pm.me" } +] + +[project.urls] +Homepage = "https://gitea.raggaroth-factory.fr/public/elironwars-python" +Documentation = "https://gitea.raggaroth-factory.fr/public/elironwars-python/wiki" +Repository = "https://gitea.raggaroth-factory.fr/public/elironwars-python.git" +Issues = "https://gitea.raggaroth-factory.fr/public/elironwars-python/issues" +Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" + +[build-system] +requires = ["setuptools >= 77.0.3"] +build-backend = "setuptools.build_meta" + +[project.scripts] +elironwars = "elironwars.app:app" + +[tool.isort] +profile = "black" +src_paths = ["src", "tests"] +known_first_party = ["elironwars"] + +[tool.autopep8] +max_line_length = 120 +in-place = true +recursive = true diff --git a/src/elironwars/__init__.py b/src/elironwars/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/elironwars/app.py b/src/elironwars/app.py new file mode 100644 index 0000000..8e086b9 --- /dev/null +++ b/src/elironwars/app.py @@ -0,0 +1,68 @@ +import sys + +from .level import Level +from .menu import Action, action_menu +from .mob import BaseMob, load_character + + +def play_room(room: list[BaseMob], level: Level) -> int: + if room is None: + print("\n ------ Pièce vide ------ \n") + return 1 + + for mob in room: + print(f"Un {mob.name} est devant toi !") + match action_menu([Action.COMBAT, Action.FUIR]): + case Action.COMBAT: + level.combat(mob, level.player) + if level.player.current_pv <= 0: + print("!!!!!!!!! Joueur mort !!!!!!!!!!") + sys.exit(0) + if mob.current_pv == 0: + print(f"Le {mob.name} est mort !!") + case Action.FUIR: + print("Tu fuis la pièce") + return 0 + + return 1 + + +def play_donjon(level: Level) -> None: + while level.exit_level: + print(f"Pièce actuelle : {level.current_room_index}") + match action_menu([Action.PIECE_SUIVANTE, Action.QUITTER]): + case Action.PIECE_SUIVANTE: + if level.current_room_index >= len(level.rooms): + print("fin du donjon !") + break + level.current_room_index += play_room(level.rooms[level.current_room_index], level) + level.player.current_pv = level.player.max_pv + case Action.QUITTER: + level.exit_level = False + print(f"Au revoir {level.player.name} !") + + +def app() -> None: + loaded_character = load_character(sys.argv[1] if len(sys.argv) > 1 else "Jamin") + if not loaded_character: + sys.exit(-1) + + print(loaded_character) + + # Créer des instances de BaseMob + yanaar = BaseMob("Yanaar", 15, 2, 1, 3, 2) + ulnarain = BaseMob("Ulnarain", 20, 3, 3, 2, 2) + skroll = BaseMob("Skroll", 25, 5, 3, 1, 2) + + # Liste d'instances de BaseMob (au lieu d'un ensemble pour autoriser des doublons) + mobs_list = [yanaar, skroll, ulnarain] + + level = Level(2, loaded_character, mobs_list) + print(f"Nombre de pièce dans le donjon : {len(level.rooms)}") + + play_donjon(level) + + +# Exemple d'utilisation +if __name__ == "__main__": + app() diff --git a/src/elironwars/level.py b/src/elironwars/level.py new file mode 100644 index 0000000..b76d7a3 --- /dev/null +++ b/src/elironwars/level.py @@ -0,0 +1,27 @@ +from .random_utils import generate_random_mob_list, generate_random_rooms + + +class Level: + """ Class Level """ + + def __init__(self, difficulty, player, mob_list): + """ Chargement du niveau """ + print("Loading Level") + self.mobs_in_level = generate_random_mob_list(mob_list, difficulty) + self.exit_level = True + self.player = player + self.rooms = generate_random_rooms(self.mobs_in_level, difficulty) # Liste des pièces du niveau + self.current_room_index = 0 # Index de la pièce actuelle + + print("Level Loaded") + + def combat(self, mob, player): + while mob.current_pv > 0 and player.current_pv > 0: + if mob.speed > player.speed: + mob.perform_attack(player) + player.perform_attack(mob) + else: + player.perform_attack(mob) + mob.perform_attack(player) + print(f"{player.name} - {player.current_pv} PV") + print(f"{mob.name} - {mob.current_pv} PV") diff --git a/src/elironwars/menu.py b/src/elironwars/menu.py new file mode 100644 index 0000000..0d10d47 --- /dev/null +++ b/src/elironwars/menu.py @@ -0,0 +1,45 @@ +import os +from enum import Enum + + +class Action(Enum): + PIECE_SUIVANTE = (1, "Pièce suivante") + COMBAT = (2, "Combat") + FUIR = (3, "Fuir") + QUITTER = (4, "Quitter") + + @property + def code(self): + return self.value[0] + + @property + def libelle(self): + return self.value[1] + + +def clear_console() -> None: + os.system('cls' if os.name == 'nt' else 'clear') + + +def action_menu(actions_autorisees: list[Action] = None) -> Action: + """Affiche le menu et retourne une action valide.""" + + actions_autorisees = list(Action) if actions_autorisees is None else actions_autorisees + + while True: + print("\n------ Menu Action ------\n") + + for action in actions_autorisees: + print(f"{action.libelle:<20} : {action.code}") + + try: + choix = int(input("\nChoix : ")) + clear_console() + for action in actions_autorisees: + if action.code == choix: + return action + + print("Mauvais choix") + except ValueError: + clear_console() + print("Veuillez saisir un nombre.") diff --git a/src/elironwars/mob/__init__.py b/src/elironwars/mob/__init__.py new file mode 100644 index 0000000..9a8ab10 --- /dev/null +++ b/src/elironwars/mob/__init__.py @@ -0,0 +1,19 @@ +import importlib.util +import os +import sys + +from .base_mob import BaseMob + +pydantic = None +name = 'pydantic' +if name in sys.modules: + pydantic = sys.modules[name] +elif (spec := importlib.util.find_spec(name)) is not None: + pydantic = importlib.util.module_from_spec(spec) + sys.modules[name] = pydantic + spec.loader.exec_module(pydantic) + +if pydantic and os.environ.get('ELIRON_BUILDER') == 'pydantic': + from .pydantic_builder import load_character, save_character +else: + from .default_builder import load_character, save_character diff --git a/base_mob.py b/src/elironwars/mob/base_mob.py similarity index 98% rename from base_mob.py rename to src/elironwars/mob/base_mob.py index a863a14..95c95ee 100644 --- a/base_mob.py +++ b/src/elironwars/mob/base_mob.py @@ -62,4 +62,4 @@ class BaseMob: """Retourne une représentation sous forme de chaîne du mob.""" return (f"{self.name} - PV: {self.current_pv}/{self.max_pv}, " f"Force: {self.strength}, Protection: {self.protection}, " - f"Vitesse: {self.speed}") \ No newline at end of file + f"Vitesse: {self.speed}") diff --git a/src/elironwars/mob/default_builder.py b/src/elironwars/mob/default_builder.py new file mode 100644 index 0000000..a8f29c9 --- /dev/null +++ b/src/elironwars/mob/default_builder.py @@ -0,0 +1,65 @@ +import json +import os + +from .base_mob import BaseMob + + +def build_character(): + """Demande à l'utilisateur de saisir les attributs du personnage.""" + print("Création d'un nouveau personnage :") + name = input("Nom du personnage : ") + max_pv = int(input("Points de vie maximum : ")) + strength = int(input("Force : ")) + protection = int(input("Protection : ")) + speed = int(input("Vitesse : ")) + mob_type = 0 + return BaseMob(name, max_pv, strength, protection, speed, mob_type) + + +def save_character(character: BaseMob) -> str: + """Enregistre un personnage dans un fichier JSON.""" + character_data = { + "name": character.name, + "max_pv": character.max_pv, + "current_pv": character.current_pv, + "strength": character.strength, + "protection": character.protection, + "speed": character.speed, + "mob_type": character.mob_type + } + filename = character.name + ".json" + with open(filename, "w", encoding='UTF-8') as file: + json.dump(character_data, file, indent=4) + return filename + + +def load_character(name: str) -> BaseMob | None: + """Charge un personnage à partir d'un fichier JSON.""" + filename = name + ".json" + if not os.path.exists(filename): + print(f"Erreur : Le fichier {filename} n'existe pas.") + return None + + try: + with open(filename, "r", encoding='UTF-8') as file: + character_data = json.load(file) + + # Vérification des clés essentielles dans le JSON pour éviter les erreurs + required_keys = ["name", "max_pv", "current_pv", + "strength", "protection", "speed", "mob_type"] + if not all(key in character_data for key in required_keys): + print(f"Erreur : Le fichier {filename} ne contient pas toutes les clés nécessaires.") + return None + + # Construction du personnage + return BaseMob( + name=character_data["name"], + max_pv=character_data["max_pv"], + strength=character_data["strength"], + protection=character_data["protection"], + speed=character_data["speed"], + mob_type=character_data["mob_type"] + ) + except json.JSONDecodeError: + print(f"Erreur : Le fichier {filename} n'est pas un JSON valide.") + return None diff --git a/pydantic_character_builder.py b/src/elironwars/mob/pydantic_builder.py similarity index 97% rename from pydantic_character_builder.py rename to src/elironwars/mob/pydantic_builder.py index cd6ec29..b5a9f8f 100644 --- a/pydantic_character_builder.py +++ b/src/elironwars/mob/pydantic_builder.py @@ -2,7 +2,7 @@ import pathlib from pydantic import BaseModel -from base_mob import BaseMob +from .base_mob import BaseMob class CharacterModel(BaseModel): diff --git a/src/elironwars/random_utils.py b/src/elironwars/random_utils.py new file mode 100644 index 0000000..94b5596 --- /dev/null +++ b/src/elironwars/random_utils.py @@ -0,0 +1,41 @@ +import random + + +def generate_random_mob_list(mob_list, max_mobs): + """ + Génère une liste aléatoire de mobs à partir de la liste fournie. + + :param mob_list: Liste de mob a choisir aleatoirement. + :param max_mobs: Le nombre maximum de mobs dans la liste générée. + :return: Une liste de mobs sélectionnés aléatoirement. + """ + if max_mobs <= 0: + return [] + + num_mobs = random.randint(2, max_mobs) + selected_mobs = [random.choice(mob_list) for _ in range(num_mobs)] + return selected_mobs + + +def generate_random_rooms(mobs_in_level, num_rooms): + """ + Génère un nombre spécifié de pièces et y distribue les mobs disponibles. + + :param mobs_in_level: Liste des mobs disponibles pour le niveau. + :param num_rooms: Nombre total de pièces à générer. + :return: Une liste de pièces, chaque pièce contenant un ou plusieurs mobs, ou étant vide si aucun mob n'est disponible. + """ + rooms = [] + + for _ in range(num_rooms): + if len(mobs_in_level) > 0: + # Si des mobs sont disponibles, ajouter le premier mob à la pièce + new_room = [] + for x in range(random.randint(1, len(mobs_in_level))): + new_room.append(mobs_in_level.pop(0)) + rooms.append(new_room) + else: + # Ajouter une pièce vide si aucun mob n'est disponible + rooms.append(None) + + return rooms diff --git a/tests/mob/__init__.py b/tests/mob/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_character_builder.py b/tests/mob/test_builder.py similarity index 86% rename from test_character_builder.py rename to tests/mob/test_builder.py index 135f49f..e7cbce1 100644 --- a/test_character_builder.py +++ b/tests/mob/test_builder.py @@ -1,10 +1,10 @@ -import unittest -from unittest.mock import patch import json import pathlib +import unittest +from unittest.mock import patch -from base_mob import BaseMob -from character_builder import CharacterBuilder +from elironwars.mob import BaseMob, save_character, load_character +from elironwars.mob.default_builder import build_character ARTHUR_JSON = """{ "name": "Arthur", @@ -37,7 +37,7 @@ class TestCharacterBuilder(unittest.TestCase): ] # Act - character = CharacterBuilder.build_character() + character = build_character() # Assert self.assertIsInstance(character, BaseMob) @@ -60,7 +60,7 @@ class TestCharacterBuilder(unittest.TestCase): ) # Act - CharacterBuilder.save_character(character) + save_character(character) # Assert self.assertTrue(ARTHUR_FILE.exists()) @@ -68,7 +68,7 @@ class TestCharacterBuilder(unittest.TestCase): def test_load_character_file_not_found(self): # Act - result = CharacterBuilder.load_character("Lancelot") + result = load_character("Lancelot") # Assert self.assertIsNone(result) @@ -78,7 +78,7 @@ class TestCharacterBuilder(unittest.TestCase): ARTHUR_FILE.write_text(ARTHUR_JSON, encoding='utf-8') # Act - character = CharacterBuilder.load_character("Arthur") + character = load_character("Arthur") # Assert self.assertIsNotNone(character) @@ -101,7 +101,7 @@ class TestCharacterBuilder(unittest.TestCase): invalid_dict), encoding='utf-8') # Act - result = CharacterBuilder.load_character("Arthur") + result = load_character("Arthur") # Assert self.assertIsNone(result) @@ -111,7 +111,7 @@ class TestCharacterBuilder(unittest.TestCase): ARTHUR_FILE.write_text(ARTHUR_JSON[1:], encoding='utf-8') # Act - result = CharacterBuilder.load_character("Arthur") + result = load_character("Arthur") # Assert self.assertIsNone(result)