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 :

1
a = input('A = ')
2
b = input('B = ')
3
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() :

1
a = input('A = ')
2
b = input('B = ')
3
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 :

1
a = int(input('A = '))
2
b = int(input('B = '))
3
print("A + B = ", a + b)
4

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 : 

1
import curses
2
3
 # Création de l'objet stdscr permetant le dialogue avec le terminal
4
stdscr = curses.initscr()
5
# Configuration de la liasion avec le terminal:
6
#   Sans echo
7
curses.noecho()
8
#   interception des touches sans appui sur <Return>
9
curses.cbreak()
10
# Activation des touche spéciales (flèche vers le haut, ...)
11
stdscr.keypad(True)
12
13
# Boucle infinie :
14
while True:
15
    c = stdscr.getch()                         # Lecture de la touche saisie
16
    stdscr.refresh()                           # 
17
    stdscr.addstr(0,0,str(c) + " -> " + chr(c))# Affichage en pos 0,0 (en haut à gauche)
18
    if c == curses.KEY_LEFT:                   # Si la fleche gauche a été appuyée
19
        stdscr.addstr(0,0,'Gauche ')           # Affichage de la chaine 'Gauche'
20
    elif c == curses.KEY_RIGHT:                # idem ici avle la flèche droite
21
        stdscr.addstr(0,0,'Droite ')           # ...
22
    elif c == 27:                              # Si on appui sur <Echap>
23
        stdscr.keypad(0)                       # desactive tout ce qui a été activé
24
        curses.nocbreak()                      #
25
        curses.echo()                          #
26
        curses.endwin()                        # Fermeture 
27
        break                                  # Break permet de sortir de la boucle
28
print("Fin du programme ...")      #  Afficher :"Fin du programme"
29
    
30

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().

1
"""
2
Affichage d'un serpent vert
3
4
Date : 29/10/2017
5
6
"""
7
8
from vrtneopixel import *
9
import time
10
import random
11
import curses
12
13
# déclaration des fonctions
14
# Afficher le serpent
15
def addSnake(snake,leds):
16
    for (x,y) in snake:
17
        leds[x+y * 8] = Color(0,255,0)
18
19
# Extinction du serpent
20
def removeSnake(snake, leds):
21
    for (x,y) in snake:
22
        leds[x+y * 8] = Color(0,0,0)
23
24
# Nouvelles coordonnées du serpent
25
# Pour monter :
26
def snakeUp(snake):
27
    snake.pop()             # Supprime le bout de la queue
28
    new_x, new_y = snake[0] # 
29
    if new_y == 0:
30
        new_y = 7
31
    else:
32
        new_y = new_y - 1         # On retranche 1 à la coordonnee en y
33
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
34
35
# Pour descendre :
36
def snakeDown(snake):
37
    snake.pop()             # Supprime le bout de la queue
38
    new_x, new_y = snake[0] # 
39
    if new_y == 7:
40
        new_y = 0
41
    else:
42
        new_y = new_y + 1         # On ajoute 1 à la coordonnee en y
43
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
44
45
    
46
# Pour tourner a droite
47
def snakeRight(snake):
48
    snake.pop()             # Supprime le bout de la queue
49
    new_x, new_y = snake[0] # 
50
    if new_x == 7:          # Si on est au bord
51
        new_x = 0           # 
52
    else:
53
        new_x = new_x + 1         # On ajoute 1 à la coordonnee en x
54
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
55
56
# Pour tourner a droite
57
def snakeLeft(snake):
58
    snake.pop()             # Supprime le bout de la queue
59
    new_x, new_y = snake[0] # 
60
    if new_x == 0:          # Si on est au bord
61
        new_x = 7           # 
62
    else:
63
        new_x = new_x - 1         # On retranche 1 à la coordonnee en x
64
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
65
66
# Changer la direction aléatoirement en prenant
67
# Gare aux directions interdites :
68
# Quant on avance 0 on ne peut pas reculer 1
69
# etc ...
70
def changeDirection(direction):
71
    unauthorized = [1,0,3,2]
72
    while True :
73
        newDirection = random.randint(0,3)
74
        if newDirection != unauthorized[direction]:
75
            return newDirection
