/* Play framework */
play(Game) :-
initialize(Game,Position,Player),
display_game(Position,Player),
play(Position,Player,Result).
play(Position,Player,Result) :-
game_over(Position,Player,Result), !, announce(Result).
play(Position,Player,Result) :-
choose_move(Position,Player,Move),
move(Move,Position,Position1),
display_game(Position1,Player),
next_player(Player,Player1),
!,
play(Position1,Player1,Result).
/* Choosing a move by minimax with alpha-beta cut-off */
choose_move(Position,computer,Move) :-
lookahead(Depth),
alpha_beta(Depth,Position,-40,40,Move,Value),
nl, write(Move), nl.
choose_move(Position,opponent,Move) :-
nl, writeln(['please make move']), read(Move), legal(Move).
alpha_beta(0,Position,Alpha,Beta,Move,Value) :-
value(Position,Value).
alpha_beta(D,Position,Alpha,Beta,Move,Value) :-
findall(M,move(Position,M),Moves),
Alpha1 is -Beta,
Beta1 is -Alpha,
D1 is D-1,
evaluate_and_choose(Moves,Position,D1,Alpha1,Beta1,nil,(Move,Value)).
cutoff(Move,Value,D,Alpha,Beta,Moves,Position,Move1,(Move,Value)) :-
Value >= Beta.
cutoff(Move,Value,D,Alpha,Beta,Moves,Position,Move1,BestMove) :-
Alpha < Value, Value < Beta,
evaluate_and_choose(Moves,Position,D,Value,Beta,Move,BestMove).
cutoff(Move,Value,D,Alpha,Beta,Moves,Position,Move1,BestMove) :-
Value =< Alpha,
evaluate_and_choose(Moves,Position,D,Alpha,Beta,Move1,BestMove).
move(Board,[M|Ms]) :-
member(M,[1,2,3,4,5,6]),
stones_in_hole(M,Board,N),
extend_move(N,M,Board,Ms).
move(board([0,0,0,0,0,0],K,Ys,L),[]).
stones_in_hole(M,board(Hs,K,Ys,L),Stones) :-
nth_member(M,Hs,Stones), Stones > 0.
extend_move(Stones,M,Board,[]) :-
Stones =\= (7-M) mod 13, !.
extend_move(Stones,M,Board,Ms) :-
Stones =:= (7-M) mod 13, !,
distribute_stones(Stones,M,Board,Board1),
move(Board1,Ms).
/* Executing a move */
move([N|Ns],Board,FinalBoard) :-
stones_in_hole(N,Board,Stones),
distribute_stones(Stones,N,Board,Board1),
move(Ns,Board1,FinalBoard).
move([],Board1,Board2) :-
swap(Board1,Board2).
/* distribute_stones(Stones,Hole,Board,Board1) :-
Board1 is the result of distributing the number of stones,
Stones, from Hole from the current Board.
It consists of two stages: distributing the stones in the player's
holes, distribute_my_holes, and distributing the stones in
the opponent's holes, distribute_your_holes.
*/
distribute_stones(Stones,Hole,Board,FinalBoard) :-
distribute_my_holes(Stones,Hole,Board,Board1,Stones1),
distribute_your_holes(Stones1,Board1,FinalBoard).
distribute_my_holes(Stones,N,board(Hs,K,Ys,L),board(Hs1,K1,Ys,L),Stones1) :-
Stones > 7-N, !,
pick_up_and_distribute(N,Stones,Hs,Hs1),
K1 is K+1, Stones1 is Stones+N-7.
distribute_my_holes(Stones,N,board(Hs,K,Ys,L),Board,0) :-
pick_up_and_distribute(N,Stones,Hs,Hs1),
check_capture(N,Stones,Hs1,Hs2,Ys,Ys1,Pieces),
update_kalah(Pieces,N,Stones,K,K1),
check_if_finished(board(Hs2,K1,Ys1,L),Board).
check_capture(N,Stones,Hs,Hs1,Ys,Ys1,Pieces) :-
FinishingHole is N+Stones,
nth_member(FinishingHole,Hs,1),
OppositeHole is 7-FinishingHole,
nth_member(OppositeHole,Ys,Y),
Y > 0, !,
n_substitute(OppositeHole,Ys,0,Ys1),
n_substitute(FinishingHole,Hs,0,Hs1),
Pieces is Y+1.
check_capture(N,Stones,Hs,Hs,Ys,Ys,0) :- !.
check_if_finished(board(Hs,K,Ys,L),board(Hs,K,Hs,L1)) :-
zero(Hs), !, sumlist(Ys,YsSum), L1 is L+YsSum.
check_if_finished(board(Hs,K,Ys,L),board(Ys,K1,Ys,L)) :-
zero(Ys), !, sumlist(Hs,HsSum), K1 is K+HsSum.
check_if_finished(Board,Board) :- !.
update_kalah(0,Stones,N,K,K) :- Stones < 7-N, !.
update_kalah(0,Stones,N,K,K1) :- Stones =:= 7-N, !, K1 is K+1.
update_kalah(Pieces,Stones,N,K,K1) :- Pieces > 0, !, K1 is K+Pieces.
distribute_your_holes(0,Board,Board) :- !.
distribute_your_holes(Stones,board(Hs,K,Ys,L),board(Hs,K,Ys1,L)) :-
1 =< Stones, Stones =< 6,
non_zero(Hs), !,
distribute(Stones,Ys,Ys1).
distribute_your_holes(Stones,board(Hs,K,Ys,L),board(Hs,K,Ys1,L)) :-
Stones > 6, !,
distribute(6,Ys,Ys1),
Stones1 is Stones-6,
distribute_stones(Stones1,0,board(Hs,K,Ys1,L),Board).
distribute_your_holes(Stones,board(Hs,K,Ys,L),board(Hs,K,Hs,L1)) :-
zero(Hs), !, sumlist(Ys,YsSum), L1 is Stones+YsSum+L.
/* Lower level stone distribution */
pick_up_and_distribute(0,N,Hs,Hs1) :-
!, distribute(N,Hs,Hs1).
pick_up_and_distribute(1,N,[H|Hs],[0|Hs1]) :-
!, distribute(N,Hs,Hs1).
pick_up_and_distribute(K,N,[H|Hs],[H|Hs1]) :-
K > 1, !, K1 is K-1, pick_up_and_distribute(K1,N,Hs,Hs1).
distribute(0,Hs,Hs) :- !.
distribute(N,[H|Hs],[H1|Hs1]) :-
N > 0, !, N1 is N-1, H1 is H+1, distribute(N1,Hs,Hs1).
distribute(N,[],[]) :- !.
/* Evaluation function */
value(board(H,K,Y,L),Value) :- Value is K-L.
/* Testing for the end of the game */
game_over(board(0,N,0,N),Player,draw) :-
pieces(K), N =:= 6*K, !.
game_over(board(H,K,Y,L),Player,Player) :-
pieces(N), K > 6*N, !.
game_over(board(H,K,Y,L),Player,Opponent) :-
pieces(N), L > 6*N, next_player(Player,Opponent).
announce(opponent) :- writeln(['You won! Congratulations.']).
announce(computer) :- writeln(['I won.']).
announce(draw) :- writeln(['The game is a draw.']).
/* Miscellaneous game utilities */
nth_member(N,[H|Hs],K) :-
N > 1, !, N1 is N - 1, nth_member(N1,Hs,K).
nth_member(1,[H|Hs],H).
n_substitute(1,[X|Xs],Y,[Y|Xs]) :- !.
n_substitute(N,[X|Xs],Y,[X|Xs1]) :-
N > 1, !, N1 is N-1, n_substitute(N1,Xs,Y,Xs1).
next_player(computer,opponent).
next_player(opponent,computer).
legal([N|Ns]) :- 0 < N, N < 7, legal(Ns).
legal([]).
swap(board(Hs,K,Ys,L),board(Ys,L,Hs,K)).
display_game(Position,computer) :-
show(Position).
display_game(Position,opponent) :-
swap(Position,Position1), show(Position1).
show(board(H,K,Y,L)) :-
reverse(H,HR), write_stones(HR), write_kalahs(K,L), write_stones(Y).
write_stones(H) :-
nl, tab(5), display_holes(H).
display_holes([H|Hs]) :-
write_pile(H), display_holes(Hs).
display_holes([]) :- nl.
write_pile(N) :- N < 10, write(N), tab(4).
write_pile(N) :- N >= 10, write(N), tab(3).
write_kalahs(K,L) :-
write(K), tab(34), write(L), nl.
zero([0,0,0,0,0,0]).
non_zero(Hs) :- Hs \== [0,0,0,0,0,0].
/* Initializing */
lookahead(2).
initialize(kalah,board([N,N,N,N,N,N],0,[N,N,N,N,N,N],0),opponent) :-
pieces(N).
pieces(6).
% Program 21.3 A complete program for playing Kalah