Neste artigo, vamos desenvolver um aplicativo de tabuada, explorando a versatilidade da Programação Orientada a Objetos (POO) em Python.
Você terá a oportunidade de praticar os conceitos fundamentais da POO e como aplicá-los na criação de um aplicativo plenamente funcional.
Python é uma linguagem de programação que suporta múltiplos paradigmas, o que significa que nós, como desenvolvedores, podemos selecionar a abordagem mais adequada para cada situação e desafio. No contexto da Programação Orientada a Objetos, estamos falando de um dos paradigmas mais empregados na construção de aplicações escaláveis nas últimas décadas.
Fundamentos da POO
Vamos analisar rapidamente o conceito mais importante da POO em Python: as classes.
Uma classe serve como um modelo que define a estrutura e o comportamento de objetos. Este modelo nos permite criar Instâncias, que são essencialmente objetos individuais formados a partir da composição da classe.
Uma classe simples de livro, com atributos como título e cor, seria definida da seguinte maneira:
class Book: def __init__(self, title, color): self.title = title self.color = color
Para criar instâncias da classe `Book`, é necessário chamar a classe e fornecer argumentos para ela:
# Criação de instâncias da classe Book blue_book = Book("A criança azul", "Azul") green_book = Book("A história do sapo", "Verde")
Uma representação visual do nosso programa neste ponto seria:
O interessante é que, ao verificarmos o tipo das instâncias `blue_book` e `green_book`, o resultado é “Book”.
# Exibindo o tipo dos livros print(type(blue_book)) # <class '__main__.Book'> print(type(green_book)) # <class '__main__.Book'>
Com esses conceitos bem claros, podemos iniciar a construção do nosso projeto 😃.
Definição do Projeto
No nosso trabalho como desenvolvedores, a maior parte do tempo não é dedicada à escrita de código. Segundo o thenewstack, apenas um terço do nosso tempo é gasto escrevendo ou refinando código.
Os outros dois terços são dedicados à análise do código de outros e à compreensão do problema que estamos enfrentando.
Para este projeto, vou apresentar a definição do problema e, em seguida, analisaremos como desenvolver nosso aplicativo a partir dela. Com isso, percorreremos todo o processo, desde a concepção da solução até a sua implementação com código.
Um professor do ensino fundamental deseja um jogo para avaliar as habilidades de multiplicação de alunos com idades entre 8 e 10 anos.
O jogo deve incluir um sistema de vidas e pontos, onde o aluno começa com 3 vidas e precisa atingir uma pontuação específica para vencer. O programa também deve exibir uma mensagem de “derrota” se o aluno perder todas as vidas.
O jogo terá dois modos: multiplicações aleatórias e multiplicações de tabuada.
O primeiro modo apresentará ao aluno uma multiplicação aleatória de 1 a 10, e ele deverá responder corretamente para ganhar um ponto. Caso erre, o aluno perderá uma vida, e o jogo continua. O aluno vence ao atingir 5 pontos.
O segundo modo exibirá uma tabuada de 1 a 10, e o aluno deverá inserir o resultado correto de cada multiplicação. Se o aluno errar três vezes, ele perde, mas se completar duas tabelas, o jogo termina.
Sei que os requisitos podem parecer um pouco extensos, mas prometo que vamos abordá-los neste artigo 😁.
Dividir para Conquistar
A habilidade mais importante na programação é a capacidade de resolver problemas. Isso ocorre porque é crucial ter um plano antes de iniciar a escrita do código.
Sempre sugiro dividir um problema complexo em problemas menores, que podem ser resolvidos de forma mais simples e eficaz.
Então, se você precisa criar um jogo, comece dividindo-o em suas partes fundamentais. Esses subproblemas serão muito mais fáceis de abordar.
Somente assim será possível ter clareza sobre como executar e integrar tudo com o código.
Vamos então criar um diagrama de como será o jogo:
Este diagrama ilustra as relações entre os objetos do nosso aplicativo. Como podemos ver, os dois objetos principais são Multiplicação Aleatória e Multiplicação de Tabuada. O que eles compartilham são os atributos Pontos e Vidas.
Com todas essas informações em mente, vamos começar a codificar.
Criação da Classe Pai do Jogo
Ao trabalharmos com programação orientada a objetos, procuramos a forma mais limpa de evitar repetição de código. Isso é conhecido como DRY (Don’t Repeat Yourself – Não Se Repita).
Observação: Esse princípio não se refere a escrever menos linhas de código (a qualidade do código não deve ser medida por esse critério), mas sim a abstrair a lógica mais utilizada.
De acordo com a ideia anterior, a classe pai do nosso aplicativo deve definir a estrutura e o comportamento desejados para as outras duas classes.
Vejamos como isso seria feito.
class BaseGame: # Comprimento para centralizar a mensagem message_lenght = 60 description = "" def __init__(self, points_to_win, n_lives=3): """Classe base do jogo Args: points_to_win (int): Os pontos necessários para concluir o jogo n_lives (int): O número de vidas que o aluno possui. O valor padrão é 3. """ self.points_to_win = points_to_win self.points = 0 self.lives = n_lives def get_numeric_input(self, message=""): while True: # Obtém a entrada do usuário user_input = input(message) # Se a entrada for numérica, retorna-a # Caso contrário, imprime uma mensagem e repete if user_input.isnumeric(): return int(user_input) else: print("A entrada precisa ser um número") continue def print_welcome_message(self): print("JOGO DE MULTIPLICAÇÃO PYTHON".center(self.message_lenght)) def print_lose_message(self): print("VOCÊ PERDEU TODAS AS SUAS VIDAS".center(self.message_lenght)) def print_win_message(self): print(f"PARABÉNS, VOCÊ ALCANÇOU {self.points}".center(self.message_lenght)) def print_current_lives(self): print(f"Atualmente você tem {self.lives} vidas\n") def print_current_score(self): print(f"\nSua pontuação é {self.points}") def print_description(self): print("\n\n" + self.description.center(self.message_lenght) + "\n") # Método básico de execução def run(self): self.print_welcome_message() self.print_description()
Essa classe parece um tanto grande. Deixe-me detalhar cada parte.
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.
Já os atributos de instância são variáveis criadas apenas dentro do construtor.
A principal diferença entre eles é o escopo. Ou seja, atributos de classe são acessíveis tanto por objetos de instância quanto pela classe. Por outro lado, atributos de instância são acessíveis apenas a partir de objetos de instância.
game = BaseGame(5) # Acessando o atributo de classe message_lenght a partir da instância print(game.message_lenght) # 60 # Acessando o atributo de classe message_lenght diretamente da classe print(BaseGame.message_lenght) # 60 # Acessando o atributo de instância points a partir da instância print(game.points) # 0 # Tentando acessar o atributo de instância points a partir da classe print(BaseGame.points) # Erro de atributo
Um outro artigo pode aprofundar mais este tópico. Fique atento para lê-lo.
A função `get_numeric_input` é usada para garantir que o usuário forneça apenas entradas numéricas. Como você pode notar, este método foi projetado para perguntar ao usuário até que ele forneça uma entrada numérica. Vamos usá-lo mais tarde nas classes filhas.
Os métodos de impressão evitam a necessidade de repetir o mesmo código toda vez que um evento ocorre no jogo.
Por último, mas não menos importante, o método `run` é apenas um invólucro que as classes Multiplicação Aleatória e Multiplicação de Tabuada usarão para interagir com o usuário e tornar tudo funcional.
Criação das Classes Filhas
Após criarmos a classe pai, que define a estrutura e algumas funcionalidades do nosso aplicativo, é hora de criar as classes concretas dos modos de jogo, utilizando o poder da herança.
Classe de Multiplicação Aleatória
Esta classe executará o “primeiro modo” do nosso jogo. Ela usará o módulo `random`, que nos permite solicitar ao usuário operações aleatórias de 1 a 10. Aqui está um ótimo artigo sobre o módulo `random` (e outros módulos importantes) 😉.
import random # Módulo para operações aleatórias
class RandomMultiplication(BaseGame): description = "Neste jogo, você deve responder corretamente as multiplicações aleatórias.\nVocê ganha ao atingir 5 pontos, ou perde se perder todas as vidas" def __init__(self): # O número de pontos necessários para vencer é 5 # Passa 5 como argumento "points_to_win" 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): # Chama a classe superior para imprimir as mensagens de boas-vindas super().run() while self.lives > 0 and self.points_to_win > self.points: # Obtém dois números aleatórios number1, number2 = self.get_random_numbers() operation = f"{number1} x {number2}: " # Solicita que o usuário responda à operação # Previne erros de valor user_answer = self.get_numeric_input(message=operation) if user_answer == number1 * number2: print("\nSua resposta está correta\n") # Adiciona um ponto self.points += 1 else: print("\nSua resposta está incorreta\n") # Subtrai uma vida self.lives -= 1 self.print_current_score() self.print_current_lives() # Só é executado quando o jogo termina # E nenhuma das condições é verdadeira else: # Imprime a mensagem final if self.points >= self.points_to_win: self.print_win_message() else: self.print_lose_message()
Esta é outra classe extensa 😅. Mas, como mencionei antes, o que importa não é o número de linhas, mas sim a sua clareza e eficiência. E o melhor do Python é que ele permite aos desenvolvedores criar um código limpo e legível como se estivessem falando em português comum.
Esta classe tem algo que pode confundi-lo, mas vou explicar da forma mais simples possível.
# Classe pai def __init__(self, points_to_win, n_lives=3): "... # Classe filha def __init__(self): # O número de pontos necessários para ganhar é 5 # Passa 5 como argumento "points_to_win" super().__init__(5)
O construtor da classe filha está chamando a função `super`, que por sua vez se refere à classe pai (BaseGame). É basicamente dizer ao Python:
Preencha o atributo “points_to_win” da classe pai com o valor 5!
Não é necessário usar o `self` dentro da parte `super().__init__()` pois já estamos chamando `super` dentro do construtor, o que resultaria em redundância.
Também estamos usando a função `super` no método `run`, e vamos ver o que acontece nesse trecho de código.
# Método básico de execução # Método da classe pai def run(self): self.print_welcome_message() self.print_description() def run(self): # Chama a classe superior para imprimir as mensagens de boas-vindas super().run() .....
Como você pode observar, o método `run` na classe pai imprime a mensagem de boas-vindas e a descrição. Mas é uma boa ideia manter essa funcionalidade e também adicionar outros recursos extras nas classes filhas. Para isso, usamos `super` para executar todo o código do método pai antes de executar a próxima parte.
O restante da função de execução é bem direta. Ela solicita um número ao usuário com a mensagem da operação que deve ser respondida. Em seguida, o resultado é comparado com a multiplicação real e, se forem iguais, um ponto é adicionado; caso contrário, uma vida é retirada.
Vale ressaltar que estamos usando loops `while-else`. Isso foge do escopo deste artigo, mas publicarei um sobre isso em breve.
Finalmente, `get_random_numbers` usa a função `random.randint`, que retorna um inteiro aleatório dentro do intervalo especificado. Em seguida, ele retorna uma tupla de dois inteiros aleatórios.
Classe de Multiplicação de Tabuada
O “segundo modo” deve exibir o jogo no formato de tabuada e garantir que o usuário acerte pelo menos duas tabuadas.
Para isso, usaremos novamente o poder de `super` e modificaremos o atributo `points_to_win` da classe pai para 2.
class TableMultiplication(BaseGame): description = "Neste jogo, você deve resolver corretamente a tabuada completa.\nVocê ganha se resolver 2 tabelas" def __init__(self): # Precisa completar 2 tabelas para vencer super().__init__(2) def run(self): # Imprime as mensagens de boas-vindas super().run() while self.lives > 0 and self.points_to_win > self.points: # Obtém um número aleatório number = random.randint(1, 10) for i in range(1, 11): if self.lives <= 0: # Garante que o jogo não continue # Se o usuário perder todas as vidas self.points = 0 break operation = f"{number} x {i}: " user_answer = self.get_numeric_input(message=operation) if user_answer == number * i: print("Ótimo! Sua resposta está correta") else: print("Sua resposta está incorreta") self.lives -= 1 self.points += 1 # Só é executado quando o jogo termina # E nenhuma das condições são verdadeiras else: # Imprime a mensagem final if self.points >= self.points_to_win: self.print_win_message() else: self.print_lose_message()
Como você pode notar, 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 depois podemos esquecer 😅.
No método `run`, estamos usando um loop `for` para obter os números de 1 a 10 e construir a operação que é mostrada ao usuário.
Mais uma vez, se as vidas forem esgotadas ou os pontos necessários para vencer forem atingidos, o loop `while` é interrompido e a mensagem de vitória ou derrota é exibida.
SIM, criamos os dois modos do jogo, mas até agora, se executarmos o programa, nada acontecerá.
Então, vamos finalizar o programa implementando a escolha do modo e instanciando as classes dependendo dessa escolha.
Implementação da Escolha do Modo
O usuário poderá escolher qual modo quer jogar. Então, vamos ver como implementar essa funcionalidade.
if __name__ == "__main__": print("Selecione o modo de jogo") choice = input("[1],[2]: ") if choice == "1": game = RandomMultiplication() elif choice == "2": game = TableMultiplication() else: print("Por favor, selecione um modo de jogo válido") exit() game.run()
Primeiro, solicitamos que o usuário escolha entre os modos 1 ou 2. Se a entrada não for válida, o script é interrompido. 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.
Eis como seria:
Conclusão
Parabéns, você acabou de 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:
- Utilizar construtores de classe em Python
- Criar um aplicativo funcional com POO
- Utilizar a função `super` em classes Python
- Aplicar os conceitos básicos de herança
- Implementar atributos de classe e de instância
Bons estudos e muita codificação 👨💻
Em seguida, explore alguns dos melhores IDEs Python para alcançar uma maior produtividade.