Construire une application de table de multiplication Python avec OOP

Dans cet article, vous allez créer une application de tables de multiplication en utilisant la puissance de la programmation orientée objet (POO) en Python.

Vous pratiquerez les principaux concepts de la POO et comment les utiliser dans une application entièrement fonctionnelle.

Python est un langage de programmation multiparadigme, ce qui signifie que nous, en tant que développeurs, pouvons choisir la meilleure option pour chaque situation et problème. Lorsque nous parlons de programmation orientée objet, nous faisons référence à l’un des paradigmes les plus utilisés pour créer des applications évolutives au cours des dernières décennies.

Les bases de la POO

Nous allons jeter un coup d’œil rapide au concept le plus important de la POO en Python, les classes.

Une classe est un modèle dans lequel nous définissons la structure et le comportement des objets. Ce modèle nous permet de créer des instances, qui ne sont rien d’autre que des objets individuels créés à la suite de la composition de la classe.

Une classe de livre simple, avec les attributs de titre et de couleur, serait définie comme suit.

class Book:
    def __init__(self, title, color):
        self.title = title
        self.color = color

Si nous voulons créer des instances de la classe book, nous devons appeler la classe et lui passer des arguments.

# Instance objects of Book class
blue_book = Book("The blue kid", "Blue")
green_book = Book("The frog story", "Green")

Une bonne représentation de notre programme actuel serait :

Ce qui est génial, c’est que lorsque nous vérifions le type des instances blue_book et green_book, nous obtenons « Book ».

# Printing the type of the books

print(type(blue_book))
# <class '__main__.Book'>
print(type(green_book))
# <class '__main__.Book'>

Une fois ces concepts parfaitement clairs, nous pouvons commencer à construire le projet 😃.

Déclaration de projet

Lorsque vous travaillez en tant que développeurs/programmeurs, la plupart du temps n’est pas consacré à l’écriture de code, selon la newsstack nous ne passons qu’un tiers de notre temps à écrire ou refactoriser du code.

Nous avons passé les deux tiers restants à lire le code des autres et à analyser le problème sur lequel nous travaillons.

Donc, pour ce projet, je vais générer un énoncé de problème et nous analyserons comment créer notre application à partir de celui-ci. En conséquence, nous réalisons le processus complet, de la réflexion sur la solution à son application avec du code.

Un enseignant du primaire veut un jeu pour tester les compétences de multiplication des élèves de 8 à 10 ans.

Le jeu doit avoir un système de vies et de points, où l’élève commence avec 3 vies et doit atteindre un certain nombre de points pour gagner. Le programme doit afficher un message « perdre » si l’élève épuise toute sa vie.

Le jeu doit avoir deux modes, des multiplications aléatoires et des multiplications de table.

Le premier doit donner à l’élève une multiplication aléatoire de 1 à 10, et il doit répondre correctement pour gagner un point. Si cela ne se produit pas, l’étudiant perd une vie et le jeu continue. L’élève ne gagne que lorsqu’il atteint 5 points.

Le deuxième mode doit afficher une table de multiplication de 1 à 10, où l’élève doit saisir le résultat de la multiplication respective. Si l’élève échoue 3 fois, il perd, mais s’il complète deux tables, la partie se termine.

Je sais que les exigences sont peut-être un peu plus grandes, mais je vous promets que nous les résoudrons dans cet article 😁.

Diviser et conquérir

La compétence la plus importante en programmation est la résolution de problèmes. C’est parce que vous devez avoir un plan avant de commencer à pirater le code.

Je suggère toujours de prendre le plus gros problème et de le diviser en plus petits qui peuvent être résolus à la fois facilement et efficacement.

Donc, si vous avez besoin de créer un jeu, commencez par le décomposer en ses parties les plus importantes. Ces sous-problèmes seront beaucoup plus faciles à résoudre.

À ce moment-là, vous pouvez avoir la clarté sur la façon d’exécuter et d’intégrer tout avec du code.

