add pgn-extract 22.11
This commit is contained in:
758
pgn-extract/end.c
Normal file
758
pgn-extract/end.c
Normal file
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* 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 "end.h"
|
||||
#include "lines.h"
|
||||
#include "tokens.h"
|
||||
#include "taglist.h"
|
||||
#include "lex.h"
|
||||
#include "apply.h"
|
||||
#include "grammar.h"
|
||||
|
||||
/**
|
||||
* Code to handle specifications describing the state of the board
|
||||
* in terms of numbers of pieces and material balance between opponents.
|
||||
*
|
||||
* Games are then matched against these specifications.
|
||||
*/
|
||||
|
||||
/* Keep a list of endings to be found. */
|
||||
static Material_details *endings_to_match = NULL;
|
||||
|
||||
/* What kind of piece is the character, c, likely to represent?
|
||||
* NB: This is NOT the same as is_piece() in decode.c
|
||||
*/
|
||||
/* Define pseudo-letter for minor pieces, used later. */
|
||||
#define MINOR_PIECE 'L'
|
||||
|
||||
static Piece
|
||||
is_English_piece(char c)
|
||||
{
|
||||
Piece piece = EMPTY;
|
||||
|
||||
switch (c) {
|
||||
case 'K': case 'k':
|
||||
piece = KING;
|
||||
break;
|
||||
case 'Q': case 'q':
|
||||
piece = QUEEN;
|
||||
break;
|
||||
case 'R': case 'r':
|
||||
piece = ROOK;
|
||||
break;
|
||||
case 'N': case 'n':
|
||||
piece = KNIGHT;
|
||||
break;
|
||||
case 'B': case 'b':
|
||||
piece = BISHOP;
|
||||
break;
|
||||
case 'P': case 'p':
|
||||
piece = PAWN;
|
||||
break;
|
||||
}
|
||||
return piece;
|
||||
}
|
||||
|
||||
/* Initialise the count of required pieces prior to reading
|
||||
* in the data.
|
||||
*/
|
||||
static Material_details *
|
||||
new_ending_details(Boolean both_colours)
|
||||
{
|
||||
Material_details *details = (Material_details *) malloc_or_die(sizeof (Material_details));
|
||||
int c;
|
||||
Piece piece;
|
||||
|
||||
details->both_colours = both_colours;
|
||||
for (piece = PAWN; piece <= KING; piece++) {
|
||||
for (c = 0; c < 2; c++) {
|
||||
details->num_pieces[c][piece] = 0;
|
||||
details->occurs[c][piece] = EXACTLY;
|
||||
}
|
||||
}
|
||||
/* Fill out some miscellaneous colour based information. */
|
||||
for (c = 0; c < 2; c++) {
|
||||
/* Only the KING is a requirement for each side. */
|
||||
details->num_pieces[c][KING] = 1;
|
||||
details->match_depth[c] = 0;
|
||||
/* How many general minor pieces to match. */
|
||||
details->num_minor_pieces[c] = 0;
|
||||
details->minor_occurs[c] = EXACTLY;
|
||||
}
|
||||
/* Assume that the match must always have a depth of at least two for
|
||||
* two half-move stability.
|
||||
*/
|
||||
details->move_depth = 2;
|
||||
details->next = NULL;
|
||||
return details;
|
||||
}
|
||||
|
||||
static const char *
|
||||
extract_combination(const char *p, Occurs *p_occurs, int *p_number, const char *line)
|
||||
{
|
||||
Boolean Ok = TRUE;
|
||||
Occurs occurs = EXACTLY;
|
||||
int number = 1;
|
||||
|
||||
if (isdigit((int) *p)) {
|
||||
/* Only single digits are allowed. */
|
||||
number = *p - '0';
|
||||
p++;
|
||||
if (isdigit((int) *p)) {
|
||||
fprintf(GlobalState.logfile, "Number > 9 is too big in %s.\n",
|
||||
line);
|
||||
while (isdigit((int) *p)) {
|
||||
p++;
|
||||
}
|
||||
Ok = FALSE;
|
||||
}
|
||||
}
|
||||
if (Ok) {
|
||||
/* Look for trailing annotations. */
|
||||
switch (*p) {
|
||||
case '*':
|
||||
number = 0;
|
||||
occurs = NUM_OR_MORE;
|
||||
p++;
|
||||
break;
|
||||
case '+':
|
||||
occurs = NUM_OR_MORE;
|
||||
p++;
|
||||
break;
|
||||
case '-':
|
||||
occurs = NUM_OR_LESS;
|
||||
p++;
|
||||
break;
|
||||
case '?':
|
||||
number = 1;
|
||||
occurs = NUM_OR_LESS;
|
||||
p++;
|
||||
break;
|
||||
case '=':
|
||||
case '#':
|
||||
case '<':
|
||||
case '>':
|
||||
switch (*p) {
|
||||
case '=':
|
||||
p++;
|
||||
occurs = SAME_AS_OPPONENT;
|
||||
break;
|
||||
case '#':
|
||||
p++;
|
||||
occurs = NOT_SAME_AS_OPPONENT;
|
||||
break;
|
||||
case '<':
|
||||
p++;
|
||||
if (*p == '=') {
|
||||
occurs = LESS_EQ_THAN_OPPONENT;
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
occurs = LESS_THAN_OPPONENT;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
p++;
|
||||
if (*p == '=') {
|
||||
occurs = MORE_EQ_THAN_OPPONENT;
|
||||
p++;
|
||||
}
|
||||
else {
|
||||
occurs = MORE_THAN_OPPONENT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Ok) {
|
||||
*p_occurs = occurs;
|
||||
*p_number = number;
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extract a single piece set of information from line.
|
||||
* Return where we have got to as the result.
|
||||
* colour == WHITE means we are looking at the first set of
|
||||
* pieces, so some of the notation is illegal (i.e. the relative ops).
|
||||
*
|
||||
* The basic syntax for a piece description is:
|
||||
* piece [number] [occurs]
|
||||
* For instance:
|
||||
* P2+ Pawn occurs at least twice or more.
|
||||
* R= Rook occurs same number of times as opponent. (colour == BLACK)
|
||||
* P1>= Exactly one pawn more than the opponent. (colour == BLACK)
|
||||
*/
|
||||
static const char *
|
||||
extract_piece_information(const char *line, Material_details *details, Colour colour)
|
||||
{
|
||||
const char *p = line;
|
||||
Boolean Ok = TRUE;
|
||||
|
||||
while (Ok && (*p != '\0') && !isspace((int) *p) && *p != MATERIAL_CONSTRAINT) {
|
||||
Piece piece = is_English_piece(*p);
|
||||
/* By default a piece should occur exactly once. */
|
||||
Occurs occurs = EXACTLY;
|
||||
int number = 1;
|
||||
|
||||
if (piece != EMPTY) {
|
||||
/* Skip over the piece. */
|
||||
p++;
|
||||
p = extract_combination(p, &occurs, &number, line);
|
||||
if (p != NULL) {
|
||||
if ((piece == KING) && (number != 1)) {
|
||||
fprintf(GlobalState.logfile, "A king must occur exactly once.\n");
|
||||
number = 1;
|
||||
}
|
||||
else if ((piece == PAWN) && (number > 8)) {
|
||||
fprintf(GlobalState.logfile,
|
||||
"No more than 8 pawns are allowed.\n");
|
||||
number = 8;
|
||||
}
|
||||
details->num_pieces[colour][piece] = number;
|
||||
details->occurs[colour][piece] = occurs;
|
||||
}
|
||||
else {
|
||||
Ok = FALSE;
|
||||
}
|
||||
}
|
||||
else if (isalpha((int) *p) && (toupper((int) *p) == MINOR_PIECE)) {
|
||||
p++;
|
||||
p = extract_combination(p, &occurs, &number, line);
|
||||
if (p != NULL) {
|
||||
details->num_minor_pieces[colour] = number;
|
||||
details->minor_occurs[colour] = occurs;
|
||||
}
|
||||
else {
|
||||
Ok = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(GlobalState.logfile, "Unknown symbol at %s\n", p);
|
||||
Ok = FALSE;
|
||||
}
|
||||
}
|
||||
if (Ok) {
|
||||
/* Make a sanity check on the use of minor pieces. */
|
||||
if ((details->num_minor_pieces[colour] > 0) ||
|
||||
(details->minor_occurs[colour] != EXACTLY)) {
|
||||
/* Warn about use of BISHOP and KNIGHT letters. */
|
||||
if ((details->num_pieces[colour][BISHOP] > 0) ||
|
||||
(details->occurs[colour][BISHOP] != EXACTLY) ||
|
||||
(details->num_pieces[colour][KNIGHT] > 0) ||
|
||||
(details->occurs[colour][KNIGHT] != EXACTLY)) {
|
||||
fprintf(GlobalState.logfile,
|
||||
"Warning: the mixture of minor pieces in %s is not guaranteed to work.\n",
|
||||
line);
|
||||
fprintf(GlobalState.logfile,
|
||||
"In a single set it is advisable to stick to either L or B and/or N.\n");
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extract the piece specification from line and fill out
|
||||
* details with the pattern information.
|
||||
*/
|
||||
static Boolean
|
||||
decompose_line(const char *line, Material_details *details)
|
||||
{
|
||||
const char *p = line;
|
||||
Boolean Ok = TRUE;
|
||||
|
||||
/* Skip initial space. */
|
||||
while (isspace((int) *p)) {
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Look for a move depth. */
|
||||
if (isdigit((int) *p)) {
|
||||
unsigned depth;
|
||||
|
||||
depth = *p - '0';
|
||||
p++;
|
||||
while (isdigit((int) *p)) {
|
||||
depth = (depth * 10)+(*p - '0');
|
||||
p++;
|
||||
}
|
||||
while (isspace((int) *p)) {
|
||||
p++;
|
||||
}
|
||||
details->move_depth = depth;
|
||||
}
|
||||
|
||||
/* Extract two pairs of piece information.
|
||||
* NB: If the first set of pieces consists of a lone king then that must
|
||||
* be included explicitly. If the second set consists of a lone
|
||||
* king then that can be omitted.
|
||||
*/
|
||||
p = extract_piece_information(p, details, WHITE);
|
||||
if (p != NULL) {
|
||||
while ((*p != '\0') && (isspace((int) *p) || (*p == MATERIAL_CONSTRAINT))) {
|
||||
p++;
|
||||
}
|
||||
if (*p != '\0') {
|
||||
p = extract_piece_information(p, details, BLACK);
|
||||
}
|
||||
else {
|
||||
/* No explicit requirements for the other colour. */
|
||||
Piece piece;
|
||||
|
||||
for (piece = PAWN; piece <= KING; piece++) {
|
||||
details->num_pieces[BLACK][piece] = 0;
|
||||
details->occurs[BLACK][piece] = EXACTLY;
|
||||
}
|
||||
details->num_pieces[BLACK][KING] = 1;
|
||||
details->occurs[BLACK][KING] = EXACTLY;
|
||||
}
|
||||
}
|
||||
if (p != NULL) {
|
||||
/* Allow trailing text as a comment. */
|
||||
}
|
||||
else {
|
||||
Ok = FALSE;
|
||||
}
|
||||
return Ok;
|
||||
}
|
||||
|
||||
/* A new game to be looked for. Indicate that we have not
|
||||
* started matching any yet.
|
||||
*/
|
||||
static void
|
||||
reset_match_depths(Material_details *endings)
|
||||
{
|
||||
for (; endings != NULL; endings = endings->next) {
|
||||
endings->match_depth[WHITE] = 0;
|
||||
endings->match_depth[BLACK] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to find a match for the given number of piece details. */
|
||||
static Boolean
|
||||
piece_match(int num_available, int num_to_find, int num_opponents, Occurs occurs)
|
||||
{
|
||||
Boolean match = FALSE;
|
||||
|
||||
switch (occurs) {
|
||||
case EXACTLY:
|
||||
match = num_available == num_to_find;
|
||||
break;
|
||||
case NUM_OR_MORE:
|
||||
match = num_available >= num_to_find;
|
||||
break;
|
||||
case NUM_OR_LESS:
|
||||
match = num_available <= num_to_find;
|
||||
break;
|
||||
case SAME_AS_OPPONENT:
|
||||
match = num_available == num_opponents;
|
||||
break;
|
||||
case NOT_SAME_AS_OPPONENT:
|
||||
match = num_available != num_opponents;
|
||||
break;
|
||||
case LESS_THAN_OPPONENT:
|
||||
match = (num_available + num_to_find) <= num_opponents;
|
||||
break;
|
||||
case MORE_THAN_OPPONENT:
|
||||
match = (num_available - num_to_find) >= num_opponents;
|
||||
break;
|
||||
case LESS_EQ_THAN_OPPONENT:
|
||||
/* This means exactly num_to_find less than the
|
||||
* opponent.
|
||||
*/
|
||||
match = (num_available + num_to_find) == num_opponents;
|
||||
break;
|
||||
case MORE_EQ_THAN_OPPONENT:
|
||||
/* This means exactly num_to_find greater than the
|
||||
* opponent.
|
||||
*/
|
||||
match = (num_available - num_to_find) == num_opponents;
|
||||
break;
|
||||
default:
|
||||
fprintf(GlobalState.logfile,
|
||||
"Inconsistent state %d in piece_match.\n", occurs);
|
||||
match = FALSE;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/* Try to find a match against one player's pieces in the piece_set_colour
|
||||
* set of details_to_find.
|
||||
*/
|
||||
static Boolean
|
||||
piece_set_match(const Material_details *details_to_find,
|
||||
int num_pieces[2][NUM_PIECE_VALUES],
|
||||
Colour game_colour, Colour piece_set_colour)
|
||||
{
|
||||
Boolean match = TRUE;
|
||||
Piece piece;
|
||||
/* Determine whether we failed on a match for minor pieces or not. */
|
||||
Boolean minor_failure = FALSE;
|
||||
|
||||
/* No need to check KING. */
|
||||
for (piece = PAWN; (piece < KING) && match; piece++) {
|
||||
int num_available = num_pieces[game_colour][piece];
|
||||
int num_opponents = num_pieces[OPPOSITE_COLOUR(game_colour)][piece];
|
||||
int num_to_find = details_to_find->num_pieces[piece_set_colour][piece];
|
||||
Occurs occurs = details_to_find->occurs[piece_set_colour][piece];
|
||||
|
||||
match = piece_match(num_available, num_to_find, num_opponents, occurs);
|
||||
if (!match) {
|
||||
if ((piece == KNIGHT) || (piece == BISHOP)) {
|
||||
minor_failure = TRUE;
|
||||
/* Carry on trying to match. */
|
||||
match = TRUE;
|
||||
}
|
||||
else {
|
||||
minor_failure = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
/* Ensure that the minor pieces match if there is a minor pieces
|
||||
* requirement.
|
||||
*/
|
||||
int num_to_find = details_to_find->num_minor_pieces[piece_set_colour];
|
||||
Occurs occurs = details_to_find->minor_occurs[piece_set_colour];
|
||||
|
||||
if ((num_to_find > 0) || (occurs != EXACTLY)) {
|
||||
int num_available =
|
||||
num_pieces[game_colour][BISHOP] +
|
||||
num_pieces[game_colour][KNIGHT];
|
||||
int num_opponents = num_pieces[OPPOSITE_COLOUR(game_colour)][BISHOP] +
|
||||
num_pieces[OPPOSITE_COLOUR(game_colour)][KNIGHT];
|
||||
|
||||
match = piece_match(num_available, num_to_find, num_opponents, occurs);
|
||||
}
|
||||
else if (minor_failure) {
|
||||
/* We actually failed with proper matching of individual minor
|
||||
* pieces, and no minor match fixup is possible.
|
||||
*/
|
||||
match = FALSE;
|
||||
}
|
||||
else {
|
||||
/* Match stands. */
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/* Look for a material match between current_details and
|
||||
* details_to_find. Only return TRUE if we have both a match
|
||||
* and match_depth >= move_depth in details_to_find.
|
||||
* NB: If the game ends before the required depth is reached then a
|
||||
* potential match would be missed. This could be considered
|
||||
* as a bug.
|
||||
*/
|
||||
static Boolean
|
||||
material_match(Material_details *details_to_find, int num_pieces[2][NUM_PIECE_VALUES],
|
||||
Colour game_colour)
|
||||
{
|
||||
Boolean match = TRUE;
|
||||
Colour piece_set_colour = WHITE;
|
||||
|
||||
match = piece_set_match(details_to_find, num_pieces, game_colour,
|
||||
piece_set_colour);
|
||||
if (match) {
|
||||
game_colour = OPPOSITE_COLOUR(game_colour);
|
||||
piece_set_colour = OPPOSITE_COLOUR(piece_set_colour);
|
||||
match = piece_set_match(details_to_find, num_pieces, game_colour,
|
||||
piece_set_colour);
|
||||
/* Reset colour to its original value. */
|
||||
game_colour = OPPOSITE_COLOUR(game_colour);
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if (details_to_find->match_depth[game_colour] < details_to_find->move_depth) {
|
||||
/* Not a full match yet. */
|
||||
match = FALSE;
|
||||
details_to_find->match_depth[game_colour]++;
|
||||
}
|
||||
else {
|
||||
/* A stable match. */
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Reset the match counter. */
|
||||
details_to_find->match_depth[game_colour] = 0;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/* Extract the numbers of each type of piece from the given board. */
|
||||
static void extract_pieces_from_board(int num_pieces[2][NUM_PIECE_VALUES], const Board *board)
|
||||
{
|
||||
/* Set up num_pieces from the board. */
|
||||
for(int c = 0; c < 2; c++) {
|
||||
for(int p = 0; p < NUM_PIECE_VALUES; p++) {
|
||||
num_pieces[c][p] = 0;
|
||||
}
|
||||
}
|
||||
for(char rank = FIRSTRANK; rank <= LASTRANK; rank++) {
|
||||
for(char col = FIRSTCOL; col <= LASTCOL; col++) {
|
||||
int r = RankConvert(rank);
|
||||
int c = ColConvert(col);
|
||||
|
||||
Piece coloured_piece = board->board[r][c];
|
||||
if(coloured_piece != EMPTY) {
|
||||
int p = EXTRACT_PIECE(coloured_piece);
|
||||
num_pieces[EXTRACT_COLOUR(coloured_piece)][p]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check to see whether the given moves lead to a position
|
||||
* that matches the given 'ending' position.
|
||||
* In other words, a position with the required balance
|
||||
* of pieces.
|
||||
*/
|
||||
static Boolean
|
||||
look_for_material_match(Game *game_details)
|
||||
{
|
||||
Boolean game_ok = TRUE;
|
||||
Boolean match_comment_added = FALSE;
|
||||
Move *next_move = game_details->moves;
|
||||
Move *move_for_comment = NULL;
|
||||
Colour colour = WHITE;
|
||||
/* The initial game position has the full set of piece details. */
|
||||
int num_pieces[2][NUM_PIECE_VALUES] = {
|
||||
/* Dummies for OFF and EMPTY at the start. */
|
||||
/* P N B R Q K */
|
||||
{0, 0, 8, 2, 2, 2, 1, 1},
|
||||
{0, 0, 8, 2, 2, 2, 1, 1}
|
||||
};
|
||||
Board *board = new_game_board(game_details->tags[FEN_TAG]);
|
||||
|
||||
if(game_details->tags[FEN_TAG] != NULL) {
|
||||
extract_pieces_from_board(num_pieces, board);
|
||||
}
|
||||
/* Ensure that all previous match indications are cleared. */
|
||||
reset_match_depths(endings_to_match);
|
||||
|
||||
/* Keep going while the game is ok, and we have some more
|
||||
* moves and we haven't exceeded the search depth without finding
|
||||
* a match.
|
||||
*/
|
||||
Boolean matches = FALSE;
|
||||
Boolean end_of_game = FALSE;
|
||||
Boolean white_matches = FALSE, black_matches = FALSE;
|
||||
while (game_ok && !matches && !end_of_game) {
|
||||
for (Material_details *details_to_find = endings_to_match; !matches && (details_to_find != NULL);
|
||||
details_to_find = details_to_find->next) {
|
||||
/* Try before applying each move.
|
||||
* Note, that we wish to try both ways around because we might
|
||||
* have WT,BT WF,BT ... If we don't try BLACK on WHITE success
|
||||
* then we might miss a match because a full match takes several
|
||||
* separate individual match steps.
|
||||
*/
|
||||
white_matches = material_match(details_to_find, num_pieces, WHITE);
|
||||
if(details_to_find->both_colours) {
|
||||
black_matches = material_match(details_to_find, num_pieces, BLACK);
|
||||
}
|
||||
else {
|
||||
black_matches = FALSE;
|
||||
}
|
||||
if (white_matches || black_matches) {
|
||||
matches = TRUE;
|
||||
/* See whether a matching comment is required. */
|
||||
if (GlobalState.add_position_match_comments && !match_comment_added) {
|
||||
CommentList *match_comment = create_match_comment(board);
|
||||
if (move_for_comment != NULL) {
|
||||
append_comments_to_move(move_for_comment, match_comment);
|
||||
}
|
||||
else {
|
||||
if(game_details->prefix_comment == NULL) {
|
||||
game_details->prefix_comment = match_comment;
|
||||
}
|
||||
else {
|
||||
CommentList *comm = game_details->prefix_comment;
|
||||
while(comm->next != NULL) {
|
||||
comm = comm->next;
|
||||
}
|
||||
comm->next = match_comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(matches) {
|
||||
/* Nothing required. */
|
||||
}
|
||||
else if(next_move == NULL) {
|
||||
end_of_game = TRUE;
|
||||
}
|
||||
else if (*(next_move->move) != '\0') {
|
||||
/* Try the next position. */
|
||||
if (apply_move(next_move, board)) {
|
||||
/* Remove any captured pieces. */
|
||||
if (next_move->captured_piece != EMPTY) {
|
||||
num_pieces[OPPOSITE_COLOUR(colour)][next_move->captured_piece]--;
|
||||
}
|
||||
if (next_move->promoted_piece != EMPTY) {
|
||||
num_pieces[colour][next_move->promoted_piece]++;
|
||||
/* Remove the promoting pawn. */
|
||||
num_pieces[colour][PAWN]--;
|
||||
}
|
||||
|
||||
move_for_comment = next_move;
|
||||
colour = OPPOSITE_COLOUR(colour);
|
||||
next_move = next_move->next;
|
||||
}
|
||||
else {
|
||||
game_ok = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* An empty move. */
|
||||
fprintf(GlobalState.logfile,
|
||||
"Internal error: Empty move in look_for_material_match.\n");
|
||||
game_ok = FALSE;
|
||||
}
|
||||
}
|
||||
(void) free((void *) board);
|
||||
if(game_ok && matches) {
|
||||
if(GlobalState.add_match_tag) {
|
||||
game_details->tags[MATERIAL_MATCH_TAG] =
|
||||
copy_string(white_matches ? "White" : "Black");
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check to see whether the given moves lead to a position
|
||||
* that matches one of the required 'material match' positions.
|
||||
* In other words, a position with the required balance
|
||||
* of pieces.
|
||||
*/
|
||||
Boolean
|
||||
check_for_material_match(Game *game)
|
||||
{
|
||||
/* Match if there are no endings to match. */
|
||||
if(endings_to_match != NULL) {
|
||||
return look_for_material_match(game);
|
||||
}
|
||||
else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Does the board's material match the constraints of details_to_find?
|
||||
* Return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
Boolean
|
||||
constraint_material_match(Material_details *details_to_find, const Board *board)
|
||||
{
|
||||
/* Only a single match position is required. */
|
||||
details_to_find->move_depth = 0;
|
||||
details_to_find->match_depth[0] = 0;
|
||||
details_to_find->match_depth[1] = 0;
|
||||
|
||||
int num_pieces[2][NUM_PIECE_VALUES];
|
||||
extract_pieces_from_board(num_pieces, board);
|
||||
Boolean white_matches = material_match(details_to_find, num_pieces, WHITE);
|
||||
Boolean black_matches;
|
||||
|
||||
if(details_to_find->both_colours) {
|
||||
black_matches = material_match(details_to_find, num_pieces, BLACK);
|
||||
}
|
||||
else {
|
||||
black_matches = FALSE;
|
||||
}
|
||||
return white_matches || black_matches;
|
||||
}
|
||||
|
||||
/* Decompose the text of line to extract two sets of
|
||||
* piece configurations.
|
||||
* If both_colours is TRUE then matches will be tried
|
||||
* for both colours in each configuration.
|
||||
* Otherwise, the first set of pieces are assumed to
|
||||
* be white and the second to be black.
|
||||
* If pattern_constraint is TRUE then the description
|
||||
* is a constraint of a FEN pattern and should not be
|
||||
* retained as a separate material match.
|
||||
*/
|
||||
Material_details *
|
||||
process_material_description(const char *line, Boolean both_colours, Boolean pattern_constraint)
|
||||
{
|
||||
Material_details *details = NULL;
|
||||
|
||||
if (non_blank_line(line)) {
|
||||
details = new_ending_details(both_colours);
|
||||
|
||||
if (decompose_line(line, details)) {
|
||||
if(!pattern_constraint) {
|
||||
/* Add it on to the list. */
|
||||
details->next = endings_to_match;
|
||||
endings_to_match = details;
|
||||
}
|
||||
}
|
||||
else {
|
||||
(void) free((void *) details);
|
||||
details = NULL;
|
||||
}
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
/* Read a file containing material matches. */
|
||||
Boolean
|
||||
build_endings(const char *infile, Boolean both_colours)
|
||||
{
|
||||
FILE *fp = fopen(infile, "r");
|
||||
Boolean Ok = TRUE;
|
||||
|
||||
if (fp == NULL) {
|
||||
fprintf(GlobalState.logfile, "Cannot open %s for reading.\n", infile);
|
||||
exit(1);
|
||||
}
|
||||
else {
|
||||
char *line;
|
||||
while ((line = read_line(fp)) != NULL) {
|
||||
if(process_material_description(line, both_colours, FALSE) == NULL) {
|
||||
Ok = FALSE;
|
||||
}
|
||||
(void) free(line);
|
||||
}
|
||||
(void) fclose(fp);
|
||||
}
|
||||
return Ok;
|
||||
}
|
Reference in New Issue
Block a user