2468 lines
85 KiB
C
2468 lines
85 KiB
C
/*
|
|
* This file is part of pgn-extract: a Portable Game Notation (PGN) extractor.
|
|
* Copyright (C) 1994-2022 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.
|
|
* Make sure the capture is directly diagonal.
|
|
*/
|
|
if(abs(from_col - to_col) == 1) {
|
|
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 Rook 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 may be 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);
|
|
/* Reject ep attempts for the colour that has just moved. */
|
|
char valid_ep_rank = colour == WHITE ? '6' : '3';
|
|
|
|
/* 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 the 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 &&
|
|
board->ep_rank == valid_ep_rank &&
|
|
(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 the 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 &&
|
|
board->ep_rank == valid_ep_rank &&
|
|
(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;
|
|
}
|
|
|