759 lines
24 KiB
C
759 lines
24 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 "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;
|
|
}
|