% PVR 20/07/92: Added entry declaration for move/5 (it is used in a bagof).

% ------------------------------------------------------------------------------
% Chess -- Mar. 1, 1987     Mike Carlton
%
% Standard rules of chess apply with the following exceptions:
%   en passant captures are not allowed,
%   pawns are promoted to queens only,
%   draw by repetition or no capture are not allowed,
%   kings may castle out of or through check (but not into).
%
% Files are numbered a to h, ranks are numbered 1 to 8,
% and white is assumed to play from the rank 1 side.
% The program always plays black.
%
% Positions are specified with the structure: File-Rank.
% The board is a list containing two state structures of the form:
%   state(white, WhiteKing, WhiteKingRook, WhiteQueenRook),
%   state(black, BlackKing, BlackKingRook, BlackQueenRook),
% followed by a list of the piece positions of the form:
%   piece(File-Rank, Color, Type),
% where the state variables will be bound to an atom once the
% corresponding piece has been moved.
% A move is stored internally as: move(From_File-From_Rank, To_File-To_Rank).
%
% Commands available:
%   Move:  entered in the form FRFR (where F is in a..h and R is in 1..8)
%   board: prints the current board
%   quit:  quits the game
%   move(Move): makes any move and does not respond
%
% The program has a very small book, it will play much better if you stick
% to the book opening!  The opening it plays is:
%   White   Black
%   -----   -----
%   e2e4    e7e5
%   g1f3    b8c6
%   f1b5    a7a6

:- use_module(library(dcgs)).

main :-
    init_board(InBoard),
    test_play(InBoard, _),
    fail.
main.

play :-
    init_board(InBoard),
    play(InBoard),
    fail.
play.

test_play -->
    execute_command(e2e4), !,
    execute_command(d2d3), !,
    execute_command(c1e3), !,
    execute_command(quit).

play(Board) :-
    get_command(Command),
    execute_command(Command, Board, NewBoard), !,
    play(NewBoard).

get_command(Command) :-
    nl, write('-> '),
    read(Command), !.
get_command(Command) :-
    get_command(Command).

execute_command(bad, Board, Board) :-       % To catch vars
        write('What?').
execute_command(set1, _, Board) :-          % DEBUG
    Board = ([state(white, king_moved, king_rook_moved, queen_rook_moved),
          state(black, king_moved, king_rook_moved, queen_rook_moved),
          piece(a-1, white, king), piece(b-1, white, queen),
          piece(c-1, black, king)]).
execute_command(set2, _, Board) :-          % DEBUG
    Board = ([state(white, king_moved, king_rook_moved, queen_rook_moved),
          state(black, king_moved, king_rook_moved, queen_rook_moved),
          piece(a-2, white, king), piece(b-1, white, queen),
          piece(h-8, black, king), piece(b-8, black, rook),
          piece(c-3, black, night)]).
execute_command(clear, _, Board) :-         % DEBUG
    Board = ([state(white, king_moved, king_rook_moved, queen_rook_moved),
          state(black, king_moved, king_rook_moved, queen_rook_moved)
         ]).
execute_command(piece(S,C,T), Board, [piece(F-R,C,T)|Board]) :-  % DEBUG
    parse_square(S, F-R),
    member(F, [a, b, c, d, e, f, g, h]),
    member(R, [1, 2, 3, 4, 5, 6, 7, 8]),
    member(C, [white, black]),
    member(T, [king, queen, rook, night, bishop, pawn]).
% execute_command(stats, Board, Board) :-           % DEBUG
%       statistics.
execute_command(ply(N), Board, Board) :-        % DEBUG
        abolish(ply_depth/1),
    assertz(ply_depth(N)).
execute_command(rate, Board, Board) :-          % DEBUG
    computer(ComputerColor),
    evaluate(Board, ComputerColor, Val),
    write(' Rating: '), write(Val), nl.
