def set_position(self, position): self.position = position self.piece_attacks = [0] * 13 self.all_attacks = [0] * 2 self.double_attacks = [0] * 2 occ = self.position.occupied[Side.WHITE] | self.position.occupied[ Side.BLACK] for side in [Side.WHITE, Side.BLACK]: pinned = self.position.pinned[Pt.piece(Pt.K, side)] king_us = self.position.pieces[Pt.piece(Pt.K, side)] for sq in range(64): pt = self.position.squares[sq] if pt == Pt.NULL: continue pt_side = Pt.get_side(pt) bt = Pt.base_type(pt) if pt_side == side: sq_bb = 1 << sq if bt == Pt.P: attacks = pawn_attack(sq_bb, pt_side) else: attacks = piece_attack(bt, sq_bb, occ) if pinned & sq_bb: attacks &= line_sqs(bit_position(king_us), sq) self.piece_attacks[pt] |= attacks self.double_attacks[ side] |= self.all_attacks[side] & attacks self.all_attacks[side] |= attacks return self
def rook_position_bonus(self, rook, side): position = self.position pawns_us = position.pieces[Pt.piece(Pt.P, side)] pawns_them = position.pieces[Pt.piece(Pt.P, side ^ 1)] bonus = 0 fill = south_attack(rook, FULL_BOARD) | north_attack(rook, FULL_BOARD) if pawns_us & fill and not pawns_them & fill: # rook supporting passed pawn bonus bonus += 5 if not pawns_us & fill: if pawns_them & fill: # semi-open file bonus += 20 else: # open file bonus += 45 rank_fill = east_attack(rook, FULL_BOARD) | west_attack( rook, FULL_BOARD) bonus += count_bits(rank_fill & pawns_them) * 8 # connected rooks, gets bonused once for each rook so halving it if self.piece_attacks[Pt.piece(Pt.R, side)] & rook: bonus += 15 return bonus
def center_attacks_bonus(self, side): position = self.position bonus = 0 for pt in Pt.piece_types(side=side): value = count_bits(self.piece_attacks[pt] & (E4 | E5 | D4 | D5)) if Pt.base_type(pt) == Pt.P: value *= 2 bonus += value return bonus * 5
def safe_attacks(self, bt, stm): # if attacked by lower weight piece, it doesn't count lower_wts = (pt for pt in [Pt.P, Pt.N, Pt.B, Pt.R, Pt.Q] if pt < bt) opp_attacks = 0 for piece_type in lower_wts: opp_pt = Pt.piece(piece_type, stm ^ 1) opp_attacks |= self.piece_attacks[opp_pt] attacks = self.piece_attacks[Pt.piece(bt, stm)] attacks &= invert(opp_attacks) # attacks &= invert(self.position.occupied[stm]) return attacks
def eval_see(position, move): # position = Position(pos) balance = 0 gains = [] # cache it once orig_pieces = position.pieces[:] orig_squares = position.squares[:] orig_stm = position.side_to_move() side = position.side_to_move() square = move.to_sq captured_piece_type = position.squares[bit_position(square)] if captured_piece_type: balance += MG_PIECES[Pt.base_type(captured_piece_type)] # light make move position.pieces[move.piece_type] ^= move.from_sq ^ square position.squares[bit_position(square)] = move.piece_type position.pieces[captured_piece_type] ^= square position.toggle_side_to_move() gains.append(balance) while True: lowest = lowest_attacker(position, square) if not lowest: break piece_type_stm, attacker = lowest captured_piece_type = position.squares[bit_position(square)] balance = MG_PIECES[Pt.base_type(captured_piece_type)] - gains[-1] gains.append(balance) if max(gains[-1], -gains[-2]) < 0: break # update the position position.pieces[piece_type_stm] ^= attacker ^ square position.squares[len(bin(square)) - 3] = piece_type_stm position.pieces[captured_piece_type] ^= square position.toggle_side_to_move() for i in reversed(range(1, len(gains))): gains[i - 1] = -max(gains[i], -gains[i - 1]) # restore position position.pieces = orig_pieces position.squares = orig_squares if position.side_to_move() != orig_stm: position.toggle_side_to_move() move.see_score = gains[0] return gains[0]
def ann_features(self): # neural net features g = [] # global g.extend(self.side_to_move()) g.extend(self.castling()) g.extend(self.in_check()) # g.extend(self.clock()) # redundant with np but doing it anyway g.extend(self.king(Side.WHITE)) g.extend(self.king(Side.BLACK)) p = [] # pawn-centric nonp = [] # piece-centric s = [] # square-centric for side in [Side.W, Side.B]: g.extend(self.counts_and_values(side)) p.extend(self.pawn_exists(side)) p.extend(self.pawn_count(side)) for bt in [Pt.N, Pt.B, Pt.R]: nonp.extend(self.BRN_pairs(bt, side)) nonp.extend(self.queens(side)) nonp.extend(self.king(side)) for bt in Pt.piece_types(base_only=True): if bt != Pt.P: s.extend(self.mobility(bt, side)) s.extend(self.lowest_attacker(side)) return [np.array(g), np.array(p), np.array(nonp), np.array(s)]
def mobility(self, bt, stm): # attacks count, safe attacks count pos = self.pos pt = Pt.piece(bt, stm) attacks = self.base_evaluator.piece_attacks[pt] safe_attacks = self.base_evaluator.safe_attacks(bt, stm) return [count_bits(attacks) / 16, count_bits(safe_attacks) / 16]
def get_pairs(self, bt, side): # exist 1, exist 2, 1 coords, 2 coords pos = self.pos pt = Pt.piece(bt, side) features = [] b = pos.pieces[pt] count = count_bits(b) if count == 1: if b & LEFT_SIDE: features.extend([1, 0]) features.extend(self.sq(b, side)) features.extend(self.sq(0, side)) else: features.extend([0, 1]) features.extend(self.sq(0, side)) features.extend(self.sq(b, side)) elif count >= 2: features.extend([1, 1]) features.extend(self.sq(ls1b(b), side)) b = reset_ls1b(b) features.extend(self.sq(ls1b(b), side)) else: features.extend([0, 0]) features.extend(self.sq(0, side)) features.extend(self.sq(0, side)) return features
def bad_bishop_penalty(self, minor, side): position = self.position color = minor & DARK_SQUARES pawns_us = position.pieces[Pt.piece(Pt.P, side)] if color == 0: pawns = pawns_us & WHITE_SQUARES else: pawns = pawns_us & DARK_SQUARES return count_bits(pawns) * 8
def queens(self, side): # coords, count queen_t = Pt.piece(Pt.Q, side) queens = self.pos.pieces[queen_t] f = self.sq(ls1b(queens), side) f.extend([count_bits(queens)]) return f
def BRN_pairs(self, bt, side): # B R and N pairs # ordered exist flags, coords, count pt = Pt.piece(bt, side) b = self.pos.pieces[pt] features = self.get_pairs(bt, side) features.extend([count_bits(b)]) return features
def all_pawn_attack_potentials(self, side): """Return attack potentials of pawns of side `side`""" position = self.position pawns = position.pieces[Pt.piece(Pt.P, side)] potential = 0 for pawn in iterate_pieces(pawns): potential |= self.pawn_attack_potential(pawn, side) return potential
def lowest_attacker(pos, square, side=None, highest_attacker=Pt.NULL): """Finds lowest piece of `side` attacking a square. `highest_attacker` means skip attacks of higher or equal value of given pt""" position = pos side = position.side_to_move() if side is None else side highest_attacker = Pt.K + 1 if highest_attacker == Pt.NULL else highest_attacker # pawn if Pt.P < highest_attacker: possible_from_squares = shift_sw(square, side) | shift_se(square, side) piece_type = Pt.piece(Pt.P, side) attackers = position.pieces[piece_type] & possible_from_squares if attackers: return piece_type, ls1b(attackers) # knight if Pt.N < highest_attacker: piece_type = Pt.piece(Pt.N, side) possible_from_squares = knight_attack(square) attackers = position.pieces[piece_type] & possible_from_squares if attackers: return piece_type, ls1b(attackers) # sliders for piece_type in [Pt.B, Pt.R]: if piece_type < highest_attacker: pt = Pt.piece(piece_type, side) occ = position.occupied[side] | position.occupied[side ^ 1] attacks = piece_attack(piece_type, square, occ) b = attacks & position.pieces[pt] if b: return pt, ls1b(b) if Pt.Q < highest_attacker: qn = Pt.piece(Pt.Q, side) b = attacks & position.pieces[qn] if b: return qn, ls1b(b) # king if Pt.K < highest_attacker: piece_type = Pt.piece(Pt.K, side) possible_from_squares = king_attack(square) attackers = position.pieces[piece_type] & possible_from_squares if attackers: return piece_type, attackers
def pawns_evaluation(self, side): position = self.position us, them = side, side ^ 1 pawns_us = position.pieces[Pt.piece(Pt.P, us)] pawns_them = position.pieces[Pt.piece(Pt.P, them)] score = 0 files = set(get_file(p) for p in iterate_pieces(pawns_us)) protected = (shift_ne(pawns_us, us) | shift_nw(pawns_us, us)) & pawns_us phalanx = (shift_east(pawns_us, us) | shift_west(pawns_us, us)) & pawns_us connected = protected | phalanx isolated = 0 opposed = 0 for f in files: fl = 1 if f == 0 else f - 1 fr = 6 if f == 7 else f + 1 if not (FILES[fl] & pawns_us) | (FILES[fr] & pawns_us): isolated |= FILES[f] & pawns_us if FILES[f] & pawns_them: opposed |= FILES[f] & pawns_us doubled = 0 for f in files: doubled |= reset_ls1b(pawns_us & FILES[f]) score -= count_bits(isolated & opposed) * 30 score -= count_bits(isolated ^ opposed) * 40 for c in iterate_pieces(connected): val = 2**get_rank(c, us) if c & phalanx: val += val - (2**(get_rank(c, us) - 1)) if c & opposed: val >> 1 score += val score -= count_bits(doubled) * 25 # TODO: add backward pawns # don't recount these if backward score -= count_bits(pawns_us ^ protected) * 15 return score
def pawn_exists(self, side): # Double pawns disappear but hopefully the counts make up for it # I'd think this is better than filling in empty slots pos = self.pos pt = Pt.piece(Pt.P, side) features = [0] * 8 pawns = pos.pieces[pt] for p in iterate_pieces(pawns): features[get_file(p)] = 1 return features
def king_safety_squares(self, side): """squares surrounding king plus one rank further""" position = self.position king_sq = position.pieces[Pt.piece(Pt.K, side)] attacks = king_attack(king_sq) if side == Side.WHITE: attacks |= attacks << 8 else: attacks |= attacks >> 8 return attacks
def mobility(self, side, PINNED): """Bonus for legal moves not attacked by lower weight piece. Pinned pieces have restricted mobility""" position = self.position mobility = 0 piece_types = [Pt.P, Pt.N, Pt.B, Pt.R, Pt.Q] pinned_piece_types = [] if PINNED[Pt.piece(Pt.K, side)]: pinned_piece_types = [ position.squares[bit_position(p)] for p in iterate_pieces(PINNED[Pt.piece(Pt.K, side)]) ] for base_pt in piece_types: pt = Pt.piece(base_pt, side) safe_attacks = self.safe_attacks(base_pt, side) mobility_factor = base_pt if base_pt < Pt.K else 1 mobility += count_bits(safe_attacks) * mobility_factor return mobility
def counts_and_values(self, side): f = [] max_counts = [0] + [8, 2, 2, 2, 1, 0] values = [0] + [1, 3.25, 3.25, 5, 9.75, 0] non_pawn_sum = 0 pawn_sum = 0 for pt in Pt.piece_types(side=side): bt = Pt.base_type(pt) if bt == Pt.K: continue count = count_bits(self.pos.pieces[pt]) f.append(count / max_counts[bt]) if bt == Pt.P: pawn_sum += count * values[bt] else: non_pawn_sum += count * values[bt] npv = non_pawn_sum / (2 * values[Pt.N] + 2 * values[Pt.B] + 2 * values[Pt.R] + values[Pt.Q]) f.append(npv) pv = pawn_sum / (8 * values[Pt.P]) f.append(pv) return f
def next_en_prise(position, side, move=None): """finds next en-prise piece for `side` optionally after `move` is done""" # update the position if move: if move.position: pos = move.position else: pos = Position(position) pos.make_move(move) else: pos = position for pt in sorted(Pt.piece_types(side=side), reverse=True): if Pt.base_type(pt) == Pt.P: continue for square in iterate_pieces(pos.pieces[pt]): # equal val but undefended more readily handled by eval_see lowest = lowest_attacker(pos, square, side ^ 1, Pt.base_type(pt)) if not lowest: continue attacker_pt, attacker_sq = lowest return pt, square, attacker_pt, attacker_sq return 0, 0, 0, 0
def unprotected_penalty(self, side, pins): position = self.position us = position.occupied[side] them = position.occupied[side ^ 1] free = (us | them) ^ FULL_BOARD us_attacked = attacked_pieces(position, side) penalty = 0 for pt in Pt.piece_types(side=side): num = count_bits(position.pieces[pt] & us_attacked) penalty += num * 10 defended = us_attacked & position.pieces[pt] & self.all_attacks[ side] for defended_piece in iterate_pieces(defended): if defended_piece & self.piece_attacks[Pt.piece(Pt.P, side=side)]: # defended by pawn penalty -= MG_PIECES[Pt.P] * .25 else: penalty -= MG_PIECES[Pt.P] * .125 if Pt.base_type(pt) == Pt.P: continue # possible to get attack from pawn. Penalty regardless if defended for p in iterate_pieces(position.pieces[pt]): if side == Side.WHITE: pawn_attack_sqs = (north_east(p) | north_west(p)) & free else: pawn_attack_sqs = (south_east(p) | south_west(p)) & free for pawn_attack_sq in iterate_pieces(pawn_attack_sqs): if side == Side.WHITE: pawn_from_sqs = (pawn_attack_sq << 8) if pawn_attack_sq & RANKS[4]: pawn_from_sqs |= (pawn_attack_sq << 16) pawn_from_sqs &= free << 8 else: pawn_from_sqs = (pawn_attack_sq >> 8) if pawn_attack_sq & RANKS[3]: pawn_from_sqs |= (pawn_attack_sq >> 16) pawn_from_sqs &= free >> 8 pawn_from_sqs &= position.pieces[Pt.piece(Pt.P, side=side ^ 1)] if pawn_from_sqs: # penalty += (MG_PIECES[Pt.base_type(pt)] / 8) - 20 # more penalty if the piece is pinned if p in pins: penalty += (MG_PIECES[Pt.base_type(pt)] / 3) - 20 # more penalty if the pawn is supported on the attack square if self.all_attacks[side ^ 1] & pawn_attack_sq or \ self.all_attacks[side] & pawn_attack_sq == 0: penalty += (MG_PIECES[Pt.base_type(pt)] / 3) - 20 return int(penalty * 1 / 4)
def lowest_attacker(self, side): f = [] for sqind in range(64): sq = 1 << sqind lowest = Pt.NULL pieces = [Pt.P, Pt.N, Pt.B, Pt.R, Pt.Q, Pt.K] if self.base_evaluator.all_attacks[side]: lowest = pieces[-1] + 1 for bt in pieces: pt = Pt.piece(bt, side) if self.base_evaluator.piece_attacks[pt] & sq: lowest = bt break f.append((7 - lowest) / 6) # lower pt gives higher "score" return f
def minor_outpost_bonus(self, minor, side, potentials): position = self.position if minor == 0: return 0 us = side them = side ^ 1 potential_them = potentials[them] potential_us = potentials[us] base_type = Pt.base_type(minor) pawns_us = position.pieces[Pt.piece(Pt.P, side)] outpost_ranks = [ RANKS[3] | RANKS[4] | RANKS[5], RANKS[2] | RANKS[3] | RANKS[4] ] outpost_squares = outpost_ranks[side] & invert( potential_them) & potential_us if outpost_squares: # reachable squares if base_type == Pt.N: bonus = 12 else: bonus = 7 # successfully outposted if minor & outpost_squares: bonus += 35 return bonus return 0
def get_feats(pos): krights = 0 if preserved_kingside_castle_rights(pos.position_flags, Side.W): krights |= G1 | F1 if preserved_queenside_castle_rights(pos.position_flags, Side.W): krights |= C1 | D1 if preserved_kingside_castle_rights(pos.position_flags, Side.B): krights |= G8 | F8 if preserved_kingside_castle_rights(pos.position_flags, Side.B): krights |= C8 | D8 krights_plane = bitfield(krights) stm_plane = np.zeros(64).reshape( (8, 8)) if pos.side_to_move() == S.W else np.ones(64).reshape((8, 8)) wp_plane = bitfield(pos.pieces[Pt.piece(Pt.P, S.W)]) bp_plane = bitfield(pos.pieces[Pt.piece(Pt.P, S.B)]) wn_plane = bitfield(pos.pieces[Pt.piece(Pt.N, S.W)]) bn_plane = bitfield(pos.pieces[Pt.piece(Pt.N, S.B)]) wb_plane = bitfield(pos.pieces[Pt.piece(Pt.B, S.W)]) bb_plane = bitfield(pos.pieces[Pt.piece(Pt.B, S.B)]) wr_plane = bitfield(pos.pieces[Pt.piece(Pt.R, S.W)]) br_plane = bitfield(pos.pieces[Pt.piece(Pt.R, S.B)]) wq_plane = bitfield(pos.pieces[Pt.piece(Pt.Q, S.W)]) bq_plane = bitfield(pos.pieces[Pt.piece(Pt.Q, S.B)]) wk_plane = bitfield(pos.pieces[Pt.piece(Pt.K, S.W)]) bk_plane = bitfield(pos.pieces[Pt.piece(Pt.K, S.B)]) return np.stack([ krights_plane, stm_plane, wp_plane, bp_plane, wn_plane, bn_plane, wb_plane, bb_plane, wr_plane, br_plane, wq_plane, bq_plane, wk_plane, bk_plane ], axis=-1) # 8x8x14
def pawn_count(self, side): pt = Pt.piece(Pt.P, side) return [count_bits(self.pos.pieces[pt]) / 8]
def king(self, side): # coords king_t = Pt.piece(Pt.K, side) k = self.pos.pieces[king_t] return self.sq(k, side)
def king_zone_attack_bonus(self, king_zones, side): """Attackers of enemy king zone, each weighted by piece weight, including xray attacks.""" position = self.position us = side them = side ^ 1 their_king = position.pieces[Pt.piece(Pt.K, them)] bonus = 0 if them == Side.WHITE: camp = (RANKS[0] | RANKS[1] | RANKS[2] | RANKS[3] | RANKS[4]) else: camp = (RANKS[3] | RANKS[4] | RANKS[5] | RANKS[6] | RANKS[7]) king_file = get_file(their_king) if 0 <= king_file <= 2: files = range(0, 4) elif 3 <= king_file <= 4: files = range(2, 6) else: files = range(4, 8) flank = 0 for f in files: flank |= FILES[f] flank &= camp # defended by their king only king_defended = self.piece_attacks[Pt.piece(Pt.K, them)] & invert( self.double_attacks[them]) bonus += count_bits(king_defended) * 8 # attacks directly around their king bonus += count_bits(self.all_attacks[us] & king_attack(their_king)) * 4 # bonus for the more coordinated attacks in the king zone bonus += count_bits(self.double_attacks[us] & king_zones[them]) * 4 # king flank huddling bonus bonus += count_bits(self.all_attacks[us] & flank) * 4 # extra bonus for double attacks in flank, not defended by pawn bonus += count_bits( self.all_attacks[us] & flank & self.double_attacks[us] & invert(self.piece_attacks[Pt.piece(Pt.P, them)])) * 7 # safe positions to check from safe = invert(self.all_attacks[them] | position.occupied[us]) # safe positions to check from b/c protected by their queen only safe2 = safe | self.piece_attacks[Pt.piece(Pt.Q, them)] & invert( self.double_attacks[them]) & self.double_attacks[us] bishop_rays = pseudo_attacks(Pt.B, bit_position(their_king)) rook_rays = pseudo_attacks(Pt.R, bit_position(their_king)) # potential safe knight check squares safe_n_checks = knight_attack(their_king) & safe2 & self.piece_attacks[ Pt.piece(Pt.N, us)] if safe_n_checks: bonus += 25 # potential safe bishop check squares safe_b_checks = bishop_rays & safe2 & self.piece_attacks[Pt.piece( Pt.B, them)] if safe_b_checks: bonus += 15 # potential safe rook check squares safe_r_checks = rook_rays & safe2 & self.piece_attacks[Pt.piece( Pt.R, them)] if safe_b_checks: bonus += 20 # potential safe queen check squares safe_q_checks = (bishop_rays | rook_rays) & safe & self.piece_attacks[Pt.piece( Pt.Q, them)] if safe_b_checks: bonus += 20 # safe queen contact checks safe_q_contact_checks = king_defended & self.double_attacks[ us] & self.piece_attacks[Pt.piece(Pt.Q, us)] if safe_q_contact_checks: bonus += 35 # attack more difficult without the queen if position.pieces[Pt.piece(Pt.Q, us)] == 0: bonus /= 2 return int(bonus)
def pawn_cover_bonus(self, king_zones, side): position = self.position pawn_type = Pt.piece(Pt.P, side) pawn_cover = king_zones[side] & position.pieces[pawn_type] # increase this too much, king will move to get close to e4,d4 pawns! or only do this when king is castled? return count_bits(pawn_cover) * 6
def evaluate(self, position=None, debug=False): # if ' '.join(map(str, position.moves)) == "e2-e4 e7-e6 Qd1-f3": # debug = True if position: self.set_position(position) position = self.position evals = defaultdict(lambda: [0, 0]) # Check for mate # if position.is_mate(): # return -1000000 # TODO: implement stalemate evaluations = [0, 0] counts = piece_counts(position) POTENTIALS_BB = [ self.all_pawn_attack_potentials(Side.WHITE), self.all_pawn_attack_potentials(Side.BLACK) ] KING_ZONE_BB = [ self.king_safety_squares(Side.WHITE), self.king_safety_squares(Side.BLACK) ] q_discoverers, q_pinned, q_sliding_checkers = self.position.get_discoveries_and_pins( Pt.Q) PINNED = self.position.pinned PINNED[Pt.piece(Pt.Q, Side.WHITE)] = q_pinned[Pt.piece(Pt.Q, Side.WHITE)] PINNED[Pt.piece(Pt.Q, Side.BLACK)] = q_pinned[Pt.piece(Pt.Q, Side.BLACK)] DISCOVERERS = self.position.discoverers phase = get_phase(self.position) for side in [Side.WHITE, Side.BLACK]: side_str = "WHITE" if side == Side.WHITE else "BLACK" # count material for base_type in [Pt.P, Pt.N, Pt.B, Pt.R, Pt.Q, Pt.K]: piece_type = Pt.piece(base_type, side) if base_type is not Pt.K: value = counts[piece_type] * material_eval( phase, counts, base_type, side) if debug: evals["Material %s" % (HUMAN_PIECE[piece_type])][side] += value evaluations[side] += value # Positional bonuses and penalties: # ..rook considerations if base_type == Pt.R: for rook in iterate_pieces(position.pieces[piece_type]): value = self.rook_position_bonus(rook, side) if debug: evals["Rook Position %s" % (HUMAN_PIECE[piece_type])][side] += value evaluations[side] += value # ..minor outpost, minor behind pawn if base_type in [Pt.B, Pt.N]: for minor in iterate_pieces(position.pieces[piece_type]): value = self.minor_outpost_bonus( base_type, side, POTENTIALS_BB) if debug: evals["Minor Outpost %s" % (HUMAN_PIECE[piece_type])][side] += value evaluations[side] += value if base_type == Pt.B: value = self.bad_bishop_penalty(minor, side) if debug: evals["Bad Bishop Penalty %s" % (HUMAN_PIECE[piece_type])][side] += value evaluations[side] -= value value = self.minor_behind_pawn(piece_type, side) if debug: evals["Minor Behind Pawn %s" % (HUMAN_PIECE[piece_type])][side] += value evaluations[side] += value # ..pawn structure if base_type == Pt.P: value = self.pawns_evaluation(side) if debug: evals["Pawn Structure"][side] += value evaluations[side] += value value = self.pawn_potential_penalty(side, POTENTIALS_BB) if debug: evals["Pawn Potential Penalty"][side] += value evaluations[side] -= value # ..piece-square table adjustments if base_type in [Pt.P, Pt.N, Pt.B, Pt.K]: value = psqt_value(piece_type, position, side) if debug: evals["PSQT adjustments"][side] += value evaluations[side] += value # center attacks bonus value = self.center_attacks_bonus(side) if debug: evals["Center Attack Bonus"][side] += value evaluations[side] += value # weak/hanging pieces penalties # for ep in next_en_prise(side): # pt, *rest = ep # bt = Pt.base_type(pt) # value = (MG_PIECES[bt] / MG_PIECES[Pt.P]) * 30 # if debug: evals["En-prise penalties %s" % (HUMAN_PIECE[bt])][side] += value # evaluations[side] -= value # unprotected # value = unprotected_penalty(side, pinned + q_pinned) # if debug: evals["Weak/Hanging penalties"][side] += value # evaluations[side] -= value # threats value = self.threats(side, PINNED) if debug: evals["Threats bonus"][side] += value evaluations[side] += value value = count_bits(PINNED[Pt.piece(Pt.K, side)]) * 15 if debug: evals["Pins to King penalty"][side] += value evaluations[side] -= value value = count_bits(DISCOVERERS[Pt.piece(Pt.K, side ^ 1)]) * 150 if debug: evals["Discovery threats to King bonus"][side] += value evaluations[side] += value value = count_bits(q_pinned[Pt.piece(Pt.Q, side)]) * 10 if debug: evals["Pins to Queen penalty"][side] += value evaluations[side] -= value value = count_bits(q_discoverers[Pt.piece(Pt.Q, side ^ 1)]) * 100 if debug: evals["Discovery threats to Queen bonus"][side] += value evaluations[side] += value # mobility, taking pins to king into account value = self.mobility(side, PINNED) if debug: evals["Mobility"][side] += value evaluations[side] += value # king safety, castle readiness value = 0 if side == Side.WHITE: if white_can_castle_kingside(position.position_flags, self.all_attacks[Side.BLACK], position.occupied[Side.WHITE]): value += (2 - count_bits(position.occupied[Side.WHITE] & (F1 | G1))) * 4 elif white_can_castle_queenside(position.position_flags, self.all_attacks[Side.BLACK], position.occupied[Side.WHITE]): value += (3 - count_bits(position.occupied[Side.WHITE] & (D1 | C1 | B1))) * 4 else: if black_can_castle_kingside( position.position_flags, self.all_attacks[Side.WHITE], position.occupied[Side.BLACK] ^ FULL_BOARD): value += (2 - count_bits(position.occupied[Side.BLACK] & (F8 | G8))) * 4 elif black_can_castle_queenside( position.position_flags, self.all_attacks[Side.WHITE], position.occupied[Side.BLACK] ^ FULL_BOARD): value += (3 - count_bits(position.occupied[Side.BLACK] & (D8 | C8 | B8))) * 4 if debug: evals["Castling readiness"][side] += value evaluations[side] += value # .. pawn cover of own king value = self.pawn_cover_bonus(KING_ZONE_BB, side) if debug: evals["Pawn cover"][side] += value evaluations[side] += value # .. king attack bonuses value = self.king_zone_attack_bonus(KING_ZONE_BB, side) if debug: evals["King Attack"][side] += value evaluations[side] += value if debug: pprint.pprint(dict(evals)) # pprint.pprint(evals.items()) res_value = int(evaluations[Side.WHITE] - evaluations[Side.BLACK]) if debug: print("EVAL", res_value) if position.white_to_move(): return res_value else: return -res_value
def minor_behind_pawn(self, minors, side): position = self.position pawns_in_front = position.pieces[Pt.piece(Pt.P, side)] & shift_north( minors, side) pawns_in_front &= (RANKS[2] | RANKS[3] | RANKS[4] | RANKS[5]) return count_bits(pawns_in_front) * 8
def threats(self, side, PINNED): position = self.position occupied = position.occupied[Side.WHITE] | position.occupied[ Side.BLACK] free = occupied ^ FULL_BOARD rank2 = RANKS[1] if side == Side.WHITE else RANKS[6] us = side them = side ^ 1 bonus = 0 penalty = 0 # them_no_qk = position.occupied[side ^ 1] \ # ^ position.pieces[Pt.piece(Pt.Q, side ^ 1)] \ # ^ position.pieces[Pt.piece(Pt.K, side ^ 1)] # loose_pieces = them_no_qk & ((self.all_attacks[side] | self.all_attacks[side]) ^ FULL_BOARD) # if loose_pieces: # bonus += 25 # The following is copied as in stockfish: # non-pawn enemies attacked by pawn weak = (position.occupied[them] ^ position.pieces[Pt.piece(Pt.P, them)]) \ & (self.piece_attacks[Pt.piece(Pt.P, us)]) if weak: # our pawns protected by us or not attacked by them b = position.pieces[Pt.piece(Pt.P, us)] & ( self.all_attacks[us] | invert(self.all_attacks[side])) safe_threats = (shift_ne(b, us) | shift_nw(b, us)) & weak if weak ^ safe_threats: bonus += 70 for threatened_piece in iterate_pieces(safe_threats): bonus += 150 if Pt.base_type( position.squares[bit_position(threatened_piece)]) in [ Pt.R, Pt.Q ]: bonus += 50 # non-pawn enemies defended by pawn defended = (position.occupied[them] ^ position.pieces[Pt.piece(Pt.P, them)]) \ & (self.piece_attacks[Pt.piece(Pt.P, them)]) # enemies not defended by a pawn and under our attack weak = position.occupied[them] \ & invert(self.piece_attacks[Pt.piece(Pt.P, them)]) \ & self.all_attacks[us] if defended | weak: # minor attacks minor_attack = self.piece_attacks[Pt.piece( Pt.N, us)] | self.piece_attacks[Pt.piece(Pt.B, us)] b = (defended | weak) & minor_attack for attacked in iterate_pieces(b): attacked_type = Pt.base_type( position.squares[bit_position(attacked)]) if attacked_type == Pt.N: bonus += 10 if attacked_type > Pt.N: bonus += 56 if attacked_type == Pt.Q: bonus += 40 # rook attacks b = (position.pieces[Pt.piece(Pt.Q, them)] | weak) & self.piece_attacks[Pt.piece(Pt.R, us)] for attacked in iterate_pieces(b): attacked_type = Pt.base_type( position.squares[bit_position(attacked)]) if attacked_type > Pt.P and attacked_type != Pt.R: bonus += 40 # hanging bonus += 44 * count_bits(weak & invert(self.all_attacks[them])) # king attacks b = weak & self.piece_attacks[Pt.piece(Pt.K, us)] more_than_one = reset_ls1b(b) > 0 if more_than_one: bonus += 18 # 120 for endgame elif b: bonus += 6 # 60 for endgame # bonus for pawn push that attacks pieces # pawns already attacking were considered earlier above b = position.pieces[Pt.piece(Pt.P, us)] b = shift_north(b | (shift_north(b & rank2, us) & free), us) b &= free & (self.all_attacks[us] | invert(self.all_attacks[them])) b = (shift_ne(b, us) | shift_nw(b, us)) & invert( self.piece_attacks[Pt.piece(Pt.P, us)]) b2 = PINNED[Pt.piece(Pt.K, side ^ 1)] | PINNED[Pt.piece( Pt.Q, side ^ 1)] bonus += count_bits(b & b2 & position.occupied[them]) * 70 + count_bits( b & invert(b2) & position.occupied[them]) * 20 return bonus