/******************************************************************* 
   by Neng-Fa Zhou, 2001.
   Solve the blocks world planning problem as a CSP.
   Based on the modeling described in:
   "CPlan: A Constraint Programming Approach to Planning", AAAI'99,
   by Peter va Beek and Xinguang Chen.
   One difference from the modeling is that goal-relevant 
   constraints are employed.
********************************************************************/

go:-
    go(1).

go(Prob):-
    T1 is cputime,
    blocks(Prob,Blocks),
    length(Blocks,N),
%    InitialLen is N*3//2,
    InitialLen=2,
    find_best_plan(Prob,InitialLen,N,Plan),
    T2 is cputime,
    T is T2-T1,
    write('plan found in '),write(T), write('milliseconds'),nl,
    write(Plan),nl.

find_best_plan(Prob,Len,N,Plan):-
    plan(Prob,Len,N,Plan),!.
find_best_plan(Prob,Len,N,Plan):-
    Len1 is Len+1,
    find_best_plan(Prob,Len1,N,Plan).

/*
find_best_plan(Prob,Len,N,Plan):-
    not(not(find_plan(Prob,Len,N,Plan))),!,
    current_best_plan(CurBestPlan),
    length(CurBestPlan,CurBestLen),
    write(CurBestLen),write(' '),write(CurBestPlan),nl,
    find_best_plan(Prob,CurBestLen,N,Plan).
find_best_plan(Prob,Len,N,Plan):-
    retract(current_best_plan(Plan)).

find_plan(Prob,Len,N,Plan):-
    plan(Prob,Len,N,Plan),!,
    (retract(current_best_plan(_))->true;true),
    assert(current_best_plan(Plan)).
*/

plan(Prob,Len,N,Plan):-
    generate_states(Prob,Len,States),
    %
    States=[InitState|States1],
    last_state(States1,GoalState,MidStates),
    initial(Prob,InitState),
    goal(Prob,GoalState),
    %
    action_constraints(States,GoalState),
%    no_cycle_constraints(States),    
    %
    label(MidStates),
    extract_plan(States,Plan).

last_state([X],Last,Rest):-!,Rest=[],Last=X.
last_state([X|Xs],Last,[X|Rest]):-last_state(Xs,Last,Rest).

extract_plan([S1],Plan):-!,Plan=[].
extract_plan([S1,S2|States],Plan):-
    functor(S1,_,N),
    add_moved_block(S1,S2,N,Plan,PlanR),
    extract_plan([S2|States],PlanR).
 
add_moved_block(S1,S2,I,Plan,PlanR):-I=:=0,!,Plan=PlanR.
add_moved_block(S1,S2,I,Plan,PlanR):-
     arg(I,S1,block(_,Below1)),
     arg(I,S2,block(_,Below2)),
     (Below1=\=Below2->Plan=[(I=>Below2)|PlanR]
      ;
      I1 is I-1,
      add_moved_block(S1,S2,I1,Plan,PlanR)).

% instantiate states forward.
label([State|States]):-
    get_below_vars(State,BVars),
    labeling_ffc(BVars),
    label(States).
label([]).

/* 
   A plan is a list of states each of which is a structure of the following form:
   
   state(block(A1,B1),block(A2,B2),...,block(An,Bn))

   where Ai and Bi are two variables that tell what are the blocks above and below
   the block i, respectively. Ai is 0 if the block is clear and Bi is 0 if the block 
   is on the table
*/
generate_states(Prob,N,States):-N>0,!,
    States=[State|StatesR],
    generate_state(Prob,State),
    N1 is N-1,
    generate_states(Prob,N1,StatesR).
generate_states(Prob,N,States):-States=[].

generate_state(Prob,State):-
    blocks(Prob,Blocks),
    length(Blocks,NBlocks),
    functor(State,state,NBlocks),
    declare_domains(State,1,NBlocks),
    state_constraints(State).
    
declare_domains(State,I,N):-I>N,!.
declare_domains(State,I,N):-
    arg(I,State,block(Above,Below)),
    domain(Above,0,N), 
    domain(Below,0,N), 
    I1 is I+1,
    declare_domains(State,I1,N).
    
get_below_vars(State,BVars):-
    functor(State,_,N),
    get_below_vars(N,State,BVars).