execute_command(rawboard, Board, Board) :-      % DEBUG
        write(Board), nl.
execute_command(move(Move), Board, NewBoard) :-
        parse_move(Move, From, To),
        make_move(Board, From, To, NewBoard).
execute_command(quit, _, _) :-
        write('Goodbye.'), nl, !, fail.
execute_command(board, Board, Board) :-
        print_board(Board).
execute_command(Move, Board, NewBoard) :-
        parse_move(Move, From, To),
        respond_to(Board, From, To, NewBoard).
execute_command(_, Board, Board) :-
        write('What?').


% ------------------------------------------------------------------------------
% Parameters

ply_depth(3).         % Depth of alpha-beta search

player(white).
computer(black).

value(king, 10000).
value(queen,  900).
value(rook,   500).
value(night,  300).
value(bishop, 300).
value(pawn,   100).


% ------------------------------------------------------------------------------
% Chess procedures

respond_to(Board, From, To, OutBoard) :-
    player(PlayerColor),
    legal_move(Board, PlayerColor, From, To),
    write('Working...'), nl,
        make_move(Board, From, To, NewBoard),
% statistics,
    select_move(NewBoard, ComputerFrom, ComputerTo, Rating),
% statistics,
    finish_move(NewBoard, ComputerFrom, ComputerTo, Rating, OutBoard),
    true, !.

respond_to(Board, _, _, Board) :-
    write('Illegal move.').

finish_move(Board, ComputerFrom, ComputerTo, -32000, Board) :-
    in_check(Board, black),
    write('Checkmate, you win.'), nl,
    print_board(Board).
finish_move(Board, ComputerFrom, ComputerTo, -32000, Board) :-
    write('Stalemate.'), nl,
    print_board(Board).
finish_move(NewBoard, ComputerFrom, ComputerTo, Rating, OutBoard) :-
    make_move(NewBoard, ComputerFrom, ComputerTo, OutBoard),
    report_move(OutBoard, ComputerFrom, ComputerTo, Rating).

select_move(Board, From, To, book) :-       % Use book
    book(Board, From, To).
select_move(Board, From, To, Rating) :-     % time for ALPHA-BETA
    computer(ComputerColor),
    ply_depth(Depth),
    alpha_beta(Board, ComputerColor, Depth, -32000, 32000, move(From, To), Rating).

alpha_beta(Board, Color, 0, Alpha, Beta, BestMove, MoveVal) :-
    computer(ComputerColor),
    evaluate(Board, ComputerColor, MoveVal).
    % write('.').
    % write(' val: '), write(MoveVal), nl.
alpha_beta(Board, Color, Depth, Alpha, Beta, BestMove, MoveVal) :-
    collect_moves(Board, Color, MoveList), !,
    %writeq(MoveList), nl,
    find_best(Board, Color, MoveList, Depth, Alpha, Beta, BestMove, MoveVal).

find_best(Board, Color, [move(From, To)|Moves], Depth, Alpha, Beta,
      BestMove, BestVal) :-
    make_move(Board, From, To, NewBoard),
%   write('*'),
    % write(Color),write(' '),write(From),write(' '),write(To),
    % write(' '),write(Alpha), write(' '),write(Beta),write(' '),
    NextDepth is Depth - 1,
    opposite(Color, OppositeColor),
    alpha_beta(NewBoard, OppositeColor, NextDepth, Alpha, Beta, _, Val),
    sufficient(Board, Color, Moves, Depth, Alpha, Beta, move(From,To), Val, BestMove, BestVal).

sufficient(Board, Color, [], Depth, Alpha, Beta, Move, Val, Move, Val).
sufficient(Board, Color, Moves, Depth, Alpha, Beta, Move, Val, Move, Val) :-
    player(Color),
    Val < Alpha,
    write('x').
    % write('       Pruning for player- val:'), write(Val),
    % write(' '),write(Alpha),write(' '),write(Beta),nl.
