示例#1
0
    def get_eval(
            self,
            comment: str,
            turn: bool,
            ply: int,
            black_eval: List[float],
            white_eval: List[float]
    ) -> float:
        """
        Returns move_eval with SPOV in pawn unit.
        """
        move_eval = 0.0

        if 'book' in comment.lower():
            return move_eval

        if comment == '':
            if ply % 2:
                move_eval = -black_eval[-1]
            else:
                move_eval = white_eval[-1]
            return move_eval

        if self.tcec:
            value = comment.split('wv=')[1].split(',')[0]
            if 'M' in value:
                mate_num = int(value.split('M')[1])

                # Todo: Get mate score of Lc0.
                move_eval = Mate(mate_num).score(mate_score=32000) / 100

                move_eval = move_eval if turn else -move_eval
            else:
                move_eval = float(comment.split('wv=')[1].split(',')[0])
                move_eval = move_eval if turn else -move_eval

        # Cutechess
        else:
            # No eval/depth comment, just time.
            if len(comment.split()) == 1:
                value = comment.split('s')[0]
                # {0}
                try:
                    if ply % 2:
                        move_eval = -black_eval[-1]
                    else:
                        move_eval = white_eval[-1]
                except ValueError:
                    pass
            elif '+M' in comment or '-M' in comment:
                mate_num = int(comment.split('/')[0].split('M')[1])
                move_eval = Mate(mate_num).score(mate_score=32000)
                move_eval = (move_eval if '+M' in comment else -move_eval) / 100
            else:
                # Not {White mates}
                if '/' in comment:
                    move_eval = float(comment.split('/')[0])

        return move_eval
示例#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_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))
示例#6
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))
示例#7
0
    def test_investigating_mate_threat_to_getting_mated(self):
        a = Mate(1)
        b = Mate(-1)
        self.assertTrue(should_investigate(a, b, board))

        a = Mate(-1)
        b = Mate(1)
        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 is_valid_mate_in_one(pair: NextMovePair, engine: SimpleEngine) -> bool:
    if pair.best.score != Mate(1):
        return False
    non_mate_win_threshold = 0.6
    if not pair.second or win_chances(pair.second.score) <= non_mate_win_threshold:
        return True
    if pair.second.score == Mate(1):
        # if there's more than one mate in one, gotta look if the best non-mating move is bad enough
        logger.debug('Looking for best non-mating move...')
        info = engine.analyse(pair.node.board(), multipv = 5, limit = pair_limit)
        for score in [pv["score"].pov(pair.winner) for pv in info]:
            if score < Mate(1) and win_chances(score) > non_mate_win_threshold:
                return False
        return True
    return False
示例#10
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)
示例#11
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
示例#12
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
示例#13
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
示例#14
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),
     ]))
示例#15
0
 def test_not_ambiguous_mate_vs_counter_mate(self):
     self.assertFalse(ambiguous_best_move([
         Mate(1),
         Mate(-14),
         Mate(-11),
     ]))
     self.assertFalse(ambiguous_best_move([
         Mate(-2),
         Mate(10),
         Mate(8),
     ]))
示例#16
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
示例#17
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
    def test_eval_last_move_no_blunder_mate_in_two(self):
        board = chess.Board(
            "Q2r4/1p1k4/1P3ppp/1Kp1r3/4p2b/1B3P2/2P2q2/8 w - - 6 44")
        game = Game()
        game.board = board
        game.player1 = FakeDiscordUser(id=1)
        game.player2 = FakeDiscordUser(id=2)
        game.current_player = game.player1
        game.last_eval = Mate(2)

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

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

        self.assertFalse(result["blunder"])
        if chess_bot.is_stockfish_enabled():
            self.assertEqual(result["mate_in"], 2)
        else:
            self.assertIsNone(result["mate_in"])
示例#19
0
 def test_puzzle_16(self):
     self.get_puzzle("kr6/p5pp/Q4np1/3p4/6P1/2P1qP2/PK4P1/3R3R w - - 1 26",
             Cp(-30), "b2a1", Mate(1), "e3c3")
示例#20
0
 def test_puzzle_10(self) -> None:
     self.get_puzzle("5rk1/pp3p2/1q1R3p/6p1/5pBb/2P4P/PPQ2PP1/3Rr1K1 w - - 6 26",
             Cp(-450), "g1h2", Mate(2), "h4g3 f2g3 b6g1")
示例#21
0
 def test_puzzle_9(self) -> None:
     self.get_puzzle("7k/p3r1bP/1p1rp2q/8/2PBB3/4P3/P3KQ2/6R1 b - - 0 38",
             Cp(-110), "e6e5", Mate(2), "f2f8 g7f8 g1g8")
示例#22
0
 def test_puzzle_3(self) -> None:
     # https://lichess.org/wQptHOX6/white#61
     self.get_puzzle("1r4k1/5p1p/pr1p2p1/q2Bb3/2P5/P1R3PP/KB1R1Q2/8 b - - 1 31",
             Cp(-4), "e5c3", Mate(3), "f2f7 g8h8 f7f6 c3f6 b2f6")
