POO : corrigé Pokémon, pas à pas et erreurs fréquentes
This commit is contained in:
+288
-53
@@ -1,11 +1,87 @@
|
||||
# Corrigé du TP : Pokémon Arena
|
||||
|
||||
Ce corrigé donne le code de chaque exercice, mais surtout **la façon d'y arriver**. Sous chaque question, vous trouverez des blocs à déplier :
|
||||
|
||||
- **Par où commencer** : comment réfléchir avant d'écrire la moindre ligne ;
|
||||
- **Ce qui se passe à l'exécution** : le code suivi pas à pas sur un exemple.
|
||||
|
||||
Si les mots *classe*, *objet* ou *méthode* ne sont pas clairs, relisez d'abord la [fiche de révision POO](../FICHE_REVISION.md).
|
||||
|
||||
---
|
||||
|
||||
## Avant de commencer : la méthode
|
||||
|
||||
### Quatre mots de vocabulaire
|
||||
|
||||
| Mot | Ce que c'est | Dans le TP |
|
||||
|:--|:--|:--|
|
||||
| **Classe** | Le moule, le plan de fabrication | `Pokemon` |
|
||||
| **Objet** (ou instance) | Une chose fabriquée avec le moule | `pikachu`, `salameche` |
|
||||
| **Attribut** | Une information rangée dans l'objet | `self.pv`, `self.nom` |
|
||||
| **Méthode** | Une fonction rangée dans la classe, qui agit sur l'objet | `attaquer`, `soigner` |
|
||||
|
||||
### Que veut dire `self` ?
|
||||
|
||||
`self` désigne **l'objet sur lequel on appelle la méthode**. Quand vous écrivez `pikachu.soigner(20)`, Python exécute la méthode `soigner` en remplaçant `self` par `pikachu`. Lire `self.pv`, c'est donc lire « les PV de **ce** Pokémon-là ».
|
||||
|
||||
Règle à retenir : **dans une méthode, chaque attribut s'écrit avec `self.` devant**, sans exception.
|
||||
|
||||
### Par où commencer une méthode ?
|
||||
|
||||
Devant une méthode vide, ne cherchez pas le code tout de suite. Répondez dans l'ordre à ces trois questions, en français, sur votre brouillon :
|
||||
|
||||
1. **De quoi ai-je besoin ?** Regardez les paramètres (entre parenthèses) et les attributs de l'objet (`self.quelque_chose`).
|
||||
2. **Que dois-je produire ?** Trois cas possibles, la docstring vous le dit :
|
||||
- **renvoyer** une valeur (`:return:` dans la docstring), donc il faut un `return` ;
|
||||
- **modifier** un attribut, donc il faut une ligne `self.attribut = ...` ;
|
||||
- **afficher** un message, donc il faut un `print`.
|
||||
3. **Quelles sont les étapes ?** Écrivez-les en français, une par ligne. Ensuite seulement, traduisez chaque étape en Python.
|
||||
|
||||
C'est exactement ce que font les blocs « Par où commencer » de ce corrigé.
|
||||
|
||||
### Les trois erreurs les plus fréquentes
|
||||
|
||||
!!! warning "Oublier `self`"
|
||||
Trois formes différentes, trois messages d'erreur différents :
|
||||
|
||||
| Ce que vous avez écrit | Ce qu'il faut écrire | Message de Python |
|
||||
|:--|:--|:--|
|
||||
| `nom = nom` dans `__init__` | `self.nom = nom` | `AttributeError: 'Pokemon' object has no attribute 'nom'` (l'attribut n'a jamais été créé) |
|
||||
| `return pv <= 0` | `return self.pv <= 0` | `NameError: name 'pv' is not defined` |
|
||||
| `def est_ko():` | `def est_ko(self):` | `TypeError: Pokemon.est_ko() takes 0 positional arguments but 1 was given` |
|
||||
|
||||
Le dernier message est déroutant : vous n'avez passé aucun argument, et Python dit en avoir reçu un. Cet argument, c'est l'objet lui-même, que Python place automatiquement dans `self`.
|
||||
|
||||
!!! warning "Les virgules dans les f-strings"
|
||||
Dans une f-string, **chaque paire d'accolades contient une seule valeur**. Tout ce qui est entre les accolades (espaces, parenthèses, tirets, virgules) est recopié tel quel.
|
||||
|
||||
| Ce que vous avez écrit | Ce qui s'affiche |
|
||||
|:--|:--|
|
||||
| `f"{self.nom, self.type_pokemon}"` | `('Pikachu', 'Electrik')` : la virgule dans l'accolade crée un tuple |
|
||||
| `return f"{self.nom}", f"({self.type_pokemon})"` | `TypeError: __str__ returned non-string (type tuple)` : la virgule après le `return` renvoie deux chaînes au lieu d'une |
|
||||
| `"{self.nom} ({self.type_pokemon})"` | `{self.nom} ({self.type_pokemon})` : sans le `f` devant, les accolades ne sont pas remplacées |
|
||||
| `f"{self.nom} ({self.type_pokemon})"` | `Pikachu (Electrik)` : correct |
|
||||
|
||||
Méthode sûre : **écrivez d'abord le texte attendu**, par exemple `Pikachu (Electrik) - Nv.25 - 80/80 PV`, puis remplacez chaque valeur variable par une accolade : `{self.nom} ({self.type_pokemon}) - Nv.{self.niveau} - {self.pv}/{self.pv_max} PV`. Ajoutez le `f` et les guillemets.
|
||||
|
||||
!!! warning "Afficher au lieu de renvoyer"
|
||||
`print` montre une valeur à l'écran, mais le programme ne peut plus s'en servir. `return` donne la valeur au programme. Quand la docstring contient `:return:`, il faut un `return`.
|
||||
|
||||
---
|
||||
|
||||
## Partie 1 : La classe Pokémon
|
||||
|
||||
### Exercice 1 : Créer la classe de base
|
||||
|
||||
??? tip "Par où commencer"
|
||||
1. **De quoi ai-je besoin ?** Les paramètres de `__init__` : `nom`, `type_pokemon`, `niveau`, `pv_max`, `attaque`.
|
||||
2. **Que dois-je produire ?** `__init__` ne renvoie rien : son rôle est de **ranger** chaque information dans l'objet, sous forme d'attribut.
|
||||
3. **Les étapes :**
|
||||
- pour chaque paramètre, créer l'attribut correspondant : `self.nom = nom`, etc. ;
|
||||
- créer l'attribut `pv`, qui n'est pas un paramètre : un Pokémon neuf a tous ses PV, donc `self.pv = pv_max`.
|
||||
|
||||
Pour `__repr__` : la docstring demande une chaîne. Écrivez le texte attendu de l'énoncé, `Pikachu (Electrik) - Nv.25 - 80/80 PV`, puis remplacez chaque valeur par l'attribut correspondant entre accolades.
|
||||
|
||||
```python
|
||||
class Pokemon:
|
||||
def __init__(self, nom, type_pokemon, niveau=1, pv_max=100, attaque=10):
|
||||
@@ -18,11 +94,11 @@ class Pokemon:
|
||||
:param pv_max: (int) Points de vie maximum
|
||||
:param attaque: (int) Puissance d'attaque
|
||||
"""
|
||||
self.nom = nom
|
||||
self.nom = nom # on range le paramètre nom dans l'objet
|
||||
self.type_pokemon = type_pokemon
|
||||
self.niveau = niveau
|
||||
self.pv_max = pv_max
|
||||
self.pv = pv_max
|
||||
self.pv = pv_max # pas de paramètre pv : on démarre avec tous ses PV
|
||||
self.attaque = attaque
|
||||
|
||||
def __repr__(self):
|
||||
@@ -30,16 +106,52 @@ class Pokemon:
|
||||
return f"{self.nom} ({self.type_pokemon}) - Nv.{self.niveau} - {self.pv}/{self.pv_max} PV"
|
||||
```
|
||||
|
||||
**Explications :**
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
```python
|
||||
pikachu = Pokemon("Pikachu", "Electrik", niveau=25, pv_max=80, attaque=15)
|
||||
```
|
||||
|
||||
- `pv` n'est pas un paramètre du constructeur : un Pokémon qui vient d'être créé est en pleine forme, donc on initialise `self.pv` avec `pv_max`.
|
||||
- `__repr__` doit **renvoyer** une chaîne (`return`), pas l'afficher avec `print`. C'est Python qui l'affiche quand on écrit `print(pikachu)`.
|
||||
1. Python fabrique un objet vide, et le place dans `self`.
|
||||
2. Il exécute `__init__` avec `nom = "Pikachu"`, `type_pokemon = "Electrik"`, `niveau = 25`, `pv_max = 80`, `attaque = 15`.
|
||||
3. Chaque ligne `self.... = ...` remplit l'objet. À la fin, l'objet contient : `nom` vaut `"Pikachu"`, `type_pokemon` vaut `"Electrik"`, `niveau` vaut 25, `pv_max` vaut 80, `pv` vaut 80, `attaque` vaut 15.
|
||||
4. L'objet est rangé dans la variable `pikachu`.
|
||||
|
||||
Ensuite, `print(pikachu)` appelle automatiquement `__repr__` : `{self.nom}` est remplacé par `Pikachu`, `{self.pv}` par `80`, etc. On obtient `Pikachu (Electrik) - Nv.25 - 80/80 PV`.
|
||||
|
||||
Remarquez que `__init__` a 6 paramètres (`self` compris) mais qu'on ne lui en donne que 5 : `self`, c'est Python qui le fournit.
|
||||
|
||||
---
|
||||
|
||||
### Exercice 2 : Méthodes de combat
|
||||
|
||||
Ces méthodes s'ajoutent dans la classe `Pokemon`.
|
||||
Ces méthodes s'ajoutent dans la classe `Pokemon`, avec la même indentation que `__repr__`.
|
||||
|
||||
??? tip "Par où commencer : `est_ko`"
|
||||
1. **De quoi ai-je besoin ?** Aucun paramètre à part `self`. L'attribut utile est `self.pv`.
|
||||
2. **Que dois-je produire ?** La docstring dit `:return: (bool)` : il faut **renvoyer** `True` ou `False`.
|
||||
3. **Les étapes :** regarder si les PV sont inférieurs ou égaux à 0, et renvoyer la réponse.
|
||||
|
||||
`self.pv <= 0` est déjà une expression qui vaut `True` ou `False` : on peut la renvoyer directement.
|
||||
|
||||
??? tip "Par où commencer : `subir_degats`"
|
||||
1. **De quoi ai-je besoin ?** Le paramètre `degats`, et l'attribut `self.pv`.
|
||||
2. **Que dois-je produire ?** Pas de `:return:` dans la docstring. On **modifie** un attribut : `self.pv`.
|
||||
3. **Les étapes :**
|
||||
- enlever les dégâts aux PV ;
|
||||
- si les PV sont passés sous 0, les remettre à 0.
|
||||
|
||||
??? tip "Par où commencer : `attaquer`"
|
||||
1. **De quoi ai-je besoin ?** Le paramètre `cible`, qui est **un objet `Pokemon`** (la docstring le précise : `(Pokemon)`). On a donc accès à `cible.nom`, `cible.pv`, et à ses méthodes. On a aussi `self.attaque` et `self.nom`.
|
||||
2. **Que dois-je produire ?** Un **affichage**, et une **modification** : les PV de la cible baissent.
|
||||
3. **Les étapes :**
|
||||
- afficher le message de l'énoncé : `Pikachu attaque Salamèche et inflige 15 dégâts !` ;
|
||||
- faire subir les dégâts à la cible. Pas besoin de réécrire le calcul : la méthode `subir_degats` existe déjà, on l'appelle **sur la cible**.
|
||||
|
||||
??? tip "Par où commencer : `soigner`"
|
||||
C'est le miroir de `subir_degats` :
|
||||
|
||||
- ajouter le soin aux PV ;
|
||||
- si les PV dépassent `pv_max`, les ramener à `pv_max`.
|
||||
|
||||
```python
|
||||
def est_ko(self):
|
||||
@@ -58,7 +170,7 @@ Ces méthodes s'ajoutent dans la classe `Pokemon`.
|
||||
:param degats: (int) Nombre de dégâts subis
|
||||
"""
|
||||
self.pv = self.pv - degats
|
||||
if self.pv < 0:
|
||||
if self.pv < 0: # on ne descend pas sous 0
|
||||
self.pv = 0
|
||||
|
||||
def attaquer(self, cible):
|
||||
@@ -69,7 +181,7 @@ Ces méthodes s'ajoutent dans la classe `Pokemon`.
|
||||
:param cible: (Pokemon) Pokémon ciblé par l'attaque
|
||||
"""
|
||||
print(f"{self.nom} attaque {cible.nom} et inflige {self.attaque} dégâts !")
|
||||
cible.subir_degats(self.attaque)
|
||||
cible.subir_degats(self.attaque) # c'est la cible qui perd des PV
|
||||
|
||||
def soigner(self, soin):
|
||||
"""
|
||||
@@ -79,20 +191,41 @@ Ces méthodes s'ajoutent dans la classe `Pokemon`.
|
||||
:param soin: (int) Nombre de PV récupérés
|
||||
"""
|
||||
self.pv = self.pv + soin
|
||||
if self.pv > self.pv_max:
|
||||
if self.pv > self.pv_max: # on ne dépasse pas le maximum
|
||||
self.pv = self.pv_max
|
||||
```
|
||||
|
||||
**Points importants :**
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
```python
|
||||
pikachu = Pokemon("Pikachu", "Electrik", niveau=25, pv_max=80, attaque=15)
|
||||
salameche = Pokemon("Salamèche", "Feu", niveau=20, pv_max=70, attaque=12)
|
||||
pikachu.attaquer(salameche)
|
||||
```
|
||||
|
||||
- `est_ko` renvoie directement le résultat de la comparaison : pas besoin d'écrire `if self.pv <= 0: return True else: return False`.
|
||||
- Dans `attaquer`, on ne modifie pas `cible.pv` directement : on **demande** à la cible de subir les dégâts avec `cible.subir_degats(...)`. C'est la cible qui sait comment gérer ses PV (et notamment le plancher à 0). C'est le principe des objets qui communiquent par leurs méthodes.
|
||||
- On peut aussi écrire `self.pv = max(0, self.pv - degats)` et `self.pv = min(self.pv_max, self.pv + soin)`, en une ligne.
|
||||
1. On appelle `attaquer` **sur `pikachu`** : dans la méthode, `self` vaut `pikachu`, et `cible` vaut `salameche`.
|
||||
2. Le `print` affiche `Pikachu attaque Salamèche et inflige 15 dégâts !` : `self.nom` donne `Pikachu`, `cible.nom` donne `Salamèche`, `self.attaque` donne 15.
|
||||
3. La ligne `cible.subir_degats(self.attaque)` appelle `subir_degats` **sur `salameche`** avec `degats = 15`. Dans cette nouvelle méthode, `self` vaut maintenant `salameche`.
|
||||
4. `self.pv = self.pv - degats` : les PV de Salamèche passent de 70 à 55. 55 n'est pas négatif, le `if` ne fait rien.
|
||||
5. `salameche.est_ko()` calcule `55 <= 0`, ce qui vaut `False`.
|
||||
|
||||
Le point clé : `self` ne désigne pas toujours le même Pokémon. Il désigne celui **devant le point** au moment de l'appel.
|
||||
|
||||
!!! note "Une version plus courte"
|
||||
`self.pv = max(0, self.pv - degats)` fait la même chose que les trois lignes de `subir_degats` : on garde le plus grand entre 0 et le résultat. De même, `self.pv = min(self.pv_max, self.pv + soin)` pour `soigner`. Les deux versions sont correctes.
|
||||
|
||||
---
|
||||
|
||||
### Exercice 3 : Système de faiblesses (Bonus)
|
||||
|
||||
??? tip "Par où commencer"
|
||||
1. **Comprendre le dictionnaire.** `FAIBLESSES["Eau"]` vaut `["Feu"]` : l'Eau est super efficace contre le Feu. La clé est le type de **l'attaquant**, la valeur est la liste des types **qu'il bat**.
|
||||
2. **Que change cette version ?** Les dégâts ne valent plus toujours `self.attaque` : ils peuvent doubler. On a donc besoin d'une variable `degats`.
|
||||
3. **Les étapes :**
|
||||
- mettre `self.attaque` dans une variable `degats` ;
|
||||
- si le type de l'attaquant est dans le dictionnaire, **et** si le type de la cible est dans sa liste, doubler `degats` et afficher `C'est super efficace !` ;
|
||||
- afficher le message d'attaque avec `degats` ;
|
||||
- faire subir `degats` à la cible.
|
||||
|
||||
Le dictionnaire est placé **en dehors** de la classe, en début de programme.
|
||||
|
||||
```python
|
||||
@@ -113,7 +246,7 @@ La nouvelle méthode `attaquer` remplace celle de l'exercice 2 :
|
||||
|
||||
:param cible: (Pokemon) Pokémon ciblé par l'attaque
|
||||
"""
|
||||
degats = self.attaque
|
||||
degats = self.attaque # variable locale : on ne touche pas à self.attaque
|
||||
|
||||
# Vérifier si le type de la cible est faible contre notre type
|
||||
if self.type_pokemon in FAIBLESSES:
|
||||
@@ -125,18 +258,22 @@ La nouvelle méthode `attaquer` remplace celle de l'exercice 2 :
|
||||
cible.subir_degats(degats)
|
||||
```
|
||||
|
||||
**Explications :**
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
```python
|
||||
tortank = Pokemon("Tortank", "Eau", 45, 140, 22)
|
||||
dracaufeu = Pokemon("Dracaufeu", "Feu", 50, 150, 25)
|
||||
tortank.attaquer(dracaufeu)
|
||||
```
|
||||
|
||||
- Le premier test `self.type_pokemon in FAIBLESSES` évite une erreur `KeyError` pour un type absent du dictionnaire (par exemple `"Roche"`).
|
||||
- On ne modifie pas `self.attaque` : on travaille sur une variable locale `degats`, sinon l'attaque du Pokémon doublerait définitivement à chaque coup super efficace.
|
||||
1. `self` vaut `tortank`, `cible` vaut `dracaufeu`. `degats` vaut 22.
|
||||
2. `self.type_pokemon in FAIBLESSES` : `"Eau"` est bien une clé du dictionnaire.
|
||||
3. `FAIBLESSES["Eau"]` vaut `["Feu"]`, et `cible.type_pokemon` vaut `"Feu"` : il est dans la liste. `degats` passe à 44, et Python affiche `C'est super efficace !`.
|
||||
4. Puis `Tortank attaque Dracaufeu et inflige 44 dégâts !`, et Dracaufeu passe de 150 à 106 PV.
|
||||
|
||||
```python
|
||||
>>> tortank = Pokemon("Tortank", "Eau", 45, 140, 22)
|
||||
>>> dracaufeu = Pokemon("Dracaufeu", "Feu", 50, 150, 25)
|
||||
>>> tortank.attaquer(dracaufeu)
|
||||
C'est super efficace !
|
||||
Tortank attaque Dracaufeu et inflige 44 dégâts !
|
||||
```
|
||||
Si un Onix (type `"Roche"`) attaquait, le premier `if` serait faux, car `"Roche"` n'est pas une clé : les dégâts resteraient normaux. Sans ce premier test, `FAIBLESSES["Roche"]` provoquerait une erreur `KeyError`.
|
||||
|
||||
!!! warning "Erreur fréquente"
|
||||
Écrire `self.attaque = self.attaque * 2` au lieu de passer par `degats`. L'attaque du Pokémon serait doublée **pour toujours**, et redoublée à chaque coup super efficace.
|
||||
|
||||
---
|
||||
|
||||
@@ -144,6 +281,36 @@ Tortank attaque Dracaufeu et inflige 44 dégâts !
|
||||
|
||||
### Exercice 4 : Créer la classe Dresseur
|
||||
|
||||
??? tip "Par où commencer : `__init__`"
|
||||
Deux attributs : `nom` est donné en paramètre, mais `equipe` ne l'est pas. Un dresseur commence sans Pokémon : `self.equipe = []`.
|
||||
|
||||
??? tip "Par où commencer : `ajouter_pokemon`"
|
||||
1. **De quoi ai-je besoin ?** Le paramètre `pokemon`, et l'attribut `self.equipe`.
|
||||
2. **Que dois-je produire ?** **Modifier** l'équipe si c'est possible, et **renvoyer** `True` ou `False` (`:return:`).
|
||||
3. **Les étapes :**
|
||||
- si l'équipe compte moins de 6 Pokémon, ajouter le Pokémon à la liste et renvoyer `True` ;
|
||||
- sinon, renvoyer `False`.
|
||||
|
||||
??? tip "Par où commencer : `pokemon_disponibles`, `premier_pokemon_dispo`, `tous_ko`"
|
||||
Ces trois méthodes parcourent l'équipe avec une boucle `for pokemon in self.equipe:`. À chaque tour, `pokemon` est un objet `Pokemon` : on peut donc lui demander `pokemon.est_ko()`.
|
||||
|
||||
- `pokemon_disponibles` : partir d'une liste vide, y ajouter chaque Pokémon qui n'est pas KO, renvoyer la liste.
|
||||
- `premier_pokemon_dispo` : dès qu'on trouve un Pokémon qui n'est pas KO, le renvoyer. Si la boucle se termine sans rien trouver, renvoyer `None`.
|
||||
- `tous_ko` : tous les Pokémon sont KO quand la liste des disponibles est vide. On réutilise la méthode précédente.
|
||||
|
||||
??? tip "Par où commencer : `__repr__`"
|
||||
Le texte attendu tient sur plusieurs lignes :
|
||||
|
||||
```
|
||||
Dresseur Sacha - 2 Pokémon
|
||||
- Pikachu (Electrik) - Nv.25 - 80/80 PV
|
||||
- Dracaufeu (Feu) - Nv.50 - 150/150 PV
|
||||
```
|
||||
|
||||
- Construire la première ligne dans une variable `texte`.
|
||||
- Pour chaque Pokémon de l'équipe, ajouter à `texte` un retour à la ligne (`\n`), deux espaces, un tiret, et le Pokémon.
|
||||
- Renvoyer `texte`.
|
||||
|
||||
```python
|
||||
class Dresseur:
|
||||
def __init__(self, nom):
|
||||
@@ -153,7 +320,7 @@ class Dresseur:
|
||||
:param nom: (str) Nom du dresseur
|
||||
"""
|
||||
self.nom = nom
|
||||
self.equipe = []
|
||||
self.equipe = [] # pas de paramètre : chaque dresseur démarre avec une équipe vide
|
||||
|
||||
def ajouter_pokemon(self, pokemon):
|
||||
"""
|
||||
@@ -175,7 +342,7 @@ class Dresseur:
|
||||
"""
|
||||
disponibles = []
|
||||
for pokemon in self.equipe:
|
||||
if not pokemon.est_ko():
|
||||
if not pokemon.est_ko(): # on demande au Pokémon s'il est KO
|
||||
disponibles.append(pokemon)
|
||||
return disponibles
|
||||
|
||||
@@ -187,8 +354,8 @@ class Dresseur:
|
||||
"""
|
||||
for pokemon in self.equipe:
|
||||
if not pokemon.est_ko():
|
||||
return pokemon
|
||||
return None
|
||||
return pokemon # le return arrête la boucle immédiatement
|
||||
return None # on n'arrive ici que si aucun n'est disponible
|
||||
|
||||
def tous_ko(self):
|
||||
"""
|
||||
@@ -202,16 +369,26 @@ class Dresseur:
|
||||
"""Représentation du dresseur."""
|
||||
texte = f"Dresseur {self.nom} - {len(self.equipe)} Pokémon"
|
||||
for pokemon in self.equipe:
|
||||
texte = texte + f"\n - {pokemon}"
|
||||
texte = texte + f"\n - {pokemon}" # {pokemon} utilise le __repr__ de Pokemon
|
||||
return texte
|
||||
```
|
||||
|
||||
**Points importants :**
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
```python
|
||||
sacha = Dresseur("Sacha")
|
||||
sacha.ajouter_pokemon(Pokemon("Pikachu", "Electrik", 25, 80, 15))
|
||||
```
|
||||
|
||||
- `equipe` n'est **pas** un paramètre du constructeur : chaque dresseur commence avec sa propre liste vide, créée dans `__init__`.
|
||||
- L'attribut `equipe` est une liste d'**objets** `Pokemon`. Le dresseur peut donc appeler leurs méthodes : `pokemon.est_ko()`.
|
||||
- `tous_ko` réutilise `pokemon_disponibles` : si la liste des Pokémon disponibles est vide, tous sont KO. Une boucle qui renvoie `False` dès qu'un Pokémon est debout fonctionne aussi.
|
||||
- Dans `__repr__`, `{pokemon}` appelle automatiquement le `__repr__` de la classe `Pokemon`.
|
||||
1. `sacha` est créé avec `nom` qui vaut `"Sacha"` et `equipe` qui vaut `[]`.
|
||||
2. `ajouter_pokemon` : `len(self.equipe)` vaut 0, qui est inférieur à 6. Le Pokémon est ajouté, la méthode renvoie `True`.
|
||||
|
||||
Si Pikachu tombe KO et qu'on appelle `sacha.premier_pokemon_dispo()` :
|
||||
|
||||
1. premier tour de boucle, `pokemon` vaut Pikachu, `pokemon.est_ko()` vaut `True`, donc `not True` vaut `False` : on passe au suivant ;
|
||||
2. deuxième tour, `pokemon` vaut Dracaufeu, qui n'est pas KO : la méthode renvoie Dracaufeu et s'arrête.
|
||||
|
||||
!!! warning "Erreur fréquente"
|
||||
Mettre le `return None` **dans** la boucle, au même niveau que le `if`. La méthode s'arrêterait alors dès le premier Pokémon KO, sans regarder les suivants. Le `return None` se place **après** la boucle, aligné avec le `for`.
|
||||
|
||||
---
|
||||
|
||||
@@ -219,6 +396,22 @@ class Dresseur:
|
||||
|
||||
### Exercice 5 : Simulateur de combat
|
||||
|
||||
??? tip "Par où commencer"
|
||||
Une bonne partie du code est déjà donnée. Avant de compléter, **jouez un tour à la main** sur votre brouillon : Pikachu (80 PV, attaque 15) contre Onix (120 PV, attaque 18).
|
||||
|
||||
- Pikachu attaque Onix. Onix est-il KO ? Non : il riposte.
|
||||
- Onix attaque Pikachu. Pikachu est-il KO ? Non : on passe au tour suivant.
|
||||
|
||||
Traduisez ensuite chaque commentaire `# À compléter` :
|
||||
|
||||
1. **Le Pokémon 1 attaque** : une seule ligne, `poke1.attaquer(poke2)`.
|
||||
2. **Vérifier si le Pokémon 2 est KO** : `if poke2.est_ko():`. Si oui, on l'annonce, et on annonce le remplaçant s'il y en a un.
|
||||
3. **Le Pokémon 2 attaque (s'il n'est pas KO)** : c'est le `else` du test précédent. Un Pokémon KO ne riposte pas.
|
||||
4. **Vérifier si le Pokémon 1 est KO** : même chose que l'étape 2, dans l'autre sens.
|
||||
5. **Afficher le vainqueur** : après la boucle, l'un des deux dresseurs n'a plus de Pokémon. Si c'est le dresseur 1, le dresseur 2 a gagné, et inversement.
|
||||
|
||||
Pour `afficher_etat`, on récupère le Pokémon actif de chaque dresseur avec `premier_pokemon_dispo()`, puis on affiche son nom et ses PV.
|
||||
|
||||
```python
|
||||
class Combat:
|
||||
def __init__(self, dresseur1, dresseur2):
|
||||
@@ -290,12 +483,26 @@ class Combat:
|
||||
print(f"*** {self.dresseur1.nom.upper()} REMPORTE LE COMBAT ! ***")
|
||||
```
|
||||
|
||||
**Points importants :**
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
Sacha (Pikachu, Dracaufeu, Tortank) contre Pierre (Onix, Racaillou).
|
||||
|
||||
- Le piège principal : un Pokémon KO **ne doit pas riposter**. C'est le rôle du `else` : le Pokémon 2 n'attaque que s'il est encore debout.
|
||||
- `afficher_etat` peut appeler `premier_pokemon_dispo()` sans vérifier `None` : elle n'est appelée qu'au début d'un tour, donc quand les deux dresseurs ont encore un Pokémon disponible (c'est la condition du `while`).
|
||||
- Après un KO, on n'a rien à faire pour « changer » de Pokémon : au tour suivant, `premier_pokemon_dispo()` renverra automatiquement le suivant de l'équipe.
|
||||
- La classe `Combat` ne manipule jamais les PV elle-même : elle orchestre des objets `Dresseur`, qui contiennent des objets `Pokemon`, qui s'attaquent entre eux.
|
||||
**Tour 1.** Les Pokémon actifs sont Pikachu (80 PV) et Onix (120 PV).
|
||||
|
||||
- Pikachu attaque : Onix passe à 105 PV. Il n'est pas KO, donc on entre dans le `else`.
|
||||
- Onix riposte : Pikachu passe à 62 PV. Il n'est pas KO.
|
||||
- Fin du tour. La condition du `while` est toujours vraie : chaque dresseur a encore des Pokémon debout.
|
||||
|
||||
**Quelques tours plus tard**, Pikachu tombe à 0 PV pendant la riposte d'Onix. On affiche `Pikachu est KO !`, puis `premier_pokemon_dispo()` renvoie Dracaufeu : `Sacha envoie Dracaufeu !`.
|
||||
|
||||
**Au tour suivant**, on n'a rien eu à faire pour changer de Pokémon : `premier_pokemon_dispo()` saute Pikachu, qui est KO, et renvoie Dracaufeu.
|
||||
|
||||
**À la fin**, Racaillou tombe KO. `pierre.tous_ko()` vaut `True`, la condition du `while` devient fausse et la boucle s'arrête. Le `if` final affiche que Sacha remporte le combat.
|
||||
|
||||
!!! warning "Erreur fréquente"
|
||||
Oublier le `else` et faire riposter le Pokémon 2 même s'il vient d'être mis KO. On voit alors un Pokémon à 0 PV attaquer, ce qui est le signe de cette erreur.
|
||||
|
||||
!!! note "Pourquoi `afficher_etat` ne vérifie pas `None`"
|
||||
`afficher_etat` est appelée au début d'un tour, donc seulement quand la condition du `while` est vraie : chaque dresseur a forcément un Pokémon disponible. `premier_pokemon_dispo()` ne peut pas renvoyer `None` à ce moment-là.
|
||||
|
||||
---
|
||||
|
||||
@@ -303,6 +510,16 @@ class Combat:
|
||||
|
||||
### Exercice 6 : Centre Pokémon
|
||||
|
||||
??? tip "Par où commencer"
|
||||
1. **De quoi ai-je besoin ?** Le paramètre `dresseur`. Son équipe est accessible par `dresseur.equipe`.
|
||||
2. **Que dois-je produire ?** **Modifier** les PV de chaque Pokémon de l'équipe, et afficher un message d'accueil.
|
||||
3. **Les étapes :**
|
||||
- afficher un message de bienvenue ;
|
||||
- pour chaque Pokémon de l'équipe, remettre ses PV au maximum ;
|
||||
- afficher un message de fin.
|
||||
|
||||
Pour remettre les PV au maximum, on peut réutiliser `soigner` : soigner de `pv_max` PV ramène forcément à `pv_max`, puisque `soigner` plafonne.
|
||||
|
||||
```python
|
||||
class CentrePokemon:
|
||||
def __init__(self, ville):
|
||||
@@ -323,12 +540,25 @@ class CentrePokemon:
|
||||
print("Vos Pokémon sont en pleine forme !")
|
||||
```
|
||||
|
||||
**Explication :** on réutilise la méthode `soigner` de l'exercice 2. Soigner de `pv_max` PV ramène forcément le Pokémon à `pv_max`, puisque `soigner` plafonne les PV. On aurait aussi pu écrire `pokemon.pv = pokemon.pv_max`, mais passer par la méthode est plus propre : c'est le Pokémon qui gère ses PV.
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
Après le combat, Dracaufeu a 96 PV sur 150. `centre.soigner_equipe(sacha)` parcourt l'équipe de Sacha. Arrivé à Dracaufeu, il appelle `soigner(150)` : 96 + 150 = 246, ce qui dépasse 150, donc les PV sont ramenés à 150.
|
||||
|
||||
Remarquez la chaîne d'objets : le **centre** reçoit un **dresseur**, qui contient une **équipe**, qui contient des **Pokémon**, sur lesquels on appelle `soigner`.
|
||||
|
||||
On aurait aussi pu écrire `pokemon.pv = pokemon.pv_max`. Passer par la méthode `soigner` est plus propre : c'est le Pokémon qui gère ses propres PV.
|
||||
|
||||
---
|
||||
|
||||
### Exercice 7 : Capacités spéciales (Pour aller plus loin)
|
||||
|
||||
??? tip "Par où commencer"
|
||||
L'énoncé est plus ouvert : c'est à vous de choisir les méthodes. Posez-vous la question « qu'est-ce qu'un Pokémon doit pouvoir **faire** avec ses capacités ? ».
|
||||
|
||||
- **Apprendre** une capacité : l'ajouter à une liste, 4 au maximum. C'est le même principe que `ajouter_pokemon` du dresseur.
|
||||
- **Attaquer avec** une capacité : choisir laquelle, vérifier qu'il reste des PP, calculer les dégâts, les infliger.
|
||||
|
||||
Pour stocker les capacités, il faut un nouvel attribut dans `Pokemon` : une liste vide, créée dans `__init__`.
|
||||
|
||||
On complète la classe `Capacite` avec une méthode qui consomme un PP :
|
||||
|
||||
```python
|
||||
@@ -383,8 +613,8 @@ Côté `Pokemon`, on ajoute la ligne `self.capacites = []` à la fin de `__init_
|
||||
:param numero: (int) Indice de la capacité dans self.capacites
|
||||
:param cible: (Pokemon) Pokémon ciblé par l'attaque
|
||||
"""
|
||||
capacite = self.capacites[numero]
|
||||
if capacite.utiliser():
|
||||
capacite = self.capacites[numero] # un objet Capacite
|
||||
if capacite.utiliser(): # consomme un PP, renvoie False s'il n'en reste plus
|
||||
degats = self.attaque + capacite.puissance
|
||||
print(f"{self.nom} utilise {capacite.nom} et inflige {degats} dégâts !")
|
||||
cible.subir_degats(degats)
|
||||
@@ -392,18 +622,23 @@ Côté `Pokemon`, on ajoute la ligne `self.capacites = []` à la fin de `__init_
|
||||
print(f"{capacite.nom} n'a plus de PP !")
|
||||
```
|
||||
|
||||
```python
|
||||
>>> pikachu = Pokemon("Pikachu", "Electrik", 25, 80, 15)
|
||||
>>> onix = Pokemon("Onix", "Roche", 30, 120, 18)
|
||||
>>> pikachu.apprendre(Capacite("Tonnerre", 30, "Electrik", 2))
|
||||
True
|
||||
>>> pikachu.attaquer_avec(0, onix)
|
||||
Pikachu utilise Tonnerre et inflige 45 dégâts !
|
||||
>>> pikachu.capacites
|
||||
[Tonnerre (Electrik) - 30 DMG - 1/2 PP]
|
||||
```
|
||||
??? example "Ce qui se passe à l'exécution"
|
||||
```python
|
||||
pikachu = Pokemon("Pikachu", "Electrik", 25, 80, 15)
|
||||
onix = Pokemon("Onix", "Roche", 30, 120, 18)
|
||||
pikachu.apprendre(Capacite("Tonnerre", 30, "Electrik", 2))
|
||||
pikachu.attaquer_avec(0, onix)
|
||||
```
|
||||
|
||||
**Explication :** c'est une **composition** d'objets : un `Pokemon` possède une liste d'objets `Capacite`, comme un `Dresseur` possède une liste d'objets `Pokemon`. La formule de dégâts (`attaque + puissance`) est un choix parmi d'autres : toute règle cohérente et justifiée est acceptée.
|
||||
1. `apprendre` : Pikachu n'a aucune capacité, il apprend Tonnerre, la méthode renvoie `True`.
|
||||
2. `attaquer_avec(0, onix)` : `self.capacites[0]` est Tonnerre.
|
||||
3. `capacite.utiliser()` : il reste 2 PP, on passe à 1, la méthode renvoie `True`.
|
||||
4. Les dégâts valent 15 + 30 = 45. Python affiche `Pikachu utilise Tonnerre et inflige 45 dégâts !`, et Onix passe de 120 à 75 PV.
|
||||
5. Au troisième appel, `utiliser()` renverra `False` et Python affichera `Tonnerre n'a plus de PP !`.
|
||||
|
||||
Remarquez le `if capacite.utiliser():` : la méthode **fait** quelque chose (elle consomme un PP) **et** renvoie une réponse qu'on teste directement.
|
||||
|
||||
C'est une **composition** d'objets : un `Pokemon` possède une liste d'objets `Capacite`, comme un `Dresseur` possède une liste d'objets `Pokemon`. La formule de dégâts (`attaque + puissance`) est un choix parmi d'autres : toute règle cohérente et justifiée est acceptée.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user