sufficient(Board, Color, Moves, Depth, Alpha, Beta, Move, Val, Move, Val) :-
    computer(Color),
    Val > Beta,
    true. % write('X'),nl.
    % write('       Pruning for computer- val:'), write(Val),
    % write(' '),write(Alpha),write(' '),write(Beta),nl.
sufficient(Board, Color, Moves, Depth, Alpha, Beta, Move, Val,
       BestMove, BestVal) :-
    new_bounds(Color, Alpha, Beta, Val, NewAlpha, NewBeta),
    find_best(Board, Color, Moves, Depth, NewAlpha, NewBeta, Move1, Val1),
    better_of(Color, Move, Val, Move1, Val1, BestMove, BestVal).

new_bounds(Color, Alpha, Beta, Val, Val, Beta) :-
    computer(Color),            % Maximizing
    Val > Alpha.
new_bounds(Color, Alpha, Beta, Val, Alpha, Val) :-
    player(Color),              % Minimizing
    Val < Beta.
new_bounds(_, Alpha, Beta, _, Alpha, Beta).

better_of(Color, Move, Val, Move1, Val1, Move, Val) :-
    player(Color),              % Minimizing
    Val < Val1.
better_of(Color, Move, Val, Move1, Val1, Move, Val) :-
    computer(Color),            % Maximizing
    Val > Val1.
better_of(_, _, _, Move1, Val1, Move1, Val1).

evaluate(Board, Color, -32000) :-
    in_check(Board, Color).
evaluate(Board, Color, Rating) :-
    opposite(Color, OppositeColor),
    strength(Board, Color, OppositeColor, Rating).

strength([], _, _, 0).
strength([state(_, _, _, _)|Board], Color, OppositeColor, Strength) :-
    strength(Board, Color, OppositeColor, Strength).
strength([piece(_, Color, Type)|Board], Color, OppositeColor, Strength) :-
    value(Type, Value),
    strength(Board, Color, OppositeColor, PartialStrength),
    Strength is PartialStrength + Value.
strength([piece(_, OppositeColor, Type)|Board], Color, OppositeColor,
     Strength) :-
    value(Type, Value),
    strength(Board, Color, OppositeColor, PartialStrength),
    Strength is PartialStrength - Value.

legal_move(Board, Color, From, To) :-
    move(Board, From, To, Color, Piece),
    make_move(Board, From, To, NewBoard), !,
    \+ in_check(NewBoard, Color).

in_check(Board, Color) :-
    member(piece(KingSquare, Color, king), Board),
    opposite(Color, OppositeColor),
    move(Board, _, KingSquare, OppositeColor, _).

%:- entry((move(Board,From,To,Color,Piece) :- nonvar(Board), atom(Color))).

collect_moves(Board, Color, Moves) :-
    findall(move(From,To), move(Board,From,To,Color,Piece), Moves).

make_move(Board, From, To, OutBoard) :-
    make_move(Board, From, To, Color, Type, NewBoard),
    update_state(NewBoard, From, Color, Type),
    check_castling(NewBoard, From, To, Color, Type, OutBoard).

make_move([], From, File-8, white, pawn, [piece(File-8, white, queen)]).
make_move([], From, File-1, black, pawn, [piece(File-1, black, queen)]).
make_move([], From, To, Color, Type, [piece(To, Color, Type)]).     % Add To sq.
make_move([piece(From, Color, Type)|Board], From, To, Color, Type, OutBoard) :-
    make_move(Board, From, To, Color, Type, OutBoard).          % Skip From
make_move([piece(To, _, _)|Board], From, To, Color, Type, OutBoard) :-
    make_move(Board, From, To, Color, Type, OutBoard).          % Skip To sq
make_move([Piece|Board], From, To, Color, Type, [Piece|OutBoard]) :-
    make_move(Board, From, To, Color, Type, OutBoard).      % Copy

