def __generate_chess_moves_tree(self, parent_node, chess_board: chess.Board, depth): player_color = 'black' if parent_node['player_color'] == 'white' else 'white' if chess_board.legal_moves.count == 0: ## parent'ın yapabilecegi hamle yok demek ## parent final node demek, o yüzden bu node icin gidip value hesaplanacak board_fen = parent_node['board_fen'] piece_map = chess_board.piece_map() value = self.__evaluate_chess_board(board_fen, chess_board.is_check(), parent_node['player_color'], piece_map) parent_node['value'] = value if depth == self.__max_depth: ## buna benzer bir islemi eger herhangi bir legal moves yoksa da yapmak lazim ... for possible_move in list(chess_board.legal_moves): chess_board.push(possible_move) board_fen = chess_board.board_fen() piece_map = chess_board.piece_map() child = self.__create_node(possible_move, board_fen, player_color, depth) parent_node['children'].append(child) # her bir child icin value hesapla value = self.__evaluate_chess_board(board_fen, chess_board.is_check(), player_color, piece_map) child['value'] = value chess_board.pop() return for possible_move in list(chess_board.legal_moves): chess_board.push(possible_move) board_fen = chess_board.board_fen() child = self.__create_node(possible_move, board_fen, player_color, depth) parent_node['children'].append(child) self.__generate_chess_moves_tree(child, chess_board, depth + 1) chess_board.pop()
def evaluate_board(board: chess.Board, depth: int, alpha: int, beta: int): # return if max depth reached if depth == 0: evaluation = evaluate_position(board) return evaluation if board.is_repetition(3): return 0 moves = board.legal_moves if moves.count() == 0: if board.is_check(): return get_negative_infinity() return 0 for move in moves: board.push(move) move_evaluation = -evaluate_board(board, depth - 1, -beta, -alpha) board.pop() #print(f'Depth: {depth}, Move: {move}, Evaluation: {move_evaluation}, Beta: {beta}, Alpha: {alpha}') if move_evaluation >= beta: return beta if move_evaluation > alpha: alpha = move_evaluation return alpha
def make_move(board: chess.Board, time_remaining: float) -> str: """ `board` gives the current board state from which you should make your move. See https://python-chess.readthedocs.io/en/v1.4.0/ for full documentation, or below for some example usage. `time_remaining` gives the number of seconds left on your chess clock. You should return a uci-formatted move representing the move that your AI wishes to make. For example, to move your pawn from e2 to e4, you should return the string 'e2e4'. If you make an invalid move, or if your chess clock times out, you forfeit the game. Note that you are passed a copy of the board object, so methods such as `board.reset()` will not change the master copy, only your local version. """ # Get some interesting information playing_white = board.turn == chess.WHITE legal_moves = list(board.legal_moves) am_i_in_check = board.is_check() # TODO: Figure out some clever logic to make a good move move = random.choice(legal_moves) # Return the code of the move return move.uci()
def render_board(board: chess.Board) -> BytesIO: boardimg = chess.svg.board( board=board, lastmove=board.peek() if board.move_stack else None, check=board.king(turn) if board.is_check() or board.is_checkmate() else None, flipped=board.turn == chess.BLACK) res = BytesIO() svg2png(bytestring=boardimg, write_to=res) res.seek(0) return res
def evaluate_game_state(board: chess.Board, possible: bool) -> float: if board.is_checkmate(): if possible: return Macros.BOARD_POSSIBLE_CHECKMATE_VALUE return Macros.BOARD_CHECKMATE_VALUE if board.is_stalemate() or board.is_insufficient_material(): return Macros.BOARD_STALEMATE_VALUE if board.is_check(): if not possible: return Macros.BOARD_CHECK_VALUE return 0
def to_png(board: chess.Board, size: int = 400) -> discord.File: args = {"size": size} try: lastmove = board.peek() args["lastmove"] = lastmove if board.is_check(): args["check"] = board.king(board.turn) except IndexError: pass svg_image = chess.svg.board(board, **args) png = cairosvg.svg2png(bytestring=svg_image.encode("UTF-8")) return discord.File(io.BytesIO(png), filename="board.png")
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 is_trapped(board: Board, square: Square) -> bool: if board.is_check() or board.is_pinned(board.turn, square): return False piece = board.piece_at(square) if piece.piece_type in [PAWN, KING]: return False if not is_in_bad_spot(board, square): return False for escape in board.legal_moves: if escape.from_square == square: capturing = board.piece_at(escape.to_square) if capturing and values[capturing.piece_type] >= values[piece.piece_type]: return False board.push(escape) if not is_in_bad_spot(board, escape.to_square): return False board.pop() return True
def evaluate(board: chess.Board): ENEMY = {chess.WHITE: chess.BLACK, chess.BLACK: chess.WHITE} score = 0 if board.result() == "1-0" or board.result() == "0-1": score += 9999 if board.is_check(): score += 10 for sq in chess.SQUARES: score += len(board.attackers(board.turn, sq)) * VALUE[board.piece_type_at(sq)] score -= len(board.attackers(ENEMY[board.turn], sq)) * VALUE[board.piece_type_at(sq)] return score
def evaluate(board: chess.Board, evaluator: Callable[[float, float, float], float]): # for every piece that is still alive award player 100 points result = board.legal_moves.count() * 100 # being in check usually means we are going the wrong route if board.is_check(): result -= 1000 if board.has_insufficient_material(chess.WHITE): result -= 1000 # add material advantage that white has result += diff_pieces(board) # if on white then return as normal since white Maximizes if board.turn is chess.WHITE: return result # if black then negate for minimization else: return -result
def evaluate(self, board: chess.Board, player: bool): turn = board.turn if board.is_checkmate(): return -10000 if turn == player else 10000 if board.is_check(): return -5000 if turn == player else 5000 material_sum = 0 weights = { 'p': 100, 'n': 320, 'b': 330, 'r': 500, 'q': 900, 'k': 20000 } for i in range(64): piece = board.piece_at(i) if piece: color_weight = 1 if piece.color == player else -1 material_sum += weights[piece.symbol().lower()] * color_weight return material_sum
def evaluateFuzzy(board: chess.Board, evaluator: Callable[[float, float], float]): player = chess.WHITE if board.turn == True else chess.BLACK opponent = chess.WHITE if player == chess.BLACK else chess.BLACK total = 0 # being in check usually means we are going the wrong route if board.is_check(): total -= 1000 if board.has_insufficient_material(player): total -= 1000 if board.has_insufficient_material(opponent): total += 1000 num_moves_by_piece = {} for move in board.legal_moves: if move.from_square in num_moves_by_piece: num_moves_by_piece[move.from_square] += 1 else: num_moves_by_piece[move.from_square] = 1 for i in range(1, 7): piece_type = board.pieces(i, player) num_attacking = 0 for piece in piece_type: movable_spaces = num_moves_by_piece[ piece] if piece in num_moves_by_piece else 0 for spot in board.attacks(piece): attacked_player = board.color_at(spot) if attacked_player == opponent: num_attacking += 1 total += i + evaluator(movable_spaces, num_attacking) return total
def say_last_move(game: chess.Board): """Take a chess.BitBoard instance and speaks the last move from it.""" move_parts = { 'K': 'king.ogg', 'B': 'bishop.ogg', 'N': 'knight.ogg', 'R': 'rook.ogg', 'Q': 'queen.ogg', 'P': 'pawn.ogg', '+': '', '#': '', 'x': 'takes.ogg', '=': 'promote.ogg', 'a': 'a.ogg', 'b': 'b.ogg', 'c': 'c.ogg', 'd': 'd.ogg', 'e': 'e.ogg', 'f': 'f.ogg', 'g': 'g.ogg', 'h': 'h.ogg', '1': '1.ogg', '2': '2.ogg', '3': '3.ogg', '4': '4.ogg', '5': '5.ogg', '6': '6.ogg', '7': '7.ogg', '8': '8.ogg' } bit_board = game.copy() move = bit_board.pop() san_move = bit_board.san(move) voice_parts = [] if san_move.startswith('O-O-O'): voice_parts += ['castlequeenside.ogg'] elif san_move.startswith('O-O'): voice_parts += ['castlekingside.ogg'] else: for part in san_move: try: sound_file = move_parts[part] except KeyError: logging.warning('unknown char found in san: [%s : %s]', san_move, part) sound_file = '' if sound_file: voice_parts += [sound_file] if game.is_game_over(): if game.is_checkmate(): wins = 'whitewins.ogg' if game.turn == chess.BLACK else 'blackwins.ogg' voice_parts += ['checkmate.ogg', wins] elif game.is_stalemate(): voice_parts += ['stalemate.ogg'] else: if game.is_seventyfive_moves(): voice_parts += ['75moves.ogg', 'draw.ogg'] elif game.is_insufficient_material(): voice_parts += ['material.ogg', 'draw.ogg'] elif game.is_fivefold_repetition(): voice_parts += ['repetition.ogg', 'draw.ogg'] else: voice_parts += ['draw.ogg'] elif game.is_check(): voice_parts += ['check.ogg'] if bit_board.is_en_passant(move): voice_parts += ['enpassant.ogg'] return voice_parts
def say_last_move(game: chess.Board): """Take a chess.BitBoard instance and speaks the last move from it.""" PicoTalkerDisplay.c_taken = False #molli PicoTalkerDisplay.c_castle = False #molli PicoTalkerDisplay.c_knight = False #molli PicoTalkerDisplay.c_rook = False #molli PicoTalkerDisplay.c_king = False #molli PicoTalkerDisplay.c_bishop = False #molli PicoTalkerDisplay.c_pawn = False #molli PicoTalkerDisplay.c_queen = False #molli PicoTalkerDisplay.c_check = False #molli PicoTalkerDisplay.c_mate = False #molli PicoTalkerDisplay.c_stalemate = False #molli PicoTalkerDisplay.c_draw = False #molli move_parts = { 'K': 'king.ogg', 'B': 'bishop.ogg', 'N': 'knight.ogg', 'R': 'rook.ogg', 'Q': 'queen.ogg', 'P': 'pawn.ogg', '+': '', '#': '', 'x': 'takes.ogg', '=': 'promote.ogg', 'a': 'a.ogg', 'b': 'b.ogg', 'c': 'c.ogg', 'd': 'd.ogg', 'e': 'e.ogg', 'f': 'f.ogg', 'g': 'g.ogg', 'h': 'h.ogg', '1': '1.ogg', '2': '2.ogg', '3': '3.ogg', '4': '4.ogg', '5': '5.ogg', '6': '6.ogg', '7': '7.ogg', '8': '8.ogg' } bit_board = game.copy() move = bit_board.pop() san_move = bit_board.san(move) voice_parts = [] if san_move.startswith('O-O-O'): voice_parts += ['castlequeenside.ogg'] PicoTalkerDisplay.c_castle = True elif san_move.startswith('O-O'): voice_parts += ['castlekingside.ogg'] PicoTalkerDisplay.c_castle = True else: for part in san_move: try: sound_file = move_parts[part] except KeyError: logging.warning('unknown char found in san: [%s : %s]', san_move, part) sound_file = '' if sound_file: voice_parts += [sound_file] if sound_file == 'takes.ogg': PicoTalkerDisplay.c_taken = True #molli elif sound_file == 'knight.ogg': PicoTalkerDisplay.c_knight = True #molli elif sound_file == 'king.ogg': PicoTalkerDisplay.c_king = True #molli elif sound_file == 'rook.ogg': PicoTalkerDisplay.c_rook = True #molli elif sound_file == 'pawn.ogg': PicoTalkerDisplay.c_pawn = True #molli elif sound_file == 'bishop.ogg': PicoTalkerDisplay.c_bishop = True #molli elif sound_file == 'queen.ogg': PicoTalkerDisplay.c_queen = True #molli if game.is_game_over(): if game.is_checkmate(): wins = 'whitewins.ogg' if game.turn == chess.BLACK else 'blackwins.ogg' voice_parts += ['checkmate.ogg', wins] PicoTalkerDisplay.c_mate = True elif game.is_stalemate(): voice_parts += ['stalemate.ogg'] PicoTalkerDisplay.c_stalemate = True else: PicoTalkerDisplay.c_draw = True if game.is_seventyfive_moves(): voice_parts += ['75moves.ogg', 'draw.ogg'] elif game.is_insufficient_material(): voice_parts += ['material.ogg', 'draw.ogg'] elif game.is_fivefold_repetition(): voice_parts += ['repetition.ogg', 'draw.ogg'] else: voice_parts += ['draw.ogg'] elif game.is_check(): voice_parts += ['check.ogg'] PicoTalkerDisplay.c_check = True if bit_board.is_en_passant(move): voice_parts += ['enpassant.ogg'] return voice_parts
def featureEngineering(id, game, game_collection): counter = 0 update_dict = {} first_check = True first_queen_move = True features_dict = defaultdict(int) # { # 'promotion': 0, # 'total_checks': 0, # 'is_checkmate': 0, # 'is_stalemate': 0, # 'insufficient_material': 0, # 'can_claim_draw': 0 # } board = None for moveDBObject in game['moves']: # print(f"\tUpdating {move['uci']} -> {move['turn']}") board = Board(moveDBObject['fen']) uci = moveDBObject['uci'] move = Move.from_uci(uci) moved_piece = board.piece_at(move.from_square) captured_piece = board.piece_at(move.to_square) if moved_piece == QUEEN and first_queen_move: features_dict['queen_moved_at'] = board.fullmove_number first_queen_move = False if captured_piece == QUEEN: features_dict['queen_changed_at'] = board.fullmove_number if move.promotion: features_dict['promotion'] += 1 if board.is_check(): features_dict['total_checks'] += 1 if first_check: features_dict['first_check_at'] = board.fullmove_number first_check = False # castling if uci == 'e1g1': features_dict['white_king_castle'] = board.fullmove_number elif uci == 'e1c1': features_dict['white_queen_castle'] = board.fullmove_number elif uci == 'e8g8': features_dict['black_king_castle'] = board.fullmove_number elif uci == 'e8c8': features_dict['black_queen_castle'] = board.fullmove_number # update_dict[f"moves.{counter}.turn"] = counter % 2 == 0 counter += 1 if counter > 0: if board.is_checkmate(): features_dict['is_checkmate'] += 1 if board.is_stalemate(): features_dict['is_stalemate'] += 1 if board.is_insufficient_material(): features_dict['insufficient_material'] += 1 if board.can_claim_draw(): features_dict['can_claim_draw'] += 1 features_dict['total_moves'] = board.fullmove_number piece_placement = board.fen().split()[0] end_pieces = Counter(x for x in piece_placement if x.isalpha()) # count number of piece at end position features_dict.update( {'end_' + piece: cnt for piece, cnt in end_pieces.items()}) # print(features_dict) game_collection.update_one({"_id": ObjectId(id)}, {"$set": features_dict}, upsert=False)
class Viridithas(): def __init__( self, human: bool = False, fen: str = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', pgn: str = '', time_limit: float = 15, fun: bool = False, contempt: int = 3000, book: bool = True, advancedTC: list = [], ): if pgn == '': self.node = Board(fen) else: self.node = Board() for move in pgn.split(): try: self.node.push_san(move) except Exception: continue self.time_limit = time_limit if advancedTC: self.endpoint = time.time()+advancedTC[0]*60 self.increment = advancedTC[1] else: self.endpoint = 0 self.increment = 0 self.fun = fun self.contempt = contempt self.human = human self.nodes = 0 self.advancedTC = advancedTC self.hashtable: dict[Hashable, TTEntry] = dict() self.inbook = book def set_position(self, fen): self.node = Board(fen) def __repr__(self) -> str: return str(self.node) + '\n' + self.__class__.__name__+"-engine at position " + str(self.node.fen()) def __str__(self) -> str: return self.__class__.__name__ def user_setup(self): if input("Do you want to configure the bot? (Y/N) ").upper() != 'Y': return myname = self.__class__.__name__.upper() print(f"BEGINNING USER CONFIGURATION OF {myname}-BOT") datadict = get_engine_parameters() self.__init__( human=datadict["human"], fen=datadict["fen"], pgn=datadict["pgn"], time_limit=datadict["time_limit"], fun=datadict["fun"], contempt=datadict["contempt"], book=datadict["book"], advancedTC=datadict["advancedTC"] ) def gameover_check_info(self): checkmate = self.node.is_checkmate() draw = self.node.is_stalemate() or \ self.node.is_insufficient_material( ) or \ self.node.is_repetition(2) or self.node.is_seventyfive_moves() or not any(self.node.generate_legal_moves()) return checkmate or draw, checkmate, draw # @profile def evaluate(self, depth: float, checkmate: bool, draw: bool) -> float: self.nodes += 1 if checkmate: return MATE_VALUE * int(max(depth+1, 1)) * (1 if self.node.turn else -1) if draw: return -self.contempt * (1 if self.node.turn else -1) rating: float = 0 rating += pst_eval(self.node) # rating += see_eval(self.node) # rating += mobility(self.node) * MOBILITY_FACTOR # rating += piece_attack_counts(self.node) * ATTACK_FACTOR # rating += king_safety(self.node) * KING_SAFETY_FACTOR # rating += space(self.node) * SPACE_FACTOR return rating def single_hash_iterator(self, best): yield best def captures_piece(self, p): # concentrate on MVV, then LVA return itertools.chain( self.node.generate_pseudo_legal_moves(self.node.pawns, p), self.node.generate_pseudo_legal_moves(self.node.knights, p), self.node.generate_pseudo_legal_moves(self.node.bishops, p), self.node.generate_pseudo_legal_moves(self.node.rooks, p), self.node.generate_pseudo_legal_moves(self.node.queens, p), self.node.generate_pseudo_legal_moves(self.node.kings, p), ) #@profile def captures(self): # (MVV/LVA) return (m for m in itertools.chain( self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.queens), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.rooks), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.bishops), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.knights), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.pawns), ) if self.node.is_legal(m)) def winning_captures(self): # (MVV/LVA) target_all = self.node.occupied_co[not self.node.turn] target_3 = target_all & ~self.node.pawns target_5 = target_3 & (~self.node.bishops | ~self.node.knights) target_9 = target_5 & ~self.node.rooks return itertools.chain( self.node.generate_pseudo_legal_moves(self.node.pawns, target_all), self.node.generate_pseudo_legal_moves(self.node.knights, target_3), self.node.generate_pseudo_legal_moves(self.node.bishops, target_3), self.node.generate_pseudo_legal_moves(self.node.rooks, target_5), self.node.generate_pseudo_legal_moves(self.node.queens, target_9), self.node.generate_pseudo_legal_moves(self.node.kings, target_9), ) def losing_captures(self): # (MVV/LVA) target_pawns = self.node.pawns target_pnb = target_pawns | self.node.bishops | self.node.knights target_pnbr = target_pnb | self.node.rooks return itertools.chain( self.node.generate_pseudo_legal_moves(self.node.knights, target_pawns), self.node.generate_pseudo_legal_moves(self.node.bishops, target_pawns), self.node.generate_pseudo_legal_moves(self.node.rooks, target_pnb), self.node.generate_pseudo_legal_moves(self.node.queens, target_pnbr), self.node.generate_pseudo_legal_moves(self.node.kings, target_pnbr), ) def ordered_moves(self): return (m for m in itertools.chain( # self.winning_captures(), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.queens), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.rooks), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.bishops), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.knights), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.pawns), self.node.generate_pseudo_legal_moves( 0xffff_ffff_ffff_ffff, ~self.node.occupied_co[not self.node.turn]), # self.losing_captures() ) if self.node.is_legal(m)) def pass_turn(self) -> None: self.node.push(Move.from_uci("0000")) #@profile def qsearch(self, alpha: float, beta: float, depth: float, colour: int, gameover: bool, checkmate: bool, draw: bool) -> float: val = self.evaluate(1, checkmate, draw) * colour if gameover: return val if val >= beta: return beta if (val < alpha - QUEEN_VALUE): return alpha alpha = max(val, alpha) for capture in self.captures(): self.node.push(capture) gameover, checkmate, draw = self.gameover_check_info() score = -self.qsearch(-beta, -alpha, depth - 1, -colour, gameover, checkmate, draw) self.node.pop() if score >= beta: return score alpha = max(score, alpha) return alpha def tt_lookup(self, board: Board) -> "TTEntry": key = board._transposition_key() return self.hashtable.get(key, TTEntry.default()) def tt_store(self, board: Board, entry: TTEntry): key = board._transposition_key() self.hashtable[key] = entry #@profile def negamax(self, depth: float, colour: int, alpha: float, beta: float) -> float: initial_alpha = alpha # (* Transposition Table Lookup; self.node is the lookup key for ttEntry *) tt_entry = self.tt_lookup(self.node) if not tt_entry.is_null() and tt_entry.depth >= depth: if tt_entry.type == EXACT: return tt_entry.value elif tt_entry.type == LOWERBOUND: alpha = max(alpha, tt_entry.value) elif tt_entry.type == UPPERBOUND: beta = min(beta, tt_entry.value) if alpha >= beta: return tt_entry.value if self.node.is_legal(tt_entry.best): moves = itertools.chain([tt_entry.best], filter(lambda x: x != tt_entry.best, self.ordered_moves())) else: moves = self.ordered_moves() else: moves = self.ordered_moves() gameover, checkmate, draw = self.gameover_check_info() if gameover: return colour * self.evaluate(depth, checkmate, draw) if depth < 1: return self.qsearch(alpha, beta, depth, colour, gameover, checkmate, draw) current_pos_is_check = self.node.is_check() if not current_pos_is_check and depth >= 3 and abs(alpha) < MATE_VALUE and abs(beta) < MATE_VALUE: # MAKE A NULL MOVE self.node.push(Move.null()) # PERFORM A LIMITED SEARCH value = - self.negamax(depth - 3, -colour, -beta, -beta + 1) # UNMAKE NULL MOVE self.node.pop() if value >= beta: return beta do_prune = self.pruning(depth, colour, alpha, beta, current_pos_is_check) best_move = Move.null() search_pv = True value = float("-inf") for move_idx, move in enumerate(moves): if move_idx == 0: best_move = move gives_check = self.node.gives_check(move) is_capture = self.node.is_capture(move) is_promo = bool(move.promotion) depth_reduction = search_reduction_factor( move_idx, current_pos_is_check, gives_check, is_capture, is_promo, depth) if do_prune: if not gives_check and not is_capture: continue self.node.push(move) if search_pv: r = -self.negamax(depth - depth_reduction, -colour, -beta, -alpha) value = max(value, r) else: r = -self.negamax(depth - depth_reduction, -colour, -alpha-1, -alpha) if (r > alpha): # // in fail-soft ... & & value < beta) is common r = -self.negamax(depth - depth_reduction, -colour, -beta, -alpha) #// re-search value = max(value, r) self.node.pop() if value > alpha: alpha = value best_move = move alpha = max(alpha, value) if alpha >= beta: break search_pv = False # (* Transposition Table Store; self.node is the lookup key for ttEntry *) # ttEntry = TTEntry() tt_entry.value = value if value <= initial_alpha: tt_entry.type = UPPERBOUND elif value >= beta: tt_entry.type = LOWERBOUND else: tt_entry.type = EXACT tt_entry.depth = depth tt_entry.best = best_move tt_entry.null = False self.tt_store(self.node, tt_entry) return value def pruning(self, depth, colour, alpha, beta, current_pos_is_check): if not (not current_pos_is_check and abs( alpha) < MATE_VALUE / 2 and abs(beta) < MATE_VALUE / 2) or depth > 2: return False see = see_eval(self.node) * colour DO_D1_PRUNING = depth <= 1 and see + FUTILITY_MARGIN < alpha DO_D2_PRUNING = depth <= 2 and see + FUTILITY_MARGIN_2 < alpha return DO_D1_PRUNING or DO_D2_PRUNING def move_sort(self, moves: list, ratings: list): pairs = zip(*sorted(zip(moves, ratings), key=operator.itemgetter(1))) moves, ratings = [list(pair) for pair in pairs] return moves, ratings def pv_string(self): count = 0 moves = [] while True: e = self.tt_lookup(self.node) if e.is_null() or not self.node.is_legal(e.best): break # print(self.node.__str__()) # print(self.node.__repr__()) # print(e.best) # print(self.node.san(e.best)) moves.append(self.node.san(e.best)) self.node.push(e.best) count += 1 for _ in moves: self.node.pop() if count == 0: return "" return " ".join(moves) def turnmod(self) -> int: return -1 if self.node.turn else 1 def show_iteration_data(self, moves: list, values: list, depth: float, start: float) -> tuple: t = round(time.time()-start, 2) print(f"{self.node.san(moves[0])} | {-round((self.turnmod()*values[0])/1000, 3)} | {str(t)}s at depth {str(depth + 1)}, {str(self.nodes)} nodes processed, at {str(int(self.nodes / (t+0.00001)))}NPS.\n", f"PV: {self.pv_string()}\n", end="") return (self.node.san(moves[0]), self.turnmod()*values[0], self.nodes, depth+1, t) def search(self, ponder: bool = False, readout: bool = True): val = float("-inf") start_time = time.time() self.nodes = 0 moves = [next(self.ordered_moves())] saved_position = deepcopy(self.node) alpha, beta = float("-inf"), float("inf") valWINDOW = PAWN_VALUE / 4 WINDOW_FAILED = False try: depth = 1 while depth < 40: best = self.tt_lookup(self.node).best time_elapsed = time.time() - start_time # check if we aren't going to finish the next search in time if time_elapsed > 0.5 * self.time_limit and not ponder and not WINDOW_FAILED: return best, val val = self.negamax( depth, self.turnmod(), alpha=alpha, beta=beta) # print(val) if ((val <= alpha) or (val >= beta)): # We fell outside the window, so try again with a # full-width window (and the same depth). alpha = float("-inf") beta = float("inf") WINDOW_FAILED = True continue WINDOW_FAILED = False best = self.tt_lookup(self.node).best # check if we've run out of time if time_elapsed > self.time_limit and not ponder: return best, val moves = [self.tt_lookup(self.node).best] values = [self.tt_lookup(self.node).value] if readout: self.show_iteration_data(moves, values, depth, start_time) alpha = val - valWINDOW # Set up the window for the next iteration. beta = val + valWINDOW depth += 1 except KeyboardInterrupt: self.node = saved_position pass return moves[0], val def ponder(self) -> None: self.origin = self.node.copy() self.search(ponder=True) def get_book_move(self): # book = chess.polyglot.open_reader( # r"ProDeo292/ProDeo292/books/elo2500.bin") book = chess.polyglot.open_reader( r"books/elo2500.bin") main_entry = book.find(self.node) choice = book.weighted_choice(self.node) book.close() return main_entry.move, choice.move def engine_move(self) -> Move: # add flag_func for egtb mode if self.advancedTC: self.time_limit = (self.endpoint-time.time())/20 print("Time for move: " + str(round(self.time_limit, 2)) + "s") if self.inbook: try: best, choice = self.get_book_move() if self.fun: self.node.push(choice) return choice else: self.node.push(best) print(chess.pgn.Game.from_board(self.node)[-1]) except IndexError: self.time_limit = self.time_limit*2 best, _ = self.search() self.node.push(best) print(chess.pgn.Game.from_board(self.node)[-1]) self.inbook = False self.time_limit = self.time_limit/2 else: best, _ = self.search() self.node.push(best) print(chess.pgn.Game.from_board(self.node)[-1]) # self.record_stack() self.endpoint += self.increment return best def user_move(self) -> str: move = input("enter move: ") while True: try: self.node.push_san(move) break except Exception: move = input("enter move: ") return move def display_ending(self) -> None: if self.node.is_stalemate(): print('END BY STALEMATE') elif self.node.is_insufficient_material(): print('END BY INSUFFICIENT MATERIAL') elif self.node.is_fivefold_repetition(): print('END BY FIVEFOLD REPETITION') elif self.node.is_checkmate: print("BLACK" if self.node.turn == BLACK else "WHITE", 'WINS ON TURN', self.node.fullmove_number) else: print('END BY UNKNOWN REASON') def run_game(self, indefinite=True) -> str: while not self.node.is_game_over(): print(self.__repr__()) if self.human and self.node.turn: try: self.ponder() except KeyboardInterrupt: self.node = self.origin self.user_move() else: # SWAP THESE ASAP self.engine_move() if not indefinite: break self.display_ending() try: return str(chess.pgn.Game.from_board(self.node)[-1]) except Exception: return "PGN ERROR" def play_viri(self, fen=None): player_colour = input( "Enter the human player's colour in the form b/w\n--> ") while player_colour not in ['b', 'w']: player_colour = input( "Enter the human player's colour in the form b/w\n--> ") player_colour = WHITE if player_colour == 'w' else BLACK timeControl = int( input("how many seconds should viri get per move?\n--> ")) self.__init__(human=True, time_limit=timeControl, fen=fen, book=True, fun=False) self.fun = False while not self.node.is_game_over(): print(self.__repr__()) if player_colour == self.node.turn: # try: # self.ponder() # except KeyboardInterrupt: # self.node = self.origin # self.user_move() self.user_move() else: self.engine_move() self.display_ending() def perftx(self, n): if n == 0: self.nodes += 1 else: for move in self.ordered_moves(): self.node.push(move) self.perftx(n - 1) self.node.pop() def perft(self, n): self.nodes = 0 self.perftx(n) print(self.nodes) def uci(self): start = input() while start != "uci": start = input() print("id", end="") while True: command = input() if command == "ucinewgame": board = Board() elif command.split()[0] == "position": fen = command[command.index( "fen") + 3: command.index("moves") - 1] moves = command[command.index("moves"):].split() if fen == "startpos": fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' board = Board(fen) for move in moves: board.push(Move.from_uci(move))