Advertisement
Guest User

Prolog GCHQ 2015 Christmas puzzle solution

a guest
Jan 8th, 2017
228
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Prolog 10.09 KB | None | 0 0
  1. % GCHQ 2015 Christmas puzzle solver inspired by techniques described at
  2. % https://chriskohlhepp.wordpress.com/reasoning-systems/gchq-2015-crypto-puzzle-full-analysis/
  3. % discussed at https://news.ycombinator.com/item?id=13349068
  4. % original puzzle at https://www.gchq.gov.uk/news-article/christmas-card-cryptographic-twist-charity
  5. %
  6. % to run in SWI-Prolog: swipl -s gchq.pl -t 'preprocess(_)'
  7. % output: a series of ever more refined QR codes ending with
  8. %
  9. % ███████ ███   █ █ ███████
  10. % █     █ ██ ██     █     █
  11. % █ ███ █     ███ █ █ ███ █
  12. % █ ███ █ █  ██████ █ ███ █
  13. % █ ███ █  █████ ██ █ ███ █
  14. % █     █  ██       █     █
  15. % ███████ █ █ █ █ █ ███████
  16. %         ███   ███        
  17. % █ ██ ███  █ █ ███ █  █ ██
  18. % █ █      ███ ██    █   █
  19. %  ████ █ ████ ██ █    ██  
  20. %  █ █   █   █ █ ████ █ ███
  21. %   ██  █ █ █      ██ █████
  22. %    ███ ██ ██ ██████ ███ █
  23. % █ █████████ █ █  ██    █
  24. %  ██ █  ██   ██ ███     █
  25. % ███ █ █ █  █    █████ █  
  26. %         █   ██ ██   █████
  27. % ███████ █  ██   █ █ █ ███
  28. % █     █ ██  █  ██   ██ █
  29. % █ ███ █   ████  █████  █
  30. % █ ███ █ ███ ██████████ ██
  31. % █ ███ █ █  ██████ ██████
  32. % █     █  ██      █ █ ██  
  33. % ███████ ██   █ ██   █████
  34.  
  35.  
  36. % for transpose/2; on other Prologs than SWI-Prolog, implement
  37. :- use_module(library(clpfd)).
  38.  
  39. row_constraints([[7,3,1,1,7],
  40.                  [1,1,2,2,1,1],
  41.                  [1,3,1,3,1,1,3,1],
  42.                  [1,3,1,1,6,1,3,1],
  43.                  [1,3,1,5,2,1,3,1],
  44.                  [1,1,2,1,1],
  45.                  [7,1,1,1,1,1,7],
  46.                  [3,3],
  47.                  [1,2,3,1,1,3,1,1,2],
  48.                  [1,1,3,2,1,1],
  49.                  [4,1,4,2,1,2],
  50.                  [1,1,1,1,1,4,1,3],
  51.                  [2,1,1,1,2,5],
  52.                  [3,2,2,6,3,1],
  53.                  [1,9,1,1,2,1],
  54.                  [2,1,2,2,3,1],
  55.                  [3,1,1,1,1,5,1],
  56.                  [1,2,2,5],
  57.                  [7,1,2,1,1,1,3],
  58.                  [1,1,2,1,2,2,1],
  59.                  [1,3,1,4,5,1],
  60.                  [1,3,1,3,10,2],
  61.                  [1,3,1,1,6,6],
  62.                  [1,1,2,1,1,2],
  63.                  [7,2,1,2,5]]).
  64.  
  65. col_constraints([[7,2,1,1,7],
  66.                  [1,1,2,2,1,1],
  67.                  [1,3,1,3,1,3,1,3,1],
  68.                  [1,3,1,1,5,1,3,1],
  69.                  [1,3,1,1,4,1,3,1],
  70.                  [1,1,1,2,1,1],
  71.                  [7,1,1,1,1,1,7],
  72.                  [1,1,3],
  73.                  [2,1,2,1,8,2,1],
  74.                  [2,2,1,2,1,1,1,2],
  75.                  [1,7,3,2,1],
  76.                  [1,2,3,1,1,1,1,1],
  77.                  [4,1,1,2,6],
  78.                  [3,3,1,1,1,3,1],
  79.                  [1,2,5,2,2],
  80.                  [2,2,1,1,1,1,1,2,1],
  81.                  [1,3,3,2,1,8,1],
  82.                  [6,2,1],
  83.                  [7,1,4,1,1,3],
  84.                  [1,1,1,1,4],
  85.                  [1,3,1,3,7,1],
  86.                  [1,3,1,1,1,2,1,1,4],
  87.                  [1,3,1,4,3,3],
  88.                  [1,1,2,2,2,6,1],
  89.                  [7,1,3,2,1,1]]).
  90.  
  91. input([[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  92.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  93.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  94.        [_,_,_,,,_,_,_,_,_,_,_,,,_,_,_,_,_,_,_,,_,_,_],
  95.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  96.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  97.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  98.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  99.        [_,_,_,_,_,_,,,_,_,,_,_,_,,,_,_,,_,_,_,_,_,_],
  100.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  101.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  102.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  103.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  104.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  105.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  106.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  107.        [_,_,_,_,_,_,,_,_,_,_,,_,_,_,_,,_,_,_,,_,_,_,_],
  108.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  109.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  110.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  111.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  112.        [_,_,_,,,_,_,_,_,,,_,_,_,_,,_,_,_,_,,,_,_,_],
  113.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  114.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
  115.        [_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]]).
  116.  
  117. print_entry(Mark) :-
  118.     (   atom(Mark)
  119.     ->  write(Mark)
  120.     ;   write('?') ).
  121.  
  122. print_line(Line) :-
  123.     maplist(print_entry, Line),
  124.     nl.
  125.  
  126. print_matrix(Matrix) :-
  127.     maplist(print_line, Matrix).
  128.  
  129. % Generate a line of length TotalLength conforming to the given Constraints.
  130. % Constraints is a list of numbers describing the lengths of blocks of marks
  131. % on the line. For example, [3,2,1] means there is a block of three marks,
  132. % one of two marks, and one of one mark. The rest of the line consists of
  133. % blanks. Blocks of marks are separated by at least one blank, but there
  134. % might be zero blanks before the first or after the last block.
  135. % The total number of blanks on the line is tracked to limit unnecessary
  136. % backtracking.
  137. line(Constraints, TotalLength) -->
  138.     { sum_list(Constraints, N) },
  139.     { MaxInitialBlanks is TotalLength - N },
  140.     { between(0, MaxInitialBlanks, InitialBlanks) },
  141.     blanks(InitialBlanks),
  142.     { BlankBudget is TotalLength - N - InitialBlanks },
  143.     constraints(Constraints, BlankBudget, 0).
  144.  
  145. % Apply the constraints one by one. Each block of marks is followed by a
  146. % block of at least one blank, except that the last block of marks may be
  147. % followed by zero blanks.
  148. constraints([C], Blanks, 0) -->
  149.     marks(C),
  150.     blanks(Blanks).
  151. constraints([C,C1|Cs], BlankBudget, RemainingBlanks) -->
  152.     marks(C),
  153.     { between(1, BlankBudget, N) },
  154.     blanks(N),
  155.     { BlankBudget1 is BlankBudget - N },
  156.     constraints([C1|Cs], BlankBudget1, RemainingBlanks).
  157.  
  158. blanks(0) -->
  159.     [].
  160. blanks(N) -->
  161.     { N > 0 },
  162.     [' '],
  163.     { N1 is N - 1 },
  164.     blanks(N1).
  165.  
  166. marks(0) -->
  167.     [].
  168. marks(N) -->
  169.     { N > 0 },
  170.     ['█'],
  171.     { N1 is N - 1 },
  172.     marks(N1).
  173.  
  174. % Apply the above DCG line//2.
  175. constrain_line(Constraint, Line) :-
  176.     phrase(line(Constraint, 25), Line).
  177.  
  178. constrain_line(Constraint, Line, Line) :-
  179.     phrase(line(Constraint, 25), Line).
  180.  
  181. % Find all solutions of line//2 for the given Constraints and an initial
  182. % line Givens which may contain previously placed marks.
  183. constraint_lines(Constraint, Givens, Lines) :-
  184.     setof(Line, Givens^constrain_line(Constraint, Givens, Line), Lines).
  185.  
  186. % Implement the interesting insight from the blog post linked above: All
  187. % solutions to a given line constraint, and possibly starting from
  188. % previously placed marks, may have something in common. For example, the
  189. % N-th mark might be a mark in every solution, or the M-th mark might always
  190. % be a blank. The indices of such always fixed "knowns" are collected in the
  191. % Knowns structure, and immediately used to bind the corresponding elements
  192. % of Givens.
  193. knowns(Constraint, Givens, Knowns) :-
  194.     constraint_lines(Constraint, Givens, Lines),
  195.     transpose(Lines, Columns),
  196.     known_marks_blanks(Columns, Knowns),
  197.     bind_known_marks(Givens, Knowns).
  198.  
  199. known_marks_blanks(Columns, known_marks_blanks(Marks, Blanks)) :-
  200.     known_marks_blanks(Columns, 1, [], [], MarksR, BlanksR),
  201.     reverse(MarksR, Marks),
  202.     reverse(BlanksR, Blanks).
  203.  
  204. known_marks_blanks([], _, Marks, Blanks, Marks, Blanks).
  205. known_marks_blanks([Col|Cols], Idx, Marks0, Blanks0, Marks, Blanks) :-
  206.     (   maplist(==('█'), Col)
  207.     ->  Marks1 = [Idx|Marks0],
  208.         Blanks1 = Blanks0
  209.     ;   maplist(==(' '), Col)
  210.     ->  Blanks1 = [Idx|Blanks0],
  211.         Marks1 = Marks0
  212.     ;   Marks1 = Marks0,
  213.         Blanks1 = Blanks0 ),
  214.     Idx1 is Idx + 1,
  215.     known_marks_blanks(Cols, Idx1, Marks1, Blanks1, Marks, Blanks).
  216.  
  217. bind_known_marks(Line, known_marks_blanks(Marks, Blanks)) :-
  218.     bind_known(Line, '█', 1, Marks),
  219.     bind_known(Line, ' ', 1, Blanks).
  220.  
  221. bind_known([], _, _, _) :-
  222.     !.
  223. bind_known(_, _, _, []) :-
  224.     !.
  225. bind_known([E|Es], Mark, Idx, [M|Ms]) :-
  226.     Idx1 is Idx + 1,
  227.     (   M = Idx
  228.     ->  E = Mark,
  229.         bind_known(Es, Mark, Idx1, Ms)
  230.     ;   bind_known(Es, Mark, Idx1, [M|Ms]) ).
  231.  
  232. % The preprocessing "derive-known-plaintext" operation from the blog post.
  233. % As it turns out, this solves the whole puzzle without the need for a
  234. % further backtracking search for solutions.
  235. preprocess(Matrix) :-
  236.     input(Matrix),
  237.     % To experiment with all solutions to the given constraints, without
  238.     % pre-marked fields (there are four such solutions), comment the
  239.     % input(Matrix) line above and uncomment the following two lines that
  240.     % set up an empty 25x25 matrix.
  241.   % length(Matrix, 25),
  242.   % maplist(same_length(Matrix), Matrix),
  243.     transpose(Matrix, Transposed),
  244.     row_constraints(RowConstraints),
  245.     col_constraints(ColConstraints),
  246.     preprocess(Matrix, Transposed, RowConstraints, ColConstraints, [], []).
  247.  
  248. % Iterate knowns/4 over the rows and columns of the matrix until a fixed
  249. % point is reached.
  250. preprocess(Matrix, Transposed, RowCs, ColCs, RowKs, ColKs) :-
  251.     print_matrix(Matrix),
  252.     nl,
  253.     maplist(knowns, RowCs, Matrix, RowKs1),
  254.     maplist(knowns, ColCs, Transposed, ColKs1),
  255.     (   ( RowKs \= RowKs1 ; ColKs \= ColKs1 )
  256.     ->  preprocess(Matrix, Transposed, RowCs, ColCs, RowKs1, ColKs1)
  257.     ;   true ).
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement