#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Crocrodile Evaluation.
Evaluation: Evaluate position.
"""
from __future__ import annotations
import chess
BISHOPS_PAIR = 50
PROTECTED_KING = 70 # TODO: Review
CENTER_BONUS = 20
PAWN_SEVENTH_ROW = 50
CENTRAL_SQUARES = [36, 35, 28, 27]
ELARGED_SQUARES = [45, 44, 43, 42, 37, 34, 29, 26, 21, 20, 19, 18]
SEVENTH_ROW = [55, 54, 53, 52, 51, 50, 49, 48]
EIGHT_ROW = [56, 57, 58, 59, 60, 61, 62, 63]
SECOND_ROW = [15, 14, 13, 12, 11, 10, 9, 8]
FIRST_ROW = [0, 1, 2, 3, 4, 5, 6, 7]
THIRD_ROW = [16, 17, 18, 19, 20, 21, 22, 23]
COLUMN_A = [0, 8, 16, 24, 32, 40, 48, 56]
COLUMN_B = [1, 9, 17, 25, 33, 41, 49, 57]
COLUMN_C = [2, 10, 18, 26, 34, 42, 50, 58]
COLUMN_D = [3, 11, 19, 27, 35, 43, 51, 59]
COLUMN_E = [4, 12, 20, 28, 36, 44, 52, 60]
COLUMN_F = [5, 13, 21, 29, 37, 45, 53, 61]
COLUMN_G = [6, 14, 22, 30, 38, 46, 54, 62]
COLUMN_H = [7, 15, 23, 31, 39, 47, 55, 63]
COLUMNS = [COLUMN_A, COLUMN_B, COLUMN_C, COLUMN_D,
COLUMN_E, COLUMN_F, COLUMN_G, COLUMN_H]
DOUBLED_PAWNS = 15
TRIPLED_PAWNS = 35
QUADRUPLED_PAWNS = 60
ISOLATED_PAWN = 17
PASSED_PAWN = 22
def pawn_on_column(column, pawn, piece_map):
pawns_count = 0
for square in column:
if piece_map.get(square, None) == pawn:
pawns_count += 1
return pawns_count
[docs]def check_passed_pawns(board: chess.Board, color: bool) -> int:
"""Evaluation: Check paseed pawns.
:param chess.Board board: Board.
:param bool color: Color to add bonus (True is white).
:return: Bonus points.
:rtype: int
"""
result = 0
piece_map = board.piece_map()
pawn = ("P" if color else "p")
def pawn_on_column_after_rank(column, rank, pawn, piece_map):
pawns_count = 0
for square in column:
if square > rank * 7 + 1:
if square in piece_map:
if piece_map[square].symbol() == pawn:
pawns_count += 1
return pawns_count
for square in piece_map:
if piece_map[square].symbol() == pawn:
# Get rank and column of square
rank = chess.square_rank(square)
column = chess.square_file(square)
if pawn_on_column_after_rank(COLUMNS[column], rank, pawn.swapcase(), piece_map) == 0 \
and pawn_on_column_after_rank(COLUMNS[min(7, column+1)], rank, pawn.swapcase(), piece_map) == 0 \
and pawn_on_column_after_rank(COLUMNS[max(0, column-1)], rank, pawn.swapcase(), piece_map) == 0:
result += PASSED_PAWN
return result
[docs]def old_evaluate(board: chess.Board):
"""Evaluate position."""
white_score = 0
black_score = 0
if board.is_stalemate():
return 0
if board.is_checkmate():
if board.turn == chess.WHITE:
return -10000
return 10000
piece_map = board.piece_map()
# Counters for bishop pair
white_bishops = 0
black_bishops = 0
for piece in piece_map:
if piece_map[piece].symbol().isupper():
white_score += PIECES_VALUES[piece_map[piece].symbol().lower()]
if piece in CENTRAL_SQUARES:
white_score += CENTER_BONUS
if piece in ELARGED_SQUARES:
white_score += int(CENTER_BONUS / 2)
if piece_map[piece].symbol() == 'P' and piece in SEVENTH_ROW:
white_score += PAWN_SEVENTH_ROW
if piece_map[piece].symbol() == 'P' and piece in EIGHT_ROW:
white_score += QUEEN_VALUE
if piece_map[piece].symbol() == 'B':
white_bishops += 1
"""if piece_map[piece].symbol() == 'Q' and piece in CENTRAL_SQUARES and len(piece_map) > 20:
white_score -= 30"""
if piece_map[piece].symbol() == 'K':
if piece + 7 in piece_map and piece_map[piece + 7].symbol() == 'P' and len(piece_map) > 16:
white_score += PROTECTED_KING
if piece + 8 in piece_map and piece_map[piece + 8].symbol() == 'P' and len(piece_map) > 16:
white_score += PROTECTED_KING
if piece + 9 in piece_map and piece_map[piece + 9].symbol() == 'P' and len(piece_map) > 16:
white_score += PROTECTED_KING
else:
black_score += PIECES_VALUES[piece_map[piece].symbol()]
if piece in CENTRAL_SQUARES and not piece_map[piece].symbol() == "q":
black_score += 10
if piece in ELARGED_SQUARES and not piece_map[piece].symbol() == "q":
black_score += 5
if piece_map[piece].symbol() == 'p' and piece in SECOND_ROW:
black_score += 20
if piece_map[piece].symbol() == 'p' and piece in FIRST_ROW:
black_score += QUEEN_VALUE
if piece_map[piece].symbol() == 'b':
black_bishops += 1
if piece_map[piece].symbol() == 'k':
if piece - 7 in piece_map and piece_map[piece - 7].symbol() == 'p' and len(piece_map) > 16:
black_score += PROTECTED_KING
if piece - 8 in piece_map and piece_map[piece - 8].symbol() == 'p' and len(piece_map) > 16:
black_score += PROTECTED_KING
if piece - 9 in piece_map and piece_map[piece - 9].symbol() == 'p' and len(piece_map) > 16:
black_score += PROTECTED_KING
if piece_map[piece].symbol() == 'q' and len(piece_map) > 28:
black_score -= 30
if white_bishops >= 2:
white_score += BISHOPS_PAIR
if black_bishops >= 2:
black_score += BISHOPS_PAIR
if board.has_kingside_castling_rights(chess.WHITE):
white_score += 7
if board.has_kingside_castling_rights(chess.BLACK):
black_score += 7
if board.has_queenside_castling_rights(chess.WHITE):
white_score += 7
if board.has_queenside_castling_rights(chess.BLACK):
black_score += 7
# if board.peek().uci() in ['e1g1', 'e1c1']:
# white_score += 101
# print("white castle !")
# if board.peek().uci() in ['e8g8', 'e8c8']:
# black_score += 101
# print("black castle !")
# Check isolated pawns
for index, column in enumerate(COLUMNS):
column_pawns = pawn_on_column(column, "P", piece_map)
if column_pawns == 2:
white_score -= DOUBLED_PAWNS
if column_pawns == 3:
white_score -= TRIPLED_PAWNS
if column_pawns >= 4:
white_score -= QUADRUPLED_PAWNS
if column_pawns > 0:
if column == COLUMN_A and pawn_on_column(COLUMN_B, "P", piece_map) == 0:
white_score -= ISOLATED_PAWN
elif column == COLUMN_H and pawn_on_column(COLUMN_G, "P", piece_map) == 0:
white_score -= ISOLATED_PAWN
else:
if pawn_on_column(COLUMNS[index-1], "P", piece_map) == 0 and pawn_on_column(COLUMNS[index+1], "P", piece_map) == 0:
white_score -= ISOLATED_PAWN
white_score += check_passed_pawns(board, True)
if board.turn == chess.WHITE:
white_score += len(list(board.legal_moves))
board.push(chess.Move.from_uci("0000"))
black_score += len(list(board.legal_moves))
board.pop()
else:
black_score += len(list(board.legal_moves))
board.push(chess.Move.from_uci("0000"))
white_score += len(list(board.legal_moves))
board.pop()
return white_score-black_score
[docs]def evaluate2(board: chess.Board) -> int:
"""Evalutaion function number 2.
:param chess.Board board: Board to evaluate.
:return: Evaluation in centipawns.
:rtype: int
"""
piece_map = board.piece_map()
score = 0
# Constants
# Material advantage
for piece in piece_map.values():
if piece.color == chess.WHITE:
score += pieces_values[piece.piece_type]
# King security
king_position = list(piece_map.keys())[list(piece_map.values()).index(chess.Piece(chess.KING,
chess.WHITE))]
king_squares = [
king_position - 1,
king_position + 1,
king_position - 7,
king_position - 8,
king_position - 9,
king_position + 7,
king_position + 8,
king_position + 9,
]
king_protection = - (2 * king_protected)
for square in king_squares:
if square in piece_map and piece_map[square].piece_type in bad_edges_pieces \
and piece_map[square].color == chess.WHITE:
king_protection += king_protected
# Pieces developement and activity
for square in knight_base:
if square in piece_map and piece_map[square].piece_type == chess.KNIGHT \
and piece_map[square].color == chess.WHITE:
score -= knight_at_home
for square in bishop_base:
if square in piece_map and piece_map[square].piece_type == chess.BISHOP \
and piece_map[square].color == chess.WHITE:
score -= bishop_at_home
for square in bad_edges:
if square in piece_map and piece_map[square].piece_type in bad_edges_pieces \
and piece_map[square].color == chess.WHITE:
score -= bad_edges_malus
# Return
return score
PAWN_VALUE = 100
KNIGHT_VALUE = 290
BISHOP_VALUE = 310
ROOK_VALUE = 500
QUEEN_VALUE = 900
PIECES_VALUES = {chess.PAWN: PAWN_VALUE, chess.KNIGHT: KNIGHT_VALUE,
chess.BISHOP: BISHOP_VALUE, chess.ROOK: ROOK_VALUE,
chess.QUEEN: QUEEN_VALUE, chess.KING: 0}
KING_PROTECTED = 5
KNIGHT_BASE = [1, 6]
KNIGHT_AT_HOME = 15
BISHOP_BASE = [2, 5]
BISHOP_AT_HOME = 10
BAD_EDGES = [8, 16, 24, 32, 40, 15, 23, 31, 39, 47]
BAD_EDGES_PIECES = [chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN]
BAD_EDGES_MALUS = 20
CENTER_BONUS: int = 30
[docs]class Evaluator():
"""Base class for evaluation (v2)."""
[docs] def evaluate(self, position: chess.Board, move: chess.Move = chess.Move.null()) -> int:
"""Evaluate position and return white's advantage in centipans.
:param chess.Board position: Position to evaluate.
:param chess.Move move: Move who will be played.
:return: White's advantage as centipawns.
:rtype: int
"""
score = 0
if position.is_checkmate():
if position.turn == chess.WHITE:
return -10000
return 10000
white_score = self.evaluate_position(position)
white_score += self.evaluate_move(position, move)
black_score = self.evaluate_position(position.mirror())
score = white_score - black_score
return score
[docs] def evaluate_position(self, position: chess.Board) -> int:
"""Wrapper for evaluating position.
:param chess.Board position: Position to evaluate.
:return: White's advantage as centipawns.
:rtype: int
"""
score = 0
score += self.eval_material(position)
score += self.eval_center(position)
score += self.eval_king_protection(position)
score += self.eval_developpement(position)
return score
[docs] @staticmethod
def eval_material(position: chess.Board) -> int:
"""Evaluate material advantage of White in the given position.
:param chess.Board position: Position to evaluate.
:return: White's material advantage as centipawns.
:rtype: int
"""
score = 0
for piece in position.piece_map().values():
if piece.color == chess.WHITE:
score += PIECES_VALUES[piece.piece_type]
return score
[docs] @staticmethod
def eval_king_protection(position: chess.Board) -> int:
"""Evaluate king protection score of White in the given position.
:param chess.Board position: Position to evaluate.
:return: White's material advantage as centipawns.
:rtype: int
"""
piece_map = position.piece_map()
king_position = list(piece_map.keys())[list(piece_map.values()).index(
chess.Piece(chess.KING, chess.WHITE))]
king_squares = [
king_position - 1,
king_position + 1,
king_position - 7,
king_position - 8,
king_position - 9,
king_position + 7,
king_position + 8,
king_position + 9,
]
score = - (2 * KING_PROTECTED)
for square in king_squares:
if square in piece_map and piece_map[square].color == chess.WHITE:
score += KING_PROTECTED
return score
[docs] @staticmethod
def eval_developpement(position: chess.Board):
"""Evaluate pieces developpement and activity in the given position.
:param chess.Board position: Position to evaluate.
:return: White's material advantage as centipawns.
:rtype: int
"""
piece_map = position.piece_map()
score = 0
for square in KNIGHT_BASE:
if square in piece_map and piece_map[square].piece_type == chess.KNIGHT \
and piece_map[square].color == chess.WHITE:
score -= KNIGHT_AT_HOME
for square in BISHOP_BASE:
if square in piece_map and piece_map[square].piece_type == chess.BISHOP \
and piece_map[square].color == chess.WHITE:
score -= BISHOP_AT_HOME
for square in BAD_EDGES:
if square in piece_map and piece_map[square].piece_type in BAD_EDGES_PIECES \
and piece_map[square].color == chess.WHITE:
score -= BAD_EDGES_MALUS
return score
[docs] def evaluate_move(self, position: chess.Board, move: chess.Move) -> int:
"""Wrapper for evaluating move.
:param chess.Board position: Position to evaluate.
:param chess.Move move: Move to evaluate.
:return: White's advantage as centipawns.
:rtype: int
"""
score = 0
score += self.eval_castling(position, move)
return score
[docs] @staticmethod
def eval_castling(position: chess.Board, move: chess.Move) -> int:
"""Evaluate castling in the given position.
:param chess.Board position: Position to evaluate.
:param chess.Move move: Move to evaluate.
:return: White's castling advantage as centipawns.
:rtype: int
"""
score = 0
if move.from_square in position.piece_map() \
and position.piece_map()[move.from_square].piece_type == chess.KING \
and move.to_square in (chess.G1, chess.C1):
score += 70
return score
[docs] @staticmethod
def eval_center(position: chess.Board) -> int:
"""
Evaluate center occupation.
:param chess.Board position: Position to evaluate.
:return: White's center advantage as centipawns.
:rtype: int
"""
score: int = 0
for square, piece in position.piece_map().items():
if piece.symbol().isupper():
if square in CENTRAL_SQUARES:
score += CENTER_BONUS
elif square in ELARGED_SQUARES:
score += int(CENTER_BONUS / 2)
return score