Animer un serpent

Contenu :

Boucles, Fonctions, Structure conditionnelles

Affichage du serpent

Dans un premier temps nous allons essayer d'afficher 3 LEDs contiguës représentant notre serpent à l'instant initial du jeu. Pour repérer un pixel dans la matrice il sera plus aisé de raisonné en coordonnées (x,y) où x et y prendrons les valeurs entières de 0 à 7 en fonction de la position du point dans le système de coordonné suivant :

L'indice des LED i, utilisé pour contrôler les LEDs, peut prendre des valeurs entières  : 0 (première LED en haut à gauche) à 63 (dernière LED en bas à droite). Lorsque nous identifierons une LED en position (x,y) nous devrons connaître sa position i dans le ruban. Par exemple la pour la première LED verte de la figure en position (2,3) correspond à la 26ème LED : i = 26.

Question : Comment trouver la fonction mathématique i = f(x,y) reliant l'indice et les coordonnées x et y ?

Réponse :

On multiplie la position en y par le nombre de LED par ligne (8) et on ajoute la position x :

i = 8 * y + x

Nous allons donc reprendre cette formule pour afficher le serpent comme sur la figure en position (2,3) (2,4) (2,5):

1
"""
2
Affichage d'un serpent vert
3
4
Date : 29/10/2017
5
6
"""
7
8
from vrtneopixel import *
9
import time
10
11
# Variables de configuration de l'ecran
12
LED_COUNT      = 64
13
LED_PIN        = 18
14
LED_FREQ_HZ    = 800000
15
LED_DMA        = 5
16
LED_BRIGHTNESS = 8
17
LED_INVERT     = False
18
# Creation d'un element permettant de piloter
19
# l'ecran de LED
20
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
21
22
# initialisation de l'ecran
23
strip.begin()
24
25
leds = 64 * [Color(0,0,0)]
26
27
# Affichage des pixels verts en (2,3) (2,4) et (2,5)
28
leds[8*3 + 2] = Color(0,255,0)
29
leds[8*4 + 2] = Color(0,255,0)
30
leds[8*5 + 2] = Color(0,255,0)
31
32
# Affichage de l'écran
33
for i in range(64):
34
    strip.setPixelColor(i,leds[i])
35
strip.show()
36
37
38
#Attendre 3s
39
time.sleep(3)
40
41
leds = 64 * [Color(0,0,0)]
42
43
# Affichage de l'écran
44
for i in range(64):
45
    strip.setPixelColor(i,leds[i])
46
strip.show()
47
48
49
50

Pour rendre plus modulaire ce programme nous allons ajouter une liste serpent qui contient les coordonnées du serpent :

1
serpent = [(2,3),(2,4),(2,5)]

C'est en fait une liste de tuples. Les tuples sont aussi des liste mais non modifiables. Vous pouvez essayer ça dans le prompt python :

Pour la liste de tuple serpent, pour accédé au premier élément du premier tuple il suffit comme dans une liste de taper :

Les lignes 28, 29 et 30 du programme pourront être remplacé par la boucle for suivante :

1
for (x,y) in serpent:
2
    leds[8*y + x] = Color(0,255,0)

Dans cette boucle for les variable x et y vont être affectées aux valeurs de chaque tuple contenu dans la liste serpent. Pour vous en convaincre vous pouvez tester les instructions suivantes dans le shell de python :

Cette dernière forme est bien plus agréable. Le nouveau code python ainsi produit :

1
"""
2
Affichage d'un serpent vert
3
4
Date : 29/10/2017
5
6
"""
7
8
from vrtneopixel import *
9
import time
10
11
# liste de tuples représentant les coordonnées du serpent sur l'écran
12
serpent = [(2,3),(2,4),(2,5)]
13
14
# Variables de configuration de l'ecran
15
LED_COUNT      = 64
16
LED_PIN        = 18
17
LED_FREQ_HZ    = 800000
18
LED_DMA        = 5
19
LED_BRIGHTNESS = 8
20
LED_INVERT     = False
21
# Creation d'un element permettant de piloter
22
# l'ecran de LED
23
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
24
25
# initialisation de l'ecran
26
strip.begin()
27
28
leds = 64 * [Color(0,0,0)]
29
30
# Affichage des pixels verts en (2,3) (2,4) et (2,5
31
for (x,y) in serpent:
32
    leds[8*y + x] = Color(0,255,0)
