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")
Exemple #4
0
 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")
Exemple #5
0
    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")
Exemple #6
0
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,
                    )
Exemple #7
0
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
Exemple #8
0
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