/*
    Copyright 2023 Harm Jetten

    This file is part of Moby Dam.

    Moby Dam 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.

    Moby Dam 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 Moby Dam.  If not, see <http://www.gnu.org/licenses/>.
*/

/* pdnreader.c: parse PDN game files in a loose (liberal) manner,  */
/*              check validity of FEN and moves, and if desired    */
/*              write board positions to file for machine learning */

#include "train.h"

#define PDNLEN 256             /* max length of a pdn token */

bool debug_info = FALSE;

char pdn_event[PDNLEN];        /* pdn seven-tag roster... */
char pdn_site[PDNLEN];
char pdn_date[PDNLEN];
char pdn_round[PDNLEN];
char pdn_white[PDNLEN];
char pdn_black[PDNLEN];
char pdn_result[PDNLEN];
char pdn_gametype[PDNLEN];     /* + gametype tag */
char pdn_fen[PDNLEN];          /* + fen tag */
char *pdn_val[] = { pdn_event, pdn_site, pdn_date, pdn_round, pdn_white,
                     pdn_black, pdn_result, pdn_gametype, pdn_fen };
char *pdn_tag[] = { "Event", "Site", "Date", "Round", "White",
                    "Black", "Result", "GameType", "FEN" };
char *str_result[] = { "*", "1-0", "0-1", "1/2-1/2",
                       "2-0", "0-2", "1-1", "0-0" };
int num_result[] = { 0, 1, 3, 2, 1, 3, 2, 0 };
int pdn_numresult;             /* 1=white win, 2=draw, 3=black win */
char pdn_token[PDNLEN];        /* current pdn token */
char tag_token[PDNLEN];        /* current header tag */
char move_token[PDNLEN];       /* current move */
char nag_token[PDNLEN];        /* annotation */
char *pdn_text;                /* the pdn text */
u64 pdn_size;                  /* the pdn text length */
char *pdn_ptr;                 /* current position in pdn text */
int pdn_pos;                   /* character position within current pdn line */

int sel_list[51];              /* list of selected squares, [0] holds count */
int capt_list[51];             /* list of captured pieces, [0] holds count */
int move_number;               /* pdn-supplied move number */
int move_count;                /* calculated move number */

/***** pattern writer *****/

FILE *fp_out;                  /* file pointer for output file */
int pieces_min = 2;
int pieces_max = 40;           /* selected range of nr of pieces on the board */
bool pieces_equal = FALSE;     /* select positions with equal nr. of pieces */
bool pieces_nokings = FALSE;   /* select positions without kings */
bool pieces_onlykings = FALSE; /* select positions with kings */
bool pos_veryquiet = FALSE;    /* select positions that are very quiet */

/* index 1=white win, 2=draw, 3=black win */
char *str_outcome[] = { "", "1.0", "0.5", "0.0" };

/* calculate index of given pattern mask */
/* wm = white men */
/* bm = black men */
/* mask = the bit pattern to calculate the index for */
/* returns: the index */
static int calc_index(u64 wm, u64 bm, u64 mask)
{
    u64 pcbit;
    int x = 0;

    while (mask != 0)
    {
        pcbit = mask & -mask;
        mask -= pcbit;
        x = 3*x + 2*((wm & pcbit) != 0) + ((bm & pcbit) != 0);
    }
    return x;
}

