示例#1
0
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
示例#2
0
    def test_not_investigating_major_advantage_to_mate_threat(self):
        a = Cp(900)
        b = Mate(5)
        self.assertFalse(should_investigate(a, b, board))

        a = Cp(-900)
        b = Mate(-5)
        self.assertFalse(should_investigate(a, b, board))
示例#3
0
    def test_investigating_minor_advantage_to_mate(self):
        a = Cp(100)
        b = Mate(5)
        self.assertTrue(should_investigate(a, b, board))

        a = Cp(-100)
        b = Mate(-5)
        self.assertTrue(should_investigate(a, b, board))
示例#4
0
    def test_investigating_major_advantage_to_getting_mated(self):
        a = Cp(700)
        b = Mate(-5)
        self.assertTrue(should_investigate(a, b, board))

        a = Cp(-700)
        b = Mate(5)
        self.assertTrue(should_investigate(a, b, board))
示例#5
0
    def test_investigating_major_advantage_to_even_position(self):
        a = Cp(700)
        b = Cp(0)
        self.assertTrue(should_investigate(a, b, board))

        a = Cp(-700)
        b = Cp(0)
        self.assertTrue(should_investigate(a, b, board))
示例#6
0
    def test_investigating_mate_threat_to_major_disadvantage(self):
        a = Mate(5)
        b = Cp(-700)
        self.assertTrue(should_investigate(a, b, board))

        a = Mate(-5)
        b = Cp(700)
        self.assertTrue(should_investigate(a, b, board))
示例#7
0
    def test_investigating_mate_threat_to_even_position(self):
        a = Mate(5)
        b = Cp(0)
        self.assertTrue(should_investigate(a, b, board))

        a = Mate(-5)
        b = Cp(0)
        self.assertTrue(should_investigate(a, b, board))
示例#8
0
    def test_investigating_even_position_to_mate(self):
        a = Cp(0)
        b = Mate(5)
        self.assertTrue(should_investigate(a, b, board))

        a = Cp(0)
        b = Mate(-5)
        self.assertTrue(should_investigate(a, b, board))
示例#9
0
    def test_not_investigating_even_position(self):
        board = Board("4k3/8/3n4/3N4/8/8/4K3/8 w - - 0 1")

        a = Cp(0)
        b = Cp(0)
        self.assertFalse(should_investigate(a, b, board))

        a = Cp(9)
        b = Cp(9)
        self.assertFalse(should_investigate(a, b, board))
示例#10
0
 def test_investigating_moderate_score_changes(self):
     score_changes = [
         [0, 200],
         [50, 200],
         [-50, 200],
     ]
     for a, b in score_changes:
         a = Cp(a)
         b = Cp(b)
         self.assertTrue(should_investigate(a, b, board))
示例#11
0
 def test_not_ambiguous_slight_advantage_vs_significant_disadvantage(self):
     self.assertFalse(ambiguous_best_move([
         Cp(146),
         Cp(-405),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(149),
         Cp(-458),
         Cp(-543),
     ]))
示例#12
0
 def test_investigating_major_score_changes(self):
     score_changes = [
         [0, 500],
         [100, 500],
         [100, -100],
     ]
     for a, b in score_changes:
         a = Cp(a)
         b = Cp(b)
         self.assertTrue(should_investigate(a, b, board))
示例#13
0
 def test_not_investigating_insignificant_score_changes(self):
     score_changes = [
         [0, 0],
         [-50, 50],
         [50, -50],
         [-70, -70],
         [70, 70],
     ]
     for a, b in score_changes:
         a = Cp(a)
         b = Cp(b)
         self.assertFalse(should_investigate(a, b, board))
示例#14
0
 def cruncher(thread_id: int):
     eval_nb = 0
     db = pymongo.MongoClient()['puzzler']
     bad_coll = db['puzzle2_bad_maybe']
     play_coll = db['puzzle2_puzzle']
     engine = SimpleEngine.popen_uci('./stockfish')
     engine.configure({'Threads': 4})
     for doc in bad_coll.find({"bad": {"$exists": False}}):
         try:
             if ord(doc["_id"][4]) % threads != thread_id:
                 continue
             doc = play_coll.find_one({'_id': doc['_id']})
             if not doc:
                 continue
             puzzle = read(doc)
             board = puzzle.mainline[len(puzzle.mainline) - 2].board()
             info = engine.analyse(
                 board,
                 multipv=5,
                 limit=chess.engine.Limit(nodes=30_000_000))
             bad = False
             for score in [pv["score"].pov(puzzle.pov) for pv in info]:
                 if score < Mate(1) and score > Cp(250):
                     bad = True
             # logger.info(puzzle.id)
             bad_coll.update_one({"_id": puzzle.id},
                                 {"$set": {
                                     "bad": bad
                                 }})
         except Exception as e:
             logger.error(e)
