def test_get_move_planes_given_underpromotions_moves_expect_correct_plane_selection( self): """ Test to see if it returns the correct indices for under promotions""" col = 4 row = 6 aggregated_selected_boards = np.zeros(9) for piece in ["n", "b", "r"]: for x_direction in [-1, 0, 1]: movement_vector = [1, x_direction] from_idx = get_board_position_index(row, col) to_idx = get_board_position_index(row + movement_vector[0], col + movement_vector[1]) selected_boards = np.sum(get_move_planes( chess.Move(from_idx, to_idx, piece))[64:73, :, :], axis=(1, 2)) # test that only a single position is selected self.assertTrue( np.sum(selected_boards) == 1, "more than one board was selected") aggregated_selected_boards += selected_boards # test that all ids where selected once self.assertTrue(np.all(aggregated_selected_boards == 1), "some boards where selected never or more than once")
def test_get_move_planes_given_knight_moves_expect_correct_plane_selection( self): """ Test to see if it returns the correct indices for knight movements""" # place the knight in the center of the board # and test all combinations col = row = 4 aggregated_selected_boards = np.zeros(8) for x in [-1, 1]: for y in [-1, 1]: for x_dominates in [True, False]: movement_vector = [ y * (1 if x_dominates else 2), x * (2 if x_dominates else 1) ] from_idx = get_board_position_index(row, col) to_idx = get_board_position_index(row + movement_vector[0], col + movement_vector[1]) selected_boards = np.sum(get_move_planes( chess.Move(from_idx, to_idx))[56:64, :, :], axis=(1, 2)) # test that only a single position is selected self.assertTrue( np.sum(selected_boards) == 1, "more than one board was selected") aggregated_selected_boards += selected_boards # test that all ids where selected once self.assertTrue(np.all(aggregated_selected_boards == 1), "some boards where selected never or more than once")
def test_get_move_planes_given_queen_promotion_moves_expect_correct_plane_selection( self): """ Test to see if it returns the correct indices for over promotions(queens)""" col = 4 row = 6 aggregated_selected_boards = np.zeros(3) for x_direction in [-1, 0, 1]: movement_vector = [1, x_direction] from_idx = get_board_position_index(row, col) to_idx = get_board_position_index(row + movement_vector[0], col + movement_vector[1]) selected_boards = np.sum(get_move_planes( chess.Move(from_idx, to_idx, "Q"))[73:76, :, :], axis=(1, 2)) # test that only a single position is selected self.assertTrue( np.sum(selected_boards) == 1, "more than one board was selected") aggregated_selected_boards += selected_boards # test that 3 ids where selected once selected = aggregated_selected_boards[aggregated_selected_boards > 0] self.assertTrue( len(selected) == 3, "more or less than 3 boards where selected") self.assertTrue(np.all(selected == 1), "some boards where selected never or more than once")
def test_get_move_planes_given_all_positions_expect_correct_position_on_plane(self): """ Test to see if it returns the correct position on the board""" for col in range(8): for row in range(8): from_idx = get_board_position_index(row, col) to_idx = get_board_position_index(row, col + 1) planes = get_move_planes(chess.Move(from_idx, to_idx)) flat_plane = np.sum(planes, axis=0) # flatten all planes onto a single board # test that only a single position is selected self.assertTrue(np.sum(flat_plane) == 1, "more than one position was selected") # test that the correct position is selected self.assertTrue(flat_plane[row, col] == 1, "an incorrect position was selected")
def test_get_move_planes_given_drops_expect_correct_plane_selection(self): """ Test to see if it returns the correct indices drops""" aggregated_selected_boards = np.zeros(5) for piece in ["p", "n", "b", "r", "q"]: # 1=knight, 2=bishop, 3=rook, 4=queen from_idx = get_board_position_index(4, 4) to_idx = get_board_position_index(4, 4) selected_boards = np.sum( get_move_planes(chess.Move(from_idx, to_idx, None, piece))[76:81, :, :], axis=(1, 2) ) # test that only a single position is selected self.assertTrue(np.sum(selected_boards) == 1, "more than one board was selected") aggregated_selected_boards += selected_boards # test that all ids where selected once self.assertTrue(np.all(aggregated_selected_boards == 1), "some boards where selected never or more than once")
def set_pieces(board, planes): # iterate over all piece types for idx, piece in enumerate(PIECES): # iterate over all fields and set the current piece type for row in range(BOARD_HEIGHT): for col in range(BOARD_WIDTH): # check if there's a piece at the current position if planes[idx, row, col] == 1: # check if the piece was promoted promoted = False board.set_piece_at( square=get_board_position_index(row, col), piece=chess.Piece.from_symbol(piece), promoted=promoted, )
def planes_to_board(planes, normalized_input=False, mode=MODE_CRAZYHOUSE): """ Converts a board in plane representation to the python chess board representation see get_planes_of_board() for input encoding description :param planes: Input plane representation :param normalized_input: True if the input has been normalized to range[0., 1.] :param mode: 0 - MODE_CRAZYHOUSE: Crazyhouse only specification. (Visit variants.crazyhouse.input_representation for detailed documentation) 1 - MODE_LICHESS: Specification for all supported variants on lichess.org (Visit variants.lichess.input_representation for detailed documentation) 2 - MODE_CHESS: Specification for chess only with chess960 support (Visit variants.chess.input_representation for detailed documentation) :return: python chess board object """ if mode not in MODES: raise ValueError(f"Given {mode} is not {MODES}.") # extract the maps for the board position planes_pos = planes[:NB_CHANNELS_POS] # extract the last maps which for the constant values end_board_idx = NB_CHANNELS_POS + NB_CHANNELS_CONST planes_const = planes[NB_CHANNELS_POS:end_board_idx] if mode == MODE_LICHESS: # extract the variants definition section planes_variants = planes[end_board_idx:end_board_idx + NB_CHANNELS_VARIANTS] # setup new initial board is960 = planes_variants[CHANNEL_MAPPING_VARIANTS["is960"], 0, 0] == 1 # iterate through all available variants board = None for variant in VARIANT_MAPPING_BOARDS: # exclude "is960" if planes_variants[CHANNEL_MAPPING_VARIANTS[variant], 0, 0] == 1: board = VARIANT_MAPPING_BOARDS[variant](chess960=is960) break if board is None: raise Exception( "No chess variant was recognized in your given input planes") elif mode == MODE_CRAZYHOUSE: board = CrazyhouseBoard() else: # mode = MODE_CHESS: # extract the variants definition section plane_960 = planes[-1:] is960 = plane_960[CHANNEL_MAPPING_VARIANTS["is960"], 0, 0] == 1 board = chess.Board(chess960=is960) # clear the full board (the pieces will be set later) board.clear() # iterate over all piece types for idx, piece in enumerate(PIECES): # iterate over all fields and set the current piece type for row in range(BOARD_HEIGHT): for col in range(BOARD_WIDTH): # check if there's a piece at the current position if planes_pos[idx, row, col] == 1: # check if the piece was promoted promoted = False if mode == MODE_CRAZYHOUSE or mode == MODE_LICHESS: # promoted pieces are not defined in the chess plane representation channel = CHANNEL_MAPPING_POS["promo"] if planes_pos[channel, row, col] == 1 or planes_pos[channel + 1, row, col] == 1: promoted = True board.set_piece_at( square=get_board_position_index(row, col), piece=chess.Piece.from_symbol(piece), promoted=promoted, ) # (I) Fill in the Repetition Data # check how often the position has already occurred in the game # TODO: Find a way to set this on the board state # -> apparently this isn't possible because it's also not available in the board uci representation # ch = channel_mapping['repetitions'] # Fill in the Prisoners / Pocket Pieces if mode == MODE_CRAZYHOUSE or board.uci_variant == "crazyhouse": # iterate over all pieces except the king for p_type in chess.PIECE_TYPES[:-1]: # p_type -1 because p_type starts with 1 channel = CHANNEL_MAPPING_POS["prisoners"] + p_type - 1 # the full board is filled with the same value # it's sufficient to take only the first value nb_prisoners = planes_pos[channel, 0, 0] # add prisoners for the current player # the whole board is set with the same entry, we can just take the first one if normalized_input is True: nb_prisoners *= MAX_NB_PRISONERS nb_prisoners = int(round(nb_prisoners)) for _ in range(nb_prisoners): board.pockets[chess.WHITE].add(p_type) # add prisoners for the opponent nb_prisoners = planes_pos[channel + 5, 0, 0] if normalized_input is True: nb_prisoners *= MAX_NB_PRISONERS nb_prisoners = int(round(nb_prisoners)) for _ in range(nb_prisoners): board.pockets[chess.BLACK].add(p_type) # (I.5) En Passant Square # mark the square where an en-passant capture is possible channel = CHANNEL_MAPPING_POS["ep_square"] ep_square = np.argmax(planes_pos[channel]) if ep_square != 0: # if no entry 'one' exists, index 0 will be returned board.ep_square = ep_square # (II) Constant Value Inputs # (II.1) Total Move Count channel = CHANNEL_MAPPING_CONST["total_mv_cnt"] total_mv_cnt = planes_const[channel, 0, 0] if normalized_input is True: total_mv_cnt *= MAX_NB_MOVES total_mv_cnt = int(round(total_mv_cnt)) board.fullmove_number = total_mv_cnt # (II.2) Castling Rights channel = CHANNEL_MAPPING_CONST["castling"] # reset the castling_rights for initialization # set to 0, previously called chess.BB_VOID for chess version of 0.23.X and chess.BB_EMPTY for versions > 0.27.X board.castling_rights = 0 # WHITE # check for King Side Castling # White can castle with the h1 rook # add castling option by modifying the castling fen castling_fen = "" # check for King Side Castling if planes_const[channel, 0, 0] == 1: castling_fen += "K" board.castling_rights |= chess.BB_H1 # check for Queen Side Castling if planes_const[channel + 1, 0, 0] == 1: castling_fen += "Q" board.castling_rights |= chess.BB_A1 # BLACK # check for King Side Castling if planes_const[channel + 2, 0, 0] == 1: castling_fen += "k" board.castling_rights |= chess.BB_H8 # check for Queen Side Castling if planes_const[channel + 3, 0, 0] == 1: castling_fen += "q" board.castling_rights |= chess.BB_A8 # configure the castling rights if castling_fen: board.set_castling_fen(castling_fen) # (II.3) No Progress Count channel = CHANNEL_MAPPING_CONST["no_progress_cnt"] no_progress_cnt = planes_const[channel, 0, 0] if normalized_input is True: no_progress_cnt *= MAX_NB_NO_PROGRESS no_progress_cnt = int(round(no_progress_cnt)) board.halfmove_clock = no_progress_cnt # set the number of remaining checks (only needed for 3check) and might be mirrored later if mode == MODE_LICHESS: channel = CHANNEL_MAPPING_CONST["remaining_checks"] if planes_const[channel, 0, 0] == 1: board.remaining_checks[chess.WHITE] -= 1 if planes_const[channel + 1, 0, 0] == 1: board.remaining_checks[chess.WHITE] -= 1 if planes_const[channel + 2, 0, 0] == 1: board.remaining_checks[chess.BLACK] -= 1 if planes_const[channel + 3, 0, 0] == 1: board.remaining_checks[chess.BLACK] -= 1 # (II.4) Color channel = CHANNEL_MAPPING_CONST["color"] if planes_const[channel, 0, 0] == 1: board.board_turn = chess.WHITE else: board = board.mirror() board.board_turn = chess.BLACK return board
def planes_to_board(planes, normalized_input=False): """ Converts a board in plane representation to the python chess board representation see get_planes_of_board() for input encoding description :param planes: Input plane representation :param normalized_input: True if the input has been normalized to range[0., 1.] :return: python chess board object """ # setup new initial board board = CrazyhouseBoard() board.clear_board() # extract the maps for the board position mat_pos = planes[:NB_CHANNELS_POS] # extract the last maps which for the constant values mat_const = planes[-NB_CHANNELS_CONST:] # iterate over all piece types for idx, piece in enumerate(PIECES): # iterate over all fields and set the current piece type for row in range(BOARD_HEIGHT): for col in range(BOARD_WIDTH): # check if there's a piece at the current position if mat_pos[idx, row, col] == 1: # check if the piece was promoted promoted = False ch = CHANNEL_MAPPING_POS["promo"] if mat_pos[ch, row, col] == 1 or mat_pos[ch + 1, row, col] == 1: promoted = True board.set_piece_at( square=get_board_position_index(row, col), piece=chess.Piece.from_symbol(piece), promoted=promoted, ) # (I) Fill in the Repetition Data # check how often the position has already occurred in the game # TODO: Find a way to set this on the board state # -> apparently this isn't possible because it's also not available in the board uci representation # ch = channel_mapping['repetitions'] # Fill in the Prisoners / Pocket Pieces # iterate over all pieces except the king for p_type in chess.PIECE_TYPES[:-1]: # p_type -1 because p_type starts with 1 ch = CHANNEL_MAPPING_POS["prisoners"] + p_type - 1 # the full board is filled with the same value # it's sufficient to take only the first value nb_prisoners = mat_pos[ch, 0, 0] # add prisoners for the current player # the whole board is set with the same entry, we can just take the first one if normalized_input is True: nb_prisoners *= MAX_NB_PRISONERS nb_prisoners = int(round(nb_prisoners)) for i in range(nb_prisoners): board.pockets[chess.WHITE].add(p_type) # add prisoners for the opponent nb_prisoners = mat_pos[ch + 5, 0, 0] if normalized_input is True: nb_prisoners *= MAX_NB_PRISONERS nb_prisoners = int(round(nb_prisoners)) for i in range(nb_prisoners): board.pockets[chess.BLACK].add(p_type) # (I.5) En Passant Square # mark the square where an en-passant capture is possible ch = CHANNEL_MAPPING_POS["ep_square"] ep_square = np.argmax(mat_pos[ch]) if ep_square != 0: # if no entry 'one' exists, index 0 will be returned board.ep_square = ep_square # (II) Constant Value Inputs # (II.1) Total Move Count ch = CHANNEL_MAPPING_CONST["total_mv_cnt"] total_mv_cnt = mat_const[ch, 0, 0] if normalized_input is True: total_mv_cnt *= MAX_NB_MOVES total_mv_cnt = int(round(total_mv_cnt)) board.fullmove_number = total_mv_cnt # (II.2) Castling Rights ch = CHANNEL_MAPPING_CONST["castling"] # reset the castling_rights for initialization board.castling_rights = chess.BB_VOID # WHITE # check for King Side Castling # White can castle with the h1 rook # add castling option by applying logical-OR operation if mat_const[ch, 0, 0] == 1: board.castling_rights |= chess.BB_H1 # check for Queen Side Castling if mat_const[ch + 1, 0, 0] == 1: board.castling_rights |= chess.BB_A1 # BLACK # check for King Side Castling if mat_const[ch + 2, 0, 0] == 1: board.castling_rights |= chess.BB_H8 # check for Queen Side Castling if mat_const[ch + 3, 0, 0] == 1: board.castling_rights |= chess.BB_A8 # (II.3) No Progress Count ch = CHANNEL_MAPPING_CONST["no_progress_cnt"] no_progress_cnt = mat_const[ch, 0, 0] if normalized_input is True: no_progress_cnt *= MAX_NB_NO_PROGRESS no_progress_cnt = int(round(no_progress_cnt)) board.halfmove_clock = no_progress_cnt # (II.4) Color ch = CHANNEL_MAPPING_CONST["color"] if mat_const[ch, 0, 0] == 1: board.board_turn = chess.WHITE else: board = board.mirror() board.board_turn = chess.BLACK return board