def get_movement_vector(move: chess.Move):
    """
    Returns a vector representation for a chess-move.
    The first coordinate describes the row offset and the second the column offset.
    :param move: Chess move object
    :return: Offset of the move [y-offset, x-offset]
    """
    from_row, from_col = get_row_col(move.from_square)
    to_row, to_col = get_row_col(move.to_square)
    return [to_row - from_row, to_col - from_col]
def get_plane_index_from_move(move) -> [int, int, int]:
    """
    Computes the index in the move policy matrix for a given move.

    Queen moves  | 56     ->  0..55
    Knight moves | 8      -> 56..63
    Promotions   | 12     -> 64..75
    Drop         | 5      -> 76..80
    ----------------------------
    Total 81
    :param move: Chess move object
    :return: Plane index

    """

    if move.drop:
        # dropping a piece
        piece_type = move.drop
        to_row, to_col = get_row_col(move.to_square)
        board_offset = get_plane_index_drop_move(piece_type)
        return board_offset, to_row, to_col
    if move.promotion:
        piece_type = move.promotion
        movement_vector = get_movement_vector(move)
        # a pawn can only move forward or capture to the left or right
        # => we only have to inspect the x axis
        # python-chess starts counting at 1
        from_row, from_col = get_row_col(move.from_square)
        board_offset = get_plane_index_promotion_move(piece_type, movement_vector)
        return board_offset, from_row, from_col

    # normal move
    movement_vector = get_movement_vector(move)
    from_row, from_col = get_row_col(move.from_square)

    absolute_movement_vector = np.abs(movement_vector)
    # a knight move can be identified by its special movement behaviour
    # only the knight has a '1' and a '2' in its movement vector
    is_knight_move = (min(absolute_movement_vector) == 1) and (max(absolute_movement_vector) == 2)
    if is_knight_move:
        board_offset = get_plane_index_knight_move(movement_vector)
        return board_offset, from_row, from_col
    move_type_offset = 0
    movement_offset = get_plane_index_queen_move(movement_vector)
    board_offset = move_type_offset + movement_offset
    return board_offset, from_row, from_col
Пример #3
0
def _fill_position_planes(planes_pos,
                          board,
                          board_occ=0,
                          mode=MODE_CRAZYHOUSE):

    # Fill in the piece positions

    # Iterate over both color starting with WHITE
    for idx, color in enumerate(chess.COLORS):
        # the PIECE_TYPE is an integer list in python-chess
        for piece_type in chess.PIECE_TYPES:
            # define the channel by the piece_type (the input representation uses the same ordering as python-chess)
            # we add an offset for the black pieces
            # note that we subtract 1 because in python chess the PAWN has index 1 and not 0
            channel = (piece_type - 1) + idx * len(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)
                # set the bit at the right position
                planes_pos[channel, row, col] = 1

    # (II) Fill in the Repetition Data
    # a game to test out if everything is working correctly is: https://lichess.org/jkItXBWy#73
    channel = CHANNEL_MAPPING_POS["repetitions"]

    # 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_pos[channel, :, :] = 1
        if board_occ >= 2:
            planes_pos[channel + 1, :, :] = 1

    # Fill in the Prisoners / Pocket Pieces
    if 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

            planes_pos[channel, :, :] = board.pockets[chess.WHITE].count(
                p_type)
            # the prison for black begins 5 channels later
            planes_pos[channel +
                       5, :, :] = board.pockets[chess.BLACK].count(p_type)

    # (III) Fill in the promoted pieces
    # iterate over all promoted pieces according to the mask and set the according bit
    if mode == MODE_CRAZYHOUSE or mode == MODE_LICHESS:
        channel = CHANNEL_MAPPING_POS["promo"]
        for pos in chess.SquareSet(board.promoted):
            row, col = get_row_col(pos)

            if board.piece_at(pos).color == chess.WHITE:
                planes_pos[channel, row, col] = 1
            else:
                planes_pos[channel + 1, row, col] = 1

    # (III.2) En Passant Square
    # mark the square where an en-passant capture is possible
    channel = CHANNEL_MAPPING_POS["ep_square"]
    if board.ep_square is not None:
        row, col = get_row_col(board.ep_square)
        planes_pos[channel, row, col] = 1

    return planes_pos
