|
|
import random
|
|
|
import torch
|
|
|
from nnue_model import NNUE
|
|
|
from infer_nnue import NNUEInfer
|
|
|
import infer_nnue
|
|
|
|
|
|
piece_score = {'K': 1000, 'Q': 900, 'R': 500, 'B': 330, 'N': 320, 'p': 100,'--':0,'-':0}
|
|
|
CHECKMATE = 100000
|
|
|
STALEMATE = 0
|
|
|
DEPTH = 3
|
|
|
|
|
|
|
|
|
|
|
|
pawn_table = [
|
|
|
[ 0, 0, 0, 0, 0, 0, 0, 0],
|
|
|
[ 5, 10, 10,-20,-20, 10, 10, 5],
|
|
|
[ 5, -5,-10, 0, 0,-10, -5, 5],
|
|
|
[ 0, 0, 0, 20, 20, 0, 0, 0],
|
|
|
[ 5, 5, 10, 25, 25, 10, 5, 5],
|
|
|
[ 10, 10, 20, 30, 30, 20, 10, 10],
|
|
|
[ 50, 50, 50, 50, 50, 50, 50, 50],
|
|
|
[ 0, 0, 0, 0, 0, 0, 0, 0]
|
|
|
]
|
|
|
|
|
|
|
|
|
knight_table = [
|
|
|
[-50,-40,-30,-30,-30,-30,-40,-50],
|
|
|
[-40,-20, 0, 0, 0, 0,-20,-40],
|
|
|
[-30, 0, 10, 15, 15, 10, 0,-30],
|
|
|
[-30, 5, 15, 20, 20, 15, 5,-30],
|
|
|
[-30, 0, 15, 20, 20, 15, 0,-30],
|
|
|
[-30, 5, 10, 15, 15, 10, 5,-30],
|
|
|
[-40,-20, 0, 5, 5, 0,-20,-40],
|
|
|
[-50,-40,-30,-30,-30,-30,-40,-50]
|
|
|
]
|
|
|
|
|
|
|
|
|
bishop_table = [
|
|
|
[-20,-10,-10,-10,-10,-10,-10,-20],
|
|
|
[-10, 5, 0, 0, 0, 0, 5,-10],
|
|
|
[-10, 10, 10, 10, 10, 10, 10,-10],
|
|
|
[-10, 0, 10, 10, 10, 10, 0,-10],
|
|
|
[-10, 5, 5, 10, 10, 5, 5,-10],
|
|
|
[-10, 0, 5, 10, 10, 5, 0,-10],
|
|
|
[-10, 0, 0, 0, 0, 0, 0,-10],
|
|
|
[-20,-10,-10,-10,-10,-10,-10,-20]
|
|
|
]
|
|
|
|
|
|
|
|
|
rook_table = [
|
|
|
[ 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]
|
|
|
]
|
|
|
|
|
|
|
|
|
queen_table = [
|
|
|
[-20,-10,-10, -5, -5,-10,-10,-20],
|
|
|
[-10, 0, 0, 0, 0, 5, 0,-10],
|
|
|
[-10, 0, 5, 5, 5, 5, 5,-10],
|
|
|
[ -5, 0, 5, 5, 5, 5, 0, -5],
|
|
|
[ 0, 0, 5, 5, 5, 5, 0, -5],
|
|
|
[-10, 5, 5, 5, 5, 5, 0,-10],
|
|
|
[-10, 0, 5, 0, 0, 0, 0,-10],
|
|
|
[-20,-10,-10, -5, -5,-10,-10,-20]
|
|
|
]
|
|
|
|
|
|
|
|
|
king_table_mid = [
|
|
|
[-30,-40,-40,-50,-50,-40,-40,-30],
|
|
|
[-30,-40,-40,-50,-50,-40,-40,-30],
|
|
|
[-30,-40,-40,-50,-50,-40,-40,-30],
|
|
|
[-30,-40,-40,-50,-50,-40,-40,-30],
|
|
|
[-20,-30,-30,-40,-40,-30,-30,-20],
|
|
|
[-10,-20,-20,-20,-20,-20,-20,-10],
|
|
|
[ 20, 20, 0, 0, 0, 0, 20, 20],
|
|
|
[ 20, 30, 10, 0, 0, 10, 30, 20]
|
|
|
]
|
|
|
|
|
|
|
|
|
king_table_end = [
|
|
|
[-50,-40,-30,-20,-20,-30,-40,-50],
|
|
|
[-30,-20,-10, 0, 0,-10,-20,-30],
|
|
|
[-30,-10, 20, 30, 30, 20,-10,-30],
|
|
|
[-30,-10, 30, 40, 40, 30,-10,-30],
|
|
|
[-30,-10, 30, 40, 40, 30,-10,-30],
|
|
|
[-30,-10, 20, 30, 30, 20,-10,-30],
|
|
|
[-30,-30, 0, 0, 0, 0,-30,-30],
|
|
|
[-50,-30,-30,-30,-30,-30,-30,-50]
|
|
|
]
|
|
|
|
|
|
king_scores = [[0]*8 for _ in range(8)]
|
|
|
for r in range(8):
|
|
|
for c in range(8):
|
|
|
king_scores[r][c]= king_table_end[r][c]+ king_table_end[r][c]
|
|
|
for r in range(4):
|
|
|
for c in range(8):
|
|
|
pawn_table[r][c],pawn_table[7-r][c]=pawn_table[r][c],pawn_table[7-r][c]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
peice_position_scores = {'N':knight_table,'K':king_scores,'N':knight_table,'B':bishop_table,'Q':queen_table,'p':pawn_table,'R':rook_table}
|
|
|
|
|
|
'''
|
|
|
use openings
|
|
|
use numpy and better board representation
|
|
|
better use p.q or something like that
|
|
|
transposition tables
|
|
|
save the evaluation zobra hash
|
|
|
add which moves it is stoping
|
|
|
add attacking and defensive
|
|
|
we can teach end game theory
|
|
|
if apeice is attacked try to move that first
|
|
|
storing the data of moves not to recalculate
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def random_move(valid_moves):
|
|
|
ind=random.randint(0,len(valid_moves)-1)
|
|
|
return valid_moves[ind]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_best_move_non_recursion(gs,valid_moves):
|
|
|
turn = 1 if gs.whiteToMove else -1
|
|
|
opponent_min_max_score= CHECKMATE
|
|
|
best_player_move = None
|
|
|
random.shuffle(valid_moves)
|
|
|
for player_move in valid_moves:
|
|
|
gs.make_move(player_move)
|
|
|
opponent_moves = gs.get_valid_moves()
|
|
|
if gs.check_mate:
|
|
|
opponent_max_score = -CHECKMATE
|
|
|
elif gs.steale_mate:
|
|
|
opponent_max_score=STALEMATE
|
|
|
else:
|
|
|
opponent_max_score = -CHECKMATE
|
|
|
random.shuffle(opponent_moves)
|
|
|
for opponent_move in opponent_moves:
|
|
|
gs.make_move(opponent_move)
|
|
|
gs.get_valid_moves()
|
|
|
if gs.check_mate:
|
|
|
score = CHECKMATE
|
|
|
elif gs.steale_mate:
|
|
|
score=STALEMATE
|
|
|
else:
|
|
|
score = -turn * score_material(gs.board)
|
|
|
|
|
|
if (score>opponent_max_score):
|
|
|
opponent_max_score=score
|
|
|
|
|
|
gs.undo_move()
|
|
|
if opponent_min_max_score> opponent_max_score:
|
|
|
opponent_min_max_score = opponent_max_score
|
|
|
best_move = player_move
|
|
|
|
|
|
gs.undo_move()
|
|
|
|
|
|
return best_move
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
helper method for best method
|
|
|
'''
|
|
|
def find_best_move(gs, valid_moves, return_queue):
|
|
|
result = find_move_nega_max_alpha_beta(
|
|
|
gs, valid_moves, DEPTH, -2*CHECKMATE, 2*CHECKMATE, 1
|
|
|
)
|
|
|
|
|
|
score, best_moves = result
|
|
|
|
|
|
print("Top moves:")
|
|
|
for sc, mv in best_moves:
|
|
|
print(mv.get_chess_notation(), "score:", sc)
|
|
|
|
|
|
chosen_move = random.choice(best_moves)[1]
|
|
|
return_queue.put(chosen_move)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
find min max move
|
|
|
'''
|
|
|
def find_move_min_max(gs,valid_moves,depth,whiteToMove):
|
|
|
global next_move
|
|
|
if depth == 0 :
|
|
|
return score_material(gs)
|
|
|
if whiteToMove:
|
|
|
max_score = - CHECKMATE
|
|
|
for move in valid_moves:
|
|
|
gs.make_move(move)
|
|
|
next_moves = gs.get_valid_moves()
|
|
|
score = find_move_min_max(gs,next_moves,depth-1,False)
|
|
|
if score>max_score:
|
|
|
max_score=score
|
|
|
if depth == DEPTH :
|
|
|
next_move = move
|
|
|
gs.undo_move()
|
|
|
return max_score
|
|
|
else:
|
|
|
min_score = CHECKMATE
|
|
|
for move in valid_moves:
|
|
|
gs.make_move(move)
|
|
|
next_moves = gs.get_valid_moves()
|
|
|
score = find_move_min_max(gs,next_moves,depth-1,True)
|
|
|
if score<min_score:
|
|
|
min_score=score
|
|
|
if depth == DEPTH :
|
|
|
next_move = move
|
|
|
gs.undo_move()
|
|
|
return min_score
|
|
|
|
|
|
|
|
|
'''
|
|
|
combine if else to one
|
|
|
'''
|
|
|
|
|
|
def find_move_nega_max(gs,valid_moves,depth,turn):
|
|
|
|
|
|
global next_move,count
|
|
|
count +=1
|
|
|
if depth == 0 :
|
|
|
return turn * score_material(gs)
|
|
|
|
|
|
max_score = CHECKMATE
|
|
|
for move in valid_moves:
|
|
|
gs.make_move(move)
|
|
|
next_moves = gs.get_valid_moves()
|
|
|
score = -find_move_nega_max(gs,next_moves,depth-1,-1 * turn)
|
|
|
if score>max_score:
|
|
|
max_score=score
|
|
|
if depth == DEPTH :
|
|
|
next_move = move
|
|
|
gs.undo_move()
|
|
|
return max_score
|
|
|
|
|
|
'''
|
|
|
the alpha beta pruning
|
|
|
remove branches that wont make any good
|
|
|
also depends on scoring algorithim
|
|
|
also add positional scores
|
|
|
need to control more squares and attack more squares
|
|
|
alpha beta these are the maximum and minimum u can acheive values overall
|
|
|
|
|
|
if max_score>alpha then max_score is alpha
|
|
|
if alpha>beta then prune that branch
|
|
|
ugot best else where no need for it
|
|
|
'''
|
|
|
|
|
|
def order_moves(gs, moves):
|
|
|
return moves
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TOP_N = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_move_nega_max_alpha_beta(gs, valid_moves, depth, alpha, beta, turn):
|
|
|
if depth == 0:
|
|
|
return turn * score_material(gs)
|
|
|
|
|
|
max_score = -CHECKMATE
|
|
|
best_local_moves = []
|
|
|
|
|
|
valid_moves = order_moves(gs, valid_moves)
|
|
|
|
|
|
for move in valid_moves:
|
|
|
gs.make_move(move)
|
|
|
score = -find_move_nega_max_alpha_beta(
|
|
|
gs, gs.get_valid_moves(), depth - 1, -beta, -alpha, -turn
|
|
|
)
|
|
|
gs.undo_move()
|
|
|
|
|
|
if score > max_score:
|
|
|
max_score = score
|
|
|
best_local_moves = [(score, move)]
|
|
|
elif score == max_score:
|
|
|
best_local_moves.append((score, move))
|
|
|
|
|
|
alpha = max(alpha, max_score)
|
|
|
if alpha >= beta:
|
|
|
break
|
|
|
|
|
|
|
|
|
if depth == DEPTH:
|
|
|
best_local_moves.sort(key=lambda x: x[0], reverse=True)
|
|
|
return max_score, best_local_moves[:TOP_N]
|
|
|
|
|
|
return max_score
|
|
|
|
|
|
'''
|
|
|
score the board
|
|
|
positive score good for white
|
|
|
a negative score good for black
|
|
|
increase the scoring function
|
|
|
counting attacking and defending moves
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
def score_material_hand(gs):
|
|
|
"""Full evaluation of the board with material, positional, mobility, defense, etc."""
|
|
|
self=gs
|
|
|
if self.check_mate:
|
|
|
if self.whiteToMove:
|
|
|
return -CHECKMATE
|
|
|
else:
|
|
|
return CHECKMATE
|
|
|
elif self.steale_mate:
|
|
|
return STALEMATE
|
|
|
|
|
|
board = self.board
|
|
|
score = 0
|
|
|
|
|
|
white_squares_controlled = set()
|
|
|
black_squares_controlled = set()
|
|
|
|
|
|
|
|
|
for r in range(8):
|
|
|
for c in range(8):
|
|
|
square = (r, c)
|
|
|
piece_info = board[r][c]
|
|
|
|
|
|
if piece_info == "--":
|
|
|
continue
|
|
|
|
|
|
color, piece = piece_info[0], piece_info[1]
|
|
|
|
|
|
base_value = piece_score[piece]
|
|
|
|
|
|
if color == 'w':
|
|
|
|
|
|
score += base_value
|
|
|
|
|
|
|
|
|
score += peice_position_scores[piece][r][c]
|
|
|
|
|
|
|
|
|
moves = self.move_functions[piece](r,c,[])
|
|
|
for move in moves:
|
|
|
white_squares_controlled.add((move.end_row, move.end_col))
|
|
|
|
|
|
|
|
|
if board[move.end_row][move.end_col][0] == 'w':
|
|
|
defended_piece = board[move.end_row][move.end_col][1]
|
|
|
score += piece_score[defended_piece]
|
|
|
|
|
|
|
|
|
if board[move.end_row][move.end_col][0] == 'b':
|
|
|
victim = board[move.end_row][move.end_col][1]
|
|
|
score += piece_score[victim] *1
|
|
|
elif color == 'b':
|
|
|
score -= base_value
|
|
|
score -= peice_position_scores[piece][7 - r][c]
|
|
|
|
|
|
moves = self.move_functions[piece](r,c,[])
|
|
|
for move in moves:
|
|
|
black_squares_controlled.add((move.end_row, move.end_col))
|
|
|
|
|
|
|
|
|
if board[move.end_row][move.end_col][0] == 'b':
|
|
|
defended_piece = board[move.end_row][move.end_col][1]
|
|
|
score -= piece_score[defended_piece]
|
|
|
|
|
|
|
|
|
if board[move.end_row][move.end_col][0] == 'w':
|
|
|
victim = board[move.end_row][move.end_col][1]
|
|
|
score -= piece_score[victim] *1
|
|
|
|
|
|
|
|
|
|
|
|
white_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'wB')
|
|
|
black_bishops = sum(1 for r in range(8) for c in range(8) if board[r][c] == 'bB')
|
|
|
if white_bishops >= 2:
|
|
|
score += 50
|
|
|
if black_bishops >= 2:
|
|
|
score -= 50
|
|
|
|
|
|
|
|
|
score += self.king_safety( "w") - self.king_safety("b")
|
|
|
score += (len(white_squares_controlled) - len(black_squares_controlled))*5
|
|
|
|
|
|
|
|
|
return score
|
|
|
|
|
|
|
|
|
device='cuda'
|
|
|
model = NNUE().to(device)
|
|
|
model.load_state_dict(torch.load("nnue_phase4_final.pt", map_location=device,weights_only=True))
|
|
|
nnue_eval = NNUEInfer(model, device)
|
|
|
CHECKMATE = 5000
|
|
|
STALEMATE = 0
|
|
|
MAX_RAW_EVAL = 2500
|
|
|
TANH_SCALE = 1000.0
|
|
|
import math
|
|
|
|
|
|
def clamp_eval(x):
|
|
|
return max(-MAX_RAW_EVAL, min(MAX_RAW_EVAL, x))
|
|
|
|
|
|
def tanh_scale_eval(x):
|
|
|
|
|
|
return math.tanh(x / TANH_SCALE)
|
|
|
def score_material(gs):
|
|
|
|
|
|
if gs.check_mate:
|
|
|
return -1 if gs.whiteToMove else 1
|
|
|
if gs.steale_mate:
|
|
|
return STALEMATE
|
|
|
|
|
|
|
|
|
features = infer_nnue.gs_to_nnue_features(gs)
|
|
|
|
|
|
|
|
|
stm = 1 if gs.whiteToMove else 0
|
|
|
|
|
|
|
|
|
score = nnue_eval(features, stm)
|
|
|
return float(score)
|
|
|
|
|
|
|
|
|
def score_nnue(gs, nnue_eval):
|
|
|
if gs.check_mate:
|
|
|
return -1.0 if gs.whiteToMove else 1.0
|
|
|
if gs.steale_mate:
|
|
|
return 0.0
|
|
|
|
|
|
feats = infer_nnue.gs_to_nnue_features(gs)
|
|
|
stm = 1 if gs.whiteToMove else 0
|
|
|
return float(nnue_eval(feats, stm))
|
|
|
|
|
|
def get_best_n_moves(gs, n=1):
|
|
|
"""
|
|
|
Returns best n moves for both White and Black.
|
|
|
"""
|
|
|
best_white, best_black = [], []
|
|
|
|
|
|
|
|
|
if gs.whiteToMove:
|
|
|
moves = gs.get_valid_moves()
|
|
|
scored = []
|
|
|
for move in moves:
|
|
|
gs.make_move(move)
|
|
|
score = -find_move_nega_max_alpha_beta(
|
|
|
gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, -1
|
|
|
)
|
|
|
gs.undo_move()
|
|
|
scored.append((score, str(move)))
|
|
|
scored.sort(key=lambda x: x[0], reverse=True)
|
|
|
best_white = scored[:n]
|
|
|
|
|
|
|
|
|
else:
|
|
|
moves = gs.get_valid_moves()
|
|
|
scored = []
|
|
|
for move in moves:
|
|
|
gs.make_move(move)
|
|
|
score = -find_move_nega_max_alpha_beta(
|
|
|
gs, gs.get_valid_moves(), DEPTH - 1, -CHECKMATE, CHECKMATE, 1
|
|
|
)
|
|
|
gs.undo_move()
|
|
|
scored.append((score, str(move)))
|
|
|
scored.sort(key=lambda x: x[0], reverse=True)
|
|
|
best_black = scored[:n]
|
|
|
return best_white if best_white else best_black
|
|
|
|
|
|
|
|
|
def find_best_move_shallow(gs, depth=2):
|
|
|
valid_moves = gs.get_valid_moves()
|
|
|
best_score = -1e9
|
|
|
best_move = None
|
|
|
|
|
|
for move in valid_moves:
|
|
|
gs.make_move(move)
|
|
|
score = -find_move_nega_max_alpha_beta(
|
|
|
gs, gs.get_valid_moves(), depth - 1,
|
|
|
-CHECKMATE, CHECKMATE,
|
|
|
-1 if gs.whiteToMove else 1
|
|
|
)
|
|
|
gs.undo_move()
|
|
|
|
|
|
if score > best_score:
|
|
|
best_score = score
|
|
|
best_move = move
|
|
|
|
|
|
return best_move, best_score
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|