/* write board positions and game outcomes to file */
/* bb -> current board */
/* mvptr -> the (non-capture) move about to be made on the current board */
static void write_pos(bitboard *bb, bitboard *mvptr)
{
    int pcnt, i;
    u64 wm, bm;
    movelist list;

    /* The board is divided into 9 slightly overlapping */
    /* 4x4 patterns/regions, of 8 squares each. */
    /* Pattern weights will be obtained from analysis of */
    /* many game positions using logistic regression by */
    /* gradient descent algorithm. */
    /*
       -------------
       | 0 | 1 | 2 |
       -------------
       | 3 | 4 | 5 |
       -------------
       | 6 | 7 | 8 |
       -------------
    */

    static u64 patlist[] = {
        (S01 | S02 | S06 | S07 | S11 | S12 | S16 | S17),
        (S02 | S03 | S08 | S09 | S12 | S13 | S18 | S19),
        (S04 | S05 | S09 | S10 | S14 | S15 | S19 | S20),

        (S16 | S17 | S21 | S22 | S26 | S27 | S31 | S32),
        (S18 | S19 | S22 | S23 | S28 | S29 | S32 | S33),
        (S19 | S20 | S24 | S25 | S29 | S30 | S34 | S35),

        (S31 | S32 | S36 | S37 | S41 | S42 | S46 | S47),
        (S32 | S33 | S38 | S39 | S42 | S43 | S48 | S49),
        (S34 | S35 | S39 | S40 | S44 | S45 | S49 | S50),
    };

    if (fp_out == NULL)                         /* not writing to a file */
    {
        return;
    }
    if (pdn_numresult == 0)                     /* unknown game result */
    {
        return;
    }
    pcnt = popcount(bb->white | bb->black);
    if (pcnt > pieces_max || pcnt < pieces_min) /* check piece count range */
    {
        return;
    }
    if (pieces_nokings && bb->kings != 0)       /* skip if kings present */
    {
        return;
    }
    if (pieces_onlykings && bb->kings == 0)     /* skip if no kings present */
    {
        return;
    }
    if (pieces_equal && (popcount(bb->white) != popcount(bb->black)))
    {                                    /* skip if unequal number of pieces */
        return;
    }
    if (pos_veryquiet)                /* skip if move is followed by capture */
    {
        gen_moves(mvptr, &list, NULL, FALSE); /* capture moves only */
        if (list.count != 0)
        {
            return;
        }
    }

    wm = bb->white & ~bb->kings;
    bm = bb->black & ~bb->kings;

    /* patterns */
    for (i = 0; i < elements(patlist); i++)
    {
        fprintf(fp_out, "%d,", calc_index(wm, bm, patlist[i]));
    }

    /* features */
#ifdef MEN
    fprintf(fp_out, "%d,", popcount(wm) - popcount(bm));
#endif
#ifdef DISTR
    int distr = 0; /* distribution (balance) over wings */
    distr -= abs(popcount(wm & (COL1 | COL2 | COL3)) -
                 popcount(wm & (COL8 | COL9 | COL10)));
    distr += abs(popcount(bm & (COL1 | COL2 | COL3)) -
                 popcount(bm & (COL8 | COL9 | COL10)));
    fprintf(fp_out, "%d,", distr);
#endif
#ifdef TEMPO
    int tempo = 0; /* tempo: degree of advancement */
    tempo += 1*popcount(wm & (ROW9 | ROW7 | ROW5 | ROW3)) +
             2*popcount(wm & (ROW8 | ROW7 | ROW4 | ROW3)) +
             4*popcount(wm & (ROW6 | ROW5 | ROW4 | ROW3)) +
             8*popcount(wm & (ROW2));
    tempo -= 1*popcount(bm & (ROB9 | ROB7 | ROB5 | ROB3)) +
             2*popcount(bm & (ROB8 | ROB7 | ROB4 | ROB3)) +
             4*popcount(bm & (ROB6 | ROB5 | ROB4 | ROB3)) +
             8*popcount(bm & (ROB2));
    fprintf(fp_out, "%d,", tempo);
#endif

    /* label (game result) */
    fprintf(fp_out, "%s\n", str_outcome[pdn_numresult]);
}

/***** PDN reader/parser *****/

/* add square to move selection list */
/* sq = square number to add */
static void add_sel(int sq)
{
    if (sel_list[0] < elements(sel_list) - 1)
    {
        sel_list[0]++;
    }
    sel_list[sel_list[0]] = sq;
}

/* clear selection list */
static void clear_sel(void)
{
    sel_list[0] = 0;
}

