Contexte
La galaxie est toujours sous la menace du Mulet. Vos équipages sont recensés, mais une simple liste ne suffit plus : la flotte Galactica compte cinq vaisseaux, des opérateurs aux métiers variés et trois mentalistes de la Seconde Fondation, capables d'influencer l'esprit des autres.
Il est temps de structurer votre programme avec des objets. Chaque personne, chaque vaisseau et la flotte elle-même deviennent des classes, qui portent leurs données et leurs comportements.
- Objectifs : modéliser avec des classes, utiliser l'héritage, le polymorphisme, l'encapsulation et la composition, puis faire passer le programme du TP1 des dictionnaires aux objets.
- Il commence une fois le TP1 terminé. Vous avancez à votre rythme, en séance ou chez vous. Le projet complet est à rendre au plus tard le jeudi 5 novembre à 16h30.
- Il enrichit votre dépôt
4pyth-fondation-nom-prenom. Commitez et poussez à chaque étape. - L'IA est autorisée pour le projet, à une condition : comprendre chaque ligne de votre code.
Un fichier par domaine. Chaque fichier a une seule responsabilité. Revoyez au besoin le principe de responsabilité unique.
| Fichier | Contenu |
|---|---|
person.py | ROLES, ACTIONS et les classes Person, Operator et Mentalist (étape 1) |
spaceship.py | la classe Spaceship (étape 2) |
fleet.py | la classe Fleet (étape 2) |
test_classes.py | les tests de vos classes (étape 3) |
fleet_data.py | les données de la flotte, à copier (étape 4) |
crew.py | les fonctions du menu, adaptées aux objets (étapes 4 et 5) |
main.py | le menu interactif (étape 5) |
1 Les personnes
Dans le fichier person.py, écrivez trois classes : la classe mère Person
et ses deux classes filles Operator et Mentalist.
La classe Person – une personne de la flotte
| Élément | Rôle |
|---|---|
first_name, last_name, gender, age | attributs initialisés par __init__() |
introduce_yourself() | renvoie Je m'appelle Bel Riose, je suis un homme de 48 ans. |
__str__() | renvoie Bel Riose (M, 48 ans) |
last_name vaut "". Vos affichages ne doivent pas laisser d'espace en trop après leur prénom.
Pour retirer un membre de l'équipage, on utilisera son nom de famille… ou son prénom s'il n'en a pas.
La classe Operator(Person) – un membre d'équipage avec un rôle
| Élément | Rôle |
|---|---|
role | un rôle de la liste ROLES du TP1. Un rôle inconnu lève une ValueError. Validez-le avec un setter @property |
experience | niveau d'expérience, 0 par défaut |
act() | affiche l'action du membre selon son rôle (voir ACTIONS ↓) puis appelle gain_experience() |
gain_experience() | augmente l'expérience de 1 |
__str__() | complète celui de Person avec son rôle : Hugo Crast (M, 37 ans) – pilote |
ROLES = ["commandant", "pilote", "technicien", "armurier", "marchand", "entretien"]
ACTIONS = {
"commandant": "donne ses ordres à l'équipage",
"pilote": "pilote le vaisseau",
"technicien": "répare les moteurs",
"armurier": "vérifie les canons",
"marchand": "négocie une cargaison",
"entretien": "nettoie le vaisseau",
}
La classe Mentalist(Person) – un membre de la Seconde Fondation
| Élément | Rôle |
|---|---|
mana | points de mana, 100 par défaut, toujours compris entre 0 et 100. Bornez-les avec un setter @property |
act() | affiche Joie se concentre. et regagne 10 points de mana |
recharge_mana() | regagne 50 points de mana |
influence(operator) | coûte 20 points de mana et force l'opérateur à agir (appelle son act()). Sans assez de mana, affiche
Joie n'a pas assez de mana pour influencer Hugo Crast. et renvoie False |
__str__() | Joie (F, 25 ans) – mentaliste, 100 mana |
Operator et Mentalist ont tous deux une méthode act() sans argument.
Un vaisseau pourra donc faire agir tout son équipage avec une simple boucle, sans jamais tester le type de chaque membre.
Indices
- Revoyez les notions d'héritage, de
super()et de@property(validation d'une écriture). - Dans
__init__(), écrivezself.role = role. L'affectation passe par votre setter, donc la validation s'applique aussi à la création. - Pour borner une valeur :
max(0, min(value, 100)). - Une propriété
namequi assemble le prénom et le nom puis retire les espaces superflus (strip()) vous simplifiera tous les affichages.
2 Les vaisseaux et la flotte
Écrivez la classe Spaceship dans le fichier spaceship.py et la classe Fleet dans le fichier fleet.py.
Importez les classes dont vous avez besoin, par exemple from person import Operator.
La classe Spaceship – un vaisseau et son équipage
| Élément | Rôle |
|---|---|
name, ship_type, condition | nom, type ("transport", "guerre" ou "marchand")
et état ("opérationnel" par défaut ou "endommagé") |
crew | liste d'objets Operator et Mentalist, vide au départ |
MAX_CREW = 10 | attribut de classe : capacité maximale |
add_member(member) | ajoute un membre. Lève une ValueError si le vaisseau est plein ou si le nom est déjà à bord |
find_member(name) | renvoie le membre correspondant ou None |
remove_member(name) | retire le membre et le renvoie (ou None s'il est introuvable) |
check_preparation() | renvoie True si l'équipage compte au moins un pilote et un technicien |
watch_round() | tour de garde : chaque membre de l'équipage agit |
__str__() | Fearless (guerre, opérationnel) – 6 membres |
La classe Fleet – la flotte
| Élément | Rôle |
|---|---|
name, spaceships | nom de la flotte et liste d'objets Spaceship, vide au départ |
MAX_SPACESHIPS = 15 | attribut de classe : capacité maximale |
add_spaceship(spaceship) | ajoute un vaisseau. Lève une ValueError si la flotte est pleine ou si le nom existe déjà |
find_spaceship(name) | renvoie le vaisseau correspondant (sans tenir compte de la casse) ou None |
statistics() | affiche le nombre de vaisseaux et de membres, la répartition par rôle, le nombre de mentalistes, l'expérience moyenne des opérateurs et le nombre de vaisseaux prêts |
Indices
- Revoyez les notions de composition (des objets dans des objets) et d'
isinstance(). - Un mentaliste n'a pas de rôle. Dans
check_preparation()etstatistics(), ne regardez le rôle que des objetsOperator.
3 Tester vos classes
Dans le fichier test_classes.py, importez vos classes, instanciez des objets de chaque classe et appelez leurs
méthodes. Les classes restent ainsi séparées de leurs tests. Voici ce que doit afficher ce programme de test.
from person import Operator, Mentalist
from spaceship import Spaceship
bel = Operator("Bel", "Riose", "M", 48, "commandant")
hugo = Operator("Hugo", "Crast", "M", 37, "pilote")
joie = Mentalist("Joie", "", "F", 25)
print(bel.introduce_yourself()) # Je m'appelle Bel Riose, je suis un homme de 48 ans.
print(joie.introduce_yourself()) # Je m'appelle Joie, je suis une femme de 25 ans.
print(hugo) # Hugo Crast (M, 37 ans) – pilote
print(joie) # Joie (F, 25 ans) – mentaliste, 100 mana
hugo.act() # Hugo Crast pilote le vaisseau.
print(hugo.experience) # 1
for _ in range(6):
joie.influence(hugo) # 5 fois : Joie influence Hugo Crast. (puis Hugo agit)
# 6e fois : Joie n'a pas assez de mana…
print(joie.mana) # 0
joie.recharge_mana()
joie.recharge_mana()
joie.recharge_mana()
print(joie.mana) # 100 : le mana ne dépasse jamais 100
ship = Spaceship("Fearless", "guerre")
ship.add_member(bel)
ship.add_member(hugo)
print(ship.check_preparation()) # False : pas de technicien
ship.add_member(Operator("Yugo", "Amary", "M", 33, "technicien"))
print(ship.check_preparation()) # True
print(ship) # Fearless (guerre, opérationnel) – 3 membres
Operator("Eve", "Hacker", "F", 30, "root")
# ValueError: rôle inconnu : root
4 Construire la flotte
Copiez les données de la flotte Galactica dans le fichier fleet_data.py. Chaque vaisseau et chaque membre y est décrit par un
dictionnaire. La clé type indique s'il faut créer un Operator ou un Mentalist.
Données à copier – fleet_data.py (5 vaisseaux, 24 membres)
FLEET_DATA = {
"name": "Galactica",
"spaceships": [
{"name": "Bayta", "ship_type": "marchand", "condition": "opérationnel", "crew": [
{"type": "operator", "first_name": "Hugo", "last_name": "Crast", "gender": "M", "age": 37, "role": "pilote"},
{"type": "operator", "first_name": "Gaal", "last_name": "Dornick", "gender": "F", "age": 34, "role": "technicien"},
{"type": "operator", "first_name": "Lathan", "last_name": "Devers", "gender": "M", "age": 46, "role": "marchand"},
{"type": "operator", "first_name": "Novi", "last_name": "Sura", "gender": "F", "age": 25, "role": "entretien"},
]},
{"name": "Dark Nebula", "ship_type": "guerre", "condition": "endommagé", "crew": [
{"type": "operator", "first_name": "Jagger", "last_name": "Fount", "gender": "M", "age": 41, "role": "pilote"},
{"type": "operator", "first_name": "Munn", "last_name": "Compor", "gender": "M", "age": 27, "role": "technicien"},
{"type": "operator", "first_name": "Salvor", "last_name": "Hardin", "gender": "F", "age": 28, "role": "armurier"},
{"type": "operator", "first_name": "Ebling", "last_name": "Mis", "gender": "M", "age": 34, "role": "entretien"},
{"type": "mentalist", "first_name": "Tellem", "last_name": "Bond", "gender": "M", "age": 48, "mana": 100},
]},
{"name": "Fearless", "ship_type": "guerre", "condition": "opérationnel", "crew": [
{"type": "operator", "first_name": "Bel", "last_name": "Riose", "gender": "M", "age": 48, "role": "commandant"},
{"type": "operator", "first_name": "Hober", "last_name": "Mallow", "gender": "M", "age": 52, "role": "pilote"},
{"type": "operator", "first_name": "Yugo", "last_name": "Amary", "gender": "M", "age": 33, "role": "technicien"},
{"type": "operator", "first_name": "Han", "last_name": "Pritcher", "gender": "M", "age": 31, "role": "armurier"},
{"type": "operator", "first_name": "Harlan", "last_name": "Branno", "gender": "F", "age": 38, "role": "entretien"},
{"type": "mentalist", "first_name": "Yanna", "last_name": "Seldon", "gender": "F", "age": 52, "mana": 100},
]},
{"name": "Unimara", "ship_type": "marchand", "condition": "opérationnel", "crew": [
{"type": "operator", "first_name": "Fallom", "last_name": "", "gender": "F", "age": 28, "role": "pilote"},
{"type": "operator", "first_name": "Toran", "last_name": "Darell", "gender": "M", "age": 52, "role": "technicien"},
{"type": "operator", "first_name": "Limmar", "last_name": "Ponyets", "gender": "M", "age": 43, "role": "marchand"},
{"type": "operator", "first_name": "Jord", "last_name": "Commasson", "gender": "M", "age": 53, "role": "entretien"},
{"type": "mentalist", "first_name": "Joie", "last_name": "", "gender": "F", "age": 25, "mana": 100},
]},
{"name": "Weinis", "ship_type": "marchand", "condition": "endommagé", "crew": [
{"type": "operator", "first_name": "Lewis", "last_name": "Pirenne", "gender": "M", "age": 34, "role": "pilote"},
{"type": "operator", "first_name": "Stora", "last_name": "Gendibal", "gender": "F", "age": 35, "role": "technicien"},
{"type": "operator", "first_name": "Eskel", "last_name": "Gorov", "gender": "M", "age": 32, "role": "marchand"},
{"type": "operator", "first_name": "Bayta", "last_name": "Darell", "gender": "F", "age": 48, "role": "entretien"},
]},
],
}
Dans le fichier crew.py, écrivez la fonction build_fleet(data), qui parcourt ces dictionnaires et renvoie un objet Fleet
complet, avec ses vaisseaux et leurs équipages.
build_fleet() vous servira alors à la recharger.
5 Le menu en objets
- Au lancement,
main.pyconstruit la flotte avecbuild_fleet(FLEET_DATA). - Adaptez les options [1] à [4] du TP1. On choisit d'abord le vaisseau par son nom, puis on agit sur son équipage. Pour ajouter un membre, on précise s'il s'agit d'un opérateur ou d'un mentaliste.
- Ajoutez les options [5] Ajouter un vaisseau et [6] Statistiques de la flotte. Le TP3 ajoutera [7] et [8].
- Les
ValueErrorlevées par vos classes (vaisseau plein, nom déjà pris, rôle inconnu…) sont interceptées et affichées. Elles ne font jamais planter le programme.
===== Flotte marchande – Poste de commandement =====
[1] Ajouter un membre
[2] Retirer un membre
[3] Afficher la flotte
[4] Vérifier un vaisseau
[5] Ajouter un vaisseau
[6] Statistiques de la flotte
[0] Quitter
Votre choix : 6
Flotte Galactica : 5 vaisseaux, 24 membres
pilote : 5
technicien : 5
marchand : 3
entretien : 5
armurier : 2
commandant : 1
mentaliste : 3
Expérience moyenne des opérateurs : 0.0
Vaisseaux prêts : 5 / 5
git add . puis git commit -m "Ajout des classes Spaceship et Fleet" et git push.
+ Pour aller plus loin
Bonus 1 – Un équipage vraiment prêt
check_preparation()exige aussi un mentaliste ayant au moins 50 points de mana.- Un vaisseau
"endommagé"n'est jamais prêt. Combien de vaisseaux de la flotte restent prêts ?
Bonus 2 – Nouvelles options du menu
- Tour de garde : tout l'équipage d'un vaisseau agit (
watch_round()). - Influence : un mentaliste influence un opérateur de son vaisseau.
- Choisissez des numéros libres. Les options [7] et [8] sont réservées au TP3.
Bonus 3 – Promotions
- Comme au TP1, un technicien ayant au moins 10 points d'expérience peut être promu pilote.
Cette fois, c'est une méthode
promote()de la classeOperator.
Bonus 4 – Messages immersifs
- Enrichissez les messages de vos classes pour faire vivre l'univers de Fondation : alertes, dialogues, ambiance…
✓ Avant de continuer
Vérifiez chaque point. Ils correspondent à la grille d'évaluation du projet Fondation.
- Les cinq classes existent avec leurs attributs et leurs méthodes. Vos tests donnent les résultats attendus.
- Chaque domaine a son fichier :
person.py,spaceship.py,fleet.py,test_classes.py,crew.pyetmain.py. OperatoretMentalisthéritent dePersonet appellentsuper().__init__().- Un rôle inconnu est refusé et le mana reste toujours entre 0 et 100.
- Les options [0] à [6] du menu fonctionnent et aucune erreur ne fait planter le programme.
- Les dictionnaires du TP1 ont disparu. Le menu ne manipule plus que des objets.
- Chaque classe et chaque méthode a une docstring. Les noms sont en
snake_case(classes enPascalCase). - Plusieurs commits ont été poussés sur GitHub depuis le TP1.
- Vous pouvez expliquer chaque ligne de votre code.
