def get_board_bits(board: chess.Board): out = [board.pieces(p, chess.WHITE).tolist() for p in chess.PIECE_TYPES] out += [board.pieces(p, chess.BLACK).tolist() for p in chess.PIECE_TYPES] out = list(reduce(lambda a, b: a + b, out, [])) out += [ board.has_kingside_castling_rights(chess.WHITE), board.has_queenside_castling_rights(chess.WHITE), board.has_kingside_castling_rights(chess.BLACK), board.has_queenside_castling_rights(chess.BLACK) ] return list(map(int, out))
def hash_castling(self, board: chess.Board) -> int: zobrist_hash = 0 # Hash in the castling flags. if board.has_kingside_castling_rights(chess.WHITE): zobrist_hash ^= self.array[768] if board.has_queenside_castling_rights(chess.WHITE): zobrist_hash ^= self.array[768 + 1] if board.has_kingside_castling_rights(chess.BLACK): zobrist_hash ^= self.array[768 + 2] if board.has_queenside_castling_rights(chess.BLACK): zobrist_hash ^= self.array[768 + 3] return zobrist_hash
def get_board_score(board: chess.Board, color: chess.Color) -> int: total = 0 pawns = board.pieces(chess.PAWN, color) bishops = board.pieces(chess.BISHOP, color) knights = board.pieces(chess.KNIGHT, color) queens = board.pieces(chess.QUEEN, color) rooks = board.pieces(chess.ROOK, color) for _ in pawns: total += 10 for _ in bishops: total += 30 for _ in knights: total += 30 for _ in queens: total += 90 for _ in rooks: total += 50 if (board.has_kingside_castling_rights(color)): total += 1 if (board.has_queenside_castling_rights(color)): total += 1 return total
def get_additional_features(board: chess.Board): ''' Get additional features like color, total move count, etc from a board state :param board: chess.Board :param env: ChessEnv environment :return: additional features ''' color = board.turn total_moves = board.fullmove_number w_castling = [board.has_kingside_castling_rights(chess.WHITE), board.has_queenside_castling_rights(chess.WHITE)] b_castling = [board.has_kingside_castling_rights(chess.BLACK), board.has_queenside_castling_rights(chess.BLACK)] no_progress_count = int(board.halfmove_clock / 2) return color, total_moves, w_castling, b_castling, no_progress_count
def write_json(self, board: chess.Board): """ Writes all of the board info in json """ best_move = self.get_best_move(board) output = OrderedDict([ ('fen', board.fen()), ('fullmoveNumber', board.fullmove_number), ('result', board.result()), ('isGameOver', board.is_game_over()), ('isCheckmate', board.is_checkmate()), ('isStalemate', board.is_stalemate()), ('isInsufficientMaterial', board.is_insufficient_material()), ('isSeventyfiveMoves', board.is_seventyfive_moves()), ('isFivefoldRepetition', board.is_fivefold_repetition()), ('white', OrderedDict([ ('hasKingsideCastlingRights', board.has_kingside_castling_rights(chess.WHITE)), ('hasQueensideCastlingRights', board.has_queenside_castling_rights(chess.WHITE)), ])), ('black', OrderedDict([ ('hasKingsideCastlingRights', board.has_kingside_castling_rights(chess.BLACK)), ('hasQueensideCastlingRights', board.has_queenside_castling_rights(chess.BLACK)), ])), ('turn', OrderedDict([ ('color', 'white' if board.turn is chess.WHITE else 'black'), ('isInCheck', board.is_check()), ('bestMove', best_move), ('legalMoves', [move.uci() for move in board.legal_moves]), ('canClaimDraw', board.can_claim_draw()), ('canClaimFiftyMoves', board.can_claim_fifty_moves()), ('canClaimThreefoldRepetition', board.can_claim_threefold_repetition()), ])), ]) self.finish(output)
def observation(self, board: chess.Board) -> np.array: """Converts chess.Board observations instance to numpy arrays. Note: This method is called by gym.ObservationWrapper to transform the observations returned by the wrapped env's step() method. In particular, calling this method will add the given board position to the history. Do NOT call this method manually to recover a previous observation returned by step(). """ self._history.push(board) history = self._history.view(orientation=board.turn) meta = np.zeros(shape=(8, 8, 7), dtype=np.int) # Active player color meta[:, :, 0] = int(board.turn) # Total move count meta[:, :, 1] = board.fullmove_number # Active player castling rights meta[:, :, 2] = board.has_kingside_castling_rights(board.turn) meta[:, :, 3] = board.has_queenside_castling_rights(board.turn) # Opponent player castling rights meta[:, :, 4] = board.has_kingside_castling_rights(not board.turn) meta[:, :, 5] = board.has_queenside_castling_rights(not board.turn) # No-progress counter meta[:, :, 6] = board.halfmove_clock observation = np.concatenate([history, meta], axis=-1) return observation
def is_illegal_castle(board: chess.Board, move: chess.Move) -> bool: if not board.is_castling(move): return False # illegal without kingside rights if board.is_kingside_castling(move) and not board.has_kingside_castling_rights(board.turn): return True # illegal without queenside rights if board.is_queenside_castling(move) and not board.has_queenside_castling_rights(board.turn): return True # illegal if any pieces are between king & rook rook_square = chess.square(7 if board.is_kingside_castling(move) else 0, chess.square_rank(move.from_square)) between_squares = chess.SquareSet(chess.between(move.from_square, rook_square)) if any(map(lambda s: board.piece_at(s), between_squares)): return True # its legal return False
def old_evaluate(board: chess.Board): """Evaluate position.""" white_score = 0 black_score = 0 if board.is_stalemate(): return 0 if board.is_checkmate(): if board.turn == chess.WHITE: return -10000 return 10000 piece_map = board.piece_map() # Counters for bishop pair white_bishops = 0 black_bishops = 0 for piece in piece_map: if piece_map[piece].symbol().isupper(): white_score += PIECES_VALUES[piece_map[piece].symbol().lower()] if piece in CENTRAL_SQUARES: white_score += CENTER_BONUS if piece in ELARGED_SQUARES: white_score += int(CENTER_BONUS / 2) if piece_map[piece].symbol() == 'P' and piece in SEVENTH_ROW: white_score += PAWN_SEVENTH_ROW if piece_map[piece].symbol() == 'P' and piece in EIGHT_ROW: white_score += QUEEN_VALUE if piece_map[piece].symbol() == 'B': white_bishops += 1 """if piece_map[piece].symbol() == 'Q' and piece in CENTRAL_SQUARES and len(piece_map) > 20: white_score -= 30""" if piece_map[piece].symbol() == 'K': if piece + 7 in piece_map and piece_map[piece + 7].symbol() == 'P' and len(piece_map) > 16: white_score += PROTECTED_KING if piece + 8 in piece_map and piece_map[piece + 8].symbol() == 'P' and len(piece_map) > 16: white_score += PROTECTED_KING if piece + 9 in piece_map and piece_map[piece + 9].symbol() == 'P' and len(piece_map) > 16: white_score += PROTECTED_KING else: black_score += PIECES_VALUES[piece_map[piece].symbol()] if piece in CENTRAL_SQUARES and not piece_map[piece].symbol() == "q": black_score += 10 if piece in ELARGED_SQUARES and not piece_map[piece].symbol() == "q": black_score += 5 if piece_map[piece].symbol() == 'p' and piece in SECOND_ROW: black_score += 20 if piece_map[piece].symbol() == 'p' and piece in FIRST_ROW: black_score += QUEEN_VALUE if piece_map[piece].symbol() == 'b': black_bishops += 1 if piece_map[piece].symbol() == 'k': if piece - 7 in piece_map and piece_map[piece - 7].symbol() == 'p' and len(piece_map) > 16: black_score += PROTECTED_KING if piece - 8 in piece_map and piece_map[piece - 8].symbol() == 'p' and len(piece_map) > 16: black_score += PROTECTED_KING if piece - 9 in piece_map and piece_map[piece - 9].symbol() == 'p' and len(piece_map) > 16: black_score += PROTECTED_KING if piece_map[piece].symbol() == 'q' and len(piece_map) > 28: black_score -= 30 if white_bishops >= 2: white_score += BISHOPS_PAIR if black_bishops >= 2: black_score += BISHOPS_PAIR if board.has_kingside_castling_rights(chess.WHITE): white_score += 7 if board.has_kingside_castling_rights(chess.BLACK): black_score += 7 if board.has_queenside_castling_rights(chess.WHITE): white_score += 7 if board.has_queenside_castling_rights(chess.BLACK): black_score += 7 # if board.peek().uci() in ['e1g1', 'e1c1']: # white_score += 101 # print("white castle !") # if board.peek().uci() in ['e8g8', 'e8c8']: # black_score += 101 # print("black castle !") # Check isolated pawns for index, column in enumerate(COLUMNS): column_pawns = pawn_on_column(column, "P", piece_map) if column_pawns == 2: white_score -= DOUBLED_PAWNS if column_pawns == 3: white_score -= TRIPLED_PAWNS if column_pawns >= 4: white_score -= QUADRUPLED_PAWNS if column_pawns > 0: if column == COLUMN_A and pawn_on_column(COLUMN_B, "P", piece_map) == 0: white_score -= ISOLATED_PAWN elif column == COLUMN_H and pawn_on_column(COLUMN_G, "P", piece_map) == 0: white_score -= ISOLATED_PAWN else: if pawn_on_column(COLUMNS[index-1], "P", piece_map) == 0 and pawn_on_column(COLUMNS[index+1], "P", piece_map) == 0: white_score -= ISOLATED_PAWN white_score += check_passed_pawns(board, True) if board.turn == chess.WHITE: white_score += len(list(board.legal_moves)) board.push(chess.Move.from_uci("0000")) black_score += len(list(board.legal_moves)) board.pop() else: black_score += len(list(board.legal_moves)) board.push(chess.Move.from_uci("0000")) white_score += len(list(board.legal_moves)) board.pop() return white_score-black_score
def board_to_planes(board: chess.Board, normalize=True, last_moves=None): """ Gets the plane representation of a given board state. ## Chess: Feature | Planes --- | --- P1 piece | 6 (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING) P2 piece | 6 (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING) En-passant square | 1 (Binary map indicating the square where en-passant capture is possible) --- 13 planes * * * P1 castling | 2 (One if castling is possible, else zero) P2 castling | 2 (One if castling is possible, else zero) --- 4 planes * * * Last 8 moves | 16 (indicated by origin and destination square, the most recent move is described by first 2 planes) --- 16 planes * * * is960 = | 1 (boolean, 1 when active) --- 1 plane --- P1 pieces | 1 | A grouped mask of all WHITE pieces | P2 pieces | 1 | A grouped mask of all BLACK pieces | Checkerboard | 1 | A chess board pattern | P1 Material Diff | 5 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN), normalized with 8, + means positive, - means negative | Opposite Color Bishops | 1 | Indicates if they are only two bishops and the bishops are opposite color | Checkers | 1 | Indicates all pieces giving check | Checking Moves | 2 | Indicates all checking moves (from sq, to sq) | Mobility | 1 | Indicates the number of legal moves P1 Material Count | 5 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN), normalized with 8 | --- 18 planes The total number of planes is calculated as follows: # -------------- 13 + 4 + 2 + 1 + 18 Total: 38 planes :param board: Board handle (Python-chess object) :param normalize: True if the inputs shall be normalized to the range [0.-1.] :param last_moves: List of last last moves. The most recent move is the first entry. :return: planes - the plane representation of the current board state """ # return the plane representation of the given board # return variants.board_to_planes(board, board_occ, normalize, mode=MODE_CHESS) planes = np.zeros((NB_CHANNELS_TOTAL, BOARD_HEIGHT, BOARD_WIDTH)) # channel will be incremented by 1 at first plane channel = 0 me = board.turn you = not board.turn colors = [me, you] # mirror all bitboard entries for the black player mirror = board.turn == chess.BLACK assert (channel == CHANNEL_PIECES) # Fill in the piece positions # Channel: 0 - 11 # Iterate over both color starting with WHITE for color in colors: # the PIECE_TYPE is an integer list in python-chess for piece_type in chess.PIECE_TYPES: # iterate over the piece mask and receive every position square of it for pos in board.pieces(piece_type, color): row, col = get_row_col(pos, mirror=mirror) # set the bit at the right position planes[channel, row, col] = 1 channel += 1 # Channel: 12 # En Passant Square assert (channel == CHANNEL_EN_PASSANT) if board.ep_square and board.has_legal_en_passant(): # is not None: row, col = get_row_col(board.ep_square, mirror=mirror) planes[channel, row, col] = 1 channel += 1 # Channel: 13 - 16 assert (channel == CHANNEL_CASTLING) for color in colors: # check for King Side Castling if board.has_kingside_castling_rights(color): planes[channel, :, :] = 1 channel += 1 # check for Queen Side Castling if board.has_queenside_castling_rights(color): planes[channel, :, :] = 1 channel += 1 # Channel: 17 - 18 assert (channel == CHANNEL_LAST_MOVES) # Last 8 moves if last_moves: assert (len(last_moves) == NB_LAST_MOVES) for move in last_moves: if move: from_row, from_col = get_row_col(move.from_square, mirror=mirror) to_row, to_col = get_row_col(move.to_square, mirror=mirror) planes[channel, from_row, from_col] = 1 channel += 1 planes[channel, to_row, to_col] = 1 channel += 1 else: channel += 2 else: channel += NB_LAST_MOVES * NB_CHANNELS_PER_HISTORY_ITEM # Channel: 19 # Chess960 assert (channel == CHANNEL_IS_960) if board.chess960: planes[channel + 1, :, :] = 1 channel += 1 # Channel: 20 - 21 # All white pieces and black pieces in a single map assert (channel == CHANNEL_PIECE_MASK) for color in colors: # the PIECE_TYPE is an integer list in python-chess for piece_type in chess.PIECE_TYPES: # iterate over the piece mask and receive every position square of it for pos in board.pieces(piece_type, color): row, col = get_row_col(pos, mirror=mirror) # set the bit at the right position planes[channel, row, col] = 1 channel += 1 # Channel: 22 # Checkerboard assert (channel == CHANNEL_CHECKERBOARD) planes[channel, :, :] = checkerboard() channel += 1 # Channel: 23 - 27 # Relative material difference (negative if less pieces than opponent and positive if more) # iterate over all pieces except the king assert (channel == CHANNEL_MATERIAL_DIFF) for piece_type in chess.PIECE_TYPES[:-1]: material_count = len(board.pieces(piece_type, me)) - len( board.pieces(piece_type, you)) planes[ channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count channel += 1 # Channel: 28 # Opposite color bishops assert (channel == CHANNEL_OPP_BISHOPS) if opposite_colored_bishops(board): planes[channel, :, :] = 1 channel += 1 # Channel: 29 # Checkers assert channel == CHANNEL_CHECKERS board_checkers = checkers(board) if board_checkers: # iterate over the piece mask and receive every position square of it for pos in chess.SquareSet(board_checkers): row, col = get_row_col(pos, mirror=mirror) # set the bit at the right position planes[channel, row, col] = 1 channel += 1 my_legal_moves = list(board.legal_moves) # Channel: 30 - 31 assert channel == CHANNEL_CHECK_MOVES for move in my_legal_moves: if gives_check(board, move): row, col = get_row_col(move.from_square, mirror=mirror) planes[channel, row, col] = 1 row, col = get_row_col(move.to_square, mirror=mirror) planes[channel + 1, row, col] = 1 channel += 2 # Channel: 32 # Mobility assert (channel == CHANNEL_MOBILITY) planes[channel, :, :] = len( my_legal_moves) / NORMALIZE_MOBILITY if normalize else len( my_legal_moves) channel += 1 # Channel: 33 # Material assert (channel == CHANNEL_MATERIAL_COUNT) for piece_type in chess.PIECE_TYPES[:-1]: material_count = len(board.pieces(piece_type, me)) planes[ channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count channel += 1 assert channel == NB_CHANNELS_TOTAL return planes
def board_to_planes(board: chess.Board, board_occ, normalize=True, last_moves=None): """ Gets the plane representation of a given board state. ## Chess: Feature | Planes --- | --- P1 piece | 6 (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING) P2 piece | 6 (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING) Repetitions | 2 (two planes (full zeros/ones) indicating how often the board positions has occurred) En-passant square | 1 (Binary map indicating the square where en-passant capture is possible) --- 15 planes * * * P1 castling | 2 (One if castling is possible, else zero) P2 castling | 2 (One if castling is possible, else zero) No-progress count | 1 (Setting the no progress counter as integer values, (described by uci halfmoves format) --- 5 planes * * * Last 8 moves | 16 (indicated by origin and destination square, the most recent move is described by first 2 planes) --- 16 planes * * * is960 = | 1 (boolean, 1 when active) --- 1 plane --- P1 pieces | 1 | A grouped mask of all WHITE pieces | P2 pieces | 1 | A grouped mask of all BLACK pieces | Checkerboard | 1 | A chess board pattern | P1 Material Diff | 5 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN), normalized with 8, + means positive, - means negative | Opposite Color Bishops | 1 | Indicates if they are only two bishops and the bishops are opposite color | Checkers | 1 | Indicates all pieces giving check | P1 Material Count | 5 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN), normalized with 8 | --- 15 planes The total number of planes is calculated as follows: # -------------- 15 + 5 + 16 + 1 + 15 Total: 52 planes :param board: Board handle (Python-chess object) :param board_occ: Number of board occurences :param normalize: True if the inputs shall be normalized to the range [0.-1.] :param last_moves: List of last last moves. The most recent move is the first entry. :return: planes - the plane representation of the current board state """ # return the plane representation of the given board # return variants.board_to_planes(board, board_occ, normalize, mode=MODE_CHESS) planes = np.zeros((NB_CHANNELS_TOTAL, BOARD_HEIGHT, BOARD_WIDTH)) # channel will be incremented by 1 at first plane channel = 0 me = board.turn you = not board.turn colors = [me, you] # mirror all bitboard entries for the black player mirror = board.turn == chess.BLACK assert channel == CHANNEL_PIECES # Fill in the piece positions # Channel: 0 - 11 # Iterate over both color starting with WHITE for color in colors: # the PIECE_TYPE is an integer list in python-chess for piece_type in chess.PIECE_TYPES: # iterate over the piece mask and receive every position square of it for pos in board.pieces(piece_type, color): row, col = get_row_col(pos, mirror=mirror) # set the bit at the right position planes[channel, row, col] = 1 channel += 1 assert channel == CHANNEL_REPETITION # Channel: 12 - 13 # set how often the position has already occurred in the game (default 0 times) # this is used to check for claiming the 3 fold repetition rule if board_occ >= 1: planes[channel, :, :] = 1 if board_occ >= 2: planes[channel + 1, :, :] = 1 channel += 2 # Channel: 14 # En Passant Square assert channel == CHANNEL_EN_PASSANT if board.ep_square and board.has_legal_en_passant(): # is not None: row, col = get_row_col(board.ep_square, mirror=mirror) planes[channel, row, col] = 1 channel += 1 # Channel: 15 - 18 assert channel == CHANNEL_CASTLING for color in colors: # check for King Side Castling if board.has_kingside_castling_rights(color): planes[channel, :, :] = 1 channel += 1 # check for Queen Side Castling if board.has_queenside_castling_rights(color): planes[channel, :, :] = 1 channel += 1 # Channel: 19 # (IV.4) No Progress Count # define a no 'progress' counter # it gets incremented by 1 each move # however, whenever a piece gets dropped, a piece is captured or a pawn is moved, it is reset to 0 # halfmove_clock is an official metric in fen notation # -> see: https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation # check how often the position has already occurred in the game assert channel == CHANNEL_NO_PROGRESS planes[ channel, :, :] = board.halfmove_clock / NORMALIZE_50_MOVE_RULE if normalize else board.halfmove_clock channel += 1 # Channel: 20 - 35 assert channel == CHANNEL_LAST_MOVES # Last 8 moves if last_moves: assert (len(last_moves) == NB_LAST_MOVES) for move in last_moves: if move: from_row, from_col = get_row_col(move.from_square, mirror=mirror) to_row, to_col = get_row_col(move.to_square, mirror=mirror) planes[channel, from_row, from_col] = 1 channel += 1 planes[channel, to_row, to_col] = 1 channel += 1 else: channel += 2 else: channel += NB_LAST_MOVES * NB_CHANNELS_PER_HISTORY_ITEM # Channel: 36 # Chess960 assert channel == CHANNEL_IS_960 if board.chess960: planes[channel + 1, :, :] = 1 channel += 1 # Channel: 37 - 38 # All white pieces and black pieces in a single map assert channel == CHANNEL_PIECE_MASK for color in colors: # the PIECE_TYPE is an integer list in python-chess for piece_type in chess.PIECE_TYPES: # iterate over the piece mask and receive every position square of it for pos in board.pieces(piece_type, color): row, col = get_row_col(pos, mirror=mirror) # set the bit at the right position planes[channel, row, col] = 1 channel += 1 # Channel: 39 # Checkerboard assert (channel == CHANNEL_CHECKERBOARD) planes[channel, :, :] = checkerboard() channel += 1 # Channel: 40 - 44 # Relative material difference (negative if less pieces than opponent and positive if more) # iterate over all pieces except the king assert channel == CHANNEL_MATERIAL_DIFF for piece_type in chess.PIECE_TYPES[:-1]: material_count = len(board.pieces(piece_type, me)) - len( board.pieces(piece_type, you)) planes[ channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count channel += 1 # Channel: 45 # Opposite color bishops assert (channel == CHANNEL_OPP_BISHOPS) if opposite_colored_bishops(board): planes[channel, :, :] = 1 channel += 1 # Channel: 46 # Checkers assert channel == CHANNEL_CHECKERS board_checkers = checkers(board) if board_checkers: # iterate over the piece mask and receive every position square of it for pos in chess.SquareSet(board_checkers): row, col = get_row_col(pos, mirror=mirror) # set the bit at the right position planes[channel, row, col] = 1 channel += 1 # Channel: 47 - 51 # Material assert channel == CHANNEL_MATERIAL_COUNT for piece_type in chess.PIECE_TYPES[:-1]: material_count = len(board.pieces(piece_type, me)) planes[ channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count channel += 1 assert channel == NB_CHANNELS_TOTAL return planes