get_below_vars(I,State,BVars):-I=:=0,!,BVars=[].
get_below_vars(I,State,BVars):-BVars=[Below|BVarsR],
    arg(I,State,block(_,Below)),
    I1 is I-1,
    get_below_vars(I1,State,BVarsR).

/*
  The following constraints ensure that a state is valid:
  (1) If block i is above block j, then block j is below block i, and vice versa.
  (2) For each block, there is at most one block above it and there is 
      also at most one block below it.
*/
state_constraints(State):-
     functor(State,_,N),
     state_constraints(State,1,N).

state_constraints(State,I,N):-I>N,!.
state_constraints(State,I,N):-
     arg(I,State,block(Above,Below)),
     below_relationship(Below,State,I),
     above_relationship(Above,State,I),
     only_one_below(Below,State,I,N),
     I1 is I+1,
     state_constraints(State,I1,N).

% if Below, which is not 0, is below I, then Below cannot be below any other blocks
only_one_below(Below,State,I,N):-
     freeze(Below,(Below\== 0->outof_below(Below,State,I,N);true)).

outof_below(Below,State,I,J):-J=:=0,!.
outof_below(Below,State,I,J):-
     (I=:=J->true;
      arg(J,State,block(AboveJ,BelowJ)),
      BelowJ #\= Below),
     J1 is J-1,
     outof_below(Below,State,I,J1).

% block J is below I, then I must be above Above J
below_relationship(J,State,I):-
     freeze(J,(J=\= 0->arg(J,State,block(AboveJ,BelowJ)),AboveJ=I;true)).
                   
% block J is above I, then I must be below Above J
above_relationship(J,State,I):-
     freeze(J,(J=\=0->arg(J,State,block(AboveJ,BelowJ)),BelowJ=I;true)).

/*
   A transition from Si to Sj is valid if only one block that is clear in Si is moved.
*/
action_constraints([_],GoalState):-!.
action_constraints([S1,S2|States],GoalState):-
      transition_constraint(S1,S2,GoalState),
      action_constraints([S2|States],GoalState).

transition_constraint(S1,S2,GoalState):-
      functor(S1,_,N), % N blocks
      transition_constraint(S1,S2,GoalState,1,N,0).

transition_constraint(S1,S2,GoalState,I,N,DiffBSum):-I>N,!,
      DiffBSum #=< 1.  % at most one block is moved each time
transition_constraint(S1,S2,GoalState,I,N,DiffBSum):-
      arg(I,S1,block(A1,B1)),
      arg(I,S2,block(A2,B2)),
      arg(I,GoalState,block(A3,B3)),
      (B1#\=B2 #=> A1#=0 #/\A2#=0), % the moved block must be clear
      ((B1 #\= B2) #<=> DiffB),
      ((B #\=B2 #/\ B2#\=0) #=> B2#=B3), % make sure the move is required by the goal
      freeze(B1,freeze(B2,((B1=\=B2,B2=\=0)-> block_in_final_position(B2,S2,GoalState);true))),
      I1 is I+1,
      transition_constraint(S1,S2,GoalState,I1,N,DiffB+DiffBSum).

% block i is moved to block j only when block j is in its final position
block_in_final_position(I,S,GoalState):-
      arg(I,S,block(A,B)),
      arg(I,GoalState,block(A,B)),
      (B=\=0->block_in_final_position(B,S,GoalState);true).

/* 
   No two states in a plan can be the same. Action constraints already guarantee that 
   any two neiboring states are different
*/
no_cycle_constraints([_]):-!.
no_cycle_constraints([_,_]):-!.
no_cycle_constraints([S1,S2|States]):-
   no_cycle_constraints(S1,States),
   no_cycle_constraints([S2|States]).

no_cycle_constraints(S1,[]).
no_cycle_constraints(S1,[S2|States]):-
   different_states(S1,S2),
   no_cycle_constraints(S1,States).

different_states(S1,S2):-
   functor(S1,_,N),
   different_states(N,S1,S2,0).

different_states(I,S1,S2,Sum):-I=:=0,!,Sum#\=0.
different_states(I,S1,S2,Sum):-
     arg(I,S1,block(_,B1)),
     arg(I,S2,block(_,B2)),
     (B1#=B2 #<=> Same),
     I1 is I-1,
     different_states(I1,S1,S2,Sum+Same).

/* 
   blocks are numbered 1,2,3, and so on. 
   clear(B,S): the block B is clear in state S.
   on_table(B,S): the block B is on table in state S.
   on(B1,B2,S): block B1 is one Block B2 in state S.
*/
%%
clear(I,S):-
    arg(I,S,block(Above,Below)),
    Above=0.

on(I1,I2,S):-
    arg(I1,S,block(Above1,Below1)),
    arg(I2,S,block(Above2,Below2)),
    Above2=I1,
    Below1=I2.

on_table(I,S):-
    arg(I,S,block(Above,Below)),
    Below= 0.

%p-0
blocks(0,[1,2]).
initial(0,S):-
   clear(1,S),
   on(1,2,S),
   on_table(2,S).

goal(0,S):-
   on_table(1,S),
   clear(2,S).

% p-1
blocks(01,[1,2,3,4,5,6,7,8,9]).
initial(01,S):-
    clear(3,S),
    clear(5,S),
    clear(9,S),
    on(2,1,S),
    on(3,2,S),
    on(5,4,S),
    on(7,6,S),
    on(8,7,S),
    on(9,8,S),
    on_table(1,S),
    on_table(4,S),
    on_table(6,S).

goal(01,S):-
    clear(1,S),
    clear(2,S),
    clear(8,S),
    on(1,5,S),
    on(2,3,S),
    on(3,7,S),
    on(7,6,S),
    on(8,9,S),
    on(9,4,S),
    on_table(4,S),
    on_table(5,S),
    on_table(6,S).

%p-2
blocks(2,[1,2,3,4,5,6,7,8,9,10,11]).
initial(2,S):-
    clear(11,S),
    clear(3,S),
    clear(9,S),
    on(10,5,S),
    on(11,10,S),
    on(2,1,S),
    on(3,2,S),
    on(5,4,S),
    on(7,6,S),
    on(8,7,S),
    on(9,8,S),
    on_table(1,S),
    on_table(4,S),
    on_table(6,S).
goal(2,S):-
    clear(1,S),
    clear(2,S),
    clear(8,S),
    on(1,5,S),
    on(11,7,S),
    on(2,3,S),
    on(3,11,S),
    on(5,10,S),
    on(7,6,S),
    on(8,9,S),
    on(9,4,S),
    on_table(10,S),
    on_table(4,S),
    on_table(6,S).

%p3
blocks(3,[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]).
initial(3,S):-
    clear(11,S),
    clear(3,S),
    clear(9,S),
    on(1,12,S),
    on(10,5,S),
    on(11,10,S),
    on(12,13,S),
    on(14,15,S),
    on(2,1,S),
    on(3,2,S),
    on(4,14,S),
    on(5,4,S),
    on(7,6,S),
    on(8,7,S),
    on(9,8,S),
    on_table(13,S),
    on_table(15,S),
    on_table(6,S).
goal(3,S):-
    clear(12,S),
    clear(14,S),
    clear(15,S),
    on(1,5,S),
    on(11,7,S),
    on(12,2,S),
    on(13,8,S),
    on(14,1,S),
    on(15,13,S),
    on(2,3,S),
    on(3,11,S),
    on(5,10,S),
    on(7,6,S),
    on(8,9,S),
    on(9,4,S),
    on_table(10,S),
    on_table(4,S),
    on_table(6,S).

%p4
blocks(4,[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]).
initial(4,S):-
    clear(1,S),
    clear(11,S),
    clear(19,S),
    clear(9,S),
    on(1,12,S),
    on(10,5,S),
    on(11,10,S),
    on(12,13,S),
    on(14,15,S),
    on(16,3,S),
    on(17,16,S),
    on(18,17,S),
    on(19,18,S),
    on(3,2,S),
    on(4,14,S),
    on(5,4,S),
    on(7,6,S),
    on(8,7,S),
    on(9,8,S),
    on_table(13,S),
    on_table(15,S),
    on_table(2,S),
    on_table(6,S).

goal(4,S):-
    clear(12,S),
    clear(15,S),
    clear(17,S),
    on(1,5,S),
    on(11,7,S),
    on(12,2,S),
    on(13,8,S),
    on(14,1,S),
    on(15,13,S),
    on(16,11,S),
    on(17,18,S),
    on(18,19,S),
    on(19,14,S),
    on(2,3,S),
    on(3,16,S),
    on(5,10,S),
    on(7,6,S),
    on(8,9,S),
    on(9,4,S),
    on_table(10,S),
    on_table(4,S),
    on_table(6,S).

