Направите Питхон апликацију за таблицу множења са ООП-ом

У овом чланку ћете направити апликацију табеле множења, користећи моћ објектно оријентисаног програмирања (ООП) у Питхон-у.

Вежбаћете главне концепте ООП-а и како да их користите у потпуно функционалној апликацији.

Питхон је вишепарадигмски програмски језик, што значи да ми као програмери можемо изабрати најбољу опцију за сваку ситуацију и проблем. Када говоримо о објектно оријентисаном програмирању, мислимо на једну од најчешће коришћених парадигми за изградњу скалабилних апликација, последњих деценија.

Основе ООП-а

Погледаћемо на кратко најважнији концепт ООП-а у Питхон-у, класе.

Класа је шаблон у коме дефинишемо структуру и понашање објеката. Тај шаблон нам омогућава да креирамо инстанце, које нису ништа друго до појединачни објекти направљени према композицији класе.

Једноставна класа књиге, са атрибутима наслова и боје, била би дефинисана на следећи начин.

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

Ако желимо да креирамо инстанце књиге класа, морамо позвати класу и проследити јој аргументе.

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

Добра репрезентација нашег тренутног програма би била:

Невероватна ствар је да када проверимо тип инстанци блуе_боок и греен_боок, добијамо „Боок“.

# Printing the type of the books

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

Након што су ови концепти кристално јасни, можемо почети да градимо пројекат 😃.

Изјава о пројекту

Док радите као програмери/програмери, већина времена се не троши на писање кода, према тхеневстацк трошимо само трећину свог времена на писање или рефакторисање кода.

Остале две трећине смо провели читајући туђи код и анализирајући проблем на коме радимо.

Дакле, за овај пројекат ћу генерисати изјаву о проблему и анализираћемо како да направимо нашу апликацију од тога. Као резултат, правимо комплетан процес, од размишљања о решењу до примене кода.

Учитељ разредне наставе жели игру којом ће испитати вештине множења ученика од 8 до 10 година.

Игра мора имати животе и бодовни систем, где ученик почиње са 3 живота и мора да достигне одређену количину поена да би победио. Програм мора да прикаже поруку „изгуби“ ако ученик исцрпи цео свој живот.

Игра мора имати два режима, случајна множења и табела множења.

Први треба да ученику да насумично множење од 1 до 10, а он/она мора тачно да одговори да би освојио поен. Ако се то не догоди, ученик губи уживо и игра се наставља. Ученик побеђује само када достигне 5 поена.

Други режим мора да прикаже табелу множења од 1 до 10, где ученик мора да унесе резултат одговарајућег множења. Ако ученик не успе 3 пута губи, али ако попуни два стола, игра се завршава.

Знам да су захтеви можда мало већи, али обећавам вам да ћемо их решити у овом чланку 😁.

Завади па владај

Најважнија вештина у програмирању је решавање проблема. То је зато што морате да имате план пре него што почнете да хакујете код.

Увек предлажем да узмете већи проблем и поделите га на мање који се могу лако и ефикасно решити.

  Како користити нову алатку за слике екрана Фирефок-а

Дакле, ако треба да креирате игру, почните тако што ћете је разбити на најважније делове. Ове подпроблеме ће бити много лакше решити.

Управо тада можете имати јасноћу како да извршите и интегришете све са кодом.

Па хајде да направимо графикон како би игра изгледала.

Ова графика успоставља односе између објеката наше апликације. Као што видите, два главна објекта су Случајно множење и Табеларно множење. И једино што деле су атрибути Поени и животи.

Имајући све ове информације на уму, хајде да уђемо у код.

Креирање класе игре Родитељ

Када радимо са објектно оријентисаним програмирањем, тражимо најчистији начин да избегнемо понављање кода. Ово се зове СУВ (не понављајте се).

Напомена: Овај циљ није везан за писање мањег броја линија кода (квалитет кода не сме да се мери тим аспектом), већ за апстраховање логике која се најчешће користи.

Према претходној идеји, родитељска класа наше апликације мора успоставити структуру и жељено понашање друге две класе.

Да видимо како би се то урадило.

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

Вау, ово изгледа прилично велика класа. Дозволите ми да то дубоко објасним.

Пре свега, хајде да разумемо атрибуте класе и конструктор.

У основи, атрибути класе су променљиве креиране унутар класе, али изван конструктора или било које методе.

Док су атрибути инстанце променљиве креиране само унутар конструктора.

Главна разлика између ова два је обим. тј. атрибути класе су доступни и из објекта инстанце и из класе. С друге стране, атрибути инстанце су доступни само из објекта инстанце.

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

