Connexion élèves

Choisir le(s) module(s) à installer :

Principes de la POO

Finalement, on se rend compte que toutes nos bulles partagent pas mal de choses :

Ce sont donc toutes des "individus" sortis du même "moule" mais dont on a adapté certaines de leurs caractéristiques ( taille, couleur,...).

D'où l'idée qui est derrière la POO : "fabriquons" un "moule", duquel nous tirerons ensuite tous les "objets" dont nous auront besoin en adaptant au passage leurs caractéristiques, et en précisant de plus la manière dont ces objets vont interagir avec leur environnement.

Vous allez voir que le code devient alors beaucoup plus simple, lisible et compréhensible.

Le "moule" : la classe Bulle

Le moule duquel sortiront tous nos objets est appelé une classe en POO.

Une classe est un ensemble d'instructions qui ne décrivent pas un objet précis ( ici une bulle particulière ), mais permettent de spécifier comment en créer un ( ici avec une certaine taille, couleur, comment la faire bouger, etc...) ( quand vous avez une poële, de la pâte et la recette, vous n'avez pas encore les crèpes...)

En Python, pour définir une classe, on utilise le mot-clé class suivi du nom de la classe avec, par convention, une majuscule :


	class Bulle :
			

Les instructions contenues dans une classe auront deux rôles :

  1. soit elles manipulent les attributs de l'objet à créer, c'est à dire les variables associées à cet objet ( ici la taille, la couleur,... d'une bulle )
  2. soit elles décrivent les méthodes applicables à l'objet à créer, c'est à dire les fonctions qui permettront à cet objet d'interagir avec son environnement ( ici afficher une bulle, la faire monter,...)

	class Bulle :
	
		def __init__(self, x, taille) :
			self.x = x # un attribut de l'objet
			self.taille = taille # un autre attribut			
			...........................
			
		def afficher(self) : # une méthode de l'objet
		
			...........................
			
		def monter(self) : # une autre méthode
		
			..........................
			

Le code d'une classe est donc constitué de la définition de toutes les méthodes associées à elle.

On notera bien que ce code est indenté par rapport au mot clé class pour indiquer qu'il fait bien partie de la classe.

Le travail que vous allez faire maintenant va être une réécriture du précédent en utilisant la POO.

Dans un premier temps, vous allez compléter la classe Bulle en écrivant le code de ses différentes méthodes :

  • dans le constructeur, toutes les instructions qui permettront de définir les caractéristiques d'une nouvelle bulle ( coordonnées initiales x et y, taille, ...)
  • dans la méthode afficher, les instructions qui permettront de dessiner la bulle en ses coordonnées avec sa taille et sa couleur
  • dans la méthode monter, les instructions qui permettront de déplacer la bulle, mais aussi de tester si elle arrive en haut de la fenêtre ( et dans ce cas la réinitialiser...)
class Bulle: # constructeur def __init__(self, x, taille, vitesse, couleur): pass # méthode d'affichage def afficher(self): couleurs = ['white', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta'] pass # méthode de déplacement def monter(self): pass

On remarque que le constructeur prend plusieurs paramètres, dont les valeurs ( non définies pour l'instant, comme pour toute fonction... ) lui seront passées par le programme principal lors de la création des objets.

SOLUTION

Création d'un objet à partir d'une classe

Instanciation d'un objet

La création d'un objet à partir d'une classe s'appelle son instanciation : on dit alors que l'objet est une instance de la classe.

L'instanciation d'un objet se fait bien sûr en dehors de la définition de la classe.

Un objet est une structure de données complexes à laquelle on accédera par un nom de variable associée à l'objet.

La syntaxe est la suivante :


nom = Classe()	# noter les parenthèses : créer un objet revient en fait à appeler la méthode constructeur de la classe		
			

C'est à cette étape que le mot self va être remplacé par le nom de la variable associée.

Ici, pour instancier un objet à partir de la classe Bulle et l'associer au nom de variable bulle1 ( par exemple ), on écrira :


bulle1 = Bulle(20, 50)
			

Comme instancier un objet revient en fait à appeler la méthode constructeur, c'est à ce moment qu'on passe donc à celle-ci les éventuels arguments qu'elle attend ( ici, une position et une taille ).

Utilisation et manipulation d'un objet

Pour appeler une méthode associée à l'objet bulle1, la syntaxe est la suivante :


bulle1.affiche()
bulle1.monte()			
			

On notera l'utilisation du point . dans la syntaxe de la POO, qui indique l'appel d'une méthode à partir d'un objet précis :


nom_de_l_objet.methode() 			
				

Mais vous avez remarqué ?

  • self est obligatoirement indiqué en paramètre dans la définition des méthodes
  • Par contre, lors de l'appel de ces méthodes, on ne donne pas self comme argument !

En effet, self est passé implicitement ( c'est à dire, sans que l'on ait besoin de le préciser ) comme argument aux méthodes, c'est pourquoi il n'est pas nécessaire de l'indiquer lors de l'appel.

Vous avez en fait déjà souvent rencontré cette notation en Python ( ou en Javascript ), par exemple pour utiliser des méthodes sur des tableaux :


tab1.append(x) # ajoute l'élément x au tableau tab1
tab2.sort()	# trie le tableau tab2
			

Et oui, les tableaux en Python sont des objets ( en fait, tout est objet en Python...).

Travail à faire

Compléter le code précédent de façon à instancier un objet-bulle dans le programme principal, et à animer son déplacement dans la fonction draw.

Vous avez remarqué ? Plus de "global" dans la fonction draw ! En effet, les objets ont par nature une portée globale, et sont donc accessibles de partout dans le code; c'est aussi un des avantages du concept de POO...

Faire le même travail avec deux, trois,....bulles.

import p5 from random import randint class Bulle: # Code ci-dessus pass # fonction de mise en place def setup(): p5.createCanvas(600, 400) # fonction d'animation def draw(): # effacement de la fenêtre p5.background(0) # Déplacement, puis affichage de la/des bulle(s) : pass # PROGRAMME PRINCIPAL # instanciation de la/des bulle(s) : pass # lancement de l'animation : p5.run(setup, draw)

SOLUTION

Facile maintenant, non ? Mais il manque encore un petit quelque chose...

Des objets dans un tableau ??

Effectivement, le code précédent devient vite lourd quand le nombre de bulles augmente...

En fait, une classe est un nouveau type de donnée : tous nos objets peuvent donc très bien être lors de leur création rangés comme élément d'un tableau; il n'est alors plus besoin de leur donner un nom particulier.

Ainsi, si on définit un tableau bulles[] qui contiendra tous nos objets, on désignera chacun de ces objets avec la syntaxe habituelle des tableaux par : bulles[0], bulles[1], ....

Réécrire le code précédent en créant un tableau d'un nombre quelconque de bulles ( 50 c'est déjà pas mal...) et en les animant.

Vous utiliserez dans la fonction draw une boucle pour parcourir tous les éléments du tableau et réaliser l'animation.

SOLUTION

Interactions entre objets

Pour les "objets-bulles" que vous venez de créer n'apparaissait pas un des autres concepts majeurs en POO, le fait que les objets peuvent interagir entre eux à travers certaines de leur méthodes.

Pour illustrer ceci, voila une application "classique" de la POO : l'animation de deux balles qui rebondissent entre elles et sur les parois de la "boîte" dans laquelle elles sont enfermées.

Une classe Balle

Dans le code ci-dessous :

  1. Compléter la méthode move de la classe Balle pour gérer les rebonds d'une balle sur les parois de la "boîte".
  2. tester votre classe en instanciant un seul objet Balle ( bien observer le constructeur ! ), puis deux. Dans un premier temps, ne vous préoccupez pas des collisions entre balles, elles pourront se "passer dessus".

Une aide pour le mouvement des balles

A la différence des bulles de "Champagne !", les balles peuvent se déplacer dans toutes les directions/dans tous les sens, pas seulement verticalement vers le haut.

Pour coder cela, la vitesse devra donc :

  • être "décomposée" en une vitesse horizontale vx et une vitesse verticale vy pour distinguer les deux directions possibles de déplacement ( verticalement ou horizontalement )
  • ces deux "composantes" devront être algébriques, c'est à dire avoir un signe pour coder le sens du déplacement dans chaque direction ( vers le haut/bas ou vers la droite/gauche )

Par exemple : vx = +3 et vy = -5 signifie : "déplacement vers la droite et vers le haut".

Réfléchissez alors aux modifications à apporter à vx et/ou à vy pour "faire rebondir" une balle sur chaque côté de la "boîte" dans laquelle elles évoluent...

import p5 from math import sqrt class Balle: # constructeur def __init__(self, x, y, couleur, taille, vx, vy): self.x = x # position horizontale self.y = y # position verticale self.couleur = couleur # indice de la couleur dans la liste 'couleurs' self.taille = taille # taille self.vx = vx # vitesse horizontale self.vy = vy # vitesse verticale # méthode d'affichage def affiche(self): couleurs = ['white', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta'] p5.fill(couleurs[self.couleur]) p5.ellipse(self.x, self.y, self.taille) # méthode de déplacement dans la boîte def move(self): pass # méthode de rebond des balles l'une sur l'autre def rebonds(self, autre_balle): pass # fonction de mise en place def setup(): p5.createCanvas(600, 400) # fonction d'animation def draw(): # Effacement des balles : p5.background(0) # Pour chacune des deux balles successivement : # - on déplace la balle # - on teste si il y a rebond sur l'autre balle # - on affiche la balle pass # PROGRAMME PRINCIPAL # instanciation des balles # Conseils : # pour la taille : entre 50 et 100 pixels # pour la vitesse initiale : entre 5 et 10 pass p5.run(setup, draw)

SOLUTION

Une méthode pour les rebonds entre les balles

Vous allez maintenant rajouter à la classe Balle la méthode ( appelez la par exemple rebonds 😛) qui permettra la gestion des collisions entre les balles.

L'idée est que cette méthode soit appelée après le déplacement de chacune des deux balles, qu'elle teste alors si les deux balles sont "en contact" en comparant leurs positions respectives, et si oui, qu'elle modifie les coordonnées vx et/ou vy des balles pour les faire "se rebondir" l'une sur l'autre...

ça devient compliqué...la méthode devra "travailler" sur deux objets en même temps ???
Alors oui, chaque objet "dispose" de cette méthode, mais elle ne sera pas utilisée de la même façon à chaque instant. Il faut distinguer :

  • d'une part l'objet à partir duquel la méthode est appelée : ce sera une des deux balles après son déplacement
  • d'autre part, l'autre objet, qui sera lui passé en paramètre à la méthode : ce sera alors l'autre balle.

Oui, une classe est en fait un nouveau type de donnée, un objet peut donc être passé comme argument à une fonction ou une méthode :


objet1.methode(objet2)
			

Il faut bien entendu que la méthode ait été codée pour traiter cet argument...

  1. écrire la méthode rebonds() pour qu'elle permette de gérer les rebonds d'une balle contre l'autre.

    Pour la détection du rebond : il ne se fera que si la distance entre le centre des deux balles est inférieure à une distance donnée par la relation ci-contre ( oui, il va falloir faire un peu de maths...)

    Pour créer un rebond d'une balle sur l'autre, vous pouvez vous contenter de simplement inverser le signe de vx et vy des deux balles. Ce n'est pas très réaliste, mais si ça marche, c'est déjà bien ! 😌

    Attention dans cette méthode à bien distinguer l'objet qui correspond à self ( = l' objet à partir duquel la méthode sera appelée ) de celui passé en argument.

    Et attention là aussi, self doit quand même être précisé dans les paramètres de la méthode, même si lors de l'appel de cette dernière, on ne le passera pas comme argument :

    
    def rebonds(self, autre_balle):  # paramètre 'self' obligatoire
    	........					
    					

    Mais :

    
    une_balle.rebonds(autre_balle) # pas d'argument 'self'			
    					
  2. utiliser ensuite cette méthode dans l'animation de manière à l'appeler correctement après le déplacement de chacune des balles.

SOLUTION

Prolongements possibles

  1. compléter votre code pour faire évoluer 3, 4, 10, 50 balles ( il faudra sans doute diminuer leur taille ! )
  2. Pour la gestion des rebonds, il faudra comparer chaque balle à toutes les autres ! Comment faire ??

  3. Si n est le nombre de balles en jeu, quelle est la complexité de l'algorithme de gestion des collisions utilisé ici ? Qu'en pensez-vous ?
𞀯