From d36a1baa193c5cf1e447b952fa7632c07c35a884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Fri, 12 Feb 2021 16:58:18 +0100 Subject: [PATCH 1/3] added compact chess draw (tested and works fine) --- chesslib/include/chesstypes.h | 4 ++++ chesslib/src/chessdraw.c | 5 +++++ chesslib/src/chesslibmodule.c | 36 ++++++++++++++++++++++++++++------- tests/test.py | 14 ++++++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/chesslib/include/chesstypes.h b/chesslib/include/chesstypes.h index 0d44761..7de3bbc 100644 --- a/chesslib/include/chesstypes.h +++ b/chesslib/include/chesstypes.h @@ -82,6 +82,10 @@ typedef uint16_t ChessPieceAtPos; | xxxxxxx | x | x | xx | xxx | xxx | xxx | xxxxxx | xxxxxx | */ typedef uint32_t ChessDraw; +/* | prom. piece type | old position | new position | + | xxx | xxxxxx | xxxxxx | */ +typedef uint16_t CompactChessDraw; + /* A chess bitboard with each bit representing a field onto a chess board. Addressing is normalized, starting with the lowest bit as A1 and the highest bit as H8 (indexes A1=0, B1=1, ..., A2=8, ..., H8=63). */ typedef uint64_t Bitboard; diff --git a/chesslib/src/chessdraw.c b/chesslib/src/chessdraw.c index 234854f..8dc8ead 100644 --- a/chesslib/src/chessdraw.c +++ b/chesslib/src/chessdraw.c @@ -90,6 +90,11 @@ ChessDraw create_draw(ChessBoard board, ChessPosition oldPos, ChessPosition newP return draw; } +CompactChessDraw to_compact_draw(ChessDraw draw) +{ + return (CompactChessDraw)(draw & 0x7FFF); +} + int get_is_first_move(ChessDraw draw) { return (int)((draw >> 24) & 0x1); diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 364e088..32b2b28 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -275,30 +275,52 @@ static PyObject* chesslib_create_chessdraw(PyObject* self, PyObject* args) /************************************************************************** Retrive a list of all possible chess draws for the current chess position given following arguments: - 1) An instance of ChessBoard representing the position before the draws + 1) An instance of ChessBoard representing the chess piece positions 2) The drawing side as ChessColor enum (White=0, Black=1) 3) The last draw or DRAW_NULL on first draw (only relevant for en-passant rule, default: DRAW_NULL) 4) A boolean indicating whether draw-into-check should be analyzed or not (default: FALSE) + 5) A boolean indicating whether the draws should be returned as compact format (default: TRUE) **************************************************************************/ static PyObject* chesslib_get_all_draws(PyObject* self, PyObject* args) { - PyObject *bitboards_obj; + PyObject *bitboards_obj, *serialized_draws; size_t dims[1]; ChessDraw *out_draws, last_draw = DRAW_NULL; + CompactChessDraw *comp_out_draws; ChessBoard board; ChessColor drawing_side; - int analyze_draw_into_check; + int analyze_draw_into_check = 0; + int is_compact_format = 0; + int i = 0; /* parse input args */ - if (!PyArg_ParseTuple(args, "Okii", &bitboards_obj, &drawing_side, &last_draw, &analyze_draw_into_check)) { return NULL; } + int is_valid = PyArg_ParseTuple(args, "Ok|iii", &bitboards_obj, &drawing_side, + &last_draw, &analyze_draw_into_check, &is_compact_format); + + /* make sure that any of the given arguments fit any of the patterns above, otherwise abort */ + if (!is_valid) { return NULL; } + + /* convert the numpy array into a chess bitboard instance */ board = deserialize_chessboard(bitboards_obj); - /* compute possible draws */ + /* compute all possible draws for the given chess position */ get_all_draws(&out_draws, dims, board, drawing_side, last_draw, analyze_draw_into_check); - /* serialize draws as numpy list */ - return PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT32, out_draws); + /* convert draws to compact format if required */ + if (is_compact_format) + { + comp_out_draws = (CompactChessDraw *)malloc((*dims) * sizeof(CompactChessDraw)); + for (i = 0; i < *dims; i++) { comp_out_draws[i] = to_compact_draw(out_draws[i]); } + free(out_draws); + } + + /* serialize all draws as a numpy array */ + serialized_draws = is_compact_format ? + PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT16, comp_out_draws) + : PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT32, out_draws); + + return serialized_draws; } /* ================================================= diff --git a/tests/test.py b/tests/test.py index 77cf804..7b552f4 100644 --- a/tests/test.py +++ b/tests/test.py @@ -248,6 +248,20 @@ def test_drawgen(): # make sure that the generated draws equal the expected draws assert_true(set(draws) == set(expected_draws)) + print("test passed!") + print("testing draw-gen (compact draws)") + + # get all draws for starting position (white side) + start_formation = chesslib.ChessBoard_StartFormation() + draws = chesslib.GenerateDraws(start_formation, + chesslib.ChessColor_White, chesslib.ChessDraw_Null, True, True) + + # define the expected draws (only lowest 15 bits of the long draw format) + expected_comp_draws = expected_draws % 32768 + + # make sure that the generated draws equal the expected draws + assert_true(set(draws) == set(expected_comp_draws)) + # TODO: add more unit tests that at least cover the correct parsing of all parameters print("test passed!") From e63caea5ce030adb5bd3cc8bdb3d4c67c43149a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Wed, 24 Feb 2021 21:15:19 +0100 Subject: [PATCH 2/3] added is_simple_board flags to all API functions with board overloads, added proper conversion functions between simple chessboards and bitboards, added implicit conversion for compact chess draws using the given chess board context --- chesslib/include/chessboard.h | 24 +-- chesslib/include/chessdraw.h | 4 +- chesslib/include/chesslibmodule.h | 2 - chesslib/include/chesstypes.h | 10 +- chesslib/src/chessboard.c | 105 ++++++++++++- chesslib/src/chessdraw.c | 15 +- chesslib/src/chesslibmodule.c | 236 ++++++++++++++++++------------ tests/test.py | 8 +- 8 files changed, 271 insertions(+), 133 deletions(-) diff --git a/chesslib/include/chessboard.h b/chesslib/include/chessboard.h index fd93afc..9fbcd3f 100644 --- a/chesslib/include/chessboard.h +++ b/chesslib/include/chessboard.h @@ -84,22 +84,11 @@ #define FIELD_G8 0x4000000000000000uLL #define FIELD_H8 0x8000000000000000uLL -/*#define START_FORMATION (Bitboard[]){ FIELD_E1, FIELD_D1, 0x0000000000000081uLL, 0x0000000000000024uLL, 0x0000000000000042uLL, 0x000000000000FF00uLL, FIELD_E8, FIELD_D8, 0x8100000000000000uLL, 0x2400000000000000uLL, 0x4200000000000000uLL, 0x00FF000000000000uLL, START_POSITIONS } -#define BOARD_NULL (Bitboard[]){ 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL }*/ - #define SIDE_OFFSET(color) (((uint8_t)(color)) * 6) #define PIECE_OFFSET(piece) (((uint8_t)(piece)) - 1) #define WHITE_MASK(color) (((Bitboard)(((int64_t)(color)) - 1))) #define BLACK_MASK(color) (~WHITE_MASK((color))) -/* ==================================================== - D E F I N E C O N S T A N T S - ==================================================== */ - - /* global array representing an empty ChessBoard */ -/*ChessBoard BOARD_NULL = (Bitboard[]){ 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL, 0x0uLL };*/ -/*ChessBoard START_FORMATION = (Bitboard[]){ FIELD_E1, FIELD_D1, 0x0000000000000081uLL, 0x0000000000000024uLL, 0x0000000000000042uLL, 0x000000000000FF00uLL, FIELD_E8, FIELD_D8, 0x8100000000000000uLL, 0x2400000000000000uLL, 0x4200000000000000uLL, 0x00FF000000000000uLL, START_POSITIONS };*/ - /* ==================================================== D E F I N E F U N C T I O N S ==================================================== */ @@ -107,11 +96,14 @@ ChessBoard create_board(const Bitboard bitboards[]); ChessBoard create_board_from_piecesatpos(const ChessPieceAtPos pieces_at_pos[], size_t pieces_count); -Bitboard is_captured_at(ChessBoard board, ChessPosition pos); -ChessPiece get_piece_at(ChessBoard board, ChessPosition pos); -int was_piece_moved(ChessBoard board, ChessPosition pos); +Bitboard is_captured_at(const Bitboard board[], ChessPosition pos); +ChessPiece get_piece_at(const Bitboard board[], ChessPosition pos); +int was_piece_moved(const Bitboard board[], ChessPosition pos); + +ChessBoard apply_draw(const Bitboard board[], ChessDraw draw); +void apply_draw_to_bitboards(ChessBoard bitboards, ChessDraw draw); -ChessBoard apply_draw(ChessBoard board, ChessDraw draw); -void apply_draw_to_bitboards(Bitboard* bitboards, ChessDraw draw); +ChessBoard from_simple_board(const ChessPiece simple_board[]); +SimpleChessBoard to_simple_board(const Bitboard board[]); #endif diff --git a/chesslib/include/chessdraw.h b/chesslib/include/chessdraw.h index 5685ad8..59e7019 100644 --- a/chesslib/include/chessdraw.h +++ b/chesslib/include/chessdraw.h @@ -47,7 +47,9 @@ D E F I N E F U N C T I O N S ==================================================== */ -ChessDraw create_draw(ChessBoard board, ChessPosition oldPos, ChessPosition newPos, ChessPieceType peasantPromotionType); +ChessDraw create_draw(const Bitboard board[], ChessPosition oldPos, ChessPosition newPos, ChessPieceType peasantPromotionType); +ChessDraw from_compact_draw(const Bitboard board[], CompactChessDraw comp_draw); +CompactChessDraw to_compact_draw(ChessDraw draw); int get_is_first_move(ChessDraw draw); ChessDrawType get_draw_type(ChessDraw draw); diff --git a/chesslib/include/chesslibmodule.h b/chesslib/include/chesslibmodule.h index ad06634..f0dc5f7 100644 --- a/chesslib/include/chesslibmodule.h +++ b/chesslib/include/chesslibmodule.h @@ -64,6 +64,4 @@ static PyObject* chesslib_visualize_draw(PyObject* self, PyObject* args); PyMODINIT_FUNC PyInit_chesslib(void); -/* TODO: add missing makros, constants, method stubs, etc. */ - #endif diff --git a/chesslib/include/chesstypes.h b/chesslib/include/chesstypes.h index 7de3bbc..6368355 100644 --- a/chesslib/include/chesstypes.h +++ b/chesslib/include/chesstypes.h @@ -95,10 +95,12 @@ typedef uint64_t Bitboard; The boards holding information on chess pieces are ordered by the occurance of the piece in the ChessPieceType enum (King=0, Queens=1, Rooks=2, Bishops=3, Knights=4, Peasants=5). All boards with indices 0-5 belong to the white side, the next 6 boards with indices 6-11 belong to the black side. */ -/*typedef struct _CHESS_BITBOARD { - Bitboard bitboards[13]; -} ChessBoard;*/ - typedef Bitboard * ChessBoard; +/* + * The chess board represented as 64 bytes, each modelling the chess piece standing at the specific position of the board. + * In case there is no piece at the given position, it is assigned to CHESS_PIECE_NULL. + */ +typedef ChessPiece * SimpleChessBoard; + #endif diff --git a/chesslib/src/chessboard.c b/chesslib/src/chessboard.c index e53829c..e54e8ac 100644 --- a/chesslib/src/chessboard.c +++ b/chesslib/src/chessboard.c @@ -26,7 +26,6 @@ ChessBoard create_board(const Bitboard bitboards[]) { - /* TODO: check if memory allocation works */ size_t i; ChessBoard board = (ChessBoard)malloc(13 * sizeof(Bitboard)); if (!board) { return NULL; } @@ -68,7 +67,7 @@ ChessBoard create_board_from_piecesatpos(const ChessPieceAtPos pieces_at_pos[], return board; } -Bitboard is_captured_at(ChessBoard board, ChessPosition pos) +Bitboard is_captured_at(const Bitboard board[], ChessPosition pos) { Bitboard mask, all_pieces; @@ -81,7 +80,7 @@ Bitboard is_captured_at(ChessBoard board, ChessPosition pos) return (all_pieces & mask); } -ChessPiece get_piece_at(ChessBoard board, ChessPosition pos) +ChessPiece get_piece_at(const Bitboard board[], ChessPosition pos) { int i; ChessPiece piece = CHESS_PIECE_NULL; @@ -111,12 +110,12 @@ ChessPiece get_piece_at(ChessBoard board, ChessPosition pos) return piece; } -int was_piece_moved(ChessBoard board, ChessPosition pos) +int was_piece_moved(const Bitboard board[], ChessPosition pos) { return ((~START_POSITIONS | board[12]) & (0x1uLL << pos)) > 0; } -ChessBoard apply_draw(ChessBoard board, ChessDraw draw) +ChessBoard apply_draw(const Bitboard board[], ChessDraw draw) { uint8_t i; ChessBoard new_board; @@ -128,8 +127,6 @@ ChessBoard apply_draw(ChessBoard board, ChessDraw draw) apply_draw_to_bitboards(new_board, draw); return new_board; - - /* TODO: check if the memory allocation actually works */ } void apply_draw_to_bitboards(ChessBoard bitboards, ChessDraw draw) @@ -198,3 +195,97 @@ void apply_draw_to_bitboards(ChessBoard bitboards, ChessDraw draw) bitboards[promotion_board_index] ^= new_pos; } } + +ChessBoard from_simple_board(const ChessPiece simple_board[]) +{ + uint8_t i, pos, white_pos, black_pos; + ChessPieceType piece_type; + ChessColor color; + Bitboard bitboard; + int set_bit; + + ChessBoard bitboards = (ChessBoard)malloc(13 * sizeof(Bitboard)); + + /* assume pieces as already moved */ + Bitboard was_moved = 0xFFFFFFFFFFFFFFFFuL; + + /* loop through all bitboards */ + for (i = 0; i < 12; i++) + { + /* determine the chess piece type and color of the iteration */ + piece_type = (ChessPieceType)((i % 6) + 1); + color = (ChessColor)(i / 6); + + /* init empty bitboard */ + bitboard = 0; + + /* loop through all positions */ + for (pos = 0; pos < 64; pos++) + { + /* set piece bit if the position is captured */ + set_bit = simple_board[pos] != CHESS_PIECE_NULL + && get_piece_type(simple_board[pos]) == piece_type + && get_piece_color(simple_board[pos]) == color; + bitboard |= set_bit ? 0x1uL << pos : 0x0uL; + } + + /* apply converted bitboard */ + bitboards[i] = bitboard; + } + + /* init was moved bitboard */ + for (i = 0; i < 16; i++) + { + white_pos = i; + black_pos = (uint8_t)(i + 48); + + /* explicitly set was moved to 0 only for unmoved pieces */ + was_moved ^= simple_board[white_pos] != CHESS_PIECE_NULL && + !get_was_piece_moved(simple_board[white_pos]) ? 0x1uL << white_pos : 0x0uL; + was_moved ^= simple_board[black_pos] != CHESS_PIECE_NULL && + !get_was_piece_moved(simple_board[black_pos]) ? 0x1uL << black_pos : 0x0uL; + } + + /* apply converted bitboard */ + bitboards[12] = was_moved; + + return bitboards; +} + +SimpleChessBoard to_simple_board(const Bitboard board[]) +{ + uint8_t i, pos; + ChessPieceType piece_type; + ChessColor color; + Bitboard bitboard; + SimpleChessBoard simple_board; + + /* init pieces array with empty fields */ + simple_board = (SimpleChessBoard)malloc(sizeof(ChessPiece) * 64); + + // loop through all bitboards + for (i = 0; i < 12; i++) + { + // determine the chess piece type and color of the iteration + piece_type = (ChessPieceType)((i % 6) + 1); + color = (ChessColor)(i / 6); + + // cache bitboard for shifting bitwise + bitboard = board[i]; + + // loop through all positions + for (pos = 0; pos < 64; pos++) + { + // write piece to array if there is one + simple_board[pos] = (bitboard & 0x1) > 0 + ? create_piece(piece_type, color, was_piece_moved(board, pos)) + : CHESS_PIECE_NULL; + + // shift bitboard + bitboard >>= 1; + } + } + + // return a new chess board with the converted chess pieces + return simple_board; +} diff --git a/chesslib/src/chessdraw.c b/chesslib/src/chessdraw.c index 8dc8ead..578d193 100644 --- a/chesslib/src/chessdraw.c +++ b/chesslib/src/chessdraw.c @@ -29,7 +29,7 @@ ChessDraw create_draw_from_hash(uint32_t hash) return (ChessDraw)hash; } -ChessDrawType determine_draw_type(ChessBoard board, ChessPosition oldPos, ChessPosition newPos, ChessPieceType peasantPromotionType) +ChessDrawType determine_draw_type(const Bitboard board[], ChessPosition oldPos, ChessPosition newPos, ChessPieceType peasantPromotionType) { ChessDrawType type = Standard; ChessPiece piece = get_piece_at(board, oldPos); @@ -56,7 +56,7 @@ ChessDrawType determine_draw_type(ChessBoard board, ChessPosition oldPos, ChessP return type; } -ChessDraw create_draw(ChessBoard board, ChessPosition oldPos, ChessPosition newPos, ChessPieceType peasantPromotionType) +ChessDraw create_draw(const Bitboard board[], ChessPosition oldPos, ChessPosition newPos, ChessPieceType peasantPromotionType) { ChessPiece piece; int is_first_move; @@ -73,8 +73,8 @@ ChessDraw create_draw(ChessBoard board, ChessPosition oldPos, ChessPosition newP draw_type = determine_draw_type(board, oldPos, newPos, peasantPromotionType); drawing_side = get_piece_color(piece); drawing_piece_type = get_piece_type(piece); - taken_piece_type = - (draw_type == EnPassant) ? Peasant : (is_captured_at(board, newPos) ? get_piece_type(get_piece_at(board, newPos)) : Invalid); + taken_piece_type = (draw_type == EnPassant) ? Peasant + : (is_captured_at(board, newPos) ? get_piece_type(get_piece_at(board, newPos)) : Invalid); /* transform property values to a hash code */ draw = (ChessDraw)( @@ -90,6 +90,13 @@ ChessDraw create_draw(ChessBoard board, ChessPosition oldPos, ChessPosition newP return draw; } +ChessDraw from_compact_draw(const Bitboard board[], CompactChessDraw comp_draw) +{ + /* determine all meta-info for the compact draw using the given chess board as context */ + return create_draw(board, get_old_position(comp_draw), + get_new_position(comp_draw), get_peasant_promotion_piece_type(comp_draw)); +} + CompactChessDraw to_compact_draw(ChessDraw draw) { return (CompactChessDraw)(draw & 0x7FFF); diff --git a/chesslib/src/chesslibmodule.c b/chesslib/src/chesslibmodule.c index 32b2b28..44a26a2 100644 --- a/chesslib/src/chesslibmodule.c +++ b/chesslib/src/chesslibmodule.c @@ -28,8 +28,11 @@ H E L P E R F U N C T I O N S T U B S ================================================= */ -static PyObject* serialize_chessboard(const Bitboard board[]); -static ChessBoard deserialize_chessboard(PyObject* board); +static PyObject* serialize_as_bitboards(const Bitboard board[]); +static PyObject* serialize_as_pieces(const ChessPiece pieces[]); +static ChessBoard deserialize_as_bitboards(PyObject* bitboards_obj, int is_simple_board); +static SimpleChessBoard deserialize_as_pieces(PyObject* bitboards_obj, int is_simple_board); +static ChessDraw deserialize_chessdraw(const Bitboard board[], const ChessDraw draw); static void compress_pieces_array(const ChessPiece pieces[], uint8_t* out_bytes); static void uncompress_pieces_array(const uint8_t hash_bytes[], ChessPiece* out_pieces); uint8_t get_bits_at(const uint8_t data_bytes[], size_t arr_size, int bit_index, int length); @@ -55,7 +58,7 @@ static PyMethodDef chesslib_methods[] = { {"ChessPieceAtPos", chesslib_create_chesspieceatpos, METH_VARARGS, "Create a new chess piece including its' position."}, {"Board_ToHash", chesslib_board_to_hash, METH_VARARGS, "Compute the given chess board's hash as string."}, {"Board_FromHash", chesslib_board_from_hash, METH_VARARGS, "Compute the given chess board's hash as string."}, - {"ChessBoard_StartFormation", chesslib_create_chessboard_startformation, METH_NOARGS, "Create a new chess board in start formation."}, + {"ChessBoard_StartFormation", chesslib_create_chessboard_startformation, METH_VARARGS, "Create a new chess board in start formation."}, {"GameState", chesslib_get_game_state, METH_VARARGS, "Determine the game state for the given chess board and side."}, {"VisualizeBoard", chesslib_visualize_board, METH_VARARGS, "Transform the chess board instance into a printable string."}, {"VisualizeDraw", chesslib_visualize_draw, METH_VARARGS, "Transform the chess draw instance into a printable string."}, @@ -205,16 +208,19 @@ static PyObject* chesslib_create_chessboard(PyObject* self, PyObject* args) ChessPieceAtPos* pieces_at_pos; uint8_t count = 0; ChessBoard board; + int is_simple_board = 0; /* parse all args */ - if (!PyArg_ParseTuple(args, "O", &pieces_list)) { return NULL; } + if (!PyArg_ParseTuple(args, "O|i", &pieces_list, &is_simple_board)) { return NULL; } nd_pieces_at_pos = (PyArrayObject*)PyArray_FromObject(pieces_list, NPY_UINT16, 1, 32); count = (size_t)PyArray_Size((PyObject*)nd_pieces_at_pos); pieces_at_pos = (ChessPieceAtPos*)PyArray_DATA(nd_pieces_at_pos); /* create the chess board */ board = create_board_from_piecesatpos(pieces_at_pos, count); - return serialize_chessboard(board); + return is_simple_board + ? serialize_as_pieces(to_simple_board(board)) + : serialize_as_bitboards(board); } /************************************************************************** @@ -223,6 +229,8 @@ static PyObject* chesslib_create_chessboard(PyObject* self, PyObject* args) **************************************************************************/ static PyObject* chesslib_create_chessboard_startformation(PyObject* self, PyObject* args) { + int is_simple_board = 0; + /* create the chess board */ const Bitboard start_formation[] = { 0x0000000000000010uLL, /* white king */ @@ -240,7 +248,12 @@ static PyObject* chesslib_create_chessboard_startformation(PyObject* self, PyObj 0x0000FFFFFFFF0000uLL /* was_moved mask */ }; - return serialize_chessboard(start_formation); + /* parse all args */ + if (!PyArg_ParseTuple(args, "|i", &is_simple_board)) { return NULL; } + + return is_simple_board + ? serialize_as_pieces(to_simple_board(start_formation)) + : serialize_as_bitboards(start_formation); } /************************************************************************** @@ -249,23 +262,28 @@ static PyObject* chesslib_create_chessboard_startformation(PyObject* self, PyObj 2) The old position of the moving chess piece 3) The new position of the moving chess piece 4) The type that a peasant promotes to in case of a peasant promotion (optional parameter) + 5) A boolean indicating whether the draw should be put as compact format (default: False) The ChessDraw value consists of an uint32 object that represent bitwise concatenation of several parameters defining the draw (lowest 25 bits). For further details see the documentation of the ChessDraw type. **************************************************************************/ static PyObject* chesslib_create_chessdraw(PyObject* self, PyObject* args) { - PyObject* bitboards; + PyObject* chessboard; ChessBoard board = NULL; ChessPosition old_pos = 0, new_pos = 0; ChessPieceType prom_type = Invalid; + int is_compact_format = 0; + int is_simple_board = 0; + ChessDraw draw; - /* TODO: make sure that both overloads work correctly */ - if (!PyArg_ParseTuple(args, "Okkk", &bitboards, &old_pos, &new_pos, &prom_type)) { return NULL; } - board = deserialize_chessboard(bitboards); + if (!PyArg_ParseTuple(args, "Okk|kii", &chessboard, &old_pos, &new_pos, + &prom_type, &is_compact_format, &is_simple_board)) { return NULL; } + board = deserialize_as_bitboards(chessboard, is_simple_board); /* create the chess draw as unsigned 32-bit integer */ - return PyLong_FromUnsignedLong(create_draw(board, old_pos, new_pos, prom_type)); + draw = create_draw(board, old_pos, new_pos, prom_type); + return PyLong_FromUnsignedLong(is_compact_format ? to_compact_draw(draw) : draw); } /* ================================================= @@ -279,11 +297,11 @@ static PyObject* chesslib_create_chessdraw(PyObject* self, PyObject* args) 2) The drawing side as ChessColor enum (White=0, Black=1) 3) The last draw or DRAW_NULL on first draw (only relevant for en-passant rule, default: DRAW_NULL) 4) A boolean indicating whether draw-into-check should be analyzed or not (default: FALSE) - 5) A boolean indicating whether the draws should be returned as compact format (default: TRUE) + 5) A boolean indicating whether the draws should be returned as compact format (default: FALSE) **************************************************************************/ static PyObject* chesslib_get_all_draws(PyObject* self, PyObject* args) { - PyObject *bitboards_obj, *serialized_draws; + PyObject *chessboard, *serialized_draws; size_t dims[1]; ChessDraw *out_draws, last_draw = DRAW_NULL; @@ -292,17 +310,18 @@ static PyObject* chesslib_get_all_draws(PyObject* self, PyObject* args) ChessColor drawing_side; int analyze_draw_into_check = 0; int is_compact_format = 0; + int is_simple_board = 0; int i = 0; /* parse input args */ - int is_valid = PyArg_ParseTuple(args, "Ok|iii", &bitboards_obj, &drawing_side, - &last_draw, &analyze_draw_into_check, &is_compact_format); + int is_valid = PyArg_ParseTuple(args, "Ok|iiii", &chessboard, &drawing_side, + &last_draw, &analyze_draw_into_check, &is_compact_format, &is_simple_board); /* make sure that any of the given arguments fit any of the patterns above, otherwise abort */ if (!is_valid) { return NULL; } /* convert the numpy array into a chess bitboard instance */ - board = deserialize_chessboard(bitboards_obj); + board = deserialize_as_bitboards(chessboard, is_simple_board); /* compute all possible draws for the given chess position */ get_all_draws(&out_draws, dims, board, drawing_side, last_draw, analyze_draw_into_check); @@ -316,8 +335,8 @@ static PyObject* chesslib_get_all_draws(PyObject* self, PyObject* args) } /* serialize all draws as a numpy array */ - serialized_draws = is_compact_format ? - PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT16, comp_out_draws) + serialized_draws = is_compact_format + ? PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT16, comp_out_draws) : PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT32, out_draws); return serialized_draws; @@ -329,19 +348,21 @@ static PyObject* chesslib_get_all_draws(PyObject* self, PyObject* args) static PyObject* chesslib_apply_draw(PyObject* self, PyObject* args) { - PyObject *bitboards_obj; + PyObject *chessboard; ChessDraw draw_to_apply; ChessBoard old_board, new_board; + int is_simple_board = 0; /* parse input args */ - if (!PyArg_ParseTuple(args, "Oi", &bitboards_obj, &draw_to_apply)) { return NULL; } - old_board = deserialize_chessboard(bitboards_obj); + if (!PyArg_ParseTuple(args, "Oi|i", &chessboard, &draw_to_apply, &is_simple_board)) { return NULL; } + old_board = deserialize_as_bitboards(chessboard, is_simple_board); + draw_to_apply = deserialize_chessdraw(old_board, draw_to_apply); /* apply the chess draw to a new ChessBoard instance */ new_board = apply_draw(old_board, draw_to_apply); /* serialize the new Chessboard as numpy list */ - return serialize_chessboard(new_board); + return serialize_as_bitboards(new_board); } /* ================================================= @@ -353,50 +374,20 @@ static PyObject* chesslib_apply_draw(PyObject* self, PyObject* args) **************************************************************************/ static PyObject* chesslib_board_to_hash(PyObject* self, PyObject* args) { - /* TODO: export this function to chessboard.c */ - - ChessBoard board; - PyObject *bitboards; - uint8_t *bytes, i; + PyObject *chessboard; + uint8_t *bytes; size_t dims[1] = { 40 }; - - ChessColor color; ChessPieceType piece_type; int was_moved; - Bitboard temp_bitboard; ChessPosition temp_pos; - ChessPiece temp_piece, temp_pieces[64] = { 0 }; + int is_simple_board = 0; + SimpleChessBoard temp_pieces; /* parse bitboards as ChessBoard struct */ - if (!PyArg_ParseTuple(args, "O", &bitboards)) { return NULL; } - board = deserialize_chessboard(bitboards); + if (!PyArg_ParseTuple(args, "O|i", &chessboard, &is_simple_board)) { return NULL; } + temp_pieces = deserialize_as_pieces(chessboard, is_simple_board); /* allocate 40-bytes array */ bytes = (uint8_t*)calloc(40, sizeof(uint8_t)); if (bytes == NULL) { return NULL; } - /* determine the pieces at each position and write them to the result bytes */ - for (i = 0; i < 12; i++) - { - color = (ChessColor)(i / 6); - piece_type = (ChessPieceType)((i % 6) + 1); - temp_bitboard = board[i]; - - /* until all set bits were evaluated */ - while (temp_bitboard) - { - /* get the index of the highest bit set on the bitboard */ - temp_pos = get_board_position(temp_bitboard); - - /* determine was_moved for the given chess piece and finally create the ChessPiece struct */ - was_moved = (int)(was_piece_moved(board, temp_pos) >> temp_pos); - temp_piece = create_piece(piece_type, color, was_moved); - - /* write the 5 bits of the ChessPiece struct to the pieces cache at the given position */ - temp_pieces[temp_pos] = temp_piece; - - /* remove bit from bitboard to make the while loop terminate eventually */ - temp_bitboard ^= 0x1uLL << temp_pos; - } - } - /* compress the pieces cache to 40 bytes by removing the unused leading 3 bits of each ChessPiece value */ compress_pieces_array(temp_pieces, bytes); @@ -411,13 +402,10 @@ static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args) { /* TODO: export this function to chessboard.c */ - Bitboard board[13] = { 0 }; + Bitboard *board; PyObject *hash_orig; PyArrayObject* hash; uint8_t *compressed_bytes; - - Bitboard temp_bitboard; ChessPosition pos; - ChessPiece temp_piece, temp_pieces[64] = { 0 }; - uint8_t board_index; + ChessPiece temp_pieces[64] = { 0 }; /* parse bitboards as ChessBoard struct */ if (!PyArg_ParseTuple(args, "O", &hash_orig)) { return NULL; } @@ -427,28 +415,11 @@ static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args) /* uncompress the pieces cache from 40 bytes by adding the unused leading 3 bits of each ChessPiece value */ uncompress_pieces_array(compressed_bytes, temp_pieces); - /* allocate chess board */ - board[12] = 0xFFFFFFFFFFFFFFFF; - - /* determine the pieces at each position and write them to the bitboards */ - for (pos = 0; pos < 64; pos++) - { - temp_piece = temp_pieces[pos]; - - /* determine which bitboard to write to */ - board_index = PIECE_OFFSET(get_piece_type(temp_piece)) - + SIDE_OFFSET(get_piece_color(temp_piece)); - - /* set the piece's bit on the according bitboard */ - temp_bitboard = (temp_piece != CHESS_PIECE_NULL ? 0x1uLL : 0x0uLL) << pos; - board[board_index] |= temp_bitboard; - - /* set was moved bit */ - board[12] ^= !get_was_piece_moved(temp_piece) ? (temp_bitboard & START_POSITIONS) : 0x0uLL; - } + /* convert the single chess pieces (aka SimpleChessBoard) to the bitboard representation */ + board = from_simple_board(temp_pieces); /* convert parsed bytes to Python bytearray struct */ - return serialize_chessboard(board); + return serialize_as_bitboards(board); } /* ================================================= @@ -457,14 +428,15 @@ static PyObject* chesslib_board_from_hash(PyObject* self, PyObject* args) static PyObject* chesslib_get_game_state(PyObject* self, PyObject* args) { - PyObject* bitboards; + PyObject* chessboard; ChessBoard board; ChessDraw last_draw = DRAW_NULL; ChessGameState state; + int is_simple_board = 0; /* parse bitboards as ChessBoard struct */ - if (!PyArg_ParseTuple(args, "Oi", &bitboards, &last_draw)) { return NULL; } - board = deserialize_chessboard(bitboards); + if (!PyArg_ParseTuple(args, "Oi|i", &chessboard, &last_draw, &is_simple_board)) { return NULL; } + board = deserialize_as_bitboards(chessboard, is_simple_board); /* determine the game state */ state = get_game_state(board, last_draw); @@ -484,10 +456,11 @@ static PyObject* chesslib_visualize_board(PyObject* self, PyObject* args) uint8_t row, column; ChessPosition pos; ChessPiece piece; + int is_simple_board = 0; /* parse bitboards as ChessBoard struct */ - if (!PyArg_ParseTuple(args, "O", &bitboards)) { return NULL; } - board = deserialize_chessboard(bitboards); + if (!PyArg_ParseTuple(args, "O|i", &bitboards, &is_simple_board)) { return NULL; } + board = deserialize_as_bitboards(bitboards, is_simple_board); /* determine the chess board's textual representation */ strcpy(out, separator); @@ -532,6 +505,8 @@ static PyObject* chesslib_visualize_draw(PyObject* self, PyObject* args) /* parse bitboards as ChessBoard struct */ if (!PyArg_ParseTuple(args, "i", &draw)) { return NULL; } + /* TODO: a chessboard is required for context information when only providing compact draws + this function only works for non-compact draws*/ old_pos = position_to_string(get_old_position(draw)); new_pos = position_to_string(get_new_position(draw)); @@ -572,7 +547,20 @@ static PyObject* chesslib_visualize_draw(PyObject* self, PyObject* args) H E L P E R F U N C T I O N S ================================================= */ -static PyObject* serialize_chessboard(const Bitboard board[]) +static PyObject* serialize_as_pieces(const ChessPiece pieces[]) +{ + /* init a one-dimensional 8-bit integer numpy array with 64 elements */ + npy_intp dims[1] = { 64 }; + + uint8_t i; + SimpleChessBoard data_copy = (SimpleChessBoard)malloc(64 * sizeof(ChessPiece)); + if (data_copy == NULL) { return NULL; } + for (i = 0; i < 64; i++) { data_copy[i] = pieces[i]; } + + return PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT8, data_copy); +} + +static PyObject* serialize_as_bitboards(const Bitboard board[]) { /* init a one-dimensional 64-bit integer numpy array with 13 elements */ npy_intp dims[1] = { 13 }; @@ -585,15 +573,73 @@ static PyObject* serialize_chessboard(const Bitboard board[]) return PyArray_SimpleNewFromData(1, (npy_intp*)dims, NPY_UINT64, data_copy); } -static ChessBoard deserialize_chessboard(PyObject* bitboards_obj) +static SimpleChessBoard deserialize_as_pieces(PyObject* bitboards_obj, int is_simple_board) { - PyArrayObject* bitboards; + SimpleChessBoard out_board = NULL; + PyArrayObject *bitboards, *pieces; - /* parse bitboards as 1-dimensional ndarray of type uint64 and size 13 */ - bitboards = (PyArrayObject*)PyArray_FromObject(bitboards_obj, NPY_UINT64, 1, 13); + /* TODO: implement automatic detection of simple/efficient board formats + use the functions PyArray_NDIM and PyArray_DTYPE + */ - /* retrieve the raw data from the PyArrayObject */ - return (Bitboard*)PyArray_DATA(bitboards); + /* check if the given board can be interpreted as simple format */ + /*if (PyArray_NDIM(bitboards_obj) == 64 && PyArray_DTYPE(bitboards_obj) == NPY_UINT8)*/ + if (is_simple_board) + { + /* parse simple chess board as ndarray of 64 raw bytes */ + pieces = (PyArrayObject*)PyArray_FromObject(bitboards_obj, NPY_UINT8, 1, 64); + + /* convert the simple chess board into the bitboard representation for efficient operations */ + out_board = (SimpleChessBoard)PyArray_DATA(pieces); + } + /* check if the given board can be interpreted as bitboards format */ + /*else if (PyArray_NDIM(bitboards_obj) == 13 && PyArray_DTYPE(bitboards_obj) == NPY_UINT64)*/ + else + { + /* parse bitboards as 1-dimensional ndarray of type uint64 and size 13 */ + bitboards = (PyArrayObject*)PyArray_FromObject(bitboards_obj, NPY_UINT64, 1, 13); + out_board = to_simple_board((Bitboard*)PyArray_DATA(bitboards)); + } + + return out_board; +} + +static ChessBoard deserialize_as_bitboards(PyObject* bitboards_obj, int is_simple_board) +{ + ChessBoard out_board = NULL; + PyArrayObject *bitboards, *pieces; + + /* TODO: implement automatic detection of simple/efficient board formats + use the functions PyArray_NDIM and PyArray_DTYPE + */ + + /* check if the given board can be interpreted as simple format */ + /*if (PyArray_NDIM(bitboards_obj) == 64 && PyArray_DTYPE(bitboards_obj) == NPY_UINT8)*/ + if (is_simple_board) + { + /* parse simple chess board as ndarray of 64 raw bytes */ + pieces = (PyArrayObject*)PyArray_FromObject(bitboards_obj, NPY_UINT8, 1, 64); + + /* convert the simple chess board into the bitboard representation for efficient operations */ + out_board = from_simple_board((SimpleChessBoard)PyArray_DATA(pieces)); + } + /* check if the given board can be interpreted as bitboards format */ + /*else if (PyArray_NDIM(bitboards_obj) == 13 && PyArray_DTYPE(bitboards_obj) == NPY_UINT64)*/ + else + { + /* parse bitboards as 1-dimensional ndarray of type uint64 and size 13 */ + bitboards = (PyArrayObject*)PyArray_FromObject(bitboards_obj, NPY_UINT64, 1, 13); + out_board = (Bitboard*)PyArray_DATA(bitboards); + } + + return out_board; +} + +static ChessDraw deserialize_chessdraw(const Bitboard board[], const ChessDraw draw) +{ + /* if none of the leading 10 bits is set, the given draw has to be + of the compact draw foramt -> append the missing properties. */ + return (draw < 0x800) ? from_compact_draw(board, (CompactChessDraw)draw) : draw; } static void compress_pieces_array(const ChessPiece pieces[], uint8_t* out_bytes) diff --git a/tests/test.py b/tests/test.py index 7b552f4..5568e24 100644 --- a/tests/test.py +++ b/tests/test.py @@ -42,10 +42,10 @@ def test_module(): # test gameplay functions test_drawgen() test_apply_draw() - #test_game_state() - test_board_hash() + # test_game_state() + # test_board_hash() # -> TODO: fix this API function - # test visualization functions + # # test visualization functions test_visualize_board() test_visualize_draw() @@ -221,7 +221,7 @@ def test_chessboard_start(): # test if the expected board in start formation is returned start = chesslib.ChessBoard_StartFormation() - #print(start) + print(start) for i in range(13): assert_equal(start[i], START_FORMATION[i]) From 808fcd9b3cea46d68fc40f41cb798912e96145c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Tr=C3=B6ster?= Date: Thu, 25 Feb 2021 01:44:33 +0100 Subject: [PATCH 3/3] added compatibility for simple chess boards and compact draws (now tested, should be fine, but would be better to back it up with tests) --- chesslib/src/chessboard.c | 9 +++++---- chesslib/src/chessdraw.c | 6 ++++++ tests/test.py | 5 +++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/chesslib/src/chessboard.c b/chesslib/src/chessboard.c index e54e8ac..5955d1e 100644 --- a/chesslib/src/chessboard.c +++ b/chesslib/src/chessboard.c @@ -204,11 +204,12 @@ ChessBoard from_simple_board(const ChessPiece simple_board[]) Bitboard bitboard; int set_bit; - ChessBoard bitboards = (ChessBoard)malloc(13 * sizeof(Bitboard)); - /* assume pieces as already moved */ Bitboard was_moved = 0xFFFFFFFFFFFFFFFFuL; + /* allocate bitboards array */ + ChessBoard bitboards = (ChessBoard)malloc(13 * sizeof(Bitboard)); + /* loop through all bitboards */ for (i = 0; i < 12; i++) { @@ -261,7 +262,7 @@ SimpleChessBoard to_simple_board(const Bitboard board[]) SimpleChessBoard simple_board; /* init pieces array with empty fields */ - simple_board = (SimpleChessBoard)malloc(sizeof(ChessPiece) * 64); + simple_board = (SimpleChessBoard)calloc(64, sizeof(ChessPiece)); // loop through all bitboards for (i = 0; i < 12; i++) @@ -279,7 +280,7 @@ SimpleChessBoard to_simple_board(const Bitboard board[]) // write piece to array if there is one simple_board[pos] = (bitboard & 0x1) > 0 ? create_piece(piece_type, color, was_piece_moved(board, pos)) - : CHESS_PIECE_NULL; + : simple_board[pos]; // shift bitboard bitboard >>= 1; diff --git a/chesslib/src/chessdraw.c b/chesslib/src/chessdraw.c index 48dbe67..f8e989c 100644 --- a/chesslib/src/chessdraw.c +++ b/chesslib/src/chessdraw.c @@ -90,6 +90,12 @@ ChessDraw create_draw(const Bitboard board[], ChessPosition oldPos, ChessPositio return draw; } +ChessDraw from_compact_draw(const Bitboard board[], CompactChessDraw comp_draw) +{ + return create_draw(board, get_old_position(comp_draw), + get_new_position(comp_draw), get_peasant_promotion_piece_type(comp_draw)); +} + CompactChessDraw to_compact_draw(ChessDraw draw) { return (CompactChessDraw)(draw & 0x7FFF); diff --git a/tests/test.py b/tests/test.py index 5568e24..bcb68f2 100644 --- a/tests/test.py +++ b/tests/test.py @@ -43,7 +43,7 @@ def test_module(): test_drawgen() test_apply_draw() # test_game_state() - # test_board_hash() # -> TODO: fix this API function + test_board_hash() # # test visualization functions test_visualize_board() @@ -221,7 +221,7 @@ def test_chessboard_start(): # test if the expected board in start formation is returned start = chesslib.ChessBoard_StartFormation() - print(start) + #print(start) for i in range(13): assert_equal(start[i], START_FORMATION[i]) @@ -319,6 +319,7 @@ def test_board_hash(): # compute the board's 40-byte hash hash = bytes(chesslib.Board_ToHash(board)) + print(chesslib.Board_ToHash(board)) # define the expected hash exp_hash = bytes([