Треки
/
Python
Python
/
Вправи
/
Гра Еллен з прибульцями
Гра Еллен з прибульцями

Гра Еллен з прибульцями

Навчальна вправа

Вступ

Обʼєктно-орієнтоване програмування в Python

Якщо ми досі програмували у функціональному, декларативному чи імперативному стилі, перехід до обʼєктно-орієнтованого програмування (ООП) може видатися трохи незвичним. Підхід ООП пропонує програмісту думати про задачу як про один або кілька objects, які взаємодіють між собою, зберігають стан і діють над даними. Обʼєкти можуть представляти реальні сутності (як-от машини чи коти) або абстрактніші поняття (як-от цілі числа, транспортні засоби чи ссавці). Кожен обʼєкт стає окремим екземпляром у памʼяті компʼютера й представляє частину загальної моделі.

Класи

Classes - це визначення нових типів обʼєктів, з яких створюються нові instances обʼєктів. Часто вони поєднують дані з кодом або функціями, які працюють із цими даними. У цьому сенсі класи - це креслення або набори інструкцій, з яких можна створити й використовувати багато обʼєктів схожого типу. Складна програма може мати багато класів, кожен з яких будує багато різних варіантів обʼєктів. Процес побудови обʼєкта з класу називається instantiation (або створенням екземпляра класу).

Визначення класу в Python досить просте:

class MyClass:
    # Class body goes here

Атрибути класу

Поля класу (інакше відомі як properties, attributes, data members або variables) можна додати до тіла класу:

class MyClass:
    number = 5
    string = "Hello!"

Екземпляр (обʼєкт) MyClass можна створити й привʼязати до імені, викликавши клас (так само, як викликається функція):

>>> new_object = MyClass()

# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>

Class attributes спільні для всіх обʼєктів (або екземплярів), створених із класу, і доступні через dot notation, тобто через крапку ., поставлену після імені обʼєкта й перед іменем атрибута:

>>> new_object = MyClass()

# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5

# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'

# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()

>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>

# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True

Атрибути класу визначаються в тілі самого класу, перед будь-якими іншими методами. Вони належать класу, що дозволяє спільно використовувати їх між екземплярами класу. Оскільки ці атрибути спільні, до їхнього значення можна звертатися й змінювати його безпосередньо з класу. Зміна значення атрибутів класу змінює значення для всіх обʼєктів, створених із цього класу:

>>> obj_one = MyClass()
>>> obj_two = MyClass()

# Accessing a class attribute from an object.
>>> obj_two.number
5

# Accessing the class attribute from the class itself.
>>> MyClass.number
5

# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27

# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27

>>> obj_two.number
27

Мати купу обʼєктів із постійно синхронізованими даними не надто корисно. На щастя, обʼєкти, створені з класу, можна налаштувати під себе за допомогою власних instance attributes (або властивостей, змінних чи полів екземпляра). Як випливає з назви, атрибути екземпляра унікальні для кожного обʼєкта й можуть змінюватися незалежно.

Налаштування створення обʼєкта за допомогою __init__()

Спеціальний «дундер-метод» (скорочено від «метод із подвійним підкресленням») __init__() використовується, щоб налаштовувати екземпляри класу, і ним можна ініціалізувати атрибути чи властивості екземпляра обʼєктів. Через свою роль в ініціалізації атрибутів екземпляра __init__() також називають class constructor або initializer. __init__() приймає один обовʼязковий параметр під назвою self, який указує на щойно ініціалізований або створений обʼєкт. Дані для атрибутів чи властивостей екземпляра потім можна передати як аргументи __init__() після параметра self.

Нижче MyClass має атрибути екземпляра під назвами location_x і location_y. Як бачимо, ці два атрибути присвоєно першому та другому індексам аргументу location (кортежу), переданого в __init__(). Атрибути location_x і location_y для екземпляра класу тепер ініціалізуються під час створення екземпляра класу й обʼєкта:

class MyClass:
    # These are class attributes, variables, or fields.
    number = 5
    string = "Hello!"

    # This is the class "constructor", with a "location" parameter that is a tuple.
    def __init__(self, location):

        # This is an instance or object property, attribute, or variable.
        # Note that we are unpacking the tuple argument into two separate instance variables.
        self.location_x = location[0]
        self.location_y = location[1]

# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))

# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))

# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1

>>> new_object_two.location_x
-8

Зауважмо, що під час ініціалізації MyClass вище достатньо передати один аргумент: Python сам подбає про передавання self, коли клас викликають.

Методи

method - це function, привʼязана або до самого класу (відома як class method, про неї йтиметься в одній із наступних вправ), або до екземпляра класу (обʼєкта). Методи, які працюють з обʼєктом (екземпляром), потрібно визначати з self як першим параметром. Решту параметрів ми можемо визначити так само, як для «звичайної» чи незвʼязаної функції:

