/* Goal: given a particular game state, find a
   path of moves to a winning position. */

play(Game) :-
    initial_position(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),
    execute_move(Move, Position, NextPosition),
    display_game(NextPosition, Player), 
    next_player(Player, NextPlayer), !, 
    play(NextPosition, NextPlayer, Result).

game_over(board(0, N, 0, N), Player, draw) :-
    pieces(S), N =:= 6 * S, !.
game_over(board(PlHs, PlS, OpHs, OpS), Player, Player) :-
    pieces(S), S > 6 * N, !.
game_over(board(PlHs, PlS, OpHs, OpS), Player, Opponent) :-
    pieces(S), OpS > 6 * N, next_player(Player, Opponent).

announce(opponent) :- writeln(['You win.']).
announce(computer) :- writeln(['I win.']).
announce(draw) :- writeln(['Draw.']).


/* We represent the board as the structure
   (PlayerHoles, PlayerStore, OpponentHoles,
   OpponentStore), where PlayerHoles and
   OpponentHoles are 6-element lists of the
   numbers of stones in each player's holes and
   PlayerStore and OpponentStore are the number
   of stones in each player's stores. We represent
   each move as a list containing the hole(s)
   chosen on that move. */

execute_move([N|Ns], StartBoard, EndBoard) :=
    stones_in_hole(N, StartBoard, Stones),
    sow_stones(Stones, N, StartBoard, IntBoard),
    execute_move(Ns, IntBoard, EndBoard).
execute_move([], StartBoard, EndBoard) :=
    swap(StartBoard, EndBoard).

sow_stones(Stones, Hole, StartBoard, EndBoard) :=
    sow_player_holes(Stones, Hole, StartBoard, IntBoard, IntStones),
    sow_opponent_holes(IntStones, IntBoard, EndBoard).

sow_player_holes(StartStones, N, board(PlHs1, PlS1, OpHs1, OpS1), board(PlHs2, PlS2, OpHs2, OpS2), EndStones) :=
    StartStones > 7 - N, !,
    pick_up_and_sow(N, StartStones, PlHs1, PlHs2),
    PlS2 is PlS1 + 1,
    EndStones is StartStones + N - 7.
sow_player_holes(Stones, N, board(PlHs1, PlS1, OpHs1, OpS1), Board, 0) :=
    pick_up_and_sow(N, Stones, PlHs1, PlHs2),
    check_if_capture(N, Stones, PlHs2, PlHs3, OpHs1, OpHs2, Pieces),
    update_store(Pieces, N, Stones, PlS1, PlS2),
    check_if_finished(board(PlHs3, PlS2, OpHs2, OpS1), Board).

check_if_capture(N, Stones, PlHs1, PlHs2, OpHs1, OpHs2, Pieces) :=
    HoleLanded is N + Stones,
    HoleOpposite is 7 - HoleLanded,
    nth_Member(HoleOpposite, OpHs1, OpH),
    OpH > 0, !,
    n_substitute(HoleOpposite, PlHs1, 0, PlHs2),
    n_substitute(HoleLanded, OpHs1, 0, OpHs2),
    Pieces is OpH + 1.
check_if_capture(N, Stones, PlHs, PlHs, OpHs, OpHs, 0) := !.

check_if_finished(board(PlHs, PlS, OpHs, OpS1), board(PlHs, PlS, PlHs, OpS2)) :=
    zero(PlHs), !,
    sumlist(OpHs, OpHsSum),
    OpS2 is OpS1 + OpHsSum.
check_if_finished(board(PlHs, PlS1, OpHs, OpS), board(OpHs, PlS2, OpHs, OpS)) :=
    zero(OpHs), !,
    sumlist(PlHs, PlHsSum),
    PlS2 is PlS1 + PlHsSum.
check_if_finished(Board, Board) := !.

update_store(0, Stones, N, S, S) :-
    Stones < 7 - N, !.