/* add square to capture list */
/* sq = square number to add */
static void add_capt(int sq)
{
    if (capt_list[0] < elements(capt_list) - 1)
    {
        capt_list[0]++;
    }
    capt_list[capt_list[0]] = sq;
}

/* clear capture list */
static void clear_capt(void)
{
    capt_list[0] = 0;
}

/* read character from pdn input buffer  */
/* returns: != 0 = character             */
/*          == 0 = end of buffer reached */
static char read_char(void)
{
    char ch;

    if (pdn_ptr > pdn_text + pdn_size) /* is padded at end */
    {
        return '\0';
    }
    ch = *pdn_ptr++;
    if (ch == '\0')
    {
        ch = ' ';
    }
    if (ch == '\r' || ch == '\n')
    {
        pdn_pos = 0;
    }
    else
    {
        pdn_pos++;
    }
    return ch;
}

/* back up position in pdn input buffer */
static void put_char(void)
{
    pdn_ptr--;
    if (pdn_pos > 0)
    {
        pdn_pos--;
    }
}

/* test if character is whitespace */
/* ch = the character */
/* returns: TRUE  = whitespace */
/*          FALSE = other      */
static bool is_white(char ch)
{
    switch (ch)
    {
    case ' ':
    case ',':
    case '\t':
    case '\n':
    case '\f':
    case '\r':
        return TRUE;
    default:
        break;
    }
    return FALSE;
}

/* test if character is part of symbol token */
/* ch = the character */
/* returns: TRUE  = symbol char */
/*          FALSE = other       */
static bool is_symbol(char ch)
{
    if (ch >= 'A' && ch <= 'Z')
    {
        return TRUE;
    }
    if (ch >= 'a' && ch <= 'z')
    {
        return TRUE;
    }
    if (ch >= '0' && ch <= '9')
    {
        return TRUE;
    }
    switch (ch)
    {
    case '_':
    case '+':
    case '#':
    case '=':
    case ':':
    case '-':
    case '/':
        return TRUE;
    default:
        break;
    }
    return FALSE;
}

/* read token from pdn input buffer */
/* returns: TRUE  = success               */
/*          FALSE = end of buffer reached */
static bool read_tok(void)
{
    char ch;
    int  i;

    i = 0;
    pdn_token[0] = '\0';
    do {                                /* skip white space */
        ch = read_char();
    } while (is_white(ch));
    if (ch == '\0')
    {
        return FALSE;
    }
    if (ch == '"')                      /* string token */
    {
        do {
            ch = read_char();
            switch (ch)
            {
            case '\0':                  /* end of string */
            case '"':
            case '\r':
            case '\n':
                break;
            case '\\':                  /* quote character */
                ch = read_char();
                if (ch == '\0' || ch == '\r' || ch == '\n')
                {
                    break;
                }
                if (i < PDNLEN - 1)
                {
                    pdn_token[i++] = ch;
                }
                continue;
            default:                    /* regular character */
                if (i < PDNLEN - 1)
                {
                    pdn_token[i++] = ch;
                }
                continue;
            }
            break;
        } while (TRUE);
    }
    else if (ch == ';' || (pdn_pos == 1 && ch == '%'))
    {                                   /* comment to end of line */
        do {
            ch = read_char();
        } while (ch != '\0' && ch != '\r' && ch != '\n');
        return read_tok();
    }
    else if (ch == '{')                 /* comment in braces, not recursive */
    {
        do {
            ch = read_char();
        } while (ch != '\0' && ch != '}');
        pdn_token[i++] = '{';
        pdn_token[i++] = '}';
    }
    else if (ch == '(')                 /* recursive annotation variations */
    {
        do {
            if (!read_tok())
            {
                return FALSE;
            }
        } while (strcmp(pdn_token, ")") != EQUAL);
        return read_tok();
    }
    else if (ch == '<' || ch == '>')    /* proposed captured pieces list */
    {
        pdn_token[i++] = ch;
    }
    else if (ch == '$')                 /* numeric annotation (NAG) token */
    {
        do {
            pdn_token[i++] = ch;
            ch = read_char();
        } while (ch >= '0' && ch <= '9');
        put_char();
    }
    else if (ch == '!')                 /* traditional suffix annotation */
    {
        pdn_token[i++] = '$';           /* convert to NAG code */
        pdn_token[i] = '1';             /* good move */
        ch = read_char();
        if (ch == '!')
        {
            pdn_token[i] = '3';         /* very good move */
        }
        else if (ch == '?')
        {
            pdn_token[i] = '5';         /* speculative move */
        }
        else
        {
            put_char();
        }
        i++;
    }
    else if (ch == '?')                 /* traditional suffix annotation */
    {
        pdn_token[i++] = '$';           /* convert to NAG code */
        pdn_token[i] = '2';             /* poor move */
        ch = read_char();
        if (ch == '!')
        {
            pdn_token[i] = '6';         /* questionable move */
        }
        else if (ch == '?')
        {
            pdn_token[i] = '4';         /* very poor move */
        }
        else
        {
            put_char();
        }
        i++;
    }
    else                                /* regular token */
    {
        pdn_token[i++] = ch;
        while (is_symbol(ch))
        {
            ch = read_char();
            if (is_symbol(ch))
            {
                if (i < PDNLEN - 1)
                {
                    pdn_token[i++] = ch;
                }
            }
            else
            {
                put_char();
            }
        }
    }
    pdn_token[i] = '\0';
    return TRUE;
}