示例#15
0
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
示例#16
0
def find_puzzle_candidates(game: Game, scan_depth=SCAN_DEPTH) -> List[Puzzle]:
    """ finds puzzle candidates from a chess game 
    """
    log(Color.DIM, "Scanning game for puzzles (depth: %d)..." % scan_depth)
    prev_score = Cp(0)
    puzzles = []
    i = 0
    node = game
    while not node.is_end():
        next_node = node.variation(0)
        next_board = next_node.board()
        cur_score = AnalysisEngine.best_move(next_board, scan_depth).score
        board = node.board()
        highlight_move = False
        if should_investigate(prev_score, cur_score, board):
            highlight_move = True
            puzzle = Puzzle(
                board,
                next_node.move,
            )
            puzzles.append(puzzle)
        log_move(board, next_node.move, cur_score, highlight=highlight_move)
        prev_score = cur_score
        node = next_node
        i += 1
    return puzzles
示例#17
0
def analyze_game(server: Server, engine: SimpleEngine, game: Game,
                 args: argparse.Namespace) -> Optional[Puzzle]:

    logger.debug("Analyzing game {}...".format(game.headers.get("Site")))

    prev_score: Score = Cp(20)

    for node in game.mainline():

        current_eval = node.eval()

        if not current_eval:
            logger.debug("Skipping game without eval on ply {}".format(
                node.ply()))
            return None

        result = analyze_position(server, engine, node, prev_score,
                                  current_eval, args)

        if isinstance(result, Puzzle):
            return result

        prev_score = -result

    logger.debug("Found nothing from {}".format(game.headers.get("Site")))

    return None
示例#18
0
def is_valid_attack(pair: NextMovePair) -> bool:
    if pair.second is None:
        return True
    if pair.best.score == Mate(1):
        return True
    if pair.best.score == Mate(2):
        return pair.second.score < Cp(500)
    if pair.best.score == Mate(3):
        return pair.second.score < Cp(300)
    if win_chances(pair.best.score) > win_chances(pair.second.score) + 0.5:
        return True
    # if best move is mate, and second move still good but doesn't win material,
    # then best move is valid attack
    if pair.best.score.is_mate() and pair.second.score < Cp(400):
        next_node = pair.node.add_variation(pair.second.move)
        return not "x" in next_node.san()
    return False
示例#19
0
def analyze_game(engine: SimpleEngine,
                 game: Game) -> Optional[Tuple[GameNode, List[Move], Kind]]:
    """
    Evaluate the moves in a game looking for puzzles
    """

    game_url = game.headers.get("Site", "?")
    logger.debug("Analyzing game {}...".format(game_url))

    prev_score: Score = Cp(0)

    for node in game.mainline():

        ev = node.eval()

        if not ev:
            logger.debug("Skipping game without eval on move {}".format(
                node.board().fullmove_number))
            return None

        winner = node.board().turn
        score = ev.pov(winner)

        # was the opponent winning until their last move
        if prev_score > Cp(-300) or not is_down_in_material(
                node.board(), winner):
            pass
        elif mate_soon < score < Mate(1):
            logger.info("Mate found on {}#{}. Probing...".format(
                game_url, ply_of(node.board())))
            solution = cook_mate(engine, node, winner)
            if solution is not None:
                return node, solution, Kind("mate")
        elif score > juicy_advantage:
            logger.info("Advantage found on {}#{}. Probing...".format(
                game_url, ply_of(node.board())))
            solution = cook_advantage(engine, node, winner)
            if solution is not None:
                return node, solution, Kind("mate")
        else:
            print(score)

        prev_score = score

    return None
示例#20
0
 def test_not_ambiguous_significant_advantage_vs_slight_advantage(self):
     self.assertFalse(ambiguous_best_move([
         Cp(379),
         Cp(78),
         Cp(77),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(365),
         Cp(110),
         Cp(95),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(-683),
         Cp(-81),
         Cp(-65),
     ]))