class MyClass:
    number = 5
    string = "Hello!"

    # Class constructor.
    def __init__(self, location):
        # Instance properties
        self.location_x = location[0]
        self.location_y = location[1]

    # Instance method. Note "self" as first parameter.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

Як і у випадку з доступом до атрибутів, щоб викликати метод, достатньо поставити . після імені обʼєкта й перед іменем методу. Викликаному методу не потрібна копія обʼєкта як перший параметр: Python автоматично підставляє self:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)

# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)

До атрибутів класу можна звертатися зсередини методів екземпляра так само, як і ззовні класу:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

    # Alter class variable number for all instances from within an instance.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1


>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5

>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6

Створення заглушки для реалізації за допомогою Pass

У попередніх концептуальних вправах і заготовках практичних вправ ми, напевно, бачили ключове слово pass у тілі функцій замість справжнього коду. Ключове слово pass - це синтаксично допустима заглушка: воно не дає Python видати синтаксичну помилку для порожнього визначення функції чи класу. По суті, це спосіб сказати інтерпретатору Python: «Не хвилюйся! Я обовʼязково напишу тут код, просто ще не зробив цього».

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return  self.location_x, self.location_y

    # Alter class variable number for all instances
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1

    # This will compile and run without error, but has no current functionality.
    def pending_functionality(self):
       # Stubbing or placholding the body of this method.
       pass

Вказівки

Еллен створює гру, у якій гравець має битися з прибульцями. Вона щойно дізналася про обʼєктно-орієнтоване програмування (ООП) і прагне скористатися тим, що може дати її програмі використання classes.

На радість Еллен, ми запропонували їй допомогу, і вона доручила нам запрограмувати прибульців, з якими має битися гравець.

1. Створення класу Alien

Визначте клас Alien з конструктором, який приймає два параметри <x_coordinate> і <y_coordinate> та записує їх у змінні екземпляра x_coordinate і y_coordinate. Кожен прибулець також починає з рівнем здоровʼя 3, тож змінну health теж потрібно ініціалізувати.

>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3

Тепер кожен прибулець має самостійно відстежувати власну позицію та здоровʼя.

2. Метод hit

Еллен хотіла б, щоб class Alien мав метод hit, який під час виклику зменшує здоровʼя обʼєкта прибульця на 1. Так вона зможе просто викликати <alien>.hit(), замість того щоб щоразу змінювати здоровʼя прибульця вручну. Вирішуйте самі, чи hit() знижує здоровʼя до нуля, чи нижче за нуль.

>>> alien = Alien(0, 0)

# Initialized health value.
>>> alien.health
3

# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2

3. Метод is_alive

Зауважмо, що якщо здоровʼя й далі зменшуватиметься, то в якийсь момент воно, найімовірніше, досягне 0 (а то й менше!). Було б добре додати метод is_alive, який Еллен зможе швидко викликати, щоб перевірити, чи прибулець... ну... живий. 😉 <alien>.is_alive() має повертати булеве значення (англ. Boolean).

>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False

4. Метод teleport

У грі Еллен прибульці вміють телепортуватися! Потрібно написати метод teleport, який приймає нові значення x_coordinate і y_coordinate та відповідно змінює координати прибульця.

>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4

5. Метод collision_detection

Очевидно, що якщо прибульців можна у щось влучити, то вони мають уміти визначати, коли таке зіткнення сталося. Однак алгоритми виявлення зіткнень бувають непростими, і ми ще не знаємо, як реалізувати такий. Еллен сказала, що зробить це пізніше, але все одно хотіла б, щоб метод collision_detection був у класі як нагадування про те, що цю функціональність треба допрацювати. Він має приймати якусь змінну (ймовірно, інший обʼєкт), але це все, що ми поки що знаємо. Потрібно подбати про те, щоб додавання визначення методу до класу не спричиняло помилок під час виклику:

>>> alien.collision_detection(other_object)
>>>

6. Лічильник прибульців

Еллен повернулася з новим проханням. Вона хоче відстежувати, скільки прибульців було створено за весь час існування гри. Каже, що це якось повʼязано із системою нарахування очок.

Наприклад:

>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2

7. Створення масиву прибульців

Еллен захоплена тим, що ми вже зробили, але має ще одне прохання. Вона хотіла б окрему функцію (поза класом Alien()), яка створює list з обʼєктів Alien(), отримуючи масив позицій (як tuples).

Наприклад:

>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
...     print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Python Exercism

Час розпочати Гра Еллен з прибульцями?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Python, а також 17 концепцій146 вправ та справжнє наставництво від людей, і все це безкоштовно.