% Assume that wasabi's are always applied to the best nigiri (if any).
%
% ?- average_score(3, 1000, X).  % with show_hands(off)
%
% ?- random_scores(3, S).        % with show_hands(on)

show_hands(on).

% card(Type, Quantity)
card(tempura, 14).
card(sashimi, 14).
card(dumpling, 14).
card(roll-1, 6).
card(roll-2, 12).
card(roll-3, 8).
card(nigiri-egg, 5).
card(nigiri-salmon, 10).
card(nigiri-squid, 5).
card(pudding, 10).
card(wasabi, 6).
card(chopsticks, 4).

% score(Type, Hand(s), Score(s))

score(tempura, Hand, Score) :-
    count(tempura, Hand, N),
    Score is N // 2 * 5.

score(sashimi, Hand, Score) :-
    count(sashimi, Hand, N),
    Score is N // 3 * 10.

score(dumpling, Hand, Score) :-
    count(dumpling, Hand, N),
    ( N >= 5 -> Score = 15
    ; nth0(N, [0,1,3,6,10], Score)
    ).

score(roll, Hands, Scores) :-
    maplist(count(roll-1), Hands, R1),
    maplist(count(roll-2), Hands, R2),
    maplist(count(roll-3), Hands, R3),
    maplist(multiply(2), R2, R2b),
    maplist(multiply(3), R3, R3b),
    sum_points([R1,R2b,R3b], R),
    max2(R, Scores).

score(nigiri, Hand, Score) :-
    count(nigiri-egg, Hand, E),
    count(nigiri-salmon, Hand, S),
    count(nigiri-squid, Hand, Q),
    count(wasabi, Hand, W),
    wasabi(Q, 3, W, 0, W1, Score1),
    wasabi(S, 2, W1, Score1, W2, Score2),
    wasabi(E, 1, W2, Score2, _, Score).

score(pudding, Hands, Scores) :-
    maplist(count(pudding), Hands, P),
    max_min(P, Scores).

score(Hands, Scores) :-
    maplist(score(tempura), Hands, T),
    maplist(score(sashimi), Hands, S),
    maplist(score(dumpling), Hands, D),
    score(roll, Hands, R),
    maplist(score(nigiri), Hands, N),
    sum_points([T,S,D,R,N], Scores).

% wasabi(N, X, W, S, W1, S1)
% - N is the # of nigiri cards
% - X is the value of the nigiri cards
% - W is the # of wasabi cards
% - S is the score until now
% - W1 is the # of remaining wasabi cards
% - S1 is the new score
wasabi(N, X, 0, S, 0, S1) :- S1 is S + N * X.
wasabi(N, X, W, S, W1, S1) :- W > N, W1 is W - N, S1 is S + N * X * 3.
wasabi(N, X, W, S, 0, S1) :- W =< N, S1 is S + W * X * 3 + (N - W) * X.

% count(X, Xs, N) : X appears N times in Xs
count(_, [], 0).
count(X, [X|Xs], N1) :- count(X, Xs, N), N1 is N + 1.
count(X, [Y|Ys], N) :- X \= Y, count(X, Ys, N).

% sushi roll rule (Xs: number of rolls in each hand, S: scores)
% - single maximum -> 6 points, second best (but not 0) -> 3 / K points
% - multiple maximum (but not 0) -> 6 / K points
max2(Xs, S) :-
    sort(0, @>, Xs, [X,Y|_]),
    count(X, Xs, Nx), count(Y, Xs, Ny),
    ( ( Nx > 1 ; Y =:= 0 ) -> K is 6 // Nx, subst(Xs, [X-K], S)
    ; K is 3 // Ny, subst(Xs, [X-6,Y-K], S)
    ).
