Search code examples
prologmaze

Incorrect results for Prolog project/puzzle


I am working in a hard Prolog project/puzzle but I cant find the solution. I would thank any help.

The practice is to model and solve logic programming through a maze. It consists of rooms, doors and keys. When two rooms are connected they are connected through a door which is closed by one or more locks, which will require to be opened to move from one room to the other. Keys and locks are indistinct , IE any key opens any lock. After opening a door it is always open, but the key's stuck in the lock and can not be recovered (lost forever), so that for opening each door they will take many keys and locks. In each room there can be unused keys, which can be collected for further opening new doors. Initially we have no keys.

The solution program must define a predicate camino / 3 (camino is road in Spanish) such that (A, F, X) is true if X is a road leading to the room F based on the stay, so that you can go opening the door with the keys corresponding available at all times. The road shall be calculated as a list of the names of the rooms in the order in which they cross (and thus starting at A and ending at F).

The program will define a predicate e / 2 of the form e (E , L ) for each room E, where L is the number of keys contained in that room. You also define a predicate p / 3 of the form p (E1, E2 , C) for each door, where C is the number of locks having the door that connects E1 and E2 rooms. The program should maintain the state of the rooms (if they have keys or not) and doors (if already open or still locked) at all times.

An example would be (Source):

%rooms+ number of keys 
e(a,1). 
e(b,2). 
e(c,1). 
e(d,0). 
e(e,0). 
e(f,0). 

%Doors 
p(a,b,1). 
p(a,c,1). 
p(b,d,1). 
p(c,e,1). 
p(d,f,1). 
p(e,f,2).

Results should be:

?‐ camino(a,f,X). 
X = [a,b,a,c,d,f] ? 
yes 
?‐ camino(a,f,X). 
X = [a,c,d,f] ? 
yes 

This is my current code. It finds its way to the destiny but it's not correct. Also I still haven't applied the cuts so that it only gives me one answer:

%CODE

% these are the rooms and the number of keys they contain
e(a,1).
e(b,2).
e(c,1).
e(d,0).
e(e,0). 
e(f,0).

%these are the doors and the number of keys needed to open them
p(a,b,1).
p(a,c,1).    
p(b,d,1).    
p(c,e,1).    
p(d,f,1).    
p(e,f,2).          

concatenate([],L,L).        
concatenate([X|M],L,[X|Y]) :-        
   concatenate(M,L,Y).

%%%%%%%%%%%%%
camino(A,F,X):-    
   A==F,                     %check if we start at the destiny    
   concatenate([],[F],X).    
%%%%%%%%%%%%%%%%%%%%%%%%%%    
camino(A,F,X):-    
   A\==F,                     %check if we dont start at the destiny    
   concatenate([],[A],R),    
   findRoad(A,F,0,R,X).    
%%%%%%%%%%%%%%%%%%
%TRUE if x is a road (list) that leads to room F starting from A
%
findRoad(A,F,K,R,X):-     %k is key ---  initial keys    
    addkey(A,K,L),        %L new key--- number of keys after we add the keys of the room    
    pickkey(A),           %we put the number of keys of the room to 0    
    passDoor(A,L,P,_),    %P is position-- position of the new room    
    opendoor(A,P),        %we put the number of keys needed to pass the door to 0    
    P == F,               %we check if we have finished    
    concatenate(R,[P],X). %we concatenate the destiny and end   
findRoad(A,F,K,R,X):-     %k is key ---  initial keys   
    addkey(A,K,L),        %L new key--- number of keys after we add the keys of the room    
    pickkey(A),           %we put the number of keys of the room to 0    
    passDoor(A,L,P,L2),   %P is position-- position of the new room   

    opendoor(A,P),        %we put the number of keys needed to pass the door to 0    
    P \== F,              %we check we haven't finished   
    concatenate(R,[P],R2),%we concatenate the path we have for the moment   
    findRoad(P,F,L2,R2,X).

addkey(A,K,L):-    
    e(A,N),    
    L is K+N.    

