Crie um aplicativo de tabela de multiplicação em Python com OOP

Neste artigo, você criará um aplicativo de tabelas de multiplicação, usando o poder da Programação Orientada a Objetos (OOP) em Python.

Você praticará os principais conceitos de OOP e como usá-los em um aplicativo totalmente funcional.

Python é uma linguagem de programação multiparadigma, o que significa que nós como desenvolvedores podemos escolher a melhor opção para cada situação e problema. Quando falamos em Programação Orientada a Objetos, estamos nos referindo a um dos paradigmas mais utilizados para construir aplicações escaláveis, nas últimas décadas.

Noções básicas de OOP

Vamos dar uma olhada rápida no conceito mais importante de OOP em Python, as classes.

Uma classe é um modelo no qual definimos a estrutura e o comportamento dos objetos. Esse template nos permite criar Instâncias, que nada mais são do que objetos individuais feitos a partir da composição da classe.

Uma classe de livro simples, com os atributos de título e cor, seria definida da seguinte forma.

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

Se quisermos criar instâncias do livro de classe, devemos chamar a classe e passar argumentos para ela.

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

Uma boa representação do nosso programa atual seria:

O incrível é que, quando verificamos o tipo das instâncias blue_book e green_book, obtemos “Book”.

# Printing the type of the books

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

Depois de ter esses conceitos bem claros, podemos começar a construir o projeto 😃.

declaração do projeto

Ao trabalhar como desenvolvedores/programadores, a maior parte do tempo não é gasta escrevendo código, de acordo com thenewstack gastamos apenas um terço do nosso tempo escrevendo ou refatorando código.

Passamos os outros dois terços lendo o código dos outros e analisando o problema em que estamos trabalhando.

Portanto, para este projeto, gerarei uma declaração de problema e analisaremos como criar nosso aplicativo a partir dela. Com isso, estamos fazendo o processo completo, desde pensar na solução até aplicá-la com código.

Um professor primário quer um jogo para testar as habilidades de multiplicação de alunos de 8 a 10 anos.

O jogo deve ter um sistema de vidas e pontos, onde o aluno começa com 3 vidas e deve atingir uma certa quantidade de pontos para vencer. O programa deve mostrar uma mensagem de “perder” se o aluno esgotar todas as suas vidas.

O jogo deve ter dois modos, multiplicações aleatórias e multiplicações de mesa.

A primeira deve dar ao aluno uma multiplicação aleatória de 1 a 10, e ele deve responder corretamente para ganhar um ponto. Caso isso não ocorra o aluno perde uma live e o jogo continua. O aluno só ganha quando atinge 5 pontos.

A segunda modalidade deverá apresentar uma tabuada de 1 a 10, onde o aluno deverá inserir o resultado da respectiva multiplicação. Se o aluno errar 3 vezes perde, mas se completar duas tabelas, o jogo acaba.

Sei que os requisitos podem ser um pouco maiores, mas prometo que vamos resolvê-los neste artigo 😁.

Dividir e conquistar

A habilidade mais importante na programação é a resolução de problemas. Isso ocorre porque você precisa ter um plano antes de começar a hackear o código.

  Como alternar e personalizar o modo de exibição de conversa no Outlook

Eu sempre sugiro pegar o problema maior e dividi-lo em problemas menores que podem ser resolvidos de maneira fácil e eficiente.

Portanto, se você precisa criar um jogo, comece dividindo-o nas partes mais importantes. Esses subproblemas serão muito mais fáceis de resolver.

Só assim você pode ter a clareza de como executar e integrar tudo com o código.

Então vamos fazer um gráfico de como seria o jogo.

Este gráfico estabelece as relações entre os objetos de nosso aplicativo. Como você pode ver, os dois objetos principais são Multiplicação aleatória e Multiplicação de tabela. E a única coisa que eles compartilham são os atributos Pontos e Vidas.

Tendo todas essas informações em mente, vamos entrar no código.

Criando a classe de jogo pai

Quando trabalhamos com programação Orientada a Objetos, buscamos a forma mais limpa de evitar a repetição de código. Isso é chamado SECO (não se repita).

Nota: Este objetivo não está relacionado a escrever menos linhas de código (a qualidade do código não deve ser medida por esse aspecto), mas a abstrair a lógica mais usada.

De acordo com a ideia anterior, a classe pai de nossa aplicação deve estabelecer a estrutura e o comportamento desejado das outras duas classes.

Vejamos como seria feito.

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

Uau, isso parece uma classe muito grande. Deixe-me explicar isso em profundidade.

Primeiramente, vamos entender os atributos da classe e o construtor.

Basicamente, atributos de classe, são variáveis ​​criadas dentro da classe, mas fora do construtor ou de qualquer método.

Enquanto os atributos de instância são variáveis ​​criadas apenas dentro do construtor.

A principal diferença entre esses dois é o escopo. ou seja, atributos de classe são acessíveis tanto de um objeto de instância quanto da classe. Por outro lado, os atributos de instância são acessíveis apenas a partir de um objeto de instância.

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

Outro artigo pode mergulhar mais fundo neste tópico. Fique em contato para lê-lo.

A função get_numeric_input é usada para evitar que o usuário forneça qualquer entrada que não seja numérica. Como você pode notar, este método foi projetado para perguntar ao usuário até que ele obtenha uma entrada numérica. Vamos usá-lo mais tarde nas aulas da criança.

  Como detectar tentativas de login suspeitas com o Zenlogin?

Os métodos de impressão nos permitem salvar a repetição de imprimir a mesma coisa cada vez que ocorre um evento no jogo.

Por último, mas não menos importante, o método run é apenas um wrapper que as classes Multiplicação aleatória e Multiplicação de tabela usarão para interagir com o usuário e tornar tudo funcional.

Criando as classes da criança

Depois de criarmos a classe pai, que estabelece a estrutura e algumas das funcionalidades de nosso aplicativo, é hora de criar as classes reais do modo de jogo, usando o poder da herança.

Classe de multiplicação aleatória

Esta classe executará o “primeiro modo” do nosso jogo. Ele vai usar o módulo random, é claro, o que nos dará a capacidade de solicitar ao usuário operações aleatórias de 1 a 10. Aqui está um excelente artigo sobre o random (e outros módulos importantes) 😉.

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

Aqui está outra aula massiva 😅. Mas, como afirmei antes, não é o número de linhas necessárias, é o quanto ele é legível e eficiente. E a melhor coisa sobre o Python é que ele permite que os desenvolvedores criem um código limpo e legível como se estivessem falando inglês normal.

Esta aula tem uma coisa que pode confundi-lo, mas vou explicar da forma mais simples possível.

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

O construtor da classe filha está chamando a função super que, ao mesmo tempo, se refere à classe pai (BaseGame). É basicamente dizer ao Python:

Preencha o atributo “points_to_win” da classe pai com 5!

Não é necessário colocar self, dentro da parte super().__init__() só porque estamos chamando super dentro do construtor, e isso resultaria em redundância.

Também estamos usando a função super no método run e veremos o que está acontecendo naquele trecho de código.

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

Como você pode observar o método run na classe pai, imprima a mensagem de boas-vindas e descrição. Mas é uma boa ideia manter essa funcionalidade e também adicionar outras extras nas classes filhas. De acordo com isso, usamos super para executar todo o código do método pai antes de executar a próxima parte.

  Como gerar uma cotação aleatória usando Python?

A outra parte da função de execução é bastante direta. Solicita ao usuário um número com a mensagem da operação que deve responder. Depois o resultado é comparado com a multiplicação real e se forem iguais soma um ponto, se não tirar 1 vida.

Vale dizer que estamos usando loops while-else. Isso excede o escopo deste artigo, mas publicarei um sobre isso em alguns dias.

Finalmente, get_random_numbers, usa a função random.randint, que retorna um número inteiro aleatório dentro do intervalo especificado. Em seguida, ele retorna uma tupla de dois inteiros aleatórios.

Classe de multiplicação aleatória

O “segundo modo” deve exibir o jogo em formato de tabuada e garantir que o usuário acerte pelo menos 2 tabuadas.

Para isso, usaremos novamente o poder de super e modificaremos o atributo da classe pai points_to_win para 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()

Como você pode perceber, estamos apenas modificando o método run desta classe. Essa é a mágica da herança, escrevemos uma vez a lógica que usamos em vários lugares e esquecemos 😅.

No método run, estamos usando um loop for para obter os números de 1 a 10 e construímos a operação que é mostrada ao usuário.

Mais uma vez, se as vidas forem esgotadas ou os pontos necessários para vencer forem alcançados, o loop while será interrompido e a mensagem de vitória ou derrota será exibida.

SIM, criamos os dois modos do jogo, mas até agora se rodarmos o programa nada vai acontecer.

Então, vamos finalizar o programa implementando a escolha do modo e instanciando as classes dependendo dessa escolha.

Implementação de escolha

O usuário poderá escolher qual modo quer jogar. Então, vamos ver como implementá-lo.

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

Primeiro, pedimos ao usuário que escolha entre os modos 1 ou 2. Se a entrada não for válida, o script para de funcionar. Se o usuário selecionar o primeiro modo, o programa executará o modo de jogo Multiplicação Aleatória, e se selecionar o segundo, o modo Tabela de multiplicação será executado.

Aqui está como seria.

Conclusão

Parabéns, você só construir um aplicativo Python com Programação Orientada a Objetos.

Todo o código está disponível no Repositório Github.

Neste artigo você aprendeu a:

  • Usar construtores de classe Python
  • Crie um aplicativo funcional com OOP
  • Use a função super nas aulas do Python
  • Aplicar os conceitos básicos de herança
  • Implementar atributos de classe e instância

Feliz codificação 👨‍💻

Em seguida, explore alguns dos melhores IDE Python para obter melhor produtividade.