Пример #4
0
def board_to_planes(board,
                    board_occ=0,
                    normalize=True,
                    mode=MODE_CRAZYHOUSE,
                    last_moves=None):
    """
    Gets the plane representation of a given board state.
    (No history of past board positions is used.)

    :param board: Board handle (Python-chess object)
    :param board_occ: Sets how often the board state has occurred before (by default 0)
    :param normalize: True if the inputs shall be normalized to the 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)
    :param last_moves: List of last moves. It is assumed that the most recent move is the first entry !
    :return: planes - the plane representation of the current board state
    """

    # TODO: Remove board.mirror() for black by addressing the according color channel

    # (I) Define the Input Representation for one position
    planes_pos = np.zeros((NB_CHANNELS_POS, BOARD_HEIGHT, BOARD_WIDTH))
    planes_const = np.zeros((NB_CHANNELS_CONST, BOARD_HEIGHT, BOARD_WIDTH))

    if mode == MODE_CRAZYHOUSE:
        # crazyhouse only doesn't contain remaining checks
        planes_variants = False
    elif mode == MODE_LICHESS:
        planes_variants = np.zeros(
            (NB_CHANNELS_VARIANTS, BOARD_HEIGHT, BOARD_WIDTH))
    else:  # mode = MODE_CHESS
        # chess doesn't contain pocket pieces and remaining checks
        planes_variants = np.zeros(
            (NB_CHANNELS_VARIANTS, BOARD_HEIGHT, BOARD_WIDTH))

    # save whose turn it is
    board_turn = chess.WHITE

    # check who's player turn it is and flip the board if it's black turn
    if board.turn == chess.BLACK:
        board_turn = chess.BLACK
        board = board.mirror()

    _fill_position_planes(planes_pos, board, board_occ, mode)
    _fill_constant_planes(planes_const, board, board_turn)
    if mode == MODE_LICHESS:
        _fill_variants_plane(board, planes_variants)
    elif mode == MODE_CHESS:
        if board.chess960 is True:
            planes_variants[:, :, :] = 1

    # create the move planes
    planes_moves = np.zeros((NB_CHANNELS_HISTORY, BOARD_HEIGHT, BOARD_WIDTH))
    if last_moves:
        for i, move in enumerate(last_moves):
            if move:
                from_row, from_col = get_row_col(
                    move.from_square, mirror=board_turn == chess.BLACK)
                to_row, to_col = get_row_col(move.to_square,
                                             mirror=board_turn == chess.BLACK)
                planes_moves[i * 2, from_row, from_col] = 1
                planes_moves[i * 2 + 1, to_row, to_col] = 1

    # (VI) Merge the Matrix-Stack
    if mode == MODE_CRAZYHOUSE:
        planes = np.concatenate((planes_pos, planes_const), axis=0)
    else:  # mode = MODE_LICHESS | mode == MODE_CHESS
        planes = np.concatenate(
            (planes_pos, planes_const, planes_variants, planes_moves), axis=0)

    # revert the board if the players turn was black
    # ! DO NOT DELETE OR UNCOMMENT THIS BLOCK BECAUSE THE PARAMETER board IS CHANGED IN PLACE !
    if board_turn == chess.BLACK:
        board = board.mirror()

    if normalize is True:
        planes *= MATRIX_NORMALIZER
        # planes = normalize_input_planes(planes)

    # return the plane representation of the given board
    return planes
