Saisie au clavier pour contrôler le serpent
Contenu :
saisie utilisateur, input, break, curses
Récupérer des informations saisies au clavier
Un petit autour de la fonction input()
la fonction input() marque une pause dans un script python en attendant que l'utilisateur saisisse des informations en terminant par la touche <Return> pour valider la saisie. input() renvoie en fait les données saisies sous la forme d'une chaîne de caractère :
En guise d'exemple on peut demander à l'utilisateur de saisir 2 entiers et réaliser la somme :
a = input('A = ')
b = input('B = ')
print("A + B = ", a + b)
Cet exemple renvoie :
Si on additionne a et b, les chaînes de caractère (string) correspondant aux touches tapées par l'utilisateur sont juste concaténée. Il faut réaliser une conversion de type à l'aide de int() :
a = input('A = ')
b = input('B = ')
print("A + B = ", int(a) + int(b))
Une autre manière d'écrire les choses, en changeant de type dés la saisie, permettant ainsi de s'assurer que a et b seront traités comme des entiers :
a = int(input('A = '))
b = int(input('B = '))
print("A + B = ", a + b)
Un cas classiquement rencontré, est celui où l'utilisateur saisit plusieurs nombre séparés par un point-virgule et valider ensuit par la touche <Return> :
Il existe une méthode bien pratique : split():
La liste message est bien une liste d'entiers. La fonction len(message) permet ici d'évaluer la taille du message.
La fonction input() n'est pas très adapté à notre problème :
Nécessite la validation de la saisie par la touche <Entrée>
Bloque le programme (incapacité de contrôler le serpent en parallèle)
Heureusement une alternative est posssible avec le module standard curses.
Le module curses
Le module curses va nous permettre de mieux gérer les intéractions avec l'utilisateur. Saisir le programme suivant qu'on pourra appeler touche.py permettra de mieux appréhender son fonctionnement :
import curses
# Création de l'objet stdscr permetant le dialogue avec le terminal
stdscr = curses.initscr()
# Configuration de la liasion avec le terminal:
# Sans echo
curses.noecho()
# interception des touches sans appui sur <Return>
curses.cbreak()
# Activation des touche spéciales (flèche vers le haut, ...)
stdscr.keypad(True)
# Boucle infinie :
while True:
c = stdscr.getch() # Lecture de la touche saisie
stdscr.refresh() #
stdscr.addstr(0,0,str(c) + " -> " + chr(c))# Affichage en pos 0,0 (en haut à gauche)
if c == curses.KEY_LEFT: # Si la fleche gauche a été appuyée
stdscr.addstr(0,0,'Gauche ') # Affichage de la chaine 'Gauche'
elif c == curses.KEY_RIGHT: # idem ici avle la flèche droite
stdscr.addstr(0,0,'Droite ') # ...
elif c == 27: # Si on appui sur <Echap>
stdscr.keypad(0) # desactive tout ce qui a été activé
curses.nocbreak() #
curses.echo() #
curses.endwin() # Fermeture
break # Break permet de sortir de la boucle
print("Fin du programme ...") # Afficher :"Fin du programme"
Si vous avez des problèmes pour l’exécution du programme avec IDLE vous devrez passer par le terminal :
Les lignes 3 à 11 sont nécessaires pour initialiser l'objet et configurer le comportement du terminal. Dans la boucle infinie while True :, une lecture d'une touche tapée au clavier est réalisé par la fonction getch() suivi d'un rafraîchissement de l'écran. Une chaîne de caractères est ensuite affichée en haut à gauche (position (0,0)) via la fonction addstr(). La fonction chr() renvoie le caractère correspondant au nombre entier c qui est codé en ASCII. Ce programme donne donc pour différents caractères tapés au clavier le code ASCII associé. Ensuite si la touche '<-' est tapé on affiche "Gauche" et pareille pour la touche '->'.
Le code 27 qui est ensuite testé correspond à la touche ESC. Dans ce bloc conditionnel, après avoir re-configuré le terminal dans son état originel, on réalise l'instruction break qui permet de sortir de la boucle.
Contrôler le serpent : Snake version 1
En utilisant le module ncurses nous allons modifier le programme de la fin du tp3 pour ajouter quelques lignes. Deux fonctions permettant l'initialisation du terminal et sa cloture ont été ajoutées : initCurses() et closeCurses().
"""
Affichage d'un serpent vert
Date : 29/10/2017
"""
from vrtneopixel import *
import time
import random
import curses
# déclaration des fonctions
# Afficher le serpent
def addSnake(snake,leds):
for (x,y) in snake:
leds[x+y * 8] = Color(0,255,0)
# Extinction du serpent
def 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
# Pour descendre :
def snakeDown(snake):
snake.pop() # Supprime le bout de la queue
new_x, new_y = snake[0] #
if new_y == 7:
new_y = 0
else:
new_y = new_y + 1 # On ajoute 1 à la coordonnee en y
snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
# Pour tourner a droite
def 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
# Pour tourner a droite
def snakeLeft(snake):
snake.pop() # Supprime le bout de la queue
new_x, new_y = snake[0] #
if new_x == 0: # Si on est au bord
new_x = 7 #
else:
new_x = new_x - 1 # On retranche 1 à la coordonnee en x
snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
# 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
# Cette fonction affecte la couleur noir à toutes les LEDs
# leds : liste des Couleurs
def 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()
# Initialisation de Curses
def initCurses():
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)
return stdscr
# Fermetur de Curses
def closeCurses(stdscr):
stdscr.keypad(0) # desactive tout ce qui a été activé
curses.nocbreak() #
curses.echo() #
curses.endwin()
# Variables de configuration de l'ecran
LED_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'écran
serpent = [(2,3),(2,4),(2,5)]
# Les 64 LEDs en Noir
leds = 64 * [Color(0,0,0)]
# Creation d'un element permettant de piloter
# l'ecran de LED
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecran
strip.begin()
stdscr = initCurses()
while True :
# Ajout du serpent vert
addSnake(serpent,leds)
# Affichage de l'écran
displayScreen(strip,leds)
#Attendre 100ms
time.sleep(0.1)
# Effacement du serpent
removeSnake(serpent,leds)
# Deplacer le serpent
direction = stdscr.getch()
stdscr.refresh()
if direction == curses.KEY_UP :
snakeUp(serpent)
elif direction == curses.KEY_DOWN :
snakeDown(serpent)
elif direction == curses.KEY_RIGHT:
snakeRight(serpent)
elif direction == curses.KEY_LEFT:
snakeLeft(serpent)
elif direction == 27:
closeCurses(stdscr)
break
# EFFACER L'ECRAN
clearScreen(leds)
displayScreen(strip,leds)
L’initialisation s'effectue à la ligne 128 juste avant d'entrée dans la boucle infinie. La lecture de la touche saisie par l'utilisateur s'opère dans cette boucle ligne 141. Le déplacement s'effectue alors en fonction de la touche tapée par l'utilisateur. (flèche du haut, bas, droite ou gauche). Si la touche ESC (correspondant au code 27 est tapée, alors on ferme le terminal et on sort de la boucle (break).
Version 2
Malheureusement, le programme précédent ne permet pas au programme de continuer car la fonction getch() est bloquante. Il est cependant possible de configurer curses pour qu'il rende la main (voir initCruses() du programme suivant). Vous pouvez donc tester le programme suivant plus complet :
"""
Affichage d'un serpent vert
Date : 29/10/2017
"""
from vrtneopixel import *
import time
import random
import curses
# déclaration des fonctions
# Afficher le serpent
def addSnake(snake,leds):
for (x,y) in snake:
leds[x+y * 8] = Color(0,255,0)
# Extinction du serpent
def 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
# Pour descendre :
def snakeDown(snake):
snake.pop() # Supprime le bout de la queue
new_x, new_y = snake[0] #
if new_y == 7:
new_y = 0
else:
new_y = new_y + 1 # On ajoute 1 à la coordonnee en y
snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
# Pour tourner a droite
def 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
# Pour tourner a droite
def snakeLeft(snake):
snake.pop() # Supprime le bout de la queue
new_x, new_y = snake[0] #
if new_x == 0: # Si on est au bord
new_x = 7 #
else:
new_x = new_x - 1 # On retranche 1 à la coordonnee en x
snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
# Mise a jour de la direction en prenant
# Gare aux directions interdites :
# Quant on avance 0 on ne peut pas reculer 1
# etc ...
def changeDirection(direction,newDirection):
unauthorized = [1,0,3,2]
if newDirection != unauthorized[direction]:
return newDirection
else :
return direction
# Cette fonction affecte la couleur noir à toutes les LEDs
# leds : liste des Couleurs
def 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()
# Initialisation de Curses
def initCurses():
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)
stdscr.nodelay(True)
return stdscr
# Fermetur de Curses
def closeCurses(stdscr):
stdscr.keypad(0) # desactive tout ce qui a été activé
curses.nocbreak() #
curses.echo() #
curses.endwin()
# Variables de configuration de l'ecran
LED_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'écran
serpent = [(2,3),(2,4),(2,5)]
# Les 64 LEDs en Noir
leds = 64 * [Color(0,0,0)]
# Creation d'un element permettant de piloter
# l'ecran de LED
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
# initialisation de l'ecran
strip.begin()
direction = 0
newDirection = direction
stdscr = initCurses()
while True :
# Ajout du serpent vert
addSnake(serpent,leds)
# Affichage de l'écran
displayScreen(strip,leds)
#Attendre 100ms
time.sleep(0.1)
# Effacement du serpent
removeSnake(serpent,leds)
# Deplacer le serpent
touche = stdscr.getch()
stdscr.refresh()
if touche == curses.KEY_UP :
newDirection = 0
elif touche == curses.KEY_DOWN :
newDirection = 1
elif touche == curses.KEY_RIGHT:
newDirection = 2
elif touche == curses.KEY_LEFT:
newDirection = 3
elif touche == 27:
closeCurses(stdscr)
break
direction = changeDirection(direction,newDirection)
if direction == 0:
snakeUp(serpent)
elif direction == 1:
snakeDown(serpent)
elif direction == 2:
snakeRight(serpent)
elif direction == 3:
snakeLeft(serpent)
# EFFACER L'ECRAN
clearScreen(leds)
displayScreen(strip,leds)
En résumé ...
input() permet d'afficher un message, d'attendre que l'utilisateur tape quelque chose au clavier. Une chaîne de caractère est alors renvoyée.
int() : permet de convertir une variable en un entier
len() : renvoie la longueur d'une liste (ou tuple)
split() : découpe une chaîne en une liste de sous-chaînes
chr() : convertie un code ASCII en caractère
break() : permet de sortir d'une boucle
curses : permet un dialogue à travers le terminal