Aller au contenu

Exemple pas à pas

Bilbo et Gollum⚓︎

Création de la classe

Nous allons commencer par écrire une classe Personnage (qui sera dans un premier temps une coquille vide) et, à partir de cette classe, créer 2 instances : bilbo et gollum.

Tester :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Pour l'instant, notre classe ne sert à rien et nos instances d'objet ne peuvent rien faire. Comme il n'est pas possible de créer une classe totalement vide, nous avons utilisé l'instruction pass qui ne fait rien. Ensuite nous avons créé 2 instances de la classe Personnage : bilbo et gollum.

Le constructeur : la méthode __init__

Les attributs de l'objet doivent être définis dans la classe, à l'aide d'une méthode d'initialisation des attributs.

Une méthode particulière, nommée le constructeur, permet de définir les attributs dès l'instanciation d'un objet.

Cette méthode est définie dans le code source par la ligne : def __init__ (self):

Rappel : La méthode __init__

La méthode __init__ est automatiquement exécutée au moment de la création d'une instance.
Le mot self est obligatoirement le premier argument d'une méthode.
Le mot self représente l'instance. Quand vous définissez une instance de classe (bilbo ou gollum) le nom de votre instance va remplacer le mot self.

Dans le code source, nous allons avoir :

class Personnage_1:
def __init__ (self):
    self.vie = 20
Ensuite lors de la création de l'instance gollum, Python va créer l'attribut vie de la variable gollum.
Cet attribut aura pour valeur de départ la valeur donnée à self.vie dans la méthode __init__

Il se passera exactement la même chose au moment de la création de l'instance bilbo, on aura automatiquement la création de l'attribut vie de la variable bilbo.

Exécutez ce code, puis dans la console, faites afficher les valeurs des attributs vie de gollum et de bilbo

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution

En console saisir et exécuter ligne par ligne:

>>> gollum.vie
>>> bilbo.vie
Passer un argument

Imaginons que nos deux personnages n'aient pas au départ les mêmes points de vie ! Pour l'instant, impossible d'introduire cette contrainte (self.vie = 20)

Une méthode, comme une fonction, peut prendre des paramètres.

Le passage de paramètres se fait au moment de la création de l'instance. Modifiez le code pour que le nombre de vies soit un paramètre dont la valeur sera fixée lors de l'instanciation.

Votre code doit :

  • Permettre de passer un argument nb_vies.
  • Créer une instance de Personnage nommée bilbo initialisée avec 20 vies.
  • Créer une autre instance, gollum, avec 15 vies.
  • Afficher les valeurs de l'attribut vie pour bilbo
  • Afficher les valeurs de l'attribut vie pour gollum

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
class Personnage_2:
    def __init__(self, nb_vies):
        self.vie = nb_vies

bilbo = Personnage_2(20)
gollum = Personnage_2(15)
print(bilbo.vie)
print(gollum.vie)
Passer deux arguments

Compléter le code pour :

  • Permettre de passer deux paramètres au constructeur.
  • Utiliser les paramètres nb_vies et age pour initialiser les attibuts vie et age.
  • Créer une instance, gollum, avec 20 vies et 127 ans.
  • Afficher ceci (en utilisant les attributs) : gollum a 127 ans et 20 vies

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
class Personnage_3:
    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

gollum = Personnage_3(20, 127)

print("gollum a ", gollum.age, " ans et ", gollum.vie, " vies")
Ajouter des méthodes

Votre code doit :

  • Ajouter une méthode perd_une_vie qui modifie l'attibut vie (retire une vie)
  • Ajouter une méthode donne_etat qui renvoie la valeur de l'attibut vie
  • Créer un seul personnage, gollum, avec 20 vies et 127 ans.
  • Créer une variable etat_1 égale à son nombre de vie, puis l'afficher
  • Modifier le nombre de vies avec la méthode perd_une_vie
  • Créer une variable etat_2 égale à son nouveau nombre de vie, puis l'afficher.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
class Personnage_4:
    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

    def donne_etat(self):
        return self.vie

    def perd_une_vie(self):
        self.vie = self.vie - 1

gollum = Personnage_4(20, 127)
etat_1 = gollum.donne_etat()
print(etat_1)
gollum.perd_une_vie()
etat_2 = gollum.donne_etat()
print(etat_2)
Mauvaise pratique

Vous avez sans doute remarqué que lors de "l'utilisation" des instances bilbo et gollum, nous avons uniquement utilisé des méthodes et nous n'avons plus directement utilisé des attributs (plus de gollum.vie). Il est important de savoir qu'en dehors de la classe l'utilisation des attributs est une mauvaise pratique en programmation orientée objet : les attributs doivent rester "à l'intérieur" de la classe, l'utilisateur de la classe ne doit pas les utiliser directement. Il peut les manipuler, mais uniquement par l'intermédiaire d'une méthode (la méthode perd_une_vie permet de manipuler l'attribut vie)

Boire une potion

