Parcours
/
Python
Python
/
Exercices
/
Le jeu d'extraterrestres d'Ellen
Le jeu d'extraterrestres d'Ellen

Le jeu d'extraterrestres d'Ellen

Exercice d'apprentissage

Introduction

La programmation orientée objet en Python

Si tu as l'habitude de programmer dans un style fonctionnel, déclaratif ou impératif, passer à la programmation orientée objet (POO) peut te sembler un peu étranger. Une approche orientée objet demande au programmeur de concevoir un problème comme un ou plusieurs objects qui interagissent entre eux, conservent un état et agissent sur des données. Les objets peuvent représenter des entités du monde réel (comme des voitures ou des chats), ou des concepts plus abstraits (comme des entiers, des véhicules ou des mammifères). Chaque objet devient une instance unique en mémoire et représente une partie du modèle global.

Les classes

Les Classes sont les définitions de nouveaux types d'objets, et c'est à partir d'elles que sont créées de nouvelles instances d'objets. Elles associent souvent des données à du code ou à des fonctions qui opèrent sur ces données. Dans ce sens, les classes sont des plans ou des ensembles d'instructions à partir desquels on peut construire et utiliser de nombreux objets d'un type similaire. Un programme complexe peut comporter de nombreuses classes, chacune produisant de nombreuses variétés d'objets. Le processus de construction d'un objet à partir d'une classe s'appelle l'instantiation (ou la création d'une instance de la classe).

La définition d'une classe en Python est simple :

class MyClass:
    # Class body goes here

Les attributs de classe

On peut ajouter des champs de classe (aussi appelés properties, attributes, data members ou variables) au corps de la classe :

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

On peut créer une instance (objet) de MyClass et la lier à un nom en appelant la classe (de la même manière qu'on appelle une fonction) :

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

Les Class attributes sont partagés par tous les objets (ou instances) créés à partir d'une classe, et sont accessibles via la dot notation, c'est-à-dire un . placé après le nom de l'objet et avant le nom de l'attribut :

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

Les attributs de classe sont définis dans le corps de la classe elle-même, avant toute autre méthode. Ils appartiennent à la classe, ce qui leur permet d'être partagés entre les instances de la classe. Comme ces attributs sont partagés, leur valeur peut être consultée et modifiée directement depuis la classe. Modifier la valeur des attributs de classe modifie cette valeur pour tous les objets instanciés à partir de la classe :

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

Avoir une multitude d'objets dont les données sont synchronisées en permanence n'est pas particulièrement utile. Heureusement, les objets créés à partir d'une classe peuvent être personnalisés avec leurs propres instance attributes (ou propriétés, variables ou champs d'instance). Comme leur nom l'indique, les attributs d'instance sont propres à chaque objet et peuvent être modifiés indépendamment.

Personnaliser l'instanciation d'objets avec __init__()

La « dunder method » (abréviation de « double underscore method ») spéciale __init__() sert à personnaliser les instances de classe, et peut servir à initialiser les attributs ou propriétés d'instance des objets. En raison de son rôle dans l'initialisation des attributs d'instance, __init__() est aussi appelé class constructor ou initializer. __init__() prend un paramètre obligatoire appelé self, qui fait référence à l'objet nouvellement créé ou initialisé. On peut ensuite passer les données des attributs ou propriétés d'instance comme arguments de __init__(), après le paramètre self.

Ci-dessous, MyClass possède désormais des attributs d'instance appelés location_x et location_y. Comme tu peux le voir, ces deux attributs ont été affectés au premier et au deuxième indice de l'argument location (un tuple) passé à __init__(). Les attributs location_x et location_y d'une instance de classe seront désormais initialisés quand tu instancies la classe et qu'un objet est créé :

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

Note que tu n'as besoin de passer qu'un seul argument pour initialiser MyClass ci-dessus : Python se charge de passer self quand la classe est appelée.

Les méthodes

Une method est une function liée soit à la classe elle-même (appelée class method, que nous verrons dans un exercice ultérieur), soit à une instance de la classe (un objet). Les méthodes qui opèrent sur un objet (une instance) doivent être définies avec self comme premier paramètre. Tu peux ensuite définir le reste des paramètres comme pour une fonction « normale » ou non liée :

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

Comme pour l'accès aux attributs, appeler une méthode demande simplement de placer un . après le nom de l'objet et avant le nom de la méthode. La méthode appelée n'a pas besoin d'une copie de l'objet comme premier paramètre : Python remplit self automatiquement :

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)

On peut accéder aux attributs de classe depuis l'intérieur des méthodes d'instance de la même manière qu'on y accède en dehors de la classe :

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

Mettre en attente ou ébaucher l'implémentation avec pass

Dans les exercices d'apprentissage précédents et les ébauches d'exercices d'entraînement, tu as déjà vu le mot-clé pass utilisé dans le corps des fonctions à la place du code réel. Le mot-clé pass est un marqueur de remplissage syntaxiquement valide : il évite que Python lève une erreur de syntaxe pour une fonction ou une définition de classe vide. En substance, c'est une façon de dire à l'interprète Python : « Ne t'inquiète pas ! Je vais mettre du code ici, c'est juste que je ne l'ai pas encore fait. »

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

Instructions

Ellen crée un jeu dans lequel le joueur doit combattre des aliens. Elle vient tout juste d'apprendre la programmation orientée objet (POO) et elle est impatiente de tirer parti de ce que l'utilisation de classes pourrait apporter à son programme.

Pour le plus grand plaisir d'Ellen, tu as proposé ton aide, et elle t'a confié la tâche de programmer les aliens que le joueur doit combattre.

1. Crée la classe Alien

Définis la classe Alien avec un constructeur qui prend deux paramètres <x_coordinate> et <y_coordinate>, et qui les place dans les variables d'instance x_coordinate et y_coordinate. Chaque alien commencera également avec 3 points de vie, donc la variable health doit aussi être initialisée.

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

Maintenant, chaque alien doit pouvoir suivre en interne sa propre position et ses propres points de vie.

2. La méthode hit

Ellen aimerait que la class Alien ait une méthode hit qui diminue de 1 les points de vie d'un objet alien lorsqu'elle est appelée. Ainsi, elle peut simplement appeler <alien>.hit() au lieu de devoir modifier manuellement les points de vie d'un alien. C'est à toi de décider si hit() amène les points de vie à zéro ou en dessous de zéro.

>>> alien = Alien(0, 0)

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

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

3. La méthode is_alive

Tu réalises que si les points de vie continuent de diminuer, à un moment donné ils atteindront probablement 0 (voire moins !). Ce serait une bonne idée d'ajouter une méthode is_alive qu'Ellen peut appeler rapidement pour vérifier si l'alien est... eh bien... vivant. 😉 <alien>.is_alive() doit renvoyer un booléen.

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

4. La méthode teleport

Dans le jeu d'Ellen, les aliens ont la capacité de se téléporter ! Tu vas devoir écrire une méthode teleport qui prend de nouvelles valeurs x_coordinate et y_coordinate, et qui modifie les coordonnées de l'alien en conséquence.

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

5. La méthode collision_detection

Évidemment, si les aliens peuvent être touchés par quelque chose, ils doivent alors être capables de détecter quand une telle collision s'est produite. Cependant, les algorithmes de détection de collision peuvent être délicats, et tu ne sais pas encore comment en implémenter un. Ellen a dit qu'elle s'en occuperait plus tard, mais elle aimerait quand même que la méthode collision_detection apparaisse dans la classe pour lui rappeler de développer cette fonctionnalité. Elle devra prendre une variable quelconque (probablement un autre objet), mais c'est vraiment tout ce que tu sais. Tu devras t'assurer que le fait de mettre la définition de la méthode dans la classe ne provoque aucune erreur lorsqu'elle est appelée :

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

6. Le compteur d'aliens

Ellen revient vers toi avec une nouvelle requête. Elle veut garder la trace du nombre d'aliens créés depuis le début de la partie. Elle dit que cela a un rapport avec le système de score.

Par exemple :

>>> 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. Crée une liste d'aliens

Ellen adore ce que tu as fait jusqu'ici, mais elle a une dernière faveur à te demander. Elle aimerait une fonction autonome (en dehors de la classe Alien()) qui crée une list d'objets Alien(), à partir d'une liste de positions (sous forme de tuples).

Par exemple :

>>> 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)
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Python Exercism

Prêt à commencer Le jeu d'extraterrestres d'Ellen ?

Inscris-toi sur Exercism pour apprendre et maîtriser Python avec 17 concepts146 exercices, et un vrai mentorat humain, le tout gratuitement.