passDoor(A,L,P,L2):-    
    p(A,P,W),    
    L2 is L-W,    
    L2 >= 0.   
passDoor(A,L,P,L2):-   
    p(P,A,W),   
    L2 is L-W,    
    L2 >= 0.    

pickkey(A):-    
    e(A,_) = e(A,0).    

opendoor(A,P):-    
    p(A,P,_) = p(A,P,0).       
opendoor(A,P):-    
    p(P,A,_) = p(P,A,0).

Solution

  • A method, that does not provides the optimal path (in the sense of minimal number of steps between rooms), but fulfills the request is as following. Main rule is:

    path( Orig, Dest, Res ) :-
       e( Orig, Keys ),
       nextDoor( Dest, [(Orig,Orig)], Keys, [_|OpenDoors] ), !,
       joinDoors( Orig, OpenDoors, [Orig], Res ), !.
    

    that means, after init the number of keys with the keys of the initial room, algorithm will decide in which order the doors must be opened (nextDoor), and second will find the path between one door to next door in previous list.

    The rationalle is: at a given moment, we have a set of open doors, and a set of rooms connected by these open doors. Movement across the area of open doors and visited rooms is free, doors are already open and visited rooms has not yet keys. Thus, we are only interested in decide which is the next door we must open. The rules to decide this order of doors to open is:

    nextDoor( Dest, OpenDoors, _, Res ) :-
      visitedRoom( Dest, OpenDoors ), 
      !,
      reverse( OpenDoors, Res ). 
    
    nextDoor( Dest, OpenDoors, Keys, Res ) :-
      /* choice a visited room */
      visitedRoom( Room, OpenDoors ), 
    
      /* next door not yet open */
      door( Room, NextRoom, DoorCost ),
      \+ member( (Room,NextRoom), OpenDoors ),
      \+ member( (NextRoom,Room), OpenDoors ),
    
      /* we can open door ? */
      DoorCost =< Keys,
    
      /* do not open doors to rooms already visited */
      \+ visitedRoom( NextRoom, OpenDoors ),
    
      /* ok, cross door and next one */
      e( NextRoom, RoomAward ),
      Keys2 is Keys-DoorCost+RoomAward,
      nextDoor( Dest, [(Room,NextRoom)|OpenDoors], Keys2, Res ).
    

    where two utilities are used, one to provide bidirectional paths:

    door(From,To,Cost) :-
      ( p(From,To,Cost); p(To,From,Cost) ).
    

    and another one to find the rooms in the area of already visited ones (rooms connected by open doors):

    visitedRoom( Room, OpenDoors ) :-
      ( member( (_,Room), OpenDoors ); member( (Room,_), OpenDoors ) ).
    

    The second step is go from one door to the next door following the previous order.

    joinDoors( _, [], Path, Res ) :-
       reverse( Path, Res ).
    joinDoors( CurrentRoom, [ (RoomBeforeDoor, RoomAfterRoom ) | Q ], Path, Res ) :-
       roomToRoom( CurrentRoom, RoomBeforeDoor, [], Path2 ),
       append( Path2, Path, Path3 ),
       joinDoors( RoomAfterRoom, Q, [RoomAfterRoom|Path3], Res ).
    

    where roomToRoom is a clasical algorithm to find a path (TODO: optimize to find the shortest path):

    roomToRoom( DestRoom, DestRoom, Path, Path ) :- !.
    roomToRoom( CurrentRoom, DestRoom, Path, Res ) :-
      door( CurrentRoom, NextRoom, _ ),
      \+ member( NextRoom, Path ),
      roomToRoom( DestRoom, NextRoom, [NextRoom|Path], Res ).
    

    If we try with the data provided in the example:

    e(a,1).
    e(b,2).
    e(c,1).
    e(d,0).
    e(e,0).
    e(f,0).
    
    p(a,b,1).
    p(a,c,1).
    p(b,d,2). /* corrected */
    p(c,d,1). /* add */
    p(c,e,1).
    p(d,f,1).
    p(e,f,2).
    

    the result is:

    ?- path(a,f,Path).
    Path = [a, b, a, c, d, f].