Други чланак може дубље заронити у ову тему. Останите у контакту да бисте је прочитали.

Функција гет_нумериц_инпут се користи да спречи корисника да пружи било који унос који није нумерички. Као што можете приметити, овај метод је дизајниран да пита корисника док не добије нумерички унос. Користићемо га касније на часовима детета.

Методе штампања нам омогућавају да сачувамо понављање штампања исте ствари сваки пут када се догоди неки догађај у игри.

  Како аутоматски покренути услуге при покретању у Линуку?

На крају, али не и најмање важно, метода рун је само омотач који ће класе Случајно множење и множење табеле користити за интеракцију са корисником и учинити све функционалним.

Креирање часова детета

Једном када смо креирали ту родитељску класу, која успоставља структуру и неке од функционалности наше апликације, време је да изградимо стварне класе режима игре, користећи моћ наслеђивања.

Класа случајног множења

Овај разред ће покренути „први режим“ наше игре. Користиће се, наравно, насумични модул, који ће нам дати могућност да постављамо кориснику случајне операције од 1 до 10. Ево одличног чланка о насумичном (и другим важним модулима) 😉.

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

Ево још једног великог часа 😅. Али као што сам раније рекао, није у питању број редова, колико је читљив и ефикасан. А најбоља ствар у вези са Питхон-ом је што омогућава програмерима да направе чист и читљив код као да причају нормалан енглески.

Овај час има једну ствар која вас може збунити, али ја ћу је објаснити што једноставније.

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

Конструктор подређене класе позива супер функцију која се у исто време односи на родитељску (БасеГаме) класу. У основи говори Пајтону:

Попуните атрибут “поинтс_то_вин” родитељске класе са 5!

Није неопходно стављати селф, унутар супер().__инит__() део само зато што позивамо супер унутар конструктора, и то би резултирало сувишним.

Такође користимо супер функцију у методи рун и видећемо шта се дешава у том делу кода.

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

Као што можете приметити методу рун у родитељској класи, одштампајте поруку добродошлице и опис. Али добра је идеја задржати ту функционалност и додати додатне у часове за децу. Према томе, користимо супер да покренемо сав код родитељске методе пре покретања следећег дела.

Други део функције покретања је прилично једноставан. Од корисника тражи број са поруком операције на коју мора одговорити. Затим се резултат пореди са реалним множењем и ако су једнаки, додаје се поен, ако не одузму 1 живот.

  Знајте о главној шеми: Звезда против пахуљице

Вреди рећи да користимо вхиле-елсе петље. Ово превазилази оквире овог чланка, али ћу објавити један о томе за неколико дана.

Коначно, гет_рандом_нумберс, користи функцију рандом.рандинт, која враћа насумични цео број унутар наведеног опсега. Затим враћа скуп од два случајна цела броја.

Класа случајног множења

„Други режим“ мора да прикаже игру у формату табеле множења и да се увери да је корисник тачно одговорио на најмање 2 табеле.

У ту сврху, поново ћемо користити моћ супер и модификовати атрибут родитељске класе поинт_то_вин на 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()

Као што можете да схватите ми само мењамо метод покретања ове класе. То је магија наслеђа, једном запишемо логику коју користимо на више места и заборавимо на њу 😅.

У методи рун, користимо фор петљу да бисмо добили бројеве од 1 до 10 и направили операцију која се приказује кориснику.

Још једном, ако се животи исцрпе или се достигну поени потребни за победу, вхиле петља ће се прекинути и биће приказана порука о победи или поразу.

ДА, направили смо два режима игре, али до сада ако покренемо програм ништа се неће догодити.

Дакле, хајде да финализујемо програм имплементацијом избора режима и инстанцирањем класа у зависности од тог избора.

Имплементација избора

Корисник ће моћи да одабере који режим жели да игра. Па да видимо како то имплементирати.

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

Прво, тражимо од корисника да бира између 1 или 2 режима. Ако унос није валидан, скрипта престаје да се изводи. Ако корисник изабере први режим, програм ће покренути режим игре случајног множења, а ако одабере други, покренуће се режим табеле множења.

Ево како би то изгледало.

Закључак

Честитам, само направите Питхон апликацију са објектно оријентисаним програмирањем.

Сав код је доступан у Гитхуб спремиште.

У овом чланку сте научили да:

  • Користите конструкторе класе Питхон
  • Направите функционалну апликацију са ООП-ом
  • Користите супер функцију у Питхон класама
  • Применити основне концепте наслеђивања
  • Имплементирајте атрибуте класе и инстанце

Срећно кодирање 👨‍💻

Затим истражите неке од најбољих Питхон ИДЕ за бољу продуктивност.