2023-03-01 02:28:50 +01:00
|
|
|
% Read a file with the puzzle and return the initial state of the game
|
|
|
|
read_board(Filename, Board) :-
|
|
|
|
open(Filename, read, Stream),
|
|
|
|
read_lines(Stream, Lines),
|
|
|
|
maplist(atom_chars, Board, Lines).
|
|
|
|
|
|
|
|
read_lines(Stream, []) :-
|
|
|
|
at_end_of_stream(Stream).
|
|
|
|
read_lines(Stream, [Line|Lines]) :-
|
|
|
|
\+ at_end_of_stream(Stream),
|
|
|
|
read_line(Stream, Line),
|
|
|
|
read_lines(Stream, Lines).
|
|
|
|
|
|
|
|
read_line(Stream, Line) :-
|
|
|
|
read_line_to_codes(Stream, Codes),
|
|
|
|
maplist(char_code, Line, Codes).
|
|
|
|
|
|
|
|
% Solve the Sokoban puzzle using DFS
|
|
|
|
sokoban(Board, Moves, Solution) :-
|
|
|
|
solved(Board),
|
|
|
|
Solution = Board,
|
|
|
|
Moves = [].
|
|
|
|
sokoban(Board, Moves, Solution) :-
|
|
|
|
move(Board, Dir, NewBoard),
|
|
|
|
\+ member(NewBoard, Moves),
|
|
|
|
sokoban(NewBoard, [NewBoard|Moves], Solution1),
|
|
|
|
Solution = [Dir|Solution1].
|
|
|
|
|
|
|
|
% Check if the board is solved (all boxes are on targets)
|
|
|
|
solved(Board) :-
|
|
|
|
findall(Box, (member(Box, Board), member($, Box)), Boxes),
|
|
|
|
findall(Target, (member(Target, Board), member(., Target)), Targets),
|
|
|
|
subset(Boxes, Targets).
|
|
|
|
|
|
|
|
% Find the position of the player on the board
|
|
|
|
player(Board, row(Row), col(Col)) :-
|
|
|
|
nth0(Row, Board, RowList),
|
|
|
|
nth0(Col, RowList, '1').
|
|
|
|
|
|
|
|
% Find the cell at the given row and column
|
|
|
|
cell(Board, row(Row), col(Col), Cell) :-
|
|
|
|
nth0(Row, Board, RowList),
|
|
|
|
nth0(Col, RowList, Cell).
|
|
|
|
|
|
|
|
% Move the player in the given direction
|
|
|
|
% If the player pushes a box, move the box as well
|
|
|
|
move(Board, Dir, NewBoard) :-
|
|
|
|
player(Board, row(Row), col(Col)),
|
|
|
|
cell(Board, row(NewRow), col(NewCol), Cell),
|
|
|
|
direction(Dir, dRow, dCol),
|
|
|
|
NewRow is Row + dRow,
|
|
|
|
NewCol is Col + dCol,
|
|
|
|
(
|
|
|
|
Cell = ' ' ; Cell = '.',
|
|
|
|
replace(Board, Row, Col, ' ', Board1),
|
|
|
|
replace(Board1, NewRow, NewCol, '1', NewBoard)
|
|
|
|
;
|
|
|
|
Cell = '$' ; Cell = '*',
|
|
|
|
cell(Board, row(NewRow2), col(NewCol2), Cell2),
|
|
|
|
NewRow2 is NewRow + dRow,
|
|
|
|
NewCol2 is NewCol + dCol,
|
|
|
|
(Cell2 = ' ' ; Cell2 = '.'),
|
|
|
|
replace(Board, Row, Col, ' ', Board1),
|
|
|
|
replace(Board1, NewRow, NewCol, '1', Board2),
|
|
|
|
replace(Board2, NewRow2, NewCol2, '$', NewBoard)
|
|
|
|
).
|
|
|
|
|
|
|
|
% Replace an element at the given row and column
|
|
|
|
replace(List, Row, Col, NewElem, NewList) :-
|
|
|
|
nth0(Row, List, RowList),
|
|
|
|
nth0(Col, RowList, _, RestRow),
|
|
|
|
nth0(Col, NewRow, NewElem, RestRow),
|
|
|
|
nth0(Row, NewList, NewRow, RestList).
|
|
|
|
|
|
|
|
% Define the directions
|
|
|
|
direction(up, -1, 0).
|
|
|
|
direction(down, 1, 0).
|
|
|
|
direction(left, 0, -1).
|
|
|
|
direction(right, 0, 1).
|
|
|
|
|
|
|
|
% Example usage
|
|
|
|
solve_sokoban(Filename, Solution) :-
|
|
|
|
read_board(Filename, Board),
|
|
|
|
sokoban(Board, [Board], Solution).
|