Bygg en Python Multiplication Table App med OOP

I den här artikeln kommer du att bygga en Multiplikationstabell-app genom att använda kraften i objektorienterad programmering (OOP) i Python.

Du kommer att träna på huvudkoncepten för OOP, och hur man använder dem i en fullt fungerande applikation.

Python är ett multiparadigm programmeringsspråk vilket gör att vi som utvecklare kan välja det bästa alternativet för varje situation och problem. När vi pratar om objektorienterad programmering syftar vi på ett av de mest använda paradigmen för att bygga skalbara applikationer under de senaste decennierna.

Grunderna i OOP

Vi ska ta en snabb titt på det viktigaste konceptet för OOP i Python, klasserna.

En klass är en mall där vi definierar strukturen och beteendet hos objekt. Den mallen låter oss skapa instanser, som inte är något annat än individuella objekt gjorda efter klassens sammansättning.

En enkel bokklass, med attributen titel och färg, skulle definieras enligt följande.

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

Om vi ​​vill skapa instanser av klassboken måste vi anropa klassen och skicka argument till den.

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

En bra representation av vårt nuvarande program skulle vara:

Det fantastiska är att när vi kontrollerar typen av blue_book- och green_book-instanserna får vi ”Book”.

# Printing the type of the books

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

Efter att ha haft dessa koncept kristallklart kan vi börja bygga projektet 😃.

Projektbeskrivning

När man jobbar som utvecklare/programmerare går det mesta inte åt att skriva kod, enligt thenewstack vi spenderar bara en tredjedel av vår tid på att skriva eller omfaktorisera kod.

Vi tillbringade de andra två tredjedelarna med att läsa andras kod och analysera problemet vi arbetar med.

Så för det här projektet kommer jag att skapa en problemformulering och vi kommer att analysera hur vi skapar vår app från den. Som ett resultat gör vi hela processen, från att tänka på lösningen till att tillämpa den med kod.

En primärlärare vill ha ett spel för att testa multiplikationsförmåga hos elever från 8 till 10 år.

Spelet måste ha ett liv och ett poängsystem, där eleven börjar med 3 liv och måste nå ett visst antal poäng för att vinna. Programmet måste visa ett ”förlora”-meddelande om eleven utarmar hela sitt liv.

Spelet måste ha två lägen, slumpmässiga multiplikationer och tabellmultiplikationer.

Den första ska ge eleven en slumpmässig multiplikation från 1 till 10, och han/hon måste svara rätt för att vinna en poäng. Om det inte inträffar förlorar studenten en live och spelet fortsätter. Eleven vinner först när hon/han når 5 poäng.

Det andra läget måste visa en multiplikationstabell från 1 till 10, där eleven måste mata in resultatet av respektive multiplikation. Om eleven misslyckas 3 gånger förlorar han/hon, men om hon/han slutför två bord, avslutas spelet.

Jag vet att kraven kanske är lite större, men jag lovar dig att vi löser dem i den här artikeln 😁.

Söndra och erövra

Den viktigaste färdigheten i programmering är problemlösning. Det beror på att du måste ha en plan innan du börjar hacka in koden.

Jag föreslår alltid att ta det större problemet och dela upp det i mindre som kan lösas både, enkelt och effektivt.

Så om du behöver skapa ett spel, börja med att dela upp det i de viktigaste delarna av det. Dessa delproblem blir mycket lättare att lösa.

Just då kan du få klarhet i hur du kör och integrerar allt med kod.

Så låt oss göra en graf över hur spelet skulle se ut.

Den här grafiken fastställer relationerna mellan objekten i vår app. Som du kan se är de två huvudobjekten slumpmässig multiplikation och tabellmultiplikation. Och det enda de delar är attributen Points och Lives.

Med all denna information i åtanke, låt oss gå in på koden.

Skapar spelklassen föräldra

När vi arbetar med Objektorienterad programmering söker vi efter det renaste sättet att undvika kodupprepning. Det här kallas TORR (upprepa inte dig själv).

Obs: Detta mål är inte relaterat till att skriva färre rader kod (kodkvalitet får inte mätas med den aspekten) utan att abstrahera den mest använda logiken.

Enligt den tidigare idén måste moderklassen för vår applikation fastställa strukturen och önskat beteende för de andra två klasserna.

Låt oss se hur det skulle göras.

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, det verkar vara en ganska stor klass. Låt mig förklara det på djupet.

Först av allt, låt oss förstå klassattributen och konstruktorn.

I grund och botten är klassattribut variabler skapade inuti klassen, men utanför konstruktorn eller någon metod.

Medan instansattribut är variabler som bara skapas inuti konstruktorn.

Den största skillnaden mellan dessa två är omfattningen. dvs klassattribut är tillgängliga både från ett instansobjekt och klassen. Å andra sidan är instansattribut endast tillgängliga från ett instansobjekt.

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

En annan artikel kan dyka djupare in i detta ämne. Håll kontakten för att läsa den.

