/* * 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 . * * David J. Barnes may be contacted as d.j.barnes@kent.ac.uk * https://www.cs.kent.ac.uk/people/staff/djb/ */ /* This file contains functions concerned with decoding * the original text of a move in order to determine: * which MoveClass it is in; * any start and end square information. * It extracts this information purely from the move text * rather than analysing the move within the context of * a board position. * This information is later refined by the semantic analysis * phase of the program as part of the checking of a game score. */ #include #include #include "bool.h" #include "mymalloc.h" #include "defs.h" #include "typedef.h" #include "decode.h" #include "tokens.h" #include "taglist.h" #include "lex.h" /* Does the character represent a column of the board? */ Boolean is_col(char c) { return (FIRSTCOL <= c) && (c <= LASTCOL); } /* Does the character represent a rank of the board? */ Boolean is_rank(char c) { return (FIRSTRANK <= c) && (c <= LASTRANK); } /* What kind of piece is *move likely to represent? * Note, the provision for double-character pieces, * like a Russian King, means we need access to a * string rather than a single char. */ Piece is_piece(const unsigned char *move) { Piece piece = EMPTY; switch (*move) { case 'K': case 'k': piece = KING; break; case 'Q': case 'q': case 'D': /* Dutch/German. */ case RUSSIAN_QUEEN: piece = QUEEN; break; case 'R': case 'r': case 'T': /* Dutch/German. */ case RUSSIAN_ROOK: piece = ROOK; break; case 'N': case 'n': case 'P': /* Dutch. */ case 'S': /* German. */ piece = KNIGHT; break; case 'B': case 'L': /* Dutch/German. */ case RUSSIAN_BISHOP: /* Lower case 'b' is most likely to be a pawn reference. */ piece = BISHOP; break; case RUSSIAN_KNIGHT_OR_KING: if (RUSSIAN_PIECE_CHECK(*(move + 1)) == RUSSIAN_KING_SECOND_LETTER) { piece = KING; } else { piece = KNIGHT; } break; } return piece; } /* Is the symbol a capturing one? * In fact, this is used to recognise any general separator * between two parts of a move, e.g.: * Nxc3, e2-e4, etc. */ static Boolean is_capture(char c) { return (c == 'x') || (c == 'X') || (c == ':') || (c == '-'); } static Boolean is_castling_character(char c) { return (c == 'O') || (c == '0') || (c == 'o'); } Boolean is_check(char c) { return (c == '+') || (c == '#'); } /* Allocate space in which to return the information that * has been gleaned from the move. */ Move * new_move_structure(void) { Move *move = (Move *) malloc_or_die(sizeof (Move)); move->terminating_result = NULL; move->piece_to_move = EMPTY; move->captured_piece = EMPTY; move->promoted_piece = EMPTY; move->check_status = NOCHECK; move->epd = NULL; move->fen_suffix = NULL; move->zobrist = ~0; move->evaluation = 0; move->NAGs = NULL; move->comment_list = NULL; move->Variants = NULL; move->prev = NULL; move->next = NULL; return move; } /* Work out whatever can be gleaned from move_string of * the starting and ending points of the given move. * The move may be any legal string. * The scanning here is libertarian, so it relies heavily on * illegal moves having already been filtered out by the process * of lexical analysis. */ Move * decode_move(const unsigned char *move_string) { /* The four components of the co-ordinates when known. */ Rank from_rank = 0, to_rank = 0; Col from_col = 0, to_col = 0; MoveClass class; Boolean Ok = TRUE; /* Temporary locations until known whether they are from_ or to_. */ Col col = 0; Rank rank = 0; /* A pointer to move along the move string. */ const unsigned char *move = move_string; /* A pointer to the structure containing the details to be returned. */ Move *move_details; Piece piece_to_move = EMPTY; /* Make an initial distinction between pawn moves and piece moves. */ if (is_col(*move)) { /* Pawn move. */ class = PAWN_MOVE; piece_to_move = PAWN; col = *move; move++; if (is_rank(*move)) { /* e4, e2e4 */ rank = *move; move++; if (is_capture(*move)) { move++; } if (is_col(*move)) { from_col = col; from_rank = rank; to_col = *move; move++; if (is_rank(*move)) { to_rank = *move; move++; } } else { to_col = col; to_rank = rank; } } else { if (is_capture(*move)) { /* axb */ move++; } if (is_col(*move)) { /* ab, or bg8 for liberal bishop moves. */ from_col = col; to_col = *move; move++; if (is_rank(*move)) { to_rank = *move; move++; /* Check the sanity of this. */ if ((from_col != 'b') && (from_col != (to_col + 1)) && (from_col != (to_col - 1))) { Ok = FALSE; } } else { /* Check the sanity of this. */ if ((from_col != (to_col + 1)) && (from_col != (to_col - 1))) { Ok = FALSE; } } } else { print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown pawn move %s.\n", move_string); Ok = FALSE; } } if (Ok) { /* Look for promotions. */ if (*move == '=') { move++; } /* djb From v17-27 allow a trailing 'b' as a Bishop promotion. */ if (is_piece(move) != EMPTY || *move == 'b') { class = PAWN_MOVE_WITH_PROMOTION; /* @@@ Strictly speaking, if the piece is a RUSSIAN_KING * then we should skip two chars. */ move++; } } } else if ((piece_to_move = is_piece(move)) != EMPTY) { class = PIECE_MOVE; /* Check for a two-character piece. */ if ((RUSSIAN_PIECE_CHECK(*move) == RUSSIAN_KNIGHT_OR_KING) && (piece_to_move == KING)) { move++; } move++; if (is_rank(*move)) { /* A disambiguating rank. * R1e1, R1xe3. */ from_rank = *move; move++; if (is_capture(*move)) { move++; } if (is_col(*move)) { to_col = *move; move++; if (is_rank(*move)) { to_rank = *move; move++; } } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown piece move %s.\n", move_string); } } else { if (is_capture(*move)) { /* Rxe1 */ move++; if (is_col(*move)) { to_col = *move; move++; if (is_rank(*move)) { to_rank = *move; move++; } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown piece move %s.\n", move_string); } } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown piece move %s.\n", move_string); } } else if (is_col(*move)) { col = *move; move++; if (is_capture(*move)) { move++; } if (is_rank(*move)) { /* Re1, Re1d1, Re1xd1 */ rank = *move; move++; if (is_capture(*move)) { move++; } if (is_col(*move)) { /* Re1d1 */ from_col = col; from_rank = rank; to_col = *move; move++; if (is_rank(*move)) { to_rank = *move; move++; } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown piece move %s.\n", move_string); } } else { to_col = col; to_rank = rank; } } else if (is_col(*move)) { /* Rae1 */ from_col = col; to_col = *move; move++; if (is_rank(*move)) { to_rank = *move; move++; } } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown piece move %s.\n", move_string); } } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown piece move %s.\n", move_string); } } } else if (is_castling_character(*move)) { /* Some form of castling. */ move++; /* Allow separators to be optional. */ if (*move == '-') { move++; } if (is_castling_character(*move)) { move++; if (*move == '-') { move++; } if (is_castling_character(*move)) { class = QUEENSIDE_CASTLE; move++; } else { class = KINGSIDE_CASTLE; } } else { print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown castling move %s.\n", move_string); Ok = FALSE; } } else if (strcmp((char *) move_string, NULL_MOVE_STRING) == 0) { class = NULL_MOVE; } else { print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown move %s.\n", move_string); Ok = FALSE; } if (Ok && class != NULL_MOVE) { /* Allow trailing checks. */ while (is_check(*move)) { move++; } if (*move == '\0') { /* Nothing more to check. */ } else if (((strcmp((const char *) move, "ep") == 0) || (strcmp((const char *) move, "e.p.") == 0)) && (class == PAWN_MOVE)) { /* These are ok. */ class = ENPASSANT_PAWN_MOVE; } else { Ok = FALSE; print_error_context(GlobalState.logfile); fprintf(GlobalState.logfile, "Unknown text trailing move %s <%s>.\n", move_string, move); } } /* Store all of the details gathered, even if the move is illegal. */ if (!Ok) { class = UNKNOWN_MOVE; } move_details = new_move_structure(); strcpy((char *) move_details->move, (const char *) move_string); move_details->class = class; move_details->piece_to_move = piece_to_move; move_details->from_col = from_col; move_details->from_rank = from_rank; move_details->to_col = to_col; move_details->to_rank = to_rank; move_details->captured_piece = EMPTY; move_details->check_status = NOCHECK; return move_details; } Move * decode_algebraic(Move *move_details, Board *board) { int from_r = RankConvert(move_details->from_rank); int from_c = ColConvert(move_details->from_col); Piece piece_to_move = EXTRACT_PIECE(board->board[from_r][from_c]); if (piece_to_move != EMPTY) { /* Check for the special case of castling. */ if ((piece_to_move == KING) && (move_details->from_col == 'e')) { if (move_details->to_col == 'g') { move_details->class = KINGSIDE_CASTLE; } else if (move_details->to_col == 'c') { move_details->class = QUEENSIDE_CASTLE; } else { move_details->class = PIECE_MOVE; move_details->piece_to_move = piece_to_move; } } else { if (piece_to_move == PAWN) { move_details->class = PAWN_MOVE; } else { move_details->class = PIECE_MOVE; } move_details->piece_to_move = piece_to_move; } move_details->captured_piece = EMPTY; move_details->check_status = NOCHECK; } return move_details; } /* See if move_string seems to represent the text of a valid move. * Don't print any error messages, just return TRUE or FALSE. */ Boolean move_seems_valid(const unsigned char *move_string) { MoveClass class; Boolean Ok = TRUE; /* A pointer to move along the move string. */ unsigned const char *move = move_string; /* Make an initial distinction between pawn moves and piece moves. */ if (is_col(*move)) { /* Pawn move. */ class = PAWN_MOVE; move++; if (is_rank(*move)) { /* e4, e2e4 */ move++; if (is_capture(*move)) { move++; } if (is_col(*move)) { move++; if (is_rank(*move)) { move++; } } else { } } else { if (is_capture(*move)) { /* axb */ move++; } if (is_col(*move)) { /* ab */ move++; if (is_rank(*move)) { move++; } } else { Ok = FALSE; } } if (Ok) { /* Look for promotions. */ if (*move == '=') { move++; } /* djb From v17-27 allow a trailing 'b' as a Bishop promotion. */ if (is_piece(move) != EMPTY || *move == 'b') { class = PAWN_MOVE_WITH_PROMOTION; /* @@@ Strictly speaking, if the piece is a RUSSIAN_KING * then we should skip two chars. */ move++; } } } else if (is_piece(move) != EMPTY) { class = PIECE_MOVE; /* Check for a two-character piece. */ if ((RUSSIAN_PIECE_CHECK(*move) == RUSSIAN_KNIGHT_OR_KING) && (is_piece(move) == KING)) { move++; } move++; if (is_rank(*move)) { /* A disambiguating rank. * R1e1, R1xe3. */ move++; if (is_capture(*move)) { move++; } if (is_col(*move)) { move++; if (is_rank(*move)) { move++; } } else { Ok = FALSE; } } else { if (is_capture(*move)) { /* Rxe1 */ move++; if (is_col(*move)) { move++; if (is_rank(*move)) { move++; } else { Ok = FALSE; } } else { Ok = FALSE; } } else if (is_col(*move)) { move++; if (is_capture(*move)) { move++; } if (is_rank(*move)) { /* Re1, Re1d1, Re1xd1 */ move++; if (is_capture(*move)) { move++; } if (is_col(*move)) { /* Re1d1 */ move++; if (is_rank(*move)) { move++; } else { Ok = FALSE; } } } else if (is_col(*move)) { /* Rae1 */ move++; if (is_rank(*move)) { move++; } else { Ok = FALSE; } } else { Ok = FALSE; } } else { Ok = FALSE; } } } else if (is_castling_character(*move)) { /* Some form of castling. */ move++; /* Allow separators to be optional. */ if (*move == '-') { move++; } if (is_castling_character(*move)) { move++; if (*move == '-') { move++; } if (is_castling_character(*move)) { class = QUEENSIDE_CASTLE; move++; } else { class = KINGSIDE_CASTLE; } } else { Ok = FALSE; } } else { Ok = FALSE; } if (Ok) { /* Allow trailing checks. */ while (is_check(*move)) { move++; } if (*move == '\0') { /* Nothing more to check. */ } else if (((strcmp((const char *) move, "ep") == 0) || (strcmp((const char *) move, "e.p.") == 0)) && (class == PAWN_MOVE)) { /* These are ok. */ class = ENPASSANT_PAWN_MOVE; } else { Ok = FALSE; } } return Ok; }