Byg en Python Multiplication Table App med OOP

I denne artikel skal du bygge en Multiplikationstabeller-app ved at bruge kraften i objektorienteret programmering (OOP) i Python.

Du vil øve dig i hovedbegreberne i OOP, og hvordan du bruger dem i en fuldt funktionel applikation.

Python er et multiparadigme programmeringssprog, hvilket betyder, at vi som udviklere kan vælge den bedste løsning til hver situation og problem. Når vi taler om objektorienteret programmering, henviser vi til et af de mest brugte paradigmer til at bygge skalerbare applikationer i de sidste årtier.

Det grundlæggende i OOP

Vi vil tage et hurtigt kig på det vigtigste koncept for OOP i Python, klasserne.

En klasse er en skabelon, hvori vi definerer strukturen og adfærden af ​​objekter. Denne skabelon giver os mulighed for at oprette Instances, som ikke er andet end individuelle objekter, der er lavet efter sammensætningen af ​​klassen.

En simpel bogklasse med egenskaberne titel og farve vil blive defineret som følger.

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

Hvis vi vil oprette forekomster af klassebogen, skal vi kalde klassen og sende argumenter til den.

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

En god repræsentation af vores nuværende program ville være:

Det fantastiske er, at når vi tjekker typen af ​​blue_book og green_book forekomsterne, 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 at have haft disse koncepter krystalklare, kan vi begynde at bygge projektet 😃.

Projekterklæring

Mens man arbejder som udviklere/programmører, bliver det meste af tiden ikke brugt på at skrive kode, iflg nyhedsstakken vi bruger kun en tredjedel af vores tid på at skrive eller omformulere kode.

Vi brugte de andre to tredjedele på at læse andres kode og analysere det problem, vi arbejder på.

Så til dette projekt vil jeg generere en problemformulering, og vi vil analysere, hvordan vi opretter vores app ud fra den. Som et resultat heraf laver vi hele processen fra at tænke på løsningen til at anvende den med kode.

En primær lærer ønsker et spil til at teste multiplikationsfærdigheder hos elever fra 8 til 10 år.

Spillet skal have et liv og et pointsystem, hvor eleven starter med 3 liv og skal nå et vist antal point for at vinde. Programmet skal vise en “tab” besked, hvis eleven udtømmer hele sit liv.

Spillet skal have to tilstande, tilfældige multiplikationer og tabelmultiplikationer.

Den første skal give eleven en tilfældig multiplikation fra 1 til 10, og han/hun skal svare rigtigt for at vinde et point. Hvis det ikke sker, mister eleven et live, og spillet fortsætter. Eleven vinder først, når hun/han når 5 point.

Den anden tilstand skal vise en multiplikationstabel fra 1 til 10, hvor eleven skal indtaste resultatet af den respektive multiplikation. Hvis eleven fejler 3 gange, taber han/hun, men hvis hun/han fuldfører to borde, slutter spillet.

Jeg ved godt, at kravene måske er lidt større, men jeg lover dig, at vi løser dem i denne artikel 😁.

Del og hersk

Den vigtigste færdighed i programmering er problemløsning. Dette skyldes, at du skal have en plan, før du begynder at begynde at hacke koden.

  Hvordan fungerer laser- og lampeprojektorer, og hvad er det rigtige for dig?

Jeg foreslår altid at tage det større problem og dele det op i mindre, der både kan, nemt og effektivt løses.

Så hvis du skal lave et spil, så start med at dele det op i de vigtigste dele af det. Disse delproblemer vil være meget nemmere at løse.

Netop da kan du få klarhed over, hvordan du udfører og integrerer alt med kode.

Så lad os lave en graf over, hvordan spillet ville se ud.

Denne grafik etablerer relationerne mellem objekterne i vores app. Som du kan se, er de to hovedobjekter tilfældig multiplikation og tabelmultiplikation. Og det eneste, de deler, er egenskaberne Points og Lives.

Med alle disse oplysninger i tankerne, lad os komme ind i koden.

Oprettelse af forældrespilklassen

Når vi arbejder med objektorienteret programmering, søger vi efter den reneste måde at undgå kodegentagelse. Dette kaldes TØR (gentag ikke dig selv).

Bemærk: Dette mål er ikke relateret til at skrive de færre linjer kode (Kodekvalitet må ikke måles efter det aspekt), men at abstrahere den mest brugte logik.

Ifølge den tidligere idé skal vores applikations overordnede klasse etablere strukturen og den ønskede adfærd for de to andre klasser.

Lad os se, hvordan det ville blive gjort.

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 virker som en ret stor klasse. Lad mig forklare det i dybden.

Først og fremmest, lad os forstå klasseattributterne og konstruktøren.

Dybest set er klasseattributter variabler skabt inde i klassen, men uden for konstruktøren eller en hvilken som helst metode.

Mens instansattributter er variabler, der kun er oprettet inde i konstruktøren.

Den største forskel mellem disse to er omfanget. dvs. klasseattributter er tilgængelige både fra et instansobjekt og klassen. På den anden side er instansattributter kun tilgængelige fra et 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 anden artikel kan dykke dybere ned i dette emne. Hold kontakten for at læse den.