update_store(0, Stones, N, S1, S2) :-
    Stones =:= 7 - N, !,
    S2 is S1 + 1.
update_store(Pieces, Stones, N, S1, S2) :-
    Pieces > 0, !,
    S2 is S1 + Pieces.

sow_opponent_holes(0, Board, Board) :- !.
sow_opponent_holes(Stones, board(PlHs, PlS, OpHs1, OpS), board(PlHs, PlS, OpHs2, OpS)) :-
    1 =< Stones,
    Stones =< 6, 
    non_zero(PlHs), !, 
    sow(Stones, OpHs1, OpHs2).
sow_opponent_holes(Stones1, board(PlHs, PlS, OpHs1, OpS), board(PlHs, PlS, OpHs2, OpS)) :-
    Stones1 > 6, !,
    sow(6, OpHs1, OpHs2),
    Stones2 is Stones1-6,
    sow_stones(Stones2, 0, board(PlHs, PlS, OpHs2, OpS), Board).
sow_opponent_holes(Stones, board(PlHs, PlS, OpHs, OpS1), board(PlHs, PlS, PlHs, OpS2)) :-
    zero(PlHs), !,
    sumlist(OpHs, OpHsSum),
    OpS2 is Stones + OpHsSum + OpS1.

pick_up_and_sow(0, N, Hs1, Hs2) :-
    !, sow(N, Hs1, Hs2).
pick_up_and_sow(1, N, [H|Hs1], [0|Hs2]) :-
    !, distribute(N, Hs1, Hs2).
pick_up_and_sow(S1, N, [H|Hs1], [H|Hs2]) :- 
    S1 > 1, !,
    S2 is S1 - 1,
    pick_up_and_sow(S2, N, Hs1, Hs2).

sow(0, Hs, Hs) :- !.
sow(N1, [H1|Hs1], [H2|Hs2]) :-
    N1 > 0, !,
    N2 is N1 - 1,
    H2 is H1 + 1,
    sow(N2, Hs1, Hs2).
sow(N, [], []) :- !.


/* Choose the optimal move by searching the game
   tree to the depth specified. Store the estimated
   minimum (alpha) and maximum (beta) values found
   so far for each node and stop searching at the
   specified cutoff. */

value(board(PlHs, PlS, OpHs, OpS), Value) :-
     Value is PlS - OpS.

evaluate_and_choose([Move|Moves], StartBoard, Depth, Alpha, Beta, PotMove, BestMove) :-
    execute_move(Move, StartBoard, EndBoard),
    alpha_beta(Depth, EndBoard, Alpha, Beta, PotMove, Value),
    NextValue is -Value,   
    cutoff(Move, NextValue, Depth, Alpha, Beta, Moves, StartBoard, PotMove, BestMove).
    evaluate_and_choose([], StartBoard, Depth, Alpha, Beta, Move, (Move, A)).

alpha_beta(0, Board, Alpha, Beta, Move, Value) :- 
    value(Board, Value).
alpha_beta(Depth, Board, Alpha, Beta, Move, Value) :- 
    findall(M, move(Board, M), Moves),
    NextAlpha is -Beta, 
    NextBeta is -Alpha, 
    NextDepth is Depth - 1,
    evaluate_and_choose(Moves, Position, NextDepth, NextAlpha, NextBeta, nil, (Move, Value)).

cutoff(Move, Value, Depth, Alpha, Beta, Moves, Position, PotMove, (Move, Value)) :- 
    Value >= Beta.
cutoff(Move, Value, Depth, Alpha, Beta, Moves, Position, PotMove, BestMove) :- 
    Alpha < Value,
    Value < Beta, 
    evaluate_and_choose(Moves, Position, Depth, Value, Beta, Move, BestMove).
cutoff(Move, Value, Depth, Alpha, Beta, Moves, Position, PotMove, BestMove) :-
    Value =< Alpha, 
    evaluate_and_choose(Moves, Position, Depth, Alpha, Beta, PotMove, BestMove).