76
77
78
# Cette fonction affecte la couleur noir à toutes les LEDs
79
# leds : liste des Couleurs
80
def clearScreen(leds):
81
    for i in range(64):
82
        leds[i]=Color(0,0,0)
83
84
# Mise a jour de l'affichage
85
# leds : liste des Couleurs
86
# strip : objet controlant le ruban (matrice de LED Ws2812b)
87
def displayScreen(strip,leds):
88
    for i in range(64):
89
        strip.setPixelColor(i,leds[i])
90
    strip.show()
91
92
# Initialisation de Curses
93
def initCurses():
94
    stdscr = curses.initscr()
95
    curses.noecho()
96
    curses.cbreak()
97
    stdscr.keypad(True)
98
    return stdscr
99
100
# Fermetur de Curses
101
def closeCurses(stdscr):
102
    stdscr.keypad(0)                       # desactive tout ce qui a été activé
103
    curses.nocbreak()                      #
104
    curses.echo()                          #
105
    curses.endwin()     
106
    
107
# Variables de configuration de l'ecran
108
LED_COUNT      = 64
109
LED_PIN        = 18
110
LED_FREQ_HZ    = 800000
111
LED_DMA        = 5
112
LED_BRIGHTNESS = 8
113
LED_INVERT     = False
114
115
if __name__ == '__main__':
116
    # liste de tuples représentant les coordonnées du serpent sur l'écran
117
    serpent = [(2,3),(2,4),(2,5)]
118
    # Les 64 LEDs en Noir
119
    leds = 64 * [Color(0,0,0)]
120
    
121
    # Creation d'un element permettant de piloter
122
    # l'ecran de LED
123
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
124
    
125
    # initialisation de l'ecran
126
    strip.begin()
127
    
128
    stdscr = initCurses()    
129
    while True :
130
        # Ajout du serpent vert 
131
        addSnake(serpent,leds)
132
        # Affichage de l'écran
133
        displayScreen(strip,leds)
134
        
135
        #Attendre 100ms
136
        time.sleep(0.1)
137
        
138
        # Effacement du serpent
139
        removeSnake(serpent,leds)
140
        # Deplacer le serpent
141
        direction = stdscr.getch()
142
        stdscr.refresh()
143
        if direction == curses.KEY_UP     :
144
            snakeUp(serpent)
145
        elif direction == curses.KEY_DOWN :
146
            snakeDown(serpent)
147
        elif direction == curses.KEY_RIGHT:
148
            snakeRight(serpent)
149
        elif direction == curses.KEY_LEFT:
150
            snakeLeft(serpent)
151
        elif direction == 27:
152
            closeCurses(stdscr)
153
            break
154
            
155
    #   EFFACER L'ECRAN
156
    clearScreen(leds)
157
    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  :

1
"""
2
Affichage d'un serpent vert
3
4
Date : 29/10/2017
5
6
"""
7
8
from vrtneopixel import *
9
import time
10
import random
11
import curses
12
13
# déclaration des fonctions
14
# Afficher le serpent
15
def addSnake(snake,leds):
16
    for (x,y) in snake:
17
        leds[x+y * 8] = Color(0,255,0)
18
19
# Extinction du serpent
20
def removeSnake(snake, leds):
21
    for (x,y) in snake:
22
        leds[x+y * 8] = Color(0,0,0)
23
24
# Nouvelles coordonnées du serpent
25
# Pour monter :
26
def snakeUp(snake):
27
    snake.pop()             # Supprime le bout de la queue
28
    new_x, new_y = snake[0] # 
29
    if new_y == 0:
30
        new_y = 7
31
    else:
32
        new_y = new_y - 1         # On retranche 1 à la coordonnee en y
33
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
34
35
# Pour descendre :
36
def snakeDown(snake):
37
    snake.pop()             # Supprime le bout de la queue
38
    new_x, new_y = snake[0] # 
39
    if new_y == 7:
40
        new_y = 0
41
    else:
42
        new_y = new_y + 1         # On ajoute 1 à la coordonnee en y
43
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
44
45
    
46
# Pour tourner a droite
47
def snakeRight(snake):
48
    snake.pop()             # Supprime le bout de la queue