33
34
# Affichage de l'écran
35
for i in range(64):
36
    strip.setPixelColor(i,leds[i])
37
strip.show()
38
39
40
#Attendre 3s
41
time.sleep(3)
42
43
leds = 64 * [Color(0,0,0)]
44
45
# Affichage de l'écran
46
for i in range(64):
47
    strip.setPixelColor(i,leds[i])
48
strip.show()
49
50
51
52

Faire avancer le serpent

A chaque itération du jeu le serpent avancera d'une case. Pour cela à fois il est nécessaire de répéter les étapes suivantes :

  • Effacer le serpent

  • calculer les nouvelles coordonnées du serpent

  • afficher le nouvel écran

Il va donc être plus judicieux de réaliser des fonctions pour améliorer la clarté du code en évitant les copier/collé à l'infini !

On pourra par exemple ajouter ces quelques lignes en début de programme juste après les import :

1
# Cette fonction affecte la couleur noir à toutes les LEDs
2
# leds : liste des Couleurs
3
def clearScreen(leds):
4
    for i in range(64):
5
        leds[i]=Color(0,0,0)
6
7
# Mise a jour de l'affichage
8
# leds : liste des Couleurs
9
# strip : objet controlant le ruban (matrice de LED Ws2812b)
10
def displayScreen(strip,leds):
11
    for i in range(64):
12
        strip.setPixelColor(i,leds[i])
13
    strip.show()
14

On constate ici que pour définir une fonction en python on utilise donc le mot clé def suivi du nom de la fonction. La fonction peut prendre ou non des paramètres. Dans l'exemple ci-dessus les deux fonctions modifient l'état des paramètres d'entrée. On parle alors de passage par référence. Après l'appel la variables passées en paramètre seront modifiées.

On peut tester ces exemples simples dans le shell :

Pour le passage par valeur, où les paramètres ne sont pas modifié par la fonction, on peut tester les instructions suivantes :

Dans l'exemple suivant le passage se fait par référence : la liste est modifiée lors de l'appel de la fonction :

L'exemple suivant montre bien l'impossibilité de changer les variables passées en paramètre pour certains types dits non-modifiables (ici entier) :

Fondamental

Le choix du type de passage, par valeur ou par référence est automatiquement déterminé par la nature modifiables ou non des types des variables passées en paramètres.

  • Les listes sont des éléments modifiables, on peut modifier leur contenu dans la fonction.

  • Les entiers, chaînes de caractères tuples sont eux non-modifiables. Seule leur valeur est transmise à la fonction.

Complémentreturn

Un moyen de retourner la valeur d'une fonction est de spécifier dans la fonction que la valeur d'une variable est retournée :

Intégrer les fonctions dans le script et définir le programme principal

On peut réorganiser le programme ainsi :

1
"""
2
Affichage d'un serpent vert
3
4
Date : 29/10/2017
5
6
"""
7
8
from vrtneopixel import *
9
import time
10
11
# déclaration des fonctions
12
13
# Cette fonction affecte la couleur noir à toutes les LEDs
14
# leds : liste des Couleurs
15
def clearScreen(leds):
16
    for i in range(64):
17
        leds[i]=Color(0,0,0)
18
19
# Mise a jour de l'affichage
20
# leds : liste des Couleurs
21
# strip : objet controlant le ruban (matrice de LED Ws2812b)
22
def displayScreen(strip,leds):
23
    for i in range(64):
24
        strip.setPixelColor(i,leds[i])
25
    strip.show()
26
27
# Variables de configuration de l'ecran
28
LED_COUNT      = 64
29
LED_PIN        = 18
30
LED_FREQ_HZ    = 800000
31
LED_DMA        = 5
32
LED_BRIGHTNESS = 8
33
LED_INVERT     = False
34
35
if __name__ == '__main__':
36
    # liste de tuples représentant les coordonnées du serpent sur l'écran