/* read pdn header from input buffer */
/* returns: TRUE if (partly) successful */
static bool read_pdnhdr(void)
{
    int i, m;

    for (i = 0; i < elements(pdn_val); i++)
    {                                   /* clear all tag values */
        memset(pdn_val[i], 0, PDNLEN);
    }
    pdn_numresult = 0;
    if (pdn_token[0] != '[')            /* find first tag section, */
    {
        if (!read_tok())
        {
            return FALSE;               /* end of file */
        }
        if (pdn_token[0] != '[')        /* game without tag section */
        {
            return TRUE;
        }
    }
    do {
        if (!read_tok())                /* read tag */
        {
            break;
        }
        strcpy(tag_token, pdn_token);
        if (!read_tok())                /* next token is tag value */
        {
            break;
        }
        for (i = 0; i < elements(pdn_val); i++)
        {                               /* case insensitive compare */
            if (strcasecmp(tag_token, pdn_tag[i]) == EQUAL)
            {                           /* save value of recognized tag */
                strcpy(pdn_val[i], pdn_token);
                if (pdn_val[i] == pdn_result)
                {
                    for (m = 0; m < elements(str_result); m++)
                    {
                        if (strcmp(pdn_token, str_result[m]) == EQUAL)
                        {
                            pdn_numresult = num_result[m];
                            break;
                        }
                    }
                }
                break;
            }
        }
        if (!read_tok())                /* read end bracket */
        {
            break;
        }
        if (strcmp(pdn_token, "]") != EQUAL)
        {
            break;
        }
        if (!read_tok())                /* possible subsequent start bracket */
        {
            break;
        }
    } while (strcmp(pdn_token, "[") == EQUAL);
    return TRUE;
}

