Projet en C : jeu Snake
1. Installer la bibliothÚque de développement
Pour réaliser le jeu, nous allons nous reposer sur une bibliothÚque de développement appelée SDL (Simple DirectMedia). Nous utiliserons la version 2. Un wiki est disponible en ligne pour lire la documentation.
Cette bibliothĂšque fournit des fonctions permettant assez facilement de crĂ©er une fenĂȘtre de d'y dessiner. Elle est capable d'utiliser les capacitĂ©s d'accĂ©lĂ©ration de la carte graphique Ă©ventuellement prĂ©sente dans l'ordinateur. Elle permet Ă©galement d'afficher des images, de jouer des sons, de gĂ©rer les entrĂ©es clavier, etc. Tout ce qu'il faut pour programmer un petit jeu vidĂ©o.
Sous Linux, l'installation de la bibliothĂšque est trĂšs simple puisqu'elle est normalement disponible dans les paquets de votre distribution. Par exemple, pour les distributions de la famille Debian (dont Ubuntu) la commande d'installation est :
On peut également installer certains paquets optionnels comme :
pour permettre d'afficher des images.Pour vérifier si l'installation a fonctionné, on peut par exemple exécuter la commande :
2. CrĂ©er une fenĂȘtre graphique
Pour nous familiariser avec SDL, nous allons commencer par crĂ©er une fenĂȘtre graphique, attendre 5 secondes, et quitter :
Fichier main.c minimal
On a inclu les fichiers d'en-tĂȘte standard et celui correspondant Ă la bibliothĂšque SDL. Nous notons que toutes les fonctions et noms concernant la bibliothĂšque SDL sont prĂ©fixĂ©s par SDL_.
Le programme précédent commence par initialiser SDL avec la fonction SDL_Init. Cette fonction a pour paramÚtre un entier qui représente les options d'initialisation. Pour construire cet entier, on se sert de constantes prédéfinies dans la bibliothÚque appelées drapeaux comme SDL_INIT_VIDEO pour signifier qu'on veut initialiser les graphismes. Il est possible de combiner plusieurs drapeaux avec l'opérateur | qui est le OU bit à bit. Par exemple, si on veut initialiser le son et la vidéo on écrit :
On appelle ensuite la focntion SDL_CreateWindow permettant de crĂ©er une fenĂȘtre. Ses paramĂštres sont le titre de la fenĂȘtre, sa position (50, 50), ses dimensions 640x480. Le dernier paramĂštre est un entier pour les options que l'on fixe encore une fois Ă l'aide de drapeaux. J'ai mis ici 0 pour dire que je voulais les options par dĂ©faut (aucun drapeau).
La suite du programme est claire : on demande une attente de 5000ms puis on fait le ménage en détruisant dans le bon ordre les objets construits précédemment. En effet, on se doute bien que la fonction SDL_CreateWindow alloue de la mémoire sur le tas et qu'il est nécessaire de la libérer avec une fonction de destruction, c'est le rÎle de SDL_DestroyWindow.
On peut compiler le jeu avec la commande :
On utilise ici l'outilsdl2-config qui construit automatiquement les paramÚtres à ajouter la ligne de compilation usuelle pour que le programme soit lié à la bibliothÚque SDL.
3. La gestion des erreurs dans SDL
Les appels aux fonctions de la bibliothÚque SDL peuvent provoquer des erreurs. Dans ce cas, les fonctions retournent généralement un code d'erreur (valeur non nulle) ou un pointeur NULL selon les cas.
Il faut donc en théorie à chaque appel de fonction de SDL, vérifier sa valeur de retour. Pour nous faciliter la vie, la bibliothÚque SDL fournit la fonction SDL_GetError() qui retourne une chaßne de caractÚres décrivant l'erreur rencontrée. Voilà comment on peut adapter le code précédent :
Illustration du traitement d'erreurs
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "Erreur lors de l'initialisation : %s \n", SDL_GetError());
exit(EXIT_FAILURE);
}
SDL_Window *fenetre = SDL_CreateWindow("Snake974", 50, 50, 640, 480, 0);
if (fenetre == NULL) {
fprintf(stderr, "Erreur lors de la crĂ©ation de la fenĂȘtre : %s \n", SDL_GetError());
exit(EXIT_FAILURE);
}
SDL_Delay(5000); // Attendre 5 sec
SDL_DestroyWindow(fenetre);
SDL_Quit();
return EXIT_SUCCESS;
}
Avertissement
Dans toute la suite, pour me concentrer uniquement sur l'essentiel, je n'inclurai aucune autre vérification d'erreur SDL, mais cela doit faire partie du travail...
3. Construire et afficher la grille du jeu
Commençons maintenant à définir à quoi ressemblera le jeu. Notre jeu sera se déroulera dans une arene qui est une grille rectangulaire de blocs carrés. On notera ARENA_W la largeur de l'arene en nombre de blocs, ARENA_H sa hauteur et BLOCK_SIZE la longueur du cÎté d'un bloc en pixels.
L'arĂšne pourra contenir les types de bloc suivants :
VIDE: bloc videWALL: qui représente un murCOCO: qui représente une noix de cocoSNAKE: qui représente une partie du corps du serpent
Cela fait de nombreuses constantes Ă dĂ©finir, nous utilisons pour cela un fichier d'en-tĂȘte main.h pour dĂ©clarer les valeurs publiques (que tout le code peut du projet peut utiliser).
Fichier main.h
#ifndef MAIN_H
#define MAIN_H
// Position et dimensions de la fenetre
#define WIN_W 640
#define WIN_H 480
#define WIN_X 50
#define WIN_Y 50
// Dimensions de l'arene
#define ARENA_W 20
#define ARENA_H 10
#define B_SIZE 20
// Types de blocs
#define VIDE 0
#define WALL 1
#define COCO 2
#define SNAKE 4
/* L'arene est définie comme une matrice
de blocs */
typedef int Arena[ARENA_W][ARENA_H];
#endif
Il suffira ensuite d'inclure ce fichier d'en-tĂȘte dans n'importe quel fichier .c pour pouvoir utiliser ces dĂ©finitions :
Attention
Notez bien la présence de guillements "" et non de chevrons <> quand on inclus un fichier situé dans le répertoire courant.
Notez qu'on en a profitĂ© pour ajouter certains paramĂštres comme les dimensions de la fenĂȘtre. Il est toujours prĂ©fĂ©rable que de telles constantes n'apparaissent pas directement dans un code source afin de faciliter la lecture et la modification.
A. SystÚme de coordonnées
C'est le moment de faire le point sur le systÚme de coordonnées utilisé par SDL. SDL définit la position d'un pixel \((x, y)\) ainsi :
L'origine \((0, 0)\) est située en haut à gauche. La coordonnée \(x\) représente l'abscisse du point et la coordonnée \(y\) sont ordonnée, sauf que l'axe des ordonnées est orienté vers le bas.
Note
Nous utiliserons par la suite toujours ces conventions pour parler de coordonnées. En particulier les coordonnées des blocs. Ainsi le bloc de coordonnées (0, 0) sera celui situé en haut à gauche et le bloc de coordonnées (0, 1) celui situé juste en dessous de lui.
B. Dessin de l'arĂšne du jeu
Pour dessiner en SDL, il est nĂ©cessaire de crĂ©er un objet appelĂ© renderer qui est associĂ© Ă une fenĂȘtre et qui est en charge du dessin dans cette fenĂȘtre.
Le renderer est construit avec la fonction SDL_CreateRenderer et détruit avec la fonction SDL_DestroyRenderer. On met à jour le code main.c ainsi :
Création et destruction du renderer
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *fenetre = SDL_CreateWindow("Snake974", 50, 50, 640, 480, 0);
SDL_Renderer *renderer = SDL_CreateRenderer(fenetre, -1, SDL_RENDERER_ACCELERATED);
SDL_Delay(5000); // Attendre 5 sec
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(fenetre);
SDL_Quit();
return EXIT_SUCCESS;
}
Il existe ensuite une multitude de fonctions de dessin, prenant en paramĂštre l'instance du renderer.
| Fonction | Effet |
|---|---|
SDL_SetRenderDrawColor(renderer, r, g, b, a) |
Change la couleur de dessin \((r,g,b,a)\) |
SDL_RenderClear(renderer) |
Efface tout et color le fond avec la couleur actuelle |
SDL_RenderDrawPoint(renderer, x, y) |
Dessine un point |
SDL_RenderDrawLine(renderer, x1, y1, x2, y2) |
Dessine une ligne |
SDL_RenderDrawRect(renderer, &rect) |
Dessine un rectangle |
SDL_RenderFillRect(renderer, &rect) |
Remplit un rectangle |
SDL_RenderPresent(renderer) |
Met Ă jour l'affichage |
Attention
Noter l'importance de la fonction SDL_RenderPresent. Tout dessin ne sera visible qu'une fois la mise à jour de l'affichage effectué.
Notre grille étant rectangulaire, ces fonctions primitives de dessin devraient suffire. Voici quelques remarques pour bien comprendre ces fonctions :
Les couleurs
En SDL les couleurs sont codées sur 4 octets, c'est-à -dire 4 valeurs entiÚres \((r, g, b, a)\) entre 0 et 255 pour les composantes rouge (r), vert (g) et bleu (b); la composante alpha (a) représente le niveau de transparence (0 = transparent, 255 = opaque).
Les rectangles
SDL implémente son propre type SDL_Rect pour représenter un rectangle. Voici un exemple de fonctionnement :
Exercice : dessiner l'arĂšne
Ătudiez le code ci-dessous, puis complĂ©ter la fonction draw_arena, qui s'occupe de dessiner l'arĂšne qu'on lui passe en paramĂštre. Cette fonction devra colorer chaque type de bloc avec une couleur diffĂ©rente. On rappelle que les blocs doivent avoir une taille de B_SIZE pixels.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
/* Construit une arĂšne entiĂšrement vide */
void init_arena(Arena mat) {
for (int x = 0; x < ARENA_W; x++) {
for (int y = 0; y < ARENA_H; y++) {
mat[x][y] = VIDE;
}
}
}
void draw_arena(SDL_Renderer *renderer, Arena mat) {
/* A VOUS DE JOUER */
}
int main(int argc, char * argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *fenetre = SDL_CreateWindow("Snake974", WIN_X, WIN_Y, WIN_W, WIN_H, 0);
SDL_Renderer *renderer = SDL_CreateRenderer(fenetre, -1, SDL_RENDERER_ACCELERATED);
Arena mat;
init_arena(mat);
mat[3][5] = WALL; // Pour tester
mat[6][6] = SNAKE; // Toujours pour tester
mat[6][7] = SNAKE;
mat[6][8] = SNAKE;
mat[15][2] = COCO;
draw_arena(renderer, mat);
SDL_RenderPresent(renderer);
SDL_Delay(5000); // Attendre 5 sec
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(fenetre);
SDL_Quit();
return EXIT_SUCCESS;
}
Voilà ce que vous devriez obtenir à l'état actuel du jeu :
Aide
Si vous n'y parvenez pas, pensez le problĂšme ainsi :
- commencez par choisir la couleur de fond puis tout effacer (clear)
- on parcourt chaque case de la matrice
maton construit unblocqui est unSDL_Rectde bonnes dimensions et placé au bon endroit - On remplit (fill)
blocavec la bonne couleur - On dessine (draw)
blocavec la couleur de contour des blocs
Bon, nous avonçons bien mais il reste encore un petit problĂšme : il n'y a pas de murs au bord de notre grille de jeu : le serpent pourrait s'Ă©chapper. Ăcrivons une petite fonction pour ajouter des murs tout autour de l'arĂšne.
Exercice : mur d'enceinte
Ăcrire une fonction void mur_enceinte(Arena mat) qui assigne la valeur WALL Ă toutes les cases sur le pourtour de la grille mat.
4. Une structure de données pour le serpent
Dans cette partie, nous allons proposer une structure de donnĂ©es pour gĂ©rer le serpent. Le serpent peut ĂȘtre implĂ©mentĂ© sous forme d'une liste simplement chaĂźnĂ©e de blocs. Cette structure permet aisĂ©ment de faire Ă©voluer le serpent lorsqu'il avance ou lorsqu'il grandit.
A. Compilation séparée
Comme le code se complexifie, il serait bon de commencer Ă le sĂ©parer dans plusieurs fichiers. On introduit un nouveau fichier d'en-tĂȘte :
Fichier serpent.h
#ifndef SERPENT_H
#define SERPENT_H
#include <stdbool.h>
#include "main.h"
struct maillon_s {
/* Coordonnées du bloc */
int x;
int y;
/* Maillon suivant (de la queue vers la tĂȘte) */
struct maillon_s *suivant;
};
typedef struct maillon_s Maillon;
/* Sens de déplacement */
#define RIGHT 0
#define UP 1
#define DOWN 2
#define LEFT 3
struct serpent_s {
Maillon *queue; /* Premier maillon : fin du serpent */
Maillon *tete; /* Dernier maillon : tĂȘte du serpent */
int direction; /* Sens de déplacement */
};
typedef struct serpent_s Serpent;
/* Coordonnées du bloc vers lequel va le serpent */
extern int prochain_x(const Serpent *s);
extern int prochain_y(const Serpent *s);
/* Teste si la case est occupée par le serpent */
extern bool appartient(const Serpent *s, int x, int y);
/* Fait avancer le serpent */
extern void avancer(Serpent *s);
/* Fait grandir le serpent */
extern void grandir(Serpent *s);
/* Créer un serpent de longueur 1 positionné en (x, y) et
se déplacant dans la direction dir */
Serpent *creer_serpent(int x, int y, int dir);
/* Marque dans l'arene mat les cases occupees par le serpent */
extern void place_serpent(const Serpent *s, Arena mat);
#endif
Ce fichier d'en-tĂȘte dĂ©clare les structures de donnĂ©es implĂ©mentant le serpent ainsi que les fonctions utiles pour le manipuler. Ces fonctions seront implĂ©mentĂ©es dans le fichier serpent.c, ressemblant Ă :
Votre projet contient maintenant 4 fichiers : main.c, main.h, serpent.h, serpent.c. Et la compilation s'obtient avec les commandes :
gcc -c main.c $(sdl2-config --cflags)
gcc -c serpent.c
gcc main.o serpent.o -o snake $(sdl2-config --cflags --libs)
Nous allons utiliser le logiciel make pour nous faciliter la tĂąche. Pour fonctionner, cet outil a besoin de disposer d'un fichier nommĂ© Makefile Ă la racine de notre projet. Ce fichier dĂ©crit un ensemble de cibles, c'est-Ă -dire de fichiers Ă compiler, suivi de ses dĂ©pendances et de la commande de compilation. Ăcrivons ce fichier :
Fichier Makefile
Attention, les tabulations utilisĂ©es sont importantes et doivent ĂȘtre de vraies tabulation (pas une succession d'espaces). Il faudra aussi adapter les valeurs de CC, CFLAGS et LIBS selon votre propre configuration.
CC=gcc # nom du compilateur
# Resultat de la commande sdl2-config --cflags
CFLAGS= -I/usr/local/include -I/usr/local/include/SDL2 -D_REENTRANT -D_THREAD_SAFE
# Resultat de la commande sdl2-config --libs
LIBS= -L/usr/local/lib -lSDL2
.PHONY: clean
snake: main.o serpent.o
$(CC) main.o serpent.o -o snake $(CFLAGS) $(LIBS)
main.o: main.h serpent.h main.c
$(CC) -c main.c $(CFLAGS)
serpent.o: main.h serpent.h serpent.c
$(CC) -c serpent.c
clean:
rm -f *.o
Une fois ce fichier renseignĂ©, l'appel Ă la commande make compile automatiquement la premiere cible. Ici, la premiĂšre cible est l'exĂ©cutable snake. En cas de modification du code, make est capable de savoir exactement ce qui a besoin d'ĂȘtre recompilĂ© : cela Ă©vite de recompiler entiĂšrement un projet Ă chaque une petite modification.
De plus nous avons ajouté une fausse (PHONY) cible clean qui fait le ménage en supprimant les fichiers intermédiaires devenus inutiles.
Résumons, désormais la commande make permet compiler ou recompuler tout le projet. La commande make clean permet de faire le ménage.
B. Implémentation des opérations
Nous allons maintenant implĂ©menter dans serpent.c les opĂ©rations dĂ©crites dans le fichier d'en-ĂȘte serpent.h. Rappelons les structures de donnĂ©es mises en jeu :
struct maillon_s {
/* Coordonnées du bloc */
int x;
int y;
/* Maillon suivant (de la queue vers la tĂȘte) */
struct maillon_s *suivant;
};
typedef struct maillon_s Maillon;
/* Sens de déplacement */
#define RIGHT 0
#define UP 1
#define DOWN 2
#define LEFT 3
struct serpent_s {
Maillon *queue; /* Premier maillon : fin du serpent */
Maillon *tete; /* Dernier maillon : tĂȘte du serpent */
int direction; /* Sens de déplacement */
};
typedef struct serpent_s Serpent;
Voici une reprĂ©sentation schĂ©matique d'un serpent qui occupe les blocs de coordonnĂ©es \((4,4)\), \((5, 4)\), \((6, 4)\) et qui se dĂ©place vers la droite. Notez bien que la liste commence avec l'Ă©lĂ©ment de fin du serpent, et remonte jusque la case de tĂȘte du serpent (en derniĂšre position dans la liste). La structure Serpent contient un pointeur permettant d'atteindre la tĂȘte du serpent en temps \(O(1)\).
flowchart LR
direction LR
subgraph Serpent
queue
tete
direction[direction = RIGHT]
end
queue --> M1
tete --> M3
subgraph M1[Maillon]
direction LR
x1[x = 4]
y1[y = 4]
suivant1[suivant]
end
suivant1 --> M2
subgraph M2[Maillon]
direction LR
x2[x = 5]
y2[y = 4]
suivant2[suivant]
end
suivant2 --> M3
subgraph M3[Maillon]
direction LR
x3[x = 6]
y3[y = 4]
suivant3[suivant]
end
N@{shape: lin-rect, label: "NULL", color: blue}
suivant3 --> N
Exercice : Implémentation des opérations
Dans le fichier serpent.c, implémentez les opérations :
creer_serpentprochain_xetprochain_yappartientavancergrandirplace_serpent
Des indications sont fournies ci-dessous.
Voici quelques indications pour vous aider dans votre tĂąche :
creer_serpent: il faudra allouer de la mĂ©moire sur le tas pour la structure et pour le premier et unique maillonprochain_xetprochain_y: les coordonnĂ©es de la prochaine case se calculent Ă partir de celles de la tĂȘte et de la direciton actuelleappartient: c'est un parcours de listeavancer: c'est ici que la structure proposĂ©e est vraiment intĂ©ressante : il suffit de calculer la prochaine case occupĂ©e et d'ajouter un maillon pour cet emplacement qui sera la nouvelle tĂȘte, le maillon de queue quant Ă lui sera supprimĂ© (ne pas oublier de libĂ©rer la mĂ©moire...).grandir: mĂȘme chose que l'opĂ©ration prĂ©cĂ©dente, mais on ne supprime pas cette fois l'Ă©lĂ©ment de queue : la longueur de la liste augmente donc de 1.place_serpent: pas de difficultĂ© particuliĂšre, on parcourt la liste et on modifie les cases correspondantes de l'arĂšne.
Remarque
Pour le moment on ne se préoccupe pas de savoir si les déplacements sont effectivement possibles (murs, collisions, sortie de l'arÚne, ...).
Attention
Pensez bien aux cas limites de votre structure de liste !
Exercice : Nettoyage
Ajouter une opération destroy_serpent dont le but est de libérer
la mémoire allouée par un serpent. Vous ajouterez cette opération
dans le fichier serpent.h en vous inspirant des autres prototypes,
puis vous coderez l'implémentation dans serpent.c.
C. Un serpent animé !
Nous sommes maintenant prĂȘt pour crĂ©er une petite animation de serpent qui se dĂ©place.
Nous allons commencer par créer un serpent de taille 1 en position (3, 3) avec un déplacement vers la droite, puis le faire grandir 5 fois pour obtenir un serpent de taille 5.
Ensuite, nous dessinons la scÚne du jeu : on part d'une arÚne vide, on ajoute le mur d'enceinte, puis on place le serpent. On met à jour l'affichage pour voir le résultat.
Pour faire déplacer le serpent, on utilise la fonction avance mais il faut alors recommencer le dessin complet de la scÚne : remettre tout à vide, mettre les murs, placer le serpent, actualiser l'affichage.
Bref, en réalisant plusieurs fois ces opérations et en ajoutant des petites pauses de 0,5s entre chaque image, on obtient une animation. Testez maintenant votre code avec l'animation proposée ci-dessous !
Example
int main(int argc, char * argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *fenetre = SDL_CreateWindow("Snake974", WIN_X, WIN_Y, WIN_W, WIN_H, 0);
SDL_Renderer *renderer = SDL_CreateRenderer(fenetre, -1, SDL_RENDERER_ACCELERATED);
Serpent *s = creer_serpent(3, 3, RIGHT);
for (int k = 0; k < 5; k++) {
grandir(s);
}
Arena mat;
for (int k = 0; k < 8; k++) {
init_arena(mat);
mur_enceinte(mat);
place_serpent(s, mat);
draw_arena(renderer, mat);
SDL_RenderPresent(renderer);
SDL_Delay(500);
avancer(s);
}
s->direction = DOWN;
for (int k = 0; k < 3; k++) {
init_arena(mat);
mur_enceinte(mat);
place_serpent(s, mat);
draw_arena(renderer, mat);
SDL_RenderPresent(renderer);
SDL_Delay(500);
avancer(s);
}
s->direction = LEFT;
for (int k = 0; k < 8; k++) {
init_arena(mat);
mur_enceinte(mat);
place_serpent(s, mat);
draw_arena(renderer, mat);
SDL_RenderPresent(renderer);
SDL_Delay(500);
avancer(s);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(fenetre);
SDL_Quit();
return EXIT_SUCCESS;
}
5. La boucle principale
Nous nous approchons du but. Il nous reste Ă programmer la boucle principale du jeu. En effet, le jeu est en fait une boucle infinie dans laquelle on effectue les actions suivantes :
- Dessiner la scĂšne
- Attendre
- Lire et prendre en compte les actions du joueur
- Mettre Ă jour le serpent
A. Lire les événements clavier
Les Ă©vĂ©nements sont toutes les actions extĂ©rieures qui peuvent ĂȘtre prises en compte par notre programme : appui sur des touches clavier, actions de la souris ou de la manette de jeu, utilisation de boutons de la fenĂȘtre, etc.
SDL a un fonctionnement trÚs simple pour gérer les événements. à chaque fois qu'un événement se produit il est enregistré dans une file FIFO interne. Il existe ensuite une fonction de signature int SDL_PollEvent(SDL_Event *event) qui a pour but d'extraire un élément de cette file. Elle retourne 1 si un événement est effectivement défilé et 0 si la file est vide.
De plus, cette fonction attend un argument un pointeur vers une structure de type SDL_Event qui permet d'enregistrer les informations sur l'Ă©vĂ©nement qui vient d'ĂȘtre extrait de la file. Notons event cette variable, nous aurons besoin des champs suivants :
event.type: dĂ©crit le type d'Ă©vĂ©nement produit. On Ă©coutera les Ă©vĂ©nements de typeSDL_KEYDOWN(une touche est pressĂ©e)event.key.keysim.sym: dans le cas oĂč l'Ă©vĂ©nement concerne une touche, ce champ contient le caractĂšre concernĂ©
Pour résumer, voilà à quoi ressemble notre boucle principale :
Boucle principale
int main(int argc, char * argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *fenetre = SDL_CreateWindow("Snake974", WIN_X, WIN_Y, WIN_W, WIN_H, 0);
SDL_Renderer *renderer = SDL_CreateRenderer(fenetre, -1, SDL_RENDERER_ACCELERATED);
Serpent *s = creer_serpent(3, 3, RIGHT);
for (int k = 0; k < 5; k++) {
grandir(s);
}
Arena mat;
bool run = true;
while (run) {
init_arena(mat);
mur_enceinte(mat);
place_serpent(s, mat);
draw_arena(renderer, mat);
SDL_RenderPresent(renderer);
SDL_Delay(200);
SDL_Event event;
while (SDL_PollEvent(&event) != 0) {
if (event.type == SDL_KEYDOWN) {
fprintf(stderr, "Touche pressée : %c \n", event.key.keysym.sym);
}
if (event.key.keysym.sym == 'x') {
run = false; // touche pour quitter le jeu
}
}
avancer(s);
}
destroy_serpent(s);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(fenetre);
SDL_Quit();
return EXIT_SUCCESS;
}
Dans le code proposĂ©, le jeu est une boucle principale qui fait avancer le serpent Ă chaque itĂ©ration et qui s'arrĂȘte lorsque le joueur appuie sur la touche x.
Exercice : Diriger le serpent
Modifiez la boucle principale proposée pour qu'elle puisse prendre en compte des commandes de déplacement q (gauche), s (bas), d (droite), z (haut). L'action a effectué consiste à modifier la direction de déplacement du serpent.
Et voilĂ , notre jeu commence Ă ĂȘtre jouable ! Essayez et Ă©tudiez le comportement du jeu lorsque le serpent percute un mur, marche sur lui-mĂȘme ou sors de la zone de jeu...
B. Gestion des collisions
Nous allons maintenant gérer la mise à jour du serpent. Pour l'instant le serpent ne fait qu'avancer... nous allons affiner son comportement.
Exercice : Gestion des collisions
Modifiez votre code pour que le jeu s'arrĂȘte lorsque le bloc vers lequel on va avancer est un mur ou appartient dĂ©jĂ au serpent. Dans ce cas, la partie est perdue.
C. Gestion de la noix de coco
Exercice : Noix de coco
- Ajouter dans votre fonction
maindeux variables locales : qui reprĂ©sentent la position d'une noix de coco. Cette position doit ĂȘtre choisie alĂ©atoirement pour correspondre Ă une case vide de l'arĂšne. - Programmer le comportement suivant : si la case suivante est une noix de coco, alors plutĂŽt qu'avancer on fait grandir le serpent. On choisit ensuite une nouvelle position valide de noix de coco.
6. Aller plus loin
Voilà ! vous avez un jeu fonctionnel minimal et il vous appartient maintenant de le faire évoluer ! Voici des pistes non limitatives :
- Programmer une touche pause
- Proposer des notions de score et de niveau. Plus le niveau augmente, plus le serpent est rapide.
- Ajouter des sons.
- Si la noix de coco reste trop longtemps en jeu alors elle devient un cocotier (= 1 mur)
- Possibilité d'avoir plusieurs noix de coco
- Possibilité d'avoir des pommes empoisonnées en plus des noix de coco
- Possibilité d'avoir un bonus à ramasser qui fait diminuer la longueur du serpent
- ...