check_castling(Board, e-Rank, g-Rank, Color, king, OutBoard) :- % King side
    make_move(Board, h-Rank, f-Rank, OutBoard).     % castling
check_castling(Board, e-Rank, c-Rank, Color, king, OutBoard) :- % Queen side
    make_move(Board, a-Rank, d-Rank, OutBoard).     % castling
check_castling(Board, _, _, _, _, Board).

parse_square(Square, File-Rank) :-
        atom_codes(Square, [F,R]),
        atom_codes(File, [F]),
        myname(Rank, [R]),
        on_board(File-Rank).

parse_move(Move, From_File-From_Rank, To_File-To_Rank) :-
        atom_codes(Move, [FF,FR,TF,TR]),
        atom_codes(From_File, [FF]),
        myname(From_Rank, [FR]),
        atom_codes(To_File, [TF]),
        myname(To_Rank, [TR]),
        on_board(From_File-From_Rank),
        on_board(To_File-To_Rank).

on_board(File-Rank) :-
    member(File, [a, b, c, d, e, f, g, h]),
    member(Rank, [1, 2, 3, 4, 5, 6, 7, 8]).

not_moved(Board, Color, king) :-
    member(state(Color, King, _, _), Board),
    var(King).
not_moved(Board, Color, king, rook) :-
    member(state(Color, _, KingRook, _), Board),
    var(KingRook).
not_moved(Board, Color, queen, rook) :-
    member(state(Color, _, _, QueenRook), Board),
    var(QueenRook).

update_state(Board, From, Color, king) :-       % Was king moved?
    member(state(Color, king_moved, _, _), Board).
update_state(Board, h-Rank, Color, rook) :-     % Was king rook moved?
    member(state(Color, _, king_rook_moved, _), Board).
update_state(Board, a-Rank, Color, rook) :-     % Was queen rook moved?
    member(state(Color, _, _, queen_rook_moved), Board).
update_state(_, _, _, _).               % Else, ignore


% ------------------------------------------------------------------------------
% Printing utilities

report_move(Board, From_File-From_Rank, To_File-To_Rank, Rating) :-
    nl,
    write('My move: '),
    write(From_File),
    write(From_Rank),
    write(To_File),
    write(To_Rank),
    write(', Rating: '),
    write(Rating), nl,
    print_board(Board).

%% print_board(Board) :-
%%  !.

print_board(Board) :-
    print_rank(Board, 8),
    print_dashes, nl,
    print_letters, nl, !.

print_rank(Board, 1) :-
    print_dashes, nl,
    write(1),
    write(' | '),
    print_squares(Board, a, 1).
print_rank(Board, Rank) :-
    print_dashes, nl,
    write(Rank),
    write(' | '),
    print_squares(Board, a, Rank),
    minus_one(Rank, NextRank),
%   Next_Rank is Rank - 1,  % the void variable
    NextRank is Rank - 1,
    print_rank(Board, NextRank).

print_squares(Board, h, Rank) :-
    print_a_square(Board, h, Rank),
    write(' |'),
    nl.
print_squares(Board, File, Rank) :-
    print_a_square(Board, File, Rank),
    write(' | '),
    plus_one(File, NextFile),
    print_squares(Board, NextFile, Rank).

print_a_square(Board, File, Rank) :-
    member(piece(File-Rank, Color, Type), Board),
    (Color = white -> write(' ') ; write('*')),
    atom_codes(Type, [T|_]),
    put_code(T).
print_a_square(Board, File, Rank) :-
    write('  ').

print_dashes :-
    write('  +----+----+----+----+----+----+----+----+').

print_letters :-
    write('    a    b    c    d    e    f    g    h').

myname(X,[Y]) :-
    myname2(Y,X), !.

myname2(48,0).
myname2(49,1).
myname2(50,2).
myname2(51,3).
myname2(52,4).
myname2(53,5).
myname2(54,6).
myname2(55,7).
myname2(56,8).
myname2(57,9).


