Contexte
La galaxie est en plein chaos et la gestion de vos équipages devient critique. Aujourd'hui, chaque fois que le programme s'arrête, la flotte revient à son état de départ. Recrues, promotions et vaisseaux ajoutés disparaissent.
Pour ne plus rien perdre, votre poste de commandement doit sauvegarder l'état de la flotte dans un fichier et savoir le recharger… sans se laisser piéger par un fichier abîmé ou falsifié.
- Objectifs : convertir des objets en dictionnaires, sauvegarder et charger en JSON, gérer les erreurs de fichier et valider des données venues de l'extérieur.
- Vous avancez à votre rythme, en séance ou chez vous. Le projet complet est à rendre au plus tard le jeudi 5 novembre à 16h30. Seul le dernier commit poussé avant cette heure est évalué.
- Commitez et poussez régulièrement. Je suis l'avancée de vos dépôts jusqu'au 5 novembre.
- Bloqué ? Relisez le cours 3, puis écrivez-moi : chris.chevalier@ecole-hexagone.com.
- L'IA est autorisée pour le projet, à une condition : comprendre chaque ligne de votre code. Je le vérifierai en soutenance.
1 Des objets aux dictionnaires
JSON ne connaît pas vos classes. json.dumps(fleet) provoque donc une TypeError. Chaque classe doit savoir
se convertir en dictionnaire, avec une méthode to_dict() écrite dans son propre fichier
(person.py, spaceship.py ou fleet.py).
| Classe | to_dict() renvoie… |
|---|---|
Person | first_name, last_name, gender et age |
Operator | "type": "operator", les clés de Person, puis role et experience |
Mentalist | "type": "mentalist", les clés de Person, puis mana |
Spaceship | name, ship_type, condition et crew : la liste des to_dict() de chaque membre |
Fleet | name et spaceships : la liste des to_dict() de chaque vaisseau |
fleet.to_dict() a exactement le format de FLEET_DATA du TP2, avec une clé
experience en plus pour les opérateurs. Votre build_fleet() saura donc reconstruire la flotte.
Adaptez-le seulement pour lire l'expérience avec m.get("experience", 0), puisqu'elle est absente de FLEET_DATA.
fleet = build_fleet(FLEET_DATA)
fleet.spaceships[0].crew[0].act() # Hugo Crast pilote le vaisseau.
data = fleet.to_dict()
print(data["spaceships"][0]["crew"][0])
# {'type': 'operator', 'first_name': 'Hugo', 'last_name': 'Crast', 'gender': 'M',
# 'age': 37, 'role': 'pilote', 'experience': 1}
again = build_fleet(data)
print(again.to_dict() == data) # True
Indices
- Revoyez la notion de sauvegarde d'objets avec
to_dict(). - Dans
Operator, ne réécrivez pas les quatre clés dePerson. Partez desuper().to_dict()puis complétez le dictionnaire. - Dans
SpaceshipetFleet, chaque objet contenu sait déjà se convertir. Une boucle qui appelle leurto_dict()suffit. C'est la composition qui travaille pour vous.
2 Sauvegarder et charger
Dans un nouveau fichier storage.py, écrivez :
save_fleet(fleet, path), qui enregistrefleet.to_dict()dans un fichier JSON lisible : indenté, en UTF-8, avec les accents conservés.load_fleet(path), qui relit le fichier et renvoie un objetFleetgrâce àbuild_fleet().
load_fleet() ne doit jamais faire planter le programme. Quand le chargement est impossible, elle lève une
ValueError avec un message clair, que le menu affichera :
| Situation | Exception d'origine | Message conseillé |
|---|---|---|
| le fichier n'existe pas | FileNotFoundError | aucune sauvegarde « fleet.json » |
| le JSON est mal formé | json.JSONDecodeError | la sauvegarde « fleet.json » est corrompue |
| une clé attendue manque | KeyError | la sauvegarde « fleet.json » est incomplète |
| une valeur est interdite (rôle inconnu…) | ValueError de vos classes | le message de votre classe, tel quel |
save_fleet(fleet, "fleet.json")
print(load_fleet("fleet.json").to_dict() == fleet.to_dict()) # True
load_fleet("missing.json")
# ValueError: aucune sauvegarde « missing.json »
Indices
- Revoyez l'écriture et la lecture du JSON ainsi que le chargement robuste.
json.dump(data, file, indent=2, ensure_ascii=False)dans un fichier ouvert avecencoding="utf-8".- Dans un
except, vous pouvez lever une nouvelle exception :raise ValueError("…").
3 Le menu
- Ajoutez [7] Sauvegarder la flotte et [8] Charger une flotte.
- Le nom du fichier est demandé à l'utilisateur, avec
fleet.jsonpar défaut s'il appuie simplement sur Entrée. - Si le chargement échoue, le message d'erreur s'affiche et la flotte actuelle est conservée.
Votre choix : 7
Fichier de sauvegarde [fleet.json] :
Flotte Galactica sauvegardée dans « fleet.json ».
Votre choix : 8
Fichier à charger [fleet.json] : old.json
Chargement impossible : aucune sauvegarde « old.json »
4 Testez comme un attaquant
Un fichier de sauvegarde se modifie avec n'importe quel éditeur de texte. Mettez-vous à la place d'un utilisateur malveillant :
faites une copie de fleet.json, modifiez-la, puis essayez de la charger. Votre programme doit réagir ainsi :
| Modification dans la copie | Comportement attendu |
|---|---|
un rôle devient "root" | chargement refusé : rôle inconnu : root |
un mana devient 9999 | chargé, mais le mana est ramené à 100 par votre setter |
la clé "age" d'un membre est supprimée | chargement refusé : sauvegarde incomplète |
la dernière accolade } est supprimée | chargement refusé : sauvegarde corrompue |
- Pour le mana, votre programme corrige la valeur. Pour le rôle, il la refuse. Quelle stratégie est la plus sûre ? Dans quels cas ?
- Si l'attaquant change un âge de 37 en 38, aucune de vos validations ne le voit. La valeur est plausible. Valider les données ne détecte pas toutes les modifications.
- C'est pour éviter ces pièges que l'on utilise JSON et jamais
eval()sur des données non fiables.
5 Fichiers de test et README
- Commitez deux fichiers de test dans votre dépôt : une sauvegarde valide (
fleet.json) et une sauvegarde piégée (fleet-corrupted.json), que votre programme doit refuser proprement. - Complétez votre
README.md:- comment lancer le programme et à quoi sert chaque option du menu ;
- les bonus réalisés, TP par TP ;
- votre déclaration d'usage de l'IA : quels outils, pour quelles tâches.
+ Pour aller plus loin
Bonus 1 – Événements aléatoires
- Après chaque action du menu, un événement peut survenir au hasard : une attaque du Mulet endommage un vaisseau opérationnel. Ou bien des renforts rejoignent un vaisseau qui a encore de la place.
- Utilisez le module
random. Il convient parfaitement pour un jeu, jamais pour générer un secret !
Bonus 2 – Statistiques enrichies
- Complétez l'option [6] avec le nombre de vaisseaux opérationnels et endommagés.
Bonus 3 – Chargement automatique
- Au lancement, si
fleet.jsonexiste, la dernière sauvegarde est chargée automatiquement. Sinon, on part deFLEET_DATA.
Bonus 4 – Une méthode de classe from_dict()
- Remplacez
build_fleet()par une méthode de classefrom_dict()dans chaque classe, symétrique deto_dict(). Découvrez le décorateur@classmethod.
✓ Avant de rendre
Vérifiez chaque point. Ils correspondent à la grille d'évaluation du projet Fondation.
- Chaque classe a sa méthode
to_dict()et l'aller-retourbuild_fleet(fleet.to_dict())est parfait. - La sauvegarde est un JSON lisible : indenté, accents conservés.
- Après une sauvegarde, un redémarrage puis un chargement, la flotte est identique (membres, expérience, mana, état des vaisseaux).
- Les quatre attaques de l'étape 4 et le fichier absent sont gérés sans jamais faire planter le programme.
- Les options [0] à [8] du menu fonctionnent.
- Le dépôt contient
fleet.json,fleet-corrupted.jsonet unREADME.mdcomplet, avec la déclaration d'usage de l'IA. - Des commits réguliers ont été poussés, avec des messages clairs.
- Vous pouvez expliquer chaque ligne de votre code.