37
    serpent = [(2,3),(2,4),(2,5)]
38
    # Les 64 LEDs en Noir
39
    leds = 64 * [Color(0,0,0)]
40
    
41
    # Creation d'un element permettant de piloter
42
    # l'ecran de LED
43
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
44
    
45
    # initialisation de l'ecran
46
    strip.begin()
47
    
48
    # Affichage des pixels verts en (2,3) (2,4) et (2,5
49
    for (x,y) in serpent:
50
        leds[8*y + x] = Color(0,255,0)
51
52
    # Affichage de l'écran
53
    displayScreen(strip,leds)
54
55
    #Attendre 3s
56
    time.sleep(3)
57
58
    # Effacement de l'écran
59
    clearScreen(leds)
60
    displayScreen(strip,leds)
61

On remarque la déclaration des deux fonctions juste après les importation de modules. Dans ce programme en ligne 35 est défini le programme principal : Programme qui est appelé en premier lors de l'exécution du script. On remarque l'utilisation de la structure conditionnelle if variable == valeur : Les deux points annoncent un nouveau bloc (indentation) qui ne sera exécuté que si le test est vérifié (le test renvoie une variable booléenne True ou False). D'autres opérateurs de comparaison existent : <,>,<=,>= et !=. Quelques exemples à tester dans le prompt :

Borderline : Gérer l'absence de frontière

Lorsque le serpent atteint le bord du cadre et avance il se retrouve à l’opposé :

Pour avancer on peut tester le programme suivant :

1
"""
2
Affichage d'un serpent vert
3
4
Date : 29/10/2017
5
6
"""
7
8
from vrtneopixel import *
9
import time
10
11
# déclaration des fonctions
12
# Afficher le serpent
13
def addSnake(snake,leds):
14
    for (x,y) in snake:
15
        leds[x+y * 8] = Color(0,255,0)
16
17
# Extinction du serpent
18
def removeSnake(snake, leds):
19
    for (x,y) in snake:
20
        leds[x+y * 8] = Color(0,0,0)
21
22
# Nouvelles coordonnées du serpent
23
# Pour monter :
24
def snakeUp(snake):
25
    snake.pop()             # Supprime le bout de la queue
26
    new_x, new_y = snake[0] # 
27
    if new_y == 0:
28
        new_y = 7
29
    else:
30
        new_y = new_y - 1         # On retranche 1 à la coordonnee en y
31
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée 
32
    
33
# Cette fonction affecte la couleur noir à toutes les LEDs
34
# leds : liste des Couleurs
35
def clearScreen(leds):
36
    for i in range(64):
37
        leds[i]=Color(0,0,0)
38
39
# Mise a jour de l'affichage
40
# leds : liste des Couleurs
41
# strip : objet controlant le ruban (matrice de LED Ws2812b)
42
def displayScreen(strip,leds):
43
    for i in range(64):
44
        strip.setPixelColor(i,leds[i])
45
    strip.show()
46
47
# Variables de configuration de l'ecran
48
LED_COUNT      = 64
49
LED_PIN        = 18
50
LED_FREQ_HZ    = 800000
51
LED_DMA        = 5
52
LED_BRIGHTNESS = 8
53
LED_INVERT     = False
54
55
if __name__ == '__main__':
56
    # liste de tuples représentant les coordonnées du serpent sur l'écran
57
    serpent = [(2,3),(2,4),(2,5)]
58
    # Les 64 LEDs en Noir
59
    leds = 64 * [Color(0,0,0)]
60
    
61
    # Creation d'un element permettant de piloter
62
    # l'ecran de LED
63
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
64
    
65
    # initialisation de l'ecran
66
    strip.begin()
67
    
68
    maxi = 64 # Nombre d'iteration
69
70
    while maxi > 0:
71
        # Ajout du serpent vert 
72
        addSnake(serpent,leds)
73
        # Affichage de l'écran
74
        displayScreen(strip,leds)
75
        
76
        #Attendre 100ms
77
        time.sleep(0.1)
78
        
79
        # Effacement du serpent
