%
% ex84.pl - 「6. 簡単なパズル(?)を解かせる」の4.
%

% gennum1/1 - 一桁の数値生成
gennum1(0).
gennum1(1).
gennum1(2).
gennum1(3).
gennum1(4).
gennum1(5).
gennum1(6).
gennum1(7).
gennum1(8).
gennum1(9).

% opx/2 - 演算子と優先順位
opx(+, 0).
opx(-, 0).
opx(*, 1).
opx(/, 1).

% 4項からなる式を乗除優先に従って計算する。
% (優先のパターンは8通り中4通りに分ければ良い)
opr(A, Op1, B, Op2, C, Op3, D, Z) :-
    opx(Op1, _), opx(Op2, P2), opx(Op3, P3),
    P2 < P3,		% 真中より右端の方が優先
    ops(C, D, X, Op3),
    ops(A, B, Y, Op1),
    ops(Y, X, Z, Op2).
opr(A, Op1, B, Op2, C, Op3, D, Z) :-
    opx(Op1, P1), opx(Op2, P2), opx(Op3, P3),
    P1 < P2, P2 = P3,	% 真中と右端が優先
    ops(B, C, X, Op2),
    ops(X, D, Y, Op3),
    ops(A, Y, Z, Op1).
opr(A, Op1, B, Op2, C, Op3, D, Z) :-
    opx(Op1, P1), opx(Op2, P2), opx(Op3, P3),
    P1 < P2, P2 > P3,	% 真中のみが優先
    ops(B, C, X, Op2),
    ops(A, X, Y, Op1),
    ops(Y, D, Z, Op3).
opr(A, Op1, B, Op2, C, Op3, D, Z) :-
    opx(Op1, P1), opx(Op2, P2), opx(Op3, P3),
    P1 >= P2, P2 >= P3,	% それ以外(左から順に計算可能)
    ops(A, B, X, Op1),
    ops(X, C, Y, Op2),
    ops(Y, D, Z, Op3).

% 式「A Op1 B Op2 C Op3 D」の結果が10であるなら真
opr10(A, Op1, B, Op2, C, Op3, D) :-
    gennum1(A),
    gennum1(B), B \= A,
    gennum1(C), C \= A, C \= B,
    gennum1(D), D \= A, D \= B, D \= C,
    opr(A, Op1, B, Op2, C, Op3, D, 10).

% ops(X, Y, Z, Op) は、式「Z is X Op Y」を意味する。
% ここで、Opは演算子である。
ops(X, Y, Z, +) :- Z is X + Y.
ops(X, Y, Z, -) :- Z is X - Y.
ops(X, Y, Z, *) :- Z is X * Y.
ops(X, Y, Z, /) :- Y \= 0, Z is X rdiv Y.
% 注: rdivは / と同様に除算であるが、結果を小数ではなく分数とする。
%     これを用いると 1 / 3 * 3 等も正確に計算される。
%     除算の際は、0で割るとエラーになるので、そのチェックも入れておく。

% 一々手でバックトラックを指示するのは面倒なので、自動的にバックトラック
% しながら、画面に表示させる述語も定義しておく。
all_opr10 :-
    opr10(A, Op1, B, Op2, C, Op3, D),
    writeln([A, Op1, B, Op2, C, Op3, D]),
    fail.
all_opr10.
