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):
"""Affichage d'un serpent vertDate : 29/10/2017"""from vrtneopixel import *
import time
# Variables de configuration de l'ecranLED_COUNT = 64
LED_PIN = 18
LED_FREQ_HZ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 8
LED_INVERT = False
# Creation d'un element permettant de piloter# l'ecran de LEDstrip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecranstrip.begin()
leds = 64 * [Color(0,0,0)]
# Affichage des pixels verts en (2,3) (2,4) et (2,5)leds[8*3 + 2] = Color(0,255,0)
leds[8*4 + 2] = Color(0,255,0)
leds[8*5 + 2] = Color(0,255,0)
# Affichage de l'écranfor i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
#Attendre 3stime.sleep(3)
leds = 64 * [Color(0,0,0)]
# Affichage de l'écranfor i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
Pour rendre plus modulaire ce programme nous allons ajouter une liste serpent qui contient les coordonnées du serpent :
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 :
for (x,y) in serpent:
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 :
"""Affichage d'un serpent vertDate : 29/10/2017"""from vrtneopixel import *
import time
# liste de tuples représentant les coordonnées du serpent sur l'écranserpent = [(2,3),(2,4),(2,5)]
# Variables de configuration de l'ecranLED_COUNT = 64
LED_PIN = 18
LED_FREQ_HZ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 8
LED_INVERT = False
# Creation d'un element permettant de piloter# l'ecran de LEDstrip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecranstrip.begin()
leds = 64 * [Color(0,0,0)]
# Affichage des pixels verts en (2,3) (2,4) et (2,5for (x,y) in serpent:
leds[8*y + x] = Color(0,255,0)
# Affichage de l'écranfor i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
#Attendre 3stime.sleep(3)
leds = 64 * [Color(0,0,0)]
# Affichage de l'écranfor i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
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 :
# Cette fonction affecte la couleur noir à toutes les LEDs# leds : liste des Couleursdef clearScreen(leds):
for i in range(64):
leds[i]=Color(0,0,0)
# Mise a jour de l'affichage# leds : liste des Couleurs# strip : objet controlant le ruban (matrice de LED Ws2812b)def displayScreen(strip,leds):
for i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
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ément : return
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 :
"""Affichage d'un serpent vertDate : 29/10/2017"""from vrtneopixel import *
import time
# déclaration des fonctions# Cette fonction affecte la couleur noir à toutes les LEDs# leds : liste des Couleursdef clearScreen(leds):
for i in range(64):
leds[i]=Color(0,0,0)
# Mise a jour de l'affichage# leds : liste des Couleurs# strip : objet controlant le ruban (matrice de LED Ws2812b)def displayScreen(strip,leds):
for i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
# Variables de configuration de l'ecranLED_COUNT = 64
LED_PIN = 18
LED_FREQ_HZ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 8
LED_INVERT = False
if __name__ == '__main__':
# liste de tuples représentant les coordonnées du serpent sur l'écranserpent = [(2,3),(2,4),(2,5)]
# Les 64 LEDs en Noirleds = 64 * [Color(0,0,0)]
# Creation d'un element permettant de piloter # l'ecran de LEDstrip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecranstrip.begin()
# Affichage des pixels verts en (2,3) (2,4) et (2,5for (x,y) in serpent:
leds[8*y + x] = Color(0,255,0)
# Affichage de l'écrandisplayScreen(strip,leds)
#Attendre 3stime.sleep(3)
# Effacement de l'écranclearScreen(leds)
displayScreen(strip,leds)
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 :
"""Affichage d'un serpent vertDate : 29/10/2017"""from vrtneopixel import *
import time
# déclaration des fonctions# Afficher le serpentdef addSnake(snake,leds):
for (x,y) in snake:
leds[x+y * 8] = Color(0,255,0)
# Extinction du serpentdef removeSnake(snake, leds):
for (x,y) in snake:
leds[x+y * 8] = Color(0,0,0)
# Nouvelles coordonnées du serpent# Pour monter :def snakeUp(snake):
snake.pop() # Supprime le bout de la queue
new_x, new_y = snake[0] #
if new_y == 0:
new_y = 7
else:new_y = new_y - 1 # On retranche 1 à la coordonnee en y
snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
# Cette fonction affecte la couleur noir à toutes les LEDs# leds : liste des Couleursdef clearScreen(leds):
for i in range(64):
leds[i]=Color(0,0,0)
# Mise a jour de l'affichage# leds : liste des Couleurs# strip : objet controlant le ruban (matrice de LED Ws2812b)def displayScreen(strip,leds):
for i in range(64):
strip.setPixelColor(i,leds[i])
strip.show()
# Variables de configuration de l'ecranLED_COUNT = 64
LED_PIN = 18
LED_FREQ_HZ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 8
LED_INVERT = False
if __name__ == '__main__':
# liste de tuples représentant les coordonnées du serpent sur l'écranserpent = [(2,3),(2,4),(2,5)]
# Les 64 LEDs en Noirleds = 64 * [Color(0,0,0)]
# Creation d'un element permettant de piloter # l'ecran de LEDstrip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecranstrip.begin()
maxi = 64 # Nombre d'iteration
while maxi > 0:
# Ajout du serpent vert addSnake(serpent,leds)
# Affichage de l'écrandisplayScreen(strip,leds)
#Attendre 100mstime.sleep(0.1)
# Effacement du serpentremoveSnake(serpent,leds)
# Deplacer le serpentsnakeUp(serpent)
# décompte de la bouclemaxi = maxi -1
# EFFACER L'ECRANclearScreen(leds)
displayScreen(strip,leds)
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.
# Pour tourner a droitedef snakeRight(snake):
snake.pop() # Supprime le bout de la queue
new_x, new_y = snake[0] #
if new_x == 7: # Si on est au bord
new_x = 0 #
else:new_x = new_x + 1 # On ajoute 1 à la coordonnee en x
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 :
# Changer la direction aléatoirement en prenant# Gare aux directions interdites :# Quant on avance 0 on ne peut pas reculer 1# etc ...def changeDirection(direction):
unauthorized = [1,0,3,2]
while True :
newDirection = random.randint(0,3)
if newDirection != unauthorized[direction]:
return newDirection
Et en modifiant légèrement le main :
if __name__ == '__main__':
# liste de tuples représentant les coordonnées du serpent sur l'écranserpent = [(2,3),(2,4),(2,5)]
# Les 64 LEDs en Noirleds = 64 * [Color(0,0,0)]
# Creation d'un element permettant de piloter # l'ecran de LEDstrip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecranstrip.begin()
maxi = 99 # Nombre d'iteration
# Direction du serpent 0 -> haut, 1 -> Bas, 2 -> Droite, 3 -> gauchedirection = 0
while maxi > 0:
# Ajout du serpent vert addSnake(serpent,leds)
# Affichage de l'écrandisplayScreen(strip,leds)
#Attendre 100mstime.sleep(0.1)
# Effacement du serpentremoveSnake(serpent,leds)
# Deplacer le serpentif maxi % 5 == 0:
direction = changeDirection(direction)
if direction == 0 :
snakeUp(serpent)
elif direction == 1 :
snakeDown(serpent)
elif direction == 2 :
snakeRight(serpent)
else :snakeLeft(serpent)
# décompte de la bouclemaxi = maxi -1
# EFFACER L'ECRANclearScreen(leds)
displayScreen(strip,leds)
Le serpent devrait s'animer !
