RosettaCodeData/Task/15-puzzle-solver/Mathematica/15-puzzle-solver.math
2025-06-11 20:16:52 -04:00

178 lines
5.8 KiB
Text

(* Constants for the correct ordering and board position mapping *)
correctOrder = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
rowOf = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3} + 1; (* +1 because Mathematica uses 1-based indexing *)
colOf = {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3} + 1; (* +1 because Mathematica uses 1-based indexing *)
(* Function to estimate remaining moves using Manhattan distance *)
EstimateMoves[board_] := Module[{h = 0},
Do[
(* Skip the empty tile (0) *)
If[board[[i]] != 0,
(* Find where this tile should be in the correct order *)
correctPos = Position[correctOrder, board[[i]]][[1, 1]];
(* Calculate Manhattan distance: |current_row - correct_row| + |current_col - correct_col| *)
h += Abs[rowOf[[i]] - rowOf[[correctPos]]] + Abs[colOf[[i]] - colOf[[correctPos]]];
],
{i, 1, 16}
];
h
]
(* Find the position of the blank tile (0) *)
FindBlankPosition[board_] := Position[board, 0][[1, 1]]
(* Generate possible moves from current state *)
GenerateMoves[board_, movesSoFar_] := Module[
{blankPos, possibleMoves = {}, newBoard, newMoves},
blankPos = FindBlankPosition[board];
(* Check if we can move left (blank goes right) *)
If[Mod[blankPos, 4] != 1,
newBoard = board;
newBoard[[blankPos]] = newBoard[[blankPos - 1]];
newBoard[[blankPos - 1]] = 0;
newMoves = movesSoFar <> "r";
AppendTo[possibleMoves, {newBoard, newMoves, Length[newMoves] + EstimateMoves[newBoard]}];
];
(* Check if we can move right (blank goes left) *)
If[Mod[blankPos, 4] != 0,
newBoard = board;
newBoard[[blankPos]] = newBoard[[blankPos + 1]];
newBoard[[blankPos + 1]] = 0;
newMoves = movesSoFar <> "l";
AppendTo[possibleMoves, {newBoard, newMoves, Length[newMoves] + EstimateMoves[newBoard]}];
];
(* Check if we can move up (blank goes down) *)
If[blankPos > 4,
newBoard = board;
newBoard[[blankPos]] = newBoard[[blankPos - 4]];
newBoard[[blankPos - 4]] = 0;
newMoves = movesSoFar <> "d";
AppendTo[possibleMoves, {newBoard, newMoves, Length[newMoves] + EstimateMoves[newBoard]}];
];
(* Check if we can move down (blank goes up) *)
If[blankPos <= 12,
newBoard = board;
newBoard[[blankPos]] = newBoard[[blankPos + 4]];
newBoard[[blankPos + 4]] = 0;
newMoves = movesSoFar <> "u";
AppendTo[possibleMoves, {newBoard, newMoves, Length[newMoves] + EstimateMoves[newBoard]}];
];
possibleMoves
]
(* A* Search algorithm for solving the puzzle *)
Solve15Puzzle[startBoard_] := Module[
{openSet = {}, closedSet = {}, current, children, bestMoves = {}},
(* Initialize the priority queue (Mathematica doesn't have a built-in priority queue,
so we'll use a list and sort it each time) *)
openSet = {{startBoard, "", EstimateMoves[startBoard]}};
While[Length[openSet] > 0,
(* Sort the open set by total estimated cost *)
openSet = Sort[openSet, #1[[3]] < #2[[3]] &];
(* Get the most promising state *)
current = First[openSet];
openSet = Rest[openSet];
(* Check if we've reached the goal state *)
If[current[[1]] == correctOrder,
Print["Solution found!"];
Print["Moves: ", current[[2]]];
Print["Number of moves: ", StringLength[current[[2]]]];
Print["Open set size: ", Length[openSet]];
Print["Closed set size: ", Length[closedSet]];
(* Format the final board *)
Print["Final board:"];
Print[Partition[current[[1]], 4]];
Return[current[[2]]];
];
(* Generate all possible moves from current state *)
children = GenerateMoves[current[[1]], current[[2]]];
(* Process each child state *)
For[i = 1, i <= Length[children], i++,
child = children[[i]];
childBoard = child[[1]];
(* Check if this board is already in the closed set *)
If[Not[MemberQ[Map[First, closedSet], childBoard]],
(* Check if this board is already in the open set *)
existingIdx = Position[Map[First, openSet], childBoard];
If[existingIdx == {},
(* Add to open set if not already there *)
AppendTo[openSet, child];
,
(* Update if this path is better than existing one *)
existingIdx = existingIdx[[1, 1]];
If[StringLength[child[[2]]] < StringLength[openSet[[existingIdx, 2]]],
openSet[[existingIdx]] = child;
];
];
];
];
(* Add current state to closed set *)
AppendTo[closedSet, current];
];
Print["No solution found!"];
Return[{}];
]
(* Example usage *)
startingBoard = {15, 14, 1, 6, 9, 11, 4, 12, 0, 10, 7, 3, 13, 8, 5, 2};
(* startingBoard = {0, 1, 2, 3, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12}; *)
solution = Solve15Puzzle[startingBoard];
(* Pretty print function to visualize a board *)
PrintBoard[board_] := Module[{},
Grid[Partition[board, 4], Frame -> All]
]
(* Visualization code to show the solution step by step *)
VisualizeSolution[startBoard_, moves_] := Module[
{currentBoard = startBoard, frames = {PrintBoard[startBoard]}, blankPos},
Do[
blankPos = FindBlankPosition[currentBoard];
Switch[StringTake[moves, {i}],
"l", (* blank moves left *)
currentBoard[[blankPos]] = currentBoard[[blankPos - 1]];
currentBoard[[blankPos - 1]] = 0,
"r", (* blank moves right *)
currentBoard[[blankPos]] = currentBoard[[blankPos + 1]];
currentBoard[[blankPos + 1]] = 0,
"u", (* blank moves up *)
currentBoard[[blankPos]] = currentBoard[[blankPos - 4]];
currentBoard[[blankPos - 4]] = 0,
"d", (* blank moves down *)
currentBoard[[blankPos]] = currentBoard[[blankPos + 4]];
currentBoard[[blankPos + 4]] = 0
];
AppendTo[frames, PrintBoard[currentBoard]];
,{i, 1, StringLength[moves]}
];
ListAnimate[frames]
]
(* Uncomment to visualize the solution *)
(* If[solution != {}, VisualizeSolution[startingBoard, solution]]; *)