% FILE: crypto.pro
% TYPE: Prolog source
% LINE: The heuristic crypto problem solver
% DATE: Fall 2019

% -------------------------------------------------------------------------------
% LOAD FILES

:- consult('crypto_rpg.pro').
:- consult('combinatorial_sets.pro').
:- consult('lp.pro').

% -------------------------------------------------------------------------------
% SOME SIMPLE "LOWER LEVEL" RECOGNITION UTILITIES

same(A,A).

adjacent(A,B) :- A is B+1.
adjacent(A,B) :- A is B-1.

% -------------------------------------------------------------------------------
% SOME SIMPLE "LOWER LEVEL" ACTION UTILITIES

multiply([A,B],ex(A,*,B)).
multiply([H|T],ex(H,*,R)) :- multiply(T,R).

subtract([A,B],ex(A,-,B)).
subtract([H|T],ex(H,-,R)) :- subtract(T,R).

%% add and divide

   add([A,B],ex(A,+,B)).
   add([H|T],ex(H,+,R)) :- add(T,R).

   divide([A,B],ex(A,/,B)).
   divide([H|T],ex(H,/,R)) :- divide(T,R).
 
% -------------------------------------------------------------------------------
% SOME "HIGHER LEVEL" CRYPTO PROBLEM SOLVING PREDICATES

goal_is_one :- value_of(problem,problem(_,goal(1))).

goal_is_two :- value_of(problem,problem(_,goal(2))).

one_in_numbers :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),_)),
  contains([N1,N2,N3,N4,N5],1).

goal_is_pair :-
   pair_in_numbers(value(V),_),
   value_of(problem,problem(_,goal(V))).

% --------------------------------------------------------

goal_is_zero :- value_of(problem,problem(_,goal(0))).

goal_is_nonzero :- value_of(problem,problem(_,goal(G))), G \= 0.

zero_in_numbers :- 
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),_)),
  contains([N1,N2,N3,N4,N5],0).

goal_in_numbers :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),goal(G))),
  contains([N1,N2,N3,N4,N5],G).

goal_plus_one_in_numbers :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),goal(G))),
  G1 is G + 1,
  contains([N1,N2,N3,N4,N5],G1).

goal_plus_one_in_numbers(value(V)) :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),goal(G))),
  G1 is G+1,
  contains([N1,N2,N3,N4,N5],G1),
  V is G1.

goal_minus_one_in_numbers :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),goal(G))),
  G1 is G - 1,
  contains([N1,N2,N3,N4,N5],G1).

goal_minus_one_in_numbers(value(V)) :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),goal(G))),
  G1 is G-1,
  contains([N1,N2,N3,N4,N5],G1),
  V is G1.

pair_in_numbers :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),_)),
  comb(set(N1,N2,N3,N4,N5),comb(A,B),_),
  same(A,B).

pair_in_numbers(value(V),rest(C,D,E)) :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),_)),
  comb(set(N1,N2,N3,N4,N5),comb(A,B),extras(C,D,E)),
  same(A,B),
  V=A.

two_pair_in_numbers :-
  pair_in_numbers(_,rest(C,D,E)),
  comb(set(C,D,E),comb(C,D),extras(E)),
  same(C,D).

two_pair_in_numbers(value(V,W),rest(E)) :-
  pair_in_numbers(value(V),rest(N6,N7,N8)),
  comb(set(N6,N7,N8),comb(C,D),extras(E)),
  same(C,D),
  W=C.

numbers(N1,N2,N3,N4,N5) :-
  value_of(problem,problem(numbers(N1,N2,N3,N4,N5),_)).

goal(G) :-
  value_of(problem,problem(_,goal(G))).

numbers_other_than(these(A1,A2),those(B1,B2,B3)) :-
  numbers(N1,N2,N3,N4,N5),
  remove_elements([A1,A2],[N1,N2,N3,N4,N5],[B1,B2,B3]).

