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