80
        removeSnake(serpent,leds)
81
        # Deplacer le serpent
82
        snakeUp(serpent)
83
        # décompte de la boucle
84
        maxi = maxi -1
85
    
86
    #   EFFACER L'ECRAN
87
    clearScreen(leds)
88
    displayScreen(strip,leds)
89

Trois fonctions ont été ajoutées :

  • addSnake() : Lecture des coordonnées du serpent et modification des LED correspondantes en vert dans la liste des LED.

  • removeSnake() : Lecture des coordonnées du serpent et modification des LED correspondantes en noir dans la liste des LED.

  • snakeUP() : Mise a jour des coordonnées du serpent lors d'un déplacement d'un pixel. La méthode pop() est utilisée ici sur une liste permet de suprimer le dernier élément, correspondant au bout de la queue. Le reste du programme calcul à partir du premier élément la nouvelle tête. Pour monter il faut enlever un à la coordonnée y : new_y = new_y - 1 si on est au bord (new_y == 0) alors new_y = 7.

Tourner !

Pour faire tourner le serpent c'est un peu la même chose sauf que ça se passe sur l'axe des x. Comme pour le déplacement vertical il faut également gérer la situation où l'on arrive sur le bord  (x = 7) alors on se retrouve de l'autre côté (x=0). Pour tourner la queue est aussi supprimée et la nouvelle tête se trouve juste à droite de l'ancienne, il faudra ajouter 1 à la position en x de l'ancienne tête.

1
# Pour tourner a droite
2
def snakeRight(snake):
3
    snake.pop()             # Supprime le bout de la queue
4
    new_x, new_y = snake[0] # 
5
    if new_x == 7:          # Si on est au bord
6
        new_x = 0           # 
7
    else:
8
        new_x = new_x + 1         # On ajoute 1 à la coordonnee en x
9
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée

De la même manière il ne reste plus qu'a compléter le programme des fonctions snakeLeft(snake) pour tourner à gauche et snakeDown(snake) pour descendre.

Déplacement aléatoire du serpent

En ajoutant cette fonction de déplacement aléatoire :

1
# Changer la direction aléatoirement en prenant
2
# Gare aux directions interdites :
3
# Quant on avance 0 on ne peut pas reculer 1
4
# etc ...
5
def changeDirection(direction):
6
    unauthorized = [1,0,3,2]
7
    while True :
8
        newDirection = random.randint(0,3)
9
        if newDirection != unauthorized[direction]:
10
            return newDirection

Et en modifiant légèrement le main :

1
if __name__ == '__main__':
2
    # liste de tuples représentant les coordonnées du serpent sur l'écran
3
    serpent = [(2,3),(2,4),(2,5)]
4
    # Les 64 LEDs en Noir
5
    leds = 64 * [Color(0,0,0)]
6
    
7
    # Creation d'un element permettant de piloter
8
    # l'ecran de LED
9
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
10
    
11
    # initialisation de l'ecran
12
    strip.begin()
13
    
14
    maxi = 99 # Nombre d'iteration
15
    # Direction du serpent 0 -> haut, 1 -> Bas, 2 -> Droite, 3 -> gauche
16
    direction = 0
17
    
18
    while maxi > 0:
19
        # Ajout du serpent vert 
20
        addSnake(serpent,leds)
21
        # Affichage de l'écran
22
        displayScreen(strip,leds)
23
        
24
        #Attendre 100ms
25
        time.sleep(0.1)
26
        
27
        # Effacement du serpent
28
        removeSnake(serpent,leds)
29
        # Deplacer le serpent
30
        if maxi % 5 == 0:
31
            direction = changeDirection(direction)
32
        if direction == 0 :
33
            snakeUp(serpent)
34
        elif direction == 1 :
35
            snakeDown(serpent)
36
        elif direction == 2 :
37
            snakeRight(serpent)
38
        else :
39
            snakeLeft(serpent)
40
        # décompte de la boucle
41
        maxi = maxi -1
42
    
43
    #   EFFACER L'ECRAN
44
    clearScreen(leds)
45
    displayScreen(strip,leds)
46

Le serpent devrait s'animer !