| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|