def parse_position(pgn_position: pgn.ChildNode, is_mainline: bool) -> SerializablePosition: nonlocal current_index nonlocal all_positions index = current_index current_index += 1 next_pos_index = None if pgn_position.variations: next_pos_index = parse_position(pgn_position.variations[0], True).index variations_indexes = [] if len(pgn_position.variations) > 1: variations_indexes = [ parse_position(p, False).index for i, p in enumerate(pgn_position.variations) if i > 0 ] position = SerializablePosition( index=index, next_position_index=next_pos_index, variations_indexes=variations_indexes, nags=list(pgn_position.nags), fen=pgn_position.board().fen(), comment=pgn_position.comment, commentBefore=pgn_position.starting_comment, san=pgn_position.san(), is_mainline=is_mainline, move=Move(from_square=square_name(pgn_position.move.from_square), to=square_name(pgn_position.move.to_square), promotion=pgn_position.move.promotion)) all_positions.insert(0, position) return position
def cook_mate(engine: SimpleEngine, node: ChildNode, winner: Color) -> Optional[List[Move]]: board = node.board() if board.is_game_over(): return [] if board.turn == winner: pair = get_next_pair(engine, node, winner) if not pair: return None if pair.best.score < mate_soon: logger.debug("Best move is not a mate, we're probably not searching deep enough") return None move = pair.best.move else: next = get_next_move(engine, node, mate_defense_limit) if not next: return None move = next follow_up = cook_mate(engine, node.add_main_variation(move), winner) if follow_up is None: return None return [move] + follow_up
def cook_mate(engine: SimpleEngine, node: ChildNode, 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.debug( "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 cook_advantage(engine: SimpleEngine, node: ChildNode, winner: Color) -> Optional[List[NextMovePair]]: if node.board().is_repetition(2): logger.debug("Found repetition, canceling") return None next = get_next_move(engine, node, winner) if not next: logger.debug("No next move") return [] if next.best.score.is_mate(): logger.debug("Expected advantage, got mate?!") return None if next.best.score < Cp(200): logger.debug("Not winning enough, aborting") 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: ChildNode, prev_score: Score, current_eval: PovScore, tier: int) -> 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(300) and score < mate_soon: 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 tier < 3: logger.debug("{} mate in one".format(node.ply())) return score elif score > mate_soon: logger.debug("Mate {}#{} Probing...".format(game_url, node.ply())) if server.is_seen_pos(node): logger.debug("Skip duplicate position") return score mate_solution = cook_mate(engine, copy.deepcopy(node), winner) if mate_solution is None or (tier == 1 and len(mate_solution) == 3): return score return Puzzle(node, mate_solution, 999999999) elif score >= Cp(200) and win_chances(score) > win_chances(prev_score) + 0.6: if score < Cp(400) and material_diff(board, winner) > -1: logger.debug("Not clearly winning and not from being down in material, aborting") return score logger.debug("Advantage {}#{} {} -> {}. Probing...".format(game_url, node.ply(), prev_score, score)) if server.is_seen_pos(node): logger.debug("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.debug("Remove final only-move") solution = solution[:-1] if not solution or len(solution) == 1 : logger.debug("Discard one-mover") return score if tier < 3 and len(solution) == 3: logger.debug("Discard two-mover") return score cp = solution[len(solution) - 1].best.score.score() return Puzzle(node, [p.best.move for p in solution], 999999998 if cp is None else cp) else: return score
def is_advanced_pawn_move(node: ChildNode) -> 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_pair(engine: SimpleEngine, node: ChildNode, winner: Color) -> Optional[NextMovePair]: pair = get_next_move_pair(engine, node, winner, pair_limit) if node.board().turn == winner and not is_valid_attack(pair, engine): logger.debug("No valid attack {}".format(pair)) return None return pair
def cook_advantage(engine: SimpleEngine, node: ChildNode, winner: Color) -> Optional[List[NextMovePair]]: board = node.board() if board.is_repetition(2): logger.debug("Found repetition, canceling") return None pair = get_next_pair(engine, node, winner) if not pair: return [] if pair.best.score < Cp(200): logger.debug("Not winning enough, aborting") return None follow_up = cook_advantage(engine, node.add_main_variation(pair.best.move), winner) if follow_up is None: return None return [pair] + follow_up
def get_next_move(engine: SimpleEngine, node: ChildNode, 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, engine): 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 get_next_move(engine: SimpleEngine, node: ChildNode, limit: chess.engine.Limit) -> Optional[Move]: result = engine.play(node.board(), limit = limit) return result.move if result else None
def moved_piece_type(node: ChildNode) -> chess.PieceType: pt = node.board().piece_type_at(node.move.to_square) assert pt return pt
def is_very_advanced_pawn_move(node: ChildNode) -> bool: if not is_advanced_pawn_move(node): return False to_rank = square_rank(node.move.to_square) return to_rank < 2 if node.turn() else to_rank > 5