max2(Xs, S) :-
    sort(Xs, [X]),
    length(Xs, N),
    ( X > 0 -> K is 6 // N ; K is 0 ),
    make_list(K, N, S).

% pudding rule (Xs: number of puddings in each hand, S: scores)
% - maximum -> 6 / K points
% - minimum (can be 0) -> -6 / K points
max_min(Xs, S) :-
    sort(Xs, [X|Ys]), reverse(Ys, [Y|_]),
    count(X, Xs, Nx), count(Y, Xs, Ny),
    Kx is -6 // Nx, Ky is 6 // Ny,
    subst(Xs, [X-Kx,Y-Ky], S).
max_min(Xs, S) :- sort(Xs, [_]), length(Xs, N), make_list(0, N, S).


% Main functions random_scores(N, Scores) :- all_cards(Cards), shuffle(Cards, Cards0), deal(Cards0, N, Hands1, Cards1), score(Hands1, R1), deal(Cards1, N, Hands2, Cards2), score(Hands2, R2), deal(Cards2, N, Hands3, _), score(Hands3, R3), append_hands([Hands1,Hands2,Hands3], Hands), score(pudding, Hands, P), ( show_hands(on) -> write('Round 1:'), nl, maplist(show_hand, Hands1, R1), write('Round 2:'), nl, maplist(show_hand, Hands2, R2), write('Round 3:'), nl, maplist(show_hand, Hands3, R3), write('Pudding scores: '), write(P), nl ; true ), sum_points([R1,R2,R3,P], Scores). average_score(N, K, S) :- sum_score(N, K, S0), S is S0 / (N * K). sum_score(_, 0, 0). sum_score(N, K, S) :- K > 0, K1 is K - 1, sum_score(N, K1, S0), random_scores(N, Scores), sum_list(Scores, S1), S is S0 + S1. all_cards(Cards) :- findall(L, (card(Type, N), make_list(Type, N, L)), Css), append(Css, Cards). deal(Cards, N, Hands, Remaining) :- K is 12 - N, KN is K * N, take(Cards, KN, Selected, Remaining), distribute(Selected, K, Hands). append_hands([X], X). append_hands([X|Xs], Z) :- append_hands(Xs, Y), maplist(append, X, Y, Z).
% Simple utilities add(X, Y, Z) :- Z is X + Y. multiply(X, Y, Z) :- Z is X * Y. % sum_points(Xss, Ys) : sums a list of lists sum_points([X], X). sum_points([X|Xs], Z) :- sum_points(Xs, Y), maplist(add, X, Y, Z). % subst(Xs, [X-Y|XYs], Ys) : Ys is the same as Xs but each X is replaced by Y subst([], _, []). subst([X|Xs], S, [N|Ys]) :- member(X-N, S), subst(Xs, S, Ys). subst([X|Xs], S, [0|Ys]) :- \+ member(X-_, S), subst(Xs, S, Ys). % make_list(X, N, Xs) : make a list of length N by repeating X make_list(_, 0, []). make_list(X, N, [X|L]) :- N > 0, N1 is N - 1, make_list(X, N1, L). % take(Xs, N, Head, Tail) : length(Head, N), append(Head, Tail, Xs) take(Xs, 0, [], Xs). take([X|Xs], N, [X|Ys], Zs) :- N > 0, N1 is N - 1, take(Xs, N1, Ys, Zs). % distribute(Xs, N, Yss) : create a list of lists, each having N elements distribute([], _, []). distribute(Xs, N, [Ys|Yss]) :- take(Xs, N, Ys, Zs), distribute(Zs, N, Yss). shuffle(Cards, Shuffled) :- random_permutation(Cards, Shuffled).
% Human-readable output show_hand(Xs, Score) :- sort(0, @=<, Xs, Sorted), maplist(show, Sorted), write(' -> '), write(Score), nl. show(tempura) :- write('T '). show(sashimi) :- write('S '). show(dumpling) :- write('D '). show(roll-N) :- write('R'), write(N), write(' '). show(nigiri-egg) :- write('NE '). show(nigiri-salmon) :- write('NS '). show(nigiri-squid) :- write('NQ '). show(pudding) :- write('P '). show(wasabi) :- write('W '). show(chopsticks) :- write('C ').