/* process the pdn movetext section */
/* returns: TRUE if successful */
static bool load_moves(bitboard *bb)
{
    char *ptr;
    int pos, m, match, i, j;
    bitboard brd, fenbrd;
    movelist list;
    lnlist ln;
    u64 captbits, jb, kb, ray;

    brd = *bb;

    move_number = move_count = 1;
    clear_sel();
    clear_capt();
    debugf("%s", (brd.side == B) ? "1... " : "");

    while (pdn_token[0] != '\0' &&
           strcmp(pdn_token, "[") != EQUAL)
    {   /* stop at any of the standard or not-so-standard result codes */
        for (m = 0; m < elements(str_result); m++)
        {
            if (strcmp(pdn_token, str_result[m]) == EQUAL)
            {
                break;
            }
        }
        if (m < elements(str_result) ||             /* pre-defined code or */
            strcmp(pdn_token, pdn_result) == EQUAL) /* custom result code */
        {                                           /* provided in header */
            break;                                  /* ends game */
        }

        if (strcmp(pdn_token, "{}") == EQUAL)       /* comment */
        {
            if (!read_tok())
            {
                break;
            }
            continue; /* with token following comment */
        }

        if (strcasecmp(pdn_token, "/FEN") == EQUAL)
        {
            if (!read_tok()) /* read in the FEN string */
            {
                break;
            }
            debugf("\n/FEN \"%s\"/\n", pdn_token);
            if (strcmp(pdn_token, "W::") == EQUAL ||
                strcmp(pdn_token, "B::") == EQUAL)
            {
                /* null move */
                brd.side = W + B - brd.side;
                debugf("null move%s", (brd.side == B) ? "\n ... " : "");
            }
            else
            {
                if (!setup_fen(&fenbrd, pdn_token))
                {
                    printf("\ninvalid FEN at move %d for %s\n",
                           move_count, (brd.side == W) ? "white" : "black");
                    return FALSE;
                }
                brd = fenbrd;
                if (debug_info)
                {
                    print_board(&brd);
                    printf("%s", (brd.side == B) ? "\n ... " : "");
                }
                move_count = move_number = 1;
            }
            do {
                if (!read_tok())
                {
                    break;
                }
            } while (strcmp(pdn_token, "/") == EQUAL); /* eat closing slash */
            continue; /* with token following slash */
        }

        strcpy(move_token, pdn_token);
        while (pos = strlen(move_token),         /* if token ends in - or x */
               pos > 1 &&                        /* the move is incomplete, */
               (move_token[pos - 1] == '-' ||    /* append next token */
                move_token[pos - 1] == 'x' ||
                move_token[pos - 1] == 'X'))
        {
            if (!read_tok() ||
                pdn_token[0] < '0' || pdn_token[0] > '9')
            {
                break;
            }
            if (pos + strlen(pdn_token) < PDNLEN)
            {
                strcat(move_token, pdn_token);
            }
        }

        /* add numbers in move token to list(s) */
        ptr = move_token;
        do {
            while (*ptr != '\0' && (*ptr < '0' || *ptr > '9'))
            {
                ptr++;
            }
            if (*ptr != '\0')
            {
                pos = 0;
                while (*ptr >= '0' && *ptr <= '9')
                {
                    pos = 10*pos + *ptr++ - '0';
                }
                add_sel(pos);
                if (sel_list[0] > 1) /* if square is occupied by opponent, */
                {                    /* move square to the capture list */
                    jb = conv_to_bit(pos);
                    if ((brd.side == W && (jb & brd.black) != 0) ||
                        (brd.side == B && (jb & brd.white) != 0))
                    {
                        sel_list[0]--;
                        add_capt(pos);
                    }
                }
            }
        } while (*ptr != '\0');

        if (read_tok()) /* next token after move number or move */
        {
            if (strcmp(pdn_token, ".") == EQUAL) /* single number followed by */
            {                                    /* a period is a move number */
                if (sel_list[0] == 1 && capt_list[0] == 0)
                {
                    move_count = move_number = sel_list[1];
                    clear_sel();
                }
                do {
                    if (!read_tok())
                    {
                        break;
                    }
                } while (strcmp(pdn_token, ".") == EQUAL); /* eat period(s) */
                continue; /* with token following period */
            }

            /* capture list (PDN 3.1 proposal) */
            if (strcmp(pdn_token, "<") == EQUAL)
            {
                do {
                    if (!read_tok())
                    {
                        break;
                    }
                    /* add numbers in tokens to captured pieces list */
                    ptr = pdn_token;
                    do {
                        while (*ptr != '\0' && (*ptr < '0' || *ptr > '9'))
                        {
                            ptr++;
                        }
                        if (*ptr != '\0')
                        {
                            pos = 0;
                            while (*ptr >= '0' && *ptr <= '9')
                            {
                                pos = 10*pos + *ptr++ - '0';
                            }
                            add_capt(pos);
                        }
                    } while (*ptr != '\0');
                } while (pdn_token[0] != '>');
                read_tok();                /* token following capture list */
            }

            while (strcmp(pdn_token, "{}") == EQUAL)       /* comment */
            {
                read_tok();
            }

            if (pdn_token[0] == '$')                       /* nag code */
            {
                strcpy(nag_token, pdn_token);
                read_tok();
            }
        }

        gen_moves(&brd, &list, &ln, TRUE); /* generate all valid moves */
        match = 0;

        /* the simple case handles plain moves and most captures; */
        /* a capture piece list (proposed for PDN 3.1) is only needed to */
        /* disambiguate between moves with the same from x to squares. */

        /* simple case and ambiguous move resolution, method 1: */
        /* compare selection list, and capture piece list, if present, */
        /* to the valid moves */
        for (m = 0; m < list.count; m++)            /* moves */
        {
            captbits = move_captbits(&list.move[m]);
            if (sel_list[0] == 2 &&
                sel_list[1] == move_square(&list.move[m], FROM) &&
                sel_list[2] == move_square(&list.move[m], TO))
            {
                for (i = 1; i <= capt_list[0]; i++) /* captured pieces */
                {
                    if ((conv_to_bit(capt_list[i]) & captbits) == 0)
                    {
                        break; /* no match, on to next move */
                    }
                }
                if (i > capt_list[0])
                {
                    /* move has no listed captures, or */
                    /* all listed captured pieces match this move */
                    match++;
                    pos = m;
                }
            }
        }

        if (match != 1 && list.npcapt > 0 && capt_list[0] == 0)
        {
            /* ambiguous move resolution, method 2: */
            /* compare selection list to long notation of the valid moves; */
            /* we don't just want to recognize fully written out captures, */
            /* but also the minimum notation needed to disambiguate moves */
            /* (like e.g. in pdn written by Dam 2.2) */

            /* first=from, last=to, intermediates=turning points in any order */
            match = 0;
            for (m = 0; m < list.count; m++)              /* moves */
            {
                if (sel_list[1] == move_square(&list.move[m], FROM) &&
                    sel_list[sel_list[0]] == move_square(&list.move[m], TO))
                {
                    for (i = 2; i < sel_list[0]; i++)     /* intermediates */
                    {
                        /* turning points, excluding first=from and last=to */
                        for (j = 1; j < list.npcapt; j++)
                        {
                            if (sel_list[i] == ln.move[m].square[j])
                            {
                                break; /* match, on to next intermediate */
                            }
                        }
                        if (j == list.npcapt)
                        {
                            /* no match found among turning points */
                            break; /* on to next move */
                        }
                    }
                    if (i >= sel_list[0])
                    {
                        /* all intermediates match a move's turning point */
                        match++;
                        pos = m;
                    }
                }
            }
        }

        if (match != 1 && list.npcapt > 0 && capt_list[0] == 0)
        {
            /* ambiguous move resolution, method 3: */
            /* perform the steps of the capture move as written; */
            /* even if the move is not fully written out, we may catch */
            /* enough captured pieces to make it unambiguous */

            /* build bitlist of captured pieces */
            captbits = 0;
            kb = conv_to_bit(sel_list[1]);
            for (i = 1; i < sel_list[0]; i++)
            {
                /* take successive squares as pairs */
                jb = kb;
                kb = conv_to_bit(sel_list[i + 1]);

                /* try a diagonal */
                ray = RAYMASK_SW * min(jb, kb);
                /* mask off diagonal where it meets a ghost square and beyond */
                ray &= (ray & (G1 | G2 | G3 | G4)) - 1;
                /* does it meet the other selection square? */
                if ((ray & max(jb, kb)) == 0)
                {
                    /* if no, try the other diagonal */
                    ray = RAYMASK_SE * min(jb, kb);
                    ray &= (ray & (G1 | G2 | G3 | G4)) - 1;
                    if ((ray & max(jb, kb)) == 0)
                    {
                        /* pair not on a diagonal? */
                        continue; /* with the next pair */
                    }
                }
                /* mask off highest square and beyond */
                ray &= max(jb, kb) - 1;
                /* add the piece(s) between the pair to the list */
                captbits |= ray & (brd.white | brd.black);
            }

            match = 0;
            for (m = 0; m < list.count; m++)
            {
                if (sel_list[1] == move_square(&list.move[m], FROM) &&
                    sel_list[sel_list[0]] == move_square(&list.move[m], TO) &&
                    (captbits & ~move_captbits(&list.move[m])) == 0)
                {
                    /* all collected captbits match a move's capture */
                    match++;
                    pos = m;
                }
            }
        }

        if (match == 1)           /* move is uniquely identified */
        {
            if (debug_info)
            {
                if (brd.side == W)
                {
                    printf("\n%d. ", move_count);
                }
                print_move_long(&list, pos);
            }

            if (list.npcapt == 0) /* not a capture move, record the position */
            {
                write_pos(&brd, &list.move[pos]);
            }

            brd = list.move[pos]; /* perform the move */

            debugf("%s ", nag_token);
            nag_token[0] = '\0';
            clear_sel();
            clear_capt();
            if (brd.side == W)
            {
                move_count++;
            }
        }
        else
        {
            printf("\nmove %d for %s is %s: %s%s\n",
                   move_count, (brd.side == W) ? "white" : "black",
                   (match > 1) ? "ambiguous" : "invalid",
                   move_token, (capt_list[0] > 0) ? "<...>" : "");
            return FALSE;
        }
    }
    debugf("\n*\n\n");
    return TRUE;
}