Пример #5
0
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
Пример #6
0
def board_to_planes(board, board_occ=0, normalize=True):
    """
    Gets the plane representation of a given board state.
    (Now history of past board positions is used.)

    ## Crazyhouse:

    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)

    P1 prisoner count | 5 (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN) (excluding the KING)

    P2 prisoner count | 5 (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN) (excluding the KING)

    P1 Promoted Pawns Mask | 1 (binary map indicating the pieces which have been promoted)

    P2 Promoted Pawns Mask | 1 (binary map indicating the pieces which have been promoted)

    En-passant square | 1 (Binary map indicating the square where en-passant capture is possible)

    ---
    27 planes

    * * *

    Colour | 1 (all zeros for black and all ones for white)

    Total move count | 1 (integer value setting the move count (uci notation))

    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)

    # --------------
    7 planes

    The history list of the past 7 board states have been removed
    The total number of planes is calculated as follows:

    27 + 7
    Total: 34 planes

    :param board: Board handle (Python-chess object)
    :param board_occ: Sets how often the board state has occurred before (by default 0)
    :param normalize: True if the inputs shall be normalized to the range [0.-1.]
    :return: planes - the plane representation of the current board state
    """

    # TODO: Remove board.mirror() for black by addressing the according color channel

    # (I) Define the Input Representation for one position
    planes_pos = np.zeros((NB_CHANNELS_POS, BOARD_HEIGHT, BOARD_WIDTH))
    planes_const = np.zeros((NB_CHANNELS_CONST, BOARD_HEIGHT, BOARD_WIDTH))

    # save whose turn it is
    board_turn = chess.WHITE

    # check who's player turn it is and flip the board if it's black turn
    if board.turn == chess.BLACK:
        board_turn = chess.BLACK
        board = board.mirror()

    # Fill in the piece positions

    # Iterate over both color starting with WHITE
    for z, color in enumerate(chess.COLORS):
        # the PIECE_TYPE is an integer list in python-chess
        for piece_type in chess.PIECE_TYPES:
            # define the channel by the piecetype (the input representation uses the same ordering as python-chess)
            # we add an offset for the black pieces
            # note that we subtract 1 because in python chess the PAWN has index 1 and not 0
            channel = (piece_type - 1) + z * len(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)
                # set the bit at the right position
                planes_pos[channel, row, col] = 1

    # (II) Fill in the Repetition Data
    # a game to test out if everything is working correctly is: https://lichess.org/jkItXBWy#73
    ch = CHANNEL_MAPPING_POS["repetitions"]

    # 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_pos[ch, :, :] = 1
        if board_occ >= 2:
            planes_pos[ch + 1, :, :] = 1

    # 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

        planes_pos[ch, :, :] = board.pockets[chess.WHITE].count(p_type)
        # the prison for black begins 5 channels later
        planes_pos[ch + 5, :, :] = board.pockets[chess.BLACK].count(p_type)

    # (III) Fill in the promoted pieces
    # iterate over all promoted pieces according to the mask and set the according bit
    ch = CHANNEL_MAPPING_POS["promo"]
    for pos in chess.SquareSet(board.promoted):
        row, col = get_row_col(pos)

        if board.piece_at(pos).color == chess.WHITE:
            planes_pos[ch, row, col] = 1
        else:
            planes_pos[ch + 1, row, col] = 1

    # (III.2) En Passant Square
    # mark the square where an en-passant capture is possible
    ch = CHANNEL_MAPPING_POS["ep_square"]
    if board.ep_square is not None:
        row, col = get_row_col(board.ep_square)
        planes_pos[ch, row, col] = 1

    # (IV) Constant Value Inputs
    # (IV.1) Color
    if board_turn == chess.WHITE:
        planes_const[CHANNEL_MAPPING_CONST["color"], :, :] = 1
    # otherwise the mat will remain zero

    # (IV.2) Total Move Count
    planes_const[
        CHANNEL_MAPPING_CONST["total_mv_cnt"], :, :] = board.fullmove_number
    # alternatively, you could use the half-moves-counter: len(board.move_stack)

    # (IV.3) Castling Rights
    ch = CHANNEL_MAPPING_CONST["castling"]

    # WHITE
    # check for King Side Castling
    if bool(board.castling_rights & chess.BB_H1) is True:
        # White can castle with the h1 rook
        planes_const[ch, :, :] = 1
    # check for Queen Side Castling
    if bool(board.castling_rights & chess.BB_A1) is True:
        planes_const[ch + 1, :, :] = 1

    # BLACK
    # check for King Side Castling
    if bool(board.castling_rights & chess.BB_H8) is True:
        # White can castle with the h1 rook
        planes_const[ch + 2, :, :] = 1
    # check for Queen Side Castling
    if bool(board.castling_rights & chess.BB_A8) is True:
        planes_const[ch + 3, :, :] = 1

    # (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
    no_progress_cnt = board.halfmove_clock

    # check how often the position has already occurred in the game
    planes_const[
        CHANNEL_MAPPING_CONST["no_progress_cnt"], :, :] = no_progress_cnt

    # (V) Merge the Matrix-Stack
    planes = np.concatenate((planes_pos, planes_const), axis=0)

    # revert the board if the players turn was black
    # ! DO NOT DELETE OR UNCOMMENT THIS BLOCK BECAUSE THE PARAMETER board IS CHANGED IN PLACE !
    if board_turn == chess.BLACK:
        board = board.mirror()

    if normalize is True:
        planes *= MATRIX_NORMALIZER
        # planes = normalize_input_planes(planes)

    # return the plane representation of the given board
    return planes
Пример #7
0
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