Faisons donc un graphique de ce à quoi ressemblerait le jeu.

Ce graphique établit les relations entre les objets de notre application. Comme vous pouvez le voir, les deux objets principaux sont la multiplication aléatoire et la multiplication de table. Et la seule chose qu’ils partagent, ce sont les attributs Points et Vies.

Ayant toutes ces informations à l’esprit, entrons dans le code.

Création de la classe de jeu Parent

Lorsque nous travaillons avec la programmation orientée objet, nous recherchons le moyen le plus propre d’éviter la répétition de code. C’est appelé SEC (ne vous répétez pas).

Remarque : Cet objectif n’est pas lié à l’écriture du moins de lignes de code (la qualité du code ne doit pas être mesurée par cet aspect) mais à l’abstraction de la logique la plus utilisée.

Selon l’idée précédente, la classe mère de notre application doit établir la structure et le comportement souhaité des deux autres classes.

Voyons comment cela se ferait.

class BaseGame:

    # Lenght which the message is centered
    message_lenght = 60
    
    description = ""    
        
    def __init__(self, points_to_win, n_lives=3):
        """Base game class

        Args:
            points_to_win (int): the points the game will need to be finished 
            n_lives (int): The number of lives the student have. Defaults to 3.
        """
        self.points_to_win = points_to_win

        self.points = 0
        
        self.lives = n_lives

    def get_numeric_input(self, message=""):

        while True:
            # Get the user input
            user_input = input(message) 
            
            # If the input is numeric, return it
            # If it isn't, print a message and repeat
            if user_input.isnumeric():
                return int(user_input)
            else:
                print("The input must be a number")
                continue     
             
    def print_welcome_message(self):
        print("PYTHON MULTIPLICATION GAME".center(self.message_lenght))

    def print_lose_message(self):
        print("SORRY YOU LOST ALL OF YOUR LIVES".center(self.message_lenght))

    def print_win_message(self):
        print(f"CONGRATULATION YOU REACHED {self.points}".center(self.message_lenght))
        
    def print_current_lives(self):
        print(f"Currently you have {self.lives} livesn")

    def print_current_score(self):
        print(f"nYour score is {self.points}")

    def print_description(self):
        print("nn" + self.description.center(self.message_lenght) + "n")

    # Basic run method
    def run(self):
        self.print_welcome_message()
        
        self.print_description()

Wow, cela semble une classe assez énorme. Permettez-moi de l’expliquer en profondeur.

Tout d’abord, comprenons les attributs de classe et le constructeur.

Fondamentalement, les attributs de classe sont des variables créées à l’intérieur de la classe, mais en dehors du constructeur ou de toute méthode.

Alors que les attributs d’instance sont des variables créées uniquement à l’intérieur du constructeur.

La principale différence entre ces deux est la portée. c’est-à-dire que les attributs de classe sont accessibles à la fois depuis un objet d’instance et depuis la classe. D’autre part, les attributs d’instance ne sont accessibles qu’à partir d’un objet d’instance.

game = BaseGame(5)

# Accessing game message lenght class attr from class
print(game.message_lenght) # 60

# Accessing the message_lenght class attr from class
print(BaseGame.message_lenght)  # 60

# Accessing the points instance attr from instance
print(game.points) # 0

# Accesing the points instance attribute from class
print(BaseGame.points) # Attribute error

Un autre article peut approfondir ce sujet. Restez en contact pour le lire.

La fonction get_numeric_input est utilisée pour empêcher l’utilisateur de fournir une entrée qui n’est pas numérique. Comme vous pouvez le remarquer, cette méthode est conçue pour demander à l’utilisateur jusqu’à ce qu’il obtienne une entrée numérique. Nous l’utiliserons plus tard dans les cours de l’enfant.

Les méthodes d’impression nous permettent d’éviter la répétition de l’impression de la même chose à chaque fois qu’un événement se produit dans le jeu.