/* load contents of pdn file into memory */
/* pdnfile = name of the file to load into pdn_text */
/* returns: TRUE if successful */
static bool load_pdn(char *pdnfile)
{
    struct stat statbuf;
    FILE *fp;
    int ret;

    ret = stat(pdnfile, &statbuf);
    if (ret != 0)
    {
        printf("load_pdn: can't find pdn file %s\n", pdnfile);
        return FALSE;
    }
    fp = fopen(pdnfile, "r");
    if (fp == NULL)
    {
        printf("load_pdn: can't open pdn file %s\n", pdnfile);
        return FALSE;
    }
    free(pdn_text);
    pdn_text = malloc(statbuf.st_size + 2);
    if (pdn_text == NULL)
    {
        printf("load_pdn: can't allocate %" PRIu64
               " + 2 bytes of memory for pdn file %s\n",
               (u64)statbuf.st_size, pdnfile);
        fclose(fp);
        return FALSE;
    }
    pdn_size = fread(pdn_text, 1, statbuf.st_size, fp);
    fclose(fp);
    if (pdn_size != statbuf.st_size)
    {
        printf("load_pdn: can't read all %" PRIu64 " bytes of pdn file %s\n",
               (u64)statbuf.st_size, pdnfile);
        return FALSE;
    }
    pdn_text[pdn_size] = ' ';      /* padding */
    pdn_text[pdn_size + 1] = '\0'; /* insurance */
    pdn_ptr = pdn_text;
    pdn_token[0] = '\0';
    pdn_pos = 0;
    return TRUE;
}