示例#21
0
 def test_not_ambiguous_equality_vs_slight_disadvantage(self):
     self.assertFalse(ambiguous_best_move([
         Cp(3),
         Cp(-131),
         Cp(-200),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(3),
         Cp(-170),
         Cp(-371),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(0),
         Cp(-282),
         Cp(-293),
     ]))
示例#22
0
 def should_resign(self, move_list):
     """
     Have ai decide if it wishes to resign or offer a draw,
     returning true if it wishes to resign
     """
     # If the best move is still pretty bad, then resign
     best_move = move_list[0]
     move_score = self.get_move_score(best_move)
     # TODO may still need to fine tune
     if move_score > Cp(2000):
         return True
     return False
示例#23
0
 def test_not_ambiguous_equality_vs_significant_disadvantage(self):
     self.assertFalse(ambiguous_best_move([
         Cp(0),
         Cp(-825),
         Cp(-1079),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(-36),
         Cp(-485),
         Cp(-504),
     ]))
     self.assertFalse(ambiguous_best_move([
         Cp(0),
         Cp(-963),
         Mate(-12),
     ]))
示例#24
0
    def analyze_game(self, game: Game, tier: int) -> Optional[Puzzle]:

        logger.debug(f'Analyzing tier {tier} {game.headers.get("Site")}...')

        prev_score: Score = Cp(20)
        seen_epds: Set[str] = set()
        board = game.board()
        skip_until_irreversible = False

        for node in game.mainline():
            if skip_until_irreversible:
                if board.is_irreversible(node.move):
                    skip_until_irreversible = False
                    seen_epds.clear()
                else:
                    board.push(node.move)
                    continue

            current_eval = node.eval()

            if not current_eval:
                logger.debug("Skipping game without eval on ply {}".format(
                    node.ply()))
                return None

            board.push(node.move)
            epd = board.epd()
            if epd in seen_epds:
                skip_until_irreversible = True
                continue
            seen_epds.add(epd)

            if board.castling_rights != maximum_castling_rights(board):
                continue

            result = self.analyze_position(node, prev_score, current_eval,
                                           tier)

            if isinstance(result, Puzzle):
                return result

            prev_score = -result

        logger.debug("Found nothing from {}".format(game.headers.get("Site")))

        return None
    def test_eval_last_move_no_blunder_no_mate(self):
        board = chess.Board()
        board.push_san("g4")
        board.push_san("e5")
        game = Game()
        game.board = board
        game.player1 = FakeDiscordUser(id=1)
        game.player2 = FakeDiscordUser(id=2)
        game.current_player = game.player1
        game.last_eval = Cp(0)

        chess_bot = Chess()
        chess_bot.games.append(game)
        chess_bot.stockfish_limit["time"] = 2

        result = asyncio.run(chess_bot.eval_last_move(game))

        self.assertFalse(result["blunder"])
        self.assertIsNone(result["mate_in"])
示例#26
0
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
示例#27
0
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
示例#28
0
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 test_eval_last_move_lost_position_blunders_mate(self):
        board = chess.Board(
            "Q1kr4/1p6/1P3ppp/1Kp1r3/4p2b/1B3P2/2P2q2/8 b - - 5 43")
        game = Game()
        game.board = board
        game.player1 = FakeDiscordUser(id=1)
        game.player2 = FakeDiscordUser(id=2)
        game.current_player = game.player1
        game.last_eval = Cp(1000)

        chess_bot = Chess()
        chess_bot.games.append(game)

        result = asyncio.run(chess_bot.eval_last_move(game))

        if chess_bot.is_stockfish_enabled():
            self.assertTrue(result["blunder"])
            self.assertEqual(result["mate_in"], -2)
        else:
            self.assertFalse(result["blunder"])
            self.assertIsNone(result["mate_in"])
    def test_eval_last_move_last_move_blunder_mate_in_one(self):
        board = chess.Board()
        board.push_san("g4")
        board.push_san("e5")
        board.push_san("f4")
        game = Game()
        game.board = board
        game.player1 = FakeDiscordUser(id=1)
        game.player2 = FakeDiscordUser(id=2)
        game.current_player = game.player1
        game.last_eval = Cp(0)

        chess_bot = Chess()
        chess_bot.games.append(game)
        result = asyncio.run(chess_bot.eval_last_move(game))

        if chess_bot.is_stockfish_enabled():
            self.assertTrue(result["blunder"])
            self.assertEqual(result["mate_in"], 1)
        else:
            self.assertFalse(result["blunder"])
            self.assertIsNone(result["mate_in"])