% ------------------------------------------------------------------------------
% Initialization stuff

init_board( [
    state(white, WhiteKing, WhiteKingRook, WhiteQueenRook),
    state(black, BlackKing, BlackKingRook, BlackQueenRook),
    piece(a-8, black, rook  ), piece(b-8, black, night ),
    piece(c-8, black, bishop), piece(d-8, black, queen ),
    piece(e-8, black, king  ), piece(f-8, black, bishop),
    piece(g-8, black, night ), piece(h-8, black, rook  ),
    piece(a-7, black, pawn  ), piece(b-7, black, pawn  ),
    piece(c-7, black, pawn  ), piece(d-7, black, pawn  ),
    piece(e-7, black, pawn  ), piece(f-7, black, pawn  ),
    piece(g-7, black, pawn  ), piece(h-7, black, pawn  ),
    piece(a-1, white, rook  ), piece(b-1, white, night ),
    piece(c-1, white, bishop), piece(d-1, white, queen ),
    piece(e-1, white, king  ), piece(f-1, white, bishop),
    piece(g-1, white, night ), piece(h-1, white, rook  ),
    piece(a-2, white, pawn  ), piece(b-2, white, pawn  ),
    piece(c-2, white, pawn  ), piece(d-2, white, pawn  ),
    piece(e-2, white, pawn  ), piece(f-2, white, pawn  ),
    piece(g-2, white, pawn  ), piece(h-2, white, pawn  ) ]).


% ------------------------------------------------------------------------------
% Valid move checkers and generators

move(Board, F_File-F_Rank, T_File-T_Rank, Color, Piece) :-
    occupied_by(Board, F_File-F_Rank, Color, Piece),
    can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, Piece),
    \+ occupied_by(Board, T_File-T_Rank, Color, _).

can_move(Board, File-F_Rank, File-T_Rank, white, pawn) :-       % White pawn
    plus_one(F_Rank, T_Rank),               % move
    \+ occupied_by(Board, File-T_Rank, black, _).
can_move(Board, File-2, File-4, white, pawn) :-
    \+ occupied_by(Board, File-3, black, _),
    \+ occupied_by(Board, File-4, black, _).
can_move(Board, F_File-F_Rank, T_File-T_Rank, white, pawn) :-   % White pawn
    plus_one(F_File, T_File),               % capture
        plus_one(F_Rank, T_Rank),
    occupied_by(Board, T_File-T_Rank, black, _).
can_move(Board, F_File-F_Rank, T_File-T_Rank, white, pawn) :-
    minus_one(F_File, T_File),
        plus_one(F_Rank, T_Rank),
    occupied_by(Board, T_File-T_Rank, black, _).

can_move(Board, File-F_Rank, File-T_Rank, black, pawn) :-   % Black pawn
    minus_one(F_Rank, T_Rank),              % move
    \+ occupied_by(Board, File-T_Rank, white, _).
can_move(Board, File-7, File-5, black, pawn) :-
    \+ occupied_by(Board, File-6, white, _),
    \+ occupied_by(Board, File-5, white, _).
can_move(Board, F_File-F_Rank, T_File-T_Rank, black, pawn) :-   % Black pawn
    minus_one(F_File, T_File),              % capture
        minus_one(F_Rank, T_Rank),
    occupied_by(Board, T_File-T_Rank, white, _).
can_move(Board, F_File-F_Rank, T_File-T_Rank, black, pawn) :-
    plus_one(F_File, T_File),
        minus_one(F_Rank, T_Rank),
    occupied_by(Board, T_File-T_Rank, white, _).

