def cook_advantage(engine: SimpleEngine, node: GameNode, winner: Color) -> Optional[List[NextMovePair]]: is_capture = "x" in node.san() # monkaS up_in_material = is_up_in_material(node.board(), winner) if node.board().is_repetition(2): logger.info("Found repetition, canceling") return None # if not is_capture and up_in_material and len(node.board().checkers()) == 0: # logger.info("Not a capture and we're up in material, end of the line") # return [] next = get_next_move(engine, node, winner) if not next: logger.debug("No next move") return [] if next.best.score.is_mate(): logger.info("Expected advantage, got mate?!") return None follow_up = cook_advantage(engine, node.add_main_variation(next.best.move), winner) if follow_up is None: return None return [next] + follow_up
def analyze_position(server: Server, engine: SimpleEngine, node: GameNode, prev_score: Score, current_eval: PovScore) -> Union[Puzzle, Score]: board = node.board() winner = board.turn score = current_eval.pov(winner) if board.legal_moves.count() < 2: return score game_url = node.game().headers.get("Site") logger.debug("{} {} to {}".format(node.ply(), node.move.uci() if node.move else None, score)) if prev_score > Cp(400): logger.debug("{} Too much of a winning position to start with {} -> {}".format(node.ply(), prev_score, score)) return score if is_up_in_material(board, winner): logger.debug("{} already up in material {} {} {}".format(node.ply(), winner, material_count(board, winner), material_count(board, not winner))) return score elif score >= Mate(1) and not allow_one_mover: logger.debug("{} mate in one".format(node.ply())) return score elif score > mate_soon: logger.info("Mate {}#{} Probing...".format(game_url, node.ply())) if server.is_seen_pos(node): logger.info("Skip duplicate position") return score mate_solution = cook_mate(engine, copy.deepcopy(node), winner) server.set_seen(node.game()) return Puzzle(node, mate_solution) if mate_solution is not None else score elif score >= Cp(0) and win_chances(score) > win_chances(prev_score) + 0.5: if score < Cp(400) and material_diff(board, winner) > -1: logger.info("Not clearly winning and not from being down in material, aborting") return score logger.info("Advantage {}#{} {} -> {}. Probing...".format(game_url, node.ply(), prev_score, score)) if server.is_seen_pos(node): logger.info("Skip duplicate position") return score puzzle_node = copy.deepcopy(node) solution : Optional[List[NextMovePair]] = cook_advantage(engine, puzzle_node, winner) server.set_seen(node.game()) if not solution: return score while len(solution) % 2 == 0 or not solution[-1].second: if not solution[-1].second: logger.info("Remove final only-move") solution = solution[:-1] if not solution or (len(solution) == 1 and not allow_one_mover): logger.info("Discard one-mover") return score last = list(puzzle_node.mainline())[len(solution)] gain = material_diff(last.board(), winner) - material_diff(board, winner) if gain > 1 or ( len(solution) == 1 and win_chances(solution[0].best.score) > win_chances(solution[0].second.score) + 0.5): return Puzzle(node, [p.best.move for p in solution]) return score else: return score
def is_advanced_pawn_move(node: GameNode) -> bool: if node.move.promotion: return True if moved_piece_type(node) != chess.PAWN: return False to_rank = square_rank(node.move.to_square) return to_rank < 3 if node.turn() else to_rank > 4
def get_next_move_pair(engine: SimpleEngine, node: GameNode, winner: Color, limit: chess.engine.Limit) -> NextMovePair: info = engine.analyse(node.board(), multipv = 2, limit = limit) global nps nps.append(info[0]["nps"] / 1000) nps = nps[-10000:] # print(info) best = EngineMove(info[0]["pv"][0], info[0]["score"].pov(winner)) second = EngineMove(info[1]["pv"][0], info[1]["score"].pov(winner)) if len(info) > 1 else None return NextMovePair(node, winner, best, second)
def cook_advantage(engine: SimpleEngine, node: GameNode, winner: Color) -> Optional[List[Move]]: """ Recursively calculate advantage solution """ best_move, second_move = get_two_best_moves(engine, node.board(), winner) if node.board().turn == winner: logger.debug("Getting only advantage move...") if best_move.score < juicy_advantage: logger.info( "Best move is not a juicy advantage, we're probably not searching deep enough" ) return None if second_move is not None and second_move.score > Cp(-300): logger.debug("Second best move is not terrible") return None else: logger.debug("Getting defensive move...") if best_move.score.is_mate(): logger.info("Expected advantage, got mate?!") return None if second_move is not None: much_worse = second_move.score < Mate(2) if not much_worse: logger.info("A second defensive move is not that worse") return None next_moves = cook_advantage(engine, node.add_main_variation(best_move.move), winner) if next_moves is None: return None return [best_move.move] + next_moves
def get_next_move(engine: SimpleEngine, node: GameNode, winner: Color) -> Optional[NextMovePair]: board = node.board() pair = get_next_move_pair(engine, node, winner, get_move_limit) logger.debug("{} {} {}".format("attack" if board.turn == winner else "defense", pair.best, pair.second)) if board.turn == winner and not is_valid_attack(pair): logger.debug("No valid attack {}".format(pair)) return None if board.turn != winner and not is_valid_defense(pair): logger.debug("No valid defense {}".format(pair)) return None return pair
def cook_mate(engine: SimpleEngine, node: GameNode, winner: Color) -> Optional[List[Move]]: """ Recursively calculate mate solution """ if node.board().is_game_over(): return [] best_move, second_move = get_two_best_moves(engine, node.board(), winner) if node.board().turn == winner: logger.debug("Getting only mate move...") if best_move.score < mate_soon: logger.info( "Best move is not a mate, we're probably not searching deep enough" ) return None if second_move is not None and second_move.score > Cp(-300): logger.debug("Second best move is not terrible") return None else: logger.debug("Getting defensive move...") if best_move.score < Mate(1) and second_move is not None: much_worse = second_move.score == Mate( 1) and best_move.score < Mate(3) if not much_worse: logger.info("A second defensive move is not that worse") return None next_moves = cook_mate(engine, node.add_main_variation(best_move.move), winner) if next_moves is None: return None return [best_move.move] + next_moves
def cook_mate(engine: SimpleEngine, node: GameNode, winner: Color) -> Optional[List[Move]]: if node.board().is_game_over(): return [] pair = get_next_move(engine, node, winner) if not pair: return None next = pair.best if next.score < mate_soon: logger.info("Best move is not a mate, we're probably not searching deep enough") return None follow_up = cook_mate(engine, node.add_main_variation(next.move), winner) if follow_up is None: return None return [next.move] + follow_up
def moved_piece_type(node: GameNode) -> chess.PieceType: return node.board().piece_type_at(node.move.to_square)
def is_pawn_move(node: GameNode) -> bool: return node.board().piece_type_at(node.move.to_square) == chess.PAWN