def test_move_planes_round_trip_given_knight_move_expect_round_trip(self): board = CrazyhouseBoard() move = construct_move_from_positions([4, 4], [2, 3]) assert_round_trip(self, board, move, comment="knight move 0") # A move = construct_move_from_positions([4, 4], [3, 2]) assert_round_trip(self, board, move, comment="knight move 1") # B move = construct_move_from_positions([4, 4], [2, 5]) assert_round_trip(self, board, move, comment="knight move 2") # C move = construct_move_from_positions([4, 4], [3, 6]) assert_round_trip(self, board, move, comment="knight move 3") # D move = construct_move_from_positions([4, 4], [6, 3]) assert_round_trip(self, board, move, comment="knight move 4") # E move = construct_move_from_positions([4, 4], [5, 2]) assert_round_trip(self, board, move, comment="knight move 5") # F move = construct_move_from_positions([4, 4], [6, 5]) assert_round_trip(self, board, move, comment="knight move 2") # G move = construct_move_from_positions([4, 4], [5, 6]) assert_round_trip(self, board, move, comment="knight move 2") # H
def test_move_planes_round_trip_given_promotion_expect_round_trip(self): board = CrazyhouseBoard() move = construct_move_from_positions([6, 4], [7, 4], promotion=3) assert_round_trip(self, board, move, comment="underpromotion (straight)") move = construct_move_from_positions([6, 4], [7, 3], promotion=3) assert_round_trip(self, board, move, comment="underpromotion (left)") move = construct_move_from_positions([6, 4], [7, 5], promotion=3) assert_round_trip(self, board, move, comment="underpromotion (right)")
def __init__(self, board=CrazyhouseBoard()): _GameState.__init__(self, board) self.board = board self._fen_dic = {} self._board_occ = 0
def new_game(self): self.board = CrazyhouseBoard() self._fen_dic = {}
class GameState(_GameState): def __init__(self, board=CrazyhouseBoard()): _GameState.__init__(self, board) self.board = board self._fen_dic = {} self._board_occ = 0 def apply_move(self, move: chess.Move): # , remember_state=False): # apply the move on the board self.board.push(move) # if remember_state is True: # self._remember_board_state() def get_state_planes(self): return board_to_planes(self.board, board_occ=self._board_occ, normalize=True) # return np.random.random((34, 8, 8)) def get_pythonchess_board(self): return self.board def is_draw(self): # check if you can claim a draw - its assumed that the draw is always claimed return self.can_claim_threefold_repetition( ) or self.board.can_claim_fifty_moves() # return self.board.can_claim_draw() def can_claim_threefold_repetition(self): """ Custom implementation for threefold-repetition check which uses the board_occ variable. :return: True if claim is legal else False """ return self._board_occ >= 2 def is_won(self): # only a is_won() and no is_lost() function is needed because the game is over # after the player found checkmate successfully return self.board.is_checkmate() def get_legal_moves(self): # return list(self.board.legal_moves) legal_moves = [] for mv in self.board.generate_legal_moves(): legal_moves.append(mv) return legal_moves def is_white_to_move(self): return self.board.turn def __str__(self): return self.board.fen() def new_game(self): self.board = CrazyhouseBoard() self._fen_dic = {} def set_fen(self, fen, remember_state=True): self.board.set_fen(fen) # if remember_state is True: # self._remember_board_state() # def _remember_board_state(self): # calculate the transposition key # transposition_key = self.get_transposition_key() # update the number of board occurrences # self._board_occ = self._transposition_table[transposition_key] # increase the counter for this transposition key # self._transposition_table.update((transposition_key,)) def is_check(self): return self.board.is_check() def are_pocket_empty(self): """ Checks wether at least one player has a piece available in their pocket :return: """ return len(self.board.pockets[chess.WHITE]) == 0 and len( self.board.pockets[chess.BLACK]) == 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
def __init__(self): self.boards = { FIRST_BOARD: CrazyhouseBoard(), SECOND_BOARD: CrazyhouseBoard(), } self.reset()
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
def test_move_planes_round_trip_given_drop_expect_round_trip(self): board = CrazyhouseBoard() move = construct_move_from_positions([5, 4], [5, 4], drop=2) assert_round_trip(self, board, move, comment="dropping (bishop)")
def test_move_planes_round_trip_given_pawn_move_expect_round_trip(self): board = CrazyhouseBoard() move = construct_move_from_positions([1, 4], [2, 4]) assert_round_trip(self, board, move, comment="pawn move")
def new_game(self): """ Create a new board on the starting position""" self.board = CrazyhouseBoard() self._fen_dic = {}
class GameState(AbsGameState): """File to group everything to recognize the game state""" def __init__(self, board=CrazyhouseBoard()): AbsGameState.__init__(self, board) self.board = board self._fen_dic = {} self._board_occ = 0 def apply_move(self, move: chess.Move): # , remember_state=False): """ Apply the move on the board""" self.board.push(move) # if remember_state is True: # self._remember_board_state() def get_state_planes(self): """Transform the current board state to a plane""" return board_to_planes(self.board, board_occ=self._board_occ, normalize=True) # return np.random.random((34, 8, 8)) def get_pythonchess_board(self): """ Get the board by calling a method""" return self.board def is_draw(self): """ Check if you can claim a draw - its assumed that the draw is always claimed """ return self.can_claim_threefold_repetition( ) or self.board.can_claim_fifty_moves() # return self.board.can_claim_draw() def can_claim_threefold_repetition(self): """ Custom implementation for threefold-repetition check which uses the board_occ variable. :return: True if claim is legal else False """ return self._board_occ >= 2 def is_won(self): """ Check if you can claim the win by checkmate""" # only a is_won() and no is_lost() function is needed because the game is over return self.board.is_checkmate( ) # after the player found checkmate successfully def get_legal_moves(self): """ Returns the legal moves based on current board state""" return [*self.board.legal_moves ] # is same as list(self.board.legal_moves) # legal_moves = [] # for move in self.board.generate_legal_moves(): # legal_moves.append(move) # return legal_moves def is_white_to_move(self): """ Returns true if its whites turn""" return self.board.turn def __str__(self): return self.board.fen() def new_game(self): """ Create a new board on the starting position""" self.board = CrazyhouseBoard() self._fen_dic = {} def set_fen(self, fen): # , remember_state=True """ Returns the fen of the current state""" self.board.set_fen(fen) # if remember_state is True: # self._remember_board_state() # def _remember_board_state(self): # calculate the transposition key # transposition_key = self.get_transposition_key() # update the number of board occurrences # self._board_occ = self._transposition_table[transposition_key] # increase the counter for this transposition key # self._transposition_table.update((transposition_key,)) def is_check(self): """ Check if the king of the player of the turn is in check""" return self.board.is_check() def are_pocket_empty(self): """ Checks if at least one player has a piece available in their pocket """ return not self.board.pockets[chess.WHITE] and not self.board.pockets[ chess.BLACK]