numbers_other_than(these(A1),those(B1,B2,B3,B4)) :-
  numbers(N1,N2,N3,N4,N5),
  remove_elements([A1],[N1,N2,N3,N4,N5],[B1,B2,B3,B4]).

numbers_other_than(these(A1,A2,A3),those(B1,B2)) :-
  numbers(N1,N2,N3,N4,N5),
  remove_elements([A1,A2,A3],[N1,N2,N3,N4,N5],[B1,B2]).

numbers_other_than(these(A1,A2,A3,A4),those(B1)) :-
  numbers(N1,N2,N3,N4,N5),
  remove_elements([A1,A2,A3,A4],[N1,N2,N3,N4,N5],[B1]).

% -------------------------------------------------------------------------------
% MORE LIST PROCESSORS

remove_elements([],List,List).

remove_elements([H|T],List,Reduced_List) :-
  member(H,List),
  remove(H,List,Partial_List),
  remove_elements(T,Partial_List,Reduced_List).

remove_elements([_|T],List,Reduced_List) :-
  remove_elements(T,List,Reduced_List).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 1

situation1 :-
  goal_is_zero,
  zero_in_numbers.

action1 :-
  numbers(N1,N2,N3,N4,N5),
  multiply([N1,N2,N3,N4,N5],Expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 2

situation2 :-
  goal_is_zero,
  pair_in_numbers.

action2 :-
  pair_in_numbers(value(V),rest(C,D,E)),
  subtract([V,V],X1),
  multiply([0,C,D,E],X2),
  substitute(X1,0,X2,Expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 3

situation3 :-
  goal_is_nonzero,
  goal_in_numbers,
  zero_in_numbers.

action3 :-
  goal(G),
  numbers_other_than(these(0,G),those(A,B,C)),
  multiply([0,A,B,C],Zero_valued_expression),
  Expression = ex(G,+,Zero_valued_expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 4

situation4 :-
  goal_is_nonzero,
  goal_in_numbers,
  pair_in_numbers.

action4 :-
  goal(G),
  pair_in_numbers(value(A),_),
  numbers_other_than(these(A,A),those(B,C,D)),
  subtract([A,A],A0),
  comb(set(B,C,D),comb(Num1,Num2),extras(G)),
  multiply([A0,Num1,Num2],Zero_valued_expression),
  add([G,Zero_valued_expression],Expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 5

situation5 :-
  goal_is_nonzero,
  zero_in_numbers,
  one_in_numbers,
  goal_plus_one_in_numbers.

action5 :-
  goal_plus_one_in_numbers(value(N)),
  numbers_other_than(these(N),those(A,B,C,D)),
  comb(set(A,B,C,D),comb(Num1,Num2),extras(0,1)),
  subtract([N,1],Sub),
  multiply([0,Num1,Num2],Zero_valued_expression),
  add([Sub,Zero_valued_expression],Expression),
  add_solution_to_KB(Expression).
  
% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 6

situation6 :-
  goal_is_nonzero,
  zero_in_numbers,
  one_in_numbers,
  goal_minus_one_in_numbers.

action6 :-
  goal_minus_one_in_numbers(value(N)),
  numbers_other_than(these(N),those(A,B,C,D)),
  comb(set(A,B,C,D),comb(Num1,Num2),extras(0,1)),
  add([N,1],Sub),
  multiply([0,Num1,Num2],Zero_valued_expression),
  add([Sub,Zero_valued_expression],Expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 7

situation7 :-
  two_pair_in_numbers,
  goal_is_one,
  zero_in_numbers.

action7 :-
  two_pair_in_numbers(value(V,W),_),
  divide([V,V],D1),
  divide([W,W],D2),
  divide([D1,D2],D3),
  add([D3,0],Expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% CRYPTO HEURISTIC 8

situation8 :-
  two_pair_in_numbers,
  goal_is_two,
  zero_in_numbers.

action8 :-
  two_pair_in_numbers(value(V,W),_),
  divide([V,V],D1),
  divide([W,W],D2),
  add([D1,D2],D3),
  add([D3,0],Expression),
  add_solution_to_KB(Expression).

% -------------------------------------------------------------------------------
% THE RULE BASE

rule(1,situation1,action1).
rule(2,situation2,action2).
rule(3,situation3,action3).
rule(4,situation4,action4).
rule(5,situation5,action5).
rule(6,situation6,action6).
rule(7,situation7,action7).
rule(8,situation8,action8).

% -------------------------------------------------------------------------------
% SOLVE AN INTERNALIZED PROBLEM (IT MAY HAVE BEEN RANDOMLY GENERATED OR 
% SPECIFICALLY ESTABLISHED) HEURISTICALLY, 
% PLACING ITS SOLUTION IN THE KB

solve_problem_heuristically :-
  rule(Number,Situation,Action),
  write('considering rule '), write(Number),write(' ...'), nl,
  Situation,
  write('application of rule '), write(Number),
  Action.

solve_problem_heuristically :-
  add_solution_to_KB(none).

add_solution_to_KB(Expression) :-
  undeclare(solution),
  declare(solution,solution(Expression)).

add_solution_to_KB(Expression) :-
  declare(solution,solution(Expression)).

% -------------------------------------------------------------------------------
% DISPLAY THE SOLUTION -- ASSUMING THAT THE PROBLEM HAS BEEN SOLVED

display_solution :-
  value_of(solution,solution(none)),
  write('NO SOLUTION found with this rule base.'), nl.

display_solution :-
  value_of(solution,solution(Expression)),
  write(' produces solution : '),
  display_result(Expression), nl.

display_solution.

display_result(ex(A,O,B)) :-
  number(A), number(B),
  write('( '), 
  write(A), 
  write(' '),
  write(O), 
  write(' '), 
  write(B), 
  write(' )').

display_result(ex(A,O,B)) :-
  number(A), B=ex(A1,O1,B1),
  write('( '),
  write(A),
  write(' '),
  write(O),
  write(' '),
  display_result(ex(A1,O1,B1)),
  write(' )').

display_result(ex(A,O,B)) :-
  number(B), A=ex(A1,O1,B1),
  write('( '),
  display_result(ex(A1,O1,B1)),
  write(' '),
  write(O),
  write(' '),
  write(B),
  write(' )').

display_result(ex(A,O,B)) :-
  A=ex(A1,O1,B1), B=ex(A2,O2,B2),
  write('( '),
  display_result(ex(A1,O1,B1)),
  write(' '),
  write(O),
  write(' '),
  display_result(ex(A2,O2,B2)),
  write(' )').

% -------------------------------------------------------------------------------
% SUBSTITUTION CODE

substitute(New,Old,ex(Old,O,Z),ex(New,O,Z)).
substitute(New,Old,ex(X,O,Old),ex(X,O,New)).
substitute(New,Old,ex(X,O,Z),ex(Y,O,Z)) :-
  substitute(New,Old,X,Y).
substitute(New,Old,ex(X,O,Y),ex(X,O,Z)) :-
  substitute(New,Old,Y,Z).

% -------------------------------------------------------------------------------
% CRYPTO PROBLEM SOLVER -- SOLVE A RANDOM PROBLEM

solve_one :-
  generate_random_crypto_problem,
  display_problem,
  solve_problem_heuristically,
  display_solution.

% -------------------------------------------------------------------------------
% CRYPTO PROBLEM SOLVER -- SOLVE A SPECIFIC PROBLEM

solve(problem(numbers(N1,N2,N3,N4,N5), goal(G))) :-
  add_crypto_problem_to_KB(N1,N2,N3,N4,N5,G), 
  display_problem,
  solve_problem_heuristically,
  display_solution.

% -------------------------------------------------------------------------------
% RANDOM CRYPTO PROBLEM SOLVING

demo(1) :-
  solve_one.

demo(N) :-
  demo(1),
  M is N-1,
  demo(M).
