Programme

Partie “structures de donneés”

Contenus : Vocabulaire de la programmation objet :

Capacités attendues :

Commentaires :

On n’aborde pas ici tous les aspects de la programmation objet comme le polymorphisme et l’héritage.

Vocabulaire de la POO

Notion de classe

Définition

Une classe est une construction de langage qui permet de réunir les données et les fonctions agissant sur ces données.

Exemple


class Robot:
    # Constructeur (appelé quand la classe est instanciée)
    def __init__(self):
        # x : attribut de la classe (ou propriété)
        self.x = 0
        # y : attribut de la classe (ou propriété)
        self.y = 0

    # méthode "bouge" de la classe Robot
    def bouge(self, dx, dy):
        self.x += dx
        self.y += dy

    # méthode "position" de la classe Robot
    def position(self):
        return (self.x, self.y)
# walle est un objet, instance de la classe Robot
walle = Robot()
# evee est un objet, autre instance de la classe Robot
evee = Robot()

walle.bouge(3,3)
evee.bouge(1,2)

print("WallE", walle.position())
print("Evee",  evee.position())
print("x WallE", walle.x) # accès direct

Représentation en mémoire

Représentation en mémoire

Vocabulaire

en python :

class NomClasse():  # définition de classe, CamelCase
    def __init__(self): # constructeur, self est une référence à l'instance
        self.attribut = .. # attribut initialisé
    def methode(self): # méthode
        ...
obj = NomClasse() # instanciation (obj = instance de NomClasse)
obj.attribut # accès direct à l'attribut
obj.methode() # appel d'une méthode, self est passé automatiquement
# équivalent à NomClasse.methode(obj)

Attributs

Les attributs sont initialisés dans le constructeur (méthode __init__).

Ils peuvent être déclarés avant le constructeur (pour indiquer leur type).

class Robot:
    x: int
    y: int
    def __init__(self):
        self.x = 0
        self.y = 0

Une valeur par défaut peut être donnée aux attributs dans la déclaration (cf attributs de classe).

Méthodes

Les méthodes sont des fonctions définies dans la classe, qui agissent sur une instance de la classe. Le premier argument d’une méthode est toujours self, qui est une référence à l’instance sur laquelle la méthode est appelée.

Les attributs de l’instance sont accessibles via self.attribut.

class Robot:
    ...
    def bouge(self, dx, dy):
        self.x += dx
        self.y += dy

Encapsulation

Il est préférable de ne pas agir directement sur les données d’un objet, mais de passer par des méthodes, qui jouent le rôle d’interface entre l’objet et le programme.

On peut protéger les attributs d’une classe en faisant débuter leur nom par un double underscore (self.__x par exemple)

class Robot:
    def __init__(self):
        self.__x = 0
        self.__y = 0
r2d2 = Robot()
r2d2.__x # ===> boum erreur pas permis

Remarque : certains langages (Java, C++, …) permettent de définir des attributs privés (non accessibles en dehors de la classe) et des attributs publics (accessibles en dehors de la classe). En python, tous les attributs sont publics, mais on peut simuler des attributs privés en utilisant le double underscore.

Infos utiles

Méthodes “magiques” / spéciales

Référence

Elles permettent (entre autres) d’implémenter les opérations “classiques” en python :

https://docs.python.org/fr/3/reference/datamodel.html

Exemple

class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, p):
        return Point(self.x + p.x, self.y + p.y)

p1 = Point(2, 4)
p2 = Point(3, 5)
p = p1 + p2
print(p.x, p.y) # 5, 9

Exemples : __repr__, __str__, __eq__, __lt__, __add__, __sub__, …

Attributs de classe

Utilisation

Un attribut de classe est défini au niveau de la classe, mais sert aussi de valeur par défaut aux attributs de la classe. On peut donc s’en servir pour stocker une information au niveau de la classe elle-même (bien) et/ou pour donner une valeur par défaut aux attributs (pas bien, mais couramment utilisé dans les faits)

class Point():
    cpt = 0
    x = 0
    def __init__(self, y):
        self.y = y
        Point.cpt += 1
    def pos(self):
        print(self.x, self.y)

Exemple d’utilisation

p1 = Point(2)
p1.pos() # 0 2
p1.x = 2
p1.pos() # 2 2
Point.x = 3
p1.pos() # 2 2
p2 = Point(3)
p2.pos() # 3 3
print(Point.cpt) # 2

De manière générale, attention aux valeurs par défaut de type mutable (listes, objets, …)

Exercices

Contact et carnet adresses

Contact

Définir et tester une classe Contact permettant de

Carnet

Définir et tester une classe Carnet permettant de

Date et heure

Heure

Définir et tester une classe Heure qui permet de

Date

Définir et tester une classe Date qui permet de

Ligne et points

Point

Définir et tester une classe Point permettant de :

Ligne

Définir et tester une classe Ligne permettant de :

Projet pyxel

Objectif