/* the program entry point */
int main(int argc, char *argv[])
{
    int opt, game, i, m, n;
    char *pdnfile;
    bitboard brd;

    while (TRUE)
    {
        opt = getopt(argc, argv, "da:ekKqr:t");
        if (opt == -1)
        {
            break;
        }
        switch (opt)
        {
        case 'd':
            debug_info = TRUE;
            break;
        case 'a':
            fp_out = fopen(optarg, "a");
            if (fp_out == NULL)
            {
                printf("can't open posfile %s for appending\n", optarg);
                exit(EXIT_FAILURE);
            }
            break;
        case 'e':
            pieces_equal = TRUE;
            break;
        case 'k':
            pieces_nokings = TRUE;
            break;
        case 'K':
            pieces_onlykings = TRUE;
            break;
        case 'q':
            pos_veryquiet = TRUE;
            break;
        case 'r':                  /* range */
            i = sscanf(optarg, "%d:%d", &m, &n);
            if (i != 2)
            {
                printf("option -r needs two numbers\n");
                exit(EXIT_FAILURE);
            }
            pieces_min = min(m, n);
            pieces_max = max(m, n);
            break;
        case 't':                  /* for tanh activation */
            str_outcome[2] = "0.0";
            str_outcome[3] = "-1.0";
            break;
        default:
            printf("Usage: %s [-d] [-a posfile] [-e] [-k] [-K] [-q] [-r num:num] [-t] pdnfiles...\n", argv[0]);
            printf("  -d = print lots of extra debug info in pdn-like form\n"
                   "  -a posfile = write board positions and game outcomes of non-capture\n"
                   "               positions encountered (appending to posfile)\n"
                   "  -e = limits writing of board positions to positions with\n"
                   "               equal number of pieces each side\n"
                   "  -k = limits writing of board positions to positions without kings\n"
                   "  -K = limits writing of board positions to positions with kings\n"
                   "  -q = limits writing of board positions to quiet positions where\n"
                   "               the next move is not followed by a capture\n"
                   "  -r num:num = limits writing of board positions to\n"
                   "               this range of total number of pieces (inclusive)\n"
                   "  -t = write game outcomes as -1.0/0.0/+1.0, for tanh activation\n"
                   "       (default is 0.0/+0.5/+1.0, for sigmoid activation)\n"
                   "  pdnfiles = file(s) containing games in pdn format\n");
            exit(EXIT_FAILURE);
        }
    }

    while (optind < argc)
    {
        pdnfile = argv[optind++];
        if (!load_pdn(pdnfile))
        {
            exit(EXIT_FAILURE);
        }
        printf("processing %s\n", pdnfile);
        game = 0;
        while (read_pdnhdr())
        {
            game++;
            if (debug_info)
            {
                for (i = 0; i < elements(pdn_val); i++)
                {
                    if (pdn_val[i][0] != '\0')
                    {
                        printf("[%s \"%s\"]\n", pdn_tag[i], pdn_val[i]);
                    }
                }
            }

            if (pdn_gametype[0] != '\0' &&
                strncmp(pdn_gametype, "20", 2) != EQUAL)
            {
                debugf("gametype is not 10x10 international draughts, "
                       "skipping %s game %d\n*\n\n", pdnfile, game);
                while (pdn_token[0] != '\0' &&
                       strcmp(pdn_token, "[") != EQUAL)
                {   /* stop at any result code */
                    for (m = 0; m < elements(str_result); m++)
                    {
                        if (strcmp(pdn_token, str_result[m]) == EQUAL)
                        {
                            break;
                        }
                    }
                    if (m < elements(str_result) ||   /* pre-defined code or */
                        strcmp(pdn_token, pdn_result) == EQUAL)
                    {     /* custom result code provided in header ends game */
                        break;
                    }
                    if (!read_tok())
                    {
                        break;
                    }
                }
                continue; /* with the next game */
            }

            if (pdn_fen[0] != '\0')
            {
                if (!setup_fen(&brd, pdn_fen))
                {
                    printf("error setting up %s game %d\n", pdnfile, game);
                    /* stop processing remaining pdn games/files */
                    optind = argc;
                    break;
                }
                if (debug_info)
                {
                    print_board(&brd);
                }
            }
            else
            {
                init_board(&brd);
            }

            if (!load_moves(&brd))
            {
                printf("error in movetext of %s game %d\n", pdnfile, game);
                /* stop processing remaining pdn games/files */
                optind = argc;
                break;
            }
        }
    }
    if (fp_out != NULL)
    {
        fclose(fp_out);
    }
    return EXIT_SUCCESS;
}
