diff --git a/.gitignore b/.gitignore index 29bcf70..495e16a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ Cours/ # IDE related compile_flags.txt + +# Archives +*.tar.gz diff --git a/TP1/C-Cpp/includes/mysok.h b/TP1/C-Cpp/includes/mysok.h index ee0d31c..8b324d2 100644 --- a/TP1/C-Cpp/includes/mysok.h +++ b/TP1/C-Cpp/includes/mysok.h @@ -1,17 +1,16 @@ #ifndef MYSOK_H #define MYSOK_H +#include +#include +#include #include #define NBL 20 #define NBC 20 -#define MAX_CRATES 20 +#define MAX_DEPTH 60 -#define MOVE_U 0 -#define MOVE_D 1 -#define MOVE_L 2 -#define MOVE_R 3 -#define MOVE_W 4 +enum movement { MOVE_U = 0, MOVE_D, MOVE_L, MOVE_R, MOVE_W }; enum board_str { OUT = ' ', @@ -27,7 +26,11 @@ enum board_str { END_OF_LINE = 'a' }; -// const std::string move_str[] = {"Up", "Down", "Left", "Right", "Wait"}; +const std::string move_str[] = {"Up", "Down", "Left", "Right", "Wait"}; + +const int dx[] = {-1, 1, 0, 0, 0}; +const int dy[] = {0, 0, -1, 1, 0}; +const int dsize = 5; struct sok_board_t { int board[NBL][NBC]; @@ -36,10 +39,20 @@ struct sok_board_t { int man1_y; int man2_x; int man2_y; + int num_crates_free; sok_board_t(); void print_board(); void load(char *_file); }; +typedef struct { + sok_board_t state; + int path_len; + std::string path; +} Node; + +int bfs(Node &result); +int dfs(Node &result, int depth, std::vector history); + #endif diff --git a/TP1/C-Cpp/src/mysok.cpp b/TP1/C-Cpp/src/mysok.cpp index 16a4048..d23defa 100644 --- a/TP1/C-Cpp/src/mysok.cpp +++ b/TP1/C-Cpp/src/mysok.cpp @@ -1,16 +1,16 @@ #include "../includes/mysok.h" sok_board_t::sok_board_t() { - for (int i = 0; i < NBL; i++) { - for (int j = 0; j < NBC; j++) { + for (int i = 0; i < NBL; ++i) { + for (int j = 0; j < NBC; ++j) { board[i][j] = FREE; } } } void sok_board_t::print_board() { - for (int i = 0; i < board_nbl; i++) { - for (int j = 0; j < NBC; j++) { + for (int i = 0; i < board_nbl; ++i) { + for (int j = 0; j < NBC; ++j) { if (board[i][j] == END_OF_LINE) { break; } @@ -33,11 +33,12 @@ void sok_board_t::load(char *_file) { exit(EXIT_FAILURE); } + num_crates_free = 0; board_nbl = 0; while ((nread = getline(&line, &len, fp)) != -1) { if ((static_cast(nread)) > 0) { bool read_ok = false; - for (int i = 0; i < nread; i++) { + for (int i = 0; i < nread; ++i) { switch (line[i]) { case (FREE): { board[board_nbl][i] = FREE; @@ -54,6 +55,7 @@ void sok_board_t::load(char *_file) { } case (CRATE_ON_FREE): { board[board_nbl][i] = CRATE_ON_FREE; + ++num_crates_free; break; } case (CRATE_ON_TARGET): { @@ -88,7 +90,7 @@ void sok_board_t::load(char *_file) { } if (read_ok) { board[board_nbl][nread - 1] = END_OF_LINE; - board_nbl++; + ++board_nbl; } } } @@ -96,3 +98,267 @@ void sok_board_t::load(char *_file) { free(line); fclose(fp); } + +bool is_in_history(Node current, std::vector history) { + // printf("Taille history : %d\n", history.size()); + + for (auto &noeud : history) { + + int c = 0; + for (int l = 0; l < NBL; ++l) { + for (int m = 0; m < NBC; ++m) { + if (current.state.board[l][m] != noeud.state.board[l][m]) + c++; + } + } + + if (c == 0) + return true; + } + return false; +} + +int bfs(Node &result) { + // Création de la queue de BFS + std::queue q; + + // Ajout de l'état initial à la queue + Node first; + first.state = result.state; + first.path_len = 0; + first.path = ""; + q.push(first); + + // Boucle principale de la recherche BFS + while (!q.empty()) { + // Retrait de l'état en tête de la queue + Node cur = q.front(); + q.pop(); + + // Si l'état actuel est celui souhaité (toutes les caisses sur des cibles) + if (cur.state.num_crates_free <= 0) { + result = cur; + return 0; + } + + // Parcours des déplacements possibles à partir de l'état actuel + for (int i = 0; i < dsize - 1; ++i) { + // Position de la case du joueur + int man1_x = cur.state.man1_x; + int man1_y = cur.state.man1_y; + + // Position du joueur après déplacement + int new_man1_x = cur.state.man1_x + dx[i]; + int new_man1_y = cur.state.man1_y + dy[i]; + + // Si la case devant le joueur est un mur + if (cur.state.board[new_man1_x][new_man1_y] == WALL) { + continue; + } + + // Si la case devant le joueur est libre + if (cur.state.board[new_man1_x][new_man1_y] == FREE || + cur.state.board[new_man1_x][new_man1_y] == TARGET) { + // On déplace le joueur dans le prochain état + sok_board_t next = cur.state; + + // Déplacement joueur, ancienne position + next.board[man1_x][man1_y] = + next.board[man1_x][man1_y] == MAN1_ON_TARGET ? TARGET : FREE; + // Déplacement joueur, nouvelle position + next.board[new_man1_x][new_man1_y] = + next.board[new_man1_x][new_man1_y] == TARGET ? MAN1_ON_TARGET + : MAN1_ON_FREE; + next.man1_x = new_man1_x; + next.man1_y = new_man1_y; + + // On ajoute cet état suivant à la queue + Node next_bfs; + next_bfs.state = next; + next_bfs.path_len = cur.path_len + 1; + next_bfs.path = cur.path + move_str[i] + " "; + q.push(next_bfs); + } + + // Si la case devant le joueur est une caisse + if (cur.state.board[new_man1_x][new_man1_y] == CRATE_ON_FREE || + cur.state.board[new_man1_x][new_man1_y] == CRATE_ON_TARGET) { + // Position de la caisse + int cx = new_man1_x; + int cy = new_man1_y; + // Position de la caisse après déplacement + int new_cx = cx + dx[i]; + int new_cy = cy + dy[i]; + + // Si la case derrière la caisse n'est pas accessible + if (cur.state.board[new_cx][new_cy] != TARGET && + cur.state.board[new_cx][new_cy] != FREE) { + continue; + } + + // On déplace la caisse et le joueur + sok_board_t next = cur.state; + + // Déplacement joueur ancienne position + next.board[man1_x][man1_y] = + next.board[man1_x][man1_y] == MAN1_ON_TARGET ? TARGET : FREE; + + // Déplacement caisse, anciennne position, ie. nouvelle position joueur + next.board[cx][cy] = next.board[cx][cy] == CRATE_ON_TARGET + ? MAN1_ON_TARGET + : MAN1_ON_FREE; + next.man1_x = new_man1_x; + next.man1_y = new_man1_y; + // S'éloigne de l'objectif si on déplace une caisse d'une cible + if (next.board[cx][cy] == MAN1_ON_TARGET) { + ++next.num_crates_free; + } + + // Déplacement caisse, nouvelle position + next.board[new_cx][new_cy] = next.board[new_cx][new_cy] == TARGET + ? CRATE_ON_TARGET + : CRATE_ON_FREE; + // S'approche de l'objectif si on place une caisse sur la cible + if (next.board[new_cx][new_cy] == CRATE_ON_TARGET) { + --next.num_crates_free; + } + + // On ajoute l'état suivant à la queue + Node next_bfs; + next_bfs.state = next; + next_bfs.path_len = cur.path_len + 1; + next_bfs.path = cur.path + move_str[i] + " "; + q.push(next_bfs); + } + } + } + + // Si la queue est vide et qu'on n'a pas trouvé de solution, c'est qu'il n'y + // en a pas + return -1; +} + +int dfs(Node &result, int depth, std::vector history) { + + // Si on a atteint la profondeur maximale, on arrête la recherche + if (depth == MAX_DEPTH) { + /* std::cout << "max\n"; */ + return -1; + } + + // Si l'état actuel est celui souhaité (toutes les caisses sur des cibles) + if (result.state.num_crates_free <= 0) { + return 0; + } + + // Parcours des déplacements possibles à partir de l'état actuel + for (int i = 0; i < dsize - 1; ++i) { + // Position de la case du joueur + int man1_x = result.state.man1_x; + int man1_y = result.state.man1_y; + + // Position du joueur après déplacement + int new_man1_x = result.state.man1_x + dx[i]; + int new_man1_y = result.state.man1_y + dy[i]; + + // Si la case devant le joueur est un mur, on ignore ce mouvement + if (result.state.board[new_man1_x][new_man1_y] == WALL) { + continue; + } + + // Si la case devant le joueur est libre ou une cible, on déplace le joueur + if (result.state.board[new_man1_x][new_man1_y] == FREE || + result.state.board[new_man1_x][new_man1_y] == TARGET) { + // On déplace le joueur dans le prochain état + sok_board_t next = result.state; + + // Déplacement joueur, ancienne position + next.board[man1_x][man1_y] = + next.board[man1_x][man1_y] == MAN1_ON_TARGET ? TARGET : FREE; + // Déplacement joueur, nouvelle position + next.board[new_man1_x][new_man1_y] = + next.board[new_man1_x][new_man1_y] == TARGET ? MAN1_ON_TARGET + : MAN1_ON_FREE; + next.man1_x = new_man1_x; + next.man1_y = new_man1_y; + + // On ajoute cet état suivant à la pile + Node next_dfs; + next_dfs.state = next; + next_dfs.path_len = result.path_len + 1; + next_dfs.path = result.path + move_str[i] + " "; + + if (!is_in_history(next_dfs, history)) { + history.push_back(next_dfs); + int res = dfs(next_dfs, depth + 1, history); + if (res == 0) { + result = next_dfs; + return 0; + } + } + } + + // Si la case devant le joueur est une caisse, on déplace la caisse et le + // joueur + if (result.state.board[new_man1_x][new_man1_y] == CRATE_ON_FREE || + result.state.board[new_man1_x][new_man1_y] == CRATE_ON_TARGET) { + // Position de la caisse + int cx = new_man1_x; + int cy = new_man1_y; + // Position de la caisse après déplacement + int new_cx = cx + dx[i]; + int new_cy = cy + dy[i]; + + // Si la case derrière la caisse n'est pas accessible, on ignore ce + // mouvement + if (result.state.board[new_cx][new_cy] != TARGET && + result.state.board[new_cx][new_cy] != FREE) { + continue; + } + + // On déplace la caisse et le joueur dans le prochain état + sok_board_t next = result.state; + + // Déplacement caisse, ancienne position + if (next.board[cx][cy] == CRATE_ON_TARGET) { + ++next.num_crates_free; + } + next.board[cx][cy] = + next.board[cx][cy] == CRATE_ON_TARGET ? TARGET : FREE; + // Déplacement caisse, nouvelle position + next.board[new_cx][new_cy] = next.board[new_cx][new_cy] == TARGET + ? CRATE_ON_TARGET + : CRATE_ON_FREE; + if (next.board[new_cx][new_cy] == CRATE_ON_TARGET) { + --next.num_crates_free; + } + // Déplacement joueur, ancienne position + next.board[man1_x][man1_y] = + next.board[man1_x][man1_y] == MAN1_ON_TARGET ? TARGET : FREE; + // Déplacement joueur, nouvelle position + next.board[new_man1_x][new_man1_y] = + next.board[new_man1_x][new_man1_y] == TARGET ? MAN1_ON_TARGET + : MAN1_ON_FREE; + next.man1_x = new_man1_x; + next.man1_y = new_man1_y; + + // On ajoute cet état suivant à la pile + Node next_dfs; + next_dfs.state = next; + next_dfs.path_len = result.path_len + 1; + next_dfs.path = result.path + move_str[i] + " "; + + if (!is_in_history(next_dfs, history)) { + history.push_back(next_dfs); + int res = dfs(next_dfs, depth + 1, history); + if (res == 0) { + result = next_dfs; + return 0; + } + } + } + } + + // Aucune solution n'a été trouvée à partir de cet état + return -1; +} diff --git a/TP1/C-Cpp/src/r0.cpp b/TP1/C-Cpp/src/r0.cpp index 746df83..8e6dbe5 100644 --- a/TP1/C-Cpp/src/r0.cpp +++ b/TP1/C-Cpp/src/r0.cpp @@ -11,5 +11,28 @@ int main(int _ac, char **_av) { S.load(_av[1]); S.print_board(); + Node result; + + result.state = S; + result.path = ""; + result.path_len = 0; + /* if (bfs(result) == -1) { + std::cout << "Aucune solution trouvée\n"; + } else { + std::cout << "Coups : " << result.path_len << "\n"; + std::cout << "Solution : " << result.path << "\n"; + } */ + + std::vector history; + + if (dfs(result, 0, history) == -1) { + std::cout << "Aucune solution trouvée\n"; + } else { + std::cout << "Coups : " << result.path_len << "\n"; + std::cout << "Solution : " << result.path << "\n"; + } + + // result.state.print_board(); + return 0; } diff --git a/TP1/Makefile b/TP1/Makefile new file mode 100644 index 0000000..f45bce5 --- /dev/null +++ b/TP1/Makefile @@ -0,0 +1,32 @@ +NAME = TP1 - Groupe 4 +CPP_NAME = C++ +PROLOG_NAME = Prolog + +TAR = tar --exclude="*AidesCPP" --exclude="*TODO.md" -czf +CP = cp -r +RM = rm -r + +RAPPORT = Rapport/rapport.pdf +CPP = C-Cpp/ +TESTS = Screens-* +PROLOG = Prolog/ + +tgz-all: + echo $(CPP) $(PROLOG) | xargs -n 1 $(CP) $(TESTS) + -$(MAKE) -C $(CPP) clean 2> /dev/null + $(TAR) "$(NAME).tar.gz" $(RAPPORT) $(CPP) $(PROLOG) + $(RM) $(CPP)$(TESTS) $(PROLOG)$(TESTS) + +tgz-cpp: + $(CP) $(TESTS) $(CPP) + -$(MAKE) -C $(CPP) clean 2> /dev/null + $(TAR) "$(NAME) - $(CPP_NAME).tar.gz" $(RAPPORT) $(CPP) + $(RM) $(CPP)$(TESTS) + +tgz-prolog: + $(CP) $(TESTS) $(PROLOG) + $(TAR) "$(NAME) - $(PROLOG_NAME).tar.gz" $(RAPPORT) $(PROLOG) + $(RM) $(PROLOG)$(TESTS) + +clean: + $(RM) *.tar.gz diff --git a/TP1/Rapport/rapport.tex b/TP1/Rapport/rapport.tex index abec066..c188cff 100644 --- a/TP1/Rapport/rapport.tex +++ b/TP1/Rapport/rapport.tex @@ -17,7 +17,12 @@ % \usepackage{minted} % intégration code % \usemintedstyle{emacs} -\title{\textbf{TP1 - Sokoban}} +% Minimum pour les colonnes des tableaux +\usepackage{array} +\newcolumntype{y}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}p{#1}} +\newcolumntype{Y}{y{126pt}|y{70pt}|y{66pt}|y{71pt}} + +\title{\textbf{TP1 (CPP) - Sokoban}} \author{Groupe 4\thanks{César PICHON, Florian POSEZ, Omar ANOUAR, Anri KENNEL}\\ \\Intelligence artificielle pour les jeux $\cdot$ Université Paris 8} @@ -28,20 +33,47 @@ \tableofcontents \clearpage -\section{Algorithme} -TODO +\section{Algorithmes} +\subsection{Algorithme de parcours en largeur} +Notre implémentation de l'algorithme de parcours en largeur est, malheureusement, +trop lente pour résoudre un Sokoban, on a décidé de ne pas l'optimiser et +d'utiliser un autre algorithme. + +\subsection{Algorithme de parcours en profondeur} +Notre implémentation de l'algorithme de parcours en profondeur, +elle arrive à résoudre le \texttt{screen-0} en temps raisonnable. + +\subsubsection{Optimisations} +Afin d'éviter les cas répétitifs, on utilises un tableau qui stockes les états +déjà visités. \section{Précalculs} -TODO +Nous n'avons pas utilisés de pré-calculs. \section{Problèmes} -\begin{figure}[h] - \centering - \begin{tabular}{c|c|c|c} - Nombre max caisses déplacées & Solution & Temps calcul & Temps précalculs \\ - \hline - TODO & TODO & TODO & TODO \\ - \end{tabular} -\end{figure} +\subsection{1 joueur} +\begin{enumerate} + \item \texttt{Screen-0} : + \begin{figure}[h] + \centering + \begin{tabular}{Y} + Nombre max caisses déplacées & Solution & Temps calcul & Temps précalculs \\ + \hline + 6 (toutes) & Oui, en 39 coups & 5-10s & Aucun \\ + \end{tabular} + \end{figure} + \item \texttt{Screen-2} : + \begin{figure}[h] + \centering + \begin{tabular}{Y} + Nombre max caisses déplacées & Solution & Temps calcul & Temps précalculs \\ + \hline + 2-3 & Non & Encore en cours & Aucun \\ + \end{tabular} + \end{figure} +\end{enumerate} + +\subsection{2 joueurs} +L'algorithme n'as pas tourné sur les parties à 2 joueurs. \end{document} diff --git a/TP1/C-Cpp/Screens-1/screen-0.txt b/TP1/Screens-1/screen-0.txt similarity index 84% rename from TP1/C-Cpp/Screens-1/screen-0.txt rename to TP1/Screens-1/screen-0.txt index ea941ec..db249cb 100644 --- a/TP1/C-Cpp/Screens-1/screen-0.txt +++ b/TP1/Screens-1/screen-0.txt @@ -4,5 +4,4 @@ #$$$$$$# #......# # # -######## - +######## \ No newline at end of file diff --git a/TP1/C-Cpp/Screens-1/screen-1.txt b/TP1/Screens-1/screen-1.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-1.txt rename to TP1/Screens-1/screen-1.txt diff --git a/TP1/C-Cpp/Screens-1/screen-2.txt b/TP1/Screens-1/screen-2.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-2.txt rename to TP1/Screens-1/screen-2.txt diff --git a/TP1/C-Cpp/Screens-1/screen-3.txt b/TP1/Screens-1/screen-3.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-3.txt rename to TP1/Screens-1/screen-3.txt diff --git a/TP1/C-Cpp/Screens-1/screen-4.txt b/TP1/Screens-1/screen-4.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-4.txt rename to TP1/Screens-1/screen-4.txt diff --git a/TP1/C-Cpp/Screens-1/screen-5.txt b/TP1/Screens-1/screen-5.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-5.txt rename to TP1/Screens-1/screen-5.txt diff --git a/TP1/C-Cpp/Screens-1/screen-6.txt b/TP1/Screens-1/screen-6.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-6.txt rename to TP1/Screens-1/screen-6.txt diff --git a/TP1/C-Cpp/Screens-1/screen-7.txt b/TP1/Screens-1/screen-7.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-7.txt rename to TP1/Screens-1/screen-7.txt diff --git a/TP1/C-Cpp/Screens-1/screen-8.txt b/TP1/Screens-1/screen-8.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-8.txt rename to TP1/Screens-1/screen-8.txt diff --git a/TP1/C-Cpp/Screens-1/screen-9.txt b/TP1/Screens-1/screen-9.txt similarity index 100% rename from TP1/C-Cpp/Screens-1/screen-9.txt rename to TP1/Screens-1/screen-9.txt diff --git a/TP1/C-Cpp/Screens-2/screen-0.txt b/TP1/Screens-2/screen-0.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-0.txt rename to TP1/Screens-2/screen-0.txt diff --git a/TP1/C-Cpp/Screens-2/screen-1.txt b/TP1/Screens-2/screen-1.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-1.txt rename to TP1/Screens-2/screen-1.txt diff --git a/TP1/C-Cpp/Screens-2/screen-2.txt b/TP1/Screens-2/screen-2.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-2.txt rename to TP1/Screens-2/screen-2.txt diff --git a/TP1/C-Cpp/Screens-2/screen-3.txt b/TP1/Screens-2/screen-3.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-3.txt rename to TP1/Screens-2/screen-3.txt diff --git a/TP1/C-Cpp/Screens-2/screen-4.txt b/TP1/Screens-2/screen-4.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-4.txt rename to TP1/Screens-2/screen-4.txt diff --git a/TP1/C-Cpp/Screens-2/screen-5.txt b/TP1/Screens-2/screen-5.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-5.txt rename to TP1/Screens-2/screen-5.txt diff --git a/TP1/C-Cpp/Screens-2/screen-6.txt b/TP1/Screens-2/screen-6.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-6.txt rename to TP1/Screens-2/screen-6.txt diff --git a/TP1/C-Cpp/Screens-2/screen-7.txt b/TP1/Screens-2/screen-7.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-7.txt rename to TP1/Screens-2/screen-7.txt diff --git a/TP1/C-Cpp/Screens-2/screen-8.txt b/TP1/Screens-2/screen-8.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-8.txt rename to TP1/Screens-2/screen-8.txt diff --git a/TP1/C-Cpp/Screens-2/screen-9.txt b/TP1/Screens-2/screen-9.txt similarity index 100% rename from TP1/C-Cpp/Screens-2/screen-9.txt rename to TP1/Screens-2/screen-9.txt