1. Programmation orientée objet
Document Professeur
1.1. Objectif
Notre objectif est développer une version Python du fameux jeu Snake en s’appuyant sur les concepts de programmation orientée objet.
1.2. Prérequis
-
Utilisation d’un environnement de développement (pycharm)
-
Types élémentaires : numériques et chaines de caractères
-
Types construis : listes, tuples
-
Structure algorithmique de base : boucle, test
-
Concepts de programmation objet : classes, objets, attribus et méthodes
1.3. Conditions
-
Activité sur PC avec connexion internet
-
Niveau première
-
Type d’activité TD/TP
-
Durée : 4h
1.4. Rubriques du programme :
-
Langage et programmation :
-
Constructions élémentaires
-
Spécification
-
Mise au point
-
Paradigme de programmation objet
-
2. Introduction : 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)
Dans notre version, le lancement du jeu devra :
-
Afficher une bannière contenant le nom du jeu PYTHON SNAKE pendant 2 secondes avant d’afficher l’aire de jeu
-
Afficher l’aire de jeu avec le score et le niveau
-
L’aire de jeu contient un serpent et au moins une pomme.
-
Le serpent se déplace dans l’aire de jeu.
-
Il réapparait sur le bord opposé lorsqu’il sort de l’aire de jeu.
-
Il grandit d’un anneau chaque fois qu’il mange une pomme.
-
A chaque niveau, il doit manger 5 pommes.
-
Au premier niveau, l’aire de jeu contient 1 pomme à la fois, au second niveau, 2 pommes, 3 au troisième, ….
-
La vitesse de déplacement du serpent augmente à chaque niveau.
-
Le jeu prend fin lorsque le joueur appui sur la touche Echap ou lorsque le serpent se mord la queue

3. Au travail
-
Sur votre espace github, créer un nouveau dépôt
PythonSnake
-
Sur pycharm, créer un nouveau projet associé à votre dépôt dans votre environnement de développement
-
Créer un nouveau fichier python
snake.py
-
Ne pas oublier de commiter puis de pousser le code après chaque étape du développement.
3.1. Analyse
Question :
D’après vous, quels sont les objets qui constituent le jeu ?
Question :
Quels sont les attributs de chacun de ces objets ?
Question :
Quels sont les comportements ou méthodes de chacun de ces objets ?
3.2. Codage du squelette de l’application
L’aire de jeu s’appuie sur l’utilisation du module |
Le modèle retenu est réprésenté ci-dessous :

Question :
-
Coder le squelette des classes avec leur constructeur
3.2.1. Instantiation de la classe jeu
Question :
-
Ajouter dans le constructeur de la classe
Jeu
la ligne suivante :
print(f"Construction d'un objet de la classe Jeu de dimension {hauteur}x{largeur}")
-
Créer un objet
mon_jeu
de la classeJeu
avec pour arguments40x120
Question :
-
Excuter le programme et observer la création de l’objet
mon_jeu

3.2.2. Affichage de la bannière d’accueil
La bannière d’accueil PYTHON SNAKE est un petit clin d’oeil au jeu, au serpent (le python) et au langage avec lequel on le programme !
Pour la réaliser, nous allons utiliser le concept de l'ASCII art
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 |
Question :
-
Réaliser votre bannière et l’insérer dans le code du constructeur du jeu sous la forme d’un tuple` comme ci-dessous :
titre =(' _______ _________ _ _ ____ _ _ _____ _ _ _ ________ ',
' | __ \ \ / |__ __| | | |/ __ \| \ | | / ____| \ | | /\ | |/ | ____|',
' | |__) \ \_/ / | | | |__| | | | | \| | | (___ | \| | / \ | /| |__ ',
' | ___/ \ / | | | __ | | | | . ` | \___ \| . ` | / /\ \ | < | __| ',
' | | | | | | | | | | |__| | |\ | ____) | |\ |/ ____ \| . \| |____ ',
' |_| |_| |_| |_| |_|\____/|_| \_| |_____/|_| \_/_/ \_|_|\_|______|')
Question :
-
Ecrire une methode
afficher_banniere(self, banniere)
qui parcours le tuplebanniere
et affiche chacune de ces lignes puis attend 2 secondes avant de passer à la suite (lancement du jeu).
Remarque : L’attente peut être obtenue en utilisant la fonction sleep()
du module time
Question :
-
Dans le programme principal, appeler la méthode
afficher_banniere()
de l’objetmon_jeu
. -
Excuter le programme et observer l’affichage de la bannière.
Les caractéristiques d’un terminal peuvent être contrôler au moyen du codage Escape ANSI. La liste de ces codes est données dans la documentation Screen User’s Manual du projet GNU : Quelques codes utiles :
Remarque : Le caractère |
Question :
-
Modifier la méthode
afficher_banniere()
pour :-
effacer le terminal avant l’affichage de la bannière,
-
le redimensionner à la hauteur de la bannière \+ 2 caractères et à la largeur de l’aire de jeu,
-
attendre 2 seconde
-
le redimensionner aux dimensions de l’aire de jeu
-
effacer de nouveau le terminal,
-
attendre 1 seconde
-
-
Excuter le programme et observer l’affichage de la bannière.
3.2.3. Création de l’aire de jeu
Le module curses
— wikipedia
https://fr.wikipedia.org/wiki/Curses |
Utilisation basic de Curses
Les méthodes principales sont très simples :
La méthode 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 |
Question :
-
Ecrire une méthode
affichage_aire_de_jeu(titre)
de la classeJeu
qui doit :-
afficher l’aire de jeu en commençant par le bord supérieur gauche du terminal (
x=0
,y=0
) -
Afficher un titre en rouge sur fond blanc au milieu de la bordure supérieure
-
Pas de curseur apparent
-
Emettre un bip à la fin de l’affichage
-
Retourner la fenêtre créée
-
-
Appeler la méthode
affichage_aire_de_jeu(hauteur, largeur, titre)
-
Excuter le programme et observer l’affichage de l’aire de jeu.

Exemple de codage des couleurs avec
|
Le retour au terminal ne se fait pas correctement car la fonction |
Question :
-
Ecrire une méthode
fin(self)
de la classeJeu
qui doit :-
Emettre un bip
-
Mettre fin à la fenêtre de jeu dans le terminal
-
Redimensionner le terminal avec 10 lignes et 80 colonnes
-
Afficher le score du joueur. Il faut donc aussi créer un attribut
score
à la classeJeu
et l’initialiser à 0.
-
-
Appeler la méthode
fin()
de l’objetmon_jeu
. Pour s’assurer de voir l’aire de jeu, utiliser la fonctionnapms()
du modulecurses
qui prend en argument une durée en millisecondes. -
Excuter le programme et observer l’affichage de l’aire de jeu et le retour normal au terminal avec l’affichage su score.
3.3. Ajout d’une pomme
Une pomme est caractérisée par sa localisation, sa couleur et sa forme. Nous allons pour le moment laisser la couleur de côté et générer aléatoirement le positionnement de la pomme qui s’affichera en vert sur fond noir. Nous utilserons le caractère ASCII étendu 211 : Ó ou 210 : Ò pour représenter une pomme.
3.3.1. Afficher la pomme
Question :
-
Ajouter au constructeur de la classe :
-
la définition de la couleur de la pomme
-
un attribut
coordonnees
dont la valeur initiale est une liste de deux éléments à 0. Le premier est la position en ligne et le second en colonne. -
un attribut
window
initialisé avec la fenêtre de jeu obtenue du constructeur.
-
-
Ecrire une méthode
afficher()
de la classePomme
qui doit :-
Obtenir aléatoirement de nouvelles coordonnées pour la pomme. Utiliser la fonction
randint
du modulerandom
. -
afficher la pomme dans l’aire de jeu obtenue du constructeur.
-
-
Créer un objet
la_pomme
de la classePomme
dans le programme principal avant la fin de l’affichage de l’aire de jeu. -
Appeler la méthode
afficher
de l’objetla_pomme
-
Excuter le programme et observer l’affichage de l’aire de jeu avec une pomme.

3.3.2. Obtenir les coordonnées de la pomme
Question :
-
Ecrire une méthode
get_xy()
de la classePomme
qui retourne la liste des coordonnées de la pomme. -
Appeler la méthode
get_xy()
de l’objetla_pomme
et faire afficher après la fin du jeu la localisation de la pomme. -
Excuter le programme et observer l’affichage de l’aire de jeu avec une pomme.

3.4. Ajout du serpent
Le serpent est caractérisé par sa taille, sa localisation, sa couleur et le caractère utilisé pour matérialisé ses anneaux.
Nous allons fixer sa taille de départ à 3 anneaux, sa couleur sera jaune sur fond noir, chaque anneau contiendra le caractère * et sa localisation de départ sur la colone 8, et la ligne 4.
Pour matérialiser sa position, on utilisera une liste de listes de deux éléments :

3.4.1. Afficher le serpent
Question :
-
Ajouter au constructeur de la classe :
-
la définition de la couleur du serpent
-
un attribut
snake
dont la valeur initiale est une liste de listes de deux éléments dont le premier est la position en ligne et le second en colonne de chaque anneau du serpent. -
un attribut
window
initialisé avec la fenêtre de jeu obtenue du constructeur.
-
-
Ecrire une méthode
afficher()
de la classeSerpent
qui doit :-
afficher le serpent dans l’aire de jeu obtenue du constructeur au coordonnées contenues dans
snake
.
-
-
Créer un objet
le_serpent
de la classeSerpent
dans le programme principal avant la fin de l’affichage de l’aire de jeu. -
Appeler la méthode
afficher
de l’objetle_serpent
-
Excuter le programme et observer l’affichage de l’aire de jeu avec le serpent.

3.4.2. Déplacer le serpent
On utilise les touches de directions pour déplacer le serpent. Les seules touches qui auront un effet sur le jeu sont :
-
UP : Le serpent va vers le haut
-
DOWN : Le serpent va vers le bas
-
LEFT : Le robot va vers la gauche
-
RIGHT : Le robot va vers la droite
-
Escape : quitter la partie en cours.
L’acquisition des touches pressées par le joueur est du ressort de la classe Jeu
.
Question :
-
Rechercher les constantes correspondantes au touches directionnelles (KEYS) dans la documentation :
Rappel : le code ASCII de la touche Escape est 0x1d
soit 27
.
-
Ajouter la méthode
controle()
à classeJeu
qui retourne la dernière touche valide utilisée.
def controle(self,
key: int = KEY_RIGHT,
keys: list = [KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_UP, 27]) -> int:
old_key = key
key = self.window.getch()
if key == -1 or key not in keys:
key = old_key
return key
Remarque : cette méthode fait appel à l’attribut window
qui contient la fenêtre de jeu, or cet attribut n’exite pas ! La fenêtre de jeu est définie dans la méthode affichage_aire_de_jeu()
.
Question :
-
Modifier la méthode
affichage_aire_de_jeu()
pour créer l’attributwindow
de la classeJeu
. -
Modifier le programme principal pour contrôler le jeu. La sortie du jeu ne peut se produire que si la touche utilisée est Escape
-
Excuter le programme et observer l’affichage de l’aire de jeu jusqu’à l’appui sur la touche Escape.
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 mangé 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 :
Codage des déplacements
Question :
-
Ajouter une méthode
tete()
à classeserpent
qui retourne une liste contenant les coordonnées de sa tête (premier élément de la listesnake
) :
def tete(self) -> list:
return ???
Question :
-
Ajouter une méthode
corps()
à classeserpent
qui retourne une liste contenant les coordonnées de tous ses éléments sauf la tête :
def corps(self) -> list:
return ???
Question :
-
Compléter la méthode
deplacement(win, key, niveau)
de la classeserpent
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é :
class Serpent:
...
def deplacement(self, key: int, niveau: int) -> list:
# si la touche est KEY_RIGHT : ajouter la tête une colonne à droite
if key == KEY_RIGHT:
self.snake.insert(0, [self.tete()[0], self.tete()[1] + 1])
# si la touche est KEY_LEFT : ajouter la tête une colonne à gauche
elif key == KEY_LEFT:
???
# si la touche est KEY_UP : ajouter la tête une ligne au dessus
elif key == KEY_UP:
???
# si la touche est KEY_DOWN : ajouter la tête une ligne en dessous
elif key == KEY_DOWN:
???
# Si la tête du serpent touche les bords de l'aire de jeu
if self.tete()[0] == 0:
???
if self.tete()[1] == 0:
???
if self.tete()[0] == self.window.getmaxyx()[0] - 1:
???
if self.tete()[1] == self.window.getmaxyx()[1] - 1:
self.tete()[1] = 1
# Supprimer le dernier élément du serpent
self.window.addstr(self.snake[???][0], self.snake[???][1], ' ', curses.color_pair(1))
self.snake.pop()
# Attendre un peu en fonction du niveau
self.window.timeout(int(150 * (1 / niveau + 1 / (niveau * 2))))
return self.snake
Question :
-
Compléter le programme principal pour faire avancer le serpent en fonction de la touche utilisée tant que le joueur n’a pas quitter le jeu.
Ne pas oublier d’afficher le serpent après le calcul des dépacements et d’initialiser le niveau à 1. -
Vérifier que le déplacement du serpent est fonctionnel dans l’aire de jeu. Modifier le niveau et constater la rapidité du déplacement.
3.5. On a mangé la pomme ?
On cherche ici à savoir si au cours du déplacement, le serpent a mangé 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 la méthode tete()
de la classe Serpent
et celle de d’une pomme par sa méthode get_xy()
de la classe Pomme
. Il suffit que les listes retournées par ces deux méthodes soient égales pour que l’on en déduise que le serpent à manger la pomme !
Attention : Il pourrait y avoir plusieurs pommes dans l’air de jeu.
3.5.2. Codage du test "le serpent a manger la pomme"
Lorsque le serpent mange une pomme, le score du joueur est incrémenté et on pourra par la suite changer le niveau par exemple tous les 5 points.
Question :
-
Ajouter la méthode
afficher_score()
à la classeJeu
. Ajouter également l’attributniveau
à la classeJeu
-
Appeler cette méthode dans le programme principal et constater l’affichage du score et du niveau en haut de l’aire jeu.
class Jeu:
...
self.niveau = 1
...
def affiche_score(self):
self.window.addstr(0, 2, 'Score : ' + str(self.score) + ' ')
self.window.addstr(0, self.largeur - 16, 'Niveau : ' + str(self.niveau) + ' ')
self.window.refresh()
Question :
-
Compléter le code de la méthode
mange_pomme(win, pommes)
de la classeSerpent
qui retourneVrai
si le serpent a mangé la pomme etFaux
si ce n’est pas le cas.
class Serpent:
...
def mange_pomme(self, win: curses, pommes: list) -> bool:
# initialisation du flag à Faux
miam = ???
# la liste <pommes> contient toutes les pommes de l'air de jeu
# parcour de la liste pommes
for pomme in ???:
# si la tête du serpent est sur la pomme
???
curses.beep()
# flag passe à Vrai
miam = ???
# afficher une nouvelle pomme dans la fenêtre de jeu
pomme.???
# Recommencer tant la nouvelle pomme apparait dans le serpent
while pomme.get_xy() in self.snake:
pomme.afficher(win)
# Si le serpent n'a pas manger la pomme
???:
# Supprimer le dernier élément du serpent
win.addstr(self.snake[-1][0], self.snake[-1][1], ' ', curses.color_pair(1))
self.snake.pop()
return miam
Question :
-
Modifier le programme principale pour incrémenter le score du jeu lorsque le serpent mange une pomme.
Remarque : On pourra accéder directement à l’attribut score
de la classe jeu
depuis le programme principal. Ce n’est pas très indiqué, la meilleur solution serait de créer une méthode pour incrémenter le score.
-
Tester le programme et constater l’incrémentation du score lorsque le serpent mange une pomme. Un nouvelle pomme doit également apparaitre de façon aléatoire.
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 pas 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 une 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 la méthode tete()
et celle du reste du corps par la méthode corps()
. Il suffit que la tête soit dans le corps pour que l’on en déduise que le serpent s’est mordu la queue !
3.6.2. Codage de la fin du jeu
Question :
-
Compléter la méthode
perdu(serpent)
de la classeJeu
:
def perdu(self, snake):
# flag initialisé à Faux
end = ???
# Si la tête du serpent est dans le corps
???
self.window.addstr(self.hauteur // 2,
self.largeur // 2 - 4,
'GAME OVER !',
curses.color_pair(4))
self.window.refresh()
curses.napms(2000)
# flag devient Vrai
end = ???
return end
Question :
-
Modifier le programme principal pour mettre fin au jeu lorsque le serpent se mord la queue. Pour le moment, le jeu ne peut se terminer que si le joueur appui sur la touche Echap
3.7. Gestion des niveaux de jeu
3.7.1. Principe
Pour faire simple, nous allons changer de niveau chaque fois que le serpent aura mangé 5 pommes. Le changement de niveau aura un effet sur :
-
la vitesse de déplacement du serpent :
self.window.timeout(int(150 * (1 / niveau + 1 / (niveau * 2))))
dans la méthodedeplacement()
de la classeSerpent
-
le nombre de pommes : on ajoutera une pomme à chaque nouveau niveau
3.7.2. Codage de la gestion du changement de niveau
Question :
-
Compléter la méthode
calcul_niveau()
de la classeJeu
qui retourne le niveau augmenté de 1 tous les 5 points (attributscore
).
Rappel : La classe jeu dispose d’un attibut niveau
affiché par la méthode affiche_score()
def calcul_niveau(self) -> int:
# niveau augmente de 1 tous les 5 points
???
# 10 est le dernier niveau
# après ça va trop vite !
if self.niveau > 10:
self.niveau = ???
return ???
Question :
-
Modifier le programme principal pour acquérir le niveau courant, le sauvegarder et le comparer au niveau précédant. Si le niveau a augmenté, on instancie une nouvelle pomme que l’on ajoute à la liste des pommes et que l’on affiche dans l’aire de jeu.
-
Tester, corriger les problèmes éventuels et jouer à volonté !

4. On joue !
Amusez vous et quand vous en aurez assez, modifiez le scénario, les bords de la fenêtre deviennent mortel, on affiche plusieurs pommes qu’il faut mangées pour changer de niveau, on sauvegarde dans un fichier texte les meilleurs scores, ….
Faites preuve d’imagination !