Solve the classic 8-puzzle sliding tile problem using BFS, DFS, or a misplaced-tile heuristic.
The 8-puzzle is a 3×3 sliding tile game with tiles numbered 1–8 and one blank space. The goal is to move tiles into a target arrangement by sliding them into the blank. This program lets you define a custom initial and goal state — or use a built-in example — and then solve it with one of three search strategies.
The first menu asks whether you want to enter your own puzzle or use the built-in example:
Menu: (1) Ingresar puzzle (2) Usar puzzle de ejemplo Ingrese una opcion:
If you choose option 1, the program prompts you to enter 9 digits for the initial state and then 9 digits for the goal state. Enter 0 wherever the blank tile should be.If you choose option 2, the predefined example is used (see Example puzzle below).
4
Choose a search algorithm
The second menu selects the solver:
Menu: (1) Amplitud (2) Profundidad (3) Heuristica Ingrese una opcion:
Amplitud() implements breadth-first search using a std::list as a FIFO queue. New child states are appended to the back of the list with push_back, and the current node is always taken from the front with pop_front. This guarantees the shortest path is found first.A separate historial list tracks every state that has already been enqueued so that cycles are not revisited.
void problema_puzzles::Amplitud() { list<puzzle> lista; list<puzzle> historial; // ... lista.push_front(inicial); historial.push_front(inicial); while (!lista.empty() && !encontrado_meta) { puzzle aux = lista.front(); // check goal ... lista.pop_front(); hijo = aux.mov_espacio_izquierda(); if (!esta_historial(hijo, historial)) { lista.push_back(hijo); // enqueue at back — BFS historial.push_back(hijo); } // derecha, arriba, abajo ... }}
BFS explores states level by level, which can consume a large amount of memory for puzzles that require many moves to solve.
Profundidad() implements depth-first search. New children are pushed to the front of the list with push_front, so the most recently generated state is explored next. The expansion order is: abajo → arriba → derecha → izquierda (reversed from BFS).
void problema_puzzles::Profundidad() { list<puzzle> lista; list<puzzle> historial; // ... lista.push_front(inicial); historial.push_front(inicial); while (!lista.empty() && !encontrado_meta) { puzzle aux = lista.front(); // check goal ... lista.pop_front(); hijo = aux.mov_espacio_abajo(); if (!esta_historial(hijo, historial)) { lista.push_front(hijo); // push to front — DFS historial.push_back(hijo); } // arriba, derecha, izquierda ... }}
DFS may explore very deep branches before finding the goal and does not guarantee the shortest solution path.
Heuristica() uses a std::priority_queue<puzzle>. Before each child state is pushed, its eval field is set by calling evaluacion(), which counts the number of misplaced tiles (excluding the blank). The operator< on puzzle is reversed so that the state with the lowest eval score (fewest misplaced tiles) is popped first.
void problema_puzzles::Heuristica() { priority_queue<puzzle> lista; list<puzzle> historial; // ... lista.push(inicial); historial.push_front(inicial); while (!lista.empty() && !encontrado_meta) { puzzle aux = lista.top(); // check goal ... lista.pop(); hijo = aux.mov_espacio_abajo(); if (!esta_historial(hijo, historial)) { hijo.eval = hijo.evaluacion(hijo, meta); // score before enqueue lista.push(hijo); historial.push_back(hijo); } // arriba, derecha, izquierda ... }}
The heuristic solver typically reaches the goal in far fewer iterations than BFS or DFS because it always expands the state closest to the goal first.
evaluacion(puzzle h, puzzle m) counts how many tiles in state h are in the wrong position relative to goal state m. The blank tile (' ') is excluded from the count:
int puzzle::evaluacion(puzzle h, puzzle m) { int iguales = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (h.matriz[i][j] != m.matriz[i][j] && h.matriz[i][j] != ' ') { iguales++; } } } return iguales;}
A score of 0 means every tile is in its correct position — the puzzle is solved.
All four movement methods follow the same pattern: they copy the current state, check whether the move is within bounds, swap the blank with the adjacent tile, update fila/columna, and append the move code to movimientos. If the blank is already at the boundary, the method returns an unchanged copy.
The three other movement methods (mov_espacio_derecha, mov_espacio_arriba, mov_espacio_abajo) follow the identical pattern, differing only in the boundary check and the direction of the swap.