Nos personnages peuvent boire une potion qui leur ajoute un point de vie.

Vous devez :

  • Modifier la classe Personnage en ajoutant une méthode boire_potion qui ajoute un point de vie.
  • créer en dehors de la classe une fonction simul(n, a) qui crée un personnage avec n vies, un âge a, lui fait boire une potion, et renvoie le nombre de vies.

Vous pourrez tester par exemple simul(10, 127) dans la console.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
class Personnage_5:

    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

    def donne_etat(self):
        return self.vie

    def perd_une_vie(self):
        self.vie = self.vie - 1

    def boire_potion(self):
        self.vie = self.vie + 1

def simul(n, a):
    mon_personnage = Personnage_5(n, a)
    mon_personnage.boire_potion()
    return mon_personnage.donne_etat()
Perdre plusieurs vies

Selon le type d'attaque subie, le personnage peut perdre plus ou moins de points de vie. Pour tenir compte de cet élément, remplacer la méthode perd_une_vie par la méthode perd_vies qui prend le nombre de vies perdues vies_perdues en paramètre.

Vous devez :

  • écrire la méthode perd_vies
  • instancier gollum avec 15 vies et 100 ans
  • lui faire perdre 2 vies avec la méthode perd_vies
  • Faire afficher le nombre de vies avec la méthode donne_etat

Contrainte

vous ne pouvez pas utiliser la notation pointée gollum.vie

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
class Personnage_6:
    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

    def donne_etat(self):
        return self.vie

    def perd_vies(self, vies_perdues):
        self.vie = self.vie - vies_perdues

    def boire_potion(self):
        self.vie = self.vie + 1

gollum = Personnage_6(15, 100)
gollum.perd_vies(2)
print(gollum.donne_etat())
Un peu de hasard ne nuira pas...

Modifier le code précédent, de façon que perd_vies(self, vies_perdues) retire entre 1 et vies_perdues vies au personnage attaqué.

Vous devez :

  • Modifier la méthode perd_vies
  • Compléter le code de façon à créer un personnage ayant 100 vies, 130 ans, puis qui subit 3 attaques consécutives, lui infligeant chacune 1 à 10 vies perdues... Il faudra ensuite afficher son nombre de vies.

Vous pouvez aussi imaginer vos propres scénarios, en rajoutant le fait de boire de la potion ...

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
from random import randint

class Personnage_7:
    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

    def donne_etat(self):
        return self.vie

    def perd_vies(self, vies_perdues):
        perdues = randint(1, vies_perdues)
        self.vie = self.vie - perdues

    def boire_potion(self):
        self.vie = self.vie + 1

monstre = Personnage_7(100, 130)
for i in range(3):
    monstre.perd_vies(10)
print(monstre.donne_etat())
Plus mort que mort ?

La méthode perd_vies doit être écrite de façon à ne jamais renvoyer un nombre de vies négatif. Si le nombre de vies est négatif, il doit être mis à 0.

Vous devez :

  • Modifier la méthode perd_vies
  • Completer le code de façon à créer un personnage ayant 100 vies puis qui subit des attaques consécutives, lui infligeant chacune 1 à 10 vies perdues, jusqu'à ce qu'il n'ait plus aucune vie. Le code affichera le nombre d'attaques subies.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
from random import randint

class Personnage_8:
    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

    def donne_etat(self):
        return self.vie

    def perd_vies(self, vies_perdues):
        perdues = randint(1, vies_perdues)
        self.vie = self.vie - perdues
        if self.vie < 0:
            self.vie = 0

    def boire_potion(self):
        self.vie = self.vie + 1

monstre = Personnage_8(100, 130)
cpt = 0
while monstre.donne_etat() > 0:
    monstre.perd_vies(10)
    cpt = cpt + 1
print(monstre.donne_etat())
print("Il y a eu ", cpt, " attaques")
Un combat

Organisez un combat virtuel entre nos 2 personnages Bilbo et Gollum:

  • Le code décide aléatoirement quel personnage va attaquer en premier

  • Tant que Bilbo et Gollum sont en vie:

    • Un personnage attaque (la victime perd de 1 à 10 vies)
    • On change de personnage

Tant qu'aucun des deux n'est mort, le combat continue

  • Quand un des deux est mort, on affiche le nombre d'attaques qui ont été menées durant le combat, et l'état de chaque personnage.
Aide pour alterner les combattants

On peut utiliser une variable attaquant = randint(0, 1) Pour changer d'attaquant, on peut écrire attaquant = 1 - attaquant

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
from random import randint

class Personnage_9:
    def __init__(self, nbre_vies, age):
        self.vie = nbre_vies
        self.age = age

    def donne_etat(self):
        return self.vie

    def perd_vies(self, vies_perdues):
        perdues = randint(1, vies_perdues)
        self.vie = self.vie - perdues
        if self.vie < 0:
            self.vie = 0

    def boire_potion(self):
        self.vie = self.vie + 1

