Skip to content

Instantly share code, notes, and snippets.

@carwash
Created August 6, 2021 16:49
Show Gist options
  • Save carwash/350ea7b217dc3fe9e80f3b6e92de9373 to your computer and use it in GitHub Desktop.
Save carwash/350ea7b217dc3fe9e80f3b6e92de9373 to your computer and use it in GitHub Desktop.
The Countdown numbers game, solved in Prolog. <https://en.wikipedia.org/wiki/Countdown_(game_show)#Numbers_round>
:- encoding(utf8).
:- use_module(library(apply)).
:- use_module(library(clpfd)).
:- use_module(library(lists)).
:- use_module(library(pairs)).
/*
The Countdown numbers game, solved in Prolog.
https://en.wikipedia.org/wiki/Countdown_(game_show)#Numbers_round
- Finds exact solutions only;
- Lists all unique solutions, shortest first;
Where Ns is a list of numbers, and T is the target number:
?- countdown(Ns,T).
E.g.:
?- countdown([25,50,75,100,3,6],952).
7 solutions found.
List candidate targets:
?- countdown([25,50,75,100,3,6],T).
List all possible puzzles:
?- valid_puzzle(Ns,T), label([T|Ns]).
List all possible puzzles and solutions:
?- countdown(Ns,T).
*/
countdown(Ns,T) :-
valid_puzzle(Ns,T), % Comment out to remove Countdown rules and generalise for all integers and list lengths
label([T|Ns]),
bagof(Steps, step(Ns,T,Steps), Solutions0),
dedup(Solutions0,Solutions),
length(Solutions,No_solutions),
write(No_solutions), writeln(' solutions found.'), nl,
list_length_sorted(Solutions, Solutions_sorted),
maplist(maplist(writeln), Solutions_sorted) .
valid_puzzle(Ns,T) :-
length(Ns,6),
maplist(#<(0), [T|Ns]),
maplist(#\=(T), Ns),
T in 100..999,
Ns ins 1..10 \/ 25 \/ 50 \/ 75 \/ 100,
cardinality(Ns) .
cardinality([]).
cardinality([N|Ns]) :- % Small numbers can, and large numbers must, occur only once
N in 1..10 \/ 25 \/ 50 \/ 75 \/ 100,
maplist(#\=(N), Ns),
cardinality(Ns) .
cardinality([N|Ns]) :- % A small number 1–10 can also occur exactly twice
N in 1..10,
selectchk(N,Ns,Ns1),
maplist(#\=(N), Ns1),
cardinality(Ns) .
% Take a valid 'step' toward a solution by performing an operation on two numbers, or reaching the target number
step(Ns,T,['---']) :- member(T,Ns) .
step(Ns,T,[S|Steps]) :-
Ns = [_,_|_],
maplist(#\=(T), Ns),
select(A,Ns,Ns1),
select(B,Ns1,Ns2),
A #>= B,
operation(A,B,C,S),
step([C|Ns2],T,Steps) .
% The four permitted arithmetic operations
operation(A,B,C,S) :-
C #= A + B,
atomic_list_concat([A,' + ',B,' = ',C], S) .
operation(A,B,C,S) :-
A #> B,
C #= A - B,
atomic_list_concat([A,' − ',B,' = ',C], S) .
operation(A,B,C,S) :-
maplist(#<(1), [A,B]),
C #= A * B,
atomic_list_concat([A,' × ',B,' = ',C], S) .
operation(A,B,C,S) :-
maplist(#<(1), [A,B]),
A mod B #= 0,
C #= A div B,
atomic_list_concat([A,' ∕ ',B,' = ',C], S) .
% Sort the list of solutions by length
list_length_sorted(Lists, Lists_sorted) :-
map_list_to_pairs(length, Lists, Pairs0),
keysort(Pairs0, Pairs),
pairs_values(Pairs, Lists_sorted) .
% Remove duplicate solutions
dedup([],[]).
dedup([Solution|Solutions0],[Solution|Solutions2]) :-
exclude(equiv(Solution), Solutions0, Solutions1),
dedup(Solutions1,Solutions2) .
% Identify equivalent lists (those differing only in the order of elements)
equiv(L1,L2) :-
length(L1, Length),
length(L2, Length),
msort(L1, Sorted),
msort(L2, Sorted) .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment