Search code examples
prologzebra-puzzle

Prolog: Murder Mystery solution


I recently started learning Prolog for fun. I found the following murder mystery puzzle. Since I don't know much about Prolog except the basics, I cannot really evaluate the solution provided in the link, however, it didn't seem particularly nice to me. My solution is not enough to generate the correct answers so I'm looking for some pointers as to how to get there or if it's at all possible to get there with my approach. Here's the puzzle just in case the link goes down:

To discover who killed Mr. Boddy, you need to learn where each person was, and what weapon was in the room. Clues are scattered throughout the quiz (you cannot solve question 1 until all 10 are read).

To begin, you need to know the suspects. There are three men (George, John, Robert) and three women (Barbara, Christine, Yolanda). Each person was in a different room (Bathroom, Dining Room, Kitchen, Living Room, Pantry, Study). A suspected weapon was found in each room (Bag, Firearm, Gas, Knife, Poison, Rope). Who was found in the kitchen?

Clue 1: The man in the kitchen was not found with the rope, knife, or bag. Which weapon, then, which was not the firearm, was found in the kitchen?

Clue 2: Barbara was either in the study or the bathroom; Yolanda was in the other. Which room was Barbara found in?

Clue 3: The person with the bag, who was not Barbara nor George, was not in the bathroom nor the dining room. Who had the bag in the room with them?

Clue 4: The woman with the rope was found in the study. Who had the rope?

Clue 5: The weapon in the living room was found with either John or George. What weapon was in the living room?

Clue 6: The knife was not in the dining room. So where was the knife?

Clue 7: Yolanda was not with the weapon found in the study nor the pantry. What weapon was found with Yolanda?

Clue 8: The firearm was in the room with George. In which room was the firearm found?

It was discovered that Mr. Boddy was gassed in the pantry. The suspect found in that room was the murderer. Who, then, do you point the finger towards?

Here's the link to the author's solution.

Here's my attempted solution:

male(george).
male(john).
male(robert).

female(barbara).
female(christine).
female(yolanda).

person(X) :- male(X).
person(X) :- female(X).

room(kitchen).
room(bathroom).
room(diningroom).
room(livingroom).
room(pantry).
room(study).

weapon(bag).
weapon(firearm).
weapon(gas).
weapon(knife).
weapon(poison).
weapon(rope).

/*
Clue 1: The man in the kitchen was not found with 
        the rope, knife, or bag. 
        Which weapon, then, which was not the firearm, 
        was found in the kitchen?
*/

/* X is Weapon, Y is Room, Z is Person */

killer(X, Y, Z) :-
    room(Y) = room(kitchen),
    male(Z),
    dif(weapon(X), weapon(rope)),
    dif(weapon(X), weapon(knife)),
    dif(weapon(X), weapon(bag)),
    dif(weapon(X), weapon(firearm)).

/*
Clue 2: Barbara was either in the study or the bathroom; 
        Yolanda was in the other. 
        Which room was Barbara found in?
*/

/* It was easy to deduce the following from other data */

killer(X, Y, Z) :-
    female(Z) = female(barbara),
    room(study) = room(Y).

killer(X, Y, Z) :-
    female(Z) = female(yolanda),
    room(bathroom) = room(Y).

/*
Clue 3: The person with the bag, who was not Barbara nor 
        George, was not in the bathroom nor the dining room. 
        Who had the bag in the room with them?
*/
killer(X, Y, Z) :-
    weapon(bag) = weapon(X),
    dif(room(Y), room(bathroom)),
    dif(room(Y), room(diningroom)),
    dif(person(Z), male(george)),
    dif(person(Z), female(barbara)).


/*
Clue 4: The woman with the rope was found in the study. 
        Who had the rope?
*/
killer(X, Y, Z) :- 
    weapon(rope) = weapon(X),
    room(study) = room(Y),
    female(Z).

/*
Clue 5: The weapon in the living room was found with either 
        John or George. What weapon was in the living room?
*/

