1. Snake
Le snake, de l’anglais signifiant « serpent », est un genre de jeu vidéo dans lequel le joueur dirige une ligne qui grandit et constitue ainsi elle-même un obstacle. Bien que le concept tire son origine du jeu vidéo d’arcade Blockade développé et édité par Gremlin Industries en 1976, il n’existe pas de version standard. Son concept simple l’a amené à être porté sur l’ensemble des plates-formes de jeu existantes sous des noms de clone.
Le joueur contrôle une longue et fine ligne semblable à un serpent, qui doit slalomer entre les bords de l’écran et les obstacles qui parsèment le niveau. Pour gagner chacun des niveaux, le joueur doit faire manger à son serpent un certain nombre de pastilles similaire à de la nourriture, allongeant à chaque fois la taille du serpent. Alors que le serpent avance inexorablement, le joueur ne peut que lui indiquer une direction à suivre (en haut, en bas, à gauche, à droite) afin d’éviter que la tête du serpent ne touche les murs ou son propre corps, auquel cas il risque de mourir.
https://fr.wikipedia.org/wiki/Snake_(genre_de_jeu_vid%C3%A9o)
2. Objectif
Notre objectif est développer une version Python de ce jeu en s’appuyant sur les concepts de programmation que nous avons appris jusqu’à maintenant :
-
Utilisation de modules
-
les fonctions
-
les listes
-
les tests conditionnels
-
les boucles
3. Au travail…
-
Cloner le dépôt depuis classroom.github : https://classroom.github.com/a/77g10ACT
-
Sur replit, créer un nouveau
repl
en important le code depuisgithub
-
Ne pas oublier de comit & push le code modifié après chaque étape du développement.
3.1. Le titre
Commençons par donner un titre à notre jeu, par exemple : PYTHON SNAKE. Petit clin d’oeil au jeu, au serpent (le python) et au langage avec lequel on le programme !
pour cela, je vous propose d’utiliser le concept de l'ASCII art
3.1.1. ASCII art
L’ASCII art consiste à réaliser des images uniquement à l’aide des lettres et caractères spéciaux contenus dans le code ASCII. Vous utilisez souvent cet art pour générer des émoticons. Nous allons ici pousser plus loin le concept pour générer une image du titre de notre jeu :
Vous êtes libre de réaliser le design que vous souhaitez du moment qu’il utilise les caractères de la table ASCII
On trouve sur le net des sites de génération d’image ASCII art, par exemple : http://www.patorjk.com/software/taag
3.1.2. Codage du titre
titre =[' _______ _________ _ _ ____ _ _ _____ _ _ _ ________ ',
' | __ \ \ / |__ __| | | |/ __ \| \ | | / ____| \ | | /\ | |/ | ____|',
' | |__) \ \_/ / | | | |__| | | | | \| | | (___ | \| | / \ | /| |__ ',
' | ___/ \ / | | | __ | | | | . ` | \___ \| . ` | / /\ \ | < | __| ',
' | | | | | | | | | | |__| | |\ | ____) | |\ |/ ____ \| . \| |____ ',
' |_| |_| |_| |_| |_|\____/|_| \_| |_____/|_| \_/_/ \_|_|\_|______|']
-
Ecrire une fonction
affichage_titre(titre)
qui parcours la listetitre
et affiche chacune de ces lignes puis attend 2 secondes avant de passer à la suite (lancement du jeu):
def affichage_titre(titre):
...
time.sleep(2)
Pour utiliser la fonction
|
3.2. L’aire de jeu
3.2.1. Le module curses
Curses est le nom d’une bibliothèque logicielle permettant le développement sous Unix d’environnements plein écran en mode texte, indépendamment du terminal informatique utilisé, et pour tout type d’applications.
Elle est issue de la distribution BSD d’Unix, dans laquelle elle était employée pour l’éditeur de texte vi et le jeu Rogue.
Avec curses, il est possible d’écrire à tout moment en toute position de l’écran. Les programmeurs peuvent ainsi concevoir des applications basées sur le mode texte, sans tenir compte des particularités de chaque terminal. La bibliothèque curses s’occupe d’envoyer les caractères de contrôle appropriés vers le moniteur lors de l’exécution du programme, en essayant d’optimiser ce qu’elle peut quand elle le peut.
Rester en mode texte, mais en y utilisant ainsi l’adressage direct de tout endroit de l’écran, permet aussi de ne pas avoir à s’occuper du gestionnaire graphique utilisé parmi les nombreux que peut utiliser Linux, Unix ou Windows.
https://fr.wikipedia.org/wiki/Curses
3.2.2. Utilisation
Utilisation basic de Curses
Les méthodes principales sont très simples :
|
La méthode newwin()
permet de créer une fenètre qui est une abstraction de base de curses
. Un objet fenêtre représente une zone rectangulaire de l’écran et prend en charge des méthodes pour afficher du texte, l’effacer, permettre à l’utilisateur de saisir des chaînes, etc.
Le détail de toutes les méthodes de curses : https://docs.python.org/fr/3/library/curses.html
Comment utiliser efficacement le module curses : https://docs.python.org/3/howto/curses.html
3.2.3. Codage de l’air de jeu
-
Ecrire une fonction
affichage_aire_de_jeu(hauteur, largeur, titre)
qui s’affichera en commençant par le bord supérieur gauche du terminal (point x=0, y=0)
def affichage_aire_de_jeu(hauteur, largeur, titre):
# Création d'une nouvelle fenètre en 0, 0
win = curses.____
# Les séquences d'échapement sont générés par certaines touches, les autres n'ont aucun effet
win.keypad(__)
# L'écho des caractères saisis est désactivé
curses.noecho()
# Pas de curseur visible
curses.curs_set(__)
# La saisie de caractère est non bloquante
win.nodelay(1)
# La fenètre a une bordure standard
win.____
# Définition d'une couleur pour le titre : texte en rouge sur fond blanc
# Voir dans la documentation la table "lists the predefined colors"
curses.init_pair(1, curses.____, curses.____)
# Affichage du titre
win.addstr(0, 27, titre, curses.color_pair(1))
# Raffraichissement de la fenêtre
win.____
# Emission d'un beep
curses.____
# retourner la fenêtre
return ____
-
Appeler cette fonction après l’appel de la fonction
affichage_titre()
et faire sorte qu’elle reste afficher à l’écran pendant 10 secondes.
# Affichge du titre pendant 2s avant l'ouverture de la fenêtre de jeu
affichage_titre(titre)
# Initialisation du terminal
curses._____
# Démarrage du mode couleur
curses.start_color()
# Affichage de l'aire de jeu
affichage_aire_de_jeu(20,60, 'SNAKE')
# Tempo 10s
curses.napms(____)
# fin de l'affichage de la fenêtre
curses.endwin()
-
Vérifier le bon fonctionnement
3.3. Contrôles du jeu
Le contôle du jeu est assuré par les touches de direction UP, DOWN, LEFT, RIGHT et Escape pour quitter la partie en cours.
3.3.1. Les définitions de touches de curses
Curses dispose de constantes qui permettent d’identifier certaines touches du clavier. Si les touches directionnelles sont bien présentes, ce n’est pas le cas de la touche Escape. Il nous faudra donc utiliser son code ASCII.
3.3.2. Codage des contrôles de jeu
-
Rechercher les constantes correspondantes au touches directionnelles (KEYS) dans la documentation : https://docs.python.org/fr/3/library/curses.html#constants
-
Rechercher dans la table ASCII la valeur décimal du code associé à la touche Escape (Echap)
-
Ecrire une fonction
controle(win, key, keys = [ __ ])
oukeys
est une liste des contrôles possibles. Renseigner la listekeys
qui ici un argument par défaut de la fonction.
def controle(win, key, keys = [____]):
'''
Controles de jeu
paramètres :
win : fenètre en cours
key : dernière touche reconnue
keys: liste des touches acceptées par défaut
retour :
code de la touche reconnue
'''
# Sauvegarde de la dernière touche reconnue
old_key = ____
# Aquisition d'un nouveau caractère depuis le clavier
key = win.____
# Si aucune touche actionnée (pas de nouveau caractère)
# ou pas dans la liste des touches acceptées
# key prend la valeur de la dernière touche connue
if key == ____ or key not in ____ :
key = ____
# Raffaichissement de la fenètre
win.refresh()
# retourne le code la touche
return ____
-
Ecrire une fonction
jeu(win)
qui affichera le sepent et la pomme qu’il doit manger, assurera le comptage des point et l’arrêt du jeu par le joueur (Escape) ou lorqu’il perd.
def jeu(win):
'''
Moteur du jeu
paramètre :
win : fenètre en cours
retour :
score à la fin du jeu
'''
# initialisation du jeu
# Le serpent se dirige vers la droite au début du jeu.
# C'est comme si le joueur avait utilisé la flèche droite au clavier
key = ____
score = 0
# Definition des coordonnées du serpent
# Le serpent est une liste de d'anneaux composées de leurs coordonnées ligne, colonne
# La tête du serpent est en 4,10, l'anneau 1 en 4,9, le 2 en 4,8
snake = [[4, 10], [4, 9], [4, 8]]
# La nouriture (pomme) se trouve en 10,20
food = [10, 20]
# Affichage la nouriture en vert sur fond noir dans la fenêtre
curses.init_pair(2, curses.______, curses.______)
win.addch(food[0], food[1], chr(211), curses.color_pair(2)) # Prints the food
# Affichage du serpent en bleu sur fond jaune
curses.init_pair(3, curses.____, curses.____)
# sur toute la longeur du serpent
for i in range(______)):
# affichage de chaque anneau dans la fenêtre en ligne, colonne
win.addstr(snake[i][0], snake[i][1], '*', curses.color_pair(3))
# Emission d'un beep au début du jeu
curses.____
# Tant que le joueur n'a pas quitter le jeu
______________
key = controle(win, key)
return score
-
Appeler cette fonction après l’appel de la fonction
affichage_aire_de_jeu()
et faire afficher le score (ici de 0) lorsque l’utilisateur quitte le jeu. Le seul moyen de quitter (proprement) le jeu doit être l’utilisation de la touche Escape
affichage_titre(titre)
curses.initscr()
curses.start_color()
window = affichage_aire_de_jeu(20, 60, 'SNAKE')
score = jeu(window)
curses.endwin()
print('\n\n\n')
print(f'Votre score est de : {____}')
print('\n\n\n')
-
Vérifier le bon fonctionnement
3.4. Gérer les déplacements
3.4.1. Principe
Les déplacements sont obtenus en agissant sur les coordonnés du serpent. Lorsque le serpent avance dans une direction donnée, on insère un nouvel élément dans sa liste à la position 0 (la tête). Le serpent à donc grandi d’un anneau. Lorsque nous traiterons du cas ou il a manger une pomme, nous ne lui retirerons pas cet anneau mais pour le moment, nous traitons seulement le cas de l’avance, donc, il nous faut supprimer son dernier anneau pour conserver sa longueur :
3.4.2. Codage des déplacements
-
Ecrire une fonction
deplacement(win, score, key, snake, vitesse)
qui gère les déplacents du serpent dans l’aire de jeu en fonction de la touche de contrôle active. Si le serpent atteint une limite de l’air de jeu, il réapparait sur le bord opposé :
def deplacement(win, score, key, snake, food):
'''
Déplacements du serpent
paramètres :
win : fenètre en cours
score : score en cours
key : touche de controle en cours
snake : liste des positions en cours des anneaux du serpent
food : liste de la position de la pomme
retourne :
tuple contenant la liste des positions en cours des anneaux du serpent et score en cours
'''
# Si on appui sur la flèche "à droite",
# la tête se déplace de 1 caractère vers la droite (colonne + 1)
if key == KEY_RIGHT:
snake.insert(0, ________, ________)
# Sinon si on appui sur la flèche "à gauche",
# la tête se déplace de 1 caractère vers la gauche (colonne - 1)
elif key == KEY_LEFT:
snake.insert(0, ________, ________)
# Sinon si on appui sur la flèche "en haut",
# la tête se déplace de 1 caractère vers le haut (ligne - 1)
elif key == KEY_UP:
snake.insert(0, ________, ________)
# Sinon si on appui sur la flèche "en bas",
# la tête se déplace de 1 caractère vers le bas (ligne + 1)
elif key == KEY_DOWN:
snake.insert(0, ________, ________)
# si la serpent arrive au bord de la fenêtre (20 lignes x 60 colonnes)
if snake[0][0] == __:
________________
if snake[0][1] == __:
________________
if snake[0][0] == __:
________________
if snake[0][1] == __:
________________
# Suppression du dernier anneau du serpent.
# Sera conditionner plus tard au fait que le serpent mange ou pas une pomme
# Le score sera alors également mis à jour
last = snake.pop()
# Affichage de la tête à sa nouvelle position en bleu sur fond jaune
win.addstr(____, ____, ___, curses.color_pair(__))
# Effacement du dernier anneau : affichage du caractère "espace" sur fond noir
win.addstr(last[0], last[1], ' ', curses.color_pair(1))
# Affichage du score dans l'aire de jeu
win.addstr(0, 2, 'Score : ' + str(score) + ' ')
# Attendre avant le pas suivant
vitesse = 1
win.timeout(150//vitesse)
# tuple contenant :
# - la liste des positions en cours des anneaux du serpent
# - score en cours
return snake, score
-
Appeler cette fonction dans le cours du jeu, juste après avoir obtenu le contrôle souhaité :
def jeu(win):
...
while key != 27:
key = controle(win, key)
snake = deplacement(win, score, key, snake, food)
return score
-
Vérifier le bon fonctionnement
3.5. On a mangé la pomme ?
On cherche ici à savoir si aucours du déplacement, le serpent a manger la pomme. Dans ce cas, les coordonnées de sa tête sont égales à celle de la pomme.
3.5.1. Principe
La position de la tête est donnée par l’élément 0 de la liste snake
(snake[0]
) et celle de la pomme par la liste food
. Il suffit que ces deux listes soient égales pour que l’on en déduise que le serpent à manger la pomme !
3.5.2. Codage du test le serpent a manger la pomme
-
Ecrire une fonction mange_pomme(win, food, snake, score) qui teste si le serpent a mangé la pomme, met à jour le score et retourne les listes nécessaires à la mises à jour du serpent.
def mange_pomme(win, food, snake, score):
'''
Le serpent a-t-il mangé la pomme ?
paramètres :
win : fenètre en cours
food : liste des coordonnées de la pomme
snake : liste des coordonnées des anneaux du serpent
score : score en cours
retour :
Tuple constitué de :
- la liste des coordonnées actualisées de la pomme,
- la liste des coordonnées du serpent,
- la liste des coordonnées du dernier anneau à supprimer
- le score en cours
'''
# initialisation de la liste contenant les coordonnées du dernier anneau du serpent
last = [0,0]
# Si le serpent a mangé la pomme
__________________:
# Emettre un beep
curses.beep()
# incrémenter le score
score _____________
# Réactualiser les coordonnées de la pomme
# On recommence tant que les coordonnées de la pomme sont dans le serpent
while _____ in _____:
# On actualise au hasard les coordonnées de la pomme
# dans les limite de la fenêtre
# voir la documentation de la fonction window.getmaxyx()
food[0] = randint(1, ___________________)
food[1] = randint(1, ___________________)
# Affichage de la pomme aux nouvelles coordonnées en vert sur fond noir
win.addch(____, ____, chr(211), curses.color_pair(2))
win.refresh()
# Sinon
else:
# Suppression du dernier anneau du serpent
last = snake.pop()
return food, snake, last, score
-
Modifier la fonction
deplacement()
pour qu’elle appelle la fonctionmange_pomme()
:
def deplacement(win, score, key, snake, food):
...
# Suppression du dernier anneau du serpent.
# Sera conditionner plus tard au fait que le serpent mange ou pas une pomme
# Le score sera alors également mis à jour
# remplacer last = snake.pop() par :
food, snake, last, score = mange_pomme(win, food, snake, score)
...
return snake, score
-
Vérifier le bon fonctionnement.
3.6. Quand est-ce qu’on perd ?
Si tout va bien pour le moment, on peut jouer mais pas perdre. En effet, nous n’avons prévu de cas perdant. Dans le scénario initial de Snake, le joueur perd si le serpent se mord la queue ou s’il entre en contact avec un bordure. Nous ne traiterons ici que du premier cas.
3.6.1. Principe
Lorsque le serpent se mord la queue, les coordonnées de sa tête sont identiques à celles d’une de ses cellules. La position de la tête est donnée par l’élément 0 de la liste snake
(snake[0]
) et celle du reste du corps la liste snake[1:]
. Il suffit que ces deux listes soient égales pour que l’on en déduise que le serpent s’est mordu la queue !
3.6.2. Codage de la fin du jeu
def perdu(win, snake):
'''
Le serpent se mange-t-il la queue ?
paramètre :
win : fenètre en cours
snake : liste des positions en cours des anneaux du serpent
retourne :
True si on perd, False sinon
'''
# initialisation de la variable end à retourner
end = ______
# Si la tête du serpent est dans le corps
if ______ in _____ :
# Afiicher "GAME OVER !" en blanc sur fond rouge au milieu de la fenêtre
curses.init_pair(4, curses.______, curses.______)
win.addstr(________, ________, ________)
win.refresh()
# Emission d'une série de beep.
# Vous devrez écrire vous-même cette fonction !
beep_fin()
# On laisse 2 secondes au joueur pour s'assurer
# qu'il ait bien compris qu'il a perdu !
curses.napms(2000)
end = True
return end
-
Modifier la fonction
jeu()
pour qu’elle appelle la fonctionperdu()
et mette fin au jeu si elle retourneTrue
def jeu(win):
...
end = False
...
# tant que pas Escape ou pas end
while key != 27 ___ ___ end:
key = controle(win, key)
snake, score = deplacement(win, score, key, snake, food)
end = perdu(win, snake)
return score
-
Vérifier le bon fonctionnement.
3.7. On complique un peu le jeu ?
Il y a plusieurs façon de compliquer le jeu. En modifiant la géométrie de l’aire de jeu (compliqué) ou plus simplement en accélérant le serpent en cours de jeu. C’est que nous allons faire ici.
3.7.1. Principe
Le changement de vitesse dans les déplacements du serpent est obtenu en agissant sur le temps entre deux raffraichissement de sa position. C’est l’appel à la fonction win.timeout(150//vitesse)
ou le paramètre vitesse qui est actuellement fixé à 1. Il faut le rendre variable, par exemple, en l’incrémentant tous les 5 points.
3.7.2. Codage de la vitesse du serpent
-
Ecrire une fonction
plus_vite(score)
qui calcul la vitesse du serpent en fonction du score
def plus_vite(score):
'''
Calcul de la vitesse du serpent
paramètre :
score : score en cours
retourne :
vitesse du serpent entre 1 et 10
'''
# vitesse est le quotient de la division entière de score par 5 ( + 1)
vitesse = ___________
# Si vitesse est superieur à 10, alors vitesse = 10
_____________________:
vitesse = _______
return vitesse
-
Modifier la fonction
deplacement()
pour qu’elle appelle la fonctionplus_vite()
def deplacement(win, score, key, snake, food):
...
vitesse = plus_vite(score)
win.timeout(150//vitesse)
return snake, score
-
Vérifier le bon fonctionnement
4. On joue !
Amusez vous, quand vous en aurez assez, modifiez le scénario, les bords de la fenêtre deviennent mortel, on place plusieurs pommes, …. Faites preuve d’imagination !