Activité pratique
Durée3h45Consignes globales
Dans cette séance, vous allez implémenter en Python une version simplifiée du jeu de société Citadelles pour 2 joueurs. Cette activité vous permettra de mettre en pratique les concepts de programmation orientée objet vus en Algo 1 et d’appliquer les bonnes pratiques de développement logiciel acquises dans les séances précédentes (documentation, tests, programmation défensive).
Vous partirez de la conception POO déjà réalisée en Algo 1 (identification des classes, attributs, méthodes et relations) pour aboutir à un programme exécutable permettant de jouer une partie complète.
A la fin de la séance, vous devrez être capable de :
- Implémenter des classes Python à partir d’un modèle de conception
- Structurer un projet multi-fichiers
- Écrire et exécuter des tests unitaires pour valider votre code
- Créer une interface simple en ligne de commande pour interagir avec votre programme
- Jouer une partie à 2 rôles (en incarnant les deux joueurs)
Important
Le but de cette session est de vous aider à maîtriser des notions importantes en informatique. Un assistant de programmation intelligent tel que GitHub Copilot, que vous avez peut-être déjà installé, sera capable de vous fournir une solution à ces exercices basée uniquement sur un nom de fichier judicieusement choisi.
Dans un but d’entraînement, nous vous conseillons de désactiver ces outils.
Contenu de l’activité
Préparation de l’environnement
Avant de commencer, créez un projet uv dédié à cette séance. Dans un terminal, placez-vous dans le dossier de la séance (par exemple imt/s5/info/prog/session4), puis lancez la commande suivante :
Vous écrirez tous les fichiers de cette séance dans ce projet, en ajoutant les bibliothèques dont vous avez besoin avec uv add et en exécutant vos programmes dans l’environnement virtuel associé. Au besoin, revoyez la fiche Gestion moderne de projet Python avec uv.
Organisation de l’activité
Vous allez travailler individuellement ou en binôme pour implémenter le jeu. Le TP est découpé en 6 étapes progressives :
| Étape | Durée | Objectif | Livrable |
|---|---|---|---|
| 0 | 15 min | Rappel de la conception et mise en place | Schéma de classes, dépôt Git |
| 1 | 45 min | Implémentation des classes de base | carte.py, quartier.py, personnage.py |
| 2 | 45 min | Collections de cartes | pioche.py, jeu.py, cite.py |
| 3 | 45 min | Classe Joueur | joueur.py + tests |
| 4 | 45 min | Classe Partie et intégration | partie.py, main.py |
| 5 | 30 min | Tests complets et jeu | Programme exécutable |
Conseil
Ne passez à l’étape suivante que lorsque tous les tests de l’étape précédente sont validés. Cela vous évitera des problèmes d’intégration complexes.
Rappel de la conception et mise en place (15 min)
Objectif : S’assurer que tout le monde part sur de bonnes bases en rappelant la conception POO déjà réalisée en Algo 1.
Approche incrémentale
Toutes les méthodes d’une classe n’ont pas besoin d’être implémentées immédiatement.
Adoptez une approche progressive :
- Commencez par créer le squelette de vos classes avec leurs attributs et des méthodes vides (avec
pass) - Implémentez d’abord les méthodes qui ne dépendent d’aucune autre classe
- Implémentez ensuite les méthodes qui dépendent de classes déjà disponibles
- Laissez pour plus tard les méthodes qui dépendent de classes pas encore créées
Exemple : Pour la classe Joueur, vous pouvez implémenter recevoir_or() et payer() tout de suite (elles ne dépendent que de l’attribut or), mais construire_quartier() devra attendre que Jeu et Cite soient implémentées.
Cela évite les blocages et correspond à la manière dont on développe réellement du code.
Rappel des classes incontournables
En Algo 1, vous avez identifié les classes principales pour modéliser Citadelles. Dans ce TP, nous avons fait un choix de conception, afin de simplifier l’exercice. Ce choix est discutable, vous pouvez d’ailleurs en discuter avec votre encadrant(e). Pour démarrer, voici un rappel des classes incontournables :
Carte: classe de base pour tous les types de cartesQuartieretPersonnage: sous-classes deCarteJoueur(ouPlayer) : représente un·e joueur·eusePartie: orchestre le déroulement du jeu
Création des classes
N’anticipez pas la création des différentes classes, l’ordre de création vous est donné tout au long du TP, veillez à bien le respecter.
Question de rappel : Quelles autres classes ou regroupements de cartes aviez-vous identifiés ?
Indice
Pensez aux différents regroupements de cartes mentionnés dans les règles :
- les quartiers construits par un joueur (la cité)
- les cartes en main d’un joueur (la main)
- les cartes disponibles pour être piochées (la pioche)
- les personnages non utilisés (l’écart)
- les personnages choisis par un joueur
Schéma conceptuel de base
Voici deux schémas simplifiés des relations entre les principales classes (à compléter et affiner) :
Relations d’héritages entre les cartes :
classDiagram
class Carte
class Quartier
class Personnage
Carte <|-- Quartier
Quartier <|-- Noble
Quartier <|-- Religieux
Quartier <|-- Commercial
Quartier <|-- Militaire
Quartier <|-- Prestige
Carte <|-- Personnage
Personnage <|-- Assassin
Personnage <|-- Voleur
Personnage <|-- Magicien
Personnage <|-- Roi
Personnage <|-- Eveque
Personnage <|-- Marchand
Personnage <|-- Architecte
Personnage <|-- Condottiere
En programmation orientée objet (POO), le mécanisme des classes abstraites permet de définir des comportements (méthodes) dont l’implémentation (le code dans la méthode) se fait dans les classes filles. Ainsi, on a l’assurance que les classes filles respecteront le contrat défini par la classe mère abstraite [src: Wikipedia]. C’est un intermédiaire entre l’interface et la classe. Dans ce TP,
Classe abstraite ?
Carte hérite de ABC pour signaler son rôle de classe de base, mais elle reste instanciable tant qu’elle ne déclare aucune méthode @abstractmethod. C’est ce qui permet de la tester directement (Carte("une carte")), de même que Quartier.
classDiagram
Partie -- "2" Joueur
Joueur -- Jeu
Joueur -- Cité
Joueur -- "2" Personnage
Partie -- Personnage
Partie -- Pioche
Vérification de la compréhension
Avant de commencer à coder, assurez-vous de pouvoir répondre à ces questions :
- Quels attributs une carte
Quartierdoit-elle avoir ? - Quels attributs une carte
Personnagedoit-elle avoir ? - Comment modéliser la relation entre un
Joueuret ses cartes (main, cité, personnages) ? - Où stocker les cartes Quartier qui n’ont pas encore été piochées ?
Mise en place de l’environnement
-
Organisation du projet : tous les fichiers de ce TP seront créés directement à la racine du projet
uvde la séance, les uns à côté des autres :- un fichier par classe (ou famille de classes) :
carte.py,quartier.py, etc. - un fichier de tests par fichier de code, nommé
test_*.py:test_carte.py,test_quartier.py, etc.
Le fichier
main.pycréé paruv initsera remplacé à l’étape 4 par celui qui vous est fourni. - un fichier par classe (ou famille de classes) :
-
Première version dans Git (optionnel) : le dépôt Git a déjà été créé par
uv init, il ne reste donc qu’à enregistrer une première version. Depuis la racine du projet :
Implémentation des classes de base (45 min)
Objectif : Coder les classes les plus simples et indépendantes.
Attributs protégés
Les attributs des classes seront toujours considérés comme protégés, ainsi leur nom devra toujours commencer par le caractère _.
Ça ne sera cependant pas le cas des paramètres (noms utilisés dans la signature d’une méthode) ni des arguments (éléments passés lors de l’appel de la méthode).
Toutes les méthodes peuvent être implémentées
Ces classes ne dépendent d’aucune autre classe du projet. Vous pouvez implémenter toutes leurs méthodes dès maintenant.
Aide
Pour créer une énumération en Python, utilisez le module enum (les Enums en Python):
Pour créer une classe abstraite, utilisez le module abc (les classes abstraites en Python.) :
Une méthode décorée par @abstractmethod doit être implémentée dans les classes filles, et la classe qui la déclare ne peut plus être instanciée.
Dans ce TP, la classe Carte hérite de ABC mais ne déclare aucune méthode abstraite : le code qui vous est fourni plus bas n’utilise donc pas @abstractmethod.
Classe Carte (classe de base)
Créez la classe Carte dans carte.py :
Carte(classe abstraite avec attribut :_nom)
Exigences :
- Chaque classe doit être documentée (docstring).
- Cette classe servira de base pour
QuartieretPersonnage.
Code pour Carte
Voici le code complet pour carte.py :
Conseil : Testez la création d’une carte et l’affichage avec __str__.
Tests pour Carte
Voici des tests à copier dans le fichier test_carte.py, à créer à côté de carte.py :
Conseil : Testez aussi les limites (_nom absent) et les types incorrects.
Remarques: Pour lancer les tests, vous pouvez cliquer sur l’icône à gauche de l’éditeur, cliquez sur Configure Python Tests et sélectionnez consécutivement unittest, . Root directory et test_*.py afin de générer (ou modifier) le fichier .vscode/settings.json. Ensuite, cliquez sur la double flèche en haut de la vue Tests pour exécuter tous les tests.
Sinon, depuis la racine du projet, lancez la commande suivante dans le terminal:
Hiérarchie des Quartiers
Créez les classes suivantes dans quartier.py :
Couleur(énumération pour les 5 couleurs : Noble, Religieux, Commercial, Militaire, Prestige)Quartier(classe héritant deCarteavec attributs :_nom,_couleur,_cout,_points)- Sous-classes de
Quartierpour chaque type (ex:Noble,Religieux, etc.)
Exigences :
Quartierhérite deCarte(utilisezfrom carte import Carte)- Chaque classe doit être documentée (docstring).
- Vérifiez que les arguments
coutetpointssont des entiers positifs (programmation défensive). - Ajoutez à
Quartierles méthodesget_points(self) -> intetget_cout(self) -> int, qui retournent respectivement les points et le coût du quartier (elles seront utilisées parCiteetPartie). - Écrivez des tests unitaires dans
test_quartier.py.
Conseils :
- Veillez à typer les paramètres des méthodes, pour une meilleure lisibilité et maintenabilité du code.
- Indiquez également le type retour des méthodes.
- Utilisez les exceptions de type
ValueErrorpour la programmation défensive. - Appuyez-vous sur les tests pour développer la classe, en particulier respectez l’ordre et les noms des paramètres.
- L’héritage impose, dans la méthode
__init__(self, ...), d’appelersuper().__init__(...)avec les arguments attendus par la classe mère.
Exemples de tests pour Quartier
Voici des exemples de tests que votre fichier test_quartier.py pourrait contenir :
Conseil : Testez aussi les limites (cout <= 0, points <= 0) et les types incorrects.
Code de chargement des quartiers
Pour vous faire gagner du temps, voici une fonction utilitaire pour créer toutes les cartes Quartier. Ajoutez-la à la fin de votre fichier quartier.py :
Utilisation : une fois la classe Pioche écrite (étape suivante), cette fonction servira à créer la pioche de quartiers. C’est ce que fait la classe Partie fournie à l’étape 4 :
Hiérarchie des Personnages
Créez les classes suivantes dans personnage.py :
Personnage(classe héritant deCarteavec attributs :_nom,_description,_ordre)- Sous-classes pour chaque rôle :
Assassin,Voleur,Magicien,Roi,Eveque,Marchand,Architecte,Condottiere
Exigences :
Personnagehérite deCarte(utilisezfrom carte import Carte)- L’attribut
_ordredétermine l’ordre de jeu (Assassin=1, Voleur=2, …, Condottiere=8). - Implémentez la méthode
__eq__dans la classePersonnagepour permettre la comparaison d’instances (ex:if p1 == p2). Deux personnages sont égaux s’ils ont le même ordre. - Documentez chaque classe.
- Écrivez des tests dans
test_personnage.py.
Conseils :
- Les conseils précédents s’appliquent ici également.
- N’hésitez pas à copier/coller depuis d’autres fichiers (en adaptant les parties dupliquées) et depuis les Règles simplifiées pour 2 joueurs.
Exemples de tests pour Personnage
Voici des exemples de tests pour votre fichier test_personnage.py :
Conseil : Testez aussi que les descriptions sont bien définies et que les noms sont corrects.
Code de chargement des personnages
Pour vous faire gagner du temps, voici une fonction utilitaire pour créer toutes les cartes Personnage. Ajoutez-la à la fin de votre fichier personnage.py :
Utilisation : cette fonction servira à obtenir les 8 personnages du jeu. C’est ce que fait la classe Partie fournie à l’étape 4 :
Collections de cartes (45 min)
Objectif : Implémenter les classes qui gèrent les groupes de cartes.
Toutes les méthodes peuvent être implémentées
Ces classes dépendent uniquement de Quartier et Personnage (déjà implémentées en Étape 1). Vous pouvez implémenter toutes leurs méthodes dès maintenant.
Pioche
À quoi sert la classe Pioche ?
La classe Pioche représente le paquet de cartes commun à tous les joueurs, posé au centre de la table, dans lequel chacun vient tirer des cartes pendant la partie. Elle permet de mélanger le paquet, d’en tirer la première carte et d’y remettre une carte. Cette classe doit simplement gérer l’ordre et le stock des cartes, sans connaître leur utilisation future.
Responsabilité unique : Pioche ne gère que le paquet de cartes. Elle ne sait pas qui pioche ni ce que devient la carte tirée : c’est la classe Partie qui décide quand piocher et à quel joueur donner la carte.
Créez la classe Pioche dans pioche.py avec les méthodes :
melanger(self) -> None: mélange les cartes de la piochepiocher(self) -> Carte | None: pioche la première carte (retourneNonesi vide)ajouter_carte(self, carte:Carte) -> None: ajoute une carte à la piocheest_vide(self) -> bool: retourneTruesi la pioche est vide
De plus, le constructeur prendra en paramètre une liste de cartes qui sera stocké dans l’attribut _cartes.
Remarque : Dans une pioche, nous ne souhaitons manipuler que des objets de type de Carte. C’est pourquoi dans le constructeur, le paramètre cartes devra être typé avec cartes : list[Carte]. Cela implique d’importer correctement la classe Carte dans Pioche.
Conseil : Le module random dispose d’une fonction nommée shuffle permettant de mélanger (en place) les éléments d’une liste passée en paramètre (Documentation).
Tests : test_pioche.py
Exemples de tests pour Pioche
Voici des exemples de tests pour votre fichier test_pioche.py :
Conseil : Testez aussi que piocher() retourne bien None quand la pioche est vide.
Jeu
À quoi sert la classe Jeu ?
La classe Jeu représente le jeu de cartes en main d’un joueur, c’est-à-dire l’ensemble des cartes Quartier qu’il a piochées mais pas encore utilisées. Elle permet de stocker temporairement les cartes disponibles pour un joueur pendant son tour. Cette classe doit simplement gérer l’ajout et le retrait de cartes, sans connaître leur utilisation future.
Responsabilité unique : Jeu ne gère que la collection de cartes. La logique de construction ou de défausse est gérée par la classe Joueur.
Créez la classe Jeu dans jeu.py avec les méthodes :
ajouter_carte(self, carte: Carte) -> None: ajoute une carte Quartier au jeu (quand le joueur pioche)retirer_carte(self, carte: Carte) -> Carte: retire une carte du jeu (retourne la carte retirée)get_cartes(self) -> list[Carte]: retourne la liste des cartes du jeu
Le constructeur déclare un paramètre optionnel nommé cartes qui, s’il est présent, initialisera l’attribut (protégé) de la classe _cartes à celle déclarée ou, s’il est absent, devra initialiser cet attribut à une liste (de cartes) vide.
Tests : test_jeu.py
Exemples de tests pour Jeu
Voici des exemples de tests pour votre fichier test_jeu.py :
Conseil : Testez aussi les cas limites (main vide, carte déjà retirée, carte retirée absente, etc.).
Cité
À quoi sert la classe Cite ?
La classe Cite représente la cité d’un joueur, c’est-à-dire l’ensemble des cartes Quartier qu’il a construites et posées devant lui. Contrairement aux cartes du Jeu, qui ne sont qu’en attente dans la main du joueur, les quartiers de la cité sont définitivement acquis et rapportent des points. Cette classe doit simplement gérer l’ajout de quartiers et le calcul du total des points.
Responsabilité unique : Cite ne gère que la collection des quartiers construits. Vérifier que le joueur possède la carte et qu’il a assez de pièces pour la payer est géré par la classe Joueur.
Créez la classe Cite dans cite.py avec les méthodes :
construire(self, quartier: Quartier) -> None: construit un quartier dans la cité.get_quartiers(self) -> list[Quartier]: retourne la liste des quartiers construits.get_points(self) -> int: retourne le total des points.
Par programmation défensive, la méthode construire(self, quartier: Quartier) -> None doit refuser une carte qui est déjà dans la cité, en levant une ValueError. Il s’agit bien de la même carte (le même objet, ce que teste quartier in ...) : deux cartes différentes portant le même nom, comme deux Manoir, peuvent toutes les deux être construites.
Conseil : N’oubliez pas d’initialiser les attributs.
Tests : test_cite.py
Exemples de tests pour Cité
Voici des exemples de tests pour votre fichier test_cite.py :
Conseil : Testez aussi que la construction d’une carte qui est déjà dans la cité lève une ValueError (avec assertRaises).
Classe Joueur (45 min)
Objectif : Implémenter la classe centrale Joueur.
La classe Joueur dépend de Jeu et Cite (implémentées en Étape 2). Dans la version de Citadelles à deux joueurs, chaque joueur gère deux personnages.
Créez la classe Joueur dans joueur.py avec :
- Attributs :
_nom,_jeu(instance deJeu),_cite(instance deCite),_pieces(entier),_personnages(liste dePersonnageou vide). Seul le nom du joueur est passé en paramètre du constructeur, les autres attributs sont initialisés dans le constructeur. - Méthodes :
recevoir_or(self, montant : int) -> None: ajoute de l’or (vérifier que le montant est strictement positif ou lever uneValueError)payer(self, montant : int) -> bool: retire de l’or (retourneTruesi succès). Vérifier que les conditions sont réunies avant de procéder au retrait du montant.construire_quartier(self, quartier: Quartier) -> bool: construit un quartier du jeu, indique par retour si la construction a eu lieu.get_points(self) -> int: retourne les points totaux
Exigences :
- Vérifiez que le joueur a assez d’or avant de construire.
- Vérifiez que le quartier est dans le jeu du joueur.
- Écrivez des tests complets dans
test_joueur.py. - Vérifier bien les imports.
Remarques: Le mot or est réservé en Python (opérateur booléen), nous ne pouvons donc pas l’utiliser comme nom d’attribut ou de paramètre. C’est pourquoi nous utilisons le terme pieces.
Exemples de tests pour Joueur
Voici des exemples de tests pour votre fichier test_joueur.py :
Conseil : Testez aussi les cas limites (montant négatif, quartier déjà construit, etc.) avec la programmation défensive.
Classe Partie et intégration (45 min)
Objectif : Orchestrer le déroulement du jeu en utilisant les fichiers fournis.
Fichiers à utiliser : Ci-après, vous trouverez 4 fichiers Python à utiliser à partir de cette étape :
Fichiers fournis
Téléchargez et déplacez ces 4 fichiers à la racine de votre projet, à côté de vos autres fichiers. Le fichier main.py fourni remplace celui créé par uv init.
Structure attendue :
Classe Partie (partie.py) :
La classe Partie fournie gère le déroulement complet d’une partie. Plusieurs méthodes sont déjà implémentées :
__init__(self, joueur1: str, joueur2: str) -> None: Initialise la partie avec 2 joueurs, la couronne, les personnages et la pioche_distribuer_quartiers_initiaux(self) -> None: Distribue 4 quartiers à chaque joueur (ordre dépend de la couronne)_distribuer_personnages(self) -> None: Gère le choix des personnages par les joueurs_lancer_tour(self) -> None: Orchestre un tour complet (appelle_jouer_tourpour chaque personnage)_choisir_personnage(self, joueur: Joueur, personnages: Jeu) -> bool: Permet à un joueur de choisir un personnage_jouer_tour(self, joueur: Joueur, personnage: Personnage, pioche: Pioche) -> None: Gère le tour d’un joueur avec un personnage_choisir_revenu(self, joueur: Joueur, pioche: Pioche) -> None: Permet de choisir entre or ou quartiers_construire_quartier(self, joueur: Joueur) -> None: Gère la construction d’un quartier_appliquer_pouvoir(self, personnage: Personnage) -> None: Affiche seulement la description du pouvoir du personnage ; à compléter si vous implémentez les pouvoirs (étape 5)
Méthodes à compléter dans partie.py :
Vous devez implémenter les 3 méthodes marquées TODO :
-
lancer_partie(self) -> None: Méthode principale qui orchestrerait :- Distribution initiale des quartiers (4 par joueur)
- Distribution initiale de l’or (2 pièces par joueur)
- Boucle des tours jusqu’à ce qu’un joueur ait 8 quartiers
- Comptage des points en fin de partie
-
_conditions_arret(self) -> bool: RetourneTruesi un joueur a construit 8 quartiers dans sa cité -
_comptage_points(self) -> None: Calcule et affiche les points de chaque joueur en fin de partie
Conseils pour compléter :
- Utilisez les méthodes déjà implémentées (
_distribuer_quartiers_initiaux,_distribuer_personnages,_lancer_tour) - Inspirez-vous des tests dans
test_partie.pypour comprendre le comportement attendu - La condition d’arrêt :
len(joueur._cite.get_quartiers()) >= 8 - Pour le comptage des points : utilisez
joueur.get_points()
Règles appliquées dans le code fourni :
- Chaque joueur choisit 2 personnages par tour (méthode
_distribuer_personnages()) - Ordre de jeu : selon l’ordre des personnages (Assassin=1 joue en premier)
- Tour de jeu pour chaque personnage :
- Choix du revenu (2 pièces ou piocher 2 quartiers et en garder 1)
- Construction d’un quartier (optionnelle)
- Application du pouvoir spécial (à implémenter si temps)
- La couronne est attribuée aléatoirement au début et change si le Roi est choisi
Important
Le fichier partie.py fourni utilise la classe Jeu pour gérer les personnages disponibles pendant le choix. Assurez-vous que votre classe Jeu (créée en Étape 2) implémente bien :
ajouter_carte(self, carte: Carte) -> Noneretirer_carte(self, carte: Carte) -> Carteget_cartes(self) -> list[Carte]
Le fichier ui.py nécessite d’importer Carte et Quartier. Votre projet doit donc avoir ces classes définies.
Fichier main.py fourni :
Fichier ui.py fourni : Classe utilitaire pour un affichage console agréable avec méthodes statiques pour les titres, menus, messages de succès/erreur, etc.
Tests fournis : Le fichier test_partie.py contient des tests pour valider votre implémentation. Exécutez-le avec python -m unittest test_partie.py.
Intégration et test complet (30 min)
Objectif : Valider que tout fonctionne ensemble.
- Exécutez le programme :
python main.py - Jouez une partie complète à 2 rôles (vous incarnez les deux joueurs).
- Corrigez les bugs en utilisant les messages d’erreur.
- Ajoutez des améliorations (si temps) :
- Implémentez les pouvoirs des personnages (Assassin, Voleur, etc.)
- Déplacez la logique du joueur depuis la classe
Partievers la classeJoueur - Améliorez l’interface (couleurs, tableaux)
- Vérifiez que la logique du jeu est respectée (et testée), comme la validité des actions par exemple.
Après le TP
Compléter l’activité
Avant la prochaine séance, vous devez :
- Terminer les parties obligatoires : Les étapes 4 à 6 (incluses) doivent être fonctionnelles.
- Enregistrer votre code dans votre dépôt Git :
- Réviser les concepts POO et les bonnes pratiques (documentation, tests).
Structure finale du projet
Astuce
Pour exécuter tous les tests d’un coup, lancez python -m unittest -v depuis la racine du projet : tous les fichiers test_*.py sont alors découverts et exécutés automatiquement.
Pour aller plus loin
Réorganiser le projet en packages
Dans ce TP, tous les fichiers sont placés à plat, à la racine du projet. C’est simple, mais le code de l’application et ses tests se retrouvent mélangés, et cela devient vite difficile à lire quand le projet grandit. L’usage est alors de regrouper le code dans un package et de mettre les tests à part, comme dans le projet Mastermind de la séance précédente.
Un module est un fichier Python (carte.py est le module carte). Un package est un dossier qui regroupe des modules. Pour indiquer à Python qu’un dossier est un package, on y place un fichier nommé __init__.py, qui peut rester vide : il est exécuté la première fois que le package est importé.
Structure visée :
Étapes :
-
Déplacez les fichiers : créez les dossiers
citadelles/ettests/, déplacez-y respectivement les fichiers de code (saufmain.py) et les fichierstest_*.py. -
Déclarez le package : créez un fichier vide
citadelles/__init__.py. -
Adaptez les imports : les modules ne sont plus à la racine, il faut donc préciser le package dans lequel ils se trouvent. Dans tous les fichiers (code, tests et
main.py), y compris pour les imports écrits à l’intérieur de certaines méthodes detest_partie.py, remplacez par exemple :par :
-
Relancez le programme et les tests, toujours depuis la racine du projet. Le programme se lance comme avant (
python main.py). Pour les tests, il faut maintenant indiquer le dossier dans lequel les chercher :Dans VS Code, relancez
Configure Python Testsen sélectionnant cette fois le dossiertests.
Pourquoi lancer les commandes depuis la racine ?
Quand Python rencontre from citadelles.carte import Carte, il cherche un dossier citadelles dans une liste de dossiers qui contient notamment celui depuis lequel la commande est lancée. Depuis la racine du projet, le package citadelles est donc trouvé ; depuis le dossier tests/, il ne le serait pas.
Pour aller encore plus loin
Si vous souhaitez prolonger le projet, vous pouvez :
- Ajouter un système de sauvegarde/chargement de partie.
- Créer une interface graphique avec
tkinteroupygame.