From 65e1b7cf960cc26c078fe06f5ef2f413476580dc Mon Sep 17 00:00:00 2001 From: Avo-k Date: Tue, 6 Jul 2021 01:08:40 +0200 Subject: [PATCH] tapered eval --- constants.py | 64 ++++++++++--- evaluation.py | 243 +++++++++++++++++++++++++++++++++++++------------- lichess.py | 96 +++++++------------- search.py | 83 +++++++++++++---- 4 files changed, 329 insertions(+), 157 deletions(-) diff --git a/constants.py b/constants.py index 3c10e61..ef63563 100644 --- a/constants.py +++ b/constants.py @@ -72,6 +72,8 @@ mate_in_2 = "k7/6R1/2K5/8/8/8/8/8 w - - 16 9" mate_in_4 = "2k5/5R2/3K4/8/8/8/8/8 w - - 12 7" +FENS = (start_position, tricky_position, killer_position, cmk_position, repetitions_position, mate_in_2, mate_in_4) + # Mating bounds for mating scores BOUND_INF, UPPER_MATE, LOWER_MATE = 50000, 49000, 48000 @@ -92,6 +94,9 @@ full_depth_moves = 4 reduction_limit = 3 +# Time +time_precision = 2047 + # Hash Constants # init random hash keys @@ -111,11 +116,18 @@ # Evaluation Constants # Material values +# P N B R Q K +material_scores = ((100, 325, 335, 500, 1000, 12000), + (110, 310, 315, 550, 1050, 12000)) -material_score = (100, 320, 330, 500, 950, 12000) +# P N B R Q K +phase_scores = (0, 1, 1, 2, 4, 0) +TOTAL_PHASE = 24 mg_phase_score = 6000 -eg_phase_score = 500 +eg_phase_score = 1000 + +opening, end_game, middle_game = np.arange(3, dtype=np.uint8) pawn_pst = ( 0, 0, 0, 0, 0, 0, 0, 0, @@ -127,6 +139,16 @@ -10, -4, 0, 16, 12, 0, -4, -10, 0, 0, 0, 0, 0, 0, 0, 0) +pawn_pst_eg = ( + 0, 0, 0, 0, 0, 0, 0, 0, + -5, -2, 0, 0, 0, 0, -2, -5, + -5, -2, 0, 3, 3, 0, -2, -5, + -5, -2, 1, 7, 7, 1, -2, -5, + -5, -2, 1, 7, 7, 1, -2, -5, + -5, -2, 1, 7, 7, 1, -2, -5, + -5, -2, 1, 7, 7, 1, -2, -5, + 0, 0, 0, 0, 0, 0, 0, -5) + knight_pst = ( -20, 0, -10, -10, -10, -10, -10, -20, -10, 0, 0, 3, 3, 0, 0, -10, @@ -157,17 +179,26 @@ -5, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0, 5, 5, 0, 0, 0) -king_pst = ( - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 5, 5, 5, 5, 0, 0, - 0, 5, 5, 10, 10, 5, 5, 0, - 0, 5, 10, 20, 20, 10, 5, 0, - 0, 5, 10, 20, 20, 10, 5, 0, - 0, 0, 5, 10, 10, 5, 0, 0, - 0, 5, 5, -5, -5, 0, 5, 0, - 0, 0, 5, 0, -15, 0, 10, 0) - -PST = np.array((pawn_pst, knight_pst, bishop_pst, rook_pst, np.zeros(64), king_pst), dtype=np.int8) +king_pst_eg = (-17, -14, -10, -7, -7, -10, -14, -17, + -10, -7, -4, 0, 0, -4, -7, -10, + -10, -4, 6, 10, 10, 6, -4, -10, + -10, -4, 10, 13, 13, 10, -4, -10, + -10, -4, 10, 13, 13, 10, -4, -10, + -10, -4, 6, 10, 10, 6, -4, -10, + -10, -10, 0, 0, 0, 0, -10, -10, + -17, -10, -10, -10, -10, -10, -10, -17) + +king_pst = (-10, -14, -14, -17, -17, -14, -14, -10, + -10, -14, -14, -17, -17, -14, -14, -10, + -10, -14, -14, -17, -17, -14, -14, -10, + -10, -14, -14, -17, -17, -14, -14, -10, + -7, -10, -10, -14, -14, -10, -10, -7, + -4, -7, -7, -7, -7, -7, -7, -4, + 6, 6, 0, 0, 0, 0, 6, 6, + 6, 10, 3, 0, 0, 3, 10, 6) + +PST = np.array(((pawn_pst, knight_pst, bishop_pst, rook_pst, np.zeros(64), king_pst), + (pawn_pst_eg, knight_pst, bishop_pst, rook_pst, np.zeros(64), king_pst_eg)), dtype=np.int8) mirror_pst = ( a1, b1, c1, d1, e1, f1, g1, h1, @@ -241,6 +272,11 @@ rook_tropism_mg = 2 queen_tropism_mg = 2 +knight_tropism_eg = 1 +bishop_tropism_eg = 1 +rook_tropism_eg = 1 +queen_tropism_eg = 4 + @njit def manhattan_distance(sq1, sq2): @@ -252,4 +288,4 @@ def manhattan_distance(sq1, sq2): arr_manhattan = np.array( [manhattan_distance(sq1, sq2) for sq1 in range(64) for sq2 in range(64)], dtype=np.uint8) -arr_manhattan.shape = (64, 64) \ No newline at end of file +arr_manhattan.shape = (64, 64) diff --git a/evaluation.py b/evaluation.py index b72c0b8..4c527de 100644 --- a/evaluation.py +++ b/evaluation.py @@ -4,114 +4,233 @@ from attack_tables import get_bishop_attacks, get_queen_attacks, king_attacks, knight_attacks, get_rook_attacks -@njit +@njit(nb.uint16(Position.class_type.instance_type), cache=True) def get_game_phase_score(pos): score = 0 for color in (black, white): for piece in range(knight, king): - score += count_bits(pos.pieces[color][piece]) * material_score[piece] - return score + score += count_bits(pos.pieces[color][piece]) * phase_scores[piece] + return (score * 256 + (TOTAL_PHASE / 2)) / TOTAL_PHASE + + +@njit(cache=True) +def bishop_mg(pos, sq, kings_sq, opp): + v = 0 + # Mobility (-30 to 30) + moves = count_bits(get_bishop_attacks(sq, pos.occupancy[both])) + v += (moves - 3) * 5 + # Tropism + v += bishop_tropism_mg * 8 - arr_manhattan[sq][kings_sq[opp]] * bishop_tropism_mg + return v + + +@njit(cache=True) +def bishop_eg(pos, sq, kings_sq, opp): + v = 0 + # Mobility (-30 to 30) + moves = count_bits(get_bishop_attacks(sq, pos.occupancy[both])) + v += (moves - 3) * 5 + # Tropism + v += bishop_tropism_eg * 8 - arr_manhattan[sq][kings_sq[opp]] * bishop_tropism_eg + return v + + +@njit(cache=True) +def knight_mg(pos, sq, kings_sq, opp, color): + v = 0 + # Mobility (from -16 to 16) + moves = count_bits(knight_attacks[sq] & ~pos.occupancy[color]) + v += (moves - 4) * 4 + # Tropism + v += knight_tropism_mg * 8 - arr_manhattan[sq][kings_sq[opp]] * knight_tropism_mg + return v + + +@njit(cache=True) +def knight_eg(pos, sq, kings_sq, opp, color): + v = 0 + # Mobility (from -16 to 16) + moves = count_bits(knight_attacks[sq] & ~pos.occupancy[color]) + v += (moves - 4) * 4 + # Tropism + v += knight_tropism_eg * 8 - arr_manhattan[sq][kings_sq[opp]] * knight_tropism_eg + return v + + +@njit +def queen_mg(pos, sq, kings_sq, opp): + v = 0 + # mobility (-19 to 19) + moves = count_bits(get_queen_attacks(sq, pos.occupancy[both])) + v += moves - 5 + # Tropism + v += queen_tropism_mg * 8 - arr_manhattan[sq][kings_sq[opp]] * queen_tropism_mg + return v + + +@njit +def queen_eg(pos, sq, kings_sq, opp): + v = 0 + # mobility (-19 to 19) + moves = count_bits(get_queen_attacks(sq, pos.occupancy[both])) + v += (moves - 5) * 2 + # Tropism + v += queen_tropism_eg * 8 - arr_manhattan[sq][kings_sq[opp]] * queen_tropism_eg + return v + + +@njit +def rook_mg(pos, sq, kings_sq, opp, color): + v = 0 + # Semi-open file + if not pos.pieces[color][pawn] & file_masks[sq]: + v += semi_open_file_bonus + # Open file + if not (pos.pieces[color][pawn] | pos.pieces[opp][pawn]) & file_masks[sq]: + v += open_file_bonus + # Mobility (-28 to 28) + moves = count_bits(get_rook_attacks(sq, pos.occupancy[both])) + v += (moves - 6) * 2 + # Tropism + v += rook_tropism_mg * 8 - arr_manhattan[sq][kings_sq[opp]] * rook_tropism_mg + return v + + +@njit +def rook_eg(pos, sq, kings_sq, opp, color): + v = 0 + # Semi-open file + if not pos.pieces[color][pawn] & file_masks[sq]: + v += semi_open_file_bonus + # Open file + if not (pos.pieces[color][pawn] | pos.pieces[opp][pawn]) & file_masks[sq]: + v += open_file_bonus + # Mobility (-28 to 28) + moves = count_bits(get_rook_attacks(sq, pos.occupancy[both])) + v += (moves - 6) * 4 + # Tropism + v += rook_tropism_eg * 8 - arr_manhattan[sq][kings_sq[opp]] * rook_tropism_eg + return v + + +@njit(cache=True) +def pawn_mg(pos, sq, opp, color): + v = 0 + if color: + # Isolated pawn + if not pos.pieces[color][pawn] & isolated_masks[sq]: + v += isolated_pawn_penalty + # Passed pawn + if not black_passed_masks[sq] & pos.pieces[opp][pawn]: + v += passed_pawn_bonus[mirror_pst[sq] // 8] + else: + # Isolated pawn + if not pos.pieces[color][pawn] & isolated_masks[sq]: + v += isolated_pawn_penalty + # Passed pawn + if not white_passed_masks[sq] & pos.pieces[opp][pawn]: + v += passed_pawn_bonus[sq // 8] + return v + + +@njit +def king_mg(pos, sq, opp, color): + v = 0 + # Semi-open file + if not pos.pieces[color][pawn] & file_masks[sq]: + v -= semi_open_file_bonus + # Open file + if not (pos.pieces[color][pawn] | pos.pieces[opp][pawn]) & file_masks[sq]: + v -= open_file_bonus + + # Pawn shield + v += -15 + count_bits(king_attacks[sq] & pos.occupancy[color]) * king_shield_bonus + + # "Anti-mobility" + moves = count_bits(get_queen_attacks(sq, pos.occupancy[both])) + v -= (moves - 5) * 2 + return v + + +@njit +def king_eg(pos, sq, opp, color): + v = 0 + # Pawn shield + v += count_bits(king_attacks[sq] & pos.occupancy[color]) * king_shield_bonus + + # "Anti-mobility" + moves = count_bits(get_queen_attacks(sq, pos.occupancy[both])) + v -= moves - 8 + return v @njit(nb.int64(Position.class_type.instance_type)) def evaluate(pos) -> int: """return evaluation of a position from side-to-play perspective""" - score = 0 + mg_score = 0 + eg_score = 0 - # tropism (white, black) - tropism = np.zeros(2, dtype=np.uint8) + game_phase_score = get_game_phase_score(pos) wk_sq = get_ls1b_index(pos.pieces[white][king]) bk_sq = get_ls1b_index(pos.pieces[black][king]) - king_sq = (wk_sq, bk_sq) + kings_sq = (wk_sq, bk_sq) for color in (black, white): opp = color ^ 1 double_pawns = count_bits(pos.pieces[color][pawn] & (pos.pieces[color][pawn] << 8)) - score += double_pawns * double_pawn_penalty + mg_score += double_pawns * double_pawn_penalty for piece in range(6): bb = pos.pieces[color][piece] - pst = PST[piece] while bb: sq = get_ls1b_index(bb) # Material score - score += material_score[piece] + mg_score += material_scores[opening][piece] + eg_score += material_scores[end_game][piece] # Positional score - score += pst[sq] + mg_score += PST[opening][piece][sq] + eg_score += PST[end_game][piece][sq] if piece == king: - # Semi-open file - if not pos.pieces[color][pawn] & file_masks[sq]: - score -= semi_open_file_bonus - # Open file - if not (pos.pieces[color][pawn] | pos.pieces[opp][pawn]) & file_masks[sq]: - score -= open_file_bonus - score += -15 + count_bits(king_attacks[sq] & pos.occupancy[color]) * king_shield_bonus + mg_score += king_mg(pos, sq, opp, color) + eg_score += king_eg(pos, sq, opp, color) elif piece == pawn: - if color: - # Isolated pawn - if not pos.pieces[color][pawn] & isolated_masks[sq]: - score += isolated_pawn_penalty - # Passed pawn - if not black_passed_masks[sq] & pos.pieces[opp][pawn]: - score += passed_pawn_bonus[mirror_pst[sq] // 8] - else: - # Isolated pawn - if not pos.pieces[color][pawn] & isolated_masks[sq]: - score += isolated_pawn_penalty - # Passed pawn - if not white_passed_masks[sq] & pos.pieces[opp][pawn]: - score += passed_pawn_bonus[sq // 8] + mg_score += pawn_mg(pos, sq, opp, color) + eg_score += pawn_mg(pos, sq, opp, color) elif piece == rook: - # Semi-open file - if not pos.pieces[color][pawn] & file_masks[sq]: - score += semi_open_file_bonus - # Open file - if not (pos.pieces[color][pawn] | pos.pieces[opp][pawn]) & file_masks[sq]: - score += open_file_bonus - # Mobility (-28 to 28) - moves = count_bits(get_rook_attacks(sq, pos.occupancy[both])) - score += (moves - 6) * 2 - # Tropism - tropism[opp] += rook_tropism_mg * 8 - arr_manhattan[sq][king_sq[color]] * rook_tropism_mg + mg_score += rook_mg(pos, sq, kings_sq, opp, color) + eg_score += rook_eg(pos, sq, kings_sq, opp, color) elif piece == queen: - # mobility (-19 to 19) - moves = count_bits(get_queen_attacks(sq, pos.occupancy[both])) - score += moves - 5 - # Tropism - tropism[opp] += queen_tropism_mg * 8 - arr_manhattan[sq][king_sq[color]] * queen_tropism_mg + mg_score += queen_mg(pos, sq, kings_sq, opp) + eg_score += queen_eg(pos, sq, kings_sq, opp) elif piece == knight: - # Mobility (from -16 to 16) - moves = count_bits(knight_attacks[sq] & ~pos.occupancy[color]) - score += (moves - 4) * 4 - # Tropism - tropism[opp] += knight_tropism_mg * 8 - arr_manhattan[sq][king_sq[color]] * knight_tropism_mg + mg_score += knight_mg(pos, sq, kings_sq, opp, color) + eg_score += knight_eg(pos, sq, kings_sq, opp, color) elif piece == bishop: - # Mobility (-30 to 30) - moves = count_bits(get_bishop_attacks(sq, pos.occupancy[both])) - score += (moves - 3) * 5 - # Tropism - tropism[opp] += bishop_tropism_mg * 8 - arr_manhattan[sq][king_sq[color]] * bishop_tropism_mg + mg_score += bishop_mg(pos, sq, kings_sq, opp) + eg_score += bishop_eg(pos, sq, kings_sq, opp) bb = pop_bit(bb, sq) if color: - score = -score + mg_score, eg_score = -mg_score, -eg_score - score -= tropism[0] - score += tropism[1] + # Initiative + if game_phase_score > 50: + mg_score -= 30 if pos.side else -30 - # print(tropism[black] - tropism[white]) + score = ((mg_score * (256 - game_phase_score)) + (eg_score * game_phase_score)) / 256 + # score = mg_score return -score if pos.side else score diff --git a/lichess.py b/lichess.py index ac087ed..7042349 100644 --- a/lichess.py +++ b/lichess.py @@ -25,14 +25,13 @@ def __init__(self, client, game_id, **kwargs): self.stream = client.bots.stream_game_state(game_id) self.current_state = next(self.stream) self.bot_is_white = self.current_state['white'].get('id') == bot_id + self.time_str = "wtime" if self.bot_is_white else "btime" self.pos = parse_fen(start_position) self.moves = "" self.bot = Black_numba() self.theory = True def run(self): - print("game start") - # From Position if self.current_state['variant']['short'] == "FEN": self.pos = parse_fen(self.current_state['initialFen']) @@ -48,7 +47,7 @@ def run(self): self.make_first_move() # if you have to start - elif not self.current_state['state']['moves'] and self.bot_is_white: + elif self.bot_is_white != self.pos.side: self.make_first_move() # main game loop @@ -80,71 +79,44 @@ def handle_state_change(self, game_state): if game_state['moves'] == self.moves: return self.moves = game_state['moves'] - new_move = parse_move(self.pos, self.moves.split()[-1]) + move_list = self.moves.split() + new_move = parse_move(self.pos, move_list[-1]) self.pos = make_move(self.pos, new_move) - color = not bool(len(self.moves.split()) % 2) - bot_turn = self.bot_is_white == color + # is it bot turn ? + bot_turn = self.bot_is_white != self.pos.side if not bot_turn: return # Look in the books if self.theory: - entry = self.look_in_da_book(self.moves.split()) + entry = self.look_in_da_book(move_list) if entry: self.client.bots.make_move(game_id, entry.move) print("still theory") return - else: - self.theory = False - print("end of theory") - - # set time variables - start = time.time() - t, opp_t = (game_state["wtime"], game_state["btime"]) if self.bot_is_white else ( - game_state["btime"], game_state["wtime"]) - - def in_sec(tim): - return tim / 1000 if isinstance(tim, int) else tim.minute * 60 + tim.second - - remaining_time = in_sec(t) - remaining_opp_t = in_sec(opp_t) + self.theory = False + print("end of theory") - # Set limits - time_limit = remaining_time / 60 - nodes_limit = time_limit * 100000 + # set time limit + remaining_time = game_state[self.time_str].timestamp() + time_limit = remaining_time / 30 * 1000 # look for a move - move, depth = None, None - for depth, move, score in search(self.bot, self.pos, print_info=True): - if time.time() - start > time_limit: - break - if self.bot.nodes > nodes_limit: - break - if not -LOWER_MATE < score < LOWER_MATE: - break - - actual_time = time.time() - start + 0.001 + start = time.time() + depth, move, score = search(self.bot, self.pos, print_info=True, time_limit=time_limit) + time_spent_ms = (time.time() - start) * 1000 + 0.0001 try: client.bots.make_move(self.game_id, get_move_uci(move)) - except: # you loose + except berserk.exceptions.ResponseError as e: # you flagged + print(e) + print('you flagged') return - self.pos = make_move(self.pos, move) - self.moves += " " + get_move_uci(move) + print(f"time: {time_spent_ms:.0f} - kns: {self.bot.nodes / time_spent_ms:.0f}") + print(f"time delta: {time_spent_ms - time_limit:.0f} ms") - print(f"time: {actual_time:.2f} - kns: {self.bot.nodes / actual_time / 1000:.0f}") - - # pondering - ponder_limit = min(time_limit / 2.5, remaining_opp_t / 150) - ponder_start = time.time() - ponder_depth = 0 - for ponder_depth, _, _ in search(self.bot, self.pos, print_info=False): - if time.time() - ponder_start > ponder_limit or ponder_depth >= depth - 1: - break - print("-" * 12) - print(f"{ponder_depth= } in {time.time() - ponder_start:.2f} sec") print("-" * 40) def make_first_move(self): @@ -155,33 +127,31 @@ def make_first_move(self): self.client.bots.make_move(game_id, entry.move) print("still theory") return - else: - self.theory = False - print("end of theory") - - move = None - for depth, move, score in search(self.bot, self.pos, print_info=True): - if depth == 8: - break + self.theory = False + print("end of theory") + + # look for a move + depth, move, score = search(self.bot, self.pos, print_info=True) + client.bots.make_move(self.game_id, get_move_uci(move)) self.pos = make_move(self.pos, move) self.moves += get_move_uci(move) @staticmethod def look_in_da_book(moves): - leela = chess.polyglot.open_reader("book/book_small.bin") + fruit = chess.polyglot.open_reader("book/book_small.bin") board = chess.Board() for m in moves: board.push_uci(m) - return leela.get(board) + return fruit.get(board) print("id name black_numba") print("id author Avo-k") print("compiling...") -# make sure it's compiled -search(Black_numba(), parse_fen(start_position), print_info=False, depth_max=1) -print("compiled!") +s = time.time() +search(Black_numba(), parse_fen(start_position), print_info=False, depth_limit=1) +print(f"compiled in {time.time() - s:.2f} seconds") for event in client.bots.stream_incoming_events(): if event['type'] == 'challenge': @@ -194,11 +164,11 @@ def look_in_da_book(moves): client.bots.decline_challenge(challenge['id']) elif event['type'] == 'gameStart': + print(event['type']) game_id = event['game']['id'] game = Game(client=client, game_id=game_id) game.run() del game else: # challengeDeclined, gameFinish, challengeCanceled - if event['type'] not in ('challengeDeclined', 'gameFinish', 'challengeCanceled'): - print('NEW EVENT', event) + print(event['type']) diff --git a/search.py b/search.py index ca949aa..2d9182b 100644 --- a/search.py +++ b/search.py @@ -1,7 +1,9 @@ +import time + from constants import * from moves import * from bb_operations import get_ls1b_index -from evaluation import evaluate +from evaluation import evaluate, get_game_phase_score def random_move(pos) -> int: @@ -21,7 +23,10 @@ def random_move(pos) -> int: ("score_pv", nb.b1), ("hash_table", hash_numba_type[:]), ("repetition_table", nb.uint64[:]), - ("repetition_index", nb.uint16)]) + ("repetition_index", nb.uint16), + ("time_limit", nb.uint64), + ("start", nb.uint64), + ("stopped", nb.b1)]) class Black_numba: def __init__(self): self.nodes = 0 @@ -40,6 +45,23 @@ def __init__(self): # Repetitions self.repetition_table = np.zeros(1000, dtype=np.uint64) self.repetition_index = 0 + # Time management + self.time_limit = 1000 + self.start = 0 + self.stopped = False + + def reset_bot(self, time_limit): + self.killer_moves = np.zeros((2, MAX_PLY), dtype=np.uint64) + self.history_moves = np.zeros((2, 6, 64), dtype=np.uint8) + self.pv_table = np.zeros((MAX_PLY, MAX_PLY), dtype=np.uint64) + self.pv_length = np.zeros(MAX_PLY, dtype=np.uint64) + self.score_pv = False + self.nodes = 0 + self.stopped = False + self.time_limit = time_limit + with nb.objmode(start=nb.uint64): + start = time.time() * 1000 + self.start = start def read_hash_entry(self, pos, depth, alpha, beta): entry = self.hash_table[pos.hash_key % MAX_HASH_SIZE] @@ -80,6 +102,12 @@ def is_repetition(self, pos): return True return False + def communicate(self): + with nb.objmode(spent=nb.uint16): + spent = time.time() * 1000 - self.start + if spent > self.time_limit: + self.stopped = True + @njit def enable_pv_scoring(bot, move_list): @@ -89,12 +117,6 @@ def enable_pv_scoring(bot, move_list): bot.score_pv = True bot.follow_pv = True - # for move in move_list: - # if bot.pv_table[0][bot.ply] == move: - # bot.score_pv = True - # bot.follow_pv = True - # break - @njit(nb.uint64(Black_numba.class_type.instance_type, Position.class_type.instance_type, nb.uint64), cache=True) def score_move(bot, pos, move) -> int: @@ -144,6 +166,10 @@ def print_move_scores(bot, pos): @njit def quiescence(bot, pos, alpha, beta): + + if not bot.nodes & time_precision: + bot.communicate() + bot.nodes += 1 # We are way too deep for lots of arrays @@ -175,6 +201,9 @@ def quiescence(bot, pos, alpha, beta): bot.ply -= 1 bot.repetition_index -= 1 + if bot.stopped: + return 0 + if score > alpha: alpha = score if score >= beta: @@ -202,6 +231,9 @@ def negamax(bot, pos, depth, alpha, beta): # at this depth or higher return hash_entry + if not bot.nodes & time_precision: + bot.communicate() + bot.pv_length[bot.ply] = bot.ply if depth == 0: @@ -234,6 +266,9 @@ def negamax(bot, pos, depth, alpha, beta): bot.ply -= 1 bot.repetition_index -= 1 + if bot.stopped: + return 0 + if score >= beta: return beta @@ -283,6 +318,10 @@ def negamax(bot, pos, depth, alpha, beta): bot.ply -= 1 bot.repetition_index -= 1 + + if bot.stopped: + return 0 + moves_searched += 1 # fail-hard beta cutoff @@ -326,19 +365,23 @@ def negamax(bot, pos, depth, alpha, beta): @njit -def search(bot, pos, print_info=False, depth_max=32): +def search(bot, pos, print_info=False, depth_limit=32, time_limit=1000): """yield depth searched, best move, score (cp)""" + # + # game_phase_score = get_game_phase_score(pos) + # print("game_phase:", game_phase_score, "opening" if game_phase_score > mg_phase_score else "end_game" if game_phase_score < eg_phase_score else "middle_game") + + bot.reset_bot(time_limit=time_limit) - bot.killer_moves = np.zeros((2, MAX_PLY), dtype=np.uint64) - bot.history_moves = np.zeros((2, 6, 64), dtype=np.uint8) - bot.pv_table = np.zeros((MAX_PLY, MAX_PLY), dtype=np.uint64) - bot.pv_length = np.zeros(MAX_PLY, dtype=np.uint64) - bot.score_pv = False - bot.nodes = 0 + depth, score = 0, 0 alpha, beta = -BOUND_INF, BOUND_INF - for depth in range(1, depth_max + 1): + for depth in range(1, depth_limit + 1): + + if bot.stopped or not -LOWER_MATE < score < LOWER_MATE: + break + bot.follow_pv = True score = negamax(bot, pos, depth, alpha, beta) @@ -362,5 +405,9 @@ def search(bot, pos, print_info=False, depth_max=32): print("info score cp", score, "depth", depth, "nodes", bot.nodes, "pv", pv_line) - # print(score == bot.read_hash_entry(pos, depth, alpha, beta)) - yield depth, bot.pv_table[0][0], score + # with nb.objmode(spent=nb.uint64): + # spent = time.time() - bot.start + # print("spent:", spent) + + # print(score == bot.read_hash_entry(pos, depth, alpha, beta)) + return depth, bot.pv_table[0][0], score