49
    new_x, new_y = snake[0] # 
50
    if new_x == 7:          # Si on est au bord
51
        new_x = 0           # 
52
    else:
53
        new_x = new_x + 1         # On ajoute 1 à la coordonnee en x
54
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
55
56
# Pour tourner a droite
57
def snakeLeft(snake):
58
    snake.pop()             # Supprime le bout de la queue
59
    new_x, new_y = snake[0] # 
60
    if new_x == 0:          # Si on est au bord
61
        new_x = 7           # 
62
    else:
63
        new_x = new_x - 1         # On retranche 1 à la coordonnee en x
64
    snake.insert(0,(new_x,new_y)) # Ajout de la nouvelle coordonnée
65
66
# Mise a jour de la direction  en prenant
67
# Gare aux directions interdites :
68
# Quant on avance 0 on ne peut pas reculer 1
69
# etc ...
70
def changeDirection(direction,newDirection):
71
    unauthorized = [1,0,3,2]
72
    if newDirection != unauthorized[direction]:
73
        return newDirection
74
    else :
75
        return direction
76
77
# Cette fonction affecte la couleur noir à toutes les LEDs
78
# leds : liste des Couleurs
79
def clearScreen(leds):
80
    for i in range(64):
81
        leds[i]=Color(0,0,0)
82
83
# Mise a jour de l'affichage
84
# leds : liste des Couleurs
85
# strip : objet controlant le ruban (matrice de LED Ws2812b)
86
def displayScreen(strip,leds):
87
    for i in range(64):
88
        strip.setPixelColor(i,leds[i])
89
    strip.show()
90
91
# Initialisation de Curses
92
def initCurses():
93
    stdscr = curses.initscr()
94
    curses.noecho()
95
    curses.cbreak()
96
    stdscr.keypad(True)
97
    stdscr.nodelay(True)
98
    return stdscr
99
100
# Fermetur de Curses
101
def closeCurses(stdscr):
102
    stdscr.keypad(0)                       # desactive tout ce qui a été activé
103
    curses.nocbreak()                      #
104
    curses.echo()                          #
105
    curses.endwin()     
106
    
107
# Variables de configuration de l'ecran
108
LED_COUNT      = 64
109
LED_PIN        = 18
110
LED_FREQ_HZ    = 800000
111
LED_DMA        = 5
112
LED_BRIGHTNESS = 8
113
LED_INVERT     = False
114
115
if __name__ == '__main__':
116
    # liste de tuples représentant les coordonnées du serpent sur l'écran
117
    serpent = [(2,3),(2,4),(2,5)]
118
    # Les 64 LEDs en Noir
119
    leds = 64 * [Color(0,0,0)]
120
    
121
    # Creation d'un element permettant de piloter
122
    # l'ecran de LED
123
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
124
    
125
    # initialisation de l'ecran
126
    strip.begin()
127
    direction = 0
128
    newDirection = direction
129
    stdscr = initCurses()    
130
    while True :
131
        # Ajout du serpent vert 
132
        addSnake(serpent,leds)
133
        # Affichage de l'écran
134
        displayScreen(strip,leds)
135
        
136
        #Attendre 100ms
137
        time.sleep(0.1)
138
        
139
        # Effacement du serpent
140
        removeSnake(serpent,leds)
141
        # Deplacer le serpent
142
        touche = stdscr.getch()
143
        stdscr.refresh()
144
        if touche == curses.KEY_UP     :
145
            newDirection = 0
146
        elif touche == curses.KEY_DOWN :
147
            newDirection = 1
148
        elif touche == curses.KEY_RIGHT:
149
            newDirection = 2
150
        elif touche == curses.KEY_LEFT:
151
            newDirection = 3
152
        elif touche == 27:
153
            closeCurses(stdscr)
154
            break
155
        direction = changeDirection(direction,newDirection)
156
        if   direction == 0:
157
            snakeUp(serpent)
158
        elif direction == 1:
159
            snakeDown(serpent)
160
        elif direction == 2:
161
            snakeRight(serpent)
162
        elif direction == 3:
163
            snakeLeft(serpent)            
164
    #   EFFACER L'ECRAN
165
    clearScreen(leds)
166
    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