/*  Part of XPCE --- The SWI-Prolog GUI toolkit

    Author:        Christian Schlichtherle
    WWW:           http://www.swi-prolog.org
    Copyright (c)  1995, Christian Schlichtherle
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

:-module(rubikpce,[rubikpce/0]).
:-use_module(library(pce)).
:- require([ send_list/3
           ]).
:- license(gplv2).

% First read the Prolog predicates for solving Rubik's Cube. These predicates
% do the real work (i.e. order Rubik's Cube) and are defined so that they
% can be used alone without XPCE. This gives you the ability to use these
% predicates with any ordinary Prolog system (hopefully).
:-use_module(rubik).
:-use_module(maplist).

:-dynamic mousepos/2,drawlist/2,steplist/2,do_pause/1.

% This neat little trick gives us the ability to store a cube as an object
% level attribute of a frame.
:-pce_begin_class(cube,vector,
                  "Instances of this class represent Rubik's Cube.").
:-pce_end_class.

rubikpce:-
    new(Frame,frame('Rubik''s Cube')),
    make_pathlist(Frame,FrontWindow,_),
    make_boxlist(Frame,CrossWindow),
    send(FrontWindow,left,CrossWindow),
    send(Frame,append,new(Dialog,dialog)),
    send(CrossWindow,above,Dialog),
    send(Dialog,append,button(disorder,message(@prolog,disorder,@receiver?frame))),
    send(Dialog,append,button(adjust,message(@prolog,adjust,@receiver?frame))),
    send(Dialog,append,button(quit,message(@prolog,quit,@receiver?frame))),
    send(Dialog,append,button(step,message(@prolog,step,@receiver?frame)),next_row),
    send(Dialog,append,button(play,message(@prolog,play,@receiver?frame))),
    send(Dialog,append,button(pause,message(@prolog,pause,@receiver?frame))),
    send(Dialog,append,button(stop,message(@prolog,stop,@receiver?frame))),
    ordered(blue,red,green,orange,gray,yellow,Cube),
    send_cube(Frame,Cube),
    refresh(Frame),
    send(FrontWindow,open).

disorder(Frame):-
    % Ensure that no sequence is running.
      (get(Frame,attribute,timer,Timer) -> get(Timer,status,idle); true),
    %load_sequence(Frame,[f/1]),
    %load_sequence(Frame,[l/ -1,r/1,f/ -1,b/1,l/1,r/ -1,f/1,b/ -1,l/ -1,r/1,f/ -1,b/1]),
    %load_sequence(Frame,[r/2,l/2,u/2,r/2,l/2,d/2]),
    %load_sequence(Frame,[r/2,d/2,r/2,d/2,r/2,d/2]),
    %load_sequence(Frame,[r/ -1,l/1,f/2,r/1,l/ -1,u/2]),
    %load_sequence(Frame,[r/ -1,d/1,r/1,f/1,d/1,f/ -1,u/1,f/1,d/ -1,f/ -1,r/ -1,d/ -1,r/1,u/ -1]),
    %load_sequence(Frame,[r/1,l/ -1,u/ -1,d/1,f/1,b/ -1,r/1,l/ -1]),
    %load_sequence(Frame,[r/ -1,l/ -1,f/ -1,b/ -1,r/ -1,l/ -1,f/ -1,b/ -1,r/ -1,l/ -1,f/ -1,b/ -1]),
    %load_sequence(Frame,[r/1,l/1,u/2,r/ -1,l/ -1,f/ -1,b/ -1,d/2,f/1,b/1]),
    %load_sequence(Frame,[r/1,l/1,f/1,b/1,u/1,d/1,r/1,l/1,f/1,b/1,u/1,d/1]),
    %load_sequence(Frame,[r/1,l/1,u/2,r/ -1,l/ -1,f/1,b/1,u/2,f/ -1,b/ -1]),
    random_draws(DrawList),load_sequence(Frame,DrawList),
    play(Frame).

adjust(Frame):-
    % Ensure that no sequence is running.
      (get(Frame,attribute,timer,Timer)->get(Timer,status,idle);true),
    get_cube(Frame,Disordered),
    time(adjust(Disordered,Draws)),
    load_sequence(Frame,Draws),
    play(Frame).

time(Goal):-
    Goal.

step(Frame):-
    pause(Frame),
    start_timer(Frame).

play(Frame):-
    retractall(do_pause(Frame)),
    start_timer(Frame).

pause(Frame):-
    retractall(do_pause(Frame)),
    assert(do_pause(Frame)).

stop(Frame):-
    discard_sequence(Frame).

quit(Frame):-
    discard_sequence(Frame),
    retractall(steplist(Frame,_)),
    retractall(mousepos(Frame,_)),
    retractall(do_pause(Frame)),
    free(Frame).

load_sequence(Frame,Draws):-
    retractall(drawlist(Frame,_)),
    assert(drawlist(Frame,Draws)).

discard_sequence(Frame):-
    retractall(drawlist(Frame,_)).

start_timer(Frame):-
    get(Frame,attribute,timer,Timer),!,
    send(Timer,start).
start_timer(Frame):-
    new(Timer,timer(0.15,message(@prolog,timer_action,Frame))),
    send(Frame,attribute,attribute(timer,Timer)),
    send(Timer,start).

stop_timer(Frame):-
    get(Frame,attribute,timer,Timer),
    send(Timer,stop).

timer_action(Frame):-
    retract(steplist(Frame,[Step|Steps])),!,
    (Steps==[]->
          (
            (retract(do_pause(Frame))->stop_timer(Frame);true),
            send_cube(Frame,Step),
            refresh(Frame)
          );(
            assert(steplist(Frame,Steps)),
            display_cube(Frame,Step)
          )
    ).
timer_action(Frame):-
    retract(drawlist(Frame,[Draw|Draws])),!,
    get_cube(Frame,Cube),
    assert(drawlist(Frame,Draws)),
    singlesteps(Cube,Draw,StepList),
    assert(steplist(Frame,StepList)),
    timer_action(Frame).
timer_action(Frame):-
    retractall(drawlist(Frame,_)),
    stop_timer(Frame).

% make_pathlist(+Frame,-FrontWindow,-BackWindow).
make_pathlist(Frame,FrontWindow,BackWindow):-
    new(Size,size(6*20,6*20)),
    send(Frame,append,new(FrontWindow,window(cube_frontside,Size))),
    send(Frame,append,new(BackWindow,window(cube_backside,Size))),
    send(FrontWindow,above,BackWindow),
    send(FrontWindow,display,new(FrontDevice,device)),
    send(BackWindow,display,new(BackDevice,device)),
    send(FrontDevice,position,point(3.5*20,2.5*20)),
    send(BackDevice,position,point(3.5*20,3.5*20)),
    new(Vector,vector),
    send(Frame,attribute,attribute(pathvector,Vector)),
    path_position(FrontDevice,BackDevice,PosList),
    box_position(BoxPosList),
    make_paths(PosList,BoxPosList,Vector,1).

% This predicate is used to compute the graphical position of each path.
% in the list.
path_position(DF,DB,L):-
    path_position(DF,DB,u,l,f,r,d,b,L).
path_position(DF,DB,U,L,F,R,D,B,
                                [(DF,2,2,U),(DF,1,2,U),(DF,0,2,U),
                                 (DF,2,1,U),(DF,1,1,U),(DF,0,1,U),
                                 (DF,2,0,U),(DF,1,0,U),(DF,0,0,U),
(DB,2,0,L),(DB,2,1,L),(DB,2,2,L),(DF,0,2,F),(DF,0,1,F),(DF,0,0,F),(DF,0,0,R),(DF,1,0,R),(DF,2,0,R),
(DB,1,0,L),(DB,1,1,L),(DB,1,2,L),(DF,1,2,F),(DF,1,1,F),(DF,1,0,F),(DF,0,1,R),(DF,1,1,R),(DF,2,1,R),
(DB,0,0,L),(DB,0,1,L),(DB,0,2,L),(DF,2,2,F),(DF,2,1,F),(DF,2,0,F),(DF,0,2,R),(DF,1,2,R),(DF,2,2,R),
                                 (DB,2,0,D),(DB,2,1,D),(DB,2,2,D),
                                 (DB,1,0,D),(DB,1,1,D),(DB,1,2,D),
                                 (DB,0,0,D),(DB,0,1,D),(DB,0,2,D),
                                 (DB,0,0,B),(DB,1,0,B),(DB,2,0,B),
                                 (DB,0,1,B),(DB,1,1,B),(DB,2,1,B),
                                 (DB,0,2,B),(DB,1,2,B),(DB,2,2,B)]
                             ).

make_paths([],[],_,_).
make_paths([(Device,X,Y,Side)|PosList],[(Row,Col)|BoxPosList],Vector,I):-
    compute_pathpoints(X,Y,Side,Point1,Point2,Point3,Point4),
    send(Device,display,new(Path,path)),
    send(Path,closed,@on),
    send_list(Path,append,[Point1,Point2,Point3,Point4]),
    send(Path,fill_pattern,@black_image),
    send(Path,recogniser,@draw_field_gesture),
    send(Path,attribute,attribute(row,Row)),
    send(Path,attribute,attribute(col,Col)),
    send(Vector,element,I,Path),
    II is I+1,
    make_paths(PosList,BoxPosList,Vector,II).

compute_pathpoints(X,Y,Side,point(OX1,OY1),point(OX2,OY2),point(OX3,OY3),point(OX4,OY4)):-
    path_shape(Side,(IX1,IY1),(IX2,IY2),(IX3,IY3),(IX4,IY4)),
      sgn(IX2, IX2SGN),
      sgn(IX4, IX4SGN),
      sgn(IY4, IY4SGN),
      sgn(IY2, IY2SGN),
      PX is Y * (IX2 + IX2SGN) + X * (IX4 + IX4SGN),
      PY is X * (IY4 + IY4SGN) + Y * (IY2 + IY2SGN),
    OX1 is PX+IX1,OY1 is PY+IY1,
    OX2 is PX+IX2,OY2 is PY+IY2,
    OX3 is PX+IX3,OY3 is PY+IY3,
    OX4 is PX+IX4,OY4 is PY+IY4.

% path_shape(?Side,?Point1,?Point2,?Point3,?Point4) defines the shape of the
% paths. The points are listed counterclockwise. The first point is always
% (0,0).
path_shape(u,(0,0),( 11,-11),( -9,-11),(-20,  0)).
path_shape(l,(0,0),( 11, 11),( 11, -9),(  0,-20)).
path_shape(f,(0,0),(-20,  0),(-20, 20),(  0, 20)).
path_shape(r,(0,0),(  0, 20),( 11,  9),( 11,-11)).
path_shape(d,(0,0),(-20,  0),( -9, 11),( 11, 11)).
path_shape(b,(0,0),(  0,-20),(-20,-20),(-20,  0)).

sgn(0,0).
sgn(X,Y):-
    (X >= 0 -> Y is 1; Y is -1).

make_boxlist(Frame,Window):-
    send(Frame,append,new(Window,window(cube_cross,size(10*20,13*20)))),
    send(Window,display,new(Device,device)),
    send(Device,move,point(10,10)),
    new(Vector,vector),
    send(Frame,attribute,attribute(boxvector,Vector)),
    box_position(PosList),
    % This does not yet work because window<-area does not return the
    % visible area of the window.
    %new(_,constraint(Window,Device,spatial(xref=x+w/2,yref=y+h/2,xref=x+w/2,yref=y+h/2))),
    make_boxes(Device,PosList,Vector,1).

% This predicate is used to compute the graphical position of each box
% in the list.
box_position(
                    [( 0,3),( 0,4),( 0,5),
                     ( 1,3),( 1,4),( 1,5),
                     ( 2,3),( 2,4),( 2,5),
( 3,0),( 3,1),( 3,2),( 3,3),( 3,4),( 3,5),( 3,6),( 3,7),( 3,8),
( 4,0),( 4,1),( 4,2),( 4,3),( 4,4),( 4,5),( 4,6),( 4,7),( 4,8),
( 5,0),( 5,1),( 5,2),( 5,3),( 5,4),( 5,5),( 5,6),( 5,7),( 5,8),
                     ( 6,3),( 6,4),( 6,5),
                     ( 7,3),( 7,4),( 7,5),
                     ( 8,3),( 8,4),( 8,5),
                     ( 9,3),( 9,4),( 9,5),
                     (10,3),(10,4),(10,5),
                     (11,3),(11,4),(11,5)
              ]).

make_boxes(_,[],_,_).
make_boxes(Device,[(Y,X)|PosList],Vector,I):-
    send(Device,display,new(Box,box(20,20)),point(X*20,Y*20)),
    send(Box,radius,3),
    send(Box,fill_pattern,@black_image),
    send(Box,recogniser,@draw_field_gesture),
    send_list(Box,attribute,[attribute(row,Y),attribute(col,X)]),
    send(Vector,element,I,Box),
    II is I+1,
    make_boxes(Device,PosList,Vector,II).

% refresh(+Frame) will redisplay the cube associated with Frame on the
% display.
%
% To alter the cube associated with the frame and refresh the display use
%
%   send_cube(Frame,Cube),refresh(Frame).
refresh(Frame):-
    get(Frame,attribute,cube,CubeVector),
    send(?(Frame,attribute,pathvector),for_all,
         message(@arg1,colour,?(CubeVector,element,@arg2))),
    send(?(Frame,attribute,boxvector),for_all,
         message(@arg1,colour,?(CubeVector,element,@arg2))).

% display_cube(+Frame,+CubeTerm) will display CubeTerm on Frame. However, it
% will not alter the cube associated with Frame. To do this, use send_cube/2.
%
% To alter the cube associated with the frame and refresh the display use
%
%   send_cube(Frame,Cube),refresh(Frame).
display_cube(Frame,CubeTerm):-
    new(CubeVector,CubeTerm),
    send(?(Frame,attribute,pathvector),for_all,
         message(@arg1,colour,?(CubeVector,element,@arg2))),
    send(?(Frame,attribute,boxvector),for_all,
         message(@arg1,colour,?(CubeVector,element,@arg2))).

% singlesteps describes the single steps needed to make draw (f,1).
% This predicate is needed for draw animation.
singlesteps(
    cube(U1,U2,U3,
         U4,U5,U6,
         U7,U8,U9,
L1,L2,L3,F1,F2,F3,R1,R2,R3,
L4,L5,L6,F4,F5,F6,R4,R5,R6,
L7,L8,L9,F7,F8,F9,R7,R8,R9,
         D1,D2,D3,
         D4,D5,D6,
         D7,D8,D9,
         B1,B2,B3,
         B4,B5,B6,
         B7,B8,B9),
         f/1,
         [
    cube(U1,U2,U3,
         U4,U5,U6,
         L3,U7,U8,
L1,L2,L6,F1,F2,F3,U9,R2,R3,
L4,L5,L9,F4,F5,F6,R1,R5,R6,
L7,L8,D1,F7,F8,F9,R4,R8,R9,
         D2,D3,R7,
         D4,D5,D6,
         D7,D8,D9,
         B1,B2,B3,
         B4,B5,B6,
         B7,B8,B9),
    cube(U1,U2,U3,
         U4,U5,U6,
         L6,L3,U7,
L1,L2,L9,F4,F1,F2,U8,R2,R3,
L4,L5,D1,F7,F5,F3,U9,R5,R6,
L7,L8,D2,F8,F9,F6,R1,R8,R9,
         D3,R7,R4,
         D4,D5,D6,
         D7,D8,D9,
         B1,B2,B3,
         B4,B5,B6,
         B7,B8,B9),
    cube(U1,U2,U3,
         U4,U5,U6,
         L9,L6,L3,
L1,L2,D1,F7,F4,F1,U7,R2,R3,
L4,L5,D2,F8,F5,F2,U8,R5,R6,
L7,L8,D3,F9,F6,F3,U9,R8,R9,
         R7,R4,R1,
         D4,D5,D6,
         D7,D8,D9,
         B1,B2,B3,
         B4,B5,B6,
         B7,B8,B9)
     ]).
singlesteps(C1,b/1,[S1,S2,S3]):-
    compute_singlesteps(C1,[tilt_cube(C1,C2),tilt_cube(C2,_)],[S1,S2,S3]).
singlesteps(C1,u/1,[S1,S2,S3]):-
    compute_singlesteps(C1,[tilt_cube(_,C1)],[S1,S2,S3]).
singlesteps(C1,d/1,[S1,S2,S3]):-
    compute_singlesteps(C1,[tilt_cube(C1,_)],[S1,S2,S3]).
singlesteps(C1,l/1,[S1,S2,S3]):-
    compute_singlesteps(C1,[tilt_cube(C1,C2),turn_cube(C2,C3),tilt_cube(_,C3)],[S1,S2,S3]).
singlesteps(C1,r/1,[S1,S2,S3]):-
    compute_singlesteps(C1,[tilt_cube(C1,C2),turn_cube(C2,C3),tilt_cube(C3,_)],[S1,S2,S3]).
singlesteps(C1,M/ -1,[S1,S2,C2]):-
    draw(C1,M/ -1,C2),
    singlesteps(C2,M/1,[S2,S1,C1]).
singlesteps(C1,M/2,[S1,S2,S3,S4,S5,S6]):-
    singlesteps(C1,M/1,[S1,S2,S3]),
    singlesteps(S3,M/1,[S4,S5,S6]).

compute_singlesteps(Cube,[],[S1,S2,S3]):-
    singlesteps(Cube,f/1,[S1,S2,S3]).
compute_singlesteps(C1,[Command|Commands],[S1,S2,S3]):-
    Command=..[Functor,C1,C2],
    Command,
    compute_singlesteps(C2,Commands,[S4,S5,S6]),
    T1=..[Functor,S1,S4],T1,
    T2=..[Functor,S2,S5],T2,
    T3=..[Functor,S3,S6],T3.
compute_singlesteps(C1,[Command|Commands],[S1,S2,S3]):-
    Command=..[Functor,C2,C1],
    Command,
    compute_singlesteps(C2,Commands,[S4,S5,S6]),
    T1=..[Functor,S4,S1],T1,
    T2=..[Functor,S5,S2],T2,
    T3=..[Functor,S6,S3],T3.

:- pce_global(@draw_field_gesture,make_draw_field_gesture).
:- pce_global(@hand2_cursor,new(cursor(hand2))).

make_draw_field_gesture(G):-
    new(G,gesture),
    send(G,send_method,send_method(verify,vector(event),message(@prolog,verify_draw,@arg1))),
    send(G,send_method,send_method(initiate,vector(event),message(@prolog,initiate_draw,G))),
    send(G,send_method,send_method(drag,vector(event),message(@prolog,drag_draw,@arg1))),
    send(G,send_method,send_method(terminate,vector(event),message(@prolog,terminate_draw,@arg1))),
    send(G,status,active).

verify_draw(PCEEvent):-
    get(PCEEvent,receiver,PCEDevice),
    get(PCEDevice,frame,PCEFrame),
    get(PCEDevice,attribute,row,FromRow),
    get(PCEDevice,attribute,col,FromCol),
    retractall(mousepos(PCEFrame,_)),
    assert(mousepos(PCEFrame,FromRow/FromCol)).

initiate_draw(PCEGesture):-
    send(PCEGesture,cursor,@hand2_cursor).

drag_draw(Event):-
    get(Event,receiver,FromField),
    get(FromField,frame,Frame),
    get(?(FromField?device,pointed_objects,Event),head,ToField),
    get(ToField,attribute,row,ToRow),
    get(ToField,attribute,col,ToCol),
    mousepos(Frame,FromRow/FromCol),
    get_cube(Frame,Cube),!,
    pos2draw(FromRow/FromCol,ToRow/ToCol,Draw,Steps),
    compute_drag(Frame,ToRow/ToCol,Cube,Draw,Steps,NewCube),
    display_cube(Frame,NewCube).

% There is a bug in here. If the user makes a drag so that more than three
% singlesteps have to be made AT ONCE and the number of the singlesteps is not
% a multiple of three and the user releases the mouse button before reaching
% the next multiple of three, the cube will jump back to the initial position
% instead of to the next multiple of three.
% You may find this a bit complicated - I agree.
% You may even never notice this bug - that's why I don't fix it.
compute_drag(_,_,Cube,_,0,Cube):-!.
compute_drag(_,_,Cube,Draw,1,NewCube):-
    !,singlesteps(Cube,Draw,[NewCube|_]).
compute_drag(_,_,Cube,Draw,2,NewCube):-
    !,singlesteps(Cube,Draw,[_,NewCube|_]).
compute_drag(Frame,ToRow/ToCol,Cube,Draw,3,NewCube):-
    !,draw(Cube,Draw,NewCube),
    send_cube(Frame,NewCube),
    asserta(mousepos(Frame,ToRow/ToCol)).
compute_drag(Frame,Pos,Cube,Draw,Steps,NewCube):-
    draw(Cube,Draw,HelpCube),
    HelpSteps is Steps - 3,
    compute_drag(Frame,Pos,HelpCube,Draw,HelpSteps,NewCube).

terminate_draw(Event):-
    get(Event?receiver,frame,Frame),
    retractall(mousepos(Frame,_)),
    refresh(Frame).

% pos2draw(+From,+To,-Draw,-StepCount).
pos2draw(From,To,Draw,StepCount):-
    tilt_pos(From,M,TiltFrom),
    tilt_pos(To,M,TiltTo),
    turn_pos(TiltFrom,N,TiltRotFrom),
    turn_pos(TiltTo,N,TiltRotTo),
    simple_pos2draw(TiltRotFrom,TiltRotTo,TiltRotDraw,StepCount),
      n_maplist(N,perm_turn_cube,[TiltDraw],[TiltRotDraw]),
      n_maplist(M,perm_tilt_cube,[Draw],[TiltDraw]).

simple_pos2draw(3/FromCol,3/ToCol,u/1,Steps):-
    FromCol >= ToCol,!,
    Steps is FromCol - ToCol.
simple_pos2draw(3/FromCol,3/ToCol,u/ -1,Steps):-
    Steps is ToCol - FromCol.

% turn_pos(+Pos,+N,?RotPos) will rotate the cube from 0 to 3 times clockwise.
turn_pos(Pos,0,Pos).
turn_pos(Pos,1,RotPos):-
    turn_pos(Pos,RotPos).
turn_pos(Pos,2,RotPos):-
    turn_pos(Pos,Help),
    turn_pos(Help,RotPos).
turn_pos(Pos,3,RotPos):-
    turn_pos(Pos,Help1),
    turn_pos(Help1,Help2),
    turn_pos(Help2,RotPos).

% turn_pos(+Pos,?RotPos). Order is important here!
turn_pos(Row1/Col1,Row2/Col2):- % upside -> right
    Row1 < 3,!,
    Row2 is Col1,
    Col2 is 8 - Row1.
turn_pos(Row1/Col1,Row2/Col2):- % left -> upside
    Col1 < 3,!,
    Row2 is Col1,
    Col2 is 8 - Row1.
turn_pos(Row1/Col1,Row2/Col2):- % right -> downside
    Col1 > 5,!,
    Row2 is Col1,
    Col2 is 8 - Row1.
turn_pos(Row1/Col1,Row2/Col2):- % back -> back
    Row1 > 8,!,
    Row2 is 14 - Col1,
    Col2 is Row1 - 6.
turn_pos(Row1/Col1,Row2/Col2):- % down -> left
    Row1 > 5,!,
    Row2 is Col1,
    Col2 is 8 - Row1.
turn_pos(Row1/Col1,Row2/Col2):- % front -> front
    Row2 is Col1,
    Col2 is 8 - Row1.

% tilt_pos(+Pos,+N,?TiltPos) will tilt the cube M times from the front to the upside.
tilt_pos(Pos,0,Pos).
tilt_pos(Pos,1,TiltPos):-
    tilt_pos(Pos,TiltPos).
tilt_pos(Pos,2,TiltPos):-
    tilt_pos(Pos,Help),
    tilt_pos(Help,TiltPos).
tilt_pos(Pos,3,TiltPos):-
    tilt_pos(Pos,Help1),
    tilt_pos(Help1,Help2),
    tilt_pos(Help2,TiltPos).

% tilt_pos(+Pos,?TiltPos) will tilt the cube from the front to the upside.
tilt_pos(Row1/Col,Row2/Col):- % up -> back
    Row1 < 3,!,
    Row2 is 9 + Row1.
tilt_pos(Row1/Col1,Row2/Col2):- % left -> left
    Col1 < 3,!,
    Row2 is 5 - Col1,
    Col2 is Row1 - 3.
tilt_pos(Row1/Col1,Row2/Col2):- % right -> right
    Col1 > 5,!,
    Row2 is Col1 - 3,
    Col2 is 11 - Row1.
tilt_pos(Row1/Col,Row2/Col):- % back -> down, down -> front, front -> up
    Row2 is Row1 - 3.

% These "methods" should be really implemented as such one day:

% send_cube(+Frame,+Cube) associates cube with the frame. However, it does not
% display the cube.
%
% To alter the cube associated with the frame and refresh the display use
%
%   send_cube(Frame,Cube),refresh(Frame).
send_cube(Frame,Cube):-
    send(Frame,attribute,attribute(cube,Cube)).

get_cube(Frame,Cube):-
    Cube=cube(_,_,_,
              _,_,_,
              _,_,_,
        _,_,_,_,_,_,_,_,_,
        _,_,_,_,_,_,_,_,_,
        _,_,_,_,_,_,_,_,_,
              _,_,_,
              _,_,_,
              _,_,_,
              _,_,_,
              _,_,_,
              _,_,_),
    get(Frame,attribute,cube,Cube).

end_of_file.
