//
// parsing.cpp
// fornax3
//
// Created by Anders on 09/12/2020.
//
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <inttypes.h>
#include "parsing.hpp"
#include "libs/require.h"
#include "libs/strings.h"
#include "libs/iolog.hpp"
#include "bits.h"
#include "tt/zobrist.hpp"
#include "moving/makemove.hpp"
#include "moving/movegen.hpp"
#include "moving/derived_knowledge.h"
#include "tt/transpositions.hpp"
void parsing_printbestmove(move best, move ponder) {
IO_PRINT("bestmove ");
if (!best) {
IO_PRINT("none");
}
else {
parsing_printmove(best);
if (ponder) {
IO_PRINT(" ponder ");
parsing_printmove(ponder);
}
}
IO_PRINT("\n");
IO_FLUSH();
}
void parsing_printcurrmove(move m, uint8_t depth, uint8_t index) {
IO_PRINT("info depth %" PRIu8 " currmove ", depth);
if (m) parsing_printmove(m);
else IO_PRINT("none");
IO_PRINT(" currmovenumber %d\n", index);
IO_FLUSH();
}
void parsing_printpv_inner(Board* board, move m, uint8_t depth) {
if (depth == 0) return;
if (!m) {
IO_PRINT("none");
return;
}
parsing_printmove(m);
makemove_makef(board, m);
ttkey hash = board_get_hash(board);
Bucket* bucket = transpositions_get(hash);
if (bucket->hash != hash || bucket->ttmove == 0);
else {
IO_PRINT(" ");
parsing_printpv_inner(board, bucket->ttmove, --depth);
}
makemove_takebackf(board, m);
}
void parsing_printpv(Board* board, move m, uint8_t depth) {
IO_PRINT("pv ");
parsing_printpv_inner(board, m, depth);
derived_knowledge_compute(board);
}
square parsing_square_from_file (char file) {
square sf = (file < 'a' || file > 'h') ? SQUARE_AXIS_NONE : (square) (file - 'a');
assert(sf <= 7 || sf == SQUARE_AXIS_NONE);
return sf;
}
square parsing_square_from_rank (char rank) {
square sr = (rank < '1' || rank > '8') ? SQUARE_AXIS_NONE : (square) (rank - '1');
assert(sr <= 7 || sr == SQUARE_AXIS_NONE);
return sr;
}
char parsing_square_to_file(square sq) {
return sq > 63 ? '?' : (char) ('a' + SQUARE_X(sq));
}
char parsing_square_to_rank(square sq) {
return sq > 63 ? '?' : (char) ('1' + SQUARE_Y(sq));
}
square parsing_square_from_uci(const char *ucisquare) {
size_t length = strlen(ucisquare);
require(length >= 2, "illegal uci square: %s", ucisquare);
square file = parsing_square_from_file(ucisquare[0]);
square rank = parsing_square_from_rank(ucisquare[1]);
require(file != SQUARE_AXIS_NONE && rank != SQUARE_AXIS_NONE, "illegal uci square: %s", ucisquare);
return SQUARE_FLATTEN(file, rank);
}
piecetype parsing_piece_from_symbol(char symbol) {
switch (symbol) {
case 'r': return ROOK;
case 'n': return KNIGHT;
case 'b': return BISHOP;
case 'q': return QUEEN;
case 'k': return KING;
case 'p': return PAWN;
default: return NONE;
}
}
char parsing_symbol_from_piece(piecetype piece) {
switch (piece) {
case ROOK: return 'r';
case KNIGHT: return 'n';
case BISHOP: return 'b';
case QUEEN: return 'q';
case KING: return 'k';
case PAWN: return 'p';
default: return '\0';
}
}
void parsing_printmove(move m) {
square origin = MOVE_GET_ORIGIN(m);
square dest = MOVE_GET_DEST(m);
piecetype promotion = MOVE_GET_PROMOTION(m);
IO_PRINT("%c%c%c%c",
parsing_square_to_file(origin),
parsing_square_to_rank(origin),
parsing_square_to_file(dest),
parsing_square_to_rank(dest));
if (promotion != KING) IO_PRINT("%c", parsing_symbol_from_piece(promotion));
}
void parsing_printmove_extra(const Board* board, evalmove m) {
square origin = MOVE_GET_ORIGIN(m);
square dest = MOVE_GET_DEST(m);
piecetype piece = board->piecemap[origin];
piecetype capture = board->piecemap[dest];
parsing_printmove((move) m);
IO_PRINT(" %c", parsing_symbol_from_piece(piece));
IO_PRINT(" [value = %d]", (eval) (m >> 16));
if (capture != NONE) {
IO_PRINT(" [capture = %c]", parsing_symbol_from_piece(capture));
}
IO_PRINT("\n");
}
move parsing_move_from_uci(const char *ucimove) {
size_t length = strlen(ucimove);
require(length >= 4, "illegal uci move: %s", ucimove);
square origin = parsing_square_from_uci(ucimove);
square dest = parsing_square_from_uci(ucimove + 2);
move m = MOVE_CREATE(origin, dest);
if (length == 5) {
m = MOVE_ADD_PROMOTION(m, parsing_piece_from_symbol(ucimove[4]));
}
return m;
}
piecetype parsing_piece_from_san_symbol(char symbol) {
switch (symbol) {
case 'R': return ROOK;
case 'N': return KNIGHT;
case 'B': return BISHOP;
case 'Q': return QUEEN;
case 'K': return KING;
case 'P': return PAWN;
default: return NONE;
}
}
bool parsing_is_valid_move(const Board* b, move m) {
evalmove moves[128];
uint8_t moveCount = movegen_generatef(b, moves);
for (uint8_t i = 0; i < moveCount; ++i) {
if (moves[i] == m) return true;
}
return false;
}
move parsing_move_from_san(const Board* b, const char* sanmove) {
if (string_starts_with(sanmove, "O-O-O")) {
return b->active == WHITE ? MOVE_WCQ : MOVE_BCQ;
}
else if (string_starts_with(sanmove, "O-O")) {
return b->active == WHITE ? MOVE_WCK : MOVE_BCK;
}
require(strlen(sanmove) <= 7, "san parse error: move string was too long (1)");
piecetype type = parsing_piece_from_san_symbol(sanmove[0]);
if (type == NONE) {
type = PAWN;
}
else {
sanmove++;
}
piecetype promotion = KING;
bool is_capture = false;
char stripped[5];
for (int i = 0; i < 5; ++i) stripped[i] = '\0';
int i = 0;
while (strlen(sanmove) > 0) {
char c = *sanmove++;
if (c == 'x') {
is_capture = true;
continue;
}
if (c == '=') {
promotion = parsing_piece_from_san_symbol(*sanmove);
break;;
}
if ((c >= '1' && c <= '8') || (c >= 'a' && c <= 'h')) {
stripped[i++] = c;
}
}
sanmove = stripped;
require(strlen(sanmove) <= 4, "san parse error (2) \n");
bits64 ambiguity_remover = UINT64_MAX;
if (strlen(sanmove) == 3) {
square origin_rank = parsing_square_from_rank(*sanmove);
ambiguity_remover = origin_rank < 8
? BB_RANKS[origin_rank]
: BB_FILES[parsing_square_from_file(*sanmove)];
require(ambiguity_remover < UINT64_MAX, "san parse error (3)\n");
sanmove++;
}
square origin;
square dest;
if (strlen(sanmove) == 4) {
origin = parsing_square_from_uci(sanmove);
dest = parsing_square_from_uci(sanmove + 2);
}
else {
dest = parsing_square_from_uci(sanmove);
bits64 pieces = b->side[b->active].pieces[type] & ambiguity_remover;
if (type == PAWN) {
if (is_capture) {
int mod = b->active ? 1 : -1;
bits64 rank = BB_RANKS[SQUARE_Y(dest) + mod];
bits64 remaining = pieces & rank;
if (BITS_COUNT(remaining) > 1) remaining &= ~movegen_get_pinned(b, b->active);
origin = bits_get_trailing(remaining);
}
else {
int mod = b->active ? 1 : -1;
bits64 rank = BB_RANKS[SQUARE_Y(dest) + (1 * mod)] | BB_RANKS[SQUARE_Y(dest) + (2 * mod)];
bits64 file = BB_FILES[SQUARE_X(dest)];
bits64 candidates = pieces & rank & file;
require(candidates > 0, "san parse error (4)\n");
origin = b->active ? bits_get_trailing(candidates) : bits_get_leading(candidates);
}
}
else {
bits64 attacks = movegen_get_attacksf(type ,dest, b->side[WHITE].piecemask | b->side[BLACK].piecemask);
bits64 remaining = pieces & attacks;
if (BITS_COUNT(remaining) > 1) remaining &= ~movegen_get_pinned(b, b->active);
origin = bits_get_trailing(remaining);
}
}
move m = MOVE_CREATE(origin, dest);
if (promotion) m = MOVE_ADD_PROMOTION(m, promotion);
require(parsing_is_valid_move(b, m), "san parse error (5)\n");
return m;
}
void parsing_printfen(const Board *b) {
char dest[100];
parsing_to_fen(b, dest);
IO_PRINT("%s", dest);
}
void parsing_printboard(const Board *b) {
const char edge[] = " -----------------\n";
IO_PRINT("%s", edge);
bool mode = 0;
for (int y = 7; y >= 0; --y) {
IO_PRINT("%d| ", (y + 1));
for (int x = 0; x < 8; ++x) {
square sq = SQUARE_FLATTEN(x, y);
bool isWhite = board_get_piece(b, WHITE, sq) != NONE;
piecetype piece = b->piecemap[sq];
char s = parsing_symbol_from_piece(piece);
if (s == '\0') s = mode ? '+' : ' ';
else s = isWhite ? (char) toupper(s) : s;
IO_PRINT("%c ", s);
mode = !mode;
}
mode = !mode;
IO_PRINT("|\n");
}
const Plyinfo *info = &b->plyinfo[b->ply];
IO_PRINT("%s", edge);
IO_PRINT(" A B C D E F G H\n");
IO_PRINT("\nhash: %" PRIu64 "", info->hash);
if (info->enpassant) {
square ep_sq = bits_get_trailing(info->enpassant);
IO_PRINT("\nen passant: %c%c", parsing_square_to_file(ep_sq), parsing_square_to_rank(ep_sq));
}
IO_PRINT("\nfen: ");
parsing_printfen(b);
IO_PRINT("\n");
IO_FLUSH();
}
void parsing_to_fen(const Board *b, char *dest) {
const Plyinfo *info = &b->plyinfo[b->ply];
bool isWhite = b->active == WHITE;
for (int j = 7; j >= 0; --j) {
char spaceCounter = 0;
for (int i = 0; i < 8; ++i) {
square sq = SQUARE_FLATTEN(i, j);
char c = parsing_symbol_from_piece(b->piecemap[sq]);
bool w = board_get_piece(b, WHITE, sq) != NONE;
c = c > 0 && w ? (char) toupper(c) : c;
if (c > 0) {
if (spaceCounter > 0) {
(*dest++) = '0' + spaceCounter;
spaceCounter = 0;
}
*dest++ = c;
}
else {
++spaceCounter;
}
}
if (spaceCounter > 0) {
(*dest++) = '0' + spaceCounter;
}
(*dest++) = '/';
}
*(dest -1) = ' ';
*dest++ = isWhite ? 'w' : 'b';
*dest++ = ' ';
char* d = dest;
if (info->castlingrights & BB_RIGHTS_WCK) (*dest++) = 'K';
if (info->castlingrights & BB_RIGHTS_WCQ) (*dest++) = 'Q';
if (info->castlingrights & BB_RIGHTS_BCK) (*dest++) = 'k';
if (info->castlingrights & BB_RIGHTS_BCQ) (*dest++) = 'q';
if (d == dest) (*dest++) = '-';
*dest++ = ' ';
if (info->enpassant) {
square pos = bits_get_trailing(info->enpassant);
(*dest++) = parsing_square_to_file(pos);
(*dest++) = parsing_square_to_rank(pos);
}
else *dest++ = '-';
*dest++ = ' ';
char halfmoves[10];
sprintf(halfmoves, "%d", info->halfmovecount);
char *digit = halfmoves;
while (*digit != '\0') *dest++ = *digit++;
*dest = 0;
}
Board parsing_from_fen_parts(char** parts, int parts_size) {
require(parts_size >= 4, "FEN was not comprised of at least 4 parts\n");
char* fenRanks[8];
int splitSizeBoard = strings_split(parts[0], fenRanks, "/");
require(splitSizeBoard == 8, "FEN board was not comprised of 8 ranks: '%s'\n", parts[0]);
Board b{};
Plyinfo *info = &b.plyinfo[b.ply];
for (int i = 0; i < 64; ++i) b.piecemap[i] = NONE;
for (int j = 0; j < 8; ++j) {
size_t size = strlen(fenRanks[j]);
require(size <= 8 && size > 0, "FEN rank could not be parsed\n");
int x = 0;
int y = 7 - j;
size_t counter = 0;
while (counter < size) {
char symbol = fenRanks[j][counter];
square sq = SQUARE_FLATTEN(x, y);
switch (symbol) {
case 'R': board_put(&b, WHITE, sq, ROOK); break;
case 'N': board_put(&b, WHITE, sq, KNIGHT); break;
case 'B': board_put(&b, WHITE, sq, BISHOP); break;
case 'Q': board_put(&b, WHITE, sq, QUEEN); break;
case 'K': board_put(&b, WHITE, sq, KING); break;
case 'P': board_put(&b, WHITE, sq, PAWN); break;
case 'r': board_put(&b, BLACK, sq, ROOK); break;
case 'n': board_put(&b, BLACK, sq, KNIGHT); break;
case 'b': board_put(&b, BLACK, sq, BISHOP); break;
case 'q': board_put(&b, BLACK, sq, QUEEN); break;
case 'k': board_put(&b, BLACK, sq, KING); break;
case 'p': board_put(&b, BLACK, sq, PAWN); break;
default: {
int range = symbol - '0';
if (range > 0 && range <= 8) x += range - 1;
else require(false, "FEN unknown symbol: %c\n", symbol);
}
};
counter++;
x++;
}
}
{
if (parts[1][0] == 'w') b.active = WHITE;
else if (parts[1][0] == 'b') b.active = BLACK;
else require(false, "FEN unknown symbol: %s\n", parts[1]);
}
b.active_initial = b.active;
size_t size = strlen(parts[2]);
for (size_t i = 0; i < size; ++i) {
if (parts[2][i] == '-') break;
else if (parts[2][i] == 'K') info->castlingrights |= BB_RIGHTS_WCK;
else if (parts[2][i] == 'Q') info->castlingrights |= BB_RIGHTS_WCQ;
else if (parts[2][i] == 'k') info->castlingrights |= BB_RIGHTS_BCK;
else if (parts[2][i] == 'q') info->castlingrights |= BB_RIGHTS_BCQ;
else require(false, "FEN unknown symbol(s): %s\n", parts[2]);
}
if (parts_size > 4 && parts[4][0] != '-') info->halfmovecount = (uint8_t) string_to_long(parts[4]);
derived_knowledge_compute(&b);
if (parts[3][0] != '-') {
/* only set ep if attacked by active sides pawns*/
bits64 ep_sq = BITS_FROM_SQUARE(parsing_square_from_uci(parts[3]));
bits64 pawn_attacks = board_get_pawns_attackmask(&b.side[b.active]);
if (ep_sq & pawn_attacks) info->enpassant = ep_sq;
}
info->hash = zobrist_compute_hash(&b);
b.side[WHITE].eval_phase = board_count_phase(&b.side[WHITE]);
b.side[WHITE].eval_material = eval_count_material(&b.side[WHITE]);
b.side[BLACK].eval_phase = board_count_phase(&b.side[BLACK]);
b.side[BLACK].eval_material = eval_count_material(&b.side[BLACK]);
b.side[WHITE].eval_mg_psqt = eval_count_psqt(&b.side[WHITE], &WHITE_MG_PIECETABLE);
b.side[BLACK].eval_mg_psqt = eval_count_psqt(&b.side[BLACK], &BLACK_MG_PIECETABLE);
b.side[WHITE].eval_eg_psqt = eval_count_psqt(&b.side[WHITE], &WHITE_EG_PIECETABLE);
b.side[BLACK].eval_eg_psqt = eval_count_psqt(&b.side[BLACK], &BLACK_EG_PIECETABLE);
return b;
}
Board parsing_from_fen(const char* fen) {
char fenCopy[256];
strcpy(fenCopy, fen);
char* parts[6];
int splitSize = strings_split(fenCopy, parts, " ");
require(splitSize >= 4, "FEN was not comprised of at least 4 parts: '%s'\n", fen);
return parsing_from_fen_parts(parts, splitSize);
}
void parsing_printbits_8by8(bits64 bits) {
char boardString[64 + 64 + 8 + 1];
char* cursor = &boardString[0];
for (int j = 7; j >= 0; --j) {
for (int i = 0; i < 8; ++i) {
*cursor++ = BITS_TEST(bits, i + j * 8) ? '1' : '0';
*cursor++ = ' ';
}
*cursor++ = '\n';
}
*cursor = '\0';
IO_PRINT("%s\n", boardString);
}
void parsing_printbits_hex(bits64 bits) {
IO_PRINT("0x%016" PRIx64 "\n", bits);
}