% Solution to the Imaginary Friends logic puzzle.

% Facts (child, animal, adventure)
child(joanne).
child(lou).
child(ralph).
child(winnie).

animal(grizzly_bear).
animal(moose).
animal(seal).
animal(zebra).

adventure(circus).
adventure(rock_band).
adventure(spaceship).
adventure(train).

solve :-
    animal(JoanneFriend), animal(LouFriend), animal(RalphFriend), animal(WinnieFriend),
    all_different([JoanneFriend, LouFriend, RalphFriend, WinnieFriend]),
    
    adventure(JoanneAdventure), adventure(LouAdventure), adventure(RalphAdventure), adventure(WinnieAdventure),
    all_different([JoanneAdventure, LouAdventure, RalphAdventure, WinnieAdventure]),
    
    Stories = [ story(joanne, JoanneFriend, JoanneAdventure),
              story(lou, LouFriend, LouAdventure),
              story(ralph, RalphFriend, RalphAdventure),
              story(winnie, WinnieFriend, WinnieAdventure) ],
              
              
    % 1a. The seal is not the creation of either Joanne or Lou.
    \+ member(story(joanne, seal, _), Stories), 
    \+ member(story(lou, seal, _), Stories),

    % 1b. The seal neither did spaceship nor did train.
    \+ member(story(_, seal, spaceship), Stories),
    \+ member(story(_, seal, train), Stories),
    
    % 2a. Joanne's friend is not the grizzly bear.
    \+ member(story(joanne, grizzly_bear, _), Stories),

    % 2b. Joanne's friend went to the circus
    member(story(joanne, _, circus), Stories),
    
    % 3. Winnie's friend is a zebra.
    member(story(winnie, zebra, _), Stories),
    
    % 4. The grizzly bear did not do spaceship.
    \+ member(story(_, grizzly_bear, spaceship), Stories),
    
    tell(joanne, JoanneFriend, JoanneAdventure),
    tell(lou, LouFriend, LouAdventure),
    tell(ralph, RalphFriend, RalphAdventure),
    tell(winnie, WinnieFriend, WinnieAdventure).
    
tell(Child, Friend, Adventure) :- write(Child), write(' and the friend '), write(Friend), write(' did the '), write(Adventure), write(' adventure.'), nl.
    
% If the head of the list is in the tail, fail.
all_different([Head|Tail]) :- member(Head, Tail), !, fail.
% Otherwise, check the tail of the list.
all_different([_|Tail]) :- all_different(Tail).
% If there's only one thing in the list, it's fine.
all_different([_]).