Get_numeric_input-funktionen används för att förhindra att användaren tillhandahåller någon inmatning som inte är numerisk. Som du kanske märker är denna metod utformad för att fråga användaren tills den får en numerisk inmatning. Vi kommer att använda det senare i barnets klasser.

Utskriftsmetoderna tillåter oss att spara upprepningen av att skriva ut samma sak varje gång en händelse inträffar i spelet.

Sist men inte minst är körmetoden bara ett omslag som klasserna Random multiplikation och Tabellmultiplikation kommer att använda för att interagera med användaren och göra allt funktionellt.

Skapar barnets klasser

När vi väl har skapat den överordnade klassen, som fastställer strukturen och en del av funktionaliteten i vår app, är det dags att bygga de faktiska spellägesklasserna, genom att använda kraften i arv.

Klass för slumpmässig multiplikation

Den här klassen kommer att köra det ”första läget” i vårt spel. Den kommer att använda den slumpmässiga modulen, vilket ger oss möjlighet att fråga användaren om slumpmässiga operationer från 1 till 10. Här är en utmärkt artikel om de slumpmässiga (och andra viktiga moduler) 😉.

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

Här är en annan massiv klass 😅. Men som jag sa tidigare, det är inte antalet rader det tar, det är hur mycket läsbart och effektivt det är. Och det bästa med Python är att det låter utvecklare göra ren och läsbar kod som om de pratade normal engelska.

Den här klassen har en sak som kan förvirra dig, men jag ska förklara det så enkelt som möjligt.

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

Konstruktören av den underordnade klassen anropar superfunktionen som samtidigt refererar till den överordnade (BaseGame) klassen. Det säger i princip till Python:

Fyll i ”points_to_win”-attributet för föräldraklassen med 5!

Det är inte nödvändigt att lägga in sig själv inuti super().__init__()-delen bara för att vi anropar super inuti konstruktorn, och det skulle resultera i redundant.

Vi använder också superfunktionen i körningsmetoden, och vi kommer att se vad som händer i den kodbiten.

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

Som du kanske märker körmetoden i föräldraklassen, skriv ut välkomst- och beskrivningsmeddelandet. Men det är en bra idé att behålla den funktionen och även lägga till extra i barnklasserna. Enligt det använder vi super för att köra all kod för den överordnade metoden innan vi kör nästa bit.

Den andra delen av körfunktionen är ganska enkel. Den ber användaren om ett nummer med meddelandet om operationen han/hon måste svara. Sedan jämförs resultatet med den verkliga multiplikationen och om de är lika, adderas en poäng, om de inte tar av 1 liv.

Det är värt att säga att vi använder while-else-loopar. Detta överskrider räckvidden för den här artikeln men jag kommer att publicera en om det om några dagar.

Slutligen, get_random_numbers, använder funktionen random.randint, som returnerar ett slumpmässigt heltal inom det angivna intervallet. Sedan returnerar den en tupel av två slumpmässiga heltal.

Klass för slumpmässig multiplikation

Det ”andra läget” måste visa spelet i ett multiplikationstabellformat och se till att användaren svarar rätt minst 2 tabeller.

För det ändamålet kommer vi att använda superkraften igen och ändra attributet för överordnad klass points_to_win till 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()

Som du förstår ändrar vi bara körmetoden för denna klass. Det är det som är magin med arv, vi skriver en gång logiken vi använder på flera ställen, och glömmer bort det 😅.

I körmetoden använder vi en for-loop för att få siffrorna från 1 till 10 och byggde operationen som visas för användaren.

Återigen om livet är uttömt eller poängen som behövs för att vinna uppnås, kommer while-slingan att bryta och meddelandet om vinst eller förlust kommer att visas.

YEAH, vi skapade spelets två lägen, men tills nu kommer ingenting att hända om vi kör programmet.

Så låt oss avsluta programmet genom att implementera lägesvalet och instansiera klasserna beroende på det valet.

Val implementering

Användaren kommer att kunna välja vilket läge som vill spela. Så låt oss se hur man implementerar det.

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

Först ber vi användaren att välja mellan 1 eller 2 lägen. Om inmatningen inte är giltig slutar skriptet att köras. Om användaren väljer det första läget kommer programmet att köra spelläget för slumpmässig multiplikation, och om han/hon väljer det andra körs läget för tabellmultiplikation.

Så här skulle det se ut.

Slutsats

Grattis, du bara bygga en Python-app med objektorienterad programmering.

All kod finns tillgänglig i Github-förvaret.

I den här artikeln lärde du dig att:

  • Använd Python-klasskonstruktörer
  • Skapa en funktionell app med OOP
  • Använd superfunktionen i Python-klasser
  • Tillämpa de grundläggande begreppen arv
  • Implementera klass- och instansattribut

Glad kodning 👨‍💻

Utforska sedan några av de bästa Python IDE för bättre produktivitet.