From 9d5159b9c872b11ce73ea7d9a1c3ba206fe24c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Wed, 4 Aug 2021 19:26:20 +0200 Subject: [PATCH 01/29] created API method stubs --- chesslib/src/chesslibmodule.c | 49 ++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 88c5b35..8d52757 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -34,11 +34,18 @@ static PyObject* chesslib_create_chesspieceatpos(PyObject* self, PyObject* args, static PyObject* chesslib_create_chessboard(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_create_startformation(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_create_chessdraw(PyObject* self, PyObject* args, PyObject *keywds); + static PyObject* chesslib_get_all_draws(PyObject* self, PyObject* args, PyObject *keywds); -static PyObject* chesslib_board_to_hash(PyObject* self, PyObject* args, PyObject *keywds); -static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_apply_draw(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_get_game_state(PyObject* self, PyObject* args, PyObject *keywds); + +static PyObject* chesslib_board_to_hash(PyObject* self, PyObject* args, PyObject *keywds); +static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args, PyObject *keywds); +static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObject *keywds); +static PyObject* chesslib_board_to_fen(PyObject* self, PyObject* args, PyObject *keywds); +static PyObject* chesslib_draw_from_pgn(PyObject* self, PyObject* args, PyObject *keywds); +static PyObject* chesslib_draw_to_pgn(PyObject* self, PyObject* args, PyObject *keywds); + static PyObject* chesslib_visualize_board(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_visualize_draw(PyObject* self, PyObject* args, PyObject *keywds); @@ -720,10 +727,46 @@ static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args, PyObje /* signal python that the PyObject is no longer used by this function */ Py_DecRef(hash_orig); - + return chessboard; } +/* ================================================= + E X C H A N G E F O R M A T S + ================================================= */ + +static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObject *keywds) +{ + /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ + /* FEN structure: + 1) positions of pieces + 2) drawing side + 3) possible rochades + 4) possible en-passants + 5) half-draws since last peasant draw + 6) round no. + */ + + /* generate a board from FEN pieces list */ + + /* set was_moved flags for kings / rooks */ +} + +static PyObject* chesslib_board_to_fen(PyObject* self, PyObject* args, PyObject *keywds) +{ + /* TODO: implement logic here ... */ +} + +static PyObject* chesslib_draw_from_pgn(PyObject* self, PyObject* args, PyObject *keywds) +{ + /* TODO: implement logic here ... */ +} + +static PyObject* chesslib_draw_to_pgn(PyObject* self, PyObject* args, PyObject *keywds) +{ + /* TODO: implement logic here ... */ +} + /* ================================================= V I S U A L I Z E ================================================= */ From 4993b4d02c546e6894c4ace98552c625ee1692e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Fri, 6 Aug 2021 15:24:25 +0200 Subject: [PATCH 02/29] refactored start formation with macro --- chesslib/src/chesslibmodule.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 8d52757..a2a8418 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -440,21 +440,7 @@ static PyObject* chesslib_create_startformation(PyObject* self, PyObject* args, static char* kwlist[] = {"is_simple", NULL}; /* create the chess board */ - const Bitboard start_formation[] = { - 0x0000000000000010uLL, /* white king */ - 0x0000000000000008uLL, /* white queen(s) */ - 0x0000000000000081uLL, /* white rooks */ - 0x0000000000000024uLL, /* white bishops */ - 0x0000000000000042uLL, /* white knights */ - 0x000000000000FF00uLL, /* white pawns */ - 0x1000000000000000uLL, /* black king */ - 0x0800000000000000uLL, /* black queen(s) */ - 0x8100000000000000uLL, /* black rooks */ - 0x2400000000000000uLL, /* black bishops */ - 0x4200000000000000uLL, /* black knights */ - 0x00FF000000000000uLL, /* black pawns */ - 0x0000FFFFFFFF0000uLL /* was_moved mask */ - }; + const Bitboard start_formation[] = START_FORMATION /* parse all args */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "|i", kwlist, &is_simple_board)) { return NULL; } @@ -749,6 +735,7 @@ static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObjec /* generate a board from FEN pieces list */ + /* set was_moved flags for kings / rooks */ } From 7b583fd77e2e41177079189e573c92832cf674a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Fri, 6 Aug 2021 15:25:11 +0200 Subject: [PATCH 03/29] added chessxformat code files --- chesslib/include/chesslibmodule.h | 1 + setup.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/chesslib/include/chesslibmodule.h b/chesslib/include/chesslibmodule.h index c61989e..6ab228d 100644 --- a/chesslib/include/chesslibmodule.h +++ b/chesslib/include/chesslibmodule.h @@ -49,5 +49,6 @@ #include "chesspieceatpos.h" #include "chessposition.h" #include "chesstypes.h" +#include "chessxformat.h" #endif diff --git a/setup.py b/setup.py index 16305d8..09a83f6 100644 --- a/setup.py +++ b/setup.py @@ -98,7 +98,8 @@ def main(): "chesslib/src/chessdraw.c", "chesslib/src/chessdrawgen.c", "chesslib/src/chesspieceatpos.c", - "chesslib/src/chessgamestate.c" + "chesslib/src/chessgamestate.c", + "chesslib/src/chessxformat.c" ] # define extension module settings (for cross-plattform builds) From 1268f34c86bb0f7dfc5fb194cf1e6ef4e89811f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Fri, 6 Aug 2021 15:25:31 +0200 Subject: [PATCH 04/29] beautified code --- chesslib/include/chessboard.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chesslib/include/chessboard.h b/chesslib/include/chessboard.h index 08ebad4..5feca14 100644 --- a/chesslib/include/chessboard.h +++ b/chesslib/include/chessboard.h @@ -41,6 +41,14 @@ D E F I N E M A K R O S ==================================================== */ +/* The chess bitboards in start formation */ +#define START_FORMATION {\ + 0x0000000000000010uLL, 0x0000000000000008uLL, 0x0000000000000081uLL,\ + 0x0000000000000024uLL, 0x0000000000000042uLL, 0x000000000000FF00uLL,\ + 0x1000000000000000uLL, 0x0800000000000000uLL, 0x8100000000000000uLL,\ + 0x2400000000000000uLL, 0x4200000000000000uLL, 0x00FF000000000000uLL,\ + 0x0000FFFFFFFF0000uLL }; + /* row masks */ #define ROW_1 0x00000000000000FFuLL #define ROW_2 0x000000000000FF00uLL From 4feab6de389fd73fd4ca2722f0c5bcee650c9780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Fri, 6 Aug 2021 15:26:03 +0200 Subject: [PATCH 05/29] added exchange format functions (WIP) --- chesslib/include/chessxformat.h | 47 +++++++++++++++++ chesslib/src/chessxformat.c | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 chesslib/include/chessxformat.h create mode 100644 chesslib/src/chessxformat.c diff --git a/chesslib/include/chessxformat.h b/chesslib/include/chessxformat.h new file mode 100644 index 0000000..48964bc --- /dev/null +++ b/chesslib/include/chessxformat.h @@ -0,0 +1,47 @@ +/* + * MIT License + * + * Copyright(c) 2020 Marco Tröster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CHESSXFORMAT_H +#define CHESSXFORMAT_H + +#include +#include "chesstypes.h" +#include "chesspiece.h" +#include "chessboard.h" + +/* A chess game session semantically at least as powerful as the FEN notation. + It is supposed to help carrying out professional chess matches. */ +typedef struct _CHESS_GAME_SESSION { + Bitboard board[13] = START_FORMATION; + ChessColor drawing_side = White; + int halfdraws_since_last_pawn_draw = 0; + int game_round = 1; +} ChessGameSession; + +int chess_board_from_fen(const char fen_str[], Bitboard* board); +int chess_board_to_fen(char** fen_str, const ChessGameSession session[]); +int chess_draw_from_pgn(const char fen_str[], Bitboard* board); +void chess_draw_to_pgn(char** fen_str, Bitboard* board); + +#endif \ No newline at end of file diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c new file mode 100644 index 0000000..794e988 --- /dev/null +++ b/chesslib/src/chessxformat.c @@ -0,0 +1,91 @@ +/* + * MIT License + * + * Copyright(c) 2020 Marco Tröster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "chessxformat.h" + +/* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ + +int chess_board_from_fen(const char fen_str[], Bitboard* board) +{ + size_t i = 0; char temp = '\0'; size_t sep_count = 0; + ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; + + /* parse the first FEN section (positions of pieces on the board) */ + do + { + /* access the next FEN character to be parsed */ + temp = fen_str[i++]; + + switch (temp) + { + /* handle ignored valid characters (do nothing) */ + case ' ': case '\0': break; + + /* ensure that the row bounds are not violated */ + case '/': if (pos != ++sep_count * 8) { return 0; } break; + + /* handle empty fields declaration (increment position) */ + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + pos += (ChessPosition)(temp - '0'); break; + + /* handle a piece to be put on the chess board */ + case 'K': case 'Q': case 'R': case 'B': case 'N': case 'P': + case 'k': case 'q': case 'r': case 'b': case 'n': case 'p': + color = (ChessColor)(isupper(temp) ? White : Black); + type = piece_type_from_char(temp); + was_moved = START_POSITIONS & (1uLL << pos) ? 0 : 1; + board[pos++] = create_piece(type, color, was_moved); + break; + + /* handle invalid / unexpected characters */ + default: return 0; + } + + /* loop until the end of the FEN string's first section */ + } while (temp != '\0' && temp != ' '); + + /* make sure that parsing the first section was successful */ + if (temp != ' ' || pos != 64 || sep_count != 7) { return 0; } + + /* parse the second FEN section (possible castlings) */ + + + return 1; +} + +int chess_board_to_fen(char** fen_str, const ChessGameSession session[]) +{ + +} + +int chess_draw_from_pgn(const char fen_str[], Bitboard* board) +{ + +} + +int chess_draw_to_pgn(char** fen_str, Bitboard* board) +{ + +} From 4eec79359300db1bacc2e1a0f2703549b8895637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Sat, 7 Aug 2021 00:36:32 +0200 Subject: [PATCH 06/29] continued working on FEN parser (WIP) --- chesslib/src/chessxformat.c | 64 ++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 794e988..60d911b 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -26,9 +26,9 @@ /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ -int chess_board_from_fen(const char fen_str[], Bitboard* board) +int chess_board_from_fen(const char fen_str[], ChessGameSession* session) { - size_t i = 0; char temp = '\0'; size_t sep_count = 0; + size_t i = 0, sep_count = 0; char temp = '\0'; int is_terminal = 0; ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; /* parse the first FEN section (positions of pieces on the board) */ @@ -39,53 +39,87 @@ int chess_board_from_fen(const char fen_str[], Bitboard* board) switch (temp) { - /* handle ignored valid characters (do nothing) */ - case ' ': case '\0': break; + /* handle termination character */ + case ' ': if (!is_terminal) { return 0; } break; /* ensure that the row bounds are not violated */ case '/': if (pos != ++sep_count * 8) { return 0; } break; - /* handle empty fields declaration (increment position) */ + /* handle empty fields declaration */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': pos += (ChessPosition)(temp - '0'); break; - /* handle a piece to be put on the chess board */ + /* put another piece on the chess board */ case 'K': case 'Q': case 'R': case 'B': case 'N': case 'P': case 'k': case 'q': case 'r': case 'b': case 'n': case 'p': color = (ChessColor)(isupper(temp) ? White : Black); type = piece_type_from_char(temp); was_moved = START_POSITIONS & (1uLL << pos) ? 0 : 1; - board[pos++] = create_piece(type, color, was_moved); + session->board[pos++] = create_piece(type, color, was_moved); break; /* handle invalid / unexpected characters */ default: return 0; } - /* loop until the end of the FEN string's first section */ - } while (temp != '\0' && temp != ' '); + /* check if the next state is supposed to be terminal */ + is_terminal = pos == 64 && sep_count == 7; - /* make sure that parsing the first section was successful */ - if (temp != ' ' || pos != 64 || sep_count != 7) { return 0; } + /* loop until the end of the FEN string's first section */ + } while (temp != ' '); /* parse the second FEN section (possible castlings) */ - + + /* disable all rochades (enable them gradually if they are still possible) */ + session->board[12] |= FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8; + + /* handle case with no castlings */ + if (fen_str[i] == '-' && fen_str[i+1] == ' ') { /* do nothing */ } + else + { + /* enable castlings (ensure the correct order) */ + if (fen_str[i] == 'K') { session->board[12] &= ~FIELD_H1; i++; } + if (fen_str[i] == 'Q') { session->board[12] &= ~FIELD_A1; i++; } + if (fen_str[i] == 'k') { session->board[12] &= ~FIELD_H8; i++; } + if (fen_str[i] == 'q') { session->board[12] &= ~FIELD_A8; i++; } + + /* ensure that any rochade is possible and the terminal symbol is hit */ + if (!(~(session->board[12]) & ~(FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8) + && fen_str[i] == 'q')) { return 0; } + } + + /* info: the third FEN section (en-passant) can be skipped */ + /* TODO: skip the en-passant section properly */ + + /* parse the fourth FEN section (halfdraws since last pawn draw) */ + do + { + temp = fen_str[i++]; + + switch (temp) + { + + } + + } while (() != ' ' && temp != '\0'); + + /* parse the fifth FEN section (game round) */ return 1; } int chess_board_to_fen(char** fen_str, const ChessGameSession session[]) { - + /* TODO: implement logic */ } int chess_draw_from_pgn(const char fen_str[], Bitboard* board) { - + /* TODO: implement logic */ } int chess_draw_to_pgn(char** fen_str, Bitboard* board) { - + /* TODO: implement logic */ } From 261a9fb1a3973d606c82773daab0a9680f3d388f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Sat, 7 Aug 2021 08:22:04 +0200 Subject: [PATCH 07/29] finished parsing FEN (needs testing) --- chesslib/include/chessxformat.h | 16 ++++++++------ chesslib/src/chessxformat.c | 38 ++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/chesslib/include/chessxformat.h b/chesslib/include/chessxformat.h index 48964bc..5a531a1 100644 --- a/chesslib/include/chessxformat.h +++ b/chesslib/include/chessxformat.h @@ -33,15 +33,17 @@ /* A chess game session semantically at least as powerful as the FEN notation. It is supposed to help carrying out professional chess matches. */ typedef struct _CHESS_GAME_SESSION { - Bitboard board[13] = START_FORMATION; - ChessColor drawing_side = White; - int halfdraws_since_last_pawn_draw = 0; - int game_round = 1; + Bitboard board[13]; + ChessColor drawing_side; + int halfdraws_since_last_pawn_draw; + int game_round; } ChessGameSession; -int chess_board_from_fen(const char fen_str[], Bitboard* board); -int chess_board_to_fen(char** fen_str, const ChessGameSession session[]); +#define INIT_GAME_SESSION {START_FORMATION, White, 0, 1} + +int chess_board_from_fen(const char fen_str[], ChessGameSession* session); +int chess_board_to_fen(char** fen_str, const ChessGameSession* session); int chess_draw_from_pgn(const char fen_str[], Bitboard* board); -void chess_draw_to_pgn(char** fen_str, Bitboard* board); +int chess_draw_to_pgn(char** fen_str, Bitboard* board); #endif \ No newline at end of file diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 60d911b..f2f8f70 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -26,9 +26,20 @@ /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ +int parse_uint(char* value_str, int* out_value, char term) +{ + int value = 0; size_t i = 0; + + while (isdigit(value_str[i]) && value_str[i] != term) + { value = value * 10 + (value_str[i] - '0'); i++; } + + *out_value = value; + return value_str[i] == term ? i : -1; +} + int chess_board_from_fen(const char fen_str[], ChessGameSession* session) { - size_t i = 0, sep_count = 0; char temp = '\0'; int is_terminal = 0; + size_t i = 0, sep_count = 0; char temp = '\0'; int is_terminal = 0, len = 0; ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; /* parse the first FEN section (positions of pieces on the board) */ @@ -85,41 +96,38 @@ int chess_board_from_fen(const char fen_str[], ChessGameSession* session) if (fen_str[i] == 'q') { session->board[12] &= ~FIELD_A8; i++; } /* ensure that any rochade is possible and the terminal symbol is hit */ - if (!(~(session->board[12]) & ~(FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8) - && fen_str[i] == 'q')) { return 0; } + if (!((~session->board[12] & (FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8)) + && fen_str[i] == ' ')) { return 0; } } /* info: the third FEN section (en-passant) can be skipped */ - /* TODO: skip the en-passant section properly */ + while ((temp = fen_str[i++]) != '\0' && temp != ' ') { } /* parse the fourth FEN section (halfdraws since last pawn draw) */ - do - { - temp = fen_str[i++]; - - switch (temp) - { - - } - - } while (() != ' ' && temp != '\0'); + len = parse_uint(fen_str + i, &(session->halfdraws_since_last_pawn_draw), ' '); + if (len > 0) { i += len + 1; } else { return 0; } /* parse the fifth FEN section (game round) */ + len = parse_uint(fen_str + i, &(session->game_round), '\0'); + if (len <= 0) { return 0; } return 1; } -int chess_board_to_fen(char** fen_str, const ChessGameSession session[]) +int chess_board_to_fen(char** fen_str, const ChessGameSession* session) { /* TODO: implement logic */ + return 0; } int chess_draw_from_pgn(const char fen_str[], Bitboard* board) { /* TODO: implement logic */ + return 0; } int chess_draw_to_pgn(char** fen_str, Bitboard* board) { /* TODO: implement logic */ + return 0; } From c38a774f71e5d8073f28e5346f2014de7968b2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Sat, 7 Aug 2021 16:45:00 +0200 Subject: [PATCH 08/29] added ChessGameContext type (WIP) --- chesslib/include/chessgamesession.h | 55 +++++++++++++++++ chesslib/include/chesstypes.h | 10 ++++ chesslib/include/chessxformat.h | 13 +--- chesslib/src/chessgamesession.c | 89 +++++++++++++++++++++++++++ chesslib/src/chessxformat.c | 93 +++++++++++++++++++++++------ 5 files changed, 232 insertions(+), 28 deletions(-) create mode 100644 chesslib/include/chessgamesession.h create mode 100644 chesslib/src/chessgamesession.c diff --git a/chesslib/include/chessgamesession.h b/chesslib/include/chessgamesession.h new file mode 100644 index 0000000..4f7ffef --- /dev/null +++ b/chesslib/include/chessgamesession.h @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright(c) 2020 Marco Tröster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CHESSGAMESESSION_H +#define CHESSGAMESESSION_H + +#include "chesstypes.h" +#include "chessboard.h" +#include "chessdraw.h" + +/* A chess game session semantically at least as powerful as the FEN notation. + It is supposed to help carrying out professional chess matches. */ +typedef struct _CHESS_GAME_SESSION { + Bitboard board[13]; + ChessGameContext context; +} ChessGameSession; + +/* default game context bits: 00000000 00001000 00011110 00000000 */ +#define DEFAULT_GAME_CONTEXT 0x00081E00uL + +/* inital game session: board in start formation and initial game context */ +#define INIT_GAME_SESSION {START_FORMATION, DEFAULT_GAME_CONTEXT} + +ChessGameContext create_context(ChessColor side, uint8_t en_passants, + uint8_t rochades, uint8_t halfdraws_since_last_pawn_draw, int game_round); + +Bitboard get_en_passant_mask(ChessGameContext context); +Bitboard get_rochade_mask(ChessGameContext context); +uint8_t get_hdslpd(ChessGameContext context); +int get_game_rounds(ChessGameContext context); + +void apply_draw(ChessDraw draw, ChessGameContext* context); + +#endif \ No newline at end of file diff --git a/chesslib/include/chesstypes.h b/chesslib/include/chesstypes.h index 61116cc..7c5cdeb 100644 --- a/chesslib/include/chesstypes.h +++ b/chesslib/include/chesstypes.h @@ -95,4 +95,14 @@ typedef uint16_t CompactChessDraw; highest bit as H8 (indexes A1=0, B1=1, ..., A2=8, ..., H8=63). */ typedef uint64_t Bitboard; +/* TODO: add a definition for a FEN game session context to replace the was_moved bitboards + e.g. 1 bit for drawing_side, 8 bits for en-passants, 4 bits for rochades, + 6 bits for draws_since_last_pawn_draw, remaining bits for game_round + -> new 32-bit integer bitwise type */ + +/* | game round | halfdraws since pawn draw | rochades | en-passants | side | + | ------------- | ------------------------- | -------- | ----------- | ---- | + | xxxxxxxxxxxxx | xxxxxx | xxxx | xxxxxxxx | x | */ +typedef uint32_t ChessGameContext; + #endif diff --git a/chesslib/include/chessxformat.h b/chesslib/include/chessxformat.h index 5a531a1..1d223c9 100644 --- a/chesslib/include/chessxformat.h +++ b/chesslib/include/chessxformat.h @@ -26,20 +26,13 @@ #define CHESSXFORMAT_H #include +#include #include "chesstypes.h" #include "chesspiece.h" #include "chessboard.h" +#include "chessgamesession.h" -/* A chess game session semantically at least as powerful as the FEN notation. - It is supposed to help carrying out professional chess matches. */ -typedef struct _CHESS_GAME_SESSION { - Bitboard board[13]; - ChessColor drawing_side; - int halfdraws_since_last_pawn_draw; - int game_round; -} ChessGameSession; - -#define INIT_GAME_SESSION {START_FORMATION, White, 0, 1} +/* TODO: move the board hashing code here */ int chess_board_from_fen(const char fen_str[], ChessGameSession* session); int chess_board_to_fen(char** fen_str, const ChessGameSession* session); diff --git a/chesslib/src/chessgamesession.c b/chesslib/src/chessgamesession.c new file mode 100644 index 0000000..69055ea --- /dev/null +++ b/chesslib/src/chessgamesession.c @@ -0,0 +1,89 @@ +/* + * MIT License + * + * Copyright(c) 2020 Marco Tröster + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "chessgamesession.h" + +#define GC_DRAWING_SIDE(context) (ChessColor)((context) & 0x1uL) + +ChessGameContext create_context(ChessColor side, uint8_t en_passants, + uint8_t rochades, uint8_t halfdraws_since_last_pawn_draw, int game_round) +{ + /* combine the attributes to a single 32-bit integer */ + return ((ChessGameContext)side & 0x1uL) + || (((ChessGameContext)en_passants & 0xFFuL) << 1) + || (((ChessGameContext)rochades & 0xFuL) << 9) + || (((ChessGameContext)halfdraws_since_last_pawn_draw & 0x3FuL) << 13) + || ((ChessGameContext)game_round << 19); +} + +Bitboard get_en_passant_mask(ChessGameContext context) +{ + /* extract the 8 en-passant bits and transform it into a bit mask */ + uint8_t shift = 8 * (GC_DRAWING_SIDE(context) == White ? 2 : 5); + return ((Bitboard)((context) >> 1) & ROW_1) << shift; +} + +Bitboard get_rochade_mask(ChessGameContext context) +{ + /* extract the 4 bits indicating possible rochades */ + Bitboard rochades = (Bitboard)((context) >> 9) & 0xFuLL; + + /* transform the rochade bits into a bitboard mask */ + return (rochades & 0x1uLL) /* rook on A1 */ + | (rochades & 0x2uLL) << 6 /* rook on H1 */ + | (rochades & 0x4uLL) << 54 /* rook on A8 */ + | (rochades & 0x8uLL) << 60; /* rook on H8 */ +} + +uint8_t get_hdslpd(ChessGameContext context) +{ + /* extract the 6 bits counting the halfdraws since the last pawn draw */ + return (uint8_t)((context >> 13) & 0x3FuL); +} + +int get_game_rounds(ChessGameContext context) +{ + /* extract the 13 bits counting the game rounds */ + return (int)(context >> 19); +} + +void apply_draw(ChessDraw draw, ChessGameContext* context) +{ + /* alternate the drawing side */ + *context ^= 0x1uL; + + /* increment the game round counter (if white is drawing) */ + if (GC_DRAWING_SIDE(*context) == White) { + *context = (((ChessGameContext)get_game_rounds(*context) + 1) << 19) + | (*context & 0x7FFFFuL); + } + + /* update the hdslpd counter (either increment or reset) */ + *context = *context & 0xFFF81FFFuL; /* reset bits */ + if (get_drawing_piece_type(draw) != Peasant) { + *context |= (((ChessGameContext)get_hdslpd(*context) + 1) << 13); + } + + /* TODO: handle rochade / en-passant */ +} \ No newline at end of file diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index f2f8f70..570908b 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -26,20 +26,35 @@ /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ +/* Parse a positive integer value from the given decimal numeric string + and return the length of the characters parsed (return -1 on format error). */ int parse_uint(char* value_str, int* out_value, char term) { int value = 0; size_t i = 0; - while (isdigit(value_str[i]) && value_str[i] != term) + /* make sure there are no heading zeros for non-zero values */ + if (value_str[i] == '0' && value_str[i+1] != term) { return -1; } + + /* parse the uint value from decimal notation */ + while (isdigit(value_str[i]) && value_str[i] != term && value_str[i] != '\0') { value = value * 10 + (value_str[i] - '0'); i++; } *out_value = value; return value_str[i] == term ? i : -1; } -int chess_board_from_fen(const char fen_str[], ChessGameSession* session) +/* Look up the next appearance of the given character in the given zero-terminated string. + Return -1 if the string does not contain the character searched. */ +int str_index_of(char* search_str, char find) +{ + size_t i = 0; char temp; + while ((temp = search_str[i++]) != find && temp != '\0') { } + return temp == find ? i - 1 : -1; +} + +int parse_first_fen_section(const char fen_str[], Bitboard* board) { - size_t i = 0, sep_count = 0; char temp = '\0'; int is_terminal = 0, len = 0; + size_t i = 0, sep_count = 0; char temp; int is_terminal = 0; ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; /* parse the first FEN section (positions of pieces on the board) */ @@ -51,7 +66,7 @@ int chess_board_from_fen(const char fen_str[], ChessGameSession* session) switch (temp) { /* handle termination character */ - case ' ': if (!is_terminal) { return 0; } break; + case '\0': if (!is_terminal) { return 0; } break; /* ensure that the row bounds are not violated */ case '/': if (pos != ++sep_count * 8) { return 0; } break; @@ -78,36 +93,78 @@ int chess_board_from_fen(const char fen_str[], ChessGameSession* session) is_terminal = pos == 64 && sep_count == 7; /* loop until the end of the FEN string's first section */ - } while (temp != ' '); + } while (temp != '\0'); - /* parse the second FEN section (possible castlings) */ + return 1; +} - /* disable all rochades (enable them gradually if they are still possible) */ - session->board[12] |= FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8; +int parse_second_fen_section(const char fen_str[], GameContext* context) +{ + ChessColor color = color_from_char(fen_str[0]); + *context ^= (GameContext)(color & 0x1); /* TODO: use a function for this assignment */ + if (fen_str[1] != '\0') { return 0; } + return 1; +} + +int parse_third_fen_section(const char fen_str[], Bitboard* board) +{ + size_t i = 0; + + /* disable all rochades (enable them explicitly if they are possible) */ + board[12] |= FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8; /* handle case with no castlings */ - if (fen_str[i] == '-' && fen_str[i+1] == ' ') { /* do nothing */ } + if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* do nothing */ } else { /* enable castlings (ensure the correct order) */ - if (fen_str[i] == 'K') { session->board[12] &= ~FIELD_H1; i++; } - if (fen_str[i] == 'Q') { session->board[12] &= ~FIELD_A1; i++; } - if (fen_str[i] == 'k') { session->board[12] &= ~FIELD_H8; i++; } - if (fen_str[i] == 'q') { session->board[12] &= ~FIELD_A8; i++; } + if (fen_str[i] == 'K') { board[12] &= ~FIELD_H1; i++; } + if (fen_str[i] == 'Q') { board[12] &= ~FIELD_A1; i++; } + if (fen_str[i] == 'k') { board[12] &= ~FIELD_H8; i++; } + if (fen_str[i] == 'q') { board[12] &= ~FIELD_A8; i++; } /* ensure that any rochade is possible and the terminal symbol is hit */ - if (!((~session->board[12] & (FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8)) - && fen_str[i] == ' ')) { return 0; } + if (!((~board[12] & (FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8)) + && fen_str[i] == '\0')) { return 0; } } - /* info: the third FEN section (en-passant) can be skipped */ + return 1; +} + +int chess_board_from_fen(const char fen_str[], ChessGameSession* session) +{ + size_t start = 0, end = 0; + char temp_str[54]; + + /* create a carbon copy of the fen string (that can be safely modified) */ + strcpy(temp_str, fen_str); + + /* parse the first FEN section (positions of pieces on the board) */ + if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } + temp_str[end] = '\0'; + if (!parse_first_fen_section(temp_str, session->board)) { return 0; } + temp_str += end + 1; + + /* parse the second FEN section (drawing side) */ + if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } + temp_str[end] = '\0'; + if (!parse_second_fen_section(temp_str, session->board)) { return 0; } + temp_str += end + 1; + + /* parse the third FEN section (possible castlings) */ + if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } + temp_str[end] = '\0'; + if (!parse_second_fen_section(temp_str, session->board)) { return 0; } + temp_str += end + 1; + + /* info: the fourth FEN section (en-passant) can be skipped */ while ((temp = fen_str[i++]) != '\0' && temp != ' ') { } - /* parse the fourth FEN section (halfdraws since last pawn draw) */ + /* parse the fifth FEN section (halfdraws since last pawn draw) */ len = parse_uint(fen_str + i, &(session->halfdraws_since_last_pawn_draw), ' '); if (len > 0) { i += len + 1; } else { return 0; } - /* parse the fifth FEN section (game round) */ + /* parse the sixth FEN section (game round) */ len = parse_uint(fen_str + i, &(session->game_round), '\0'); if (len <= 0) { return 0; } From af0e735d8954af4b79f71a268dd0fcb3ea29d41e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Sat, 7 Aug 2021 19:31:05 +0200 Subject: [PATCH 09/29] continued work on chess game session and FEN (WIP) --- chesslib/include/chessgamesession.h | 2 +- chesslib/src/chessgamesession.c | 2 +- chesslib/src/chesslibmodule.c | 4 +++ chesslib/src/chesspiece.c | 2 +- chesslib/src/chessxformat.c | 41 ++++++++++++++++------------- setup.py | 3 ++- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/chesslib/include/chessgamesession.h b/chesslib/include/chessgamesession.h index 4f7ffef..bcccbd5 100644 --- a/chesslib/include/chessgamesession.h +++ b/chesslib/include/chessgamesession.h @@ -50,6 +50,6 @@ Bitboard get_rochade_mask(ChessGameContext context); uint8_t get_hdslpd(ChessGameContext context); int get_game_rounds(ChessGameContext context); -void apply_draw(ChessDraw draw, ChessGameContext* context); +void apply_draw_to_context(ChessDraw draw, ChessGameContext* context); #endif \ No newline at end of file diff --git a/chesslib/src/chessgamesession.c b/chesslib/src/chessgamesession.c index 69055ea..22c892a 100644 --- a/chesslib/src/chessgamesession.c +++ b/chesslib/src/chessgamesession.c @@ -68,7 +68,7 @@ int get_game_rounds(ChessGameContext context) return (int)(context >> 19); } -void apply_draw(ChessDraw draw, ChessGameContext* context) +void apply_draw_to_context(ChessDraw draw, ChessGameContext* context) { /* alternate the drawing side */ *context ^= 0x1uL; diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index a2a8418..03f6ca5 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -737,21 +737,25 @@ static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObjec /* set was_moved flags for kings / rooks */ + return NULL; } static PyObject* chesslib_board_to_fen(PyObject* self, PyObject* args, PyObject *keywds) { /* TODO: implement logic here ... */ + return NULL; } static PyObject* chesslib_draw_from_pgn(PyObject* self, PyObject* args, PyObject *keywds) { /* TODO: implement logic here ... */ + return NULL; } static PyObject* chesslib_draw_to_pgn(PyObject* self, PyObject* args, PyObject *keywds) { /* TODO: implement logic here ... */ + return NULL; } /* ================================================= diff --git a/chesslib/src/chesspiece.c b/chesslib/src/chesspiece.c index e9d63be..d571ec1 100644 --- a/chesslib/src/chesspiece.c +++ b/chesslib/src/chesspiece.c @@ -55,7 +55,7 @@ ChessColor color_from_char(char c) { case 'W': return White; case 'B': return Black; - default: return White; + default: return White; /* TODO: think of throwing an error instead */ } } diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 570908b..9eb40d6 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -56,6 +56,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) { size_t i = 0, sep_count = 0; char temp; int is_terminal = 0; ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; + ChessPiece simple_board[64]; /* parse the first FEN section (positions of pieces on the board) */ do @@ -82,7 +83,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) color = (ChessColor)(isupper(temp) ? White : Black); type = piece_type_from_char(temp); was_moved = START_POSITIONS & (1uLL << pos) ? 0 : 1; - session->board[pos++] = create_piece(type, color, was_moved); + simple_board[pos++] = create_piece(type, color, was_moved); break; /* handle invalid / unexpected characters */ @@ -95,46 +96,50 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) /* loop until the end of the FEN string's first section */ } while (temp != '\0'); + /* convert the simple board to a bitboard representation */ + from_simple_board(simple_board, board); + return 1; } -int parse_second_fen_section(const char fen_str[], GameContext* context) +int parse_second_fen_section(const char fen_str[], ChessGameContext* context) { ChessColor color = color_from_char(fen_str[0]); - *context ^= (GameContext)(color & 0x1); /* TODO: use a function for this assignment */ + *context = (*context & ~0x1uL) | color; if (fen_str[1] != '\0') { return 0; } return 1; } -int parse_third_fen_section(const char fen_str[], Bitboard* board) +int parse_third_fen_section(const char fen_str[], ChessGameContext* context) { - size_t i = 0; - - /* disable all rochades (enable them explicitly if they are possible) */ - board[12] |= FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8; + size_t i = 0; uint8_t poss_rochades = 0xF; /* handle case with no castlings */ if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* do nothing */ } + + /* handle case with rochades */ else { /* enable castlings (ensure the correct order) */ - if (fen_str[i] == 'K') { board[12] &= ~FIELD_H1; i++; } - if (fen_str[i] == 'Q') { board[12] &= ~FIELD_A1; i++; } - if (fen_str[i] == 'k') { board[12] &= ~FIELD_H8; i++; } - if (fen_str[i] == 'q') { board[12] &= ~FIELD_A8; i++; } + if (fen_str[i] == 'K') { poss_rochades ^= 0x2; i++; } + if (fen_str[i] == 'Q') { poss_rochades ^= 0x1; i++; } + if (fen_str[i] == 'k') { poss_rochades ^= 0x8; i++; } + if (fen_str[i] == 'q') { poss_rochades ^= 0x4; i++; } /* ensure that any rochade is possible and the terminal symbol is hit */ - if (!((~board[12] & (FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8)) - && fen_str[i] == '\0')) { return 0; } + if (!poss_rochades || fen_str[i] != '\0') { return 0; } } + /* TODO: apply the parsed rochades to the game context */ + return 1; } int chess_board_from_fen(const char fen_str[], ChessGameSession* session) { - size_t start = 0, end = 0; - char temp_str[54]; + size_t start = 0, end = 0; char temp; size_t len, i = 0; + char cache[54]; char* temp_str = cache; + ChessGameContext context = session->context; /* create a carbon copy of the fen string (that can be safely modified) */ strcpy(temp_str, fen_str); @@ -148,13 +153,13 @@ int chess_board_from_fen(const char fen_str[], ChessGameSession* session) /* parse the second FEN section (drawing side) */ if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } temp_str[end] = '\0'; - if (!parse_second_fen_section(temp_str, session->board)) { return 0; } + if (!parse_second_fen_section(temp_str, session->context)) { return 0; } temp_str += end + 1; /* parse the third FEN section (possible castlings) */ if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } temp_str[end] = '\0'; - if (!parse_second_fen_section(temp_str, session->board)) { return 0; } + if (!parse_third_fen_section(temp_str, session->board)) { return 0; } temp_str += end + 1; /* info: the fourth FEN section (en-passant) can be skipped */ diff --git a/setup.py b/setup.py index 09a83f6..0be0460 100644 --- a/setup.py +++ b/setup.py @@ -99,7 +99,8 @@ def main(): "chesslib/src/chessdrawgen.c", "chesslib/src/chesspieceatpos.c", "chesslib/src/chessgamestate.c", - "chesslib/src/chessxformat.c" + "chesslib/src/chessxformat.c", + "chesslib/src/chessgamesession.c", ] # define extension module settings (for cross-plattform builds) From 2cb54d4e50e06d11c371e5f33d5874c153b46119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Mon, 9 Aug 2021 19:33:26 +0200 Subject: [PATCH 10/29] added bit masks, finished apply_draw func --- chesslib/src/chessgamesession.c | 50 ++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/chesslib/src/chessgamesession.c b/chesslib/src/chessgamesession.c index 22c892a..e582ca7 100644 --- a/chesslib/src/chessgamesession.c +++ b/chesslib/src/chessgamesession.c @@ -26,6 +26,12 @@ #define GC_DRAWING_SIDE(context) (ChessColor)((context) & 0x1uL) +const ChessGameContext SIDE_MASK = 0x00000001uL; +const ChessGameContext EN_PASSANT_MASK = 0x000001FEuL; +const ChessGameContext ROCHADE_MASK = 0x00001E00uL; +const ChessGameContext HDSLPD_MASK = 0x0007E000uL; +const ChessGameContext GAME_ROUND_MASK = 0xFFF80000uL; + ChessGameContext create_context(ChessColor side, uint8_t en_passants, uint8_t rochades, uint8_t halfdraws_since_last_pawn_draw, int game_round) { @@ -59,31 +65,61 @@ Bitboard get_rochade_mask(ChessGameContext context) uint8_t get_hdslpd(ChessGameContext context) { /* extract the 6 bits counting the halfdraws since the last pawn draw */ - return (uint8_t)((context >> 13) & 0x3FuL); + return (uint8_t)((context & HDSLPD_MASK) >> 13); } int get_game_rounds(ChessGameContext context) { /* extract the 13 bits counting the game rounds */ - return (int)(context >> 19); + return (int)((context & GAME_ROUND_MASK) >> 19); } void apply_draw_to_context(ChessDraw draw, ChessGameContext* context) { + ChessGameContext temp = *context; + /* alternate the drawing side */ *context ^= 0x1uL; /* increment the game round counter (if white is drawing) */ if (GC_DRAWING_SIDE(*context) == White) { - *context = (((ChessGameContext)get_game_rounds(*context) + 1) << 19) - | (*context & 0x7FFFFuL); + *context = (((ChessGameContext)get_game_rounds(temp) + 1) << 19) + | (*context & ~GAME_ROUND_MASK); } /* update the hdslpd counter (either increment or reset) */ - *context = *context & 0xFFF81FFFuL; /* reset bits */ + *context = *context & ~HDSLPD_MASK; /* reset bits */ if (get_drawing_piece_type(draw) != Peasant) { - *context |= (((ChessGameContext)get_hdslpd(*context) + 1) << 13); + *context |= (((ChessGameContext)get_hdslpd(temp) + 1) << 13); + } + + /* handle en-passant (if a peasant moved double-forward) */ + *context = *context & ~EN_PASSANT_MASK; /* reset bits */ + if (get_drawing_piece_type(draw) == Peasant + && abs(get_new_position(draw) - get_old_position(draw)) == 16 + && ((get_drawing_side(draw) ? ROW_5 : ROW_4) & get_new_position(draw))) + { + /* determine the column of the possible en-passant and set the bit */ + *context |= ((ChessGameContext)0x1uL << (get_new_position(draw) % 8 + 1)); } - /* TODO: handle rochade / en-passant */ + /* handle rochade (if a king or a rook moved) */ + *context = *context & ~ROCHADE_MASK; /* reset bits */ + if (get_drawing_piece_type(draw) == King + && (get_old_position(draw) & (FIELD_E1 | FIELD_E8))) + { + /* disable the rochade bits of the given side accordingly */ + *context |= (get_drawing_side(draw) ? 0x1800uL : 0x600uL); + } + if (get_drawing_piece_type(draw) == Rook + && (get_old_position(draw) & (FIELD_A1 | FIELD_H1 | FIELD_A8 | FIELD_H8))) + { + /* disable the rochade bit of a single rook accordingly */ + *context |= (ChessGameContext)( + ((get_old_position(draw) & FIELD_A1) << 9) /* shift to 10th bit */ + | ((get_old_position(draw) & FIELD_H1) << 3) /* shift to 11th bit */ + | ((get_old_position(draw) & FIELD_A8) >> 54) /* shift to 12th bit */ + | ((get_old_position(draw) & FIELD_H8) >> 60)); /* shift to 13th bit */ + /* TODO: make sure the bit shifts are correct */ + } } \ No newline at end of file From 16a8fe66056d797b185233b8ac8933035a20da31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 00:30:33 +0200 Subject: [PATCH 11/29] continued work on FEN parser / serializer --- chesslib/include/chessxformat.h | 8 ++-- chesslib/src/chessxformat.c | 80 ++++++++++++++++++++++++--------- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/chesslib/include/chessxformat.h b/chesslib/include/chessxformat.h index 1d223c9..9feff5f 100644 --- a/chesslib/include/chessxformat.h +++ b/chesslib/include/chessxformat.h @@ -34,9 +34,9 @@ /* TODO: move the board hashing code here */ -int chess_board_from_fen(const char fen_str[], ChessGameSession* session); -int chess_board_to_fen(char** fen_str, const ChessGameSession* session); -int chess_draw_from_pgn(const char fen_str[], Bitboard* board); -int chess_draw_to_pgn(char** fen_str, Bitboard* board); +int chess_session_from_fen(const char fen_str[], ChessGameSession* session); +int chess_session_to_fen(char* fen_str, const ChessGameSession* session); +int chess_session_from_pgn(const char pgn_str[], ChessGameSession* session); +int chess_session_to_pgn(char* pgn_str, const ChessGameSession* session); #endif \ No newline at end of file diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 9eb40d6..4a4e04e 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -28,7 +28,7 @@ /* Parse a positive integer value from the given decimal numeric string and return the length of the characters parsed (return -1 on format error). */ -int parse_uint(char* value_str, int* out_value, char term) +int parse_uint(const char* value_str, int* out_value, char term) { int value = 0; size_t i = 0; @@ -56,7 +56,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) { size_t i = 0, sep_count = 0; char temp; int is_terminal = 0; ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; - ChessPiece simple_board[64]; + ChessPiece simple_board[64] = { 0 }; /* parse the first FEN section (positions of pieces on the board) */ do @@ -102,15 +102,15 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) return 1; } -int parse_second_fen_section(const char fen_str[], ChessGameContext* context) +int parse_second_fen_section(const char fen_str[], ChessColor* side) { ChessColor color = color_from_char(fen_str[0]); - *context = (*context & ~0x1uL) | color; if (fen_str[1] != '\0') { return 0; } + *side = color; return 1; } -int parse_third_fen_section(const char fen_str[], ChessGameContext* context) +int parse_third_fen_section(const char fen_str[], uint8_t* rochades) { size_t i = 0; uint8_t poss_rochades = 0xF; @@ -130,16 +130,17 @@ int parse_third_fen_section(const char fen_str[], ChessGameContext* context) if (!poss_rochades || fen_str[i] != '\0') { return 0; } } - /* TODO: apply the parsed rochades to the game context */ + /* apply the parsed rochades to the game context */ + *rochades = poss_rochades; return 1; } -int chess_board_from_fen(const char fen_str[], ChessGameSession* session) +int chess_session_from_fen(const char fen_str[], ChessGameSession* session) { - size_t start = 0, end = 0; char temp; size_t len, i = 0; - char cache[54]; char* temp_str = cache; - ChessGameContext context = session->context; + size_t end = 0; size_t len, i = 0; int game_round = 0; + ChessColor side; uint8_t rochades = 0, en_passants = 0, hdslpd = 0; + char cache[54]; char* temp_str = cache; Bitboard board[13] = { 0 }; /* create a carbon copy of the fen string (that can be safely modified) */ strcpy(temp_str, fen_str); @@ -147,48 +148,83 @@ int chess_board_from_fen(const char fen_str[], ChessGameSession* session) /* parse the first FEN section (positions of pieces on the board) */ if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } temp_str[end] = '\0'; - if (!parse_first_fen_section(temp_str, session->board)) { return 0; } + if (!parse_first_fen_section(temp_str, board)) { return 0; } temp_str += end + 1; /* parse the second FEN section (drawing side) */ if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } temp_str[end] = '\0'; - if (!parse_second_fen_section(temp_str, session->context)) { return 0; } + if (!parse_second_fen_section(temp_str, &side)) { return 0; } temp_str += end + 1; /* parse the third FEN section (possible castlings) */ if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } temp_str[end] = '\0'; - if (!parse_third_fen_section(temp_str, session->board)) { return 0; } + if (!parse_third_fen_section(temp_str, &rochades)) { return 0; } temp_str += end + 1; - /* info: the fourth FEN section (en-passant) can be skipped */ - while ((temp = fen_str[i++]) != '\0' && temp != ' ') { } + /* parse the fourth FEN section (en-passant) */ + /* TODO: add parsing logic */ /* parse the fifth FEN section (halfdraws since last pawn draw) */ - len = parse_uint(fen_str + i, &(session->halfdraws_since_last_pawn_draw), ' '); + len = parse_uint(fen_str + i, (int*)&hdslpd, ' '); if (len > 0) { i += len + 1; } else { return 0; } /* parse the sixth FEN section (game round) */ - len = parse_uint(fen_str + i, &(session->game_round), '\0'); + len = parse_uint(fen_str + i, &game_round, '\0'); if (len <= 0) { return 0; } + /* assign the parsed FEN string content to the game session object */ + copy_board(board, session->board); + session->context = create_context(side, + en_passants, rochades, hdslpd, game_round); + return 1; } -int chess_board_to_fen(char** fen_str, const ChessGameSession* session) +int chess_session_to_fen(char* fen_str, const ChessGameSession* session) { - /* TODO: implement logic */ - return 0; + size_t i = 0, empty_cnt = 0; ChessPosition pos; ChessPiece piece; char temp; + ChessPiece simple_board[64] = { 0 }; + + /* write the first section to the game session (pieces on board) */ + to_simple_board(session->board, simple_board); + + for (pos = 0; pos < 64; pos++) + { + piece = simple_board[pos]; + + /* in case the field position is not empty */ + if (piece != CHESS_PIECE_NULL) + { + /* write the piece type (either uppercase for white or lowercase for black) */ + temp = piece_type_to_char(get_piece_type(piece)); + fen_str[i++] = get_piece_color(piece) + ? tolower(temp) : toupper(temp); + } + else { empty_cnt++; } + + /* handle empty field spaces */ + if ((piece != CHESS_PIECE_NULL || (pos + 1) % 8 == 0) && empty_cnt > 0) + { fen_str[i++] = '0' + empty_cnt; empty_cnt = 0; } + + /* handle end-of-row separator */ + if ((pos + 1) % 8 == 0 && pos < 63) { fen_str[i++] = '/'; } + } + + /* TODO */ + + fen_str[i] = '\0'; + return 1; } -int chess_draw_from_pgn(const char fen_str[], Bitboard* board) +int chess_session_from_pgn(const char pgn_str[], ChessGameSession* session) { /* TODO: implement logic */ return 0; } -int chess_draw_to_pgn(char** fen_str, Bitboard* board) +int chess_session_to_pgn(char* pgn_str, const ChessGameSession* session) { /* TODO: implement logic */ return 0; From 8c3fa34e0f2b16bbc264d70d9d8b3e6b7188943e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 10:21:48 +0200 Subject: [PATCH 12/29] refactored position parsing logic --- chesslib/include/chessposition.h | 2 ++ chesslib/src/chessposition.c | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/chesslib/include/chessposition.h b/chesslib/include/chessposition.h index 8e5789f..9add8a6 100644 --- a/chesslib/include/chessposition.h +++ b/chesslib/include/chessposition.h @@ -33,6 +33,7 @@ #include #include +#include /* ==================================================== D E F I N E F U N C T I O N S @@ -45,6 +46,7 @@ ChessPosition create_position(int8_t row, int8_t column); int8_t get_row(ChessPosition position); int8_t get_column(ChessPosition position); +int position_from_string(const char* pos_str, ChessPosition* pos); void position_to_string(ChessPosition position, char* pos_str); #endif diff --git a/chesslib/src/chessposition.c b/chesslib/src/chessposition.c index ca68978..d08d239 100644 --- a/chesslib/src/chessposition.c +++ b/chesslib/src/chessposition.c @@ -39,6 +39,31 @@ int8_t get_column(ChessPosition position) return (position & 7); } +int position_from_string(const char* pos_str, ChessPosition* pos) +{ + uint8_t row, column; + + /* make sure the first character is within [a-h] or [A-H] */ + if (!isalpha(pos_str[0]) + || toupper(pos_str[0]) - 'A' >= 8 + || toupper(pos_str[0]) - 'A' < 0) + { return 0; } + + /* make sure the second character is within [1-8] */ + if (!isdigit(pos_str[1]) || pos_str[1] - '1' >= 8) { return 0; } + + /* make sure the third character is a zero-terminal */ + if (pos_str[2] != '\0') { return 0; } + + /* finally, do the actual parsing */ + row = pos_str[1] - '1'; + column = toupper(pos_str[0]) - 'A'; + *pos = create_position(row, column); + + /* parsing successful! */ + return 1; +} + void position_to_string(ChessPosition position, char* pos_str) { /* serialize position as string*/ From aea5883977614d011245a52641fbacd53e0bfea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 10:28:22 +0200 Subject: [PATCH 13/29] added en-passant parser logic --- chesslib/src/chessxformat.c | 44 ++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 4a4e04e..86854d5 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -136,6 +136,29 @@ int parse_third_fen_section(const char fen_str[], uint8_t* rochades) return 1; } +int parse_fourth_fen_section(const char fen_str[], uint8_t* en_passants) +{ + size_t i = 0; uint8_t poss_en_passants = 0xF; ChessPosition pos = 0; + + /* handle case with no en-passant */ + if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* do nothing */ } + + /* handle case with rochades */ + else + { + /* parse the en-passant position from string */ + if (!position_from_string(fen_str, &pos)) { return 0; } + + /* set the en-passant bit accordingly */ + poss_en_passants = 0x1 << (pos % 8); + } + + /* apply the parsed en-passant to the game context */ + *en_passants = poss_en_passants; + + return 1; +} + int chess_session_from_fen(const char fen_str[], ChessGameSession* session) { size_t end = 0; size_t len, i = 0; int game_round = 0; @@ -164,7 +187,10 @@ int chess_session_from_fen(const char fen_str[], ChessGameSession* session) temp_str += end + 1; /* parse the fourth FEN section (en-passant) */ - /* TODO: add parsing logic */ + if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } + temp_str[end] = '\0'; + if (!parse_third_fen_section(temp_str, &en_passants)) { return 0; } + temp_str += end + 1; /* parse the fifth FEN section (halfdraws since last pawn draw) */ len = parse_uint(fen_str + i, (int*)&hdslpd, ' '); @@ -190,29 +216,31 @@ int chess_session_to_fen(char* fen_str, const ChessGameSession* session) /* write the first section to the game session (pieces on board) */ to_simple_board(session->board, simple_board); + /* loop through each field on the chess board */ for (pos = 0; pos < 64; pos++) { piece = simple_board[pos]; - /* in case the field position is not empty */ + /* handle empty field spaces symbol */ + if ((piece != CHESS_PIECE_NULL || (pos + 1) % 8 == 0) && empty_cnt > 0) + { fen_str[i++] = '0' + empty_cnt; empty_cnt = 0; } + + /* handle piece symbol */ if (piece != CHESS_PIECE_NULL) { - /* write the piece type (either uppercase for white or lowercase for black) */ + /* write the piece type (uppercase -> white, lowercase -> black) */ temp = piece_type_to_char(get_piece_type(piece)); fen_str[i++] = get_piece_color(piece) ? tolower(temp) : toupper(temp); } + /* handle empty field -> increment counter */ else { empty_cnt++; } - /* handle empty field spaces */ - if ((piece != CHESS_PIECE_NULL || (pos + 1) % 8 == 0) && empty_cnt > 0) - { fen_str[i++] = '0' + empty_cnt; empty_cnt = 0; } - /* handle end-of-row separator */ if ((pos + 1) % 8 == 0 && pos < 63) { fen_str[i++] = '/'; } } - /* TODO */ + /* TODO: write missing FEN sections to the output string */ fen_str[i] = '\0'; return 1; From dcdd298e240c1c5b09f569b46061af2bb43f4241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 10:28:59 +0200 Subject: [PATCH 14/29] refactored position parsing function --- chesslib/src/chesslibmodule.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 03f6ca5..fd72f1a 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -359,25 +359,17 @@ PyMODINIT_FUNC PyInit_chesslib(void) **************************************************************************/ static PyObject* chesslib_create_chessposition(PyObject* self, PyObject* args, PyObject *keywds) { - const char* pos_as_str; - uint8_t row = 0, column = 0; + const char* pos_as_str; ChessPosition pos = 0; static char *kwlist[] = {"pos_as_str", NULL}; /* read position string, quit if the parameter does not exist */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "s", kwlist, &pos_as_str)) { return NULL; } - /* make sure that the overloaded string is of the correct format */ - if (*(pos_as_str + 2) != '\0' - || (!isalpha(pos_as_str[0]) || toupper(pos_as_str[0]) - 'A' >= 8 - || toupper(pos_as_str[0]) - 'A' < 0) - || (!isdigit(pos_as_str[1]) || pos_as_str[1] - '1' >= 8)) { return NULL; } - - /* parse position from position string */ - row = pos_as_str[1] - '1'; - column = toupper(pos_as_str[0]) - 'A'; + /* parse the position index from string */ + if (!position_from_string(pos_as_str, &pos)) { return NULL; } /* create uint32 python object and return it */ - return PyLong_FromUnsignedLong(create_position(row, column)); + return PyLong_FromUnsignedLong(pos); } /************************************************************************** From 44843520b2c3fc3caf65e67a3fbad4ec077ec9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 14:46:12 +0200 Subject: [PATCH 15/29] added idea for saving some bits --- chesslib/include/chesstypes.h | 1 + 1 file changed, 1 insertion(+) diff --git a/chesslib/include/chesstypes.h b/chesslib/include/chesstypes.h index 7c5cdeb..cb67ac6 100644 --- a/chesslib/include/chesstypes.h +++ b/chesslib/include/chesstypes.h @@ -104,5 +104,6 @@ typedef uint64_t Bitboard; | ------------- | ------------------------- | -------- | ----------- | ---- | | xxxxxxxxxxxxx | xxxxxx | xxxx | xxxxxxxx | x | */ typedef uint32_t ChessGameContext; +/* TODO: think of compressing the en-passant part to 3 bits, pos indices [0-7] */ #endif From a79c18509189ca59ff0cfc41f45c472b2fc62404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 14:46:57 +0200 Subject: [PATCH 16/29] finished session to FEN (needs testing) --- chesslib/src/chessxformat.c | 48 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 86854d5..3195d0c 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -26,6 +26,8 @@ /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ +#define DECIMAL_LEN(num) ((int)((ceil(log10((num))) + 1) * sizeof(char))) + /* Parse a positive integer value from the given decimal numeric string and return the length of the characters parsed (return -1 on format error). */ int parse_uint(const char* value_str, int* out_value, char term) @@ -211,9 +213,12 @@ int chess_session_from_fen(const char fen_str[], ChessGameSession* session) int chess_session_to_fen(char* fen_str, const ChessGameSession* session) { size_t i = 0, empty_cnt = 0; ChessPosition pos; ChessPiece piece; char temp; + Bitboard rochades_mask = 0, en_passant_mask = 0; int i_temp; ChessPiece simple_board[64] = { 0 }; - /* write the first section to the game session (pieces on board) */ + /* write the first section to the FEN string (pieces on board) */ + + /* convert the session's board to the simple board format */ to_simple_board(session->board, simple_board); /* loop through each field on the chess board */ @@ -230,8 +235,7 @@ int chess_session_to_fen(char* fen_str, const ChessGameSession* session) { /* write the piece type (uppercase -> white, lowercase -> black) */ temp = piece_type_to_char(get_piece_type(piece)); - fen_str[i++] = get_piece_color(piece) - ? tolower(temp) : toupper(temp); + fen_str[i++] = get_piece_color(piece) ? tolower(temp) : toupper(temp); } /* handle empty field -> increment counter */ else { empty_cnt++; } @@ -240,8 +244,44 @@ int chess_session_to_fen(char* fen_str, const ChessGameSession* session) if ((pos + 1) % 8 == 0 && pos < 63) { fen_str[i++] = '/'; } } - /* TODO: write missing FEN sections to the output string */ + /* write the second section to the FEN string (drawing side) */ + fen_str[i++] = ' '; + fen_str[i++] = tolower(color_to_char((ChessColor)(session->context & 1uL))); + + /* write the third section to the FEN string (rochades) */ + fen_str[i++] = ' '; + rochades_mask = get_rochade_mask(session->context); + if (rochades_mask & FIELD_H1) { fen_str[i++] = 'K'; } + if (rochades_mask & FIELD_A1) { fen_str[i++] = 'Q'; } + if (rochades_mask & FIELD_H8) { fen_str[i++] = 'k'; } + if (rochades_mask & FIELD_A8) { fen_str[i++] = 'q'; } + if (!rochades_mask) { fen_str[i++] = '-'; } + + /* write the fourth section to the FEN string (en-passant) */ + fen_str[i++] = ' '; + en_passant_mask = get_en_passant_mask(session->context); + if (!en_passant_mask) { fen_str[i++] = '-'; } + else + { + /* get the set bit's position as index and convert it to a string */ + pos = get_board_position(en_passant_mask); + position_to_string(pos, (fen_str + i)); + i += 2; + } + + /* write the fifth section to the FEN string (hdslpd) */ + fen_str[i++] = ' '; + i_temp = (int)get_hdslpd(session->context); + sprintf((fen_str + i), "%d", i_temp); + i += DECIMAL_LEN(i_temp); + + /* write the sixth section to the FEN string (game round) */ + fen_str[i++] = ' '; + i_temp = (int)get_hdslpd(session->context); + sprintf((fen_str + i), "%d", i_temp); + i += DECIMAL_LEN(i_temp); + /* write the zero-terminal char and return success! */ fen_str[i] = '\0'; return 1; } From ad97e50e4d2794c829577d3f28674a7b3f17821c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Tue, 10 Aug 2021 16:23:18 +0200 Subject: [PATCH 17/29] finalized implementation (needs more testing) --- chesslib/include/chessboard.h | 2 +- chesslib/include/chessgamesession.h | 10 ++++-- chesslib/src/chessgamesession.c | 11 ++++++- chesslib/src/chesslibmodule.c | 49 +++++++++++++++++++++-------- chesslib/src/chessxformat.c | 5 +-- tests/test.py | 21 +++++++++++++ 6 files changed, 79 insertions(+), 19 deletions(-) diff --git a/chesslib/include/chessboard.h b/chesslib/include/chessboard.h index 5feca14..8afc4dc 100644 --- a/chesslib/include/chessboard.h +++ b/chesslib/include/chessboard.h @@ -47,7 +47,7 @@ 0x0000000000000024uLL, 0x0000000000000042uLL, 0x000000000000FF00uLL,\ 0x1000000000000000uLL, 0x0800000000000000uLL, 0x8100000000000000uLL,\ 0x2400000000000000uLL, 0x4200000000000000uLL, 0x00FF000000000000uLL,\ - 0x0000FFFFFFFF0000uLL }; + 0x0000FFFFFFFF0000uLL } /* row masks */ #define ROW_1 0x00000000000000FFuLL diff --git a/chesslib/include/chessgamesession.h b/chesslib/include/chessgamesession.h index bcccbd5..1be295a 100644 --- a/chesslib/include/chessgamesession.h +++ b/chesslib/include/chessgamesession.h @@ -36,10 +36,15 @@ typedef struct _CHESS_GAME_SESSION { ChessGameContext context; } ChessGameSession; -/* default game context bits: 00000000 00001000 00011110 00000000 */ +/* default game context bits: 00000000 00001000 00011110 00000000 + * + * meaning: + * first game round, all rochades possible, no en-passant possible, + * white drawing and zero halfdraws since the last pawn draw. + */ #define DEFAULT_GAME_CONTEXT 0x00081E00uL -/* inital game session: board in start formation and initial game context */ +/* initial game session: board in start formation and initial game context */ #define INIT_GAME_SESSION {START_FORMATION, DEFAULT_GAME_CONTEXT} ChessGameContext create_context(ChessColor side, uint8_t en_passants, @@ -51,5 +56,6 @@ uint8_t get_hdslpd(ChessGameContext context); int get_game_rounds(ChessGameContext context); void apply_draw_to_context(ChessDraw draw, ChessGameContext* context); +void apply_game_context_to_board(Bitboard* board, ChessGameContext context); #endif \ No newline at end of file diff --git a/chesslib/src/chessgamesession.c b/chesslib/src/chessgamesession.c index e582ca7..0b4bba7 100644 --- a/chesslib/src/chessgamesession.c +++ b/chesslib/src/chessgamesession.c @@ -122,4 +122,13 @@ void apply_draw_to_context(ChessDraw draw, ChessGameContext* context) | ((get_old_position(draw) & FIELD_H8) >> 60)); /* shift to 13th bit */ /* TODO: make sure the bit shifts are correct */ } -} \ No newline at end of file +} + +void apply_game_context_to_board(Bitboard* board, ChessGameContext context) +{ + Bitboard rochade_mask = 0; + + /* set was_moved bits for rooks */ + rochade_mask = get_rochade_mask(context); + board[12] &= ~rochade_mask; /* TODO: check if this works */ +} diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index fd72f1a..84d5118 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -230,7 +230,20 @@ Args:\n\ is_simple_board: Indicates whether the resulting chess board should be of the simple board format, defaults to False\n\ \n\ Returns:\n\ - the game state related to the given game situation, encoded as integer/ASCII byte"; + the chess board represented by the given hash, as numpy array"; + +/* if (!PyArg_ParseTuple(args, "s|i", &fen_str, &is_simple_board)) { return NULL; } */ +const char Board_FromFen_Docstring[] = +"Board_FromFen(fen_str: str, is_simple_board: bool=False) -> int\n\ +\n\ +Convert the given FEN string to a chess board.\n\ +\n\ +Args:\n\ + fen_str: The FEN string representation to be imported\n\ + is_simple_board: Indicates whether the resulting chess board should be of the simple board format, defaults to False\n\ +\n\ +Returns:\n\ + the chess board represented by the given FEN string, as numpy array"; /* if (!PyArg_ParseTuple(args, "O|i", &bitboards, &is_simple_board)) { return NULL; } */ const char VisualizeBoard_Docstring[] = @@ -286,6 +299,7 @@ static PyMethodDef chesslib_methods[] = { /* extensions for data compression */ {"Board_ToHash", chesslib_board_to_hash, METH_VARARGS | METH_KEYWORDS, Board_ToHash_Docstring}, {"Board_FromHash", chesslib_board_from_hash, METH_VARARGS | METH_KEYWORDS, Board_FromHash_Docstring}, + {"Board_FromFen", chesslib_board_from_fen, METH_VARARGS | METH_KEYWORDS, Board_FromFen_Docstring}, /* extensions for data visualization of complex type encodings */ {"VisualizeBoard", chesslib_visualize_board, METH_VARARGS | METH_KEYWORDS, VisualizeBoard_Docstring}, @@ -432,7 +446,7 @@ static PyObject* chesslib_create_startformation(PyObject* self, PyObject* args, static char* kwlist[] = {"is_simple", NULL}; /* create the chess board */ - const Bitboard start_formation[] = START_FORMATION + const Bitboard start_formation[] = START_FORMATION; /* parse all args */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "|i", kwlist, &is_simple_board)) { return NULL; } @@ -716,20 +730,29 @@ static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args, PyObje static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObject *keywds) { /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ - /* FEN structure: - 1) positions of pieces - 2) drawing side - 3) possible rochades - 4) possible en-passants - 5) half-draws since last peasant draw - 6) round no. - */ - /* generate a board from FEN pieces list */ + char* fen_str; int is_simple_board = 0; PyObject* chessboard; + ChessGameSession session = INIT_GAME_SESSION; ChessPiece simple_board[64]; + static char* kwlist[] = {"fen_str", "is_simple", NULL}; + + /* parse bitboards as ChessBoard struct */ + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|i", kwlist, + &fen_str, &is_simple_board)) { return NULL; } + /* convert the FEN string to a game session */ + if (!chess_session_from_fen(fen_str, &session)) { return NULL; } - /* set was_moved flags for kings / rooks */ - return NULL; + /* convert the session's bitboard to a simple board if required */ + if (is_simple_board) { to_simple_board(session.board, simple_board); } + + /* return as simple board or bitboards format */ + chessboard = is_simple_board + ? serialize_as_pieces(simple_board) + : serialize_as_bitboards(session.board); + + /* TODO: think of returning the game session instead of just a chess board */ + + return chessboard; } static PyObject* chesslib_board_to_fen(PyObject* self, PyObject* args, PyObject *keywds) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 3195d0c..632f7f1 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -57,7 +57,7 @@ int str_index_of(char* search_str, char find) int parse_first_fen_section(const char fen_str[], Bitboard* board) { size_t i = 0, sep_count = 0; char temp; int is_terminal = 0; - ChessPosition pos = 0; ChessColor color; ChessPieceType type; int was_moved; + ChessPosition pos = 56; ChessColor color; ChessPieceType type; int was_moved; ChessPiece simple_board[64] = { 0 }; /* parse the first FEN section (positions of pieces on the board) */ @@ -72,7 +72,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) case '\0': if (!is_terminal) { return 0; } break; /* ensure that the row bounds are not violated */ - case '/': if (pos != ++sep_count * 8) { return 0; } break; + case '/': pos -= 16; if (pos != ++sep_count * 8) { return 0; } break; /* handle empty fields declaration */ case '1': case '2': case '3': case '4': @@ -206,6 +206,7 @@ int chess_session_from_fen(const char fen_str[], ChessGameSession* session) copy_board(board, session->board); session->context = create_context(side, en_passants, rochades, hdslpd, game_round); + apply_game_context_to_board(session->board, session->context); return 1; } diff --git a/tests/test.py b/tests/test.py index d07e01d..14b43eb 100644 --- a/tests/test.py +++ b/tests/test.py @@ -46,6 +46,7 @@ def test_module(): test_apply_draw() test_game_state() test_board_hash() + test_board_from_fen() # # test visualization functions test_visualize_board() @@ -660,5 +661,25 @@ def test_visualize_draw(): # TODO: add more tests for edge cases (rochade, en-passant, promotion) +def test_board_from_fen(): + + # TODO: make this test work + + print("testing visualize chess draw") + + # FEN string representing the initial game state + fen_str = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' + board_exp = chesslib.ChessBoard_StartFormation() + + # now, parse the FEN string into a chess board + board = chesslib.Board_FromFen(fen_str) + print(board) + + # make sure that the expected content is retrieved + assert_true(np.array_equal(board, board_exp)) + + print("test passed!") + + if __name__ == '__main__': test_module() From 4d37535299bbce6895a205809de50ffe18d99d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 14:01:21 +0200 Subject: [PATCH 18/29] moved chess board functions between code files --- chesslib/include/chessboard.h | 4 +++ chesslib/src/chessboard.c | 64 +++++++++++++++++++++++++++++++++++ chesslib/src/chessdrawgen.c | 64 ----------------------------------- 3 files changed, 68 insertions(+), 64 deletions(-) diff --git a/chesslib/include/chessboard.h b/chesslib/include/chessboard.h index 8afc4dc..f515504 100644 --- a/chesslib/include/chessboard.h +++ b/chesslib/include/chessboard.h @@ -123,4 +123,8 @@ void to_simple_board(const Bitboard board[], ChessPiece* target); void compress_pieces_array(const ChessPiece pieces[], uint8_t* compr_bytes); void uncompress_pieces_array(const uint8_t compr_bytes[], ChessPiece* out_pieces); +Bitboard get_captured_fields(const Bitboard bitboards[], ChessColor side); +void get_board_positions(Bitboard bitboard, ChessPosition* out_positions, size_t* out_length); +ChessPosition get_board_position(Bitboard bitboard); + #endif diff --git a/chesslib/src/chessboard.c b/chesslib/src/chessboard.c index 4e0495a..fc9536d 100644 --- a/chesslib/src/chessboard.c +++ b/chesslib/src/chessboard.c @@ -350,3 +350,67 @@ void uncompress_pieces_array(const uint8_t compr_bytes[], ChessPiece* out_pieces out_pieces[pos] = piece_bits; } } + +/* ==================================================== + C H E S S P O S I T I O N S + O N B I T B O A R D + ==================================================== */ + +Bitboard get_captured_fields(const Bitboard bitboards[], ChessColor side) +{ + uint8_t offset = SIDE_OFFSET(side); + + return bitboards[offset] | bitboards[offset + 1] | bitboards[offset + 2] + | bitboards[offset + 3] | bitboards[offset + 4] | bitboards[offset + 5]; +} + +void get_board_positions(Bitboard bitboard, ChessPosition* out_positions, size_t* out_length) +{ + uint8_t pos; + *out_length = 0; + + /* loop through all bits of the board */ + for (pos = 0; pos < 64; pos++) + { + if ((bitboard & 0x1uLL << pos)) { out_positions[(*out_length)++] = (ChessPosition)pos; } + } +} + +/************************************************************************************************** + this returns the index of the highest bit set on the given bitboard. + if the given bitboard has multiple bits set, only the position of the highest bit is returned. + info: the result is mathematically equal to floor(log2(x)) + **************************************************************************************************/ +ChessPosition get_board_position(Bitboard bitboard) +{ + /* code was taken from https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers */ + +#ifdef __GNUC__ + /* use built-in leading zeros function for GCC Linux build + (this compiles to the very fast 'bsr' instruction on x86 AMD processors) */ + return (ChessPosition)((unsigned)(8 * sizeof(unsigned long long) - __builtin_clzll(bitboard) - 1)); +#else + /* use abstract DeBruijn algorithm with table lookup */ + /* TODO: think of implementing this as assembler code */ + + const uint8_t tab64[64] = { + 63, 0, 58, 1, 59, 47, 53, 2, + 60, 39, 48, 27, 54, 33, 42, 3, + 61, 51, 37, 40, 49, 18, 28, 20, + 55, 30, 34, 11, 43, 14, 22, 4, + 62, 57, 46, 52, 38, 26, 32, 41, + 50, 36, 17, 19, 29, 10, 13, 21, + 56, 45, 25, 31, 35, 16, 9, 12, + 44, 24, 15, 8, 23, 7, 6, 5 + }; + + bitboard |= bitboard >> 1; + bitboard |= bitboard >> 2; + bitboard |= bitboard >> 4; + bitboard |= bitboard >> 8; + bitboard |= bitboard >> 16; + bitboard |= bitboard >> 32; + + return (ChessPosition)tab64[((Bitboard)((bitboard - (bitboard >> 1)) * 0x07EDD5E59A4E28C2uLL)) >> 58]; +#endif +} diff --git a/chesslib/src/chessdrawgen.c b/chesslib/src/chessdrawgen.c index 064f2d2..450ca2d 100644 --- a/chesslib/src/chessdrawgen.c +++ b/chesslib/src/chessdrawgen.c @@ -490,67 +490,3 @@ Bitboard get_peasant_draw_positions(const Bitboard bitboards[], return draws; } - -/* ==================================================== - C H E S S P O S I T I O N S - O N B I T B O A R D - ==================================================== */ - -Bitboard get_captured_fields(const Bitboard bitboards[], ChessColor side) -{ - uint8_t offset = SIDE_OFFSET(side); - - return bitboards[offset] | bitboards[offset + 1] | bitboards[offset + 2] - | bitboards[offset + 3] | bitboards[offset + 4] | bitboards[offset + 5]; -} - -void get_board_positions(Bitboard bitboard, ChessPosition* out_positions, size_t* out_length) -{ - uint8_t pos; - *out_length = 0; - - /* loop through all bits of the board */ - for (pos = 0; pos < 64; pos++) - { - if ((bitboard & 0x1uLL << pos)) { out_positions[(*out_length)++] = (ChessPosition)pos; } - } -} - -/************************************************************************************************** - this returns the index of the highest bit set on the given bitboard. - if the given bitboard has multiple bits set, only the position of the highest bit is returned. - info: the result is mathematically equal to floor(log2(x)) - **************************************************************************************************/ -ChessPosition get_board_position(Bitboard bitboard) -{ - /* code was taken from https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers */ - -#ifdef __GNUC__ - /* use built-in leading zeros function for GCC Linux build - (this compiles to the very fast 'bsr' instruction on x86 AMD processors) */ - return (ChessPosition)((unsigned)(8 * sizeof(unsigned long long) - __builtin_clzll(bitboard) - 1)); -#else - /* use abstract DeBruijn algorithm with table lookup */ - /* TODO: think of implementing this as assembler code */ - - const uint8_t tab64[64] = { - 63, 0, 58, 1, 59, 47, 53, 2, - 60, 39, 48, 27, 54, 33, 42, 3, - 61, 51, 37, 40, 49, 18, 28, 20, - 55, 30, 34, 11, 43, 14, 22, 4, - 62, 57, 46, 52, 38, 26, 32, 41, - 50, 36, 17, 19, 29, 10, 13, 21, - 56, 45, 25, 31, 35, 16, 9, 12, - 44, 24, 15, 8, 23, 7, 6, 5 - }; - - bitboard |= bitboard >> 1; - bitboard |= bitboard >> 2; - bitboard |= bitboard >> 4; - bitboard |= bitboard >> 8; - bitboard |= bitboard >> 16; - bitboard |= bitboard >> 32; - - return (ChessPosition)tab64[((Bitboard)((bitboard - (bitboard >> 1)) * 0x07EDD5E59A4E28C2uLL)) >> 58]; -#endif -} From 5528e0d656d01119c02cd82acb1cbe035596a4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 14:01:55 +0200 Subject: [PATCH 19/29] fixed header import warnings --- chesslib/include/chessxformat.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chesslib/include/chessxformat.h b/chesslib/include/chessxformat.h index 9feff5f..75a3051 100644 --- a/chesslib/include/chessxformat.h +++ b/chesslib/include/chessxformat.h @@ -26,7 +26,10 @@ #define CHESSXFORMAT_H #include +#include #include +#include + #include "chesstypes.h" #include "chesspiece.h" #include "chessboard.h" From 78c9211f66b026e66bafcbc77b22038ed720ed80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 14:02:50 +0200 Subject: [PATCH 20/29] fixed typecast warnings for py_func declarations --- chesslib/src/chesslibmodule.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 84d5118..4091558 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -284,26 +284,26 @@ Returns:\n\ static PyMethodDef chesslib_methods[] = { /* data types and structures */ - {"ChessPosition", chesslib_create_chessposition, METH_VARARGS | METH_KEYWORDS, ChessPosition_Docstring}, - {"ChessPiece", chesslib_create_chesspiece, METH_VARARGS | METH_KEYWORDS, ChessPiece_Docstring}, - {"ChessPieceAtPos", chesslib_create_chesspieceatpos, METH_VARARGS | METH_KEYWORDS, ChessPieceAtPos_Docstring}, - {"ChessBoard", chesslib_create_chessboard, METH_VARARGS | METH_KEYWORDS, ChessBoard_Docstring}, - {"ChessBoard_StartFormation", chesslib_create_startformation, METH_VARARGS | METH_KEYWORDS, ChessBoard_StartFormation_Docstring}, - {"ChessDraw", chesslib_create_chessdraw, METH_VARARGS | METH_KEYWORDS, ChessDraw_Docstring}, + {"ChessPosition", (PyCFunction)chesslib_create_chessposition, METH_VARARGS | METH_KEYWORDS, ChessPosition_Docstring}, + {"ChessPiece", (PyCFunction)chesslib_create_chesspiece, METH_VARARGS | METH_KEYWORDS, ChessPiece_Docstring}, + {"ChessPieceAtPos", (PyCFunction)chesslib_create_chesspieceatpos, METH_VARARGS | METH_KEYWORDS, ChessPieceAtPos_Docstring}, + {"ChessBoard", (PyCFunction)chesslib_create_chessboard, METH_VARARGS | METH_KEYWORDS, ChessBoard_Docstring}, + {"ChessBoard_StartFormation", (PyCFunction)chesslib_create_startformation, METH_VARARGS | METH_KEYWORDS, ChessBoard_StartFormation_Docstring}, + {"ChessDraw", (PyCFunction)chesslib_create_chessdraw, METH_VARARGS | METH_KEYWORDS, ChessDraw_Docstring}, /* core chess logic for gameplay */ - {"GenerateDraws", chesslib_get_all_draws, METH_VARARGS | METH_KEYWORDS, GenerateDraws_Docstring}, - {"ApplyDraw", chesslib_apply_draw, METH_VARARGS | METH_KEYWORDS, ApplyDraw_Docstring}, - {"GameState", chesslib_get_game_state, METH_VARARGS | METH_KEYWORDS, GameState_Docstring}, + {"GenerateDraws", (PyCFunction)chesslib_get_all_draws, METH_VARARGS | METH_KEYWORDS, GenerateDraws_Docstring}, + {"ApplyDraw", (PyCFunction)chesslib_apply_draw, METH_VARARGS | METH_KEYWORDS, ApplyDraw_Docstring}, + {"GameState", (PyCFunction)chesslib_get_game_state, METH_VARARGS | METH_KEYWORDS, GameState_Docstring}, /* extensions for data compression */ - {"Board_ToHash", chesslib_board_to_hash, METH_VARARGS | METH_KEYWORDS, Board_ToHash_Docstring}, - {"Board_FromHash", chesslib_board_from_hash, METH_VARARGS | METH_KEYWORDS, Board_FromHash_Docstring}, - {"Board_FromFen", chesslib_board_from_fen, METH_VARARGS | METH_KEYWORDS, Board_FromFen_Docstring}, + {"Board_ToHash", (PyCFunction)chesslib_board_to_hash, METH_VARARGS | METH_KEYWORDS, Board_ToHash_Docstring}, + {"Board_FromHash", (PyCFunction)chesslib_board_from_hash, METH_VARARGS | METH_KEYWORDS, Board_FromHash_Docstring}, + {"Board_FromFen", (PyCFunction)chesslib_board_from_fen, METH_VARARGS | METH_KEYWORDS, Board_FromFen_Docstring}, /* extensions for data visualization of complex type encodings */ - {"VisualizeBoard", chesslib_visualize_board, METH_VARARGS | METH_KEYWORDS, VisualizeBoard_Docstring}, - {"VisualizeDraw", chesslib_visualize_draw, METH_VARARGS | METH_KEYWORDS, VisualizeDraw_Docstring}, + {"VisualizeBoard", (PyCFunction)chesslib_visualize_board, METH_VARARGS | METH_KEYWORDS, VisualizeBoard_Docstring}, + {"VisualizeDraw", (PyCFunction)chesslib_visualize_draw, METH_VARARGS | METH_KEYWORDS, VisualizeDraw_Docstring}, /* TODO: add functions for visualizing remaining data structures like chess piece, chess pos, piece@pos */ /* TODO: add functions for converting from/to portable formats like FEN / PGN, etc. */ From f9343804377214fd3cddbec9acb4e65fb3099b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 14:03:47 +0200 Subject: [PATCH 21/29] minor changes --- tests/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test.py b/tests/test.py index 14b43eb..07c7320 100644 --- a/tests/test.py +++ b/tests/test.py @@ -190,7 +190,7 @@ def test_create_chessdraw(): # board = chesslib.ChessBoard_StartFormation(True) gen_draw = chesslib.ChessDraw(board, chesslib.ChessPosition('E2'), chesslib.ChessPosition('E4'), is_compact_draw=True, is_simple=True) assert_equal(gen_draw, exp_compact_draw) - print(gen_draw, exp_compact_draw) + # print(gen_draw, exp_compact_draw) assert_equal(sys.getrefcount(board), 2) # TODO: add a peasant prom. test case @@ -665,7 +665,7 @@ def test_board_from_fen(): # TODO: make this test work - print("testing visualize chess draw") + print("testing FEN to chess board") # FEN string representing the initial game state fen_str = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' From ad7593542c8c1bd3312f460d7b4da205c4219696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 19:48:08 +0200 Subject: [PATCH 22/29] fixed parser for 1st FEN section (chess pieces) --- chesslib/src/chessxformat.c | 53 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 632f7f1..3ebbded 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -35,14 +35,14 @@ int parse_uint(const char* value_str, int* out_value, char term) int value = 0; size_t i = 0; /* make sure there are no heading zeros for non-zero values */ - if (value_str[i] == '0' && value_str[i+1] != term) { return -1; } + if (value_str[i] == '0' && value_str[i+1] != term) { return 0; } /* parse the uint value from decimal notation */ while (isdigit(value_str[i]) && value_str[i] != term && value_str[i] != '\0') { value = value * 10 + (value_str[i] - '0'); i++; } *out_value = value; - return value_str[i] == term ? i : -1; + return value_str[i] == term ? i : 0; } /* Look up the next appearance of the given character in the given zero-terminated string. @@ -51,7 +51,7 @@ int str_index_of(char* search_str, char find) { size_t i = 0; char temp; while ((temp = search_str[i++]) != find && temp != '\0') { } - return temp == find ? i - 1 : -1; + return temp == find ? i - 1 : 0; } int parse_first_fen_section(const char fen_str[], Bitboard* board) @@ -72,7 +72,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) case '\0': if (!is_terminal) { return 0; } break; /* ensure that the row bounds are not violated */ - case '/': pos -= 16; if (pos != ++sep_count * 8) { return 0; } break; + case '/': if (pos != (8 - (sep_count++)) * 8) { return 0; } pos -= 16; break; /* handle empty fields declaration */ case '1': case '2': case '3': case '4': @@ -84,7 +84,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) case 'k': case 'q': case 'r': case 'b': case 'n': case 'p': color = (ChessColor)(isupper(temp) ? White : Black); type = piece_type_from_char(temp); - was_moved = START_POSITIONS & (1uLL << pos) ? 0 : 1; + was_moved = (START_POSITIONS & (1uLL << pos)) ? 0 : 1; simple_board[pos++] = create_piece(type, color, was_moved); break; @@ -93,7 +93,7 @@ int parse_first_fen_section(const char fen_str[], Bitboard* board) } /* check if the next state is supposed to be terminal */ - is_terminal = pos == 64 && sep_count == 7; + is_terminal = (pos == 8 && sep_count == 7); /* loop until the end of the FEN string's first section */ } while (temp != '\0'); @@ -117,7 +117,7 @@ int parse_third_fen_section(const char fen_str[], uint8_t* rochades) size_t i = 0; uint8_t poss_rochades = 0xF; /* handle case with no castlings */ - if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* do nothing */ } + if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* use default value */ } /* handle case with rochades */ else @@ -128,7 +128,7 @@ int parse_third_fen_section(const char fen_str[], uint8_t* rochades) if (fen_str[i] == 'k') { poss_rochades ^= 0x8; i++; } if (fen_str[i] == 'q') { poss_rochades ^= 0x4; i++; } - /* ensure that any rochade is possible and the terminal symbol is hit */ + /* ensure any rochade is possible and the terminal symbol is hit */ if (!poss_rochades || fen_str[i] != '\0') { return 0; } } @@ -163,44 +163,45 @@ int parse_fourth_fen_section(const char fen_str[], uint8_t* en_passants) int chess_session_from_fen(const char fen_str[], ChessGameSession* session) { - size_t end = 0; size_t len, i = 0; int game_round = 0; - ChessColor side; uint8_t rochades = 0, en_passants = 0, hdslpd = 0; - char cache[54]; char* temp_str = cache; Bitboard board[13] = { 0 }; + size_t end = 0; size_t len, i = 0; int game_round; + ChessColor side; uint8_t rochades, en_passants, hdslpd; + char cache[100]; char* temp_str = cache; Bitboard board[13] = { 0 }; + /* TODO: figure out the exact cap for the cache memory, 100 should be enough though */ /* create a carbon copy of the fen string (that can be safely modified) */ strcpy(temp_str, fen_str); /* parse the first FEN section (positions of pieces on the board) */ - if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } + if (!(end = str_index_of(temp_str, ' '))) { return 0; } temp_str[end] = '\0'; if (!parse_first_fen_section(temp_str, board)) { return 0; } temp_str += end + 1; /* parse the second FEN section (drawing side) */ - if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } + if (!(end = str_index_of(temp_str, ' '))) { return 0; } temp_str[end] = '\0'; if (!parse_second_fen_section(temp_str, &side)) { return 0; } temp_str += end + 1; /* parse the third FEN section (possible castlings) */ - if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } - temp_str[end] = '\0'; - if (!parse_third_fen_section(temp_str, &rochades)) { return 0; } - temp_str += end + 1; + // if (!(end = str_index_of(temp_str, ' '))) { return 0; } + // temp_str[end] = '\0'; + // if (!parse_third_fen_section(temp_str, &rochades)) { return 0; } + // temp_str += end + 1; /* parse the fourth FEN section (en-passant) */ - if ((end = str_index_of(temp_str, ' ')) == -1) { return 0; } - temp_str[end] = '\0'; - if (!parse_third_fen_section(temp_str, &en_passants)) { return 0; } - temp_str += end + 1; + // if (!(end = str_index_of(temp_str, ' '))) { return 0; } + // temp_str[end] = '\0'; + // if (!parse_fourth_fen_section(temp_str, &en_passants)) { return 0; } + // temp_str += end + 1; /* parse the fifth FEN section (halfdraws since last pawn draw) */ - len = parse_uint(fen_str + i, (int*)&hdslpd, ' '); - if (len > 0) { i += len + 1; } else { return 0; } + // len = parse_uint(fen_str + i, (int*)&hdslpd, ' '); + // if (len > 0) { i += len + 1; } else { return 0; } /* parse the sixth FEN section (game round) */ - len = parse_uint(fen_str + i, &game_round, '\0'); - if (len <= 0) { return 0; } + // len = parse_uint(fen_str + i, &game_round, '\0'); + // if (len <= 0) { return 0; } /* assign the parsed FEN string content to the game session object */ copy_board(board, session->board); @@ -208,6 +209,8 @@ int chess_session_from_fen(const char fen_str[], ChessGameSession* session) en_passants, rochades, hdslpd, game_round); apply_game_context_to_board(session->board, session->context); + // if (session->context != DEFAULT_GAME_CONTEXT) { return NULL; } + return 1; } From 7c5a12a2331acd14e29c561976321e051464995d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 20:50:18 +0200 Subject: [PATCH 23/29] fixed bug, bitwise OR instead of logical OR --- chesslib/src/chessgamesession.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chesslib/src/chessgamesession.c b/chesslib/src/chessgamesession.c index 0b4bba7..639c016 100644 --- a/chesslib/src/chessgamesession.c +++ b/chesslib/src/chessgamesession.c @@ -37,10 +37,10 @@ ChessGameContext create_context(ChessColor side, uint8_t en_passants, { /* combine the attributes to a single 32-bit integer */ return ((ChessGameContext)side & 0x1uL) - || (((ChessGameContext)en_passants & 0xFFuL) << 1) - || (((ChessGameContext)rochades & 0xFuL) << 9) - || (((ChessGameContext)halfdraws_since_last_pawn_draw & 0x3FuL) << 13) - || ((ChessGameContext)game_round << 19); + | (((ChessGameContext)en_passants & 0xFFuL) << 1) + | (((ChessGameContext)rochades & 0xFuL) << 9) + | (((ChessGameContext)halfdraws_since_last_pawn_draw & 0x3FuL) << 13) + | ((ChessGameContext)game_round << 19); } Bitboard get_en_passant_mask(ChessGameContext context) From 6b3177b6a48a9a13c9ae33a6cb00fc15ac967741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 20:51:28 +0200 Subject: [PATCH 24/29] fixed several bugs, can parse start formation now --- chesslib/src/chessxformat.c | 52 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 3ebbded..09e8950 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -29,24 +29,24 @@ #define DECIMAL_LEN(num) ((int)((ceil(log10((num))) + 1) * sizeof(char))) /* Parse a positive integer value from the given decimal numeric string - and return the length of the characters parsed (return -1 on format error). */ + and return the length of the characters parsed (return 0 on format error). */ int parse_uint(const char* value_str, int* out_value, char term) { int value = 0; size_t i = 0; /* make sure there are no heading zeros for non-zero values */ - if (value_str[i] == '0' && value_str[i+1] != term) { return 0; } + if (value_str[i] == '0' && value_str[++i] != term) { return 0; } /* parse the uint value from decimal notation */ while (isdigit(value_str[i]) && value_str[i] != term && value_str[i] != '\0') - { value = value * 10 + (value_str[i] - '0'); i++; } + { value = value * 10 + (value_str[i++] - '0'); } *out_value = value; - return value_str[i] == term ? i : 0; + return value_str[i] == term && i > 0 ? i : 0; } /* Look up the next appearance of the given character in the given zero-terminated string. - Return -1 if the string does not contain the character searched. */ + Return 0 if the string does not contain the character searched. */ int str_index_of(char* search_str, char find) { size_t i = 0; char temp; @@ -123,10 +123,10 @@ int parse_third_fen_section(const char fen_str[], uint8_t* rochades) else { /* enable castlings (ensure the correct order) */ - if (fen_str[i] == 'K') { poss_rochades ^= 0x2; i++; } - if (fen_str[i] == 'Q') { poss_rochades ^= 0x1; i++; } - if (fen_str[i] == 'k') { poss_rochades ^= 0x8; i++; } - if (fen_str[i] == 'q') { poss_rochades ^= 0x4; i++; } + if (fen_str[i] == 'K') { poss_rochades |= 0x2; i++; } + if (fen_str[i] == 'Q') { poss_rochades |= 0x1; i++; } + if (fen_str[i] == 'k') { poss_rochades |= 0x8; i++; } + if (fen_str[i] == 'q') { poss_rochades |= 0x4; i++; } /* ensure any rochade is possible and the terminal symbol is hit */ if (!poss_rochades || fen_str[i] != '\0') { return 0; } @@ -140,10 +140,10 @@ int parse_third_fen_section(const char fen_str[], uint8_t* rochades) int parse_fourth_fen_section(const char fen_str[], uint8_t* en_passants) { - size_t i = 0; uint8_t poss_en_passants = 0xF; ChessPosition pos = 0; + size_t i = 0; uint8_t poss_en_passants = 0x00; ChessPosition pos = 0; /* handle case with no en-passant */ - if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* do nothing */ } + if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* use default value */ } /* handle case with rochades */ else @@ -163,10 +163,10 @@ int parse_fourth_fen_section(const char fen_str[], uint8_t* en_passants) int chess_session_from_fen(const char fen_str[], ChessGameSession* session) { - size_t end = 0; size_t len, i = 0; int game_round; + size_t end = 0; size_t len; int game_round; ChessColor side; uint8_t rochades, en_passants, hdslpd; char cache[100]; char* temp_str = cache; Bitboard board[13] = { 0 }; - /* TODO: figure out the exact cap for the cache memory, 100 should be enough though */ + /* TODO: figure out the exact cap for the cache memory */ /* create a carbon copy of the fen string (that can be safely modified) */ strcpy(temp_str, fen_str); @@ -184,24 +184,24 @@ int chess_session_from_fen(const char fen_str[], ChessGameSession* session) temp_str += end + 1; /* parse the third FEN section (possible castlings) */ - // if (!(end = str_index_of(temp_str, ' '))) { return 0; } - // temp_str[end] = '\0'; - // if (!parse_third_fen_section(temp_str, &rochades)) { return 0; } - // temp_str += end + 1; + if (!(end = str_index_of(temp_str, ' '))) { return 0; } + temp_str[end] = '\0'; + if (!parse_third_fen_section(temp_str, &rochades)) { return 0; } + temp_str += end + 1; /* parse the fourth FEN section (en-passant) */ - // if (!(end = str_index_of(temp_str, ' '))) { return 0; } - // temp_str[end] = '\0'; - // if (!parse_fourth_fen_section(temp_str, &en_passants)) { return 0; } - // temp_str += end + 1; + if (!(end = str_index_of(temp_str, ' '))) { return 0; } + temp_str[end] = '\0'; + if (!parse_fourth_fen_section(temp_str, &en_passants)) { return 0; } + temp_str += end + 1; /* parse the fifth FEN section (halfdraws since last pawn draw) */ - // len = parse_uint(fen_str + i, (int*)&hdslpd, ' '); - // if (len > 0) { i += len + 1; } else { return 0; } + len = parse_uint(temp_str, (int*)&hdslpd, ' '); + if (len > 0) { temp_str += len + 1; } else { return 0; } /* parse the sixth FEN section (game round) */ - // len = parse_uint(fen_str + i, &game_round, '\0'); - // if (len <= 0) { return 0; } + len = parse_uint(temp_str, &game_round, '\0'); + if (len <= 0) { return 0; } /* assign the parsed FEN string content to the game session object */ copy_board(board, session->board); @@ -209,8 +209,6 @@ int chess_session_from_fen(const char fen_str[], ChessGameSession* session) en_passants, rochades, hdslpd, game_round); apply_game_context_to_board(session->board, session->context); - // if (session->context != DEFAULT_GAME_CONTEXT) { return NULL; } - return 1; } From c1f9f21ef991021d2325467d46b5d01d088224f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 20:52:11 +0200 Subject: [PATCH 25/29] added ideas how to extend FEN parser unit tests --- tests/test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test.py b/tests/test.py index 07c7320..5acf9d5 100644 --- a/tests/test.py +++ b/tests/test.py @@ -678,6 +678,10 @@ def test_board_from_fen(): # make sure that the expected content is retrieved assert_true(np.array_equal(board, board_exp)) + # TODO: add test case with rochades deactivated + # TODO: add test case with en-passant activated + # TODO: add test case with multiple spaces in one row + print("test passed!") From 73815400493340133dc44e7c136dedddc97d36d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 22:47:44 +0200 Subject: [PATCH 26/29] fixed bug in rochade game context parser --- chesslib/src/chessxformat.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chesslib/src/chessxformat.c b/chesslib/src/chessxformat.c index 09e8950..6d5bbc5 100644 --- a/chesslib/src/chessxformat.c +++ b/chesslib/src/chessxformat.c @@ -114,7 +114,7 @@ int parse_second_fen_section(const char fen_str[], ChessColor* side) int parse_third_fen_section(const char fen_str[], uint8_t* rochades) { - size_t i = 0; uint8_t poss_rochades = 0xF; + size_t i = 0; uint8_t poss_rochades = 0x0; /* handle case with no castlings */ if (fen_str[i] == '-' && fen_str[i+1] == '\0') { /* use default value */ } @@ -123,10 +123,10 @@ int parse_third_fen_section(const char fen_str[], uint8_t* rochades) else { /* enable castlings (ensure the correct order) */ - if (fen_str[i] == 'K') { poss_rochades |= 0x2; i++; } - if (fen_str[i] == 'Q') { poss_rochades |= 0x1; i++; } - if (fen_str[i] == 'k') { poss_rochades |= 0x8; i++; } - if (fen_str[i] == 'q') { poss_rochades |= 0x4; i++; } + if (fen_str[i] == 'K') { poss_rochades ^= 0x2; i++; } + if (fen_str[i] == 'Q') { poss_rochades ^= 0x1; i++; } + if (fen_str[i] == 'k') { poss_rochades ^= 0x8; i++; } + if (fen_str[i] == 'q') { poss_rochades ^= 0x4; i++; } /* ensure any rochade is possible and the terminal symbol is hit */ if (!poss_rochades || fen_str[i] != '\0') { return 0; } From 26fdcf14b94d746920e1c89ba3dcfab72f056cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 22:47:59 +0200 Subject: [PATCH 27/29] beautified code --- chesslib/src/chessgamesession.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chesslib/src/chessgamesession.c b/chesslib/src/chessgamesession.c index 639c016..b144255 100644 --- a/chesslib/src/chessgamesession.c +++ b/chesslib/src/chessgamesession.c @@ -33,13 +33,13 @@ const ChessGameContext HDSLPD_MASK = 0x0007E000uL; const ChessGameContext GAME_ROUND_MASK = 0xFFF80000uL; ChessGameContext create_context(ChessColor side, uint8_t en_passants, - uint8_t rochades, uint8_t halfdraws_since_last_pawn_draw, int game_round) + uint8_t rochades, uint8_t hdslpd, int game_round) { /* combine the attributes to a single 32-bit integer */ return ((ChessGameContext)side & 0x1uL) | (((ChessGameContext)en_passants & 0xFFuL) << 1) | (((ChessGameContext)rochades & 0xFuL) << 9) - | (((ChessGameContext)halfdraws_since_last_pawn_draw & 0x3FuL) << 13) + | (((ChessGameContext)hdslpd & 0x3FuL) << 13) | ((ChessGameContext)game_round << 19); } From 636a3f577f194a7ab8a6e51d741be5cf9a555006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 22:48:35 +0200 Subject: [PATCH 28/29] implemented FEN to game session (as tuple) --- chesslib/src/chesslibmodule.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 4091558..ce0b904 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -41,7 +41,7 @@ static PyObject* chesslib_get_game_state(PyObject* self, PyObject* args, PyObjec static PyObject* chesslib_board_to_hash(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args, PyObject *keywds); -static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObject *keywds); +static PyObject* chesslib_gamesession_from_fen(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_board_to_fen(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_draw_from_pgn(PyObject* self, PyObject* args, PyObject *keywds); static PyObject* chesslib_draw_to_pgn(PyObject* self, PyObject* args, PyObject *keywds); @@ -299,7 +299,7 @@ static PyMethodDef chesslib_methods[] = { /* extensions for data compression */ {"Board_ToHash", (PyCFunction)chesslib_board_to_hash, METH_VARARGS | METH_KEYWORDS, Board_ToHash_Docstring}, {"Board_FromHash", (PyCFunction)chesslib_board_from_hash, METH_VARARGS | METH_KEYWORDS, Board_FromHash_Docstring}, - {"Board_FromFen", (PyCFunction)chesslib_board_from_fen, METH_VARARGS | METH_KEYWORDS, Board_FromFen_Docstring}, + {"Board_FromFen", (PyCFunction)chesslib_gamesession_from_fen, METH_VARARGS | METH_KEYWORDS, Board_FromFen_Docstring}, /* extensions for data visualization of complex type encodings */ {"VisualizeBoard", (PyCFunction)chesslib_visualize_board, METH_VARARGS | METH_KEYWORDS, VisualizeBoard_Docstring}, @@ -727,11 +727,12 @@ static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args, PyObje E X C H A N G E F O R M A T S ================================================= */ -static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObject *keywds) +static PyObject* chesslib_gamesession_from_fen(PyObject* self, PyObject* args, PyObject *keywds) { /* start formation in FEN: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' */ - char* fen_str; int is_simple_board = 0; PyObject* chessboard; + PyObject* chessboard, * tuple; + char* fen_str; int is_simple_board = 0; ChessGameSession session = INIT_GAME_SESSION; ChessPiece simple_board[64]; static char* kwlist[] = {"fen_str", "is_simple", NULL}; @@ -750,9 +751,12 @@ static PyObject* chesslib_board_from_fen(PyObject* self, PyObject* args, PyObjec ? serialize_as_pieces(simple_board) : serialize_as_bitboards(session.board); - /* TODO: think of returning the game session instead of just a chess board */ + /* zip the chess board and game context as a PyTuple */ + tuple = PyTuple_New(2); + PyTuple_SetItem(tuple, 0, chessboard); + PyTuple_SetItem(tuple, 1, PyLong_FromUnsignedLong(session.context)); - return chessboard; + return tuple; } static PyObject* chesslib_board_to_fen(PyObject* self, PyObject* args, PyObject *keywds) From 1aad30770aaa9b25380931f4c5f973bbdab50de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 12 Aug 2021 22:48:58 +0200 Subject: [PATCH 29/29] added more test cases, FEN parser seems solid now --- tests/test.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/test.py b/tests/test.py index 5acf9d5..6d0d887 100644 --- a/tests/test.py +++ b/tests/test.py @@ -671,16 +671,36 @@ def test_board_from_fen(): fen_str = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' board_exp = chesslib.ChessBoard_StartFormation() - # now, parse the FEN string into a chess board - board = chesslib.Board_FromFen(fen_str) - print(board) + # parse the FEN string and make sure it's correct + board, context = chesslib.Board_FromFen(fen_str) + assert_true(np.array_equal(board, board_exp)) + assert_equal(531968, context) - # make sure that the expected content is retrieved + # FEN string representing the game state after first draw 'white pawn F2-F4' + fen_str = 'rnbqkbnr/pppppppp/8/8/5P2/8/PPPPP1PP/RNBQKBNR b KQkq f3 10 12' + board_exp = chesslib.ApplyDraw(board, chesslib.ChessDraw( + board, chesslib.ChessPosition('F2'), chesslib.ChessPosition('F4'))) + + # parse the FEN string and make sure it's correct + board, context = chesslib.Board_FromFen(fen_str) assert_true(np.array_equal(board, board_exp)) + assert_equal(6381121, context) + + # FEN string representing the game state after first draw 'white pawn F2-F4' + fen_str = '5k2/8/8/8/8/8/8/3K4 b - - 41 71' + board_exp = np.array([ + 1 << chesslib.ChessPosition('D1'), + 0, 0, 0, 0, 0, + 1 << chesslib.ChessPosition('F8'), + 0, 0, 0, 0, 0, + 0xFFFF00000000FFFF # info: was_moved mask cannot be 100% accurately restored + # -> draw-gen needs to be the same + ], dtype=np.uint64) - # TODO: add test case with rochades deactivated - # TODO: add test case with en-passant activated - # TODO: add test case with multiple spaces in one row + # parse the FEN string and make sure it's correct + board, context = chesslib.Board_FromFen(fen_str) + assert_true(np.array_equal(board[:12], board_exp[:12])) + assert_equal(37560321, context) print("test passed!")