787 lines
26 KiB
C
787 lines
26 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/
|
|
*/
|
|
|
|
/***
|
|
* These routines are concerned with gathering moves of
|
|
* the various sorts of variations specified by the -v
|
|
* and -x flags. In the former case, there are also functions
|
|
* for checking the moves of a game against the variation
|
|
* lists that are wanted. Checking of the variations specified
|
|
* by the -x flag is handled elsewhere by apply_move_list().
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include "bool.h"
|
|
#include "mymalloc.h"
|
|
#include "lines.h"
|
|
#include "defs.h"
|
|
#include "typedef.h"
|
|
#include "map.h"
|
|
#include "lists.h"
|
|
#include "tokens.h"
|
|
#include "taglist.h"
|
|
#include "lex.h"
|
|
#include "moves.h"
|
|
#include "apply.h"
|
|
#include "decode.h"
|
|
#include "fenmatcher.h"
|
|
|
|
/* Define a character that can be used in the variations file to
|
|
* mean that we don't mind what move was played at this point.
|
|
* So:
|
|
* * b6
|
|
* means look for all games in which Black playes 1...b6, regardless
|
|
* of White's first move.
|
|
*/
|
|
#define ANY_MOVE '*'
|
|
/* Define a character that can be used in the variations file to
|
|
* mean that we do not wish to match a particular move.
|
|
* So:
|
|
* e4 c5 !Nf3
|
|
* means look for games in which Black does not play 2. Nf3 against
|
|
* the Sicilian defence.
|
|
*/
|
|
#define DISALLOWED_MOVE '!'
|
|
|
|
/* Hold details of a single move within a variation. */
|
|
typedef struct {
|
|
/* Characters of the move.
|
|
* Alternative notations for the same move are separated by
|
|
* a non-move character, e.g.:
|
|
* cxd|cxd4|c5xd4
|
|
* could all be alternatives for the same basic pawn capture.
|
|
*/
|
|
char *move;
|
|
/* If we are interested in matching the moves in any order,
|
|
* then we need to record whether or not the current move has
|
|
* been matched or not.
|
|
*/
|
|
Boolean matched;
|
|
} variant_move;
|
|
|
|
/* Hold details of a single variation, with a pointer to
|
|
* an alternative variation.
|
|
*/
|
|
typedef struct variation_list {
|
|
/* The list of moves. */
|
|
variant_move *moves;
|
|
/* Keep a count of how many ANY_MOVE moves there are in the move
|
|
* list for each colour. If these are non-zero then one is used
|
|
* when a match fails when looking for permutations.
|
|
*/
|
|
unsigned num_white_any_moves;
|
|
unsigned num_black_any_moves;
|
|
/* Keep a count of how many DISALLOWED_MOVE moves there are in the move
|
|
* list for each colour. If these are non-zero then the game
|
|
* must be searched for them when looking for permutations before
|
|
* the full search is made.
|
|
*/
|
|
unsigned num_white_disallowed_moves;
|
|
unsigned num_black_disallowed_moves;
|
|
/* How many half-moves in the variation? */
|
|
unsigned length;
|
|
struct variation_list *next;
|
|
} variation_list;
|
|
|
|
/* The head of the variations-of-interest list. */
|
|
static variation_list *games_to_keep = NULL;
|
|
|
|
static Boolean textual_variation_match(const char *variation_move,
|
|
const unsigned char *actual_move);
|
|
|
|
/*** Functions concerned with reading details of the variations
|
|
*** of interest.
|
|
***/
|
|
|
|
/* Remove any move number prefix from str.
|
|
* Return NULL if there is no move (only number)
|
|
* otherwise return the head of the move portion.
|
|
*/
|
|
static char *
|
|
strip_move_number(char *str)
|
|
{
|
|
while (isdigit((int) *str)) {
|
|
str++;
|
|
}
|
|
while (*str == '.') {
|
|
str++;
|
|
}
|
|
if (*str != '\0') {
|
|
return str;
|
|
}
|
|
else {
|
|
return (char *) NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Define values for malloc/realloc allocation. */
|
|
#define INIT_MOVE_NUMBER 10
|
|
#define MOVE_INCREMENT 5
|
|
|
|
/* Break up a single line of moves into a list of moves
|
|
* comprising a variation.
|
|
*/
|
|
static variation_list *
|
|
compose_variation(char *line)
|
|
{
|
|
variation_list *variation;
|
|
variant_move *move_list;
|
|
/* Keep track of the number of moves extracted from line. */
|
|
unsigned num_moves = 0;
|
|
unsigned max_moves = 0;
|
|
char *move;
|
|
|
|
variation = (variation_list *) malloc_or_die(sizeof (variation_list));
|
|
/* We don't yet know how many ANY_MOVEs or DISALLOWED_MOVES there
|
|
* will be in this variation.
|
|
*/
|
|
variation->num_white_any_moves = 0;
|
|
variation->num_black_any_moves = 0;
|
|
variation->num_white_disallowed_moves = 0;
|
|
variation->num_black_disallowed_moves = 0;
|
|
/* Allocate an initial number of pointers for the moves of the variation. */
|
|
move_list = (variant_move *) malloc_or_die(INIT_MOVE_NUMBER *
|
|
sizeof (*move_list));
|
|
max_moves = INIT_MOVE_NUMBER;
|
|
/* Find the first move. */
|
|
move = strtok(line, " ");
|
|
while (move != NULL) {
|
|
if ((move = strip_move_number(move)) == NULL) {
|
|
/* Only a move number. */
|
|
}
|
|
else {
|
|
/* See if we need some more space. */
|
|
if (num_moves == max_moves) {
|
|
move_list = (variant_move *) realloc_or_die((void *) move_list,
|
|
(max_moves + MOVE_INCREMENT) * sizeof (*move_list));
|
|
if (move_list == NULL) {
|
|
/* Catastrophic failure. */
|
|
free((void *) variation);
|
|
return NULL;
|
|
}
|
|
else {
|
|
max_moves += MOVE_INCREMENT;
|
|
}
|
|
}
|
|
/* Keep the move and initialise the matched field for
|
|
* when we start matching games.
|
|
*/
|
|
move_list[num_moves].move = move;
|
|
move_list[num_moves].matched = FALSE;
|
|
/* Keep track of moves that will match anything. */
|
|
if (*move == ANY_MOVE) {
|
|
/* Odd numbered half-moves in the variant list are Black. */
|
|
if (num_moves & 0x01) {
|
|
variation->num_black_any_moves++;
|
|
}
|
|
else {
|
|
variation->num_white_any_moves++;
|
|
}
|
|
/* Beware of the potential for false matches. */
|
|
if (strlen(move) > 1) {
|
|
fprintf(GlobalState.logfile,
|
|
"Warning: %c in %s should not be followed by additional move text.\n",
|
|
*move, move);
|
|
fprintf(GlobalState.logfile, "It could give false matches.\n");
|
|
}
|
|
}
|
|
else if (*move == DISALLOWED_MOVE) {
|
|
/* Odd numbered half-moves in the variant list are Black. */
|
|
if (num_moves & 0x01) {
|
|
variation->num_black_disallowed_moves++;
|
|
}
|
|
else {
|
|
variation->num_white_disallowed_moves++;
|
|
}
|
|
}
|
|
else {
|
|
/* Unadorned move. */
|
|
}
|
|
num_moves++;
|
|
}
|
|
move = strtok((char *) NULL, " ");
|
|
}
|
|
variation->moves = move_list;
|
|
variation->length = num_moves;
|
|
variation->next = NULL;
|
|
return variation;
|
|
}
|
|
|
|
/* Read each line of input and decompose it into a variation
|
|
* to be placed in the games_to_keep list.
|
|
*/
|
|
void
|
|
add_textual_variations_from_file(FILE *fpin)
|
|
{
|
|
char *line;
|
|
|
|
while ((line = read_line(fpin)) != NULL) {
|
|
add_textual_variation_from_line(line);
|
|
}
|
|
}
|
|
|
|
/* Add the text of the given line to the list of games_to_keep.
|
|
*/
|
|
void
|
|
add_textual_variation_from_line(char *line)
|
|
{
|
|
if (non_blank_line(line)) {
|
|
variation_list *next_variation = compose_variation(line);
|
|
if (next_variation != NULL) {
|
|
next_variation->next = games_to_keep;
|
|
games_to_keep = next_variation;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*** Functions concerned with reading details of the positional
|
|
*** variations of interest.
|
|
***/
|
|
|
|
/* Break up a single line of moves into a list of moves
|
|
* comprising a positional variation.
|
|
* In doing so, set GlobalState.depth_of_positional_search
|
|
* if this variation is longer than the default.
|
|
*/
|
|
static Move *
|
|
compose_positional_variation(char *line)
|
|
{
|
|
char *move;
|
|
/* Build a linked list of the moves of the variation. */
|
|
Move *head = NULL, *tail = NULL;
|
|
Boolean Ok = TRUE;
|
|
/* Keep track of the ply depth. */
|
|
unsigned depth = 0;
|
|
|
|
move = strtok(line, " ");
|
|
while (Ok && (move != NULL) && (*move != '*')) {
|
|
if ((move = strip_move_number(move)) == NULL) {
|
|
/* Only a move number. */
|
|
}
|
|
else {
|
|
Move *next = decode_move((unsigned char *) move);
|
|
|
|
if (next == NULL) {
|
|
fprintf(GlobalState.logfile, "Failed to identify %s\n", move);
|
|
Ok = FALSE;
|
|
}
|
|
else {
|
|
/* Chain it on to the list. */
|
|
if (tail == NULL) {
|
|
head = next;
|
|
tail = next;
|
|
}
|
|
else {
|
|
tail->next = next;
|
|
tail = next;
|
|
}
|
|
next->next = NULL;
|
|
}
|
|
depth++;
|
|
}
|
|
/* Pick up the next likely move. */
|
|
move = strtok(NULL, " ");
|
|
}
|
|
if (Ok) {
|
|
/* Determine whether the depth of this variation exceeds
|
|
* the current default.
|
|
* Depth is counted in ply.
|
|
* Add some extras, in order to catch transpositions.
|
|
*/
|
|
depth += 8;
|
|
if (depth > GlobalState.depth_of_positional_search) {
|
|
GlobalState.depth_of_positional_search = depth;
|
|
}
|
|
}
|
|
else {
|
|
if (head != NULL) {
|
|
free_move_list(head);
|
|
}
|
|
head = NULL;
|
|
}
|
|
return head;
|
|
}
|
|
|
|
/* Read each line of input and decompose it into a positional variation
|
|
* to be placed in the list of required hash values.
|
|
*/
|
|
void
|
|
add_positional_variations_from_file(FILE *fpin)
|
|
{
|
|
char *line;
|
|
|
|
while ((line = read_line(fpin)) != NULL) {
|
|
add_positional_variation_from_line(line);
|
|
}
|
|
}
|
|
|
|
void
|
|
add_positional_variation_from_line(char *line)
|
|
{
|
|
if (non_blank_line(line)) {
|
|
Move *next_variation = compose_positional_variation(line);
|
|
if (next_variation != NULL) {
|
|
/* We need a NULL fen string, because this is from
|
|
* the initial position.
|
|
*/
|
|
store_hash_value(next_variation, (const char *) NULL);
|
|
free_move_list(next_variation);
|
|
/* We need to know globally that positional variations
|
|
* are of interest.
|
|
*/
|
|
GlobalState.positional_variations = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Treat fen_string as being a position to be matched.
|
|
*/
|
|
void
|
|
add_fen_positional_match(const char *fen_string)
|
|
{
|
|
store_hash_value((Move *) NULL, fen_string);
|
|
GlobalState.positional_variations = TRUE;
|
|
}
|
|
|
|
/* Treat fen_pattern as being a position to be matched.
|
|
*/
|
|
void
|
|
add_fen_pattern_match(const char *fen_pattern, Boolean add_reverse, const char *label)
|
|
{
|
|
add_fen_pattern(fen_pattern, add_reverse, label);
|
|
GlobalState.positional_variations = TRUE;
|
|
}
|
|
|
|
/* Roughly define a move character for the purposes of textual
|
|
* matching.
|
|
*/
|
|
static Boolean
|
|
move_char(char c)
|
|
{
|
|
return (Boolean) isalpha((int) c) || isdigit((int) c) || (c == '-');
|
|
}
|
|
|
|
/* Return TRUE if there is a match for actual_move in variation_move.
|
|
* A match means that the string in actual_move is found surrounded
|
|
* by non-move characters in variation_move. For instance,
|
|
* variation_move == "Nc6|Nf3|f3" would match
|
|
* actual_move == "f3" but not actual_move == "c6".
|
|
*/
|
|
static Boolean
|
|
textual_variation_match(const char *variation_move, const unsigned char *actual_move)
|
|
{
|
|
const char *match_point;
|
|
Boolean found = FALSE;
|
|
|
|
for (match_point = variation_move; !found && (match_point != NULL);) {
|
|
/* Try for a match from where we are. */
|
|
match_point = strstr(match_point, (const char *) actual_move);
|
|
if (match_point != NULL) {
|
|
/* A possible match. Make sure that the match point
|
|
* is surrounded by non-move characters so as to be sure
|
|
* that we haven't picked up part way through a variation string.
|
|
* Assume success.
|
|
*/
|
|
found = TRUE;
|
|
if (match_point != variation_move) {
|
|
if (move_char(match_point[-1])) {
|
|
found = FALSE;
|
|
}
|
|
}
|
|
if (move_char(match_point[strlen((const char *) actual_move)])) {
|
|
found = FALSE;
|
|
}
|
|
if (!found) {
|
|
/* Move on the match point and try again. */
|
|
while (move_char(*match_point)) {
|
|
match_point++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return found;
|
|
|
|
}
|
|
|
|
/*** Functions concerned with matching the moves of the current game
|
|
*** against the variations of interest.
|
|
***/
|
|
|
|
/* Do the moves of the current game match the given variation?
|
|
* Go for a straight 1-1 match in the ordering, without considering
|
|
* permutations.
|
|
* Return TRUE if so, FALSE otherwise.
|
|
*/
|
|
static Boolean
|
|
straight_match(Move *current_game_head, variation_list variation)
|
|
{
|
|
variant_move *moves_of_the_variation;
|
|
/* Which is the next move that we wish to match. */
|
|
Move *next_move;
|
|
unsigned move_index = 0;
|
|
/* Assume that it matches. */
|
|
Boolean matches = TRUE;
|
|
|
|
/* Access the head of the current game. */
|
|
next_move = current_game_head;
|
|
/* Go for a straight move-by-move match in the order in which
|
|
* the variation is listed.
|
|
* Point at the head of the moves list.
|
|
*/
|
|
moves_of_the_variation = variation.moves;
|
|
move_index = 0;
|
|
while (matches && (next_move != NULL) && (move_index < variation.length)) {
|
|
Boolean this_move_matches;
|
|
|
|
if (*(moves_of_the_variation[move_index].move) == ANY_MOVE) {
|
|
/* Still matching as we don't care what the actual move is. */
|
|
}
|
|
else {
|
|
this_move_matches =
|
|
textual_variation_match(moves_of_the_variation[move_index].move,
|
|
next_move->move);
|
|
if (this_move_matches) {
|
|
/* We found a match, check that it isn't disallowed. */
|
|
if (*moves_of_the_variation[move_index].move == DISALLOWED_MOVE) {
|
|
/* This move is disallowed and implies failure. */
|
|
matches = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
if (*moves_of_the_variation[move_index].move != DISALLOWED_MOVE) {
|
|
/* No match found for this move. */
|
|
matches = FALSE;
|
|
}
|
|
else {
|
|
/* This is ok, because we didn't want a match. */
|
|
}
|
|
}
|
|
|
|
}
|
|
/* If we are still matching, go on to the next move. */
|
|
if (matches) {
|
|
move_index++;
|
|
next_move = next_move->next;
|
|
}
|
|
}
|
|
/* The game could be shorter than the variation, so don't rely
|
|
* on the fact that matches is still true.
|
|
*/
|
|
matches = (move_index == variation.length);
|
|
return matches;
|
|
}
|
|
|
|
/* Do the moves of the current game match the given variation?
|
|
* Try all possible orderings for the moves, within the
|
|
* constraint of proper WHITE/BLACK moves.
|
|
* The parameter variation is passed as a copy because we modify
|
|
* the num_ fields within it.
|
|
* Note that there is a possibility of a false match in this
|
|
* function if a variant move is specified in a form such as:
|
|
* *|c4
|
|
* This is because the num_ field is set from this on the basis of the
|
|
* ANY_MOVE character at the start. However, this could also match a
|
|
* normal move with its c4 component. If it is used for the latter
|
|
* purpose then it should not count as an any_ move. There is a warning
|
|
* issued about this when variations are read in.
|
|
* Return TRUE if we match, FALSE otherwise.
|
|
*
|
|
* The DISALLOWED_MOVE presents some problems with permutation matches
|
|
* because an ANY_MOVE could match an otherwise disallowed move. The
|
|
* approach that has been taken is to cause matching of a single disallowed
|
|
* move to result in complete failure of the current match.
|
|
*/
|
|
static Boolean
|
|
permutation_match(Move *current_game_head, variation_list variation)
|
|
{
|
|
variant_move *moves_of_the_variation;
|
|
/* Which is the next move that we wish to match? */
|
|
Move *next_move;
|
|
unsigned variant_index = 0;
|
|
/* Assume that it matches. */
|
|
Boolean matches = TRUE;
|
|
/* How many moves have we matched?
|
|
* When this reaches variation.length we have a full match.
|
|
*/
|
|
unsigned matched_moves = 0;
|
|
Boolean white_to_move = TRUE;
|
|
|
|
moves_of_the_variation = variation.moves;
|
|
/* Clear all of the matched fields of the variation. */
|
|
for (variant_index = 0; variant_index < variation.length; variant_index++) {
|
|
moves_of_the_variation[variant_index].matched = FALSE;
|
|
}
|
|
/* Access the head of the current game. */
|
|
next_move = current_game_head;
|
|
|
|
/*** Stage One.
|
|
* The first task is to ensure that there are no DISALLOWED_MOVEs in
|
|
* the current game.
|
|
*/
|
|
if ((variation.num_white_disallowed_moves > 0) ||
|
|
(variation.num_black_disallowed_moves > 0)) {
|
|
unsigned tested_moves = 0;
|
|
Boolean disallowed_move_found = FALSE;
|
|
|
|
/* Keep going as long as we still have not found a diallowed move,
|
|
* we haven't matched the whole variation, and we haven't reached the end of
|
|
* the game.
|
|
*/
|
|
while ((!disallowed_move_found) && (tested_moves < variation.length) &&
|
|
(next_move != NULL)) {
|
|
/* We want to see if next_move is a disallowed move of the variation. */
|
|
if (white_to_move) {
|
|
/* White; start with the first move. */
|
|
variant_index = 0;
|
|
}
|
|
else {
|
|
/* For a Black move, start at the second half-move in the list, if any. */
|
|
variant_index = 1;
|
|
}
|
|
/* Try each move of the variation in turn, until a match is found. */
|
|
while ((!disallowed_move_found) && (variant_index < variation.length)) {
|
|
if ((*moves_of_the_variation[variant_index].move ==
|
|
DISALLOWED_MOVE) &&
|
|
(textual_variation_match(
|
|
moves_of_the_variation[variant_index].move, next_move->move))) {
|
|
/* Found one. */
|
|
disallowed_move_found = TRUE;
|
|
}
|
|
if (!disallowed_move_found) {
|
|
/* Move on to the next available move -- 2 half moves along. */
|
|
variant_index += 2;
|
|
}
|
|
}
|
|
if (!disallowed_move_found) {
|
|
/* Ok so far, so move on. */
|
|
tested_moves++;
|
|
white_to_move = !white_to_move;
|
|
next_move = next_move->next;
|
|
}
|
|
}
|
|
if (disallowed_move_found) {
|
|
/* This rules out the whole match. */
|
|
matches = FALSE;
|
|
}
|
|
else {
|
|
/* In effect, each DISALLOWED_MOVE now becomes an ANY_MOVE. */
|
|
for (variant_index = 0; variant_index < variation.length; variant_index++) {
|
|
if (*moves_of_the_variation[variant_index].move == DISALLOWED_MOVE) {
|
|
moves_of_the_variation[variant_index].matched = TRUE;
|
|
if ((variant_index & 1) == 0) {
|
|
variation.num_white_any_moves++;
|
|
}
|
|
else {
|
|
variation.num_black_any_moves++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*** Stage Two.
|
|
* Having eliminated moves which have been disallowed, try permutations
|
|
* of the variation against the moves of the current game.
|
|
*/
|
|
/* Access the head of the current game. */
|
|
next_move = current_game_head;
|
|
white_to_move = TRUE;
|
|
/* Keep going as long as we still have matches, we haven't
|
|
* matched the whole variation, and we haven't reached the end of
|
|
* the game.
|
|
*/
|
|
while (matches && (matched_moves < variation.length) && (next_move != NULL)) {
|
|
/* Assume failure. */
|
|
matches = FALSE;
|
|
/* We want to find next_move in an unmatched move of the variation. */
|
|
if (white_to_move) {
|
|
/* Start with the first move. */
|
|
variant_index = 0;
|
|
}
|
|
else {
|
|
/* For a Black move, start at the second half-move in the list, if any. */
|
|
variant_index = 1;
|
|
}
|
|
/* Try each move of the variation in turn, until a match is found. */
|
|
while ((!matches) && (variant_index < variation.length)) {
|
|
if (moves_of_the_variation[variant_index].matched) {
|
|
/* We can't try this. */
|
|
}
|
|
else {
|
|
Boolean this_move_matches = textual_variation_match(
|
|
moves_of_the_variation[variant_index].move,
|
|
next_move->move);
|
|
if (this_move_matches) {
|
|
/* Found it. */
|
|
moves_of_the_variation[variant_index].matched = TRUE;
|
|
matches = TRUE;
|
|
}
|
|
}
|
|
if (!matches) {
|
|
/* Move on to the next available move -- 2 half moves along. */
|
|
variant_index += 2;
|
|
}
|
|
}
|
|
/* See if we made a straight match. */
|
|
if (!matches) {
|
|
/* See if we have some ANY_MOVEs available. */
|
|
if (white_to_move && (variation.num_white_any_moves > 0)) {
|
|
matches = TRUE;
|
|
variation.num_white_any_moves--;
|
|
}
|
|
else if (!white_to_move && (variation.num_black_any_moves > 0)) {
|
|
matches = TRUE;
|
|
variation.num_black_any_moves--;
|
|
}
|
|
else {
|
|
/* No slack. */
|
|
}
|
|
}
|
|
/* We have tried everything, did we succeed? */
|
|
if (matches) {
|
|
/* Yes, so move on. */
|
|
matched_moves++;
|
|
next_move = next_move->next;
|
|
white_to_move = !white_to_move;
|
|
}
|
|
}
|
|
if (matches) {
|
|
/* Ensure that we completed the variation. */
|
|
matches = matched_moves == (variation.length);
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
/* Determine whether or not the current game is wanted.
|
|
* It will be if we are either not looking for checkmate-only
|
|
* games, or if we are and the games does end in checkmate.
|
|
*/
|
|
Boolean
|
|
check_for_only_checkmate(const Game *game_details)
|
|
{
|
|
if (GlobalState.match_only_checkmate) {
|
|
const Move *moves = game_details->moves;
|
|
/* Check that the final move is checkmate. */
|
|
while (moves != NULL && moves->check_status != CHECKMATE) {
|
|
moves = moves->next;
|
|
}
|
|
if (moves == NULL) {
|
|
return FALSE;
|
|
}
|
|
else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
else {
|
|
/* No restriction to a checkmate game. */
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* Determine whether or not the current game is wanted.
|
|
* It will be if we are either not looking for stalemate-only
|
|
* games, or if we are and the games does end in stalemate.
|
|
*/
|
|
Boolean
|
|
check_for_only_stalemate(const Board *board, const Move *moves)
|
|
{
|
|
if (GlobalState.match_only_stalemate) {
|
|
return is_stalemate(board, moves);
|
|
}
|
|
else {
|
|
/* No restriction to a stalemate game. */
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine whether the final position on the given board
|
|
* is stalemate or not.
|
|
*/
|
|
Boolean
|
|
is_stalemate(const Board *board, const Move *moves)
|
|
{
|
|
if (moves != NULL) {
|
|
/* Check that the final move is not check or checkmate. */
|
|
const Move *move = moves;
|
|
while (move->next != NULL) {
|
|
move = move->next;
|
|
}
|
|
if (move->check_status != NOCHECK) {
|
|
/* Cannot be stalemate. */
|
|
return FALSE;
|
|
}
|
|
}
|
|
return !at_least_one_move(board, board->to_move);
|
|
}
|
|
|
|
/* Determine whether or not the current game is wanted.
|
|
* It will be if it matches one of the current variations
|
|
* and its tag details match those that we are interested in.
|
|
*/
|
|
Boolean
|
|
check_textual_variations(const Game *game_details)
|
|
{
|
|
Boolean wanted = FALSE;
|
|
variation_list *variation;
|
|
|
|
if (games_to_keep != NULL) {
|
|
for (variation = games_to_keep; (variation != NULL) && !wanted;
|
|
variation = variation->next) {
|
|
if (GlobalState.match_permutations) {
|
|
wanted = permutation_match(game_details->moves, *variation);
|
|
}
|
|
else {
|
|
wanted = straight_match(game_details->moves, *variation);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* There are no variations, assume that selection is done
|
|
* on the basis of the Details.
|
|
*/
|
|
wanted = TRUE;
|
|
}
|
|
return wanted;
|
|
}
|
|
|
|
/* Determine whether the number of ply in this game
|
|
* is within the bounds of what we want.
|
|
*/
|
|
Boolean
|
|
check_move_bounds(unsigned plycount)
|
|
{
|
|
|
|
if (GlobalState.check_move_bounds) {
|
|
return (GlobalState.lower_move_bound <= plycount) &&
|
|
(plycount <= GlobalState.upper_move_bound);
|
|
}
|
|
else {
|
|
// No restriction.
|
|
return TRUE;
|
|
}
|
|
}
|