Διαδρομές
/
Python
Python
/
Ασκήσεις
/
Το εξωγήινο παιχνίδι της Ellen
Το εξωγήινο παιχνίδι της Ellen

Το εξωγήινο παιχνίδι της Ellen

Άσκηση εκμάθησης

Εισαγωγή

Αντικειμενοστρεφής προγραμματισμός στην Python

Αν μέχρι τώρα προγραμμάτιζες με συναρτησιακό, δηλωτικό ή προστακτικό στυλ, η στροφή στον αντικειμενοστρεφή προγραμματισμό (OOP) μπορεί να σου φανεί λίγο ξένη. Η προσέγγιση του αντικειμενοστρεφούς προγραμματισμού ζητά από τον προγραμματιστή να σκεφτεί πώς θα μοντελοποιήσει ένα πρόβλημα ως ένα ή περισσότερα 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__()

Η ειδική "dunder method" (συντομογραφία του "double underscore method") __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 που είναι δεσμευμένη είτε στην ίδια την κλάση (γνωστή ως μέθοδος κλάσης, η οποία θα συζητηθεί σε επόμενη άσκηση) είτε σε ένα στιγμιότυπο της κλάσης (αντικείμενο). Οι μέθοδοι που επενεργούν σε ένα αντικείμενο (στιγμιότυπο) πρέπει να οριστούν με το 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

Προσωρινή ή stub υλοποίηση με το pass

Σε προηγούμενες ασκήσεις εννοιών και σε stubs ασκήσεων εξάσκησης, θα έχεις δει τη λέξη-κλειδί 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

Οδηγίες

Η Ellen φτιάχνει ένα παιχνίδι στο οποίο ο παίκτης πρέπει να πολεμήσει εξωγήινους. Μόλις έμαθε για τον Αντικειμενοστρεφή Προγραμματισμό (OOP) και ανυπομονεί να αξιοποιήσει όσα θα μπορούσε να προσφέρει στο πρόγραμμά της η χρήση classes.

Προς μεγάλη χαρά της Ellen, προσφέρθηκες να βοηθήσεις και σου ανέθεσε να προγραμματίσεις τους εξωγήινους που πρέπει να πολεμήσει ο παίκτης.

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

Η Ellen θα ήθελε η 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 που θα μπορεί να καλέσει γρήγορα η Ellen για να ελέγξει αν ο εξωγήινος είναι... λοιπόν... ζωντανός. 😉 Το <alien>.is_alive() θα πρέπει να επιστρέφει μια τιμή boolean.

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

4. Η μέθοδος teleport

Στο παιχνίδι της Ellen, οι εξωγήινοι έχουν την ικανότητα να τηλεμεταφέρονται! Θα χρειαστεί να γράψεις μια μέθοδο teleport που παίρνει νέες τιμές x_coordinate και y_coordinate και αλλάζει ανάλογα τις συντεταγμένες του εξωγήινου.

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

5. Η μέθοδος collision_detection

Προφανώς, αν οι εξωγήινοι μπορούν να χτυπηθούν από κάτι, τότε πρέπει να μπορούν να ανιχνεύσουν πότε συνέβη μια τέτοια σύγκρουση. Ωστόσο, οι αλγόριθμοι ανίχνευσης συγκρούσεων μπορεί να είναι δύσκολοι και δεν ξέρεις ακόμη πώς να υλοποιήσεις έναν. Η Ellen είπε ότι θα το κάνει αργότερα, αλλά θα ήθελε παρ' όλα αυτά η μέθοδος collision_detection να εμφανίζεται στην κλάση ως υπενθύμιση για να υλοποιηθεί η λειτουργικότητα. Θα χρειαστεί να δέχεται κάποιου είδους μεταβλητή (πιθανώς ένα άλλο αντικείμενο), αλλά αυτό είναι πραγματικά το μόνο που ξέρεις. Θα χρειαστεί να βεβαιωθείς ότι αν βάλεις τον ορισμό της μεθόδου στην κλάση, αυτό δε θα προκαλέσει σφάλματα όταν κληθεί:

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

6. Μετρητής εξωγήινων

Η Ellen επέστρεψε με ένα νέο αίτημα για εσένα. Θέλει να παρακολουθεί πόσοι εξωγήινοι έχουν δημιουργηθεί κατά τη διάρκεια ζωής του παιχνιδιού. Λέει ότι έχει κάποια σχέση με το σύστημα βαθμολόγησης.

Για παράδειγμα:

>>> 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. Δημιουργία λίστας εξωγήινων

Η Ellen λατρεύει αυτό που έχεις κάνει μέχρι τώρα, αλλά έχει να σου ζητήσει μια ακόμη χάρη. Θα ήθελε μια αυτόνομη συνάρτηση (έξω από την κλάση 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

Έτοιμος να ξεκινήσεις την άσκηση Το εξωγήινο παιχνίδι της Ellen;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Python με 17 έννοιες146 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.