示例#23
0
 def test_puzzle_1(self) -> None:
     # https://lichess.org/analysis/standard/3q1k2/p7/1p2Q2p/5P1K/1P4P1/P7/8/8_w_-_-_5_57#112
     self.get_puzzle("3q1k2/p7/1p2Q2p/5P1K/1P4P1/P7/8/8 w - - 5 57",
             Cp(-1000), "h5g6", Mate(2), "d8g5 g6h7 g5g7")
示例#24
0
 def test_not_puzzle_15(self) -> None:
     # https://lichess.org/nq1x9tln/black#76
     self.not_puzzle("3r4/8/2p2n2/7k/P1P4p/1P6/2K5/6R1 w - - 0 43",
             Cp(-1000), "b3b4", Mate(4))
示例#25
0
 def test_not_puzzle_13(self):
     self.not_puzzle("8/5p1k/4p1p1/4Q3/3Pp1Kp/4P2P/5qP1/8 w - - 2 44",
             Cp(-6360), "e5e4", Mate(1))
示例#26
0
    def go(self, command):
        self.send(command)

        d = {}

        for eline in iter(self.__engine__.stdout.readline, ''):
            line = eline.rstrip()

            if 'depth' in line and 'score' in line and 'multipv' in line:
                if 'score cp' in line:
                    score = int(line.split('score cp')[1].split()[0])
                elif 'score mate' in line:
                    mate_num = int(line.split('score mate')[1].split()[0])

                    # Todo: Create a formula to convert mate score to cp score.
                    score = Mate(mate_num).score(mate_score=32000)
                else:
                    print('info string missing score, use 0.')
                    sys.stdout.flush()
                    score = 0

                mpv = int(line.split('multipv')[1].split()[0])
                pv = line.split(' pv ')[1].split()[0]
                d.update({mpv: [pv, score]})

            if 'bestmove ' in eline:
                # Returns a move based on prng.
                K = self.K
                logging.info(f'K = {self.K}')

                top_score = d[1][1] / 100

                cnt, num, moves, scores = 0, [], [], []
                for k, v in d.items():
                    cnt += 1
                    num.append(cnt)
                    moves.append(v[0])
                    scores.append(round(v[1] / 100, 2))

                d = []
                for i, s in enumerate(scores):
                    d.append(round(s - top_score, 2))

                p = proba(d, K)
                f = F(p)

                data = {
                    'num': num,
                    'move': moves,
                    'scores': scores,
                    'd(i)': d,
                    'P(i)': p,
                    'F(i)': f
                }
                df = pd.DataFrame(data)

                print(df.to_string(index=False))
                sys.stdout.flush()

                move_num, rn = search_move_num(d, f)
                bestmove = moves[move_num - 1]
                bestscore = scores[move_num - 1]

                print(
                    f'info string movenumber {move_num} randomnumber {rn} minf {min(f)} maxf {max(f)}'
                )
                print(f'info score cp {int(bestscore*100)}')
                sys.stdout.flush()

                print(f'bestmove {bestmove}')
                sys.stdout.flush()

                break
示例#27
0
def is_valid_defense(pair: NextMovePair) -> bool:
    return True
    if pair.second is None or pair.second.score == Mate(1):
        return True
    return win_chances(pair.second.score) > win_chances(pair.best.score) + 0.25
示例#28
0
from chess import Move, Color
from chess.engine import SimpleEngine, Mate, Cp, Score, PovScore
from chess.pgn import Game, ChildNode
from typing import List, Optional, Union
from util import get_next_move_pair, material_count, material_diff, is_up_in_material, win_chances
from server import Server

version = 47

logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)-4s %(message)s', datefmt='%m/%d %H:%M')

pair_limit = chess.engine.Limit(depth = 50, time = 30, nodes = 30_000_000)
mate_defense_limit = chess.engine.Limit(depth = 15, time = 10, nodes = 10_000_000)

mate_soon = Mate(15)

def is_valid_mate_in_one(pair: NextMovePair, engine: SimpleEngine) -> bool:
    if pair.best.score != Mate(1):
        return False
    non_mate_win_threshold = 0.6
    if not pair.second or win_chances(pair.second.score) <= non_mate_win_threshold:
        return True
    if pair.second.score == Mate(1):
        # if there's more than one mate in one, gotta look if the best non-mating move is bad enough
        logger.debug('Looking for best non-mating move...')
        info = engine.analyse(pair.node.board(), multipv = 5, limit = pair_limit)
        for score in [pv["score"].pov(pair.winner) for pv in info]:
            if score < Mate(1) and win_chances(score) > non_mate_win_threshold:
                return False
        return True
示例#29
0
 def test_not_puzzle_11(self) -> None:
     self.not_puzzle("2kr3r/ppp2pp1/1b6/1P2p3/4P3/P2B2P1/2P2PP1/R4RK1 w - - 0 18",
             Cp(20), "f1d1", Mate(4))
示例#30
0
 def test_not_puzzle_14(self) -> None:
     # https://lichess.org/nq1x9tln/black#76
     self.not_puzzle("3R4/1Q2nk2/4p2p/4n3/BP3ppP/P7/5PP1/2r3K1 w - - 2 39",
             Cp(-1000), "g1h2", Mate(4))