prologriver-crossing-puzzle

The Farmer's puzzle - recursive rules and accumulators break my approach


I started learning Prolog a few hours ago, and I'm quite stuck trying to implement a solver for the Farmer problem. I know that there are many examples in the net, but for the purposes of learning I'd like to understand why my code doesn't work, whether or not the approach is valid, and what's the correct way to reason a problem like this.

See below the code. What I've done is:

And what I've achieved so far is:

  1. If I test the trip rule by providing potential solutions, it behaves correctly
  2. If I question the trip rule, it only finds a solution if I shorten the problem to three steps, e.g. trip( state(s,s,s,s), State(s,n,s,s), R)

I think I have found the problem, please correct me if I'm wrong: if the solution requires more than 3 steps, the last trip rule evaluates at least twice, and after the first recursive execution the PreviousStates accumulator is not empty. When unifying? the explored answer, the not(member(Next,PreviousStates)) then fails because the Next state contains variables that will match what's already in the head of the PreviousStates list.

So, my questions are:

  1. Are my conclusions correct? If not, what's the real problem?
  2. If I'm right in the previous point, how can I solve this? Maybe I'm wrong, but the approach I've taken seems quite logical to me. Where did I failed? Do I have to completely change the approach to the problem?

Thanks in advance for your help!

%Define what are the unsafe states for the Farmer, Goat, Cabbage and Wolf
unsafeState(F,G,C,W) :-
    (C=G, not(C=F));
    (G=W, not(G=F)).
safeState(F,G,C,W) :- not(unsafeState(F,G,C,W)).

%Define what are the valid and safe transitions
isSafeTransition(state(F,F,C,W),state(Fend,Fend,C,W)) :- safeState(F,F,C,W), safeState(Fend,Fend,C,W).
isSafeTransition(state(F,G,F,W),state(Fend,G,Fend,W)) :- safeState(F,G,F,W), safeState(Fend,G,Fend,W).
isSafeTransition(state(F,G,C,F),state(Fend,G,C,Fend)) :- safeState(F,G,C,F), safeState(Fend,G,C,Fend).
isSafeTransition(state(F,G,C,W),state(Fend,G,C,W))    :- safeState(F,G,C,W), safeState(Fend,G,C,W).

% Initial matching rule
trip(A,B,Path):- trip(A,B,Path, []).

% Finishing rule
trip(CurrentState, EndState,Path, _):-
    [CurrentState| [EndState|[]] ] = Path,
    isSafeTransition(CurrentState, EndState).

trip(CurrentState,EndState,Path, PreviousStates):-
    [CurrentState|[Next|Tail]] = Path,
    not(member(Next,PreviousStates)),
    isSafeTransition(CurrentState,Next),
    trip(Next,EndState, [Next|Tail], [CurrentState|PreviousStates]).

Solution

  • I've found the problem myself, I'm posting the explanation in case is helpful for someone.

    The issue is that there isn't any rule that limits the possible values for the positions. Basically I've forgotten to limit the possible values to be n or s...

    When solving the query, the potential values for the variables are infinite. That's why a state containing variables is stored in the accumulator and matches the next state.

    To solve the problem, I've added two facts that define the valid values for the positions, and modified the rule that controls whether a state is valid or not to include these values.

    Find below the fixed program:

    % Define the valid sides for any participant    
    side(n).
    side(s).
    
    % Define what are the valid states for the Farmer, Goat, Cabbage and Wolf
    unsafeState(F,G,C,W) :-
        (C=G, not(C=F));
        (G=W, not(G=F)).
    validState(F,G,C,W) :-
        side(F),side(G),side(C),side(W),
        not(unsafeState(F,G,C,W)).
    
    %Define what are the valid and safe transitions
    isSafeTransition(state(F,F,C,W),state(Fend,Fend,C,W)) :- 
        validState(F,F,C,W), validState(Fend,Fend,C,W).
    isSafeTransition(state(F,G,F,W),state(Fend,G,Fend,W)) :- 
        validState(F,G,F,W), validState(Fend,G,Fend,W).
    isSafeTransition(state(F,G,C,F),state(Fend,G,C,Fend)) :- 
        validState(F,G,C,F), validState(Fend,G,C,Fend).
    isSafeTransition(state(F,G,C,W),state(Fend,G,C,W))    :- 
        validState(F,G,C,W), validState(Fend,G,C,W).
    
    % Initial matching rule
    trip(A,B,Path):- trip(A,B,Path, []).
    
    % Finishing rule
    trip(CurrentState, EndState,Path, _):-
        [CurrentState| [EndState|[]] ] = Path,
        isSafeTransition(CurrentState, EndState).
    
    trip(CurrentState,EndState,Path, PreviousStates):-
        [CurrentState|[Next|Tail]] = Path,
        not(member(CurrentState,PreviousStates)),
        isSafeTransition(CurrentState,Next),
        trip(Next,EndState, [Next|Tail], [CurrentState|PreviousStates]).
    

    I'm still a total newbie in Prolog, so if anyone can contribute with any correction, please do it!