def best_play(board: chess.Board, player, profondeur=5): """ :param profondeur: profondeur de l'algorithme :param board: chess.Board :param player: boolean :return: chess.Move """ results = [] id = 0 moves = [] pool = mp.Pool(processes=16) for move in board.legal_moves: moves.append(move) clone = board.copy(stack=False) clone.push(move) if player: results.append((pool.apply(_min, args=(clone, profondeur)), id)) else: results.append((pool.apply(_max, args=(clone, profondeur)), id)) id += 1 if player: m = max(results) else: m = min(results) return moves[m[1]]
def without_opponent_pieces(board: chess.Board) -> chess.Board: """Returns a copy of `board` with the opponent's pieces removed.""" b = board.copy() b.ep_square = None for piece_type in chess.PIECE_TYPES: for sq in b.pieces(piece_type, not board.turn): b.remove_piece_at(sq) return b
def __init__(self, board: chess.Board = None, main_UI=None, *args, **kwargs): super().__init__(*args, **kwargs) self.board = board.copy() self.main_UI = main_UI
def expand_state_chess(state: chess.Board) -> Dict[str, chess.Board]: children = {} for move in state.legal_moves: # TODO: can we push/pop to aviod copies? board = state.copy() board.push(move) children[move.uci()] = board return children
def get_defending_pieces(board: chess.Board, defending_color: chess.Color, square: chess.Square) -> [chess.PieceType]: cloned_board = board.copy() defending_pieces = get_attacking_pieces(cloned_board, defending_color, square) del cloned_board return defending_pieces
def make_move(self, board: chess.Board, is_white: Boolean): t_start = time.time() self.reset() #root = ChessNode(board, 0) #print('The current system have {} CPUs of which {} are usable'.format(mp.cpu_count(),len(os.sched_getaffinity(0)))) max_num_processes = 3 num_legal_moves = board.legal_moves.count() legal_moves_per_process = math.ceil(num_legal_moves / max_num_processes) processes = [] root_parts: List[ChessNode] = [] out_queue = mp.Queue() print("Starting make_move") # When there are few moves left, they might be all distributed among less than all processes. # Eg. with max_num_processes==3 and num_legal_moves==4, we get legal_moves_per_process==2 # so we'll only use 2 processes. num_processes = math.ceil(num_legal_moves / legal_moves_per_process) for i in range(0, num_processes): # Only create node if there are legal moves left to use if num_legal_moves > legal_moves_per_process * i: root_parts.append( ChessNode(board.copy(), 0, i * legal_moves_per_process, legal_moves_per_process)) processes.append( mp.Process(target=self.run_it, args=(board.copy(), is_white, root_parts[-1], t_start, i + 1, out_queue))) processes[-1].start() for p in processes: p.join() max_val, max_val_move = -math.inf, None for i in range(0, num_processes): cur_max_move, cur_max_val = out_queue.get() print(cur_max_move, cur_max_val) if cur_max_val > max_val: max_val = cur_max_val max_val_move = cur_max_move print("Done with all the jobs") move = max_val_move print( 'Chose move {} with value/visits={}. Node count: {} Max depth: {}'. format(max_val_move, max_val, self.no_nodes, self.max_level)) return move
def quiesce(fen, alpha, beta, flag, options): # flag check if not flag.is_set(): return 0, 0 # heuristic if options.heuristic == 'NeuralNetwork': stand_pat = heuristic.nn_heuristic(fen, options, options.model) elif options.heuristic == 'Random': stand_pat = heuristic.random_heuristic() else: stand_pat = heuristic.heuristic(fen, options) nodes = 1 if (stand_pat >= beta): return beta, nodes board = Board(fen) if len(board.piece_map()) > 8: delta = True else: delta = False if delta: # full delta pruning if (stand_pat < alpha - 1000): return alpha, nodes if (stand_pat > alpha): alpha = stand_pat # expansion and search legal = board.legal_moves for move in legal: board.push(move) new = board.copy() board.pop() if board.is_capture(move) or new.is_check(): # delta pruning if delta and board.is_capture(move): value = value_captured_piece( board.piece_type_at(move.to_square)) + 200 if (stand_pat + value < alpha): continue score, count = quiesce(new.fen(), -beta, -alpha, flag, options) score = -score nodes += count if (score >= beta): return beta, nodes if (score > alpha): alpha = score return alpha, nodes
def alphabeta(board: chess.Board, depth: int, alpha: chess.Board, beta: chess.Board, player: chess.Color): if depth == 0: return eval(board, alpha, beta, player) if player == chess.WHITE: for move in board.generate_legal_moves(): new_board = board.copy() new_board.push(move) alpha = alphabeta(new_board, depth-1, alpha, beta, chess.BLACK) if model.predict_mlp(utils.bitify(beta.fen()), utils.bitify(alpha.fen()))[0] == 1: break return alpha else: for move in board.generate_legal_moves(): new_board = board.copy() new_board.push(move) beta = alphabeta(new_board, depth-1, alpha, beta, chess.WHITE) if model.predict_mlp(utils.bitify(beta.fen()), utils.bitify(alpha.fen()))[0] == 1: break return beta
def get_move(self, board: chess.Board) -> chess.Move: # TODO: Implement Minimax to explore more than a single move ahead. possible_moves = [] board = board.copy() for move in board.legal_moves: board.push(move) possible_moves.append((self.evaluate(board), move)) board.pop() sorted_moves = sorted(possible_moves, key=lambda x: x[0], reverse=board.turn) for move in sorted_moves[:3]: print(f'Move: {move[1]}, evaluated at: {move[0]}') return sorted_moves[0][1]
def is_defended(board: Board, piece: Piece, square: Square) -> bool: if board.attackers(piece.color, square): return True # ray defense https://lichess.org/editor/6k1/3q1pbp/2b1p1p1/1BPp4/rp1PnP2/4PRNP/4Q1P1/4B1K1_w_-_-_0_1 for attacker in board.attackers(not piece.color, square): if board.piece_at(attacker).piece_type in ray_piece_types: bc = board.copy(stack = False) bc.remove_piece_at(attacker) if bc.attackers(piece.color, square): return True return False
def generate_ply_info_list_for_game(game): "Returns a dict which contains the list of tasks to be run" board = Board() ply_no = 0 game_tasks = [] for ply in game.mainline_moves(): board.push(ply) ply_info = {"ply_no": ply_no, "board": board.copy()} game_tasks.append(ply_info) ply_no = ply_no + 1 return {"game_tasks": game_tasks, "ply_count": ply_no}
def rollout_move(board: Board, move: str, simulations: int = 10) -> Tuple[str, Number[Any]]: rollouts_scores = list() for _ in range(simulations): child_node = board.copy() child_node.push(move) while not child_node.is_game_over(): rand_move = get_random_move(child_node) child_node.push_san(rand_move) result = parse_result(child_node.result()) rollouts_scores.append(result) return (move, np.mean(rollouts_scores))
def create_image(self, board: chess.Board): fen = board.copy().fen().split(' ')[0].split('/') obervations = np.empty(shape=(8, 8), dtype=str) # make empty container for i, row in enumerate(fen): j = 0 for square in row: if square.isnumeric(): for _ in range(j, int(square) + j): obervations[i][j] = '.' j += 1 continue obervations[i][j] = square j += 1 return obervations.tolist()
def erase_random_piece(board: chess.Board): removable_piece_squares = [] for square, piece in board.piece_map().items(): if piece.piece_type != chess.KING: removable_piece_squares.append(square) for _ in range(0, 10): square = random.choice(removable_piece_squares) new_board = board.copy() new_board.remove_piece_at(square) if new_board.is_valid(): return new_board return None
def mcts_player(board: chess.Board, evaluation: EvaluationFunction, iterations: int, maxdepth: int) -> chess.Move: for move_choice in board.legal_moves: copy = board.copy() copy.push(move_choice) if copy.is_game_over(): board.push(move_choice) return move_choice root = MCTSNode( ChessState(board, board.turn, maxdepth), evaluation, move_to_get_here=None, ) move = root.best_action(iterations) # move = UCT(board, itermax, depthmax, evaluation) board.push(move) return move
def get_syzygy(self, board: chess.Board) -> tuple[int, chess.Move]: """ Get a move from Syzygy tablebases. :param chess.Board board: Board to get best move and evaluation. :return: The evaluation from Syzygy tablebases and the best move. :rtype: tuple[int, chess.Move] """ # Generate DTZ list wdl: dict[int, dict[int, chess.Move]] = { 2: {}, 1: {}, 0: {}, -1: {}, -2: {} } for move in board.legal_moves: test_board: chess.Board = board.copy() test_board.push(move) wdl[self.syzygy_tb.probe_wdl(test_board)][self.syzygy_tb.probe_dtz( test_board)] = move # Get best WDL best_wdl: int = -2 while wdl[best_wdl] == {}: best_wdl += 1 # Get best move best_dtz: int = 0 best_dtz = max(wdl[best_wdl].items(), key=lambda key: key[0])[0] # Evaluation print(wdl) syzygy_evaluation: int = 0 if best_wdl < 0: if board.turn: syzygy_evaluation = 10000 else: syzygy_evaluation = -10000 elif best_wdl == 0: syzygy_evaluation = 0 else: if board.turn: syzygy_evaluation = -10000 else: syzygy_evaluation = 10000 # Return return syzygy_evaluation, wdl[best_wdl][best_dtz]
def _evaluate_move(turn: chess.Color, board: chess.Board, move: chess.Move) -> float: # sum up the total weighted pieces for each player and return a score board = board.copy() board.push(move) total = 0 if board.is_game_over(): return -1000 if board.turn == turn else 1000 for p in (chess.PAWN, chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN, chess.KING): for c in board.pieces(p, turn): total += piece[p] + pst[p][c] return total
def run(col, depth): refer = get_ref() if col == True: old_bo = online_minmax(col, Board(), refer, depth) else: old_bo = Board() while True: see_bo = read2(ref=refer, flip=col) print(old_bo, '\n_______________') print(see_bo, '\n_______________') if str(old_bo) != str(see_bo): new_bo = old_bo.copy() new_bo.push(find_move(old_bo, see_bo)) move_bo = online_minmax(col, new_bo, refer, depth) read_bo = read2(ref=refer, flip=col) if str(move_bo) == str(read_bo): old_bo = move_bo
def predict_best_move(self, curr_board: chess.Board) -> list: turn = curr_board.turn eval_moves = [] mate_moves = [] opponent_mates = [] for move in curr_board.legal_moves: new_board = curr_board.copy() new_board.push(move) # analyze position and remember the best one, analyze is not yet implemented prediction = self.analyze(new_board) evaluation = prediction[0].item() moves_til_mate = prediction[1].item() is_mate = prediction[2].item() if turn: if is_mate > 0.5 and moves_til_mate >= 0: mate_moves.append((curr_board.san(move), moves_til_mate)) elif is_mate > 0.5: opponent_mates.append((curr_board.san(move), moves_til_mate)) else: eval_moves.append((curr_board.san(move), evaluation)) else: if is_mate > 0.5 and moves_til_mate <= 0: mate_moves.append((curr_board.san(move), moves_til_mate)) elif is_mate > 0.5: opponent_mates.append((curr_board.san(move), moves_til_mate)) else: eval_moves.append((curr_board.san(move), evaluation)) if turn: eval_moves.sort(key=lambda x: x[1], reverse=True) mate_moves.sort(key=lambda x: x[1]) opponent_mates.sort(key=lambda x: x[1]) else: eval_moves.sort(key=lambda x: x[1]) mate_moves.sort(key=lambda x: x[1], reverse=True) opponent_mates.sort(key=lambda x: x[1], reverse=True) return eval_moves, mate_moves, opponent_mates
def lookahead1_move(original: Board, verbose: bool = False) -> Move: """Looks ahead one move """ best_score = None best_moves = None def is_better(reference_score, new_score) -> bool: if reference_score is None: return True if original.turn == chess.WHITE: return new_score > reference_score else: return reference_score > new_score my_moves = list(original.legal_moves) for my_move in my_moves: b = original.copy() b.push(my_move) if b.is_game_over(): score = score_board(b) else: their_move = pick_move(b) b.push(their_move) score = score_board(b) if verbose: print(f"{my_move} then {their_move} gives {score}.") if is_better(best_score, score): best_score = score best_moves = [my_move] elif score == best_score: best_moves.append(my_move) if verbose: print(f"\nBest moves are {best_moves} scoring {best_score}") if best_moves: return random.choice(best_moves) else: print("No valid moves to pick from") return None
def getGameEnded(self, board: chess.Board, player): """ Input: board: current board player: current player (1 or -1) Returns: r: 0 if game has not ended. 1 if player won, -1 if player lost, small non-zero value for draw. """ new_board = board.copy() if new_board.is_game_over(claim_draw=True): if new_board.result() == "1-0": return 1 elif new_board.result() == "1/2-1/2": return -0.5 else: return -1 return 0
def search(self, board: chess.Board): """ Recursevly search a tree using UCT and the NNet to intellegently select the proper tree to search. """ if board.is_game_over(): result = board.result() return -end_states[result] board_hash = polyglot.zobrist_hash(board) if board_hash not in self.Policy_vectors: # Get nnet prediction of the current board for use in the # Determine which branch to explore next. # If black to play, mirror the board, unmirror at end. NNet always sees current player as white. # Make a copy of the board as to preserve our move stack. temp = None if not board.turn: # If black to play, flip and mirror board. temp = board.transform(chess.flip_vertical) temp.apply_mirror() else: temp = board.copy() cannonical = adapter.get_cannonical(temp) policy_vector, nnet_value = self.network.predict(cannonical) # Mask out invalid moves valids = adapter.moves_to_policy_mask(list(board.legal_moves)) policy_vector *= valids # Normalize vector, add valid moves if needed. if np.sum(policy_vector > 0): policy_vector /= np.linalg.norm(policy_vector) self.Policy_vectors[board_hash] = policy_vector else: print( "All valid moves were masked. Adding valids. Warning if lots of these messages." ) policy_vector += valids policy_vector /= np.linalg.norm(policy_vector) self.N_vals[board_hash] = 0 del temp # Return the esimate until we actually reach the end. return -nnet_value # Iterate over legal moves and get the probability of making that move, according to the nnet. action_heuristic_dict = {} curr_move_policy = self.Policy_vectors[board_hash] for move in list(board.legal_moves): move_prob = adapter.get_move_prob(curr_move_policy, move) action_heuristic_dict[move] = self.get_ucb(board_hash, move, move_prob * 2.0) # Pick move with max value, make it bigger move = max(action_heuristic_dict, key=action_heuristic_dict.get) # action_heuristic_dict[max_move] *= 50.0 # # Normalize # values = np.array(list(action_heuristic_dict.values())) # values += np.abs(values.min()) # values /= np.linalg.norm(values) # p = self.fix_p(values) # move = np.random.choice(list(action_heuristic_dict.keys()), p=p) board.push(move) value = self.search(board) board.pop() # We've done our search, now we're back-propigating values for the next search. self.update_values(board_hash, move, value) self.N_vals[board_hash] += 1 return -value
def make_child(self, board: chess.Board, move: str) -> chess.Board: child = board.copy() child.push(move) return child
def get_mobility(board: Board, mobility_coeff: float = 1) -> float: player_legal_moves = len(list(board.legal_moves)) _board = board.copy() _board.turn = not board.turn oponent_legal_moves = len(list(_board.legal_moves)) return mobility_coeff * (player_legal_moves - oponent_legal_moves)
def successors(board: chess.Board) -> Iterator[chess.Board]: for move in board.legal_moves: board_after = board.copy(stack=False) board_after.push(move) yield board_after
def search(self, board: chess.Board, depth, side, alpha=-2, beta=2): """ Performs a minimax search with alpha-beta pruning. Alpha, beta are set to -2, 2 respectively; because the suppport of the CNN is [0,1]. Args: `board`: Chess board state at current node. `depth`: Search depth. `side`: White (True), Black (False). White is the maximising player. `alpha`: Current minimum score White is assured of. `beta`: Current maximum score Black is assured of. Returns: (evaluation, uci-encoded move that `side` should play) """ if depth == 0: input = szr.Serializer(board).serialize() return (self.model.predict(np.expand_dims(input, 0)), board.pop()) if board.is_game_over(): res = board.result() if res == '0-1': return 0 elif res == '1-0': return 1 else: return 0.5 if side: max_score = -2 best_move = '' for move in board.legal_moves: new_board = board.copy() new_board.push(move) score = self.search(new_board, depth - 1, alpha, beta, False)[0] if score > max_score: max_score = score best_move = move alpha = max(alpha, score) if alpha >= beta: # black has a better option available earlier on in the tree. break return (max_score, best_move) else: min_score = 2 best_move = '' for move in board.legal_moves: new_board = board.copy() new_board.push(move) score = self.search(new_board, depth - 1, alpha, beta, True)[0] min_score = min(score, min_score) if score < min_score: min_score = score best_move = move beta = min(beta, score) if beta <= alpha: # white has a better option avilable earlier on in the tree. break return (min_score, best_move)
class Viridithas(): def __init__( self, human: bool = False, fen: str = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', pgn: str = '', time_limit: float = 15, fun: bool = False, contempt: int = 3000, book: bool = True, advancedTC: list = [], ): if pgn == '': self.node = Board(fen) else: self.node = Board() for move in pgn.split(): try: self.node.push_san(move) except Exception: continue self.time_limit = time_limit if advancedTC: self.endpoint = time.time()+advancedTC[0]*60 self.increment = advancedTC[1] else: self.endpoint = 0 self.increment = 0 self.fun = fun self.contempt = contempt self.human = human self.nodes = 0 self.advancedTC = advancedTC self.hashtable: dict[Hashable, TTEntry] = dict() self.inbook = book def set_position(self, fen): self.node = Board(fen) def __repr__(self) -> str: return str(self.node) + '\n' + self.__class__.__name__+"-engine at position " + str(self.node.fen()) def __str__(self) -> str: return self.__class__.__name__ def user_setup(self): if input("Do you want to configure the bot? (Y/N) ").upper() != 'Y': return myname = self.__class__.__name__.upper() print(f"BEGINNING USER CONFIGURATION OF {myname}-BOT") datadict = get_engine_parameters() self.__init__( human=datadict["human"], fen=datadict["fen"], pgn=datadict["pgn"], time_limit=datadict["time_limit"], fun=datadict["fun"], contempt=datadict["contempt"], book=datadict["book"], advancedTC=datadict["advancedTC"] ) def gameover_check_info(self): checkmate = self.node.is_checkmate() draw = self.node.is_stalemate() or \ self.node.is_insufficient_material( ) or \ self.node.is_repetition(2) or self.node.is_seventyfive_moves() or not any(self.node.generate_legal_moves()) return checkmate or draw, checkmate, draw # @profile def evaluate(self, depth: float, checkmate: bool, draw: bool) -> float: self.nodes += 1 if checkmate: return MATE_VALUE * int(max(depth+1, 1)) * (1 if self.node.turn else -1) if draw: return -self.contempt * (1 if self.node.turn else -1) rating: float = 0 rating += pst_eval(self.node) # rating += see_eval(self.node) # rating += mobility(self.node) * MOBILITY_FACTOR # rating += piece_attack_counts(self.node) * ATTACK_FACTOR # rating += king_safety(self.node) * KING_SAFETY_FACTOR # rating += space(self.node) * SPACE_FACTOR return rating def single_hash_iterator(self, best): yield best def captures_piece(self, p): # concentrate on MVV, then LVA return itertools.chain( self.node.generate_pseudo_legal_moves(self.node.pawns, p), self.node.generate_pseudo_legal_moves(self.node.knights, p), self.node.generate_pseudo_legal_moves(self.node.bishops, p), self.node.generate_pseudo_legal_moves(self.node.rooks, p), self.node.generate_pseudo_legal_moves(self.node.queens, p), self.node.generate_pseudo_legal_moves(self.node.kings, p), ) #@profile def captures(self): # (MVV/LVA) return (m for m in itertools.chain( self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.queens), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.rooks), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.bishops), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.knights), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.pawns), ) if self.node.is_legal(m)) def winning_captures(self): # (MVV/LVA) target_all = self.node.occupied_co[not self.node.turn] target_3 = target_all & ~self.node.pawns target_5 = target_3 & (~self.node.bishops | ~self.node.knights) target_9 = target_5 & ~self.node.rooks return itertools.chain( self.node.generate_pseudo_legal_moves(self.node.pawns, target_all), self.node.generate_pseudo_legal_moves(self.node.knights, target_3), self.node.generate_pseudo_legal_moves(self.node.bishops, target_3), self.node.generate_pseudo_legal_moves(self.node.rooks, target_5), self.node.generate_pseudo_legal_moves(self.node.queens, target_9), self.node.generate_pseudo_legal_moves(self.node.kings, target_9), ) def losing_captures(self): # (MVV/LVA) target_pawns = self.node.pawns target_pnb = target_pawns | self.node.bishops | self.node.knights target_pnbr = target_pnb | self.node.rooks return itertools.chain( self.node.generate_pseudo_legal_moves(self.node.knights, target_pawns), self.node.generate_pseudo_legal_moves(self.node.bishops, target_pawns), self.node.generate_pseudo_legal_moves(self.node.rooks, target_pnb), self.node.generate_pseudo_legal_moves(self.node.queens, target_pnbr), self.node.generate_pseudo_legal_moves(self.node.kings, target_pnbr), ) def ordered_moves(self): return (m for m in itertools.chain( # self.winning_captures(), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.queens), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.rooks), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.bishops), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.knights), self.captures_piece( self.node.occupied_co[not self.node.turn] & self.node.pawns), self.node.generate_pseudo_legal_moves( 0xffff_ffff_ffff_ffff, ~self.node.occupied_co[not self.node.turn]), # self.losing_captures() ) if self.node.is_legal(m)) def pass_turn(self) -> None: self.node.push(Move.from_uci("0000")) #@profile def qsearch(self, alpha: float, beta: float, depth: float, colour: int, gameover: bool, checkmate: bool, draw: bool) -> float: val = self.evaluate(1, checkmate, draw) * colour if gameover: return val if val >= beta: return beta if (val < alpha - QUEEN_VALUE): return alpha alpha = max(val, alpha) for capture in self.captures(): self.node.push(capture) gameover, checkmate, draw = self.gameover_check_info() score = -self.qsearch(-beta, -alpha, depth - 1, -colour, gameover, checkmate, draw) self.node.pop() if score >= beta: return score alpha = max(score, alpha) return alpha def tt_lookup(self, board: Board) -> "TTEntry": key = board._transposition_key() return self.hashtable.get(key, TTEntry.default()) def tt_store(self, board: Board, entry: TTEntry): key = board._transposition_key() self.hashtable[key] = entry #@profile def negamax(self, depth: float, colour: int, alpha: float, beta: float) -> float: initial_alpha = alpha # (* Transposition Table Lookup; self.node is the lookup key for ttEntry *) tt_entry = self.tt_lookup(self.node) if not tt_entry.is_null() and tt_entry.depth >= depth: if tt_entry.type == EXACT: return tt_entry.value elif tt_entry.type == LOWERBOUND: alpha = max(alpha, tt_entry.value) elif tt_entry.type == UPPERBOUND: beta = min(beta, tt_entry.value) if alpha >= beta: return tt_entry.value if self.node.is_legal(tt_entry.best): moves = itertools.chain([tt_entry.best], filter(lambda x: x != tt_entry.best, self.ordered_moves())) else: moves = self.ordered_moves() else: moves = self.ordered_moves() gameover, checkmate, draw = self.gameover_check_info() if gameover: return colour * self.evaluate(depth, checkmate, draw) if depth < 1: return self.qsearch(alpha, beta, depth, colour, gameover, checkmate, draw) current_pos_is_check = self.node.is_check() if not current_pos_is_check and depth >= 3 and abs(alpha) < MATE_VALUE and abs(beta) < MATE_VALUE: # MAKE A NULL MOVE self.node.push(Move.null()) # PERFORM A LIMITED SEARCH value = - self.negamax(depth - 3, -colour, -beta, -beta + 1) # UNMAKE NULL MOVE self.node.pop() if value >= beta: return beta do_prune = self.pruning(depth, colour, alpha, beta, current_pos_is_check) best_move = Move.null() search_pv = True value = float("-inf") for move_idx, move in enumerate(moves): if move_idx == 0: best_move = move gives_check = self.node.gives_check(move) is_capture = self.node.is_capture(move) is_promo = bool(move.promotion) depth_reduction = search_reduction_factor( move_idx, current_pos_is_check, gives_check, is_capture, is_promo, depth) if do_prune: if not gives_check and not is_capture: continue self.node.push(move) if search_pv: r = -self.negamax(depth - depth_reduction, -colour, -beta, -alpha) value = max(value, r) else: r = -self.negamax(depth - depth_reduction, -colour, -alpha-1, -alpha) if (r > alpha): # // in fail-soft ... & & value < beta) is common r = -self.negamax(depth - depth_reduction, -colour, -beta, -alpha) #// re-search value = max(value, r) self.node.pop() if value > alpha: alpha = value best_move = move alpha = max(alpha, value) if alpha >= beta: break search_pv = False # (* Transposition Table Store; self.node is the lookup key for ttEntry *) # ttEntry = TTEntry() tt_entry.value = value if value <= initial_alpha: tt_entry.type = UPPERBOUND elif value >= beta: tt_entry.type = LOWERBOUND else: tt_entry.type = EXACT tt_entry.depth = depth tt_entry.best = best_move tt_entry.null = False self.tt_store(self.node, tt_entry) return value def pruning(self, depth, colour, alpha, beta, current_pos_is_check): if not (not current_pos_is_check and abs( alpha) < MATE_VALUE / 2 and abs(beta) < MATE_VALUE / 2) or depth > 2: return False see = see_eval(self.node) * colour DO_D1_PRUNING = depth <= 1 and see + FUTILITY_MARGIN < alpha DO_D2_PRUNING = depth <= 2 and see + FUTILITY_MARGIN_2 < alpha return DO_D1_PRUNING or DO_D2_PRUNING def move_sort(self, moves: list, ratings: list): pairs = zip(*sorted(zip(moves, ratings), key=operator.itemgetter(1))) moves, ratings = [list(pair) for pair in pairs] return moves, ratings def pv_string(self): count = 0 moves = [] while True: e = self.tt_lookup(self.node) if e.is_null() or not self.node.is_legal(e.best): break # print(self.node.__str__()) # print(self.node.__repr__()) # print(e.best) # print(self.node.san(e.best)) moves.append(self.node.san(e.best)) self.node.push(e.best) count += 1 for _ in moves: self.node.pop() if count == 0: return "" return " ".join(moves) def turnmod(self) -> int: return -1 if self.node.turn else 1 def show_iteration_data(self, moves: list, values: list, depth: float, start: float) -> tuple: t = round(time.time()-start, 2) print(f"{self.node.san(moves[0])} | {-round((self.turnmod()*values[0])/1000, 3)} | {str(t)}s at depth {str(depth + 1)}, {str(self.nodes)} nodes processed, at {str(int(self.nodes / (t+0.00001)))}NPS.\n", f"PV: {self.pv_string()}\n", end="") return (self.node.san(moves[0]), self.turnmod()*values[0], self.nodes, depth+1, t) def search(self, ponder: bool = False, readout: bool = True): val = float("-inf") start_time = time.time() self.nodes = 0 moves = [next(self.ordered_moves())] saved_position = deepcopy(self.node) alpha, beta = float("-inf"), float("inf") valWINDOW = PAWN_VALUE / 4 WINDOW_FAILED = False try: depth = 1 while depth < 40: best = self.tt_lookup(self.node).best time_elapsed = time.time() - start_time # check if we aren't going to finish the next search in time if time_elapsed > 0.5 * self.time_limit and not ponder and not WINDOW_FAILED: return best, val val = self.negamax( depth, self.turnmod(), alpha=alpha, beta=beta) # print(val) if ((val <= alpha) or (val >= beta)): # We fell outside the window, so try again with a # full-width window (and the same depth). alpha = float("-inf") beta = float("inf") WINDOW_FAILED = True continue WINDOW_FAILED = False best = self.tt_lookup(self.node).best # check if we've run out of time if time_elapsed > self.time_limit and not ponder: return best, val moves = [self.tt_lookup(self.node).best] values = [self.tt_lookup(self.node).value] if readout: self.show_iteration_data(moves, values, depth, start_time) alpha = val - valWINDOW # Set up the window for the next iteration. beta = val + valWINDOW depth += 1 except KeyboardInterrupt: self.node = saved_position pass return moves[0], val def ponder(self) -> None: self.origin = self.node.copy() self.search(ponder=True) def get_book_move(self): # book = chess.polyglot.open_reader( # r"ProDeo292/ProDeo292/books/elo2500.bin") book = chess.polyglot.open_reader( r"books/elo2500.bin") main_entry = book.find(self.node) choice = book.weighted_choice(self.node) book.close() return main_entry.move, choice.move def engine_move(self) -> Move: # add flag_func for egtb mode if self.advancedTC: self.time_limit = (self.endpoint-time.time())/20 print("Time for move: " + str(round(self.time_limit, 2)) + "s") if self.inbook: try: best, choice = self.get_book_move() if self.fun: self.node.push(choice) return choice else: self.node.push(best) print(chess.pgn.Game.from_board(self.node)[-1]) except IndexError: self.time_limit = self.time_limit*2 best, _ = self.search() self.node.push(best) print(chess.pgn.Game.from_board(self.node)[-1]) self.inbook = False self.time_limit = self.time_limit/2 else: best, _ = self.search() self.node.push(best) print(chess.pgn.Game.from_board(self.node)[-1]) # self.record_stack() self.endpoint += self.increment return best def user_move(self) -> str: move = input("enter move: ") while True: try: self.node.push_san(move) break except Exception: move = input("enter move: ") return move def display_ending(self) -> None: if self.node.is_stalemate(): print('END BY STALEMATE') elif self.node.is_insufficient_material(): print('END BY INSUFFICIENT MATERIAL') elif self.node.is_fivefold_repetition(): print('END BY FIVEFOLD REPETITION') elif self.node.is_checkmate: print("BLACK" if self.node.turn == BLACK else "WHITE", 'WINS ON TURN', self.node.fullmove_number) else: print('END BY UNKNOWN REASON') def run_game(self, indefinite=True) -> str: while not self.node.is_game_over(): print(self.__repr__()) if self.human and self.node.turn: try: self.ponder() except KeyboardInterrupt: self.node = self.origin self.user_move() else: # SWAP THESE ASAP self.engine_move() if not indefinite: break self.display_ending() try: return str(chess.pgn.Game.from_board(self.node)[-1]) except Exception: return "PGN ERROR" def play_viri(self, fen=None): player_colour = input( "Enter the human player's colour in the form b/w\n--> ") while player_colour not in ['b', 'w']: player_colour = input( "Enter the human player's colour in the form b/w\n--> ") player_colour = WHITE if player_colour == 'w' else BLACK timeControl = int( input("how many seconds should viri get per move?\n--> ")) self.__init__(human=True, time_limit=timeControl, fen=fen, book=True, fun=False) self.fun = False while not self.node.is_game_over(): print(self.__repr__()) if player_colour == self.node.turn: # try: # self.ponder() # except KeyboardInterrupt: # self.node = self.origin # self.user_move() self.user_move() else: self.engine_move() self.display_ending() def perftx(self, n): if n == 0: self.nodes += 1 else: for move in self.ordered_moves(): self.node.push(move) self.perftx(n - 1) self.node.pop() def perft(self, n): self.nodes = 0 self.perftx(n) print(self.nodes) def uci(self): start = input() while start != "uci": start = input() print("id", end="") while True: command = input() if command == "ucinewgame": board = Board() elif command.split()[0] == "position": fen = command[command.index( "fen") + 3: command.index("moves") - 1] moves = command[command.index("moves"):].split() if fen == "startpos": fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' board = Board(fen) for move in moves: board.push(Move.from_uci(move))
def say_last_move(game: chess.Board): """Take a chess.BitBoard instance and speaks the last move from it.""" move_parts = { 'K': 'king.ogg', 'B': 'bishop.ogg', 'N': 'knight.ogg', 'R': 'rook.ogg', 'Q': 'queen.ogg', 'P': 'pawn.ogg', '+': '', '#': '', 'x': 'takes.ogg', '=': 'promote.ogg', 'a': 'a.ogg', 'b': 'b.ogg', 'c': 'c.ogg', 'd': 'd.ogg', 'e': 'e.ogg', 'f': 'f.ogg', 'g': 'g.ogg', 'h': 'h.ogg', '1': '1.ogg', '2': '2.ogg', '3': '3.ogg', '4': '4.ogg', '5': '5.ogg', '6': '6.ogg', '7': '7.ogg', '8': '8.ogg' } bit_board = game.copy() move = bit_board.pop() san_move = bit_board.san(move) voice_parts = [] if san_move.startswith('O-O-O'): voice_parts += ['castlequeenside.ogg'] elif san_move.startswith('O-O'): voice_parts += ['castlekingside.ogg'] else: for part in san_move: try: sound_file = move_parts[part] except KeyError: logging.warning('unknown char found in san: [%s : %s]', san_move, part) sound_file = '' if sound_file: voice_parts += [sound_file] if game.is_game_over(): if game.is_checkmate(): wins = 'whitewins.ogg' if game.turn == chess.BLACK else 'blackwins.ogg' voice_parts += ['checkmate.ogg', wins] elif game.is_stalemate(): voice_parts += ['stalemate.ogg'] else: if game.is_seventyfive_moves(): voice_parts += ['75moves.ogg', 'draw.ogg'] elif game.is_insufficient_material(): voice_parts += ['material.ogg', 'draw.ogg'] elif game.is_fivefold_repetition(): voice_parts += ['repetition.ogg', 'draw.ogg'] else: voice_parts += ['draw.ogg'] elif game.is_check(): voice_parts += ['check.ogg'] if bit_board.is_en_passant(move): voice_parts += ['enpassant.ogg'] return voice_parts
def main(): """ https://stackoverflow.com/a/19655992 """ # pylint: disable=too-many-branches input_queue = queue.Queue() input_thread = Thread(target=add_input, args=(input_queue,)) input_thread.daemon = True input_thread.start() board = chess.Board(INITIAL) time_manager = TimeManager() LOGGER.debug("------ NEW SESSION ------") while True: if not input_queue.empty(): smove = input_queue.get() LOGGER.warning("Command captured: %s", smove) else: if time_manager.done: move_to_go = time_manager.decision assert move_to_go is not None print('bestmove', move_to_go) continue if smove == 'quit': LOGGER.debug("UCI Quit called, Engine dismissed") break elif smove == "isready": print("readyok") elif smove == 'uci': print("id name Studienarbeitsengine") print("id author Daniel und Stefan") elif smove == "ucinewgame": board = Board() elif smove == "position startpos": board = Board() elif smove.startswith("position startpos moves"): moves = [Move.from_uci(uci=uci) for uci in smove.split(" ")[3:]] LOGGER.debug("Setting moves: %s", str(moves)) board = Board() for move in moves: board.push(move) elif smove.startswith("go"): parts = smove.split(" ")[1:] LOGGER.debug("Parts of go: %s", parts) information = process_uci_time_information(*parts)\ if len(parts) % 2 == 0 else {"infinite": True} time_manager.info_from_uci(**information) time_manager.perform_search(board.copy()) elif smove.startswith("stop"): pass else: print("Error (unkown command):", smove)
def score(bord: chess.Board, move): scr = 0 board = bord.copy() current_turn = board.turn #Material Value if board.is_capture(move): captured_piece = board.piece_type_at(move.to_square) for c in range(1, 7): if captured_piece == c: if current_turn == False: #print("black caps white") scr += COSTS[c - 1] break else: scr -= COSTS[c - 1] #print("white caps black") break #Pre-move misc # if board.is_check(): # if current_turn == 0: # scr += 5 # else: # scr -= 5 #Freedom of board # board.push(move) #New board with testing move # possible_moves = board.legal_moves # if current_turn == 0: # scr += len(list(possible_moves)) * 0.1 # else: # scr -= len(list(possible_moves)) * 0.1 #Centre control # for sq in TRUE_CENTER: # if board.piece_at(sq): # pc = board.piece_at(sq) # if pc.color == chess.WHITE: # scr += 0.5 # else: # scr -= 0.5 # # for sq in EXTENDED_CENTER: # if board.piece_at(sq): # pc = board.piece_at(sq) # if pc.color == chess.WHITE: # scr += 0.3 # else: # scr -= 0.3 #Attacks and Attackers: # for square in chess.SQUARES: # # if board.piece_at(square): # # #TODO Pins # # piece = board.piece_at(square) # attacks = board.attacks(square) # attackers = board.attackers(not piece.color, square) # # for atk_sqr in attacks: # # attacked_piece = board.piece_at(atk_sqr) # # for d in range(1, 7): # try: # if attacked_piece.piece_type == d: # # if current_turn == 0: # scr += COSTS[d - 1] / 2 # break # else: # scr -= COSTS[d - 1] / 2 # break # # except AttributeError: # pass return scr
def say_last_move(game: chess.Board): """Take a chess.BitBoard instance and speaks the last move from it.""" PicoTalkerDisplay.c_taken = False #molli PicoTalkerDisplay.c_castle = False #molli PicoTalkerDisplay.c_knight = False #molli PicoTalkerDisplay.c_rook = False #molli PicoTalkerDisplay.c_king = False #molli PicoTalkerDisplay.c_bishop = False #molli PicoTalkerDisplay.c_pawn = False #molli PicoTalkerDisplay.c_queen = False #molli PicoTalkerDisplay.c_check = False #molli PicoTalkerDisplay.c_mate = False #molli PicoTalkerDisplay.c_stalemate = False #molli PicoTalkerDisplay.c_draw = False #molli move_parts = { 'K': 'king.ogg', 'B': 'bishop.ogg', 'N': 'knight.ogg', 'R': 'rook.ogg', 'Q': 'queen.ogg', 'P': 'pawn.ogg', '+': '', '#': '', 'x': 'takes.ogg', '=': 'promote.ogg', 'a': 'a.ogg', 'b': 'b.ogg', 'c': 'c.ogg', 'd': 'd.ogg', 'e': 'e.ogg', 'f': 'f.ogg', 'g': 'g.ogg', 'h': 'h.ogg', '1': '1.ogg', '2': '2.ogg', '3': '3.ogg', '4': '4.ogg', '5': '5.ogg', '6': '6.ogg', '7': '7.ogg', '8': '8.ogg' } bit_board = game.copy() move = bit_board.pop() san_move = bit_board.san(move) voice_parts = [] if san_move.startswith('O-O-O'): voice_parts += ['castlequeenside.ogg'] PicoTalkerDisplay.c_castle = True elif san_move.startswith('O-O'): voice_parts += ['castlekingside.ogg'] PicoTalkerDisplay.c_castle = True else: for part in san_move: try: sound_file = move_parts[part] except KeyError: logging.warning('unknown char found in san: [%s : %s]', san_move, part) sound_file = '' if sound_file: voice_parts += [sound_file] if sound_file == 'takes.ogg': PicoTalkerDisplay.c_taken = True #molli elif sound_file == 'knight.ogg': PicoTalkerDisplay.c_knight = True #molli elif sound_file == 'king.ogg': PicoTalkerDisplay.c_king = True #molli elif sound_file == 'rook.ogg': PicoTalkerDisplay.c_rook = True #molli elif sound_file == 'pawn.ogg': PicoTalkerDisplay.c_pawn = True #molli elif sound_file == 'bishop.ogg': PicoTalkerDisplay.c_bishop = True #molli elif sound_file == 'queen.ogg': PicoTalkerDisplay.c_queen = True #molli if game.is_game_over(): if game.is_checkmate(): wins = 'whitewins.ogg' if game.turn == chess.BLACK else 'blackwins.ogg' voice_parts += ['checkmate.ogg', wins] PicoTalkerDisplay.c_mate = True elif game.is_stalemate(): voice_parts += ['stalemate.ogg'] PicoTalkerDisplay.c_stalemate = True else: PicoTalkerDisplay.c_draw = True if game.is_seventyfive_moves(): voice_parts += ['75moves.ogg', 'draw.ogg'] elif game.is_insufficient_material(): voice_parts += ['material.ogg', 'draw.ogg'] elif game.is_fivefold_repetition(): voice_parts += ['repetition.ogg', 'draw.ogg'] else: voice_parts += ['draw.ogg'] elif game.is_check(): voice_parts += ['check.ogg'] PicoTalkerDisplay.c_check = True if bit_board.is_en_passant(move): voice_parts += ['enpassant.ogg'] return voice_parts