:- op(900,fy,not).
mastermind(Code) :-
cleanup, guess(Code), check(Code), announce.
guess(Code) :-
Code = [X1,X2,X3], selects(Code,[1,2,3,4]).
/* Verify the proposed guess */
check(Guess) :-
not inconsistent(Guess), ask(Guess).
inconsistent(Guess) :-
query(OldGuess,Bulls,Cows),
not bulls_and_cows_match(OldGuess,Guess,Bulls,Cows).
bulls_and_cows_match(OldGuess,Guess,Bulls,Cows) :-
exact_matches(OldGuess,Guess,N1),
Bulls =:= N1,
common_members(OldGuess,Guess,N2),
Cows =:= N2 - Bulls.
exact_matches(X,Y,N) :-
size_of(A,same_place(A,X,Y),N).
common_members(X,Y,N) :-
size_of(A,(member(A,X),member(A,Y)),N).
same_place(X,[X|Xs],[X|Ys]).
same_place(A,[X|Xs],[Y|Ys]) :- same_place(A,Xs,Ys).
/* Asking a guess */
ask(Guess) :-
repeat,
writeln(['How many bulls and cows in ',Guess,'?']),
read((Bulls,Cows)),
sensible(Bulls,Cows), !,
assert(query(Guess,Bulls,Cows)),
Bulls =:= 4.
sensible(Bulls,Cows) :-
integer(Bulls), integer(Cows), Bulls + Cows =< 4.
/* Bookkeeping */
cleanup :- abolish(query,3).
announce :-
size_of(X,query(X,A,B),N),
writeln(['Found the answer after ',N,' queries']).
size_of(X,Goal,N) :-
findall(X,Goal,Instances), length(Instances,N).
selects([X|Xs],Ys) :-
select(X,Ys,Ys1),selects(Xs,Ys1).
selects([],Ys).
select(X,[X|Xs],Xs).
select(X,[Y|Ys],[Y|Zs]) :-
select(X,Ys,Zs).
length(Xs,N) :- length(Xs,0,N).
length([X|Xs],Acc,N) :-
Acc1 is Acc + 1,
length(Xs,Acc1,N).
length([],N,N).
% Program 21.1 Playing mastermind