/*
* This file is part of pgn-extract: a Portable Game Notation (PGN) extractor.
* Copyright (C) 1994-2019 David J. Barnes
*
* pgn-extract is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* pgn-extract is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with pgn-extract. If not, see <http://www.gnu.org/licenses/>.
*
* David J. Barnes may be contacted as d.j.barnes@kent.ac.uk
* https://www.cs.kent.ac.uk/people/staff/djb/
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "bool.h"
#include "mymalloc.h"
#include "defs.h"
#include "typedef.h"
#include "tokens.h"
#include "taglist.h"
#include "lex.h"
#include "map.h"
#include "decode.h"
#include "apply.h"
/* Structures to hold the x,y displacements of the various
* piece movements.
*/
/* Define a list of possible Knight moves. */
#define NUM_KNIGHT_MOVES 8
static int Knight_moves[2 * NUM_KNIGHT_MOVES] = {
1, 2,
1, -2,
2, 1,
2, -1,
-1, 2,
-1, -2,
-2, 1,
-2, -1,};
/* Define a list of possible Bishop moves. */
#define NUM_BISHOP_MOVES 4
static int Bishop_moves[2 * NUM_BISHOP_MOVES] = {
1, 1,
1, -1,
-1, 1,
-1, -1};
/* Define a list of possible Rook moves. */
#define NUM_ROOK_MOVES 4
static int Rook_moves[2 * NUM_ROOK_MOVES] = {
1, 0,
0, 1,
-1, 0,
0, -1};
/* Define a list of possible King moves. */
#define NUM_KING_MOVES 8
static int King_moves[2 * NUM_KING_MOVES] = {
1, 0,
1, 1,
1, -1,
0, 1,
0, -1,
-1, 0,
-1, 1,
-1, -1,};
/* Define a list of possible Queen moves. */
#define NUM_QUEEN_MOVES 8
static int Queen_moves[2 * NUM_QUEEN_MOVES] = {
1, 0,
1, 1,
1, -1,
0, 1,
0, -1,
-1, 0,
-1, 1,
-1, -1,};
/* A table of hash values for square/piece/colour combinations.
* When a piece is moved, the hash value is xor-ed into a
* running description of the current board state.
*/
#define NUMBER_OF_PIECES 6
static HashCode HashTab[BOARDSIZE][BOARDSIZE][NUMBER_OF_PIECES][2];
/* Code to allocate and free MovePair structures. New moves are
* allocated from the move_pool, if it isn't empty. Old moves
* are freed to this pool.
* This is used to avoid too much fragmentation of the heap.
* Since the program will spend a lot of its life allocating and
* deallocating move structures, we might as well hang on to them.
*/
/* Keep a pool of free move structures. */
static MovePair *move_pool = NULL;
static MovePair *
malloc_move(void)
{
MovePair *move;
if (move_pool != NULL) {
move = move_pool;
move_pool = move_pool->next;
}
else {
move = (MovePair *) malloc_or_die(sizeof (MovePair));
}
move->next = NULL;
return move;
}
/* Append another move pair onto moves, and return the new list. */
static MovePair *
append_move_pair(Col from_col, Rank from_rank, Col to_col, Rank to_rank, MovePair *moves)
{
MovePair *move = malloc_move();
move->from_rank = from_rank;
move->from_col = from_col;
move->to_rank = to_rank;
move->to_col = to_col;
move->next = moves;
return move;
}
/* Simply add the move to the free move pool. */
static void
free_move_pair(MovePair *move)
{
move->next = move_pool;
move_pool = move;
}
/* Free a whole list of moves to the move pool. */
void
free_move_pair_list(MovePair *move_list)
{
if(move_pool == NULL) {
move_pool = move_list;
}
else if (move_list != NULL) {
MovePair *tail = move_list;
while(tail->next != NULL) {
tail = tail->next;
}
tail->next = move_pool;
move_pool = move_list;
}
}
/* Produce a hash value for each piece, square, colour combination.
* This code is a modified version of that to be found in
* Steven J. Edwards' SAN kit.
*/
#define SHIFT_LENGTH 7
void
init_hashtab(void)
{
Piece piece;
Colour colour;
Rank rank;
Col col;
static HashCode seed = 0;
for (col = FIRSTCOL; col <= LASTCOL; col++) {
for (rank = FIRSTRANK; rank <= LASTRANK; rank++) {
for (piece = PAWN; piece <= KING; piece++) {
for (colour = BLACK; colour <= WHITE; colour++) {
HashCode code;
/* Try to use a wider range of the available values
* in an attempt to avoid spurious hash matches.
*/
seed = (seed * 1103515245L) + 123456789L;
code = (seed >> SHIFT_LENGTH);
HashTab[col - FIRSTCOL][rank - FIRSTRANK][piece - PAWN][colour - BLACK] = code;
}
}
}
}
}
/* Look up the hash value for this combination. */
HashCode
hash_lookup(Col col, Rank rank, Piece piece, Colour colour)
{
return HashTab[col - FIRSTCOL][rank - FIRSTRANK][piece - PAWN][colour - BLACK];
}
/* Is the given piece of the named colour? */
static Boolean
piece_is_colour(Piece coloured_piece, Colour colour)
{
return EXTRACT_COLOUR(coloured_piece) == colour;
}
/* Make the given move. This is assumed to have been thoroughly
* checked beforehand, and the from_ and to_ information to be
* complete. Update the board structure to reflect
* the full set of changes implied by it.
*/
void
make_move(MoveClass class, Col from_col, Rank from_rank, Col to_col, Rank to_rank,
Piece piece, Colour colour, Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
int from_r = RankConvert(from_rank);
int from_c = ColConvert(from_col);
/* Does this move involve a capture? */
Boolean capture = FALSE;
/* For a castling move, where is the Rook? */
Col castling_rook_col;
/* Determine which rook will be moving if castling.
* Needed for Chess960.
*/
if (class == KINGSIDE_CASTLE || class == QUEENSIDE_CASTLE) {
castling_rook_col = find_castling_rook_col(colour, board, class);
}
else {
castling_rook_col = '\0';
}
/* If a KING or ROOK is moved, this might affect castling rights. */
if (piece == KING) {
if (colour == WHITE) {
board->WKingCol = to_col;
board->WKingRank = to_rank;
board->WKingCastle = board->WQueenCastle = '\0';
}
else {
board->BKingCol = to_col;
board->BKingRank = to_rank;
board->BKingCastle = board->BQueenCastle = '\0';
}
}
else if (piece == ROOK) {
/* Some castling rights may need disallowing. */
if (colour == WHITE) {
if (from_rank == FIRSTRANK) {
if (from_col == board->WQueenCastle) {
board->WQueenCastle = '\0';
}
else if (from_col == board->WKingCastle) {
board->WKingCastle = '\0';
}
else {
/* No change. */
}
}
}
else {
if (from_rank == LASTRANK) {
if (from_col == board->BQueenCastle) {
board->BQueenCastle = '\0';
}
else if (from_col == board->BKingCastle) {
board->BKingCastle = '\0';
}
else {
/* No change. */
}
}
}
}
else {
/* Castling not in question from the piece being moved,
* but see below for checks on any captured piece.
*/
}
/* Check for en-passant rights resulting from this move. */
if (piece != PAWN) {
/* The move cannot result in en-passant rights. */
board->EnPassant = FALSE;
}
else {
if (colour == WHITE) {
if ((from_rank == '2') && (to_rank == '4')) {
/* This move permits an en-passant capture on the following move. */
board->EnPassant = TRUE;
board->ep_rank = to_rank - 1;
board->ep_col = to_col;
}
else if ((board->EnPassant) && (board->ep_rank == to_rank) &&
(board->ep_col == to_col)) {
/* This is an ep capture. Remove the intermediate pawn. */
board->board[RankConvert(to_rank) - 1][ColConvert(to_col)] = EMPTY;
board->weak_hash_value ^= hash_lookup(to_col, to_rank - 1, PAWN, BLACK);
board->EnPassant = FALSE;
}
else {
board->EnPassant = FALSE;
}
}
else {
if ((from_rank == '7') && (to_rank == '5')) {
board->EnPassant = TRUE;
board->ep_rank = to_rank + 1;
board->ep_col = to_col;
}
else if ((board->EnPassant) && (board->ep_rank == to_rank) &&
(board->ep_col == to_col)) {
/* This is an ep capture. Remove the intermediate pawn. */
board->board[RankConvert(to_rank) + 1][ColConvert(to_col)] = EMPTY;
board->weak_hash_value ^= hash_lookup(to_col, to_rank + 1, PAWN, WHITE);
board->EnPassant = FALSE;
}
else {
board->EnPassant = FALSE;
}
}
}
/* Clear the source square. */
if (class == PAWN_MOVE_WITH_PROMOTION && piece != PAWN) {
/* Remove the promoted pawn. */
board->weak_hash_value ^= hash_lookup(from_col, from_rank, PAWN, colour);
}
else {
board->weak_hash_value ^= hash_lookup(from_col, from_rank, piece, colour);
}
board->board[from_r][from_c] = EMPTY;
if (board->board[to_r][to_c] != EMPTY) {
/* Delete the removed piece from the hash value. */
Piece coloured_piece = board->board[to_r][to_c];
Piece removed_piece;
Colour removed_colour;
removed_piece = EXTRACT_PIECE(coloured_piece);
removed_colour = EXTRACT_COLOUR(coloured_piece);
board->weak_hash_value ^= hash_lookup(to_col, to_rank, removed_piece, removed_colour);
/* See whether the removed piece is a Rook, as this could
* affect castling rights.
*/
if (removed_piece == ROOK) {
if (removed_colour == WHITE) {
if (to_rank == FIRSTRANK) {
if (to_col == board->WQueenCastle) {
board->WQueenCastle = '\0';
}
else if (to_col == board->WKingCastle) {
board->WKingCastle = '\0';
}
else {
/* No change. */
}
}
}
else {
if (to_rank == LASTRANK) {
if (to_col == board->BQueenCastle) {
board->BQueenCastle = '\0';
}
else if (to_col == board->BKingCastle) {
board->BKingCastle = '\0';
}
else {
/* No change. */
}
}
}
}
if (class != KINGSIDE_CASTLE && class != QUEENSIDE_CASTLE) {
/* A genuine capture. */
capture = TRUE;
}
}
/* Deal with the half-move clock. */
if (piece == PAWN || class == PAWN_MOVE_WITH_PROMOTION) {
board->halfmove_clock = 0;
}
else if (capture) {
board->halfmove_clock = 0;
}
else {
board->halfmove_clock++;
}
/* Place the piece at its destination. */
board->board[to_r][to_c] = MAKE_COLOURED_PIECE(colour, piece);
/* Insert the moved piece into the hash value. */
board->weak_hash_value ^= hash_lookup(to_col, to_rank, piece, colour);
if(!board->EnPassant) {
board->ep_rank = '\0';
board->ep_col = '\0';
}
if (castling_rook_col != '\0') {
/* The rook involved in the castling move must now be moved. */
if (castling_rook_col != to_col) {
/* It must be removed. */
board->weak_hash_value ^= hash_lookup(castling_rook_col, from_rank, ROOK, colour);
board->board[from_r][ColConvert(castling_rook_col)] = EMPTY;
}
int rook_offset = (class == KINGSIDE_CASTLE ? -1 : 1);
/* Place the rook at its destination. */
board->board[to_r][to_c + rook_offset] = MAKE_COLOURED_PIECE(colour, ROOK);
board->weak_hash_value ^= hash_lookup(to_col + rook_offset, to_rank, ROOK, colour);
}
}
/* Find pawn moves matching the to_ and from_ information.
* Depending on the input form of the move, some of this will be
* incomplete. For instance: e4 supplies just the to_ information
* whereas cb supplies some from_ and some to_.
*/
MovePair *
find_pawn_moves(Col from_col, Rank from_rank, Col to_col, Rank to_rank,
Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
int from_r = RankConvert(from_rank);
int from_c = ColConvert(from_col);
MovePair *move_list = NULL;
MovePair *move = NULL;
/* White pawn moves are offset by +1, Black by -1. */
int offset = COLOUR_OFFSET(colour);
Piece piece_to_move = MAKE_COLOURED_PIECE(colour, PAWN);
if ((to_col != 0) && (to_rank != 0)) {
/* We know the complete destination. */
if (board->board[to_r][to_c] == EMPTY) {
/* Destination must be empty for this form. */
if (board->board[to_r - offset][to_c] == piece_to_move) {
/* MovePair of one square. */
move = append_move_pair(ToCol(to_c), ToRank(to_r - offset),
to_col, to_rank, NULL);
}
else if ((board->board[to_r - offset][to_c] == EMPTY) &&
(to_rank == (colour == WHITE ? '4' : '5'))) {
/* Special case of initial two square move. */
if (board->board[to_r - 2 * offset][to_c] == piece_to_move) {
move = append_move_pair(ToCol(to_c), ToRank(to_r - 2 * offset),
to_col, to_rank, NULL);
}
}
else if (board->EnPassant &&
(board->ep_rank == to_rank) &&
(board->ep_col == to_col)) {
/* Make sure that there is a valid pawn in position. */
if (from_col != 0) {
from_r = to_r - offset;
if (board->board[from_r][from_c] == piece_to_move) {
move = append_move_pair(ToCol(from_c), ToRank(from_r),
to_col, ToRank(to_r), NULL);
}
}
}
}
else if (piece_is_colour(board->board[to_r][to_c], OPPOSITE_COLOUR(colour))) {
/* Capture on the destination square. */
if (from_col != 0) {
/* We know the from column. */
from_r = to_r - offset;
if (board->board[from_r][from_c] == piece_to_move) {
move = append_move_pair(ToCol(from_c), ToRank(from_r),
to_col, to_rank, NULL);
}
}
}
else {
/* We have no move. */
move = NULL;
}
move_list = move;
}
else if ((from_col != 0) && (to_col != 0)) {
/* Should be a diagonal capture. */
if (((from_col + 1) != to_col) && ((from_col - 1) != to_col)) {
/* Inconsistent information. */
}
else {
if (from_rank != 0) {
/* We have complete source information. Check its
* veracity.
*/
to_r = from_r - offset;
if (board->board[from_r][from_c] == piece_to_move) {
Piece occupant = board->board[to_r][to_c];
if ((occupant != EMPTY) &&
(piece_is_colour(occupant, OPPOSITE_COLOUR(colour)))) {
move = append_move_pair(ToCol(from_c), ToRank(from_r),
to_col, ToRank(to_r), NULL);
}
else if (board->EnPassant && (board->ep_rank == ToRank(to_r)) &&
(board->ep_col == ToCol(to_c))) {
move = append_move_pair(ToCol(from_c), ToRank(from_r),
to_col, ToRank(to_r), NULL);
}
}
}
else {
/* We must search the from_col and to_col for appropriate
* combinations. There may be more than one.
*/
int start_rank, end_rank;
/* Work out from where to start and end. */
if (colour == WHITE) {
start_rank = RankConvert(FIRSTRANK + 1);
end_rank = RankConvert(LASTRANK);
}
else {
start_rank = RankConvert(LASTRANK - 1);
end_rank = RankConvert(FIRSTRANK);
}
for (from_r = start_rank; from_r != end_rank; from_r += offset) {
to_r = from_r + offset;
if (board->board[from_r][from_c] == piece_to_move) {
Piece occupant = board->board[to_r][to_c];
if ((occupant != EMPTY) &&
(piece_is_colour(occupant, OPPOSITE_COLOUR(colour)))) {
move_list = append_move_pair(ToCol(from_c), ToRank(from_r),
to_col, ToRank(to_r), move_list);
}
else if (board->EnPassant && (board->ep_rank == ToRank(to_r)) &&
(board->ep_col == ToCol(to_c))) {
move_list = append_move_pair(ToCol(from_c), ToRank(from_r),
to_col, ToRank(to_r), move_list);
}
}
}
}
}
}
return move_list;
}
/* Find knight moves to the given square. */
MovePair *
find_knight_moves(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
unsigned ix;
MovePair *move_list = NULL;
Piece target_piece = MAKE_COLOURED_PIECE(colour, KNIGHT);
/* Pick up pairs of offsets from to_r,to_c to look for a Knight of
* the right colour.
*/
for (ix = 0; ix < 2 * NUM_KNIGHT_MOVES; ix += 2) {
int r = Knight_moves[ix] + to_r;
int c = Knight_moves[ix + 1] + to_c;
if (board->board[r][c] == target_piece) {
move_list = append_move_pair(ToCol(c), ToRank(r), to_col, to_rank, move_list);
}
}
return move_list;
}
/* Find bishop moves to the given square. */
MovePair *
find_bishop_moves(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = NULL;
Piece target_piece = MAKE_COLOURED_PIECE(colour, BISHOP);
/* Pick up pairs of offsets from to_r,to_c to look for a Bishop of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_BISHOP_MOVES; ix += 2) {
int r = to_r, c = to_c;
/* Work backwards from the destination to find a bishop of
* the right colour.
*/
do {
r += Bishop_moves[ix];
c += Bishop_moves[ix + 1];
} while (board->board[r][c] == EMPTY);
if (board->board[r][c] == target_piece) {
move_list = append_move_pair(ToCol(c), ToRank(r), to_col, to_rank, move_list);
}
}
return move_list;
}
/* Find rook moves to the given square. */
MovePair *
find_rook_moves(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = NULL;
Piece target_piece = MAKE_COLOURED_PIECE(colour, ROOK);
/* Pick up pairs of offsets from to_r,to_c to look for a Rook of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_ROOK_MOVES; ix += 2) {
int r = to_r, c = to_c;
/* Work backwards from the destination to find a rook of
* the right colour.
*/
do {
r += Rook_moves[ix];
c += Rook_moves[ix + 1];
} while (board->board[r][c] == EMPTY);
if (board->board[r][c] == target_piece) {
move_list = append_move_pair(ToCol(c), ToRank(r), to_col, to_rank, move_list);
}
}
return move_list;
}
/* Find queen moves to the given square. */
MovePair *
find_queen_moves(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = NULL;
Piece target_piece = MAKE_COLOURED_PIECE(colour, QUEEN);
/* Pick up pairs of offsets from to_r,to_c to look for a Queen of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_QUEEN_MOVES; ix += 2) {
int r = to_r, c = to_c;
/* Work backwards from the destination to find a queen of
* the right colour.
*/
do {
r += Queen_moves[ix];
c += Queen_moves[ix + 1];
} while (board->board[r][c] == EMPTY);
if (board->board[r][c] == target_piece) {
move_list = append_move_pair(ToCol(c), ToRank(r), to_col, to_rank, move_list);
}
}
return move_list;
}
/* Find King moves to the given square. */
MovePair *
find_king_moves(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = NULL;
Piece target_piece = MAKE_COLOURED_PIECE(colour, KING);
/* Stop once the single King is found. */
Boolean found = FALSE;
/* Pick up pairs of offsets from to_r,to_c to look for a King of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_KING_MOVES && !found; ix += 2) {
int r = King_moves[ix] + to_r;
int c = King_moves[ix + 1] + to_c;
if (board->board[r][c] == target_piece) {
move_list = append_move_pair(ToCol(c), ToRank(r), to_col, to_rank, move_list);
found = TRUE;
}
}
if(!found) {
/* Check for possible Chess960 castling moves.
* Represented as a move to the position occupied by
* one of colour's own rooks.
*/
Boolean possible_castling_move = FALSE;
if(colour == WHITE) {
if(to_rank == '1') {
if(to_col == board->WKingCastle || to_col == board->WQueenCastle) {
possible_castling_move = TRUE;
}
}
}
else {
if(to_rank == '8') {
if(to_col == board->BKingCastle || to_col == board->BQueenCastle) {
possible_castling_move = TRUE;
}
}
}
if(possible_castling_move) {
int c = ColConvert(COLBASE);
for(char col = COLBASE; col < COLBASE + BOARDSIZE && !found; col++ ) {
if(board->board[to_r][c] == target_piece) {
move_list = append_move_pair(col, to_rank, to_col, to_rank, move_list);
found = TRUE;
}
else {
c++;
}
}
}
}
return move_list;
}
/* Find pawn moves matching the to_ and from_ information.
* Depending on the input form of the move, some of this will be
* incomplete. For instance: e4 supplies just the to_ information
* whereas cb supplies some from_ and some to_.
*/
static Boolean
find_single_pawn_move(Col from_col, Rank from_rank, Col to_col, Rank to_rank,
Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
int from_r = RankConvert(from_rank);
int from_c = ColConvert(from_col);
/* White pawn moves are offset by +1, Black by -1. */
int offset = COLOUR_OFFSET(colour);
Piece piece_to_move = MAKE_COLOURED_PIECE(colour, PAWN);
Boolean found = FALSE;
if ((to_col != 0) && (to_rank != 0)) {
/* We know the complete destination. */
if (board->board[to_r][to_c] == EMPTY) {
/* Destination must be empty for this form. */
if (board->board[to_r - offset][to_c] == piece_to_move) {
/* MovePair of one square. */
found = TRUE;
}
else if ((board->board[to_r - offset][to_c] == EMPTY) &&
(to_rank == (colour == WHITE ? '4' : '5'))) {
/* Special case of initial two square move. */
if (board->board[to_r - 2 * offset][to_c] == piece_to_move) {
found = TRUE;
}
}
else if (board->EnPassant &&
(board->ep_rank == to_rank) &&
(board->ep_col == to_col)) {
/* Make sure that there is a valid pawn in position. */
if (from_col != 0) {
from_r = to_r - offset;
if (board->board[from_r][from_c] == piece_to_move) {
found = TRUE;
}
}
}
}
else if (piece_is_colour(board->board[to_r][to_c], OPPOSITE_COLOUR(colour))) {
/* Capture on the destination square. */
if (from_col != 0) {
/* We know the from column. */
from_r = to_r - offset;
if (board->board[from_r][from_c] == piece_to_move) {
found = TRUE;
}
}
}
else {
/* We have no move. */
}
}
else if ((from_col != 0) && (to_col != 0)) {
/* Should be a diagonal capture. */
if (((from_col + 1) != to_col) && ((from_col - 1) != to_col)) {
/* Inconsistent information. */
}
else {
if (from_rank != 0) {
/* We have complete source information. Check its
* veracity.
*/
to_r = from_r - offset;
if (board->board[from_r][from_c] == piece_to_move) {
Piece occupant = board->board[to_r][to_c];
if ((occupant != EMPTY) &&
(piece_is_colour(occupant, OPPOSITE_COLOUR(colour)))) {
found = TRUE;
}
else if (board->EnPassant && (board->ep_rank == ToRank(to_r)) &&
(board->ep_col == ToCol(to_c))) {
found = TRUE;
}
}
}
else {
/* We must search the from_col and to_col for appropriate
* combinations. There may be more than one.
*/
int start_rank, end_rank;
/* Work out from where to start and end. */
if (colour == WHITE) {
start_rank = RankConvert(FIRSTRANK + 1);
end_rank = RankConvert(LASTRANK);
}
else {
start_rank = RankConvert(LASTRANK - 1);
end_rank = RankConvert(FIRSTRANK);
}
for (from_r = start_rank; from_r != end_rank && !found; from_r += offset) {
to_r = from_r + offset;
if (board->board[from_r][from_c] == piece_to_move) {
Piece occupant = board->board[to_r][to_c];
if ((occupant != EMPTY) &&
(piece_is_colour(occupant, OPPOSITE_COLOUR(colour)))) {
found = TRUE;
}
else if (board->EnPassant && (board->ep_rank == ToRank(to_r)) &&
(board->ep_col == ToCol(to_c))) {
found = TRUE;
}
}
}
}
}
}
return found;
}
/* Find knight moves to the given square. */
static Boolean
find_single_knight_move(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
unsigned ix;
Piece target_piece = MAKE_COLOURED_PIECE(colour, KNIGHT);
Boolean found = FALSE;
/* Pick up pairs of offsets from to_r,to_c to look for a Knight of
* the right colour.
*/
for (ix = 0; ix < 2 * NUM_KNIGHT_MOVES && !found; ix += 2) {
int r = Knight_moves[ix] + to_r;
int c = Knight_moves[ix + 1] + to_c;
if (board->board[r][c] == target_piece) {
found = TRUE;
}
}
return found;
}
/* See if there is at least one bishop move to the given square. */
static Boolean
find_single_bishop_move(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
Piece target_piece = MAKE_COLOURED_PIECE(colour, BISHOP);
Boolean found = FALSE;
/* Pick up pairs of offsets from to_r,to_c to look for a Bishop of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_BISHOP_MOVES && !found; ix += 2) {
int r = to_r, c = to_c;
/* Work backwards from the destination to find a bishop of
* the right colour.
*/
do {
r += Bishop_moves[ix];
c += Bishop_moves[ix + 1];
} while (board->board[r][c] == EMPTY);
if (board->board[r][c] == target_piece) {
found = TRUE;
}
}
return found;
}
/* Find rook moves to the given square. */
static Boolean
find_single_rook_move(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
Piece target_piece = MAKE_COLOURED_PIECE(colour, ROOK);
Boolean found = FALSE;
/* Pick up pairs of offsets from to_r,to_c to look for a Rook of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_ROOK_MOVES && !found; ix += 2) {
int r = to_r, c = to_c;
/* Work backwards from the destination to find a rook of
* the right colour.
*/
do {
r += Rook_moves[ix];
c += Rook_moves[ix + 1];
} while (board->board[r][c] == EMPTY);
if (board->board[r][c] == target_piece) {
found = TRUE;
}
}
return found;
}
/* Find queen moves to the given square. */
static Boolean
find_single_queen_move(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
Piece target_piece = MAKE_COLOURED_PIECE(colour, QUEEN);
Boolean found = FALSE;
/* Pick up pairs of offsets from to_r,to_c to look for a Queen of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_QUEEN_MOVES && !found; ix += 2) {
int r = to_r, c = to_c;
/* Work backwards from the destination to find a queen of
* the right colour.
*/
do {
r += Queen_moves[ix];
c += Queen_moves[ix + 1];
} while (board->board[r][c] == EMPTY);
if (board->board[r][c] == target_piece) {
found = TRUE;
}
}
return found;
}
/* Find King moves to the given square. */
static Boolean
find_single_king_move(Col to_col, Rank to_rank, Colour colour, const Board *board)
{
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
Piece target_piece = MAKE_COLOURED_PIECE(colour, KING);
/* Store once the single King is found. */
Boolean found = FALSE;
/* Pick up pairs of offsets from to_r,to_c to look for a King of
* the right colour.
*/
for (unsigned ix = 0; ix < 2 * NUM_KING_MOVES && !found; ix += 2) {
int r = King_moves[ix] + to_r;
int c = King_moves[ix + 1] + to_c;
if (board->board[r][c] == target_piece) {
found = TRUE;
}
}
return found;
}
/* Return true if the king of the given colour is
* in check on the board, FALSE otherwise.
*/
CheckStatus
king_is_in_check(const Board *board, Colour king_colour)
{
/* Assume that there is a check. */
CheckStatus in_check = CHECK;
Col king_col;
Rank king_rank;
Colour opponent_colour = OPPOSITE_COLOUR(king_colour);
/* Find out where the king is now. */
if (king_colour == WHITE) {
king_col = board->WKingCol;
king_rank = board->WKingRank;
}
else {
king_col = board->BKingCol;
king_rank = board->BKingRank;
}
/* Try and find one move that leaves this king in check.
* There is probably an optimal order for these tests but
* I don't know for sure what it is.
* Try the pieces with greatest mobility first.
*
* @@@ NB: Since a single move would be enough, this could
* be made more efficient.
*/
if (find_single_queen_move(king_col, king_rank,
opponent_colour, board)) {
/* King is in check from a queen. */
}
else if (find_single_rook_move(king_col, king_rank,
opponent_colour, board)) {
/* King is in check from a rook. */
}
else if (find_single_bishop_move(king_col, king_rank,
opponent_colour, board)) {
/* King is in check from a bishop. */
}
else if (find_single_knight_move(king_col, king_rank,
opponent_colour, board)) {
/* King is in check from a knight. */
}
else if ((king_col != LASTCOL) &&
(find_single_pawn_move(king_col + 1, 0, king_col, king_rank,
opponent_colour, board))) {
/* King is in check from a pawn to its right. */
}
else if ((king_col != FIRSTCOL) &&
(find_single_pawn_move(king_col - 1, 0, king_col, king_rank,
opponent_colour, board))) {
/* King is in check from a pawn. to its left */
}
else if (find_single_king_move(king_col, king_rank,
opponent_colour, board)) {
/* King is in check from a king. */
}
else {
/* King is safe. */
in_check = NOCHECK;
}
return in_check;
}
/* possibles contains a list of possible moves of piece.
* NB: Elements of possibles might be freed by this function
* so it is invalidated by the call.
*
* This function should exclude all of those moves of this piece
* which leave its own king in check.
* The list of remaining legal moves is returned as result.
* This function operates by looking for at least one reply by the
* opponent that could capture the king of the given colour.
* Only one such move needs to be found to invalidate one of the
* possible moves.
*/
MovePair *
exclude_checks(Piece piece, Colour colour, MovePair *possibles, const Board *board)
{ /* As this function is not called recursively, it should be
* safe to retain a single copy of the board on the stack without risking
* overflow. This has been a problem in the past with the PC version.
*/
Board copy_board;
MovePair *valid_move_list = NULL;
MovePair *move;
/* For each possible move, make the move and see if it leaves the king
* in check.
*/
for (move = possibles; move != NULL;) {
/* Take a copy of the board before playing this next move. */
copy_board = *board;
make_move(UNKNOWN_MOVE, move->from_col, move->from_rank,
move->to_col, move->to_rank, piece, colour, ©_board);
if (king_is_in_check(©_board, colour) != NOCHECK) {
MovePair *illegal_move = move;
move = move->next;
/* Free the illegal move. */
free_move_pair(illegal_move);
}
else {
/* King is safe and the move may be kept. */
MovePair *legal_move = move;
move = move->next;
legal_move->next = valid_move_list;
valid_move_list = legal_move;
}
}
return valid_move_list;
}
/* We must exclude the possibility of the king passing
* through check, or castling out of it.
*/
static Boolean
exclude_castling_across_checks(Col king_start_col, Col king_end_col,
Colour colour, const Board *board)
{
Boolean Ok = TRUE;
MovePair *move = malloc_move();
Rank rank = (colour == WHITE) ? FIRSTRANK : LASTRANK;
int direction = king_end_col >= king_start_col ? 1 : -1;
Col boundary = king_end_col + direction;
Col to_col;
/* Start where we are, because you can't castle out of check. */
for (to_col = king_start_col; (to_col != boundary) && Ok; to_col += direction) {
move->from_col = king_start_col;
move->from_rank = rank;
move->to_col = to_col;
move->to_rank = rank;
move = exclude_checks(KING, colour, move, board);
if (move == NULL) {
Ok = FALSE;
}
}
if (move != NULL) {
free_move_pair(move);
}
return Ok;
}
/* Possibles is a list of possible moves of piece.
* Exclude all of those that either leave the king in check
* or those excluded by non-null information in from_col or from_rank.
*/
static MovePair *
exclude_moves(Piece piece, Colour colour, Col from_col, Rank from_rank,
MovePair *possibles, const Board *board)
{
MovePair *move_list = NULL;
/* See if we have disambiguating from_ information. */
if ((from_col != 0) || (from_rank != 0)) {
MovePair *move, *temp;
for (move = possibles; move != NULL;) {
Boolean excluded = FALSE;
if (from_col != 0) {
/* The from_col is specified. */
if (move->from_col != from_col) {
excluded = TRUE;
}
}
if ((from_rank != 0) && !excluded) {
if (move->from_rank != from_rank) {
excluded = TRUE;
}
}
temp = move;
move = move->next;
if (!excluded) {
/* Add it to the list of possibles. */
temp->next = move_list;
move_list = temp;
}
else {
/* Discard it. */
free_move_pair(temp);
}
}
}
else {
/* Everything is still possible. */
move_list = possibles;
}
if (move_list != NULL) {
move_list = exclude_checks(piece, colour, move_list, board);
}
return move_list;
}
/* Make a pawn move.
* En-passant information in the original move text is not currently used
* to disambiguate pawn moves. E.g. with Black pawns on c4 and c5 after
* White move 1. d4 a reply 1... cdep will be rejected as ambiguous.
*/
static Boolean
pawn_move(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
/* Find the basic set of moves that match the move_details criteria. */
MovePair *move_list;
Boolean Ok = TRUE;
/* Make sure that the col values are consistent with a pawn move. */
if ((from_col != '\0') && (to_col != '\0') &&
/* Forward. */
(from_col != to_col) &&
/* Capture. */
(from_col != (to_col + 1)) && (from_col != (to_col - 1))) {
/* Inconsistent. */
Ok = FALSE;
}
else if ((move_list = find_pawn_moves(from_col, from_rank, to_col, to_rank,
colour, board)) == NULL) {
Ok = FALSE;
}
else {
/* Exclude any moves that leave the king in check, or are disambiguate
* by from_information.
*/
move_list = exclude_moves(PAWN, colour, from_col, from_rank, move_list, board);
if (move_list != NULL) {
if (move_list->next == NULL) {
/* Unambiguous move. Some pawn moves will have supplied
* incomplete destinations (e.g. cd as opposed to cxd4)
* so pick up both from_ and to_ information.
*/
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
move_details->to_col = move_list->to_col;
move_details->to_rank = move_list->to_rank;
}
else {
/* Ambiguous. */
Ok = FALSE;
}
free_move_pair_list(move_list);
}
else {
/* Excluded. */
Ok = FALSE;
}
}
return Ok;
}
/* Make a pawn move that involves an explicit promotion to promoted_piece. */
static Boolean
promote(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
MovePair *move_list;
Boolean Ok = FALSE;
if (to_rank == '\0') {
/* We can fill this in. */
if (colour == WHITE) {
to_rank = LASTRANK;
}
else {
to_rank = FIRSTRANK;
}
}
/* Now check that to_rank makes sense for the given colour. */
if (((colour == WHITE) && (to_rank != LASTRANK)) ||
((colour == BLACK) && (to_rank != FIRSTRANK))) {
fprintf(GlobalState.logfile, "Illegal pawn promotion to %c%c\n", to_col, to_rank);
}
else {
move_list = find_pawn_moves(from_col, from_rank, to_col, to_rank, colour, board);
if (move_list != NULL) {
if (move_list->next == NULL) {
/* Unambiguous move. Some pawn moves will have supplied
* incomplete destinations (e.g. cd as opposed to cxd8)
* so pick up both from_ and to_ information.
*/
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
move_details->to_col = move_list->to_col;
move_details->to_rank = move_list->to_rank;
Ok = TRUE;
}
else {
fprintf(GlobalState.logfile, "Ambiguous pawn move to %c%c\n", to_col, to_rank);
}
free_move_pair_list(move_list);
}
else {
fprintf(GlobalState.logfile, "Illegal pawn promotion to %c%c\n", to_col, to_rank);
}
}
return Ok;
}
/* Make a knight move, indicated by move_details.
* This may result in further information being added to move_details.
*/
static Boolean
knight_move(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = find_knight_moves(to_col, to_rank, colour, board);
/* Assume everything will be ok. */
Boolean Ok = TRUE;
move_list = exclude_moves(KNIGHT, colour, from_col, from_rank, move_list, board);
if (move_list == NULL) {
fprintf(GlobalState.logfile, "No knight move possible to %c%c.\n", to_col, to_rank);
Ok = FALSE;
}
else if (move_list->next == NULL) {
/* Only one possible. Check for legality. */
Piece occupant = board->board[to_r][to_c];
if ((occupant == EMPTY) || piece_is_colour(occupant, OPPOSITE_COLOUR(colour))) {
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
}
else {
fprintf(GlobalState.logfile, "Knight destination square %c%c is illegal.\n",
to_col, to_rank);
Ok = FALSE;
}
free_move_pair(move_list);
}
else {
fprintf(GlobalState.logfile, "Ambiguous knight move to %c%c.\n", to_col, to_rank);
free_move_pair_list(move_list);
Ok = FALSE;
}
return Ok;
}
/* Make a bishop move, indicated by move_details.
* This may result in further information being added to move_details.
*/
static Boolean
bishop_move(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = find_bishop_moves(to_col, to_rank, colour, board);
/* Assume that it is ok. */
Boolean Ok = TRUE;
move_list = exclude_moves(BISHOP, colour, from_col, from_rank, move_list, board);
if (move_list == NULL) {
fprintf(GlobalState.logfile, "No bishop move possible to %c%c.\n", to_col, to_rank);
Ok = FALSE;
}
else if (move_list->next == NULL) {
/* Only one possible. Check for legality. */
Piece occupant = board->board[to_r][to_c];
if ((occupant == EMPTY) || piece_is_colour(occupant, OPPOSITE_COLOUR(colour))) {
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
}
else {
fprintf(GlobalState.logfile, "Bishop's destination square %c%c is illegal.\n",
to_col, to_rank);
Ok = FALSE;
}
free_move_pair(move_list);
}
else {
fprintf(GlobalState.logfile, "Ambiguous bishop move to %c%c.\n", to_col, to_rank);
free_move_pair_list(move_list);
Ok = FALSE;
}
return Ok;
}
/* Make a rook move, indicated by move_details.
* This may result in further information being added to move_details.
*/
static Boolean
rook_move(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = find_rook_moves(to_col, to_rank, colour, board);
/* Assume that it is ok. */
Boolean Ok = TRUE;
if (move_list == NULL) {
fprintf(GlobalState.logfile, "No rook move possible to %c%c.\n", to_col, to_rank);
Ok = FALSE;
}
else {
move_list = exclude_moves(ROOK, colour, from_col, from_rank, move_list, board);
if (move_list == NULL) {
fprintf(GlobalState.logfile, "Indicated rook move is excluded.\n");
Ok = FALSE;
}
else if (move_list->next == NULL) {
/* Only one possible. Check for legality. */
Piece occupant = board->board[to_r][to_c];
if ((occupant == EMPTY) || piece_is_colour(occupant, OPPOSITE_COLOUR(colour))) {
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
}
else {
fprintf(GlobalState.logfile,
"Rook's destination square %c%c is illegal.\n",
to_col, to_rank);
Ok = FALSE;
}
free_move_pair(move_list);
}
else {
fprintf(GlobalState.logfile, "Ambiguous rook move to %c%c.\n", to_col, to_rank);
free_move_pair_list(move_list);
Ok = FALSE;
}
}
return Ok;
}
/* Find a queen move indicated by move_details.
* This may result in further information being added to move_details.
*/
static Boolean
queen_move(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
MovePair *move_list = find_queen_moves(to_col, to_rank, colour, board);
/* Assume that it is ok. */
Boolean Ok = TRUE;
move_list = exclude_moves(QUEEN, colour, from_col, from_rank, move_list, board);
if (move_list == NULL) {
fprintf(GlobalState.logfile, "No queen move possible to %c%c.\n", to_col, to_rank);
Ok = FALSE;
}
else if (move_list->next == NULL) {
/* Only one possible. Check for legality. */
Piece occupant = board->board[to_r][to_c];
if ((occupant == EMPTY) || piece_is_colour(occupant, OPPOSITE_COLOUR(colour))) {
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
}
else {
fprintf(GlobalState.logfile, "Queen's destination square %c%c is illegal.\n",
to_col, to_rank);
Ok = FALSE;
}
free_move_pair(move_list);
}
else {
fprintf(GlobalState.logfile, "Ambiguous queen move to %c%c.\n", to_col, to_rank);
free_move_pair_list(move_list);
Ok = FALSE;
}
return Ok;
}
/* Find the column where the King of the given colour is.
* The King is assumed to be in position to castle.
* This supports Chess960 positioning.
* Return \0 if the King cannot be found.
*/
Col
find_castling_king_col(Colour colour, const Board *board)
{
Boolean found = FALSE;
int king_r;
Piece coloured_king = MAKE_COLOURED_PIECE(colour, KING);
if (colour == WHITE) {
king_r = RankConvert(FIRSTRANK);
}
else {
king_r = RankConvert(LASTRANK);
}
/* Short-circuit check for standard chess. */
if ((board->board[king_r][ColConvert('e')] == coloured_king)) {
return 'e';
}
else {
/* Search elsewhere. */
Col king_col = FIRSTCOL;
while (!found && king_col <= LASTCOL) {
if ((board->board[king_r][ColConvert(king_col)] == coloured_king)) {
found = TRUE;
}
else {
king_col++;
}
}
if (found) {
return king_col;
}
else {
return '\0';
}
}
}
/* Find the column where the Rook of the given colour is to execute
* the indicated castling move (KINGSIDE_CASTLE or QUEENSIDE_CASTLE).
* The Room is assumed to be in position to castle.
* This supports Chess960 positioning.
* Return \0 if the Rook cannot be found.
*/
Col
find_castling_rook_col(Colour colour, const Board *board, MoveClass castling)
{
int rook_r;
Piece coloured_rook = MAKE_COLOURED_PIECE(colour, ROOK);
Col rook_col;
if (colour == WHITE) {
rook_r = RankConvert(FIRSTRANK);
rook_col = castling == KINGSIDE_CASTLE ? board->WKingCastle : board->WQueenCastle;
}
else {
rook_r = RankConvert(LASTRANK);
rook_col = castling == KINGSIDE_CASTLE ? board->BKingCastle : board->BQueenCastle;
}
if (rook_col == '\0') {
return '\0';
}
else if ((board->board[rook_r][ColConvert(rook_col)] == coloured_rook)) {
return rook_col;
}
else {
return '\0';
}
}
/* Can colour castle in the indicated direction? */
static Boolean
can_castle(MoveClass castling, Colour colour, const Board *board)
{ /* Assume failure. */
Boolean Ok = FALSE;
Rank king_rank;
Col king_col;
Col rook_col;
/* Make sure the rights are available. */
if (colour == WHITE) {
king_rank = FIRSTRANK;
Ok = (castling == KINGSIDE_CASTLE && board->WKingCastle != '\0') ||
(castling == QUEENSIDE_CASTLE && board->WQueenCastle != '\0');
}
else {
king_rank = LASTRANK;
Ok = (castling == KINGSIDE_CASTLE && board->BKingCastle != '\0') ||
(castling == QUEENSIDE_CASTLE && board->BQueenCastle != '\0');
}
if (!Ok) {
return FALSE;
}
/* Find the king. */
king_col = find_castling_king_col(colour, board);
/* Find where the appropriate rook is. */
rook_col = find_castling_rook_col(colour, board, castling);
Ok = king_col != '\0' && rook_col != '\0';
if (!Ok) {
return FALSE;
}
/* It is potentially permitted. */
int king_c = ColConvert(king_col);
int king_r = RankConvert(king_rank);
/* The king's destination column. */
int king_final_c = castling == KINGSIDE_CASTLE ? ColConvert('g') : ColConvert('c');
int rook_c = ColConvert(rook_col);
int rook_final_c = castling == KINGSIDE_CASTLE ? ColConvert('f') : ColConvert('d');
if (king_final_c != king_c) {
/* Check for clear spaces for the king. */
int direction = king_final_c > king_c ? 1 : -1;
int next_c = king_c + direction;
Boolean check_again = TRUE;
while (Ok && check_again) {
check_again = next_c != king_final_c;
if (board->board[king_r][next_c] == EMPTY) {
}
else if (next_c == rook_c) {
/* Permitted. */
}
else {
/* Blocked. */
Ok = FALSE;
}
next_c += direction;
}
}
if (Ok && rook_final_c != rook_c) {
/* Check for clear spaces for the rook. */
int direction = rook_final_c > rook_c ? 1 : -1;
int next_c = rook_c + direction;
Boolean check_again = TRUE;
while (Ok && check_again) {
check_again = next_c != rook_final_c;
if (board->board[king_r][next_c] == EMPTY) {
/* Ok. */
}
else if (next_c == king_c) {
/* Permitted. */
}
else {
/* Blocked. */
Ok = FALSE;
}
next_c += direction;
}
}
if (Ok) {
if (exclude_castling_across_checks(king_col, castling == KINGSIDE_CASTLE ? 'g' : 'c', colour, board)) {
Ok = TRUE;
}
else {
/* Can't castle across check. */
Ok = FALSE;
}
}
else {
/* Kingside castling is blocked. */
}
return Ok;
}
/* Castle king side. */
static Boolean
kingside_castle(Move *move_details, Colour colour, Board *board)
{
Boolean Ok;
if (can_castle(KINGSIDE_CASTLE, colour, board)) {
Rank rank = colour == WHITE ? FIRSTRANK : LASTRANK;
move_details->from_col = find_castling_king_col(colour, board);
move_details->from_rank = rank;
/* @@@ Chess960 has different requirements on output.
* to_col should be the column of the corresponding Rook
* but is used within the program as the true target of the King..
*/
move_details->to_col = 'g';
move_details->to_rank = rank;
Ok = TRUE;
}
else {
fprintf(GlobalState.logfile, "Kingside castling is forbidden to %s.\n",
colour == WHITE ? "White" : "Black");
Ok = FALSE;
}
return Ok;
}
static Boolean
queenside_castle(Move *move_details, Colour colour, Board *board)
{
Boolean Ok;
if (can_castle(QUEENSIDE_CASTLE, colour, board)) {
Rank rank = colour == WHITE ? FIRSTRANK : LASTRANK;
move_details->from_col = find_castling_king_col(colour, board);
move_details->from_rank = rank;
/* @@@ Chess960 has different requirements on output.
* to_col should be the column of the corresponding Rook
* but is used within the program as the true target of the King..
*/
move_details->to_col = 'c';
move_details->to_rank = rank;
Ok = TRUE;
}
else {
fprintf(GlobalState.logfile, "Queenside castling is forbidden to %s.\n",
colour == WHITE ? "White" : "Black");
Ok = FALSE;
}
return Ok;
}
/* Move the king according to move_details.
* This may result in further information being added to move_details.
*/
static Boolean
king_move(Move *move_details, Colour colour, Board *board)
{
Col from_col = move_details->from_col;
Rank from_rank = move_details->from_rank;
Col to_col = move_details->to_col;
Rank to_rank = move_details->to_rank;
/* Find all possible king moves to the destination squares. */
MovePair *move_list = find_king_moves(to_col, to_rank, colour, board);
/* Assume that it is ok. */
Boolean Ok = TRUE;
if (move_list == NULL) {
fprintf(GlobalState.logfile, "No king move possible to %c%c.\n",
to_col, to_rank);
Ok = FALSE;
}
else {
/* Check for legality. */
int to_r = RankConvert(to_rank);
int to_c = ColConvert(to_col);
Piece occupant = board->board[to_r][to_c];
if(occupant == MAKE_COLOURED_PIECE(colour, ROOK)) {
/* Possible Chess960 castling move. */
/* @@@ Strictly speaking, we should check that the Variant tag
* is set for this to be a sensible move.
*/
Boolean kingside;
if(colour == WHITE) {
kingside = move_list->to_col == board->WKingCastle;
}
else {
kingside = move_list->to_col == board->BKingCastle;
}
if(kingside) {
move_details->class = KINGSIDE_CASTLE;
Ok = kingside_castle(move_details, colour, board);
}
else {
move_details->class = QUEENSIDE_CASTLE;
Ok = queenside_castle(move_details, colour, board);
}
}
else {
/* Exclude disambiguated and illegal moves. */
move_list = exclude_moves(KING, colour, from_col, from_rank, move_list, board);
if(move_list == NULL) {
fprintf(GlobalState.logfile, "No king move possible to %c%c.\n",
to_col, to_rank);
Ok = FALSE;
}
else if (occupant == EMPTY) {
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
}
else if(piece_is_colour(occupant, OPPOSITE_COLOUR(colour))) {
move_details->from_col = move_list->from_col;
move_details->from_rank = move_list->from_rank;
}
else {
fprintf(GlobalState.logfile, "King's destination square %c%c is illegal.\n",
to_col, to_rank);
Ok = FALSE;
}
}
if(move_list != NULL) {
free_move_pair(move_list);
}
}
return Ok;
}
/* Try to complete the full set of move information for
* move details.
* In the process, several fields of move_details are modified
* as accurate information is determined about the effect of the move.
* The from_ and to_ fields are completed, class may be refined, and
* captured_piece is filled in.
* The purpose of this is to make it possible to call apply_move with
* a full set of information on the move.
* In addition, once determine_move_details has done its job,
* it should be possible to use the information to reconstruct the effect
* of a move in reverse, if necessary. This would be required by a display
* program, for instance.
* NB: this function does not determine whether or not the move gives check.
*/
Boolean
determine_move_details(Colour colour, Move *move_details, Board *board)
{
Boolean Ok = FALSE;
if (move_details == NULL) {
/* Shouldn't happen. */
fprintf(GlobalState.logfile,
"Internal error: Empty move details in apply_move.\n");
}
else if (move_details->move[0] == '\0') {
/* Shouldn't happen. */
fprintf(GlobalState.logfile,
"Internal error: Null move string in apply_move.\n");
}
else if (move_details->class == NULL_MOVE) {
/* Non-standard PGN.
* Nothing more to be done.
*/
Ok = TRUE;
}
else {
/* We have something -- normal case. */
const unsigned char *move = move_details->move;
MoveClass class = move_details->class;
Boolean move_handled = FALSE;
/* A new piece on promotion. */
move_details->promoted_piece = EMPTY;
/* Because the decoding process did not have the current board
* position available, trap apparent pawn moves that maybe something
* else.
* At the moment, only do this when full positional information is
* available.
*/
if ((class == PAWN_MOVE) &&
(move_details->from_col != 0) &&
(move_details->from_rank != 0) &&
(move_details->to_col != 0) &&
(move_details->to_rank != 0)) {
/* Try to work out its details and handle it below. */
move_details = decode_algebraic(move_details, board);
/* Pick up the new class. */
class = move_details->class;
}
/* Deal with apparent pawn moves first. */
if ((class == PAWN_MOVE) || (class == ENPASSANT_PAWN_MOVE) ||
(class == PAWN_MOVE_WITH_PROMOTION)) {
move_details->piece_to_move = PAWN;
/* Fill in any promotional details. */
if (class == PAWN_MOVE) {
/* Check for implicit promotion. */
if (((move_details->to_rank == LASTRANK) && (colour == WHITE)) ||
((move_details->to_rank == FIRSTRANK) && (colour == BLACK))) {
/*
* @@@ Up to version 17-35 we didn't alter move_details->class
* because we didn't add the promoted piece to the class.
* From version 17-36 we do.
*/
class = PAWN_MOVE_WITH_PROMOTION;
move_details->class = PAWN_MOVE_WITH_PROMOTION;
/* Since the move string doesn't tell us, we have to
* assume a queen.
*/
move_details->promoted_piece = QUEEN;
}
}
else if (class == ENPASSANT_PAWN_MOVE) {
/* No promotion possible. */
}
else {
/* Pick up the promoted_piece from the move string. */
size_t last_char = strlen((const char *) move) - 1;
/* Skip any check character. */
while (is_check(move[last_char])) {
last_char--;
}
/* Pick up what kind of piece it is. */
move_details->promoted_piece = is_piece(&move[last_char]);
switch (move_details->promoted_piece) {
case QUEEN: case ROOK: case BISHOP: case KNIGHT:
/* Ok. */
break;
default:
/* djb From v17-27 allow a trailing 'b' as a Bishop promotion. */
if (move[last_char] == 'b') {
move_details->promoted_piece = BISHOP;
}
else {
fprintf(GlobalState.logfile,
"Unknown piece in promotion %s\n", move);
move_details->promoted_piece = EMPTY;
}
break;
}
}
if ((class == PAWN_MOVE) || (class == ENPASSANT_PAWN_MOVE)) {
/* Check out the details and confirm the move's class. */
Ok = pawn_move(move_details, colour, board);
/* See if we are dealing with and En Passant move. */
if (Ok) {
if ((board->EnPassant) &&
(board->ep_rank == move_details->to_rank) &&
(board->ep_col == move_details->to_col)) {
move_details->class = class = ENPASSANT_PAWN_MOVE;
}
else {
/* Just in case the original designation was incorrect. */
move_details->class = class = PAWN_MOVE;
}
move_handled = TRUE;
}
}
else if (class == PAWN_MOVE_WITH_PROMOTION) {
/* Handle a move involving promotion. */
Ok = promote(move_details, colour, board);
move_handled = TRUE;
}
else {
/* Shouldn't get here. */
}
if (!move_handled) {
/* We failed to find the move, for some reason. */
/* See if it might be a Bishop move with a lower case 'b'. */
if (move_details->move[0] == 'b') {
/* See if we can find a valid Bishop alternative for it. */
unsigned char *alternative_move =
(unsigned char *) malloc_or_die(strlen((char *) move_details->move) + 1);
strcpy((char *) alternative_move,
(const char *) move_details->move);
*alternative_move = 'B';
Move *alternative = decode_move((unsigned char *) alternative_move);
(void) free((void *) alternative_move);
if (alternative != NULL) {
Ok = bishop_move(alternative, colour, board);
if (Ok) {
/* Copy the relevant details. */
move_details->class = alternative->class;
move_details->from_col = alternative->from_col;
move_details->from_rank = alternative->from_rank;
move_details->to_col = alternative->to_col;
move_details->to_rank = alternative->to_rank;
move_details->piece_to_move =
alternative->piece_to_move;
free_move_list(alternative);
move_handled = TRUE;
}
}
}
}
}
if (!move_handled) {
/* Pick up any moves not handled as pawn moves.
* This includes algebraic moves that were originally assumed to
* be pawn moves.
*/
switch (class) {
case PAWN_MOVE:
case ENPASSANT_PAWN_MOVE:
case PAWN_MOVE_WITH_PROMOTION:
/* No more tries left. */
break;
case PIECE_MOVE:
switch (move_details->piece_to_move) {
case KING:
Ok = king_move(move_details, colour, board);
break;
case QUEEN:
Ok = queen_move(move_details, colour, board);
break;
case ROOK:
Ok = rook_move(move_details, colour, board);
break;
case KNIGHT:
Ok = knight_move(move_details, colour, board);
break;
case BISHOP:
Ok = bishop_move(move_details, colour, board);
break;
default:
Ok = FALSE;
fprintf(GlobalState.logfile, "Unknown piece move %s\n", move);
break;
}
break;
case KINGSIDE_CASTLE:
move_details->piece_to_move = KING;
Ok = kingside_castle(move_details, colour, board);
break;
case QUEENSIDE_CASTLE:
move_details->piece_to_move = KING;
Ok = queenside_castle(move_details, colour, board);
break;
case UNKNOWN_MOVE:
Ok = FALSE;
break;
default:
fprintf(GlobalState.logfile,
"Unknown move class in determine_move_details(%d).\n",
move_details->class);
break;
}
}
if (Ok) {
/* Fill in the remaining items in move_details. */
int to_r = RankConvert(move_details->to_rank);
int to_c = ColConvert(move_details->to_col);
/* Keep track of any capture. */
if (board->board[to_r][to_c] != EMPTY) {
move_details->captured_piece =
EXTRACT_PIECE(board->board[to_r][to_c]);
}
else if (move_details->class == ENPASSANT_PAWN_MOVE) {
move_details->captured_piece = PAWN;
}
else {
move_details->captured_piece = EMPTY;
}
}
}
if(!Ok) {
/* Treat invalid moves as being of an unknown move class. */
move_details->class = UNKNOWN_MOVE;
}
return Ok;
}
/* Generate a list of moves of a king or knight from from_col,from_rank.
* This does not include castling for the king, because it is used
* by the code that looks for ways to escape from check for which
* castling is illegal, of course.
*/
static MovePair *
generate_single_moves(Colour colour, Piece piece,
const Board *board, Col from_col, Rank from_rank)
{
int from_r = RankConvert(from_rank);
int from_c = ColConvert(from_col);
unsigned ix;
MovePair *moves = NULL;
Colour target_colour = OPPOSITE_COLOUR(colour);
unsigned num_directions = piece == KING ? NUM_KING_MOVES : NUM_KNIGHT_MOVES;
const int *Piece_moves = piece == KING ? King_moves : Knight_moves;
/* Pick up pairs of offsets from from_r,from_c to look for an
* EMPTY square, or one containing a piece of OPPOSITE_COLOUR(colour).
*/
for (ix = 0; ix < 2 * num_directions; ix += 2) {
int r = Piece_moves[ix] + from_r;
int c = Piece_moves[ix + 1] + from_c;
Piece occupant = board->board[r][c];
Boolean Ok = FALSE;
if (occupant == OFF) {
/* Not a valid move. */
}
else if (board->board[r][c] == EMPTY) {
Ok = TRUE;
}
else if (EXTRACT_COLOUR(occupant) == target_colour) {
Ok = TRUE;
}
else {
}
if (Ok) {
/* Fill in the details, and add it to the list. */
moves = append_move_pair(from_col, from_rank, ToCol(c), ToRank(r), moves);
}
}
if (moves != NULL) {
moves = exclude_checks(piece, colour, moves, board);
}
return moves;
}
/* Generate a list of moves of a queen, rook or bishop from
* from_col,from_rank.
*/
static MovePair *
generate_multiple_moves(Colour colour, Piece piece,
const Board *board, Col from_col, Rank from_rank)
{
int from_r = RankConvert(from_rank);
int from_c = ColConvert(from_col);
unsigned ix;
MovePair *moves = NULL;
Colour target_colour = OPPOSITE_COLOUR(colour);
unsigned num_directions = piece == QUEEN ? NUM_QUEEN_MOVES :
piece == ROOK ? NUM_ROOK_MOVES : NUM_BISHOP_MOVES;
const int *Piece_moves = piece == QUEEN ? Queen_moves :
piece == ROOK ? Rook_moves : Bishop_moves;
/* Pick up pairs of offsets from from_r,from_c to look for an
* EMPTY square, or one containing a piece of OPPOSITE_COLOUR(colour).
*/
for (ix = 0; ix < 2 * num_directions; ix += 2) {
int r = Piece_moves[ix] + from_r;
int c = Piece_moves[ix + 1] + from_c;
Piece occupant = board->board[r][c];
/* Include EMPTY squares as possible moves. */
while (occupant == EMPTY) {
/* Fill in the details, and add it to the list. */
moves = append_move_pair(from_col, from_rank, ToCol(c), ToRank(r), moves);
/* Move on to the next square in this direction. */
r += Piece_moves[ix];
c += Piece_moves[ix + 1];
occupant = board->board[r][c];
}
/* We have come up against an obstruction. */
if (occupant == OFF) {
/* Not a valid move. */
}
else if (EXTRACT_COLOUR(occupant) == target_colour) {
moves = append_move_pair(from_col, from_rank, ToCol(c), ToRank(r), moves);
}
else {
/* Should be a piece of our own colour. */
}
}
if (moves != NULL) {
moves = exclude_checks(piece, colour, moves, board);
}
return moves;
}
/* Generate a list of moves of a pawn from from_col,from_rank. */
static MovePair *
generate_pawn_moves(Colour colour, const Board *board, Col from_col, Rank from_rank)
{
MovePair *moves = NULL;
Piece piece = PAWN;
Colour target_colour = OPPOSITE_COLOUR(colour);
/* Determine the direction in which a pawn can move. */
int offset = colour == WHITE ? 1 : -1;
int to_r, to_c = ColConvert(from_col);
/* Try single step ahead. */
to_r = RankConvert(from_rank) + offset;
if (board->board[to_r][to_c] == EMPTY) {
/* Fill in the details, and add it to the list. */
moves = append_move_pair(from_col, from_rank, ToCol(to_c), ToRank(to_r), moves);
if (((colour == WHITE) && (from_rank == FIRSTRANK + 1)) ||
((colour == BLACK) && (from_rank == LASTRANK - 1))) {
/* Try two steps. */
to_r = RankConvert(from_rank) + 2 * offset;
if (board->board[to_r][to_c] == EMPTY) {
moves = append_move_pair(from_col, from_rank, ToCol(to_c), ToRank(to_r), moves);
}
}
}
/* Try to left. */
to_r = RankConvert(from_rank) + offset;
to_c = ColConvert(from_col) - 1;
if (board->board[to_r][to_c] == OFF) {
}
else if (board->board[to_r][to_c] == EMPTY) {
if (board->EnPassant && (ToRank(to_r) == board->ep_rank) &&
(ToCol(to_c) == board->ep_col)) {
moves = append_move_pair(from_col, from_rank, ToCol(to_c), ToRank(to_r), moves);
}
}
else if (EXTRACT_COLOUR(board->board[to_r][to_c]) == target_colour) {
moves = append_move_pair(from_col, from_rank, ToCol(to_c), ToRank(to_r), moves);
}
else {
}
/* Try to right. */
to_r = RankConvert(from_rank) + offset;
to_c = ColConvert(from_col) + 1;
if (board->board[to_r][to_c] == OFF) {
}
else if (board->board[to_r][to_c] == EMPTY) {
if (board->EnPassant && (ToRank(to_r) == board->ep_rank) &&
(ToCol(to_c) == board->ep_col)) {
moves = append_move_pair(from_col, from_rank, ToCol(to_c), ToRank(to_r), moves);
}
}
else if (EXTRACT_COLOUR(board->board[to_r][to_c]) == target_colour) {
moves = append_move_pair(from_col, from_rank, ToCol(to_c), ToRank(to_r), moves);
}
else {
}
if (moves != NULL) {
moves = exclude_checks(piece, colour, moves, board);
}
return moves;
}
/* See whether the king of the given colour is in checkmate.
* Assuming that the king is in check, generate all possible moves
* for colour on board until at least one saving move is found.
* Excepted from this are the castling moves (not legal whilst in check)
* and underpromotions (inadequate in thwarting a check).
*/
Boolean
king_is_in_checkmate(Colour colour, Board *board)
{
Rank rank;
Col col;
MovePair *moves = NULL;
Boolean in_checkmate = FALSE;
/* Search the board for pieces of the right colour.
* Keep going until we have exhausted all pieces, or until
* we have found a saving move.
*/
for (rank = LASTRANK; (rank >= FIRSTRANK) && (moves == NULL); rank--) {
for (col = FIRSTCOL; (col <= LASTCOL) && (moves == NULL); col++) {
int r = RankConvert(rank);
int c = ColConvert(col);
Piece occupant = board->board[r][c];
if ((occupant != EMPTY) && (colour == EXTRACT_COLOUR(occupant))) {
/* This square is occupied by a piece of the required colour. */
Piece piece = EXTRACT_PIECE(occupant);
switch (piece) {
case KING:
case KNIGHT:
moves = generate_single_moves(colour, piece, board, col, rank);
break;
case QUEEN:
case ROOK:
case BISHOP:
moves = generate_multiple_moves(colour, piece, board, col, rank);
break;
case PAWN:
moves = generate_pawn_moves(colour, board, col, rank);
break;
default:
fprintf(GlobalState.logfile,
"Internal error: unknown piece %d in king_is_in_checkmate().\n",
piece);
}
}
}
}
if (moves != NULL) {
/* No checkmate. Free the move list. */
free_move_pair_list(moves);
}
else {
in_checkmate = TRUE;
}
return in_checkmate;
}
#if INCLUDE_UNUSED_FUNCTIONS
/* Return an approximation of how many moves there are for
* board->to_move on board.
* This may be exact, but I haven't checked.
* This is not currently used, but I found it useful at one
* point for generating game statistics.
*/
static unsigned
approx_how_many_moves(Board *board)
{
Rank rank;
Col col;
Colour colour = board->to_move;
unsigned num_moves = 0;
/* Search the board for pieces of the right colour.
* Keep going until we have exhausted all pieces, or until
* we have found a saving move.
*/
for (rank = LASTRANK; rank >= FIRSTRANK; rank--) {
for (col = FIRSTCOL; col <= LASTCOL; col++) {
int r = RankConvert(rank);
int c = ColConvert(col);
Piece occupant = board->board[r][c];
MovePair *moves = NULL;
if ((occupant != EMPTY) && (colour == EXTRACT_COLOUR(occupant))) {
/* This square is occupied by a piece of the required colour. */
Piece piece = EXTRACT_PIECE(occupant);
switch (piece) {
case KING:
case KNIGHT:
moves = generate_single_moves(colour, piece, board, col, rank);
break;
case QUEEN:
case ROOK:
case BISHOP:
moves = generate_multiple_moves(colour, piece, board, col, rank);
break;
case PAWN:
moves = generate_pawn_moves(colour, board, col, rank);
break;
default:
fprintf(GlobalState.logfile,
"Internal error: unknown piece %d in king_is_in_checkmate().\n",
piece);
}
if (moves != NULL) {
/* At least one move. */
MovePair *m;
for (m = moves; m != NULL; m = m->next) {
num_moves++;
}
/* Free the move list. */
free_move_pair_list(moves);
}
}
}
}
return num_moves;
}
#endif
/* Find all moves for on the given board for colour. */
MovePair *
find_all_moves(const Board *board, Colour colour)
{
Rank rank;
Col col;
/* All moves for colour. */
MovePair *all_moves = NULL;
/* Pick up each piece of the required colour. */
for (rank = LASTRANK; rank >= FIRSTRANK; rank--) {
int r = RankConvert(rank);
for (col = FIRSTCOL; col <= LASTCOL; col++) {
int c = ColConvert(col);
Piece occupant = board->board[r][c];
if ((occupant != EMPTY) && (colour == EXTRACT_COLOUR(occupant))) {
/* This square is occupied by a piece of the required colour. */
Piece piece = EXTRACT_PIECE(occupant);
/* List of moves for this piece. */
MovePair *moves = NULL;
switch (piece) {
case KING:
moves = generate_single_moves(colour, piece, board, col, rank);
/* Add any castling, as this is not covered
* by GenerateSingleMoves.
*/
if (can_castle(KINGSIDE_CASTLE, colour, board)) {
MovePair *m = (MovePair *)
malloc_or_die(sizeof (MovePair));
m->from_col = find_castling_king_col(colour, board);
m->from_rank = rank;
m->to_col = 'g';
m->to_rank = rank;
/* Prepend. */
m->next = moves;
moves = m;
}
if (can_castle(QUEENSIDE_CASTLE, colour, board)) {
MovePair *m = (MovePair *)
malloc_or_die(sizeof (MovePair));
m->from_col = find_castling_king_col(colour, board);
m->from_rank = rank;
m->to_col = 'c';
m->to_rank = rank;
/* Prepend. */
m->next = moves;
moves = m;
}
break;
case KNIGHT:
moves = generate_single_moves(colour, piece, board, col, rank);
break;
case QUEEN:
case ROOK:
case BISHOP:
moves = generate_multiple_moves(colour, piece, board, col, rank);
break;
case PAWN:
moves = generate_pawn_moves(colour, board, col, rank);
break;
default:
fprintf(GlobalState.logfile,
"Internal error: unknown piece %d in king_is_in_checkmate().\n",
piece);
}
if (moves != NULL) {
/* At least one move.
* Append what we have so far to this list.
* Find the last one.
*/
MovePair *m;
for (m = moves; m->next != NULL; m = m->next) {
}
m->next = all_moves;
all_moves = moves;
}
}
}
}
return all_moves;
}
/* Return TRUE if there is at least one move on the given board for colour. */
Boolean
at_least_one_move(const Board *board, Colour colour)
{
Boolean move_found = FALSE;
/* Pick up each piece of the required colour. */
for (Rank rank = LASTRANK; rank >= FIRSTRANK && !move_found; rank--) {
int r = RankConvert(rank);
for (Col col = FIRSTCOL; col <= LASTCOL && !move_found; col++) {
Piece occupant = board->board[r][ColConvert(col)];
if ((occupant != EMPTY) && (colour == EXTRACT_COLOUR(occupant))) {
/* This square is occupied by a piece of the required colour. */
Piece piece = EXTRACT_PIECE(occupant);
MovePair *moves = NULL;
switch (piece) {
case KING:
moves = generate_single_moves(colour, piece, board, col, rank);
if (moves != NULL) {
move_found = TRUE;
free_move_pair_list(moves);
}
/* Add any castling, as this is not covered
* by GenerateSingleMoves.
*/
else if (can_castle(KINGSIDE_CASTLE, colour, board)) {
move_found = TRUE;
}
else if (can_castle(QUEENSIDE_CASTLE, colour, board)) {
move_found = TRUE;
}
break;
case KNIGHT:
moves = generate_single_moves(colour, piece, board, col, rank);
if (moves != NULL) {
move_found = TRUE;
free_move_pair_list(moves);
}
break;
case QUEEN:
case ROOK:
case BISHOP:
moves = generate_multiple_moves(colour, piece, board, col, rank);
if (moves != NULL) {
move_found = TRUE;
free_move_pair_list(moves);
}
break;
case PAWN:
moves = generate_pawn_moves(colour, board, col, rank);
if (moves != NULL) {
move_found = TRUE;
free_move_pair_list(moves);
}
break;
default:
fprintf(GlobalState.logfile,
"Internal error: unknown piece %d in king_is_in_checkmate().\n",
piece);
}
}
}
}
return move_found;
}