can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-  % Knight move
    plus_one(F_File, T_File), plus_two(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    plus_one(F_File, T_File), minus_two(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    minus_one(F_File, T_File), plus_two(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    minus_one(F_File, T_File), minus_two(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    plus_two(F_File, T_File), plus_one(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    plus_two(F_File, T_File), minus_one(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    minus_two(F_File, T_File), plus_one(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, night) :-
    minus_two(F_File, T_File), minus_one(F_Rank, T_Rank).

can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, bishop) :- % Bishop move
    can_step(Board,  1,  1, F_File-F_Rank, T_File-T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, bishop) :-
    can_step(Board,  1, -1, F_File-F_Rank, T_File-T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, bishop) :-
    can_step(Board, -1,  1, F_File-F_Rank, T_File-T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, bishop) :-
    can_step(Board, -1, -1, F_File-F_Rank, T_File-T_Rank).

can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, rook) :-   % Rook move
    can_step(Board,  1,  0, F_File-F_Rank, T_File-T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, rook) :-
    can_step(Board, -1,  0, F_File-F_Rank, T_File-T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, rook) :-
    can_step(Board,  0,  1, F_File-F_Rank, T_File-T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, rook) :-
    can_step(Board,  0, -1, F_File-F_Rank, T_File-T_Rank).

can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, queen) :-  % Queen move
    can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, bishop).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, queen) :-
    can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, rook).

can_move(Board, File-F_Rank, File-T_Rank, Color, king) :-   % King move
    plus_one(F_Rank, T_Rank).
can_move(Board, File-F_Rank, File-T_Rank, Color, king) :-
    minus_one(F_Rank, T_Rank).
can_move(Board, F_File-Rank, T_File-Rank, Color, king) :-   % King move
    plus_one(F_File, T_File).
can_move(Board, F_File-Rank, T_File-Rank, Color, king) :-
    minus_one(F_File, T_File).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, king) :-
    plus_one(F_File, T_File),
    minus_one(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, king) :-
    plus_one(F_File, T_File),
    plus_one(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, king) :-
    minus_one(F_File, T_File),
    plus_one(F_Rank, T_Rank).
can_move(Board, F_File-F_Rank, T_File-T_Rank, Color, king) :-
    minus_one(F_File, T_File),
    minus_one(F_Rank, T_Rank).

can_move(Board, e-Rank, g-Rank, Color, king) :-         % King side
        not_moved(Board, Color, king),              % castle
        not_moved(Board, Color, king, rook),
    \+ occupied_by(Board, f-Rank, _, _).
can_move(Board, e-Rank, g-Rank, Color, king) :-         % Queen side
        not_moved(Board, Color, king),              % castle
        not_moved(Board, Color, queen, rook),
    \+ occupied_by(Board, d-Rank, _, _),
    \+ occupied_by(Board, b-Rank, _, _).

can_step(Board,  1,  1, F_File-F_Rank, T_File-T_Rank) :-    % Step moves
    plus_one(F_File, T_File),
    plus_one(F_Rank, T_Rank).
can_step(Board,  1,  1, F_File-F_Rank, T_File-T_Rank) :-
    plus_one(F_File, I_File),
    plus_one(F_Rank, I_Rank),
    \+ occupied_by(Board, I_File-I_Rank, _, _),
    can_step(Board,  1,  1, I_File-I_Rank, T_File-T_Rank).
can_step(Board,  1, 0, F_File-Rank, T_File-Rank) :-
    plus_one(F_File, T_File).
can_step(Board,  1, 0, F_File-Rank, T_File-Rank) :-
    plus_one(F_File, I_File),
    \+ occupied_by(Board, I_File-Rank, _, _),
    can_step(Board,  1, 0, I_File-Rank, T_File-Rank).
can_step(Board,  1, -1, F_File-F_Rank, T_File-T_Rank) :-
    plus_one(F_File, T_File),
    minus_one(F_Rank, T_Rank).
can_step(Board,  1, -1, F_File-F_Rank, T_File-T_Rank) :-
    plus_one(F_File, I_File),
    minus_one(F_Rank, I_Rank),
    \+ occupied_by(Board, I_File-I_Rank, _, _),
    can_step(Board,  1, -1, I_File-I_Rank, T_File-T_Rank).
can_step(Board,  0,  1, File-F_Rank, File-T_Rank) :-
    plus_one(F_Rank, T_Rank).
can_step(Board,  0,  1, File-F_Rank, File-T_Rank) :-
    plus_one(F_Rank, I_Rank),
    \+ occupied_by(Board, File-I_Rank, _, _),
    can_step(Board,  0,  1, File-I_Rank, File-T_Rank).
can_step(Board,  0, -1, File-F_Rank, File-T_Rank) :-
    minus_one(F_Rank, T_Rank).
can_step(Board,  0, -1, File-F_Rank, File-T_Rank) :-
    minus_one(F_Rank, I_Rank),
    \+ occupied_by(Board, File-I_Rank, _, _),
    can_step(Board,  0, -1, File-I_Rank, File-T_Rank).
can_step(Board, -1,  1, F_File-F_Rank, T_File-T_Rank) :-
    minus_one(F_File, T_File),
    plus_one(F_Rank, T_Rank).
can_step(Board, -1,  1, F_File-F_Rank, T_File-T_Rank) :-
    minus_one(F_File, I_File),
    plus_one(F_Rank, I_Rank),
    \+ occupied_by(Board, I_File-I_Rank, _, _),
    can_step(Board, -1,  1, I_File-I_Rank, T_File-T_Rank).
can_step(Board, -1,  0, F_File-Rank, T_File-Rank) :-
    minus_one(F_File, T_File).
can_step(Board, -1,  0, F_File-Rank, T_File-Rank) :-
    minus_one(F_File, I_File),
    \+ occupied_by(Board, I_File-I_Rank, _, _),
    can_step(Board, -1,  0, I_File-Rank, T_File-Rank).
can_step(Board, -1, -1, F_File-F_Rank, T_File-T_Rank) :-
    minus_one(F_File, T_File),
    minus_one(F_Rank, T_Rank).
can_step(Board, -1, -1, F_File-F_Rank, T_File-T_Rank) :-
    minus_one(F_File, I_File),
    minus_one(F_Rank, I_Rank),
    \+ occupied_by(Board, I_File-I_Rank, _, _),
    can_step(Board, -1, -1, I_File-I_Rank, T_File-T_Rank).

occupied_by(Board, File-Rank, Color, Piece) :-
    member(piece(File-Rank, Color, Piece), Board).

plus_one(1,2).
plus_one(2,3).
plus_one(3,4).
plus_one(4,5).
plus_one(5,6).
plus_one(6,7).
plus_one(7,8).

plus_one(a,b).
plus_one(b,c).
plus_one(c,d).
plus_one(d,e).
plus_one(e,f).
plus_one(f,g).
plus_one(g,h).

minus_one(X,Y) :-
    plus_one(Y,X).

plus_two(1,3).
plus_two(2,4).
plus_two(3,5).
plus_two(4,6).
plus_two(5,7).
plus_two(6,8).

plus_two(a,c).
plus_two(b,d).
plus_two(c,e).
plus_two(d,f).
plus_two(e,g).
plus_two(f,h).

minus_two(X,Y) :-
    plus_two(Y,X).


% ------------------------------------------------------------------------------
% Book moves

book( [ state(white, WhiteKing, WhiteKingRook, WhiteQueenRook), % e2e4
    state(black, BlackKing, BlackKingRook, BlackQueenRook), % respond with
    piece(a-8, black, rook  ), piece(b-8, black, night ),   % ...   e7e5
    piece(c-8, black, bishop), piece(d-8, black, queen ),
    piece(e-8, black, king  ), piece(f-8, black, bishop),
    piece(g-8, black, night ), piece(h-8, black, rook  ),
    piece(a-7, black, pawn  ), piece(b-7, black, pawn  ),
    piece(c-7, black, pawn  ), piece(d-7, black, pawn  ),
    piece(e-7, black, pawn  ), piece(f-7, black, pawn  ),
    piece(g-7, black, pawn  ), piece(h-7, black, pawn  ),
    piece(a-1, white, rook  ), piece(b-1, white, night ),
    piece(c-1, white, bishop), piece(d-1, white, queen ),
    piece(e-1, white, king  ), piece(f-1, white, bishop),
    piece(g-1, white, night ), piece(h-1, white, rook  ),
    piece(a-2, white, pawn  ), piece(b-2, white, pawn  ),
    piece(c-2, white, pawn  ), piece(d-2, white, pawn  ),
    piece(f-2, white, pawn  ), piece(g-2, white, pawn  ),
    piece(h-2, white, pawn  ), piece(e-4, white, pawn  ) ], e-7, e-5).
book( [ state(white, WhiteKing, WhiteKingRook, WhiteQueenRook), % e2e4  e7e5
    state(black, BlackKing, BlackKingRook, BlackQueenRook), % g1f3
    piece(a-8, black, rook  ), piece(b-8, black, night ),   % respond with
    piece(c-8, black, bishop), piece(d-8, black, queen ),   % ...   b8c6
    piece(e-8, black, king  ), piece(f-8, black, bishop),
    piece(g-8, black, night ), piece(h-8, black, rook  ),
    piece(a-7, black, pawn  ), piece(b-7, black, pawn  ),
    piece(c-7, black, pawn  ), piece(d-7, black, pawn  ),
    piece(f-7, black, pawn  ), piece(g-7, black, pawn  ),
    piece(h-7, black, pawn  ), piece(a-1, white, rook  ),
    piece(b-1, white, night ), piece(c-1, white, bishop),
    piece(d-1, white, queen ), piece(e-1, white, king  ),
    piece(f-1, white, bishop), piece(h-1, white, rook  ),
    piece(a-2, white, pawn  ), piece(b-2, white, pawn  ),
    piece(c-2, white, pawn  ), piece(d-2, white, pawn  ),
    piece(f-2, white, pawn  ), piece(g-2, white, pawn  ),
    piece(h-2, white, pawn  ), piece(e-4, white, pawn  ),
    piece(e-5, black, pawn  ), piece(f-3, white, night ) ], b-8, c-6).
book( [ state(white, WhiteKing, WhiteKingRook, WhiteQueenRook), % e2e4  e7e5
    state(black, BlackKing, BlackKingRook, BlackQueenRook), % g1f3  b8f6
    piece(a-8, black, rook  ), piece(c-8, black, bishop),   % f1b5
    piece(d-8, black, queen ), piece(e-8, black, king  ),   % respond with
    piece(f-8, black, bishop), piece(g-8, black, night ),   % ...   a7a6
    piece(h-8, black, rook  ), piece(a-7, black, pawn  ),
    piece(b-7, black, pawn  ), piece(c-7, black, pawn  ),
    piece(d-7, black, pawn  ), piece(f-7, black, pawn  ),
    piece(g-7, black, pawn  ), piece(h-7, black, pawn  ),
    piece(a-1, white, rook  ), piece(b-1, white, night ),
    piece(c-1, white, bishop), piece(d-1, white, queen ),
    piece(e-1, white, king  ), piece(h-1, white, rook  ),
    piece(a-2, white, pawn  ), piece(b-2, white, pawn  ),
    piece(c-2, white, pawn  ), piece(d-2, white, pawn  ),
    piece(f-2, white, pawn  ), piece(g-2, white, pawn  ),
    piece(h-2, white, pawn  ), piece(e-4, white, pawn  ),
    piece(e-5, black, pawn  ), piece(f-3, white, night ),
    piece(c-6, black, night ), piece(b-5, white, bishop) ], a-7, a-6).

% ------------------------------------------------------------------------------
% Utilities

opposite(white, black).
opposite(black, white).
