I am trying to implement a blocks world program in prolog. Blocks world is a well-known problem in AI, and in and of itself, is rather simple. This is my current code:
% Define the blocks in your world
blocks([a, b, c, d, e, f]).
% A block X is a member of the list of blocks
block(X) :-
blocks(BLOCKS), % Extract the list BLOCKS
member(X, BLOCKS).
% Given predicates
move(X, Y, Z, S1, S2):-
member([clear, X], S1),
member([on, X, Y], S1),
block(Y),
member([clear, Z], S1),
notequal(X, Z),
substitute([on, X, Y], [on, X, Z], S1, INT),
substitute([clear, Z], [clear, Y], INT, S2).
notequal(X, X):-!, fail.
notequal(_, _).
substitute(X, Y, [X|T], [Y|T]).
substitute(X, Y, [H|T], [H|T1]):-
substitute(X, Y, T, T1).
% Additional predicates to be implemented
% (i) move from a block onto the table
move_onto_table(X, Y, S1, S2):-
member([clear, X], S1),
member([on, X, Y], S1),
substitute([on, X, Y], [on, X, 'table'], S1, INT),
substitute([clear, Y], [clear, X], INT, S2).
% (ii) move from the table onto a block
move_onto_block(X, Y, S1, S2):-
member([clear, X], S1),
member([on, X, 'table'], S1),
substitute([on, X, 'table'], [on, X, Y], S1, INT),
substitute([clear, X], [clear, Y], INT, S2).
% Define start and goal states
start([[on, d, 'table'], [on, c, d], [on, a, c], [on, b, 'table'], [clear, a], [clear, b]]).
goal([[on, d, 'table'], [on, c, 'table'], [on, a, b], [on, b, 'table'], [clear, a], [clear, c], [clear, d]]).
% Define notYetVisited
notYetVisited(State, PathSoFar):-
permutation(State, PermuteState),
\+ member(PermuteState, PathSoFar).
% Define path and connect predicates
path(S1, S2):-
move(X, Y, Z, S1, S2).
path(S1, S2):-
move_onto_table(X, Y, S1, S2).
path(S1, S2):-
move_onto_block(X, Y, S1, S2).
connect(S1, S2) :- path(S1, S2).
connect(S1, S2) :- path(S2, S1).
% Define DFS predicate with added debugging statements and iterative deepening
dfs(X, [X], _):-
goal(X),
write('Goal reached: '), write(X), nl.
% else expand X by Y and find path from Y
dfs(X, [X|Ypath], VISITED):-
connect(X, Y),
notYetVisited(Y, VISITED),
write('Current state: '), write(X), nl,
write('Moving to: '), write(Y), nl,
dfs(Y, Ypath, [Y|VISITED]).
The query I'm using is thus:
start(Start), dfs(Start, Path, []).
Now, the output that appears is simply a loop. Despite implementing a check for repeated states, dfs simply seems to oscillate between the same two states as such:
Current state: [[on, d, table], [on, c, d], [on, a, c], [on, b, table], [clear, a], [clear, b]]
Moving to: [[on, d, table], [on, c, d], [on, a, b], [on, b, table], [clear, a], [clear, c]]
Current state: [[on, d, table], [on, c, d], [on, a, b], [on, b, table], [clear, a], [clear, c]]
Moving to: [[on, d, table], [on, c, d], [on, a, c], [on, b, table], [clear, a], [clear, b]]
Current state: [[on, d, table], [on, c, d], [on, a, c], [on, b, table], [clear, a], [clear, b]]
Moving to: [[on, d, table], [on, c, d], [on, a, b], [on, b, table], [clear, a], [clear, c]]
Current state: [[on, d, table], [on, c, d], [on, a, b], [on, b, table], [clear, a], [clear, c]]
Moving to: [[on, d, table], [on, c, d], [on, a, c], [on, b, table], [clear, a], [clear, b]]
Current state: [[on, d, table], [on, c, d], [on, a, c], [on, b, table], [clear, a], [clear, b]]
Moving to: [[on, d, table], [on, c, d], [on, a, b], [on, b, table], [clear, a], [clear, c]]
Current state: [[on, d, table], [on, c, d], [on, a, b], [on, b, table], [clear, a], [clear, c]]
Moving to: [[on, d, table], [on, c, d], [on, a, c], [on, b, table], [clear, a], [clear, b]]
I have tried multiple solutions. Obviously, the expectation is for it to go through as many states as needed to reach the goal state (which is hard-coded in the script). Here's what I've done so far:
- Changing the way notYetVisited works, trying to do a simple equality rather than using permutation. However, this results in the stack limit being exceeded.
- Adding debugging statements at various points. These are included in the provided code.
- Using a DFID (Depth-First Iterative Deepening) approach for the dfs procedure instead. This did not work, as again, stack limit was exceeded almost instantaneously.
- Changing dfs to update the state when backtracking.
- Altering the move procedures (move_onto_table and move_onto_block) to make sure the 'clear' condition is satisfied.
The main problems with your code are:
The predicate
notYetVisited/2
establishes that a state has not yet been visited if there exists some permutation of it that has not yet been visited. For example, the following query should fail (since the lists[a,b]
and[b,a]
represent the same state).The actions
move_onto_table
andmove_onto_block
are not defined correctly. For example:Your code can be improved in several aspects:
Use terms to represent fluents and lists of terms to represent states. Thus, the start state can be represented by
[clear(a), clear(b), on(a,c), on(b,t), on(c,d), on(d,t)]
, wheret
stands for table.Use ordered sets, so you don't have to handle permutations.
Thus, a possible solution (using swi-prolog) is:
Example: