<div class="notebook">
<div class="nb-cell program" name="p1">
:- use_module(library(clpfd)). % we're working with numbers, this makes it easier.
clue_1([9, 2, 8, 5]). % one number correct, but in the wrong place
clue_2([1, 9, 3, 7]). % two numbers are correct, but in the wrong place
clue_3([5, 2, 0, 1]). % one number is correct, and is also in the right place
clue_4([6, 5, 0, 7]). % none of the numbers are correct, anywhere
clue_5([8, 5, 2, 4]). % two numbers are correct, but in the wrong place
% rule: a digit is correct but it is in the wrong place
wrong_place(Digit, Index, Digits) :- nth1(Index1, Digits, Digit), Index \== Index1.
% rule: a digit is correct and it is in the right place
right_place(Digit, Index, Digits) :- nth1(Index, Digits, Digit).
% rule: the digit is wrong.
wrong(_, []).
wrong(Digit, [D|Ds]) :- Digit #\= D, wrong(Digit, Ds).
crack_code(Code) :-
% A, B, C and D represent the four digits of the code, which are all between 0 and 9.
A in 0..9,
B in 0..9,
C in 0..9,
D in 0..9,
% ';' means 'or', whereas ',' means 'and'
% one digit in D1 is correct, but in the wrong place
% the other three digits must therefore be incorrect
% query this for each digit.
clue_1(D1),
(
wrong_place(A, 1, D1), wrong(B, D1), wrong(C, D1), wrong(D, D1);
wrong_place(B, 2, D1), wrong(A, D1), wrong(C, D1), wrong(D, D1);
wrong_place(C, 3, D1), wrong(A, D1), wrong(B, D1), wrong(D, D1);
wrong_place(D, 4, D1), wrong(A, D1), wrong(B, D1), wrong(C, D1)
),
% two digits are correct this time, and they are both in the wrong place
% exhaustively check every combination where two numbers are correct, and the other two are incorrect.
clue_2(D2),
(
wrong_place(A, 1, D2), wrong_place(B, 2, D2), wrong(C, D2), wrong(D, D2);
wrong_place(A, 1, D2), wrong_place(C, 3, D2), wrong(B, D2), wrong(D, D2);
wrong_place(A, 1, D2), wrong_place(D, 4, D2), wrong(B, D2), wrong(C, D2);
wrong_place(B, 2, D2), wrong_place(A, 1, D2), wrong(C, D2), wrong(D, D2);
wrong_place(B, 2, D2), wrong_place(C, 3, D2), wrong(A, D2), wrong(D, D2);
wrong_place(B, 2, D2), wrong_place(D, 4, D2), wrong(A, D2), wrong(C, D2);
wrong_place(C, 3, D2), wrong_place(A, 1, D2), wrong(B, D2), wrong(D, D2);
wrong_place(C, 3, D2), wrong_place(B, 2, D2), wrong(A, D2), wrong(D, D2);
wrong_place(C, 3, D2), wrong_place(D, 4, D2), wrong(A, D2), wrong(B, D2);
wrong_place(D, 4, D2), wrong_place(A, 1, D2), wrong(B, D2), wrong(C, D2);
wrong_place(D, 4, D2), wrong_place(B, 2, D2), wrong(A, D2), wrong(C, D2);
wrong_place(D, 4, D2), wrong_place(C, 3, D2), wrong(A, D2), wrong(B, D2)
),
% one digit is correct, and also in the right place
% as above, we still don't know which digit that is, so we check each one.
clue_3(D3),
(
right_place(A, 1, D3), wrong(B, D3), wrong(C, D3), wrong(D, D3);
right_place(B, 2, D3), wrong(A, D3), wrong(C, D3), wrong(D, D3);
right_place(C, 3, D3), wrong(A, D3), wrong(B, D3), wrong(D, D3);
right_place(D, 4, D3), wrong(A, D3), wrong(B, D3), wrong(C, D3)
),
% none of the digits are correct, so they can be completely excluded
% we know for a fact the final result will not contain any of these digits.
clue_4(D4),
(
wrong(A, D4), wrong(B, D4), wrong(C, D4), wrong(D, D4)
),
% again, two digits are correct but not in the right order
% we do a similar check as before but also need to look
% back into the previous clue to eliminate wrong candidates;
% this is why we query D2, as well as D5.
clue_5(D5),
(
wrong_place(A, 1, D5), wrong_place(B, 2, D5), wrong(C, D5), wrong(D, D5);
wrong_place(A, 1, D5), wrong_place(C, 3, D5), wrong(B, D5), wrong(D, D5);
wrong_place(A, 1, D5), wrong_place(D, 4, D5), wrong(B, D2), wrong(C, D2);
wrong_place(B, 2, D5), wrong_place(A, 1, D5), wrong(C, D5), wrong(D, D5);
wrong_place(B, 2, D5), wrong_place(C, 3, D5), wrong(A, D5), wrong(D, D5);
wrong_place(B, 2, D5), wrong_place(D, 4, D5), wrong(A, D2), wrong(C, D2);
wrong_place(C, 3, D5), wrong_place(A, 1, D5), wrong(B, D5), wrong(D, D5);
wrong_place(C, 3, D5), wrong_place(B, 2, D5), wrong(A, D5), wrong(D, D5);
wrong_place(C, 3, D5), wrong_place(D, 4, D5), wrong(A, D2), wrong(B, D2);
wrong_place(D, 4, D5), wrong_place(A, 1, D5), wrong(B, D5), wrong(C, D5);
wrong_place(D, 4, D5), wrong_place(B, 2, D5), wrong(A, D5), wrong(C, D5);
wrong_place(D, 4, D5), wrong_place(C, 3, D5), wrong(A, D2), wrong(B, D2)
),
% Take (or cut) the first result, no need for continued backtracking
% this is probably most similar to an early return or short-circuit.
!,
% we've cracked the code! A, B, C, and D each refer to
% the only answer that makes sense given the previous
% rules.
Code = [A, B, C, D].
</div>
</div>