Sokoban 1.1.2
Le fameux jeu Sokoban, poussez les boîtes !
Chargement...
Recherche...
Aucune correspondance
grid.c
Aller à la documentation de ce fichier.
1/*
2Game level management functions for Sokoban
3Copyright (C) 2022, 2023 Efe ERKEN
4
5This file is part of Sokoban
6
7Sokoban is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12Sokoban is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with Sokoban. If not, see <https://www.gnu.org/licenses/>.
19
20SPDX-License-Identifier: GPL-3.0-or-later
21*/
22
34#include <stdio.h>
35#include <stdlib.h>
36#include <ncurses.h>
37#include "grid.h"
38#include "player.h"
39#include "sdl2.h"
40
56grid *creer_level(int row, int column, int goals)
57{
58 // on alloue la structure elle-même dynamiquement
59 grid *G = (grid *)malloc(sizeof(grid));
60 // on vérifie si l'allocation s'est bien passée
61 if (G == NULL)
62 {
63 fprintf(stderr, "Error game structure alloc failed\n");
64 exit(-1);
65 }
66 // on alloue la première dimension du tableau dynamique à deux dimensions dans la structure
67 G->game_grid = (enum CaseType **)(malloc(row * (sizeof(enum CaseType *))));
68 // on vérifie si l'allocation s'est bien passée
69 if (G->game_grid == NULL)
70 {
71 fprintf(stderr, "Error game_grid row alloc failed\n");
72 exit(-1);
73 }
74 // on alloue la deuxième dimension du tableau dynamique
75 for (int i = 0; i < row; i++)
76 {
77 G->game_grid[i] = (enum CaseType *)(malloc(column * (sizeof(enum CaseType))));
78 // on vérifie si l'allocation s'est bien passée
79 if (G->game_grid[i] == NULL)
80 {
81 fprintf(stderr, "Error game_grid column alloc failed\n");
82 exit(-1);
83 }
84 }
85 // on initialise les valeurs de taille et d'objectifs du niveau
86 G->row_number = row;
87 G->column_number = column;
88 G->goal_number = goals;
89 return G;
90}
91
106{
107 // on désalloue la deuxième dimension du tableau dynamique dans la structure
108 for (int i = 0; i < G->row_number; i++)
109 {
110 free(G->game_grid[i]);
111 }
112 // on désalloue la première dimension du tableau dynamique
113 free(G->game_grid);
114 // on désalloue la structure
115 free(G);
116}
117
133grid *init_level(const char *file_path)
134{
135 // ouverture du fichier en mode lecture
136 FILE *file = fopen(file_path, "r");
137 // vérification si le fichier est bien ouvert
138 if (!file)
139 {
140 fprintf(stderr, "Error %s not found\n", file_path);
141 exit(-1);
142 }
143 char line[100] = {0}; // buffer pour lire une ligne dans le fichier
144 int number_column = 0; // nombre de colonne
145 int number_row = 0; // nombre de ligne
146 int number_goals = 0; // nombre d'objectifs
147 // on lit la première ligne du fichier
148 fgets(line, 100, file);
149 // on récupère les informations sur le niveau depuis la première ligne
150 sscanf(line, "%d %d %d", &number_column, &number_row, &number_goals);
151
152 // on alloue la structure pour stocker le niveau
153 grid *level = creer_level(number_row, number_column, number_goals);
154
155 int current_row = 0; // la ligne où on se trouve actuellement en lisant le niveau
156 int current_goal = 0; // le nombre d'objectifs déjà réussi du niveau
157 // On lit le fichier ligne par ligne jusqu'à la fin du fichier
158 while (fgets(line, 100, file) != NULL)
159 {
160 char *buffer = line;
161 int current_column = 0; // la colonne où on se trouve actuellement en lisant le niveau
162 // tant qu'on arrive pas à une ligne vide ou à la fin d'une ligne, on continue à lire la ligne
163 while (*buffer && *buffer != '\n')
164 {
165 // on charge chaque case dans la structure
166 level->game_grid[current_row][current_column] = *buffer;
167 // on initialise la position du joueur dans la structure quand on la retrouve
168 if (*buffer == PLAYER)
169 {
170 level->player.x = current_column;
171 level->player.y = current_row;
172 }
173 else if (*buffer == BOX_GOAL)
174 { // on incrémente à chaque fois on trouve un objectif déjà réussi
175 current_goal++;
176 }
177
178 current_column += 1;
179 buffer += 1;
180 }
181 current_row += 1;
182 }
183 // on sauvegarde le nombre d'objectifs déjà réussi dans la structure de jeu
184 level->box_over_goal_number = current_goal;
185 // fermeture du fichier
186 fclose(file);
187 return level;
188}
189
201void display(grid *G)
202{
203 // on parcourt chaque ligne et colonne du tableau pour afficher le niveau
204 for (int row = 0; row < G->row_number; row++)
205 {
206 for (int column = 0; column < G->column_number; column++)
207 {
208 printf("%c", G->game_grid[row][column]);
209 }
210 printf("\n");
211 }
212}
213
225{
226 // on initialise <ncurses.h>
227 initscr();
228 // on efface le buffer d'avant
229 clear();
230 // on établi les options <ncurses.h> nécessaires
231 noecho();
232 cbreak();
233}
234
250{
251 // on efface le buffer d'avant
252 clear();
253 // on charge dans le buffer les messages sur comment interagir
254 mvprintw(0, 0, "Appuyez sur \"q\" pour quitter");
255 mvprintw(1, 0, "Appuyez sur \"h, j, k, l\" pour vous déplacer");
256 // on recherche la taille maximale de la fenêtre
257 int maxY, maxX;
258 getmaxyx(stdscr, maxY, maxX);
259 // on retrouve les coordonnées telles que le niveau soit centré pour commencer l'affichage
260 int centerY = (maxY - G->row_number) / 2;
261 int centerX = (maxX - G->column_number) / 2;
262 // on parcourt chaque ligne et colonne du tableau pour charger dans le buffer
263 // on veille à régler le curseur à chaque caractère pour que le niveau soit centré au final
264 for (int row = 0, cursorY = centerY; row < G->row_number; row++, cursorY++)
265 {
266 for (int column = 0, cursorX = centerX; column < G->column_number; column++, cursorX++)
267 {
268 mvprintw(cursorY, cursorX, "%c", G->game_grid[row][column]);
269 }
270 }
271 // on écarte le curseur pour ne pas être dérangé
272 move(2, 0);
273 // on affiche le buffer, donc le niveau entier
274 refresh();
275}
276
301{
302 // on vide d'abord le buffer d'entrée
303 nodelay(stdscr, TRUE);
304 while ((getch()) != ERR);
305 nodelay(stdscr, FALSE);
306 // on lit une touche au clavier
307 return (char)getch();
308}
309
321{
322 // on efface le buffer d'avant
323 clear();
324 // on charge dans le buffer un message d'erreur
325 mvprintw(0, 0, "---> Cette touche n'a pas de fonctionnalité");
326 // on écarte le curseur pour ne pas être dérangé
327 move(1, 0);
328 // on affiche le buffer, donc le message d'erreur
329 refresh();
330 // on donne à l'utilisateur 3 secondes pour lire le message
331 napms(3000);
332}
333
344{
345 // on referme <ncurses.h> pour désallouer la mémoire qu'elle utilisait
346 endwin();
347}
348
366{
367 // on choisit la couleur marron pastel pour l'arrière plan
368 SDL_SetRenderDrawColor(context.renderer, 130, 125, 85, 255);
369 // on dessine toute la fenêtre en marron pastel
370 SDL_RenderClear(context.renderer);
371 // on calcule la taille des rectangles représentants les cases du jeu
372 // pour une fenêtre de taille fixe
373 int squareHeight = context.height / G->row_number;
374 int squareWidth = context.width / G->column_number;
375 // on parcourt toutes les cases du niveau
376 for (int row = 0; row < G->row_number; row++)
377 {
378 for (int column = 0; column < G->column_number; column++)
379 {
380 enum CaseType current_case = G->game_grid[row][column];
381 // on choisit une couleur en fonction du type de la case
382 switch (current_case)
383 {
384 case WALL:
385 // couleur marron pastel
386 SDL_SetRenderDrawColor(context.renderer, 130, 125, 85, 255);
387 break;
388 case BOX:
389 // couleur jaune pastel
390 SDL_SetRenderDrawColor(context.renderer, 180, 135, 85, 255);
391 break;
392 case PLAYER:
393 // couleur bleue pastel
394 SDL_SetRenderDrawColor(context.renderer, 100, 115, 130, 255);
395 break;
396 case GOAL:
397 // couleur grise pastel
398 SDL_SetRenderDrawColor(context.renderer, 155, 150, 120, 255);
399 break;
400 case NONE:
401 // couleur citron pastel
402 SDL_SetRenderDrawColor(context.renderer, 220, 215, 180, 255);
403 break;
404 case BOX_GOAL:
405 // couleur marron foncée pastel
406 SDL_SetRenderDrawColor(context.renderer, 95, 60, 25, 255);
407 break;
408 case PLAYER_GOAL:
409 // couleur bleue foncée pastel
410 SDL_SetRenderDrawColor(context.renderer, 50, 65, 80, 255);
411 break;
412 default:
413 // couleur rouge
414 SDL_SetRenderDrawColor(context.renderer, 255, 0, 0, 255);
415 }
416 // on crée un rectangle correspondant à la case de taille et de position voulu
417 SDL_Rect rect = {.x = column * squareWidth, .y = row * squareHeight, .w = squareWidth, .h = squareHeight};
418 // on dessine le rectangle de la case avec la couleur choisie
419 SDL_RenderFillRect(context.renderer, &rect);
420 }
421 }
422 // on affiche dans la fenêtre tous ce qu'on a déssiné
423 SDL_RenderPresent(context.renderer);
424}
425
441{
442 enum Event game_event = EVENT_NONE;
443 // on demande une entrée à l'utilisateur
444 printf("Entrez la direction voulu : ");
445 // on lit un caractère de l'entrée
446 char entry = (char)fgetc(stdin);
447 // on vide le buffer pour la fois prochaine
448 while ((fgetc(stdin)) != '\n');
449 // on décide de l'événement en fonction de l'entrée
450 switch (entry)
451 {
452 // événement = quitter le jeu si l'entrée est 'q'
453 case 'q':
454 game_event = EVENT_QUIT;
455 break;
456 // événement = aller à gauche si l'entrée est 'h'
457 case 'h':
458 game_event = EVENT_LEFT;
459 break;
460 // événement = aller en bas si l'entrée est 'j'
461 case 'j':
462 game_event = EVENT_DOWN;
463 break;
464 // événement = aller en haut si l'entrée est 'k'
465 case 'k':
466 game_event = EVENT_UP;
467 break;
468 // événement = aller à droite si l'entrée est 'l'
469 case 'l':
470 game_event = EVENT_RIGHT;
471 break;
472 }
473 return game_event;
474}
491{
492 enum Event game_event = EVENT_NONE;
493 // on récupère l'entrée au clavier
494 char entry = display_ncurses_input();
495 // on décide de l'événement en fonction de l'entrée
496 switch (entry)
497 {
498 // événement = quitter le jeu si l'entrée est 'q'
499 case 'q':
500 game_event = EVENT_QUIT;
501 break;
502 // événement = aller à gauche si l'entrée est 'h'
503 case 'h':
504 game_event = EVENT_LEFT;
505 break;
506 // événement = aller en bas si l'entrée est 'j'
507 case 'j':
508 game_event = EVENT_DOWN;
509 break;
510 // événement = aller en haut si l'entrée est 'k'
511 case 'k':
512 game_event = EVENT_UP;
513 break;
514 // événement = aller à droite si l'entrée est 'l'
515 case 'l':
516 game_event = EVENT_RIGHT;
517 break;
518 // on affiche un message si l'entrée n'est pas définie dans le programme
519 default:
521 }
522 return game_event;
523}
524
541{
542 enum Event game_event = EVENT_NONE;
543 SDL_Event scan_event;
544 // on initialise la variable SDL2 pour récupérer l'événement SDL2
545 SDL_WaitEvent(&scan_event);
546 // événement = quitter le jeu si on ferme la fenêtre
547 if (scan_event.type == SDL_QUIT)
548 {
549 game_event = EVENT_QUIT;
550 }
551 else if (scan_event.type == SDL_KEYDOWN)
552 {
553 switch (scan_event.key.keysym.sym)
554 {
555 // événement = quitter le jeu si l'entrée est q
556 case SDLK_q:
557 game_event = EVENT_QUIT;
558 break;
559 // événement = aller à gauche si l'entrée est flèche gauche ou h
560 case SDLK_h:
561 case SDLK_LEFT:
562 game_event = EVENT_LEFT;
563 break;
564 // événement = aller en bas si l'entrée est flèche basse ou j
565 case SDLK_j:
566 case SDLK_DOWN:
567 game_event = EVENT_DOWN;
568 break;
569 // événement = aller en haut si l'entrée est flèche haute ou k
570 case SDLK_k:
571 case SDLK_UP:
572 game_event = EVENT_UP;
573 break;
574 // événement = aller à droite si l'entrée est flèche droite ou l
575 case SDLK_l:
576 case SDLK_RIGHT:
577 game_event = EVENT_RIGHT;
578 break;
579 }
580 }
581 return game_event;
582}
583
599 // on referme le système d'affichage de niveau pour désallouer la mémoire qu'il utilisait
600 handle_quit();
601 // on désalloue la structure qui stockait le niveau
602 free_level(G);
603}
void free_level(grid *G)
Fonction pour désallouer la structure du jeu.
Definition: grid.c:105
void exit_routine(grid *G)
Fonction qui s'occupe des routines de fermeture du programme.
Definition: grid.c:598
grid * creer_level(int row, int column, int goals)
Fonction qui alloue la grille du jeu.
Definition: grid.c:56
enum Event event()
Fonction qui renvoie un événement en fonction du clavier.
Definition: grid.c:440
void display_sdl2(grid *G)
Fonction qui affiche le niveau en paramètre dans une fenêtre avec SDL2.
Definition: grid.c:365
enum Event event_sdl2()
Fonction qui renvoie un événement en fonction du clavier en utilisant SDL2.
Definition: grid.c:540
void display(grid *G)
Fonction qui affiche le niveau chargé dans le terminal.
Definition: grid.c:201
char display_ncurses_input()
Fonction lit une touche au clavier avec ncurses et la renvoie.
Definition: grid.c:300
grid * init_level(const char *file_path)
Fonction qui charge le niveau de jeu depuis un fichier dans la structure grid.
Definition: grid.c:133
void display_ncurses_init()
Fonction qui initialise la bibliothèque d'affichage ncurses.
Definition: grid.c:224
void display_ncurses_end()
Fonction qui termine l'affichage ncurses.
Definition: grid.c:343
void display_ncurses_input_error()
Fonction qui affiche un message d'erreur.
Definition: grid.c:320
void display_ncurses_draw(grid *G)
Fonction qui affiche le niveau en paramètre dans le terminal avec ncurses.
Definition: grid.c:249
enum Event event_ncurses()
Fonction qui renvoie un événement en fonction du clavier en utilisant <ncurses.h>
Definition: grid.c:490
Fichier header contenant les structures de données pour traiter les niveaux du jeu sokoban.
CaseType
Structure indiquant quel caractère correspond à quel élément du niveau.
Definition: grid.h:48
@ PLAYER_GOAL
Superposition d'un joueur et d'un objectif.
Definition: grid.h:55
@ BOX
Une boîte.
Definition: grid.h:50
@ GOAL
Les objectifs.
Definition: grid.h:52
@ BOX_GOAL
Superposition d'une boîte et d'un objectif.
Definition: grid.h:54
@ NONE
Le vide.
Definition: grid.h:53
@ PLAYER
Le joueur.
Definition: grid.h:51
@ WALL
Un mur.
Definition: grid.h:49
Event
Structure indiquant les différents événements dans le jeu.
Definition: grid.h:66
@ EVENT_RIGHT
Bouger le jouer vers la droite.
Definition: grid.h:71
@ EVENT_DOWN
Bouger le joueur vers le bas.
Definition: grid.h:69
@ EVENT_LEFT
Bouger le joueur vers la gauche.
Definition: grid.h:68
@ EVENT_NONE
Tout autre événement qui n'est pas géré dans le jeu.
Definition: grid.h:72
@ EVENT_QUIT
Quitter le jeu.
Definition: grid.h:67
@ EVENT_UP
Bouger le joueur vers le haut.
Definition: grid.h:70
void(* handle_quit)()
Fonction de fermeture de <ncurses.h> ou SDL2.
Definition: main.c:48
Fichier header contenant les structures pour traiter le joueur et son mouvement.
Fichier header contenant la structure pour gérer SDL2 pour le jeu.
Cette structure contient les informations concernant le niveau du jeu et son contenu.
Definition: grid.h:84
enum CaseType ** game_grid
Tableau contenant les entités présents dans le jeu.
Definition: grid.h:85
player player
Structure pour stocker la position du joueur.
Definition: grid.h:90
int column_number
Nombre de colonnes de game_grid.
Definition: grid.h:86
int row_number
Nombre de lignes de game_grid.
Definition: grid.h:87
int goal_number
Nombre d'objectifs de game_grid.
Definition: grid.h:88
int box_over_goal_number
Nombre d'objectifs réussi de game_grid.
Definition: grid.h:89
int y
Coordonnée y du joueur.
Definition: player.h:48
int x
Coordonnée x du joueur.
Definition: player.h:47
SDL_Renderer * renderer
Pointeur sur le renderer.
Definition: sdl2.h:49
int width
Largeur de la fenêtre.
Definition: sdl2.h:50
int height
Hauteur de la fenêtre.
Definition: sdl2.h:51