Enfin, la méthode run n’est qu’un wrapper que les classes de multiplication aléatoire et de multiplication de table utiliseront pour interagir avec l’utilisateur et rendre tout fonctionnel.

Créer les classes de l’enfant

Une fois que nous avons créé cette classe parent, qui établit la structure et certaines des fonctionnalités de notre application, il est temps de créer les classes de mode de jeu réelles, en utilisant la puissance de l’héritage.

Classe de multiplication aléatoire

Cette classe exécutera le « premier mode » de notre jeu. Il va bien sûr utiliser le module random, ce qui nous donnera la possibilité de demander à l’utilisateur des opérations aléatoires de 1 à 10. Voici un excellent article sur le random (et d’autres modules importants) 😉.

import random # Module for random operations
class RandomMultiplication(BaseGame):

    description = "In this game you must answer the random multiplication correctlynYou win if you reach 5 points, or lose if you lose all your lives"

    def __init__(self):
        # The numbers of points needed to win are 5
        # Pass 5 "points_to_win" argument
        super().__init__(5)

    def get_random_numbers(self):

        first_number = random.randint(1, 10)
        second_number = random.randint(1, 10)

        return first_number, second_number
        
    def run(self):
        
        # Call the upper class to print the welcome messages
        super().run()
        

        while self.lives > 0 and self.points_to_win > self.points:
            # Gets two random numbers
            number1, number2 = self.get_random_numbers()

            operation = f"{number1} x {number2}: "

            # Asks the user to answer that operation 
            # Prevent value errors
            user_answer = self.get_numeric_input(message=operation)

            if user_answer == number1 * number2:
                print("nYour answer is correctn")
                
                # Adds a point
                self.points += 1
            else:
                print("nSorry, your answer is incorrectn")

                # Substracts a live
                self.lives -= 1
            
            self.print_current_score()
            self.print_current_lives()
            
        # Only get executed when the game is finished
        # And none of the conditions are true
        else:
            # Prints the final message
            
            if self.points >= self.points_to_win:
                self.print_win_message()
            else:
                self.print_lose_message()

Voici un autre cours massif 😅. Mais comme je l’ai déjà dit, ce n’est pas le nombre de lignes qu’il faut, c’est à quel point c’est lisible et efficace. Et la meilleure chose à propos de Python est qu’il permet aux développeurs de créer un code propre et lisible comme s’ils parlaient un anglais normal.

Cette classe a une chose qui peut vous dérouter, mais je vais l’expliquer aussi simplement que possible.

    # Parent class
    def __init__(self, points_to_win, n_lives=3):
        "...
    # Child class
    def __init__(self):
        # The numbers of points needed to win are 5
        # Pass 5 "points_to_win" argument
        super().__init__(5)

Le constructeur de la classe enfant appelle la super fonction qui, en même temps, fait référence à la classe parent (BaseGame). Il s’agit essentiellement de dire à Python :

Remplissez l’attribut « points_to_win » de la classe mère avec 5 !

Il n’est pas nécessaire de mettre self, à l’intérieur de la partie super().__init__() simplement parce que nous appelons super à l’intérieur du constructeur, et cela entraînerait une redondance.

Nous utilisons également la fonction super dans la méthode run, et nous verrons ce qui se passe dans ce morceau de code.

    # Basic run method
    # Parent method
    def run(self):
        self.print_welcome_message()
        
        self.print_description()
    def run(self):
        
        # Call the upper class to print the welcome messages
        super().run()
        
        .....

Comme vous pouvez le remarquer, la méthode run dans la classe parente affiche le message de bienvenue et de description. Mais c’est une bonne idée de conserver cette fonctionnalité et d’en ajouter d’autres dans les classes enfants. Selon cela, nous utilisons super pour exécuter tout le code de la méthode parent avant d’exécuter la pièce suivante.

L’autre partie de la fonction run est assez simple. Il demande à l’utilisateur un numéro avec le message de l’opération auquel il doit répondre. Ensuite, le résultat est comparé à la multiplication réelle et s’ils sont égaux, ajoute un point, s’ils n’enlèvent pas 1 point de vie.

