```% 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, 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).

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  ').

```