Animation de balles

Balles en folie.

On veut créer un nombre n de balles dans un canevas.

  • Chaque balle a sa vitesse propre (que l'on choisira au hasard) et se déplace suivant une droite (mouvement rectiligne).
  • Chaque balle se déplace avec la vitesse qui lui a été choisie, mais est replacée au centre du canevas lorsqu'elle sort du canevas dans lequel les balles se déplacent.

Pour donner une impression de vitesse à une balle, on translate la balle dans le canevas et on attend un certain temps t avant de régénérer l'affichage de l'écran. Si une balle b1 est translatée du vecteur (1,1) tandis qu'une balle b2 est translatée du vecteur (2,2), le temps d'attente avant cette translation (temps entre deux "update" de l'affichage de la fenêtre principale) étant le même, b2 donnera l'impression d'aller deux fois plus vite que b1.

Une solution possible.

Prenez le temps de tester, de modifier, de comprendre...


import tkinter as tk
from random import randint

maitre = tk.Tk() # fenêtre conteneur principal

largeur = 700 # largeur du canevas contenant les balles
longueur = 700 # hauteur du canevas contenant les balles
terrain = tk.Canvas(maitre,width=largeur, height=longueur) # canevas dans lequel les balles se déplacent
terrain.pack()


def hexadecimal(n):
    """Fonction qui renvoie l'écriture héxadécimale de l'entier n
        compris entre 0 et 255."""
    ecritureHexa = hex(n)[2:]  # on utilise la fonction hex de python 
                               # hex(3) = '0x3', hex(25)= '0x19'
                               # on supprime le '0x' de début avec [2:]
    if len(ecritureHexa) < 2 : 
        return '0' + ecritureHexa  # on veut une écriture à deux chiffres conformément 
                                   # à l'écriture hexa usuelle des couleurs
    else :
        return ecritureHexa

def unecouleur():
    """Fonction qui détermine une couleur au hasard au format hexadécimal."""
    r = hexadecimal(randint(0, 255))
    v = hexadecimal(randint(0, 255))
    b = hexadecimal(randint(0, 255))
    return '#'+r+v+b
    
    

def balle(centrex, centrey, diagonale , couleur, zone) :
    """
    Entrées :
        centrex, centrey :  coordonnées du centre du cercle représentant la balle.
        diagonale :  diagonale du rectangle dans lequel le cercle représentant la balle est inscrit.
        couleur : couleur de la balle.
        zone : canvas dans lequel on crée la balle.
    """
    b = zone.create_oval(centrex-diagonale/2,centrey-diagonale/2, centrex+diagonale/2,centrey+diagonale/2, fill=couleur)
    return b
    
    
    
     
    

    
    
def deplacement(balle, zone, v) :
    """
     translation de la balle  dans le canevas zone.
     vecteur de translation = (v[0],v[1]). 
     Si la balle sort du canevas, on la replace au centre.
    """ 
    # liste (x0,y0,x1,y1) des coordonnées coin sup gauche et coin inf droit du rectangle
    # dans lequel s'inscrit le cercle rerpésentant la balle :
    x0,y0,x1,y1 = zone.coords(balle)
                                                            
    # on enregistre les dimensions de la balle :
    dx, dy = x1-x0, y1-y0
    
    # translation de la balle : 
    x0 += v[0]
    x1 += v[0]
    y0 += v[1]
    y1 += v[1]
    
    # si le coin supérieur gauche de la balle sort du canevas, on replace la balle au centre :
    if x0<0 or x0>largeur or y0<0 or y0>longueur : 
        x0  = largeur/2 
        x1 = x0 + dx
        y0 = longueur/2
        y1 = y0 + dy
        
    # on met à jour les coordonnées de la balle :
    zone.coords(balle, x0,y0,x1,y1)
    
 
 
jeu = [] # liste de balles
n = 100 # nombre de balles


# boucle de création de n balles :
for _ in range(n) :
    jeu.append( balle(largeur/2, longueur/2, randint(10,50) , unecouleur(), terrain) )
 
 
# boucle de création de n vitesses :
vitesse = [] # liste des vitesses des balles
for _ in range(n) :
    vitesse.append( (randint(-20,20), randint(-20,20)) )



while(True) :  # boucle de mises à jour affichage du canevas
    
    for j in range(n) :  # boucle de translation de toutes les balles
        deplacement(jeu[j], terrain, vitesse[j]) 
        
    maitre.after(100,maitre.update()) # réaffichage de la fenêtre après un dixième de seconde
     


maitre.mainloop()

Balles en folie (2).

On reprend la situation précédente. Mais lorsqu'une balle sort de la fenêtre tkinter, au lieu de se replacer au centre, elle repart de la position du pointeur de la souris.

On pourra utiliser winfo_pointerx(), winfo_rootx() (et les fonctions y correspondantes).

Une solution possible.

Prenez le temps de tester, de modifier, de comprendre...


import tkinter as tk
from random import randint

maitre = tk.Tk() # fenêtre conteneur principal

largeur = 700 # largeur du canevas contenant les balles
longueur = 700 # hauteur du canevas contenant les balles
terrain = tk.Canvas(maitre,width=largeur, height=longueur) # canevas dans lequel les balles se déplacent
terrain.pack()


def hexadecimal(n):
    """Fonction qui renvoie l'écriture héxadécimale de l'entier n
        compris entre 0 et 255."""
    chiffres = list('0123456789abcdef')
    if 0 <= n <= 255:
        return chiffres[n//16]+chiffres[n%16]
    else:
        return '00'

def unecouleur():
    """Fonction qui détermine une couleur au hasard au format hexadécimal."""
    r = hexadecimal(randint(0, 255))
    v = hexadecimal(randint(0, 255))
    b = hexadecimal(randint(0, 255))
    return '#'+r+v+b
    

def balle(centrex, centrey, diagonale , couleur, zone) :
    """
    Entrées :
        centrex, centrey :  coordonnées du centre du cercle représentant la balle.
        diagonale :  diagonale du rectangle dans lequel le cercle représentant la balle est inscrit.
        couleur : couleur de la balle.
        zone : canvas dans lequel on crée la balle.
    """
    b = zone.create_oval(centrex-diagonale/2,centrey-diagonale/2, centrex+diagonale/2,centrey+diagonale/2, fill=couleur)
    return b
    
    
    
def deplacement( balle, zone, v) :
    """
     translation de la balle  dans le canevas zone.
     vecteur de translation = (v[0],v[1]). 
     Si la balle sort du canevas, on la replace à la position du pointeur de souris
    """ 
    # liste (x0,y0,x1,y1) des coordonnées coin sup gauche et coin inf droit du rectangle
    # dans lequel s'inscrit le cercle rerpésentant la balle :
    x0,y0,x1,y1 = zone.coords(balle)
                                                            
    # on enregistre les dimensions de la balle :
    dx, dy = x1-x0, y1-y0
    
    # translation de la balle : 
    x0 += v[0]
    x1 += v[0]
    y0 += v[1]
    y1 += v[1]
    
    # si le coin supérieur gauche de la balle sort du canevas, on replace la balle au pointeur :
    if x0<0 or x0>largeur or y0<0 or y0>longueur : 
        x0 = maitre.winfo_pointerx() - maitre.winfo_rootx()
        x1 = x0 + dx
        y0 =  maitre.winfo_pointery() - maitre.winfo_rooty()
        y1 = y0 + dy
        
    # on met à jour les coordonnées de la balle :
    zone.coords(balle, x0,y0,x1,y1)
    
 
 
jeu = [] # liste de balles
n = 100 # nombre de balles


# boucle de création de n balles :
for _ in range(n) :
    jeu.append( balle(largeur/2, longueur/2, randint(10,50) , unecouleur(), terrain) )
 
 
# boucle de création de n vitesses :
vitesse = [] # liste des vitesses des balles
for _ in range(n) :
    vitesse.append( (randint(-20,20), randint(-20,20)) )



while(True) :  # boucle de mises à jour affichage du canevas
    
    for j in range(n) :  # boucle de translation de toutes les balles
        deplacement(jeu[j], terrain, vitesse[j]) 
        
    maitre.after(1,maitre.update()) # réaffichage de la fenêtre 
    


maitre.mainloop()

Balles rebondissantes.

Reprendre la situation de l'exercice précédent. Mais, maintenant, lorsque une balle atteint un bord elle repart en sens inverse (au lieu de se replacer au centre ou sous le pointeur de souris).

Une solution possible.

Prenez le temps de tester, de modifier, de comprendre...


import tkinter as tk
from random import randint

maitre = tk.Tk() # fenêtre conteneur principal

largeur = 700 # largeur du canevas contenant les balles
longueur = 700 # hauteur du canevas contenant les balles
terrain = tk.Canvas(maitre,width=largeur, height=longueur) # canevas dans lequel les balles se déplacent
terrain.pack()


def unecouleur() :
    """
        Une couleur au hasard
    """
    r =  randint(0,255)
    v =  randint(0,255)
    b =  randint(0,255)
    
    return "#%02x%02x%02x" % (r,v,b)
    
    

def balle(centrex, centrey, diagonale , couleur, zone) :
    """
    Entrées :
        centrex, centrey :  coordonnées du centre du cercle représentant la balle.
        diagonale :  diagonale du rectangle dans lequel le cercle représentant la balle est inscrit.
        couleur : couleur de la balle.
        zone : canvas dans lequel on crée la balle.
    """
    b = zone.create_oval(centrex-diagonale/2,centrey-diagonale/2, centrex+diagonale/2,centrey+diagonale/2, fill=couleur)
    return b
    
    
   
    
    
def deplacement(balle, zone, j) :
    """
     translation de la balle d'indice j  dans le canevas zone.
     Si la balle touche un bord du canevas, elle repart en sens inverse.
    """ 
    # liste (x0,y0,x1,y1) des coordonnées coin sup gauche et coin inf droit du rectangle
    # dans lequel s'inscrit le cercle rerpésentant la balle :
    x0,y0,x1,y1 = zone.coords(balle)
                                                            
    # on enregistre les dimensions de la balle :
    dx, dy = x1-x0, y1-y0
    
    # translation de la balle : 
    x0 += vitesse[j][0]
    x1 += vitesse[j][0]
    y0 += vitesse[j][1]
    y1 += vitesse[j][1]
    
    # si le coin supérieur gauche de la balle sort du canevas,  la balle part en sens inverse   :
    if x0<0 or x0>largeur or y0<0 or y0>longueur : 
        vitesse[j][0] = - vitesse[j][0]
        vitesse[j][1] = - vitesse[j][1]
        x0 += vitesse[j][0]
        x1 += vitesse[j][0]
        y0 += vitesse[j][1]
        y1 += vitesse[j][1]
        
    # on met à jour les coordonnées de la balle :
    zone.coords(balle, x0,y0,x1,y1)
    
 
 
jeu = [] # liste de balles
n = 100 # nombre de balles


# boucle de création de n balles :
for _ in range(n) :
    jeu.append( balle(largeur/2, longueur/2, randint(10,50) , unecouleur(), terrain) )
 
 
# boucle de création de n vitesses :
vitesse = [] # liste des vitesses des balles
for _ in range(n) :
    vitesse.append( [randint(-20,20), randint(-20,20)] )



while(True) :  # boucle de mises à jour affichage du canevas
    
    for j in range(n) :  # boucle de translation de toutes les balles
        deplacement(jeu[j], terrain,   j ) 
       
    maitre.after(10,  maitre.update())
     


maitre.mainloop()

Balles rebondissantes (2).

Lorsque une balle atteint un bord, elle rebondit sur ce bord. Et lorsqu'une balle en rencontre une autre, elle repart avec une vitesse opposée à celle qu'elle avait avant l'impact.

Une solution possible.

Prenez le temps de tester, de modifier, de comprendre...

Une première solution

Dans cette solution, on utilise la poo, plus adaptée.


import tkinter as tk
from random import randint


def valobs(x) :
    """
        Entrée : un nombre x
        Sortie : sa valeur absolue
    """
    if x >= 0 : return x
    else : return -x





class BalleAuHasard() :
    """
        création d'une balle,
        rayon au hasard,
        couleur au hasard,
        position initiale au hasard, 
        vitesse au hasard.
    """
    
    def __init__(self, terrain, largeur, longueur) :
        self.canevas = terrain # canvas dans lequel on dessine la balle
        self.largeur = largeur # largeur du canvas
        self.longueur = longueur # hauteur du canvas
        self.rayon = randint(5, 8) # rayon de la balle
        self.centreHasard() 
        self.vitesseHasard()
        self.couleur = "#%02x%02x%02x" % (randint(0,255),randint(0,255),randint(0,255))
        self.cree_balle()
        self.deplace() # déplacement de la balle à la création pour création xold, yold
        
        
        
        
    def anciennescoord(self) :
        """
            coordonnées de la position précédente de la balle
        """
        self.xold = self.xcentre
        self.yold = self.ycentre
        
        
    def centreHasard(self) :
        """
            choix d'une position (initiale) de la balle au hasard
        """
        self.xcentre = randint( 2*self.rayon, self.largeur-2*self.rayon)
        self.ycentre = randint( 2*self.rayon, self.longueur-2*self.rayon)
        
        
        
    def vitesseHasard(self) :
        """
            vitesse de la balle, choisie au hasard
            avec une pondération inverse au rayon
        """
        vitesseMin = 10
        vitesseMax = 20
        self.xvitesse = (1/self.rayon) * randint(vitesseMin, vitesseMax)
        self.yvitesse = (1/self.rayon) * randint(vitesseMin, vitesseMax)
        
        
    def rectangleBalle(self) :
        """
         le disque définissant la balle est inscrit dans un rectangle.
         le coin supérieur gauche de ce rectangle a pour coordonnées (x1,y1)
         le coin inférieur droit de ce rectangle a pour coordonnées (x2,y2)
        """
        dd = self.rayon * 2**0.5
        self.x1 = self.xcentre - dd
        self.y1 = self.ycentre - dd
        self.x2 = self.xcentre + dd
        self.y2 = self.ycentre + dd
        
        
    def cree_balle(self) :
        """
            création de l'objet graphique dans le canevas
        """
        self.rectangleBalle()
        self.balle = self.canevas.create_oval(self.x1,self.y1,self.x2,self.y2, fill = self.couleur, outline = self.couleur)
        
        
    def metAJourCoords(self) :
        """
            mise à jour coordonnées de la balle graphique
        """
        self.rectangleBalle()
        self.canevas.coords(self.balle, self.x1,self.y1,self.x2,self.y2)
        
        
    def deplace(self) : 
        """
            La balle avance à sa vitesse.
            Si elle tape un mur, la vitesse est changée en son opposé.
        """
        if (self.xcentre <= self.rayon) :
            if self.xvitesse < 0 : 
                self.xvitesse = -self.xvitesse 
                
        elif (self.xcentre >= self.largeur-self.rayon) :
            if self.xvitesse > 0 :
                self.xvitesse = -self.xvitesse 
                
        if (self.ycentre <= self.rayon) :
            if self.yvitesse < 0 :
                self.yvitesse = -self.yvitesse 
                
        elif (self.ycentre >= self.longueur-self.rayon) :
            if self.yvitesse > 0 :
                self.yvitesse = -self.yvitesse 
        
        self.anciennescoord()   
        self.xcentre += self.xvitesse 
        self.ycentre += self.yvitesse 
            
        self.metAJourCoords()   
        
        
    def distance(self, autreballe) :
        """
            distance entre la balle et autreballe.
        """
        d = (self.xcentre - autreballe.xcentre)**2
        d += (self.ycentre - autreballe.ycentre)**2
        return d**0.5
        
    def distanceold(self, autreballe) :
        """
            distance entre balle et autreballe à leurs positions précédentes
        """
        d = (self.xold - autreballe.xold)**2
        d += (self.yold - autreballe.yold)**2
        return d**0.5
        
        
    def superposition(self, autreballe) :
        """
            renvoie True si les balles se superposent en partie, False sinon
        """
        return (self.distance(autreballe) < (self.rayon + autreballe.rayon))
        
        
    def vitesseOpposee(self) :
        """
            change la vitesse de la balle en son opposé
        """
        self.xvitesse = -self.xvitesse 
        self.yvitesse = -self.yvitesse
        
        
    def rencontre(self, autreballe) :
        """
            Quand deux balles se percutent, chacune change sa vitesse  en la vitesse opposée.
        """
        # si deux balles se percutent
        if self.superposition(autreballe) :
            # et si  elles sont en phase de rapprochement :
            if self.distanceold(autreballe) > self.distance(autreballe) :
                # alors vitesses changées en leurs opposés
                self.vitesseOpposee()
                autreballe.vitesseOpposee()
            self.deplace() 
            autreballe.deplace()
    
        
        
        
class fenetreBalles :
    """
        Création fenêtre root, création des balles et fonction d'animation
    """
    
    def __init__(self, nombreBalle, largeur, hauteur) :
        self.lesBalles = [] # liste qui contiendra les balles
        self.fenetreRacine = tk.Tk() # fenêtre root
        
        # création d'un canevas dans lequel on lancera les balles :
        self.canevas = tk.Canvas(self.fenetreRacine, width=largeur, height=hauteur,background='white')
        self.canevas.pack()
        
        # boucle de création des balles :
        for _ in range(nombreBalle) :
            balle  = BalleAuHasard(self.canevas, largeur, hauteur )
            self.lesBalles.append(balle)
            
        
        self.fenetreRacine.bind('<Key>', self.ferme) # pour fermer la fenêtre en appuyant sur une touche
        self.lancerAnimation()
        self.fenetreRacine.mainloop()
        
    def ferme(self, evenementDeclencheur):
        self.fenetreRacine.destroy()
        
            
    def lancerAnimation(self) :
        
        while True :
            
            for balle in self.lesBalles :
                # gestion des rencontres entre balles :
                for baballe in self.lesBalles :
                    if baballe != balle : balle.rencontre(baballe)
                # chaque balle avance :
                balle.deplace()
                
            self.fenetreRacine.after(1, self.fenetreRacine.update() )
        
    
        
if __name__ == "__main__" :    
    fenetreBalles(50,largeur=800, hauteur=600)  

La version ci-dessous est une variante de la première dans laquelle la fenêtre s'adpate à l'écran.


import tkinter as tk
from random import randint, random


def valobs(x) :
    """
        Entrée : un nombre x
        Sortie : sa valeur absolue
    """
    return x if (x>=0) else -x





class BalleAuHasard() :
    """
        création d'une balle,
        rayon au hasard,
        couleur au hasard,
        position initiale au hasard, 
        vitesse au hasard.
    """
    
    def __init__(self, terrain) :
        self.canevas = terrain # canvas dans lequel on dessine la balle
        self.longueur =   self.canevas.winfo_reqheight() #  hauteur du canvas
        self.largeur = self.canevas.winfo_reqwidth()  # largeur du canvas
        self.rayon = randint(5, 8) # rayon de la balle
        self.centreHasard() 
        self.vitesseHasard()
        self.couleur = "#%02x%02x%02x" % (randint(0,255),randint(0,255),randint(0,255))
        self.cree_balle()
        self.deplace() # déplacement de la balle à la création pour création xold, yold
        
        
        
        
    def anciennescoord(self) :
        """
            coordonnées de la position précédente de la balle
        """
        self.xold = self.xcentre
        self.yold = self.ycentre
        
        
    def centreHasard(self) :
        """
            choix d'une position (initiale) de la balle au hasard
        """
        self.xcentre = randint( 2*self.rayon, self.largeur-2*self.rayon)
        self.ycentre = randint( 2*self.rayon, self.longueur-2*self.rayon)
        
        
        
    def vitesseHasard(self) :
        """
            vitesse de la balle, choisie au hasard
            
        """
        vitesseMin = 1
        vitesseMax = 5
        self.xvitesse = randint(vitesseMin, vitesseMax)
        self.yvitesse = randint(vitesseMin, vitesseMax)
        
        
    def rectangleBalle(self) :
        """
         le disque définissant la balle est inscrit dans un rectangle.
         le coin supérieur gauche de ce rectangle a pour coordonnées (x1,y1)
         le coin inférieur droit de ce rectangle a pour coordonnées (x2,y2)
        """
        dd = self.rayon * 2**0.5
        self.x1 = self.xcentre - dd
        self.y1 = self.ycentre - dd
        self.x2 = self.xcentre + dd
        self.y2 = self.ycentre + dd
        
        
    def cree_balle(self) :
        """
            création de l'objet graphique dans le canevas
        """
        self.rectangleBalle()
        self.balle = self.canevas.create_oval(self.x1,self.y1,self.x2,self.y2, fill = self.couleur, outline = self.couleur)
        
        
    def metAJourCoords(self) :
        """
            mise à jour coordonnées de la balle graphique
        """
        self.rectangleBalle()
        self.canevas.coords(self.balle, self.x1,self.y1,self.x2,self.y2)
        
        
    def deplace(self, dvx=0, dvy=0) : 
        """
            La balle avance à sa vitesse.
            Si elle tape un mur, elle rebondit.
            dvx, dvy servent à pouvoir ajouter un peu de hasard aux vitesses 
        """
        if (self.xcentre <= self.rayon) :
            if self.xvitesse < 0 : 
                self.xvitesse = -self.xvitesse 
                
        elif (self.xcentre >= self.largeur-self.rayon) :
            if self.xvitesse > 0 :
                self.xvitesse = -self.xvitesse 
                
        if (self.ycentre <= self.rayon) :
            if self.yvitesse < 0 :
                self.yvitesse = -self.yvitesse 
                
        elif (self.ycentre >= self.longueur-self.rayon) :
            if self.yvitesse > 0 :
                self.yvitesse = -self.yvitesse 
        
        self.anciennescoord()   
        
        self.xcentre += self.xvitesse + dvx
        self.ycentre += self.yvitesse + dvy 
            
        self.metAJourCoords()   
        
        
    def distance(self, autreballe) :
        """
            distance entre la balle et autreballe.
        """
        d = (self.xcentre - autreballe.xcentre)**2
        d += (self.ycentre - autreballe.ycentre)**2
        return d**0.5
        
    def distanceold(self, autreballe) :
        """
            distance entre balle et autreballe à leurs positions précédentes
        """
        d = (self.xold - autreballe.xold)**2
        d += (self.yold - autreballe.yold)**2
        return d**0.5
        
        
    def superposition(self, autreballe) :
        """
            renvoie True si les balles se superposent en partie, False sinon
        """
        return (self.distance(autreballe) < (self.rayon + autreballe.rayon))
        
        
    def vitesseOpposee(self) :
        """
            change la vitesse de la balle en son opposé
        """
        self.xvitesse = -self.xvitesse 
        self.yvitesse = -self.yvitesse
        
        
    def rencontre(self, autreballe) :
        """
            Quand deux balles se percutent, chacune change sa vitesse  en la vitesse opposée.
        """
        # si deux balles se percutent
        if self.superposition(autreballe) :
            # et si  elles sont en phase de rapprochement :
            if self.distanceold(autreballe) > self.distance(autreballe) :
                # alors vitesses changées en leurs opposés
                self.vitesseOpposee()
                autreballe.vitesseOpposee()
            #self.deplace(dvx=random()-0.5,dvy=random()-0.5) 
            autreballe.deplace(dvx=random()-0.5,dvy=random()-0.5)   
    
        
        
        
class fenetreBalles :
    """
        Création fenêtre root, création des balles et fonction d'animation
    """
    
    def __init__(self, nombreBalle) :
        self.lesBalles = [] # liste qui contiendra les balles
        self.fenetreRacine = tk.Tk() # fenêtre root
        
        
        # adaptation à l'écran :
        ll = self.fenetreRacine.winfo_screenwidth() 
        hh = self.fenetreRacine.winfo_screenheight()
        
        # création d'un canevas dans lequel on lancera les balles :
        self.canevas = tk.Canvas(self.fenetreRacine, width=ll, height=hh,background='white')
        self.canevas.pack()
        
        # boucle de création des balles :
        for _ in range(nombreBalle) :
            balle  = BalleAuHasard(self.canevas)
            self.lesBalles.append(balle)
        
        self.fenetreRacine.bind('<Key>', self.ferme)
        self.lancerAnimation()
        self.fenetreRacine.mainloop()
        
    def ferme(self, event):
        self.fenetreRacine.destroy()
        
            
    def lancerAnimation(self) :
        for balle in self.lesBalles :
            # gestion des rencontres entre balles :
            for baballe in self.lesBalles :
                if baballe != balle : balle.rencontre(baballe)
            # chaque balle avance :
            balle.deplace()
        self.fenetreRacine.after(1, self.lancerAnimation)
        
     
     
    
        
if __name__ == "__main__" :
    fenetreBalles(100)  

Autre version

Dans cette dernière version, on reprend la structure choisie dans les exercices précédents (c'est à dire : programmation non poo en utilisant des listes).


import tkinter as tk
from random import randint

maitre = tk.Tk() # fenêtre conteneur principal

largeur = 700 # largeur du canevas contenant les balles
longueur = 700 # hauteur du canevas contenant les balles
terrain = tk.Canvas(maitre,width=largeur, height=longueur) # canevas dans lequel les balles se déplacent
terrain.pack()

def hexadecimal(n):
    """ 
		Fonction qui renvoie l'écriture héxadécimale 
        de l'entier n compris entre 0 et 255.
    """
    chiffres = list('0123456789abcdef')
    if 0 <= n <= 255:
        return chiffres[n//16]+chiffres[n%16]
    else:
        return '00'

def unecouleur():
    """Fonction qui détermine une couleur au hasard au format hexadécimal."""
    r = hexadecimal(randint(0, 255))
    v = hexadecimal(randint(0, 255))
    b = hexadecimal(randint(0, 255))
    return '#'+r+v+b
    
    
    
    

def balle(centrex, centrey, diagonale , couleur, zone) :
    """
    Entrées :
        centrex, centrey :  coordonnées du centre du cercle représentant la balle.
        diagonale :  diagonale du rectangle dans lequel le cercle représentant la balle est inscrit.
        couleur : couleur de la balle.
        zone : canvas dans lequel on crée la balle.
    """
    b = zone.create_oval(centrex-diagonale/2,centrey-diagonale/2, centrex+diagonale/2,centrey+diagonale/2, fill=couleur)
    return b
    
    
    
     
    

    
    
def deplacement(balle, zone, v) :
    """
     translation de la balle  dans le canevas zone.
     vecteur de translation = (v[0],v[1]). 
     Si la balle sort du canevas, on la replace au centre.
    """ 
    # liste (x0,y0,x1,y1) des coordonnées coin sup gauche et coin inf droit du rectangle
    # dans lequel s'inscrit le cercle représentant la balle :
    x0,y0,x1,y1 = zone.coords(balle)
                                                            
    # on enregistre les dimensions de la balle :
    dx, dy = x1-x0, y1-y0
    
    # translation de la balle : 
    x0 += v[0]
    x1 += v[0]
    y0 += v[1]
    y1 += v[1]
    
    # si le coin supérieur gauche de la balle sort du canevas, on replace la balle au centre :
    if x0 < 0 or x0 > largeur or y0 < 0 or y0 > longueur : 
        x0  = largeur/2 
        x1 = x0 + dx
        y0 = longueur/2
        y1 = y0 + dy
        
    # on met à jour les coordonnées de la balle :
    zone.coords(balle, x0,y0,x1,y1)
    
 
 
jeu = [] # liste de balles
n = 100 # nombre de balles


# boucle de création de n balles :
for _ in range(n) :
    jeu.append( balle(largeur/2, longueur/2, randint(10,50) , unecouleur(), terrain) )
 
 
# boucle de création de n vitesses :
vitesse = [] # liste des vitesses des balles
for _ in range(n) :
    vitesse.append( (randint(-20,20), randint(-20,20)) )



while(True) :  # boucle de mises à jour affichage du canevas
    
    for j in range(n) :  # boucle de translation de toutes les balles
        deplacement(jeu[j], terrain, vitesse[j]) 
        
    maitre.after(100,maitre.update()) # réaffichage de la fenêtre après un dixième de seconde
     


maitre.mainloop()