Réaliser un mini-jeu avec pyxel, en utilisant la programmation orientée objet.

Le jeu est un jeu de type “évite les obstacles” : une pluie de météorites tombe, et le joueur doit les éviter en se déplaçant sur l’écran.

Vous sauvegarderez chaque étape de votre travail en utilisant git.

Étapes

  1. Définir la classe Jeu avec 2 attributs : width et height (par défaut 160 et 120) qui sont la largeur et la hauteur de l’écran, et 3 méthodes :

    • play (lance le jeu : pyxel.init(self.width, self.height) et pyxel.run(self.update, self.draw))
    • update (met à jour l’état du jeu, ne fait rien pour l’instant)
    • draw (dessine l’état du jeu; pour l’instant elle ne fait que effacer l’écran). La classe doit comporter 2 attributs, width et height, qui sont la largeur et la hauteur de l’écran.

    L’appel du jeu se fait via :

    if __name__ == "__main__":
        jeu = Jeu(160, 120)
        jeu.play()
  2. Définir la classe Joueur avec 4 attributs : x, y, width et height (par défaut 8 et 8) qui sont la position et la taille du joueur, et 3 méthodes :

    • update() (met à jour la position du joueur en fonction des touches fléchées sur le clavier)
    • draw() (dessine le joueur sous la forme d’un carré plein)
    • get_rect() (renvoie un tuple (x, y, width, height) représentant le rectangle du joueur)

    Le constructeur de la classe doit initialiser x et y pour que le joueur soit positionné au centre de l’écran.

    Ajouter un attribut joueur à la classe Jeu, et modifier les méthodes update et draw pour utiliser les méthodes de la classe Joueur, ainsi que le constructeur de Jeu pour initialiser l’attribut joueur.

  3. Définir la classe Meteore avec 6 attributs : x, y, width, height, speed et color speed qui sont la position, la taille, la vitesse et la couleur de la météorite, et 3 méthodes :

    • update() (met à jour la position de la météorite en fonction de sa vitesse). Lorsque la météorite sort de l’écran (i.e. y > Jeu.height), elle doit être repositionnée en haut de l’écran (i.e. y est remis à une valeur aléatoire entre -Jeu.height et 0, et x est remis à une valeur aléatoire entre 0 et Jeu.width)
    • draw() (dessine la météorite sous la forme d’un carré plein)
    • get_rect() (renvoie un tuple (x, y, width, height) représentant le rectangle de la météorite)

    Le constructeur de la classe doit initialiser x à une valeur aléatoire entre 0 et Jeu.width, y à une valeur aléatoire entre 0 et -Jeu.height (au dessus de l’écran pour qu’elle apparaisse en tombant à un moment aléatoire), width et height à une valeur aléatoire comprise entre deux bornes à choisir et speed à une valeur aléatoire entre 1 et 3. Idem pour la couleur.

    Ajouter un attribut level à la classe Jeu, qui est un entier représentant le niveau actuel du jeu (initialisé à 1), et un attribut meteores à la classe Jeu, qui est une liste de météorites, et modifier les méthodes update et draw pour utiliser les méthodes de la classe Meteore, ainsi que le constructeur de Jeu pour initialiser l’attribut meteores avec une liste de level*10 météorites (où level est un attribut de Jeu initialisé à 1 dans le constructeur).

  4. Ajouter un attribut phase à la classe Jeu, qui peut prendre les valeurs :

    • "pre" : phase de préparation
    • "play" : phase de jeu
    • "gameover" : phase de fin de jeu

    Modifier les méthodes update et drawde la classe Jeu pour gérer les différentes phases :

    • en phase "pre", afficher “Appuyez sur ESPACE pour commencer” au centre de l’écran dans la méthode draw, et passer en phase "play" si la touche ESPACE est pressée dans la méthode update
    • en phase "play", mettre à jour et dessiner le joueur et les météorites
    • en phase "gameover", afficher “Game Over! Appuyez sur ESPACE pour rejouer” au centre de l’écran dans la méthode draw, et repasser en phase "pre" si la touche ESPACE est pressée.
  5. Ajouter une méthode collision(self, rect1, rect2) à la classe Jeu qui teste si deux rectangles (donnés sous la forme de tuples (x, y, width, height)) se chevauchent. Le test se fait en vérifiant que les rectangles ne sont pas disjoints :

    x1, y1, w1, h1 = rect1
    x2, y2, w2, h2 = rect2
    return not (x1 + w1 < x2 or x2 + w2 < x1 or y1 + h1 < y2 or y2 + h2 < y1)

    Modifier la méthode update de la classe Jeu pour tester si le rectangle du joueur (obtenu via joueur.get_rect()) chevauche le rectangle d’une météorite (obtenu via meteore.get_rect()). Si c’est le cas, passer en phase "gameover".

  6. (optionnel) Ajouter toutes les améliorations que vous souhaitez (son, score, niveaux, sprites, effets visuels, multijoueur…)