Correction : introduction à la POO

Une "bulle" qui monte...


import p5
from random import randint

# liste des couleurs possibles
couleurs = ['white', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta'] 

# fonction de mise en place
def setup():
	p5.createCanvas(600, 400)
	
# fonction d'animation
def draw():
	global x, y, couleur, taille, vitesse
	
	# 1. Effacement global de la fenêtre :   
	p5.background(0)
	
	# 2 . Déplacement de la bulle vers le haut en tenant compte de sa vitesse;
	# Quand la bulle arrive en haut de la fenêtre, on réinitialise ses caractéristiques.  
	y = y - vitesse # ou y -= vitesse
	
	if y <= 0:
		y = p5.height
		x = randint(10,590)
		couleur = randint(0,6)
		taille = randint(10,20)
		vitesse = randint(15,20)	
		

	# 3. Dessin de la bulle dans la fenêtre :
	p5.fill(couleurs[couleur])
	p5.ellipse(x, y, taille)

# initialisation des caractéristiques de la bulle
x = randint(10,590)
y = p5.height
couleur = randint(0,6)
taille = randint(10,20)
vitesse = randint(15,20)	

p5.run(setup, draw)			
			

Deux choses pour gérer le mouvement de la bulle :

  1. à chaque "tour" de la boucle d'animation (= à chaque appel de la fonction draw ), modifier l'ordonnée y de la bulle pour faire "monter" cette dernière : il faut donc décrémenter la variable y ( ligne 21 ).
  2. tester ( ligne 23 ) siy <= 0, c'est à dire si la bulle "sort" de l'écran par le haut, et, dans ce cas, réinitialiser les caractéristiques la bulle.

Une remarque sur la condition à tester pour "savoir" si la bulle atteint le sommet : on ne peut pas utiliser une simple égalité, car, selon la valeur de la variable vitesse, la variable y n'obtiendra peut-être jamais la valeur 0; il faut donc tester une inégalité qui, elle, sera forcément vérifiée à un moment ou un autre, quelle que soit la valeur de vitesse.

Deux bulles...

Facile : on recopie les variables et les instructions pour gérer le mouvement de la bulle, et on les réutilise avec de nouveaux noms :


import p5
from random import randint

# liste des couleurs possibles
couleurs = ['white', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta'] 

# fonction de mise en place
def setup():
	p5.createCanvas(600, 400)
	
# fonction d'animation
def draw():
	global x, y, couleur, taille, vitesse
	global x2, y2, couleur2, taille2, vitesse2
	
	# 1. Effacement global de la fenêtre :   
	p5.background(0)
	
	# 2 . Déplacement des bulles  
	y = y - vitesse
	y2 = y2 - vitesse2
	
	if y <= 0:
		y = p5.height
		x = randint(10,590)
		couleur = randint(0,6)
		taille = randint(10,20)
		vitesse = randint(15,20)	
	if y2 <= 0:
		y2 = p5.height
		x2 = randint(10,590)
		couleur2 = randint(0,6)
		taille2 = randint(10,20)
		vitesse2 = randint(15,20)	
		

	# 3. Dessin des bulles dans la fenêtre :
	p5.fill(couleurs[couleur])
	p5.ellipse(x, y, taille)
	
	p5.fill(couleurs[couleur2])
	p5.ellipse(x2, y2, taille2)

# initialisation des caractéristiques des bulles
x = randint(10,590)
y = p5.height
couleur = randint(0,6)
taille = randint(10,20)
vitesse = randint(15,20)	

x2 = randint(10,590)
y2 = p5.height
couleur2 = randint(0,6)
taille2 = randint(10,20)
vitesse2 = randint(15,20)	

p5.run(setup, draw)	
			

Trois, quatre,... bulles...

Argh....

Cette façon d'envisager le programme devient de plus en plus délicate à gérer :

  • le code devient parfaitement illisible.
  • on fait X fois pratiquement la même chose, X étant le nombre de bulles : le code est lourd, pas optimisé,
  • si on veut modifier par exemple l'intervalle dans lequel la taille d'une bulle doit se trouver, il faut le faire X fois dans le code pour chacune des bulles ! Le code n'est donc pas maintenable, donc pas fiable car entraînant une grande probabilité d'erreur, surtout si quelqu'un d'autre prenait votre relève et essayait de le développer ( bonne chance alors ! )

D'où la nécessité de passer, dans cette situation, à une autre façon de concevoir le programme, un autre paradigme de programmation, la Programmation Orientée Objet ( POO ).

En réfléchissant bien au problème, on pouvait tout de même remplacer toutes ces variables par les éléments d'un tableau de tableaux, chaque sous-tableau codant une bulle; en parcourant ce tableau, on peut alors s'adresser à chaque bulle sans avoir besoin de préciser de nom de variables :


import p5
from random import randint

# liste des couleurs possibles
couleurs = ['white', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta'] 

# fonction de mise en place
def setup():
	p5.createCanvas(600, 400)
	
# fonction d'animation
def draw():
	# 1. Effacement global de la fenêtre :   
	p5.background(0)
	
	# 2 . Déplacement de la bulle vers le haut en tenant compte de sa vitesse;	
	for bulle in tab: # parcours du tableau des bulles
		bulle[1] -= bulle[4]
		# Quand la bulle arrive en haut de la fenêtre, on réinitialise ses caractéristiques. 
		if bulle[1] < 0:
			bulle[0] = randint(10,590)
			bulle[1] = p5.height
			bulle[2] = randint(0,6)
			bulle[3] = randint(10,30)
			bulle[4] = randint(5,15)
			# 3. Dessin de la bulle dans la fenêtre :
		p5.fill(couleurs[bulle[2]])
		p5.ellipse(bulle[0], bulle[1], bulle[3])
 		
# initialisation des caractéristiques des bulles
tab = [] # tableau des bulles
for i in range(50):
		tab_bulle = [] # tableau qui représente une bulle
		x = randint(10, 590)
		y = p5.height
		couleur = randint(0, 6)
		taille = randint(10, 30)
		vitesse = randint(5, 15)
		tab_bulle = [x, y, couleur, taille, vitesse]
		tab.append(tab_bulle)	

p5.run(setup, draw)
				

En plus, plus besoin de global, un tableau est déjà global à un script ( cf. cours de Première ) !

Le "moule" : la classe Bulle


class Bulle:
    
	# constructeur
	def __init__(self,x, y, couleur, taille, vitesse):
		self.x = x
		self.y = y
		self.couleur = couleur # indice de la couleur dans la liste 'couleurs'
		self.taille = taille
		self.vitesse = vitesse

	def affiche(self):
		couleurs = ['white', 'black', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta']
		p5.fill(couleurs[self.couleur])
		p5.ellipse(self.x, self.y, self.taille)
        
	def monte(self):
		if y <= 0:
			self.y = p5.height
			self.x = randint(10,590)
			self.couleur = randint(0,6)
			self.taille = randint(10,20)
			self.vitesse = randint(15,20)		
			

Pour chaque méthode ou attribut d'un objet, il faut préfixer son nom par self.

Par contre, la liste couleurs ( attention, pas l'attribut self.couleur ! ) est commune à tous les objets : on n'a donc pas besoin de la préfixer par self., ce sera la même pour tous les objets.

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


import p5
...

class Bulle:
	...
	
# fonction de mise en place
def setup():
	p5.createCanvas(600, 400)
	
# fonction d'animation
def draw():
    p5.background(0) # effacement de la bulle
    bulle1.monter() # déplacement
    bulle1.afficher() # affichage
  
# instanciation d'un objet de type "Bulle"
x = randint(10,290)
y = p5.height
couleur = randint(0,7) 
taille = randint(2,10)
vitesse = randint(1,10)
bulle1 = Bulle(x, y, couleur, taille, vitesse) # arguments dans le même ordre que les paramètres du constructeur

p5.run(setup, draw)
			

Des objets dans une liste ??

Dans le programme principal, on crée une liste dont chaque élément sera un objet :

  
# instanciation des objets de type "Bulle"
bulles = [] # tableau d'objets de type 'Bulle'

for i in range(50): # 50 bulles
	x = randint(10,290)
	y = 190
	couleur = randint(0,7)
	taille = randint(2,10)
	vitesse = randint(1,10)
	bulle = Bulle(x, y, couleur, taille, vitesse) # création de l'objet
	bulles.append(bulle)	# ajout comme élément du tableau 'bulles'	

p5.run(setup, draw)		
			

Dans la fonction draw, on parcourt la liste d'objets, en appelant, pour chacun de ces éléments ( qui sont des objets de type Bulle ) leurs méthodes monter et afficher :


# fonction d'animation
def draw():
	p5.background(0) # effacement de la bulle
	for bulle in bulles: # parcours par valeur du tableau des bulles
		bulle.monter() # déplacement
		bulle.afficher() # affichage	
			

Le code complet du script est ici.

Les balles rebondissantes

La méthode importante est la méthode rebonds : elle prend comme paramètre un autre objet de type Balle :


class Balle:
	
	....
				
	def rebonds(self, autre_balle): # le constructeur prend comme paramètre un AUTRE objet de type 'Balle'
		dx = autre_balle.x - self.x
		dy = autre_balle.y - self.y
		distance = sqrt(dx**2 + dy**2)
		minDist = autre_balle.taille//2 + self.taille//2
	
		if (distance < minDist):
			self.vx = -self.vx
			self.vy = -self.vy
			autre_balle.vx = -autre_balle.vx
			autre_balle.vy = -autre_balle.vy				
			

Et cette méthode doit être appelée dans la fonction draw pour chacun des balles successivement :


def draw():
	# Effacement de la fenêtre
	p5.background(0)

	# Traitement de la première balle
	balle1.move() # déplacement
	balle1.rebonds(balle2) # appel de la méthode 'rebonds' depuis l'objet 'balle1' avec comme argument l'objet 'balle2'
	balle1.affiche() # affichage

	# Traitement de la deuxième balle
	balle2.move()
	balle2.rebonds(balle1) # # appel de la méthode 'rebonds' depuis l'objet 'balle2' avec comme argument l'objet 'balle1'
	balle2.affiche()			
			

Le code complet du script est ici.

La fontaine colorée

Le code complet du script est ici.

Rebonds

Le code complet du script est ici.