killer(X, Y, Z) :-
    room(Y) = room(livingroom),
    dif(male(Z), male(robert)).

/*
Clue 6: The knife was not in the dining room. 
        So where was the knife?
*/

killer(X, Y, Z) :-
    weapon(knife) = weapon(X),
    room(Y) \= room(diningroom).

/*
Clue 7: Yolanda was not with the weapon found 
        in the study nor the pantry. 
        What weapon was found with Yolanda?
*/

killer(X, Y, Z) :-
    female(yolanda) = female(Z),
    dif(room(study), room(Y)),
    dif(room(pantry), room(Y)).

/*
Clue 8: The firearm was in the room with George. 
        In which room was the firearm found?
*/

killer(X, Y, Z) :-
    weapon(firearm) = weapon(X),
    male(george) = male(Z).

/*
It was discovered that Mr. Boddy was gassed in the pantry. 
The suspect found in that room was the murderer. 
Who, then, do you point the finger towards?
*/

killer(X, Y, Z) :-
    room(Y) = room(pantry),
    weapon(X) = weapon(gas).

Solution

  • I took a more positive approach to this problem. Rather than trying any form of negation I went with just plain unification.

    Key is this predicate pair:

    members([],_).
    members([M|Ms],Xs) :- select(M,Xs,Ys),members(Ms,Ys).
    

    This is a basic permutation predicate. It will take a list of the first argument and try to unify against all permutations of second list.

    Now a lot of the rules became quite easy to express:

    For example, clue 1:

    clue1(House) :- members([[P,kitchen,_],[_,_,rope],[_,_,knife],[_,_,bag],[_,_,firearm]],House),man(P).
    

    So this meant that the rope, knife, bag and firearm were all members of the house, but in different rooms than the kitchen. Prolog would keep backtracking util it found a fit for these items.

    Here's my full solution:

    man(george).
    man(john).
    man(robert).
    woman(barbara).
    woman(christine).
    woman(yolanda).
    
    members([],_).
    members([M|Ms],Xs) :- select(M,Xs,Ys),members(Ms,Ys).
    
    clue1(House) :- members([[P,kitchen,_],[_,_,rope],[_,_,knife],[_,_,bag],[_,_,firearm]],House),man(P).
    clue2(House) :- member([barbara,study,_],House), member([yolanda,bathroom,_],House).
    clue2(House) :- member([barbara,bathroom,_],House), member([yolanda,study,_],House).
    clue3(House) :- members([[_,_,bag],[barbara,_,_],[george,_,_]],House),members([[_,_,bag],[_,bathroom,_],[_,dining_room,_]],House).
    clue4(House) :- members([[P,study,rope]],House),woman(P).
    clue5(House) :- members([[john,living_room,_]],House).
    clue5(House) :- members([[george,living_room,_]],House).
    clue6(House) :- members([[_,_,knife],[_,dining_room,_]],House).
    clue7(House) :- members([[yolanda,_,_],[_,study,_],[_,pantry,_]],House).
    clue8(House) :- member([george,_,firearm],House).
    clue9(House,P) :- members([[P,pantry,gas]],House).
    
    solve(X) :-
        House = [[_,bathroom,_],[_,dining_room,_],[_,kitchen,_],[_,living_room,_],[_,pantry,_],[_,study,_]],
        clue1(House),
        clue2(House),
        clue3(House),
        clue4(House),
        clue5(House),
        clue6(House),
        clue7(House),
        clue8(House),
        clue9(House,X),
        members([[george,_,_],[john,_,_],[robert,_,_],[barbara,_,_],[christine,_,_],[yolanda,_,_]],House),
        members([[_,_,bag],[_,_,firearm],[_,_,gas],[_,_,knife],[_,_,poison],[_,_,rope]],House),
        write(House),
        true.
    

    That gave me:

    ?- solve(X).
    [[yolanda,bathroom,knife],[george,dining_room,firearm],[robert,kitchen,poison],[john,living_room,bag],[christine,pantry,gas],[barbara,study,rope]]
    X = christine .