Rapport : Labyrinthe
Table des matières
- 1. Introduction
- 2. Méthode de résolution
- 3. Tests
- 3.1. Compilation
- 3.2. Test de Codingame
- 3.2.1. Test 1 : Easy, follow the path, 1 direction
- 3.2.2. Test 2 : Easy, follow the path, 4 directions
- 3.2.3. Test 3 : Easy, empty
- 3.2.4. Test 4 : Several paths, no cycle
- 3.2.5. Test 5 : Several paths with cycles
- 3.2.6. Test 6 : Difficult with cycles
- 3.2.7. Test 7 : Difficult, long with cycles
- 3.2.8. Test 8 : Difficult, longer with cycles
- 3.3. Résumé des tests
- 4. Capture d’écran avec DDD
- 5. Solution de la communauté
1. Introduction
1.1. Principe de focntionnment
Url du challenge : https://www.codingame.com/ide/puzzle/the-labyrinth
Documentation Doxygen du code et du fichier de test : lien
L’objectif de ce challenge est d’explorer un labyrinthe. Dans ce labyrinthe vous incarnez Rick, il peut se déplacer dans 4 directions : Nord, Sud, Est et Ouest.
Rick utilise son tricordeur pour scanner la zone se trouvant autour de lui mais à cause des interférences, il ne peut scanner que les cellules situées dans un carré de 5 cases de côté et centré sur lui.
L’objectif est de trouver le poste de commande du labyrinthe. Une fois le poste de commande atteint, le compte-à-rebours d’une alarme se déclenche et vous avez un nombre limité de tours avant que l’alarme ne s’active. Une fois l’alarme activée, Rick est mort et vous avez perdu la partie.
1.2. Données fournies
Au premier tour de jeu, on reçoit les données suivantes :
- R : le nombre de lignes du labyrinthe
- C : le nombre de colonnes du labyrinthe
- A : le nombre de tours avant que l’alarme ne s’active
10 30 23
Ce qui signifie que le labyrinthe est composé de 10 lignes et 30 colonnes, et que Rick dispose de 23 tours avant que l’alarme ne s’active pour revenir au point de départ depuis le poste de commande.
Puis, à chaque tour de jeu, on reçoit les coordonnées de Rick et le labyrinthe. Les coordonnées de Rick sont données sous la forme de deux entiers : X et Y. Le labyrinthe est une matrice de R lignes et C colonnes. Chaque case est représentée par un caractère :
- `.` : case vide
- `#` : mur
- `?` : case inconnue
- `S` : case de départ de Rick
- `C` : case du poste de commande
3 6 ?????????????????????????????? ????.....????????????????????? ????#####????????????????????? ????..T..????????????????????? ????.....????????????????????? ????#####????????????????????? ?????????????????????????????? ?????????????????????????????? ?????????????????????????????? ??????????????????????????????
1.3. Données à fournir
À chaque tour de jeu, on doit fournir la direction dans laquelle Rick doit se déplacer. Les directions possibles sont :
- RIGHT
- LEFT
- UP
- DOWN
1.4. Contraintes
- 10 ≤ R ≤ 100
- 20 ≤ C ≤ 200
- 1 ≤ A ≤ 100
Temps de réponse pour un tour ≤ 150ms Il y a un seul caractère T et un seul caractère C dans le labyrinthe.
2. Méthode de résolution
Le principe de résolution est le suivant :
- On commence par explorer le labyrinthe en utilisant une pile pour stocker les coordonnées des cases à explorer.
- Tant que l’on a pas trouvé le poste de commande, on continue d’explorer le labyrinthe.
- Si on trouve le poste de commande et que le chemin reliant le poste de commande à la case de départ est inférieur au nombre de coups limite, on arrête d’explorer le labyrinthe.
- On se déplace ensuite vers le poste de commande en utilisant l’algorithme de Dijkstra.
- Enfin on se déplace vers la case de départ en utilisant l’algorithme de Dijkstra une nouvelle fois.
Exploration du labyrinthe
L’exploration du labyrinthe se fait en utilisant une pile.
- On commence par empiler les coordonnées de la case de départ.
- On dépile ensuite les coordonnées de la case à explorer.
- On empile ensuite les coordonnées des cases adjacentes à la case à explorer si elles sont vides et si elles n’ont pas déjà été explorées.
- On continue ainsi jusqu’à ce que la pile soit vide ou que l’on ait trouvé le poste de commande.
- Pour se déplacer à la case suivante, on utilise l’algorithme de Dijkstra.
2.1. Schéma de l’algorithme
Figure 1 : Schema de l’algorithme
2.2. Code
Le code est découpé en plusieurs parties:
- Les structures de données (pile, file, grille, coordonnées, etc.)
- Les fonctions relatives aux piles (création, ajout, suppression, etc.)
- Les fonctions relatives aux files (création, ajout, suppression, etc.)
- Les fonctions relatives à la grille (création, recherche, etc.)
- Les fonctions relatives au deplacement de Rick (recherche du chemin, dijkstra, etc.)
- La fonction main
/** * @brief Constantes pour les caractères de la grille */ enum { // map WALL = '#', ///< Mur EMPTY = '.', ///< Case vide START = 'T', ///< Case de départ et d'arrivée COMMAND = 'C', ///< Case de commande UNKNOWN = '?', ///< Case inconnue (non découverte) // custom UNEXPLORED = 'U', ///< Case non explorée EXPLORED = 'E', ///< Case explorée SKIPPED = 'S', ///< Case ignorée (aucun intérêt d'explorer cette case) PLANNED = 'N', ///< Case planifiée (à explorer) }; /** * @brief Structure de données pour une coordonnée */ typedef struct { int x; ///< La coordonnée x int y; ///< La coordonnée y } coord_s; /** * @brief Structure de données pour une grille */ typedef struct { char **grid; ///< La grille (tableau à deux dimensions de caractères) int nb_lines; ///< Le nombre de lignes de la grille int nb_columns; ///< Le nombre de colonnes de la grille } grid_s; /** * @brief Structure de données pour un élément de pile ou de file contenant une coordonnée */ typedef struct element_s { coord_s position; ///< La coordonnée struct element_s *previous; ///< L'élément précédent (NULL si premier élément) } element_s; /** * @brief Structure de données pour une pile (LIFO) */ typedef struct { element_s *last; ///< Le dernier élément de la pile (NULL si vide) } stack_s; /** * @brief Structure de données pour une file (FIFO) */ typedef struct { element_s *first; ///< Le premier élément de la file (NULL si vide) element_s *last; ///< Le dernier élément de la file (NULL si vide) } queue_s;
Les fonctions relatives aux piles sont les suivantes :
/** * @brief Crée une pile vide (LIFO) * @return La pile nouvellement créée */ stack_s *create_stack() { stack_s *stack = malloc(sizeof *stack); stack->last = NULL; return stack; } /** * @brief Empile une coordonnée sur la pile * @param stack La pile sur laquelle empiler les coordonnées * @param x La coordonnée x * @param y La coordonnée y */ void push_coordinate(stack_s *stack, int x, int y) { element_s *element = malloc(sizeof *element); element->position.x = x; element->position.y = y; element->previous = stack->last; stack->last = element; } /** * @brief Dépile une coordonnée de la pile et la retourne * @param stack La pile sur laquelle dépiler * @return La coordonnée dépilée */ coord_s pop(stack_s *stack) { element_s *element = stack->last; stack->last = element->previous; coord_s position = element->position; free(element); return position; } /** * @brief Retourne la taille de la pile (nombre d'éléments) * @param stack La pile dont on veut connaître la taille * @return La taille de la pile */ size_t size(stack_s *stack) { int n = 0; element_s *current = stack->last; while (current != NULL) { n++; current = current->previous; } return n; } /** * @brief Libère la pile (et ses éléments) * @param stack La pile à libérer */ void free_stack(stack_s *stack) { element_s *current = stack->last; while (current != NULL) { element_s *next = current->previous; free(current); current = next; } free(stack); }
Les fonctions relatives aux files sont les suivantes :
/** * @brief Crée une file vide (FIFO) * @return La file nouvellement créée */ queue_s *create_queue() { queue_s *queue = malloc(sizeof *queue); queue->first = NULL; queue->last = NULL; return queue; } /** * @brief Enfile des coordonnées à la file (à la fin) * @param queue La file sur laquelle ajouter les coordonnées * @param x La coordonnée x * @param y La coordonnée y */ void enqueue_coordinate(queue_s *queue, int x, int y) { element_s *element = malloc(sizeof *element); element->position.x = x; element->position.y = y; element->previous = NULL; if (queue->first == NULL) { queue->first = element; } else { queue->last->previous = element; } queue->last = element; } /** * @brief Defile des coordonnées de la file et les retourne * @param queue La file sur laquelle defiler les coordonnées * @return Les coordonnées défilées */ coord_s dequeue(queue_s *queue) { element_s *element = queue->first; queue->first = element->previous; coord_s position = element->position; free(element); return position; } /** * @brief Vérifie si la file est vide * @param queue La file à vérifier * @return true si la file est vide, false sinon */ bool is_empty_q(queue_s *queue) { return queue->first == NULL; } /** * @brief Vérifie si des coordonnées sont dans la file * @param queue La file à vérifier * @param x La coordonnée x * @param y La coordonnée y * @return true si les coordonnées sont dans la file, false sinon */ bool is_in_queue(queue_s *queue, int x, int y) { element_s *current = queue->first; while (current != NULL) { if (current->position.x == x && current->position.y == y) { return true; } current = current->previous; } return false; } /** * @brief Libère la file (et ses éléments) * @param queue La file à libérer */ void free_queue(queue_s *queue) { element_s *current = queue->first; while (current != NULL) { element_s *next = current->previous; free(current); current = next; } free(queue); }
/** * @brief Vérifie si une coordonnée est valide (dans la grille) * @param grid La grille * @param x La coordonnée x * @param y La coordonnée y * @return true si la coordonnée est valide, false sinon */ bool is_valid(grid_s *grid, int x, int y) { return x >= 0 && x < grid->nb_columns && y >= 0 && y < grid->nb_lines; } /** * @brief Vérifie si deux coordonnées sont voisines (adjacentes) (pas en diagonale) et non égales * @param x La coordonnée x de la première coordonnée * @param y La coordonnée y de la première coordonnée * @param x2 La coordonnée x2 de la deuxième coordonnée * @param y2 La coordonnée y2 de la deuxième coordonnée */ bool is_next_to(int x, int y, int x2, int y2) { return (x == x2 && (y == y2 - 1 || y == y2 + 1)) || (y == y2 && (x == x2 - 1 || x == x2 + 1)); } /** * @brief Lit l'entrée standard et modifie les coordonnées du joueur * @param grid La grille * @param x La coordonnée x du joueur * @param y La coordonnée y du joueur */ void read_input(grid_s *grid, int *x, int *y) { scanf("%d%d", y, x); for (int i = 0; i < grid->nb_lines; i++) { scanf("%s", grid->grid[i]); } } /** * @brief Initialise une grille avec une lettre donnée * @param nb_rows Le nombre de lignes * @param nb_cols Le nombre de colonnes * @param letter La lettre à mettre dans la grille * @return La grille nouvellement créée */ grid_s *create_grid(int nb_rows, int nb_cols, char letter) { grid_s *grid = malloc(sizeof *grid); grid->nb_lines = nb_rows; grid->nb_columns = nb_cols; grid->grid = malloc(nb_rows * sizeof *grid->grid); for (int i = 0; i < nb_rows; i++) { grid->grid[i] = malloc(nb_cols * sizeof *grid->grid[i]); for (int j = 0; j < nb_cols; j++) { grid->grid[i][j] = letter; } } return grid; } /** * @brief Recherche la position d'un caractère dans une grille * * La fonction parcourt la grille et renvoie la position du caractère. * Si le caractère n'est pas trouvé, la fonction renvoie la position (-1, -1). * * @param grid La grille à parcourir * @param c Le caractère à rechercher * @return La position du caractère dans la grille */ coord_s found_char(grid_s *grid, char c) { for (int i = 0; i < grid->nb_lines; i++) { for (int j = 0; j < grid->nb_columns; j++) { if (grid->grid[i][j] == c) { coord_s coord = {j, i}; return coord; } } } return (coord_s) {-1, -1}; } /** * @brief Indique si une case est inutile à explorer * * Une case est inutile à explorer si elle est entourée de murs ou de cases déjà visitées/connues. * * @param grid La grille dans laquelle se trouve la case * @param visited La grille des cases visitées * @param x La coordonnée x de la case * @param y La coordonnée y de la case */ bool is_useless(grid_s *grid, int x, int y) { // cul-de-sac int nb_walls = 0; for (int i = x - 1; i < x + 2; i++) { for (int j = y - 1; j < y + 2; j++) { if (is_valid(grid, i, j) && is_next_to(i, j, x, y) && grid->grid[j][i] == WALL) { nb_walls++; } } } if (nb_walls == 3) { return true; } // entouré de cases connues for (int i = x - 3; i < x + 4; i++) { for (int j = y - 3; j < y + 4; j++) { if (is_valid(grid, i, j) && !(i == x && j == y) && grid->grid[j][i] == UNKNOWN) { return false; } } } return true; } /** * @brief Libère une grille de lettres * @param grid La grille à libérer */ void free_grid(grid_s *grid) { for (int i = 0; i < grid->nb_lines; i++) { free(grid->grid[i]); } free(grid->grid); free(grid); } /** * @brief Libère une grille d'entiers * @param grid La grille à libérer * @param nb_rows Le nombre de lignes de la grille */ void free_int_grid(int **grid, int nb_rows) { for (int i = 0; i < nb_rows; i++) { free(grid[i]); } free(grid); }
La fonction dijkstra, qui implémente l’algorithme du même nom permet de trouver le plus court chemin entre deux points d’une grille. Elle renvoie une pile contenant les coordonnées du chemin le plus court.
/** * @brief Implémente l'algorithme de Dijkstra * * L'algorithme de Dijkstra permet de trouver le plus court chemin entre deux points d'une grille. * @see https://fr.wikipedia.org/wiki/Plus_court_chemin * @see https://fr.wikipedia.org/wiki/Algorithme_de_Dijkstra * * La variable `distances` est un tableau d'entiers qui contient la distance entre le point de départ et chaque case de la grille (ou -1 si la case n'est pas accessible). * La file contient les coordonnées des prochaines cases à calculer. * La pile contient les coordonnées du chemin le plus court. * * Si le chemin n'existe pas, la fonction renvoie NULL. * Si le chemin existe, la fonction renvoie une pile contenant les coordonnées du chemin. * @note Pour se déplacer, il faut dépiler la pile. * @warning La pile doit être libérée avec la fonction free_stack. * * @param grid La grille dans laquelle chercher le chemin * @param start_x La coordonnée x de départ * @param start_y La coordonnée y de départ * @param end_x La coordonnée x de destination * @param end_y La coordonnée y de destination * @return La pile contenant le chemin le plus court ou NULL si le chemin n'existe pas */ stack_s *dijkstra(grid_s *grid, int start_x, int start_y, int end_x, int end_y) { // On initialise un tableau de distances à -1 int **distances = malloc(grid->nb_lines * sizeof *distances); for (int i = 0; i < grid->nb_lines; i++) { distances[i] = malloc(grid->nb_columns * sizeof *distances[i]); for (int j = 0; j < grid->nb_columns; j++) { distances[i][j] = -1; } } // On initialise la distance de la case de départ à 0 distances[start_y][start_x] = 0; queue_s *queue = create_queue(); enqueue_coordinate(queue, start_x, start_y); bool found = false; // On parcourt la file jusqu'à trouver la destination ou jusqu'à ce qu'elle soit vide while (!found && !is_empty_q(queue)) { coord_s position = dequeue(queue); int x = position.x; int y = position.y; int distance = distances[y][x]; int i, j; for (i = x - 1; i < x + 2; i++) { for (j = y - 1; j < y + 2; j++) { if (is_valid(grid, i, j) && is_next_to(i, j, x, y)) { if (grid->grid[j][i] != WALL && grid->grid[j][i] != UNKNOWN && distances[j][i] == -1) { if (!is_in_queue(queue, i, j)) { enqueue_coordinate(queue, i, j); distances[j][i] = distance + 1; if (i == end_x && j == end_y) { found = true; break; } } } } } if (found) { break; } } } free_queue(queue); // si la case de destination est toujours à -1, on n'a pas trouvé de chemin, on renvoie NULL if (distances[end_y][end_x] == -1) { free_int_grid(distances, grid->nb_lines); return NULL; } // sinon, on reconstruit le chemin depuis la destination jusqu'à l'origine stack_s *path = create_stack(); int x = end_x; int y = end_y; int i = distances[end_y][end_x]; for (; i > 0; i--) { push_coordinate(path, x, y); int j, k; found = false; for (j = x - 1; j < x + 2; j++) { for (k = y - 1; k < y + 2; k++) { if (is_valid(grid, j, k) && is_next_to(j, k, x, y) && distances[k][j] == i - 1) { x = j; y = k; found = true; break; } } if (found) { break; } } } free_int_grid(distances, grid->nb_lines); return path; }
Les fonctions de déplacement
/** * @brief Renvoie dans quelle direction aller pour se rendre à une case * @param actual_x La coordonnée x de départ * @param actual_y La coordonnée y de départ * @param x La coordonnée x de destination * @param y La coordonnée y de destination */ void direction(int actual_x, int actual_y, int x, int y) { if (actual_x < x) { printf("RIGHT\n"); } else if (actual_x > x) { printf("LEFT\n"); } else if (actual_y < y) { printf("DOWN\n"); } else if (actual_y > y) { printf("UP\n"); } } /** * @brief Va à une case de destination depuis la position actuelle du joueur en utilisant l'algorithme de Dijkstra * * La fonction trouve le plus court chemin entre la position actuelle et la case de destination. * Elle déplace le joueur en suivant ce chemin et en lisant les entrées. * La dernière case du chemin est la case d'arrivée passée en paramètre et elle n'est pas lue. * * @note La fonction ne vérifie pas si la case de destination est accessible. * * @see dijkstra * @see read_input * * @param grid La grille dans laquelle se déplacer * @param x La coordonnée x de la position actuelle du joueur * @param y La coordonnée y de la position actuelle du joueur * @param end_x La coordonnée x de la case de destination * @param end_y La coordonnée y de la case de destination */ void go_to(grid_s *grid, int *x, int *y, int end_x, int end_y) { stack_s *path = dijkstra(grid, *x, *y, end_x, end_y); int i = size(path); for (; i > 1; i--) { coord_s coord = pop(path); direction(*x, *y, coord.x, coord.y); read_input(grid, x, y); } coord_s coord = pop(path); direction(*x, *y, coord.x, coord.y); free_stack(path); }
Fonction principale et boucle de jeu
/** * @brief Fonction principale * * La fonction commence par lire les entrées et les stocker. * Ensuite, elle explore la grille à la recherche de la salle de commande à l'aide d'une pile. * Lorsque la salle de commande est trouvée et qu'il existe un chemin de la salle de commande à la position de départ en moins de `rounds` tours, on arrête l'exploration. * On se déplace ensuite vers la salle de commande. * Enfin, on retourne à la position de départ. */ int main() { // Lecture des entrées int nb_rows, nb_cols, rounds, x, y; scanf("%d%d%d", &nb_rows, &nb_cols, &rounds); // Initialisation des structures grid_s *grid = create_grid(nb_rows, nb_cols, UNKNOWN); grid_s *visited = create_grid(nb_rows, nb_cols, UNEXPLORED); stack_s *stack = create_stack(); // Exploration de la grille while (1) { read_input(grid, &x, &y); visited->grid[y][x] = EXPLORED; coord_s command = found_char(grid, COMMAND); if (command.x != -1) { coord_s start = found_char(grid, START); stack_s *path = dijkstra(grid, command.x, command.y, start.x, start.y); if (path != NULL) { if ((int) size(path) <= rounds) { free_stack(path); break; } free_stack(path); } } for (int i = x - 1; i < x + 2; i++) { for (int j = y - 1; j < y + 2; j++) { if (is_valid(grid, i, j) && is_next_to(i, j, x, y)) { if (grid->grid[j][i] == EMPTY && visited->grid[j][i] == UNEXPLORED) { push_coordinate(stack, i, j); visited->grid[j][i] = PLANNED; } } } } coord_s next = pop(stack); while (is_useless(grid, next.x, next.y) || grid->grid[next.y][next.x] == COMMAND) { visited->grid[next.y][next.x] = SKIPPED; next = pop(stack); } go_to(grid, &x, &y, next.x, next.y); } // Déplacement vers la salle de commande coord_s command = found_char(grid, COMMAND); go_to(grid, &x, &y, command.x, command.y); read_input(grid, &x, &y); // Retour à la position de départ coord_s start = found_char(grid, START); go_to(grid, &x, &y, start.x, start.y); // Libération de la mémoire free_grid(grid); free_grid(visited); free_stack(stack); return 0; }
2.3. Implémentation complète
/** * @file main.c * @author GIRARD Jules * @date 28/04/2023 * @version 1.0 * @brief Codingame : Le labyrinthe * * Le but de ce projet est de résoudre le problème du labyrinthe de Codingame. * Le programme doit lire une grille de labyrinthe et trouver le chemin le plus court pour atteindre la case de commande et revenir à la case de départ. * @see https://www.codingame.com/training/hard/the-labyrinth */ #include <stdlib.h> #include <stdio.h> #include <stdbool.h> #include <string.h> /** * @brief Constantes pour les caractères de la grille */ enum { // map WALL = '#', ///< Mur EMPTY = '.', ///< Case vide START = 'T', ///< Case de départ et d'arrivée COMMAND = 'C', ///< Case de commande UNKNOWN = '?', ///< Case inconnue (non découverte) // custom UNEXPLORED = 'U', ///< Case non explorée EXPLORED = 'E', ///< Case explorée SKIPPED = 'S', ///< Case ignorée (aucun intérêt d'explorer cette case) PLANNED = 'N', ///< Case planifiée (à explorer) }; /** * @brief Structure de données pour une coordonnée */ typedef struct { int x; ///< La coordonnée x int y; ///< La coordonnée y } coord_s; /** * @brief Structure de données pour une grille */ typedef struct { char **grid; ///< La grille (tableau à deux dimensions de caractères) int nb_lines; ///< Le nombre de lignes de la grille int nb_columns; ///< Le nombre de colonnes de la grille } grid_s; /** * @brief Structure de données pour un élément de pile ou de file contenant une coordonnée */ typedef struct element_s { coord_s position; ///< La coordonnée struct element_s *previous; ///< L'élément précédent (NULL si premier élément) } element_s; /** * @brief Structure de données pour une pile (LIFO) */ typedef struct { element_s *last; ///< Le dernier élément de la pile (NULL si vide) } stack_s; /** * @brief Structure de données pour une file (FIFO) */ typedef struct { element_s *first; ///< Le premier élément de la file (NULL si vide) element_s *last; ///< Le dernier élément de la file (NULL si vide) } queue_s; /** * @brief Crée une pile vide (LIFO) * @return La pile nouvellement créée */ stack_s *create_stack() { stack_s *stack = malloc(sizeof *stack); stack->last = NULL; return stack; } /** * @brief Empile une coordonnée sur la pile * @param stack La pile sur laquelle empiler les coordonnées * @param x La coordonnée x * @param y La coordonnée y */ void push_coordinate(stack_s *stack, int x, int y) { element_s *element = malloc(sizeof *element); element->position.x = x; element->position.y = y; element->previous = stack->last; stack->last = element; } /** * @brief Dépile une coordonnée de la pile et la retourne * @param stack La pile sur laquelle dépiler * @return La coordonnée dépilée */ coord_s pop(stack_s *stack) { element_s *element = stack->last; stack->last = element->previous; coord_s position = element->position; free(element); return position; } /** * @brief Retourne la taille de la pile (nombre d'éléments) * @param stack La pile dont on veut connaître la taille * @return La taille de la pile */ size_t size(stack_s *stack) { int n = 0; element_s *current = stack->last; while (current != NULL) { n++; current = current->previous; } return n; } /** * @brief Libère la pile (et ses éléments) * @param stack La pile à libérer */ void free_stack(stack_s *stack) { element_s *current = stack->last; while (current != NULL) { element_s *next = current->previous; free(current); current = next; } free(stack); } /** * @brief Crée une file vide (FIFO) * @return La file nouvellement créée */ queue_s *create_queue() { queue_s *queue = malloc(sizeof *queue); queue->first = NULL; queue->last = NULL; return queue; } /** * @brief Enfile des coordonnées à la file (à la fin) * @param queue La file sur laquelle ajouter les coordonnées * @param x La coordonnée x * @param y La coordonnée y */ void enqueue_coordinate(queue_s *queue, int x, int y) { element_s *element = malloc(sizeof *element); element->position.x = x; element->position.y = y; element->previous = NULL; if (queue->first == NULL) { queue->first = element; } else { queue->last->previous = element; } queue->last = element; } /** * @brief Defile des coordonnées de la file et les retourne * @param queue La file sur laquelle defiler les coordonnées * @return Les coordonnées défilées */ coord_s dequeue(queue_s *queue) { element_s *element = queue->first; queue->first = element->previous; coord_s position = element->position; free(element); return position; } /** * @brief Vérifie si la file est vide * @param queue La file à vérifier * @return true si la file est vide, false sinon */ bool is_empty_q(queue_s *queue) { return queue->first == NULL; } /** * @brief Vérifie si des coordonnées sont dans la file * @param queue La file à vérifier * @param x La coordonnée x * @param y La coordonnée y * @return true si les coordonnées sont dans la file, false sinon */ bool is_in_queue(queue_s *queue, int x, int y) { element_s *current = queue->first; while (current != NULL) { if (current->position.x == x && current->position.y == y) { return true; } current = current->previous; } return false; } /** * @brief Libère la file (et ses éléments) * @param queue La file à libérer */ void free_queue(queue_s *queue) { element_s *current = queue->first; while (current != NULL) { element_s *next = current->previous; free(current); current = next; } free(queue); } /** * @brief Vérifie si une coordonnée est valide (dans la grille) * @param grid La grille * @param x La coordonnée x * @param y La coordonnée y * @return true si la coordonnée est valide, false sinon */ bool is_valid(grid_s *grid, int x, int y) { return x >= 0 && x < grid->nb_columns && y >= 0 && y < grid->nb_lines; } /** * @brief Vérifie si deux coordonnées sont voisines (adjacentes) (pas en diagonale) et non égales * @param x La coordonnée x de la première coordonnée * @param y La coordonnée y de la première coordonnée * @param x2 La coordonnée x2 de la deuxième coordonnée * @param y2 La coordonnée y2 de la deuxième coordonnée */ bool is_next_to(int x, int y, int x2, int y2) { return (x == x2 && (y == y2 - 1 || y == y2 + 1)) || (y == y2 && (x == x2 - 1 || x == x2 + 1)); } /** * @brief Lit l'entrée standard et modifie les coordonnées du joueur * @param grid La grille * @param x La coordonnée x du joueur * @param y La coordonnée y du joueur */ void read_input(grid_s *grid, int *x, int *y) { scanf("%d%d", y, x); for (int i = 0; i < grid->nb_lines; i++) { scanf("%s", grid->grid[i]); } } /** * @brief Initialise une grille avec une lettre donnée * @param nb_rows Le nombre de lignes * @param nb_cols Le nombre de colonnes * @param letter La lettre à mettre dans la grille * @return La grille nouvellement créée */ grid_s *create_grid(int nb_rows, int nb_cols, char letter) { grid_s *grid = malloc(sizeof *grid); grid->nb_lines = nb_rows; grid->nb_columns = nb_cols; grid->grid = malloc(nb_rows * sizeof *grid->grid); for (int i = 0; i < nb_rows; i++) { grid->grid[i] = malloc(nb_cols * sizeof *grid->grid[i]); for (int j = 0; j < nb_cols; j++) { grid->grid[i][j] = letter; } } return grid; } /** * @brief Recherche la position d'un caractère dans une grille * * La fonction parcourt la grille et renvoie la position du caractère. * Si le caractère n'est pas trouvé, la fonction renvoie la position (-1, -1). * * @param grid La grille à parcourir * @param c Le caractère à rechercher * @return La position du caractère dans la grille */ coord_s found_char(grid_s *grid, char c) { for (int i = 0; i < grid->nb_lines; i++) { for (int j = 0; j < grid->nb_columns; j++) { if (grid->grid[i][j] == c) { coord_s coord = {j, i}; return coord; } } } return (coord_s) {-1, -1}; } /** * @brief Indique si une case est inutile à explorer * * Une case est inutile à explorer si elle est entourée de murs ou de cases déjà visitées/connues. * * @param grid La grille dans laquelle se trouve la case * @param visited La grille des cases visitées * @param x La coordonnée x de la case * @param y La coordonnée y de la case */ bool is_useless(grid_s *grid, int x, int y) { // cul-de-sac int nb_walls = 0; for (int i = x - 1; i < x + 2; i++) { for (int j = y - 1; j < y + 2; j++) { if (is_valid(grid, i, j) && is_next_to(i, j, x, y) && grid->grid[j][i] == WALL) { nb_walls++; } } } if (nb_walls == 3) { return true; } // entouré de cases connues for (int i = x - 3; i < x + 4; i++) { for (int j = y - 3; j < y + 4; j++) { if (is_valid(grid, i, j) && !(i == x && j == y) && grid->grid[j][i] == UNKNOWN) { return false; } } } return true; } /** * @brief Libère une grille de lettres * @param grid La grille à libérer */ void free_grid(grid_s *grid) { for (int i = 0; i < grid->nb_lines; i++) { free(grid->grid[i]); } free(grid->grid); free(grid); } /** * @brief Libère une grille d'entiers * @param grid La grille à libérer * @param nb_rows Le nombre de lignes de la grille */ void free_int_grid(int **grid, int nb_rows) { for (int i = 0; i < nb_rows; i++) { free(grid[i]); } free(grid); } /** * @brief Implémente l'algorithme de Dijkstra * * L'algorithme de Dijkstra permet de trouver le plus court chemin entre deux points d'une grille. * @see https://fr.wikipedia.org/wiki/Plus_court_chemin * @see https://fr.wikipedia.org/wiki/Algorithme_de_Dijkstra * * La variable `distances` est un tableau d'entiers qui contient la distance entre le point de départ et chaque case de la grille (ou -1 si la case n'est pas accessible). * La file contient les coordonnées des prochaines cases à calculer. * La pile contient les coordonnées du chemin le plus court. * * Si le chemin n'existe pas, la fonction renvoie NULL. * Si le chemin existe, la fonction renvoie une pile contenant les coordonnées du chemin. * @note Pour se déplacer, il faut dépiler la pile. * @warning La pile doit être libérée avec la fonction free_stack. * * @param grid La grille dans laquelle chercher le chemin * @param start_x La coordonnée x de départ * @param start_y La coordonnée y de départ * @param end_x La coordonnée x de destination * @param end_y La coordonnée y de destination * @return La pile contenant le chemin le plus court ou NULL si le chemin n'existe pas */ stack_s *dijkstra(grid_s *grid, int start_x, int start_y, int end_x, int end_y) { // On initialise un tableau de distances à -1 int **distances = malloc(grid->nb_lines * sizeof *distances); for (int i = 0; i < grid->nb_lines; i++) { distances[i] = malloc(grid->nb_columns * sizeof *distances[i]); for (int j = 0; j < grid->nb_columns; j++) { distances[i][j] = -1; } } // On initialise la distance de la case de départ à 0 distances[start_y][start_x] = 0; queue_s *queue = create_queue(); enqueue_coordinate(queue, start_x, start_y); bool found = false; // On parcourt la file jusqu'à trouver la destination ou jusqu'à ce qu'elle soit vide while (!found && !is_empty_q(queue)) { coord_s position = dequeue(queue); int x = position.x; int y = position.y; int distance = distances[y][x]; int i, j; for (i = x - 1; i < x + 2; i++) { for (j = y - 1; j < y + 2; j++) { if (is_valid(grid, i, j) && is_next_to(i, j, x, y)) { if (grid->grid[j][i] != WALL && grid->grid[j][i] != UNKNOWN && distances[j][i] == -1) { if (!is_in_queue(queue, i, j)) { enqueue_coordinate(queue, i, j); distances[j][i] = distance + 1; if (i == end_x && j == end_y) { found = true; break; } } } } } if (found) { break; } } } free_queue(queue); // si la case de destination est toujours à -1, on n'a pas trouvé de chemin, on renvoie NULL if (distances[end_y][end_x] == -1) { free_int_grid(distances, grid->nb_lines); return NULL; } // sinon, on reconstruit le chemin depuis la destination jusqu'à l'origine stack_s *path = create_stack(); int x = end_x; int y = end_y; int i = distances[end_y][end_x]; for (; i > 0; i--) { push_coordinate(path, x, y); int j, k; found = false; for (j = x - 1; j < x + 2; j++) { for (k = y - 1; k < y + 2; k++) { if (is_valid(grid, j, k) && is_next_to(j, k, x, y) && distances[k][j] == i - 1) { x = j; y = k; found = true; break; } } if (found) { break; } } } free_int_grid(distances, grid->nb_lines); return path; } /** * @brief Renvoie dans quelle direction aller pour se rendre à une case * @param actual_x La coordonnée x de départ * @param actual_y La coordonnée y de départ * @param x La coordonnée x de destination * @param y La coordonnée y de destination */ void direction(int actual_x, int actual_y, int x, int y) { if (actual_x < x) { printf("RIGHT\n"); } else if (actual_x > x) { printf("LEFT\n"); } else if (actual_y < y) { printf("DOWN\n"); } else if (actual_y > y) { printf("UP\n"); } } /** * @brief Va à une case de destination depuis la position actuelle du joueur en utilisant l'algorithme de Dijkstra * * La fonction trouve le plus court chemin entre la position actuelle et la case de destination. * Elle déplace le joueur en suivant ce chemin et en lisant les entrées. * La dernière case du chemin est la case d'arrivée passée en paramètre et elle n'est pas lue. * * @note La fonction ne vérifie pas si la case de destination est accessible. * * @see dijkstra * @see read_input * * @param grid La grille dans laquelle se déplacer * @param x La coordonnée x de la position actuelle du joueur * @param y La coordonnée y de la position actuelle du joueur * @param end_x La coordonnée x de la case de destination * @param end_y La coordonnée y de la case de destination */ void go_to(grid_s *grid, int *x, int *y, int end_x, int end_y) { stack_s *path = dijkstra(grid, *x, *y, end_x, end_y); int i = size(path); for (; i > 1; i--) { coord_s coord = pop(path); direction(*x, *y, coord.x, coord.y); read_input(grid, x, y); } coord_s coord = pop(path); direction(*x, *y, coord.x, coord.y); free_stack(path); } /** * @brief Fonction principale * * La fonction commence par lire les entrées et les stocker. * Ensuite, elle explore la grille à la recherche de la salle de commande à l'aide d'une pile. * Lorsque la salle de commande est trouvée et qu'il existe un chemin de la salle de commande à la position de départ en moins de `rounds` tours, on arrête l'exploration. * On se déplace ensuite vers la salle de commande. * Enfin, on retourne à la position de départ. */ int main() { // Lecture des entrées int nb_rows, nb_cols, rounds, x, y; scanf("%d%d%d", &nb_rows, &nb_cols, &rounds); // Initialisation des structures grid_s *grid = create_grid(nb_rows, nb_cols, UNKNOWN); grid_s *visited = create_grid(nb_rows, nb_cols, UNEXPLORED); stack_s *stack = create_stack(); // Exploration de la grille while (1) { read_input(grid, &x, &y); visited->grid[y][x] = EXPLORED; coord_s command = found_char(grid, COMMAND); if (command.x != -1) { coord_s start = found_char(grid, START); stack_s *path = dijkstra(grid, command.x, command.y, start.x, start.y); if (path != NULL) { if ((int) size(path) <= rounds) { free_stack(path); break; } free_stack(path); } } for (int i = x - 1; i < x + 2; i++) { for (int j = y - 1; j < y + 2; j++) { if (is_valid(grid, i, j) && is_next_to(i, j, x, y)) { if (grid->grid[j][i] == EMPTY && visited->grid[j][i] == UNEXPLORED) { push_coordinate(stack, i, j); visited->grid[j][i] = PLANNED; } } } } coord_s next = pop(stack); while (is_useless(grid, next.x, next.y) || grid->grid[next.y][next.x] == COMMAND) { visited->grid[next.y][next.x] = SKIPPED; next = pop(stack); } go_to(grid, &x, &y, next.x, next.y); } // Déplacement vers la salle de commande coord_s command = found_char(grid, COMMAND); go_to(grid, &x, &y, command.x, command.y); read_input(grid, &x, &y); // Retour à la position de départ coord_s start = found_char(grid, START); go_to(grid, &x, &y, start.x, start.y); // Libération de la mémoire free_grid(grid); free_grid(visited); free_stack(stack); return 0; }
3. Tests
3.1. Compilation
Compilation avec gcc :
✅ Compilation sans erreur/warning OK
3.2. Test de Codingame
Les tests sont automatisés grace au fichier fake_io qui simule les entrées/sorties fournient par Codingame et vérifie que les règles soient respectées. Pour utiliser les mécanisme du fichier fake_io.c, le fichier source est compilé avec l’option: « –include fake_io.c »
3.2.1. Test 1 : Easy, follow the path, 1 direction
Input:
30 15 2 2 1200 7 ############################## ############################## ############################## ############################## ############################## ############################## #####T......C################# ############################## ############################## ############################## ############################## ############################## ############################## ############################## ##############################
Output:
RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT LEFT LEFT LEFT LEFT LEFT LEFT LEFT Success: Kirk made it out in one piece!
Valgrind:
==59060== Memcheck, a memory error detector ==59060== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59060== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59060== Command: ./temp/code tests/input1.txt ==59060== ==59060== ==59060== HEAP SUMMARY: ==59060== in use at exit: 0 bytes in 0 blocks ==59060== total heap usage: 276 allocs, 276 frees, 27,544 bytes allocated ==59060== ==59060== All heap blocks were freed -- no leaks are possible ==59060== ==59060== For lists of detected and suppressed errors, rerun with: -s ==59060== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 1: Easy, follow the path, 1 direction ✅ Test passed ✅ Valgrind passed
3.2.2. Test 2 : Easy, follow the path, 4 directions
Input:
30 15 2 2 1200 21 #####################.....#### ########........#.############ #################.#......T#### ############...#..#.#######.## ######...##.#######...#####.## ######...##.#C..#####.#####.## ######...##.###.......######## ###########.#.########.......# #######..##...##....########## #######################..##### ######.....############..##### ######.....############.###### ######.....#########....###### #####################..####### ##############################
Output:
LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN RIGHT RIGHT DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT UP LEFT LEFT RIGHT RIGHT DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT Success: Kirk made it out in one piece!
Valgrind:
==59070== Memcheck, a memory error detector ==59070== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59070== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59070== Command: ./temp/code tests/input2.txt ==59070== ==59070== ==59070== HEAP SUMMARY: ==59070== in use at exit: 0 bytes in 0 blocks ==59070== total heap usage: 621 allocs, 621 frees, 54,592 bytes allocated ==59070== ==59070== All heap blocks were freed -- no leaks are possible ==59070== ==59070== For lists of detected and suppressed errors, rerun with: -s ==59070== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 2: Easy, follow the path, 4 directions ✅ Test passed ✅ Valgrind passed
3.2.3. Test 3 : Easy, empty
Input:
30 15 2 2 1200 39 ############################## #T...........................# ##...........................# #............................# #............................# #............................# #............................# #............................# #............................# #............................# #............................# #............................# #............................# #...........................C# ##############################
Output:
RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN LEFT LEFT UP UP UP UP UP UP UP UP UP UP LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP UP LEFT Success: Kirk made it out in one piece!
Valgrind:
==59083== Memcheck, a memory error detector ==59083== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59083== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59083== Command: ./temp/code tests/input3.txt ==59083== ==59083== ==59083== HEAP SUMMARY: ==59083== in use at exit: 0 bytes in 0 blocks ==59083== total heap usage: 1,359 allocs, 1,359 frees, 97,864 bytes allocated ==59083== ==59083== All heap blocks were freed -- no leaks are possible ==59083== ==59083== For lists of detected and suppressed errors, rerun with: -s ==59083== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 3: Easy, empty ✅ Test passed ✅ Valgrind passed
3.2.4. Test 4 : Several paths, no cycle
Input:
30 15 2 2 1200 68 ############################## ##.###########.############### #...#.######.#......#......T## ###.#.######.#.######.######## ##...........#.######.######## ###.#.######......###.######## #...#.###....#.##.###.######## #.############.##.....######## #......##......############### ###.###############..........# ###.#####......####.########.# ###.......####............#### ##############.######.###.##.# ####C..........###....###....# ##############################
Output:
LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN DOWN DOWN DOWN LEFT LEFT LEFT LEFT UP UP LEFT LEFT LEFT DOWN DOWN DOWN LEFT LEFT LEFT LEFT RIGHT RIGHT RIGHT RIGHT UP UP UP UP UP UP RIGHT RIGHT LEFT LEFT DOWN DOWN DOWN LEFT LEFT DOWN LEFT LEFT RIGHT RIGHT UP UP UP DOWN LEFT LEFT LEFT LEFT LEFT LEFT LEFT DOWN UP UP DOWN LEFT LEFT DOWN DOWN LEFT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT LEFT LEFT DOWN DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP UP LEFT LEFT LEFT LEFT LEFT DOWN LEFT LEFT LEFT LEFT LEFT LEFT UP UP UP LEFT LEFT UP UP RIGHT RIGHT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT RIGHT RIGHT UP UP UP UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT Success: Kirk made it out in one piece!
Valgrind:
==59093== Memcheck, a memory error detector ==59093== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59093== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59093== Command: ./temp/code tests/input4.txt ==59093== ==59093== ==59093== HEAP SUMMARY: ==59093== in use at exit: 0 bytes in 0 blocks ==59093== total heap usage: 2,590 allocs, 2,590 frees, 203,776 bytes allocated ==59093== ==59093== All heap blocks were freed -- no leaks are possible ==59093== ==59093== For lists of detected and suppressed errors, rerun with: -s ==59093== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 4: Several paths, no cycle ✅ Test passed ✅ Valgrind passed
3.2.5. Test 5 : Several paths with cycles
Input:
30 15 2 2 1200 42 ############################## #.##.....#..........T........# #.########.#################.# #.##.......................#.# #.##.##########.##########.#.# #.##.......................#.# #.########.#########.#######.# #.########...........#######.# #.########.#########.#######.# #.##.......................#.# #.##.##########.##########.#.# #.##.......................#.# #.########.#########.#######.# #.##.....#C###########.....#.# ##############################
Output:
RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN UP UP UP UP UP UP UP UP UP UP UP LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT LEFT LEFT LEFT LEFT LEFT UP UP LEFT LEFT LEFT LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT LEFT LEFT LEFT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN UP UP RIGHT RIGHT RIGHT RIGHT RIGHT UP UP LEFT LEFT LEFT LEFT LEFT UP UP UP UP RIGHT RIGHT RIGHT RIGHT RIGHT UP UP LEFT LEFT LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT Success: Kirk made it out in one piece!
Valgrind:
==59103== Memcheck, a memory error detector ==59103== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59103== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59103== Command: ./temp/code tests/input5.txt ==59103== ==59103== ==59103== HEAP SUMMARY: ==59103== in use at exit: 0 bytes in 0 blocks ==59103== total heap usage: 3,369 allocs, 3,369 frees, 269,128 bytes allocated ==59103== ==59103== All heap blocks were freed -- no leaks are possible ==59103== ==59103== For lists of detected and suppressed errors, rerun with: -s ==59103== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 5: Several paths with cycles ✅ Test passed ✅ Valgrind passed
3.2.6. Test 6 : Difficult with cycles
Input:
30 15 2 2 1200 33 ############################## ##.##....#.##.....##.###.....# ##....##......####.......###.# #..##.##.#.##.####.##.##.###.# ##.##.....#........##C.......# #.....###...###......###.##### ###.#.###.#.....##..........## #.........###.###.##........## ###.###.#............###.##..# ###.....###.###.#........##.## #.########...##.##.##.#.#...## #.............###..#.#......## #.####.######.###.#######...## ###........T#.......##......## ##############################
Output:
LEFT LEFT LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT LEFT UP UP RIGHT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT RIGHT DOWN DOWN DOWN DOWN DOWN DOWN LEFT LEFT UP UP UP DOWN LEFT LEFT RIGHT RIGHT DOWN DOWN LEFT LEFT RIGHT RIGHT UP UP UP RIGHT RIGHT UP UP UP UP LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT UP UP UP LEFT LEFT LEFT LEFT DOWN LEFT LEFT LEFT DOWN UP LEFT LEFT LEFT DOWN DOWN DOWN RIGHT LEFT DOWN UP LEFT LEFT UP LEFT LEFT LEFT UP UP UP DOWN LEFT LEFT LEFT LEFT LEFT DOWN DOWN RIGHT DOWN RIGHT RIGHT DOWN UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT RIGHT DOWN DOWN UP UP LEFT LEFT LEFT DOWN DOWN LEFT LEFT LEFT DOWN DOWN LEFT LEFT DOWN DOWN LEFT LEFT DOWN DOWN LEFT DOWN LEFT LEFT LEFT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT Success: Kirk made it out in one piece!
Valgrind:
==59113== Memcheck, a memory error detector ==59113== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59113== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59113== Command: ./temp/code tests/input6.txt ==59113== ==59113== ==59113== HEAP SUMMARY: ==59113== in use at exit: 0 bytes in 0 blocks ==59113== total heap usage: 9,368 allocs, 9,368 frees, 388,296 bytes allocated ==59113== ==59113== All heap blocks were freed -- no leaks are possible ==59113== ==59113== For lists of detected and suppressed errors, rerun with: -s ==59113== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 6: Difficult with cycles ✅ Test passed ✅ Valgrind passed
3.2.7. Test 7 : Difficult, long with cycles
Input:
30 15 2 2 1200 70 ############################## #............................# #.#######################.#..# #.....T.................#.#..# #.....#.................#.#..# #.#######################.#..# #.....##......##......#....### #...####..##..##..##..#..#...# #.........##......##.....#...# ###########################.## #......#......#..............# #...C..#.....................# #...#..####################..# #............................# ##############################
Output:
RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP LEFT LEFT DOWN LEFT LEFT UP LEFT LEFT DOWN DOWN DOWN RIGHT RIGHT RIGHT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT RIGHT UP UP RIGHT RIGHT DOWN RIGHT RIGHT DOWN LEFT DOWN DOWN RIGHT DOWN DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP UP LEFT RIGHT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP UP UP UP LEFT UP UP LEFT UP UP UP UP UP LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT Success: Kirk made it out in one piece!
Valgrind:
==59123== Memcheck, a memory error detector ==59123== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59123== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59123== Command: ./temp/code tests/input7.txt ==59123== ==59123== ==59123== HEAP SUMMARY: ==59123== in use at exit: 0 bytes in 0 blocks ==59123== total heap usage: 3,325 allocs, 3,325 frees, 258,488 bytes allocated ==59123== ==59123== All heap blocks were freed -- no leaks are possible ==59123== ==59123== For lists of detected and suppressed errors, rerun with: -s ==59123== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 7: Difficult, long with cycles ✅ Test passed ✅ Valgrind passed
3.2.8. Test 8 : Difficult, longer with cycles
Input:
30 15 2 2 1200 71 ############################## #............................# #..####################......# #.....................#..C...# #............###..##..#..#...# ##.########################### #...#.....##......##.........# #...#..#..##..##..##..####...# ###....#......##......##.....# #..#.#######################.# #..#.#................#......# #..#.#................T......# #..#.#######################.# #............................# ##############################
Output:
RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP UP UP UP UP RIGHT RIGHT UP UP RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT RIGHT RIGHT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN LEFT LEFT LEFT LEFT UP UP LEFT LEFT LEFT LEFT DOWN DOWN LEFT LEFT LEFT LEFT UP UP LEFT LEFT LEFT DOWN DOWN LEFT LEFT UP UP LEFT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN LEFT UP LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT UP UP RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT DOWN DOWN RIGHT RIGHT LEFT LEFT UP UP LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT DOWN DOWN DOWN DOWN DOWN DOWN RIGHT DOWN RIGHT DOWN DOWN DOWN DOWN DOWN RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT UP UP LEFT LEFT LEFT LEFT LEFT LEFT Success: Kirk made it out in one piece!
Valgrind:
==59133== Memcheck, a memory error detector ==59133== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. ==59133== Using Valgrind-3.20.0 and LibVEX; rerun with -h for copyright info ==59133== Command: ./temp/code tests/input8.txt ==59133== ==59133== ==59133== HEAP SUMMARY: ==59133== in use at exit: 0 bytes in 0 blocks ==59133== total heap usage: 3,928 allocs, 3,928 frees, 301,256 bytes allocated ==59133== ==59133== All heap blocks were freed -- no leaks are possible ==59133== ==59133== For lists of detected and suppressed errors, rerun with: -s ==59133== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Run test:
Test 8: Difficult, longer with cycles ✅ Test passed ✅ Valgrind passed
3.3. Résumé des tests
Nom du test | Status | Valgrind | Test 1: Easy, follow the path, 1 direction | ✅ | ✅ | Test 2: Easy, follow the path, 4 directions | ✅ | ✅ | Test 3: Easy, empty | ✅ | ✅ | Test 4: Several paths, no cycle | ✅ | ✅ | Test 5: Several paths with cycles | ✅ | ✅ | Test 6: Difficult with cycles | ✅ | ✅ | Test 7: Difficult, long with cycles | ✅ | ✅ | Test 8: Difficult, longer with cycles | ✅ | ✅ | Passed: 8 (100.0%) Failed: 0 (0.0%) Valgrind passed: 8 (100.0%) Valgrind failed: 0 (0.0%)
4. Capture d’écran avec DDD
Figure 2 : Capture d’écran d’une pile (Test 3)
Figure 3 : Capture d’écran d’une file
5. Solution de la communauté
Solution de Nefalas : https://www.codingame.com/training/hard/the-labyrinth/solution?id=28978359
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #define SIZE 100 * 200 typedef struct Pos { int row; int col; struct Pos *prev; } pos; pos queue[SIZE]; int front = -1, rear = -1; void enqueue(pos p) { if (rear < SIZE - 1) { if (front == -1) front = 0; queue[++rear] = p; } } pos *dequeue() { if (front >= 0 && front <= rear) { return &queue[front++]; } return NULL; } int queueSize() { if (front == -1 || front > rear) { return 0; } return rear - front + 1; } void resetQueue() { front = -1; rear = -1; } const int dr[] = {-1, 1, 0, 0}; const int dc[] = {0, 0, 1, -1}; /** * Auto-generated code below aims at helping you parse * the standard input according to the problem statement. **/ int main() { // number of rows. int R; // number of columns. int C; // number of rounds between the time the alarm countdown is activated and the time the alarm goes off. int A; scanf("%d%d%d", &R, &C, &A); bool goingBack = false; // game loop while (1) { // row where Rick is located. int KR; // column where Rick is located. int KC; scanf("%d%d", &KR, &KC); bool foundRoom = false; char grid[R][C + 1]; for (int i = 0; i < R; i++) { // C of the characters in '#.TC?' (i.e. one line of the ASCII maze). scanf("%s", grid[i]); if (strchr(grid[i], 'C') != NULL) foundRoom = true; } if (grid[KR][KC] == 'C') goingBack = true; bool visited[R * C]; for (int i = 0; i < R * C; i++) { visited[i] = false; } visited[KR * C + KC] = true; pos start; start.row = KR; start.col = KC; start.prev = NULL; enqueue(start); pos *dest = NULL; while (queueSize() > 0) { pos *node = dequeue(); if (node == NULL) { return 1; } int index = node->row * C + node->col; char c = grid[node->row][node->col]; char target = goingBack ? 'T' : foundRoom ? 'C' : '?'; if (c == target) { dest = node; break; } else { for (int i = 0; i < 4; i++) { int nRow = node->row + dr[i]; int nCol = node->col + dc[i]; if (nRow >= 0 && nRow < R && nCol >= 0 && nCol < C) { int nIndex = nRow * C + nCol; int nC = grid[nRow][nCol]; if (!visited[nIndex] && nC != '#' && !(goingBack && nC == '?')) { pos neighbour; neighbour.row = nRow; neighbour.col = nCol; neighbour.prev = node; enqueue(neighbour); visited[nIndex] = true; } } } } } pos *nextPos = dest; while (nextPos->prev->prev != NULL) { nextPos = nextPos->prev; } if (nextPos != NULL) { for (int i = 0; i < 4; i++) { if (nextPos->row + dr[i] == KR && nextPos->col + dc[i] == KC) { switch (i) { case 0: printf("DOWN\n"); break; case 1: printf("UP\n"); break; case 2: printf("LEFT\n"); break; case 3: printf("RIGHT\n"); break; } break; } } } // Write an action using printf(). DON'T FORGET THE TRAILING \n // To debug: fprintf(stderr, "Debug messages...\n"); resetQueue(); } return 0; }
J’ai choisi cette solution car elle est différente de ma solution. Elle n’utilise que des files et pas de piles. De plus, elle utilise une structure de données qui contient les coordonnées de la position et un pointeur vers la position précédente ce qui correspond aux deux structures que j’ai utilisées rendant le code plus consis. Ensuite, la solution utilise comme la mienne, un tableau de caractères pour représenter le labyrinthe, on note cependant que le tableau est déclaré de manière différente avec la notation `char grid[R][C + 1]` du c99. Enfin, on peut noter que la solution de libère pas la mémoire allouée.
Lors de cet exercice, j’ai mis en application les connaissances acquises sur les structures de données pile et file. J’ai aussi appris à implémenter un algorithme de parcours de labyrinthe en utilisant une pile. Enfin j’ai découvert et implémenté l’algorithme de dijkstra pour trouver le plus court chemin entre deux points.
Retour vers ma page Web personnelle
Lien vers la documentation Doxygen
Crédits :
- Template : github