Get_numeric_input-funktionen bruges til at forhindre brugeren i at levere input, der ikke er numerisk. Som du måske bemærker, er denne metode designet til at spørge brugeren, indtil den får et numerisk input. Vi vil bruge det senere i barnets timer.

  Genoptag en mislykket Chrome-download, hvor den slap [Tutorial]

Udskrivningsmetoderne giver os mulighed for at gemme gentagelsen af ​​at udskrive det samme, hver gang der opstår en begivenhed i spillet.

Sidst, men ikke mindst, er kørselsmetoden blot en indpakning, som klasserne Tilfældig multiplikation og Tabel multiplikation vil bruge til at interagere med brugeren og gøre alt funktionelt.

Oprettelse af barnets klasser

Når vi har oprettet den overordnede klasse, som etablerer strukturen og noget af funktionaliteten af ​​vores app, er det tid til at bygge de faktiske spiltilstandsklasser ved at bruge arvens kraft.

Tilfældig multiplikationsklasse

Denne klasse vil køre den “første tilstand” i vores spil. Det kommer selvfølgelig til at bruge det tilfældige modul, som vil give os mulighed for at stille brugeren tilfældige operationer fra 1 til 10. Her er en fremragende artikel om de tilfældige (og andre vigtige 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()

Her er endnu en massiv klasse 😅. Men som jeg sagde før, er det ikke antallet af linjer, det tager, men hvor meget læsbart og effektivt det er. Og det bedste ved Python er, at det giver udviklere mulighed for at lave ren og læsbar kode, som om de talte normalt engelsk.

Denne klasse har én ting, der kan forvirre dig, men jeg vil forklare det så enkelt som muligt.

    # 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 af ​​den underordnede klasse kalder superfunktionen, som samtidig refererer til den overordnede (BaseGame) klasse. Det er dybest set at fortælle Python:

Udfyld attributten “points_to_win” for forældreklassen med 5!

Det er ikke nødvendigt at sætte self inde i super().__init__() delen, bare fordi vi kalder super inde i konstruktøren, og det ville resultere i redundant.

Vi bruger også superfunktionen i kørselsmetoden, og vi vil se, hvad der sker i det stykke kode.

    # 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 måske bemærker kørselsmetoden i forældreklassen, skal du udskrive velkomst- og beskrivelsesmeddelelsen. Men det er en god idé at beholde den funktionalitet og også tilføje ekstra i børneklasserne. Ifølge det bruger vi super til at køre al koden fra den overordnede metode, før vi kører det næste stykke.

  8 bedste e-handelsanalyseværktøjer til marketingfolk

Den anden del af løbefunktionen er ret ligetil. Den beder brugeren om et nummer med beskeden om den operation, han/hun skal svare. Derefter sammenlignes resultatet med den reelle multiplikation, og hvis de er lige store, tilføjes et point, hvis de ikke tager 1 liv.

Det er værd at sige, at vi bruger while-else-løkker. Dette overskrider omfanget af denne artikel, men jeg udgiver en om det om få dage.

Til sidst, get_random_numbers, bruger funktionen random.randint, som returnerer et tilfældigt heltal inden for det angivne interval. Derefter returnerer den en tupel af to tilfældige heltal.

Tilfældig multiplikationsklasse

“Anden tilstand” skal vise spillet i et multiplikationstabelformat og sørge for, at brugeren svarer rigtigt mindst 2 tabeller.

Til det formål vil vi igen bruge superkraften og ændre den overordnede klasse-attribut points_to_win til 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 kan indse, ændrer vi kun kørselsmetoden for denne klasse. Det er magien ved arv, vi skriver én gang den logik, vi bruger flere steder, og glemmer det 😅.

I kørselsmetoden bruger vi en for-løkke til at få tallene fra 1 til 10 og byggede den operation, der vises til brugeren.

Endnu en gang, hvis livet er opbrugt, eller de nødvendige point for at vinde er nået, brydes while-løkken, og meddelelsen om vind eller tab vil blive vist.

YEAH, vi skabte de to tilstande i spillet, men indtil nu, hvis vi kører programmet, vil der ikke ske noget.

Så lad os færdiggøre programmet ved at implementere tilstandsvalget og instansiere klasserne afhængigt af det valg.

Valg implementering

Brugeren vil være i stand til at vælge, hvilken tilstand der vil spille. Så lad os se, hvordan man implementerer 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 beder vi brugeren om at vælge mellem 1 eller 2 tilstande. Hvis inputtet ikke er gyldigt, stopper scriptet med at køre. Hvis brugeren vælger den første tilstand, vil programmet køre spiltilstanden tilfældig multiplikation, og hvis han/hun vælger den anden, køres tabelmultiplikationstilstanden.

Her er hvordan det ville se ud.

Konklusion

Tillykke, du bare bygge en Python-app med objektorienteret programmering.

Al koden er tilgængelig i Github-depot.

I denne artikel har du lært at:

  • Brug Python-klassekonstruktører
  • Opret en funktionel app med OOP
  • Brug superfunktionen i Python-klasser
  • Anvend de grundlæggende begreber om arv
  • Implementer klasse- og instansattributter

God kodning 👨‍💻

Udforsk derefter nogle af de bedste Python IDE for bedre produktivitet.