gollum = Personnage_9(100, 127)
bilbo = Personnage_9(100, 127)
nb_attaques = 0
attaquant = randint(0, 1)
while gollum.donne_etat() > 0 and bilbo.donne_etat() > 0 :
    if attaquant == 0 :
        gollum.perd_vies(10)
    else :
        bilbo.perd_vies(10)
    nb_attaques = nb_attaques + 1
    attaquant = 1 - attaquant
print(nb_attaques)
print("Gollum a ", gollum.donne_etat(), "vies")
print("bilbo a ", bilbo.donne_etat(), "vies")
Facultatif : Faites vos propres règles et implémentez les

Il n'y aura pas de correction toute faite à cette question ... 😊

Modifiez (à votre convenance) les règles et améliorez le programme en modifiant des méthodes ou en implémentant vos propres méthodes.

Quelques propositions si vous ne voyez pas trop quoi ajouter :

  1. Dans l’initialisation, on ne peut donner de nom au personnage !

    • créez un attribut nom qu’on doit donner en premier paramètre : on crée une instance de Personnage comme ceci :

      gollum = Personnage("Gollum", 100, 127)
      
    • créez une méthode donne_nom qui renvoie le nom du personnage.

  2. Modifiez la fonction game pour qu’elle tienne compte du nom du personnage.
    On doit pouvoir créer d’autres personnages et les messages doivent tenir compte des noms de ceux-ci.

    >>> frodon = Personnage("Frodon", 100, 70)
    >>> araignee = Personnage("Araignée", 50, 4)
    >>> game()
    Frodon est vainqueur, il lui reste encore 3 points
    alors que Araignée est mort
    

    Remarquez bien que la signature de la fonction game est différente !

    On doit créer les personnages AVANT de l’appeler. Il faut changer plusieurs éléments.

  3. Améliorez encore la fonction game pour qu’elle affiche un journal détaillé du combat :

    >>> aragorn =Personnage("Aragorn", 100, 35)
    >>> orc = Personnage("Orc", 100, 25)
    >>> game(aragorn, orc)
    Aragorn perd un point de vie
    Orc perd deux points de vie
    ...
    Aragorn perd deux points de vie
    Orc perd deux points de vie
    Orc est vainqueur, il lui reste encore 3 points alors que Aragorn est mort
    
  4. Revenons à Personnage
    On dispose maintenant des méthodes suivantes :

    class Personnage:
    |
    |  donne_etat
    |     --> int
    |     renvoie le nombre de pts de vie
    |
    |  perd_vies
    |     enleve un ou deux points de vie
    |
    |  donne_nom
    |     --> str
    |     renvoie le nom du personnage
    

    On veut créer un attribut chance lors de l’instanciation du personnage. C’est un entier entre 0 et 4.

    L’effet de la chance est le suivant :

    • dans la méthode perd_vie, on tire toujours un nombre aléatoire entre 0 et 1.

      • Si ce nombre multiplié par 10 dépasse la chance du personnage, il perd un point de vie.

      • Sinon il ne perd pas de vie et on affiche “Aragorn a de la chance !”

      Par exemple, Aragorn a 2 de chance.

      • dans perd_vies, on tire 0.3,
        10 * 0.3 = 3 et 3 > 2 : il perd un point de vie.

      • dans perd_vies, on tire 0.12345
        10 * 0.12345 = 1.2345 et 1.2345 < 2 : il ne perd pas de vie.

    • Il faut aussi changer la méthode init pour pouvoir créer nos personnages ainsi :

      aragorn = Personnage("Aragorn", 100, 35, 2)
      

    Implémentez la chance et faites quelques essais.

    Attention, si vous donnez une chance trop élevée, le personnage ne perdra jamais de vie et la boucle de la fonction game sera infinie !

  5. Maintenant qu’on peut donner un attribut chance, il faut protéger le programme.

    Une valeur de chance trop élevée peut conduire à un programme qui ne termine jamais, il suffit de donner une chance de 10 pour qu’un personnage soit invicible !

    Nous allons créer une méthode interne __limiter_chance qui empêche la chance d’être supérieure à 4.

    Si le paramètre chance est inférieure ou égale à 4, il est inchangé, S’il dépasse 4, il est ramené à 4.

    Cette méthode interne ne sera pas appelée par les éléments extérieurs au programme, seulement par le programme lui même !

    On utilise cette méthode interne dans __init__, il faut penser à l’appeler.

Voici aussi d'autre possibilités :

  • Quand une personne attaque, elle peut en réalité décider de ne pas le faire et à la place, boire une potion qui lui rend par exemple 1-8 vies. Le code devra activer cette action si perso.vie est trop bas (à vous de préciser!)
  • Un personnage peut se mettre en fuite, dans ce cas par exemple, il passe son tour, l'autre peut porter une dernière attaque, mais le combat s'arrête ensuite
  • Les possibilités sont infinies, ajouter une attaque spéciale, ajouter une vie à chaque tour (autoguérison) etc.

Et c’est la fin de cette très longue partie !

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier