Sokoban  1.1.3
Le fameux jeu Sokoban, poussez les boîtes !
grid.c
Aller à la documentation de ce fichier.
1 /*
2 Game level management functions for Sokoban
3 Copyright (C) 2022, 2023 Efe ERKEN
4 
5 This file is part of Sokoban
6 
7 Sokoban is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11 
12 Sokoban is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Sokoban. If not, see <https://www.gnu.org/licenses/>.
19 
20 SPDX-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 
56 grid *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 
105 void free_level(grid *G)
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 
133 grid *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 
201 void 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 
440 enum Event event()
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 }
490 enum Event event_ncurses()
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 
540 enum Event event_sdl2()
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 
598 void exit_routine(grid* G) {
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
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
grid * creer_level(int row, int column, int goals)
Fonction qui alloue la grille du jeu.
Definition: grid.c:56
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
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
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
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