• Get application security done the right way! Detect, Protect, Monitor, Accelerate, and more…
  • In this article, you are going to build a  Multiplication tables app, by using the power of Object-Oriented Programming (OOP) in Python.

    You will practice the main concepts of O.O.P, and how to use them in a fully functional application.

    Python is a multiparadigm programming language, which means that we as developers can choose the best option for each situation and problem. When we talk about Object-Oriented Programming, we are referring to one of the most used paradigms to build scalable applications, in the last decades.

    The basics of OOP

    We are going to take a quick look at the most important concept of O.O.P in Python, the classes.

    A class is a template in which we define the structure and behavior, of objects. That template allows us to create Instances, which are nothing but individual objects made following the composition of the class.

    A simple book class, with the attributes of title and color, would be defined as follows.

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

    If we want to create instances of the class book, we must call the class and pass arguments to it.

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

    A good representation of our current program would be:

    Class.png

    The awesome thing is that when we check the type of the blue_book and green_book  instances, we get “Book”.

    # Printing the type of the books
    
    print(type(blue_book))
    # <class '__main__.Book'>
    print(type(green_book))
    # <class '__main__.Book'>

    After having these concepts crystal clear, we can start building the project 😃.

    Project statement

    While working as developers/programmers, most of the time isn’t spent writing code, according to thenewstack we only spend a third of our time writing or refactoring code.

    We spent the other two-thirds reading other’s code and analyzing the problem we are working on.

    So for this project, I’ll generate a problem statement and we will analyze how to create our app from it. As a result, we are making the complete process, from thinking about the solution to apply it with code.

    A primary teacher wants a game to test multiplication skills of students from 8 to 10 years old.

    The game must have a lives and a points system, where the student starts with 3 lives and must reach a certain amount of points to win. The program must show a “lose” message if the student depletes all his/her lives.

    The game must have two modes, random multiplications and table multiplications.

    The first should give the student a random multiplication from 1 to 10, and he/she have to answer correctly to win a point. If that doesn’t ocurr the student lose a live and the game continues. The student only wins when she/he reaches 5 points.

    The second mode must display a multiplication table from 1 to 10, where the student must  input the result of the respective multiplication. If the student fails 3 times he/she loses, but if she/he completes two tables, the game finish.

    I know that the requirements maybe a little bigger, but I promise you that we will solve them in this article 😁.

    Divide and conquer

    The most important skill in programming is problem-solving. This is because you need to have a plan before starting to start hacking in the code.

    I always suggest taking the bigger problem and dividing it into smaller ones that can be both, easy and efficiently solved.

    So if you need to create a game, start by breaking it into the most important parts of it. These sub-problems will be much easier to solve.

    Just then you can have the clarity of how to execute and integrate everything with code.

    So let’s make a graph of how the game would look like.

    divide and conquer.png

    This graphic establishes the relations between the objects of our app. As you can see the two main objects are Random multiplication and Table multiplication. And the only thing they share is the attributes Points and Lives.

    Having all this information in mind, let’s get into the code.

    Creating the Parent game class

    When we work with Object-Oriented programming, we search for the cleanest way to avoid code repetition. This is called DRY (don’t repeat yourself).

    Note: This objective isn’t related to writing the fewer lines of code (Code quality mustn’t be measure by that aspect) but to abstract the most used logic.

    According to the previous idea, the parent class of our application must establish the structure and desired behavior of the other two classes.

    Let’s see how it would be done.

    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} lives\n")
    
        def print_current_score(self):
            print(f"\nYour score is {self.points}")
    
        def print_description(self):
            print("\n\n" + self.description.center(self.message_lenght) + "\n")
    
        # Basic run method
        def run(self):
            self.print_welcome_message()
            
            self.print_description()

    Wow, this seems a pretty huge class. Let me explain it in deep.

    First of all, let’s understand the class attributes and the constructor.

    Base-game-constructor.png

    Basically, class attributes, are variables created inside the class, but outside of the constructor or any method.

    While instance attributes are variables created only inside the constructor.

    The main difference between, these two is the scope. i.e class attributes are accessible both, from an instance object and the class. On the other hand, instance attributes are only accessible from an instance object.

    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
    

    Another article can dive deeper into this topic. Stay in touch to read it.

    The get_numeric_input function is used to prevent the user from providing any input that isn’t numeric. As you may notice this method is designed to ask the user until it gets a numeric input. We will use it later in the child’s classes.

    Base-game-input.png

    The print methods allow us to save the repetition of printing the same thing each time an event occurs in the game.

    Last but not least, the run method is just a wrapper that the Random multiplication and Table multiplication classes will use to interact with the user and make everything functional.

    Base-game-run.png

    Creating the child’s classes

    Once we’ve created that parent class, which establishes the structure and some of the functionality of our app, it’s time to built the actual game mode classes, by using the power of inheritance.

    Random multiplication class

    This class will run the “first mode” of our game. It is going to use the random module of course, which will give us the ability to ask the user random operations from 1 to 10. Here is an excellent article about the random (and other important modules) 😉.

    import random # Module for random operations
    class RandomMultiplication(BaseGame):
    
        description = "In this game you must answer the random multiplication correctly\nYou 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 correct\n")
                    
                    # Adds a point
                    self.points += 1
                else:
                    print("\nSorry, your answer is incorrect\n")
    
                    # 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()
    

    Here is another massive class 😅. But as I stated before, it’s not the numbers of lines it takes, is how much readable and efficient it is. And the best thing about Python is that it allows developers to make clean and readable code as if they were talking normal English.

    This class has one thing that may confuse you, but I’ll explain it as simply as 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)

    The constructor of the child class is calling the super function which, at the same time refers to the parent (BaseGame) class. It is basically telling Python:

    Fill out the “points_to_win” attribute of the parent class with 5!

    It is not necessary to put self, inside the  super().__init__() part just because we are calling super inside the constructor, and it would result in redundant.

    We also are using the super function in the run method, and we will see what’s happening in that piece of 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()
            
            .....

    As you may notice the run method in the parent class, prin the welcome and description message. But it is a good idea to keep that functionality and also adding extra ones in the child classes. According to that, we use super to run all the code of the parent method before running the next piece.

    The other part of the run function is pretty straightforward. It asks the user for a number with the message of the operation he/she must respond. Then the result is compared with the real multiplication and if they are equal, adds a point, if they don’t take off 1 life.

    It’s worth saying that we are using while-else loops. This exceeds the scope of this article but I’ll publish one about it in few days.

    Finally, get_random_numbers, uses the function random.randint, which returns a random integer within the specified range. Then it returns a tuple of two random integers.

    Random multiplication class

    The “second mode”, must display the game in a multiplication table format, and make sure the user answers correctly at least 2 tables.

    For that purpose, we will use again the power of super and modify the parent class attribute points_to_win to 2.

    class TableMultiplication(BaseGame):
    
        description = "In this game you must resolve the complete multiplication table correctly\nYou 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()

    As you can realize we are only modifying the run method of this class. That’s the magic of inheritance, we write once the logic we use in multiple places, and forget about it 😅.

    In the run method, we are using a for loop to get the numbers from 1 to 10 and built the operation that is shown to the user.

    Once again if the lives are depleted or the points needed to win are reached, the while loop will break, and the win or lose message will be displayed.

    YEAH, we created the two modes of the game, but until now if we run the program nothing will happen.

    So let’s finalize the program by implementing the mode choice, and instantiating the classes depending on that choice.

    Choice implementation

    The user will be able to choose what mode wants to play. So let’s see how to implement it.

    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()

    First, we ask the user to choose between the 1 or 2 modes. If the input isn’t valid the script stops running. If the user selects the first mode, the program will run the Random Multiplication game mode, and if he/she selects the second, the Table multiplication mode will be run.

    Here is how it would look like.

    game.png

    Conclusion

    Congratulations, you just build a Python app with Object-Oriented Programming.

    All of the code is available in the Github repository.

    In this article you learned to :

    • Use Python class constructors
    • Create a functional app with OOP
    • Use the super function in Python classes
    • Apply the basic concepts of inheritance
    • Implement class and instance attributes

    Happy coding 👨‍💻

    Next, explore some of the best Python IDE for better productivity.