% file:    towers.pl
% author:  keller
% purpose: depth-first search for solution in Towers of Hanoi

% towers([S1, S2, S3], Moves) will mean that Moves is a valid move sequence 
% that results in S1 and S2 being empty (so all disks are on S3).

towers(InitialState, Moves) :- towers(InitialState, [], Moves).


% towers([S1, S2, S3], Seen, Moves) means the same, except that Seen will be a 
% list of all previous states (to prevent infinite looping)

towers([[], [], _], _, []).	% final state

towers(Before, Seen, [Move | Moves]) :-
    nonMember(Before, Seen),
    move(Move, Before, After),
    towers(After, [Before | Seen], Moves).


% move([From, To], Before, After) means that a disk can be moved from stack
% From in state Before to stack To, resulting in state After

move([1, 2], [[F1 | R1], S2, S3], [R1, [F1 | S2], S3]) :- ok(F1, S2).
move([1, 3], [[F1 | R1], S2, S3], [R1, S2, [F1 | S3]]) :- ok(F1, S3).
move([2, 1], [S1, [F2 | R2], S3], [[F2 | S1], R2, S3]) :- ok(F2, S1).
move([2, 3], [S1, [F2 | R2], S3], [S1, R2, [F2 | S3]]) :- ok(F2, S3).
move([3, 1], [S1, S2, [F3 | R3]], [[F3 | S1], S2, R3]) :- ok(F3, S1).
move([3, 2], [S1, S2, [F3 | R3]], [S1, [F3 | S2], R3]) :- ok(F3, S2).


% ok(Disk, Stack) means that it is ok to move the indicated Disk onto Stack

ok(_, []).

ok(A, [B | _]) :- smaller(A, B).

smaller(A, B) :- A < B.


% nonMember(X, L) means that X is not a member of list L.

nonMember(X, L) :-
  \+ member(X, L).

member(X, [X | _]).

member(X, [_ | L]) :- member(X, L).
    

% simple test case

test :- towers([[1, 2, 3, 4], [], []], Moves),
        write(Moves),
        nl.    
    

% simulate the moves, for demonstration purposes

play(State, [], [State]).	% no moves left

play(Before, [Move | Moves], [Before | States]) :-
    move(Move, Before, After),
    play(After, Moves, States).

% demonstrate on an N-disk puzzle

demo(N) :-
    gen(N, Stack),
    Initial = [Stack, [], []],
    towers(Initial, Moves),
    play(Initial, Moves, States),
    show(States).


% gen(N, Stack) generates [1, 2, 3, ..., N]

gen(N, Stack) :- gen(1, N, Stack).

gen(K, N, []) :- K > N.

gen(K, N, [K | Stack]) :- 
    K1 is K+1,
    gen(K1, N, Stack).


% show(List) prints a list of states, one per line

show([]).
show([State | States]) :-
    write(State),
    nl,
    show(States).

demo :- demo(4).