Cela vaut la peine de dire que nous utilisons des boucles while-else. Cela dépasse le cadre de cet article mais j’en publierai un dans quelques jours.

Enfin, get_random_numbers utilise la fonction random.randint, qui renvoie un entier aléatoire dans la plage spécifiée. Ensuite, il renvoie un tuple de deux entiers aléatoires.

Classe de multiplication aléatoire

Le « deuxième mode », doit afficher le jeu sous forme de table de multiplication, et s’assurer que l’utilisateur répond correctement à au moins 2 tables.

Pour cela, nous utiliserons à nouveau la puissance de super et modifierons l’attribut de la classe mère points_to_win à 2.

class TableMultiplication(BaseGame):

    description = "In this game you must resolve the complete multiplication table correctlynYou win if you solve 2 tables"
    
    def __init__(self):
        # Needs to complete 2 tables to win
        super().__init__(2)

    def run(self):

        # Print welcome messages
        super().run()

        while self.lives > 0 and self.points_to_win > self.points:
            # Gets two random numbers
            number = random.randint(1, 10)            

            for i in range(1, 11):
                
                if self.lives <= 0:
                    # Ensure that the game can't continue 
                    # if the user depletes the lives

                    self.points = 0
                    break 
                
                operation = f"{number} x {i}: "

                user_answer = self.get_numeric_input(message=operation)

                if user_answer == number * i:
                    print("Great! Your answer is correct")
                else:
                    print("Sorry your answer isn't correct") 

                    self.lives -= 1

            self.points += 1
            
        # Only get executed when the game is finished
        # And none of the conditions are true
        else:
            # Prints the final message
            
            if self.points >= self.points_to_win:
                self.print_win_message()
            else:
                self.print_lose_message()

Comme vous pouvez le constater, nous ne modifions que la méthode run de cette classe. C’est la magie de l’héritage, on écrit une fois la logique qu’on utilise à plusieurs endroits, et on l’oublie 😅.

Dans la méthode run, nous utilisons une boucle for pour obtenir les nombres de 1 à 10 et construisons l’opération qui est montrée à l’utilisateur.

Encore une fois, si les vies sont épuisées ou si les points nécessaires pour gagner sont atteints, la boucle while se rompra et le message de gain ou de perte s’affichera.

OUAIS, nous avons créé les deux modes de jeu, mais jusqu’à présent, si nous exécutons le programme, rien ne se passera.

Finalisons donc le programme en implémentant le choix de mode, et en instanciant les classes en fonction de ce choix.

Mise en œuvre du choix

L’utilisateur pourra choisir quel mode veut jouer. Voyons donc comment l’implémenter.

if __name__ == "__main__":

    print("Select Game mode")

    choice = input("[1],[2]: ")

    if choice == "1":
        game = RandomMultiplication()
    elif choice == "2":
        game = TableMultiplication()
    else:
        print("Please, select a valid game mode")
        exit()

    game.run()

Tout d’abord, nous demandons à l’utilisateur de choisir entre les 1 ou 2 modes. Si l’entrée n’est pas valide, le script s’arrête. Si l’utilisateur sélectionne le premier mode, le programme exécutera le mode de jeu Random Multiplication, et s’il sélectionne le second, le mode de multiplication de table sera exécuté.

Voici à quoi cela ressemblerait.

Conclusion

Félicitations, vous venez de créer une application Python avec la programmation orientée objet.

Tout le code est disponible dans le Référentiel Github.

Dans cet article vous avez appris à :

  • Utiliser les constructeurs de classes Python
  • Créer une application fonctionnelle avec OOP
  • Utiliser la super fonction dans les classes Python
  • Appliquer les concepts de base de l’héritage
  • Implémenter les attributs de classe et d’instance

Bon codage 👨‍💻

Ensuite, explorez certains des meilleurs IDE Python pour une meilleure productivité.