From d8dd01a932d9b1ebabae4c74e34288cf73312396 Mon Sep 17 00:00:00 2001 From: Avo-k Date: Wed, 7 Jul 2021 11:43:06 +0200 Subject: [PATCH] new psts --- bb_operations.py | 1 + constants.py | 94 +++++++++-------------------------------------- evaluation.py | 14 +++++-- lichess.py | 94 +++++++++++++++++++++++++++-------------------- pst.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ search.py | 9 ++--- 6 files changed, 182 insertions(+), 126 deletions(-) create mode 100644 pst.py diff --git a/bb_operations.py b/bb_operations.py index feae5f7..fb99cce 100644 --- a/bb_operations.py +++ b/bb_operations.py @@ -18,6 +18,7 @@ """ + @njit(nb.b1(nb.uint64, nb.uint8), cache=True) def get_bit(bb, sq): return bb & (1 << sq) diff --git a/constants.py b/constants.py index ef63563..da6e631 100644 --- a/constants.py +++ b/constants.py @@ -1,7 +1,6 @@ import numpy as np import numba as nb from numba import njit -from bb_operations import print_bb EMPTY = np.uint64(0) BIT = np.uint64(1) @@ -10,7 +9,6 @@ white, black, both = np.arange(3, dtype=np.uint8) pawn, knight, bishop, rook, queen, king = range(6) -# piece_names = ("pawn", "knight", "bishop", "rook", "queen", "king") a8, b8, c8, d8, e8, f8, g8, h8, \ a7, b7, c7, d7, e7, f7, g7, h7, \ @@ -19,9 +17,16 @@ a4, b4, c4, d4, e4, f4, g4, h4, \ a3, b3, c3, d3, e3, f3, g3, h3, \ a2, b2, c2, d2, e2, f2, g2, h2, \ -a1, b1, c1, d1, e1, f1, g1, h1, no_sq = range(65) +a1, b1, c1, d1, e1, f1, g1, h1, no_sq = np.arange(65, dtype=np.uint8) squares = range(64) +black_squares = np.array(sorted([s for s in range(1, 64, 2) if not (s // 8) % 2] + + [s for s in range(0, 64, 2) if (s // 8) % 2]), + dtype=np.uint8) +white_squares = np.array(sorted([s for s in range(0, 64, 2) if not (s // 8) % 2] + + [s for s in range(1, 64, 2) if (s // 8) % 2]), + dtype=np.uint8) + square_to_coordinates = ( "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", @@ -116,9 +121,9 @@ # Evaluation Constants # Material values -# P N B R Q K -material_scores = ((100, 325, 335, 500, 1000, 12000), - (110, 310, 315, 550, 1050, 12000)) +# P N B R Q K +material_scores = ((70, 325, 325, 500, 975, 12000), + (90, 315, 315, 500, 975, 12000)) # P N B R Q K phase_scores = (0, 1, 1, 2, 4, 0) @@ -129,77 +134,6 @@ opening, end_game, middle_game = np.arange(3, dtype=np.uint8) -pawn_pst = ( - 0, 0, 0, 0, 0, 0, 0, 0, - -10, -4, 0, -5, -5, 0, -4, -10, - -10, -4, 0, 8, 5, 0, -4, -10, - -10, -4, 0, 16, 12, 0, -4, -10, - -10, -4, 0, 18, 14, 0, -4, -10, - -10, -4, 0, 17, 13, 0, -4, -10, - -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, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 0, 3, 3, 0, 0, -10, - -20, -10, -10, -10, -10, -10, -10, -20) - -bishop_pst = ( - -2, -2, -2, -2, -2, -2, -2, -2, - -2, 8, 5, 5, 5, 5, 8, -2, - -2, 3, 3, 5, 5, 3, 3, -2, - -2, 2, 5, 4, 4, 5, 2, -2, - -2, 2, 5, 4, 4, 5, 2, -2, - -2, 3, 3, 5, 5, 3, 3, -2, - -2, 8, 5, 5, 5, 5, 8, -2, - -2, -2, -2, -2, -2, -2, -2, -2) - -rook_pst = ( - 0, 0, 0, 0, 0, 0, 0, 0, - 5, 10, 10, 10, 10, 10, 10, 5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - 0, 0, 0, 5, 5, 0, 0, 0) - -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, a2, b2, c2, d2, e2, f2, g2, h2, @@ -267,6 +201,9 @@ king_shield_bonus = 5 +bishop_pair_mg = 50 +bishop_pair_eg = 70 + knight_tropism_mg = 3 bishop_tropism_mg = 2 rook_tropism_mg = 2 @@ -277,6 +214,9 @@ rook_tropism_eg = 1 queen_tropism_eg = 4 +pawns_on_bishop_colour_opening = (9,6,3,0,-3,-6,-9,-12,-15) +pawns_on_bishop_colour_endgame = (12,8,4,0,-4,-8,-12,-16,-20) + @njit def manhattan_distance(sq1, sq2): diff --git a/evaluation.py b/evaluation.py index 4c527de..b1fd8b1 100644 --- a/evaluation.py +++ b/evaluation.py @@ -2,6 +2,7 @@ from bb_operations import * from position import Position from attack_tables import get_bishop_attacks, get_queen_attacks, king_attacks, knight_attacks, get_rook_attacks +from pst import PST @njit(nb.uint16(Position.class_type.instance_type), cache=True) @@ -171,17 +172,17 @@ def evaluate(pos) -> int: eg_score = 0 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]) - kings_sq = (wk_sq, bk_sq) + kings_sq = (get_ls1b_index(pos.pieces[white][king]), get_ls1b_index(pos.pieces[black][king])) for color in (black, white): opp = color ^ 1 + # double pawns double_pawns = count_bits(pos.pieces[color][pawn] & (pos.pieces[color][pawn] << 8)) mg_score += double_pawns * double_pawn_penalty + # bishop counter + bish = 0 for piece in range(6): bb = pos.pieces[color][piece] @@ -218,11 +219,16 @@ def evaluate(pos) -> int: eg_score += knight_eg(pos, sq, kings_sq, opp, color) elif piece == bishop: + bish += 1 mg_score += bishop_mg(pos, sq, kings_sq, opp) eg_score += bishop_eg(pos, sq, kings_sq, opp) bb = pop_bit(bb, sq) + if bish > 1: + mg_score += bishop_pair_mg + eg_score += bishop_pair_eg + if color: mg_score, eg_score = -mg_score, -eg_score diff --git a/lichess.py b/lichess.py index 7042349..453f7c6 100644 --- a/lichess.py +++ b/lichess.py @@ -5,7 +5,7 @@ import threading from position import parse_fen, print_position -from constants import start_position, LOWER_MATE +from constants import start_position from search import Black_numba, search, random_move from moves import get_move_uci, make_move from uci import parse_move @@ -17,18 +17,17 @@ client = berserk.Client(session=session) -class Game(threading.Thread): - def __init__(self, client, game_id, **kwargs): - super().__init__(**kwargs) +class Game: + def __init__(self, client, game_id): self.game_id = game_id self.client = client 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.pos = parse_fen(start_position) self.theory = True def run(self): @@ -50,47 +49,67 @@ def run(self): elif self.bot_is_white != self.pos.side: self.make_first_move() + # ponder_thread = threading.Thread(target=self.ponder) + # main game loop for event in self.stream: if event['type'] == 'gameState': if event['status'] == 'started': - self.handle_state_change(event) - # self.play_random_fast(event) + if not event['moves'] == self.moves: + new_move = parse_move(self.pos, event['moves'][len(self.moves):].strip()) + self.moves = event['moves'] + self.pos = make_move(self.pos, new_move) + bot_turn = self.bot_is_white != self.pos.side + + if bot_turn: + self.play(event) + + # print("my turn") + # if ponder_thread.is_alive(): + # assert threading.active_count() == 2 + # print("i am killing it") + # self.bot.stopped = True + # s = time.perf_counter_ns() + # ponder_thread.join() + # print(f"killed in {(time.perf_counter_ns() - s) / 10**6:.0f} ms") + # # del ponder_thread + # assert threading.active_count() == 1 + # self.play(event) + + # else: + # ponder_thread = threading.Thread(target=self.ponder) + # print("opp turn") + # ponder_thread.start() + elif event['status'] in ('mate', 'resign', 'outoftime', 'aborted', 'draw'): + print(event['status']) break else: print('NEW', event['status']) break - def play_random_fast(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]) - self.pos = make_move(self.pos, new_move) - + def play_random_fast(self): move = random_move(self.pos) client.bots.make_move(self.game_id, get_move_uci(move)) - self.pos = make_move(self.pos, move) - self.moves += " " + get_move_uci(move) + def ponder(self, game_state): + print("i am pondering") - def handle_state_change(self, game_state): - if game_state['moves'] == self.moves: - return - self.moves = game_state['moves'] - move_list = self.moves.split() - new_move = parse_move(self.pos, move_list[-1]) - self.pos = make_move(self.pos, new_move) - - # is it bot turn ? - bot_turn = self.bot_is_white != self.pos.side - if not bot_turn: - return + # set time limit + remaining_time = game_state[self.time_str].timestamp() + time_limit = remaining_time / 40 * 1000 + start = time.perf_counter_ns() + depth, move, score = search(self.bot, self.pos, print_info=False, time_limit=time_limit) + time_spent_ms = (time.perf_counter_ns() - start) / 10 ** 6 + print(f"pondering time: {time_spent_ms:.0f}") + print(f"pondering depth: {depth} - kns: {self.bot.nodes / time_spent_ms:.0f}") + print("-" * 40) + + def play(self, game_state): # Look in the books if self.theory: - entry = self.look_in_da_book(move_list) + entry = self.look_in_da_book(self.moves.split()) if entry: self.client.bots.make_move(game_id, entry.move) print("still theory") @@ -100,23 +119,21 @@ def handle_state_change(self, game_state): # set time limit remaining_time = game_state[self.time_str].timestamp() - time_limit = remaining_time / 30 * 1000 + time_limit = remaining_time / 40 * 1000 # look for a move - start = time.time() + start = time.perf_counter_ns() depth, move, score = search(self.bot, self.pos, print_info=True, time_limit=time_limit) - time_spent_ms = (time.time() - start) * 1000 + 0.0001 + time_spent_ms = (time.perf_counter_ns() - start) / 10**6 try: client.bots.make_move(self.game_id, get_move_uci(move)) except berserk.exceptions.ResponseError as e: # you flagged print(e) - print('you flagged') return 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 delta: {time_spent_ms - time_limit:.0f} ms") print("-" * 40) def make_first_move(self): @@ -131,11 +148,10 @@ def make_first_move(self): print("end of theory") # look for a move + print("playing 1st 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): @@ -149,9 +165,9 @@ def look_in_da_book(moves): print("id name black_numba") print("id author Avo-k") print("compiling...") -s = time.time() +compiling_time = time.time() search(Black_numba(), parse_fen(start_position), print_info=False, depth_limit=1) -print(f"compiled in {time.time() - s:.2f} seconds") +print(f"compiled in {time.time() - compiling_time:.2f} seconds") for event in client.bots.stream_incoming_events(): if event['type'] == 'challenge': diff --git a/pst.py b/pst.py new file mode 100644 index 0000000..2af33f9 --- /dev/null +++ b/pst.py @@ -0,0 +1,96 @@ +from constants import np, njit, \ + opening, end_game, pawn, knight, bishop, rook, queen, king, a8, h8, d5, e5, d4, e4, d3, e3, a1, h1 + +PawnFileOpening = 5 +KnightCentreOpening = 5 +KnightCentreEndgame = 5 +KnightRankOpening = 5 +KnightBackRankOpening = 0 +KnightTrapped = 100 +BishopCentreOpening = 2 +BishopCentreEndgame = 3 +BishopBackRankOpening = 10 +BishopDiagonalOpening = 4 +RookFileOpening = 3 +QueenCentreOpening = 0 +QueenCentreEndgame = 4 +QueenBackRankOpening = 5 +KingCentreEndgame = 12 +KingFileOpening = 10 +KingRankOpening = 10 + + +PawnFile = (-3, -1, +0, +1, +1, +0, -1, -3) +KnightLine = (-4, -2, +0, +1, +1, +0, -2, -4) +KnightRank = (+1, +2, +3, +2, +1, +0, -1, -2) +BishopLine = (-3, -1, +0, +1, +1, +0, -1, -3) +RookFile = (-2, -1, +0, +1, +1, +0, -1, -2) +QueenLine = (-3, -1, +0, +1, +1, +0, -1, -3) +KingLine = (-3, -1, +0, +1, +1, +0, -1, -3) +KingFile = (+3, +4, +2, +0, +0, +2, +4, +3) +KingRank = (-7, -6, -5, -4, -3, -2, +0, +1) + + +@njit(cache=True) +def init_pst(): + pst = np.zeros((2, 6, 64), dtype=np.int16) + + for sq in range(64): + # PAWN + pst[opening][pawn][sq] += PawnFile[sq % 8] * PawnFileOpening + + # KNIGHT + pst[opening][knight][sq] += KnightLine[sq % 8] * KnightCentreOpening + pst[opening][knight][sq] += KnightLine[sq // 8] * KnightCentreOpening + pst[end_game][knight][sq] += KnightLine[sq % 8] * KnightCentreEndgame + pst[end_game][knight][sq] += KnightLine[sq // 8] * KnightCentreEndgame + # rank + pst[opening][knight][sq] += KnightRank[sq // 8] * KnightRankOpening + + # BISHOP + pst[opening][bishop][sq] += BishopLine[sq % 8] * BishopCentreOpening + pst[opening][bishop][sq] += BishopLine[sq // 8] * BishopCentreOpening + pst[end_game][bishop][sq] += BishopLine[sq % 8] * BishopCentreEndgame + pst[end_game][bishop][sq] += BishopLine[sq // 8] * BishopCentreEndgame + + # ROOK + pst[opening][rook][sq] += RookFile[sq % 8] * RookFileOpening + + # QUEEN + pst[opening][queen][sq] += QueenLine[sq % 8] * QueenCentreOpening + pst[opening][queen][sq] += QueenLine[sq // 8] * QueenCentreOpening + pst[end_game][queen][sq] += QueenLine[sq % 8] * QueenCentreEndgame + pst[end_game][queen][sq] += QueenLine[sq // 8] * QueenCentreEndgame + + # KING + pst[end_game][king][sq] += KingLine[sq % 8] * KingCentreEndgame + pst[end_game][king][sq] += KingLine[sq // 8] * KingCentreEndgame + + pst[opening][king][sq] += KingFile[sq % 8] * KingFileOpening + pst[opening][king][sq] += KingRank[sq // 8] * KingRankOpening + + # pawn center control + for sq in (d3, e3, d5, e5, d4, d4, e4, e4): + pst[opening][pawn][sq] += 10 + + # back rank + for sq in range(a1, h1 + 1): + pst[opening][knight][sq] -= KnightBackRankOpening + pst[opening][bishop][sq] -= BishopBackRankOpening + pst[opening][queen][sq] -= QueenBackRankOpening + + # trapped knight + for sq in (a8, h8): + pst[opening][knight][sq] -= KnightTrapped + + # bishop diagonals + for coord in range(8): + sq1 = (coord + 1) * 7 + sq2 = coord * 9 + pst[opening][bishop][sq1] += BishopDiagonalOpening + pst[opening][bishop][sq2] += BishopDiagonalOpening + + return pst + + +PST = init_pst() diff --git a/search.py b/search.py index 2d9182b..9768217 100644 --- a/search.py +++ b/search.py @@ -48,7 +48,7 @@ def __init__(self): # Time management self.time_limit = 1000 self.start = 0 - self.stopped = False + self.stopped = True def reset_bot(self, time_limit): self.killer_moves = np.zeros((2, MAX_PLY), dtype=np.uint64) @@ -367,18 +367,16 @@ def negamax(bot, pos, depth, alpha, beta): @njit 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) depth, score = 0, 0 - alpha, beta = -BOUND_INF, BOUND_INF for depth in range(1, depth_limit + 1): + # print(depth, bot.stopped) + if bot.stopped or not -LOWER_MATE < score < LOWER_MATE: break @@ -389,7 +387,6 @@ def search(bot, pos, print_info=False, depth_limit=32, time_limit=1000): if score <= alpha or score >= beta: alpha, beta = -BOUND_INF, BOUND_INF continue - alpha, beta = score - 50, score + 50 if print_info: