예제 #1
0
def indefinite_analysis(engine):
    with engine.analysis(chess.Board()) as analysis:
        for info in analysis:
            print(info.get("score"), info.get("pv"))

            # Arbitrary stop condition.
            if info.get("seldepth", 0) > 20:
                break
예제 #2
0
def solve(engine, epd, max_time=2):
    board = chess.Board()
    case = board.set_epd(epd)
    bm = case["bm"][0]
    with engine.analysis(
        board, info=chess.engine.INFO_BASIC | chess.engine.INFO_PV
    ) as analysis:
        t0 = time.time()
        for info in analysis:
            if time.time() - t0 >= max_time:
                return False
            best_move = info.get("pv")
            if best_move is not None and best_move[0] == bm:
                return True
예제 #3
0
    def _start_engine(self, index, cmds, infinite):
        engine = self._engines[index]
        ##engine.ucinewgame()
        #engine.position(self.board)

        if infinite:
            limit = None
        else:
            white_clock = int(int(cmds.get("wtime")) /
                              1000) if cmds.get("wtime") is not None else None
            black_clock = int(int(cmds.get("btime")) /
                              1000) if cmds.get("btime") is not None else None
            white_inc = int(int(cmds.get("winc")) /
                            1000) if cmds.get("winc") is not None else None
            black_inc = int(int(cmds.get("binc")) /
                            1000) if cmds.get("binc") is not None else None
            depth = int(
                cmds.get("depth")) if cmds.get("depth") is not None else None
            nodes = int(
                cmds.get("nodes")) if cmds.get("nodes") is not None else None
            time = int(int(cmds.get("movetime")) /
                       1000) if cmds.get("movetime") is not None else None
            mate = int(
                cmds.get("mate")) if cmds.get("mate") is not None else None
            remaining_moves = int(cmds.get("movestogo")) if cmds.get(
                "movestogo") is not None else None

            limit = chess.engine.Limit(white_clock=white_clock,
                                       black_clock=black_clock,
                                       white_inc=white_inc,
                                       black_inc=black_inc,
                                       depth=depth,
                                       nodes=nodes,
                                       time=time,
                                       mate=mate,
                                       remaining_moves=remaining_moves)

        self._results[index] = engine.analysis(self.board, limit)
        ## print_and_flush(self._results[index])

        engineName = self.engineFileNames[index]
        if index == 0:
            print_and_flush("info string started engine 0 " + engineName +
                            " as boss, limit: " + str(limit) + ", infinite:" +
                            str(infinite))
        else:
            print_and_flush("info string started engine 1 " + engineName +
                            " as clerk, limit: " + str(limit) + ", infinite:" +
                            str(infinite))
예제 #4
0
	def __engine(self, tName):
		'''
		Engine analyzing the current board
		This is always run in a separate thread by self.spawnEngine()
		tName str name of the thread
		The thread running this engine will quit if it is no longer named
		as the active engine in self.boardPane.activeEngine
		'''
		print(f"Engine {tName} On.")
		board = self.boardPane.curNode.board()
		engine = chess.engine.SimpleEngine.popen_uci(self.enginePath)
		with engine.analysis(board) as analysis:
			for info in analysis:
				# if this is no longer the active engine, kill this thread
				try:
					if self.boardPane.activeEngine != tName:
						print(f"Engine {tName} Off.")
						break
				# if the boardPane was closed by the user
				except:
					print(f"Board closed while analysis active")
					print(f"Engine {tName} Off.")
					break

				pv = info.get('pv')
				if pv != None and (len(pv) > 5 or info.get("score").is_mate()):
					output = analysisHeader.format(
						score = info.get("score").white(),
						depth = info.get('depth'),
						nps = info.get('nps'),
						nodes = info.get('nodes'),
						time = info.get('time'),
						pvString = board.variation_san(pv)	
					)
					# if the board pane has been closed by the user, catch the exception and quit
					try:
						self.boardPane.analysis.delete('0.0', 'end')
						self.boardPane.analysis.insert("0.1", output)
					except:
						print(f"Board closed while analysis active")
						print(f"Engine {tName} Off.")
						break
		engine.quit()
예제 #5
0
def chooseMove(board, piece, turn):
    bestMove = {"cp": chess.engine.Cp(-10000), "move": chess.Move.null()}
    for move in board.legal_moves:
        if (board.piece_at(
                move.from_square).symbol().lower() == piece.lower()):
            board.push(move)

            analysis = engine.analysis(board,
                                       chess.engine.Limit(time=0.3, depth=15))
            analysis.get()

            if (bestMove['cp'].score() <
                    analysis.info['score'].pov(turn).score()):
                bestMove['cp'] = analysis.info['score'].pov(turn)
                bestMove['move'] = move
            board.pop()

    selectedMove = bestMove['move']
    print('BestMove:', selectedMove.uci(), 'Score', bestMove['cp'])
    board.push(selectedMove)
    printBoard()
    return selectedMove.uci()
예제 #6
0
def analysis(engine,
             display,
             board=chess.Board(),
             end=lambda: False,
             options={"multipv": 3}):
    """
    Syncronous analysis for use in a threading.Thread

    :param engine: the awaited chess.engine.popen_uci engine
    :param display: the display queue for analysis
    :param board: the chess.Board() of the position
    :param end: the function telling whether to stop analysis
    """
    try:
        with engine.analysis(board, **options) as analysis:
            for info in analysis:
                display.add(info, chess.Board(board.fen()))

                if end():
                    return
    except BaseException:
        return
예제 #7
0
def game_analysis():
    engine = chess.engine.SimpleEngine.popen_uci("stockfish")
    # test_fen = 'r1bq2n1/pp3kbQ/2n2p2/3p1P2/3Pp2p/2P5/PP4P1/RNB1KB1R w KQ - 0 14'
    test_fen = 'r1bq2n1/pp3kbQ/2n2p1B/3p1P2/3Pp2p/2P5/PP4P1/RN2KB1R b KQ - 1 14'
    test_board = chess.Board(test_fen)

    if test_board.turn:
        print('White to move')
    else:
        print('black to move')

    # with pd.option_context("display.max_rows", None, "display.max_columns", None):
    with engine.analysis(test_board) as analysis:
        for index, info in enumerate(analysis):
            #         print(info.get("score"), info.get("pv"))
            #         print(f'{index} => {info.get("score")}, PV: {info.get("pv")}')
            print(f'{index} => {info.get("score")}')
            # print
            # str(board.san(el)), 'eval = ', round(handler.info["score"][1].cp / 100.0, 2)

            # Arbitrary stop condition.
            if info.get("seldepth", 0) > 30:
                break
    engine.quit()
예제 #8
0
def analyze_game(game,
                 engine,
                 enginefn,
                 hash_val,
                 thread_val,
                 analysis_start_move_num,
                 outepdfn,
                 gcnt,
                 engname,
                 dullfn,
                 outpgnfn,
                 mintime=5.0,
                 maxtime=15.0,
                 minscorediffcheck=25,
                 minbs1th1=2000,
                 minbs1th2=1000,
                 minbs1th3=500,
                 maxbs2th1=300,
                 maxbs2th2=200,
                 maxbs2th3=100,
                 weightsfile=None,
                 skipdraw=False,
                 pin=False,
                 positional=False,
                 minpiecevalue=0,
                 maxpiecevalue=62,
                 disable_complexity=False,
                 save_last_move=False):
    """ Analyze positons in the game and save interesting and dull positions to a file """

    limit = chess.engine.Limit(time=maxtime)

    # Copy orig game header to our epd output
    ev = game.headers['Event']
    si = game.headers['Site']
    da = game.headers['Date']
    ro = game.headers['Round']
    wp = game.headers['White']
    bp = game.headers['Black']
    res = game.headers['Result']

    # If result of this game is a draw and skipdraw is true, we skip it
    if skipdraw and res == '1/2-1/2':
        return

    c0_val = wp + ' - ' + bp + ', ' + ev + ', ' + si + ', ' + da + ', R' + ro

    poscnt = 0

    # Parse move in reverse
    game_end = game.end()
    curboard = game_end.board()

    while curboard:
        board = curboard
        fmvn = board.fullmove_number

        if fmvn < analysis_start_move_num:
            logging.warning('move start limit is reached, exit from this game')
            break

        g_move = board.pop()
        curboard = board
        fen = curboard.fen()

        # Print the fen before g_move is made on the board
        poscnt += 1

        print('game {} / position {} \r'.format(gcnt, poscnt), end='')

        logging.info('game {} / position {}'.format(gcnt, poscnt))
        logging.info('{}'.format(board.fen()))
        logging.info('game move: {}'.format(curboard.san(g_move)))

        # piece value conditions
        pcval = piece_value(curboard)
        if pcval < minpiecevalue:
            logging.warning(
                'Skip this pos piece value {} is below minimmum of {}'.format(
                    pcval, minpiecevalue))
            continue

        if pcval > maxpiecevalue:
            logging.warning(
                'Skip this pos and game piece value {} is above maximum of {}'.
                format(pcval, maxpiecevalue))
            break

        # Skip this position if --pin is set and no one of the not stm piece is pinned
        if pin and not abs_pinned(board, board.turn ^ 1):
            logging.warning('Skip this pos no piece of not stm is pinned')
            continue

        # If side to move is in check, skip this position
        if board.is_check():
            logging.warning('Skip this pos, stm is in check')
            continue

        # Run engine in multipv 2
        logging.info('{} is searching at multipv {} for {}s ...'.format(
            engname, 2, maxtime))

        bm1, bm2, depth = None, None, None
        raw_pv = None
        bestmovechanges = 0  # Start comparing bestmove1 at depth 4
        tmpmove, oldtmpmove = None, None

        # Run engine at multipv 2
        with engine.analysis(board, limit, multipv=2) as analysis:
            for info in analysis:
                time.sleep(0.01)
                try:
                    multipv = info['multipv']
                    depth = info['depth']
                    if info['score'].is_mate():
                        s = info['score'].relative.score(mate_score=32000)
                    else:
                        s = info['score'].relative.score()
                    pv = info['pv'][0:5]
                    t = info['time']

                    if multipv == 1:
                        bm1 = pv[0]
                        bs1 = s
                        raw_pv = pv

                        # Exit early if score is below half of minbest1score3
                        if t >= mintime and bs1 < minbs1th3 / 2:
                            logging.warning(
                                'Exit search early, current best score is only {}'
                                .format(bs1))
                            break

                        # Record bestmove move changes to determine position complexity
                        if 'depth' in info and 'pv' in info \
                                and 'score' in info \
                                and not 'lowerbound' in info \
                                and not 'upperbound' in info \
                                and depth >= 4:
                            tmpmove = info['pv'][0]
                            if oldtmpmove is not None and tmpmove != oldtmpmove:
                                bestmovechanges += 1

                    elif multipv == 2:
                        bm2 = pv[0]
                        bs2 = s

                    # Save analysis time by exiting it if score difference
                    # between bestscore1 and bestcore2 is way below the
                    # minimum score difference based from user defined
                    # score thresholds
                    if t >= mintime and bs1 - bs2 < minscorediffcheck:
                        logging.warning(
                            'Exit search early, scorediff of {} is below minscorediff of {}'
                            .format(bs1 - bs2, minscorediffcheck))
                        break

                    oldtmpmove = tmpmove

                except (KeyError):
                    pass
                except Exception as e:
                    logging.error(
                        'Unexpected exception {} in parsing engine analysis'.
                        format(e))

        time.sleep(0.1)

        logging.info('Search is done!!'.format(engname))
        logging.info('game move       : {} ({})'.format(
            g_move, curboard.san(g_move)))
        logging.info('complexity      : {}'.format(bestmovechanges))
        logging.info('best move 1     : {}, best score 1: {}'.format(bm1, bs1))
        logging.info('best move 2     : {}, best score 2: {}'.format(bm2, bs2))
        logging.info('scorediff       : {}'.format(bs1 - bs2))

        # Don't save positions if score is already bad
        if bs1 < minbs1th3:
            logging.warning(
                'Skip this pos, score {} is below minbs1th3 of {}'.format(
                    bs1, minbs1th3))
            continue

        # If complexity is 1 or less and if bestmove1 is a capture, skip this position
        if board.is_capture(
                bm1) and not disable_complexity and bestmovechanges <= 1:
            logging.warning(
                'Skip this pos, bm1 is a capture and pos complexity is below 2'
            )
            continue

        if bs1 - bs2 < minbs1th3 - maxbs2th3:
            logging.warning(
                'Skip this pos, min score diff of {} is below user min score diff of {}'
                .format(bs1 - bs2, minbs1th3 - maxbs2th3))
            continue

        # Filter on --positional to skip positions
        if positional:
            # (1) Skip if bestmove1 is a capture or promote
            if board.is_capture(bm1) or len(str(bm1)) == 5:
                logging.warning(
                    'Skip this pos, the bestmove1 is a {} move'.format(
                        'promote' if len(str(bm1)) == 5 else 'capture'))
                continue

        # Save epd if criteria is satisfied
        is_save = False
        if positional:
            if positional_pos(board, bs1, bs2, minbs1th1, minbs1th2, minbs1th3,
                              maxbs2th1, maxbs2th2, maxbs2th3):
                is_save = True
        else:
            if interesting_pos(board, bs1, bs2, minbs1th1, minbs1th2,
                               minbs1th3, maxbs2th1, maxbs2th2, maxbs2th3):
                is_save = True

        # Create new epd
        ae_oper = 'Analyzing engine: ' + engname
        complexity_oper = 'Complexity: ' + str(bestmovechanges)
        bs2_oper = 'bestscore2: ' + str(bs2)
        new_epd = board.epd(bm=bm1,
                            ce=bs1,
                            sm=g_move,
                            acd=depth,
                            acs=int(t),
                            fmvn=board.fullmove_number,
                            hmvc=board.halfmove_clock,
                            pv=raw_pv,
                            c0=c0_val,
                            c1=complexity_oper,
                            c2=bs2_oper,
                            c3=ae_oper)

        # Save this new epd to either interesting.epd or dull.epd
        if is_save:
            logging.info('Save this position to {}'.format(outepdfn))
            with open(outepdfn, 'a') as f:
                f.write('{}\n'.format(new_epd))

            save_as_pgn(outpgnfn, curboard, game, fen, bm1, save_last_move)
        else:
            # Save all pos to dull.epd that were analyzed to a maxtime but
            # failed to be saved in interesting.epd. It can be useful to
            # improve the algorith by examing these positions visually.
            logging.info('Saved to {}'.format(dullfn))
            with open(dullfn, 'a') as f:
                f.write('{}\n'.format(new_epd))
예제 #9
0
def main(argv=None):
    global options
    global results
    global position
    global solved
    global tried

    engine = None

    if argv is None:
        argv = sys.argv[1:]

    parser = argparse.ArgumentParser(
        description="run test suite using UCI engine")
    parser.add_argument("-e", "--engine", type=str, help="engine path")
    parser.add_argument("-c", "--cores", type=int, help="number of cores")
    parser.add_argument("-m",
                        "--multipv",
                        type=int,
                        help="number of lines to search/display")
    parser.add_argument("-s",
                        "--syzygy",
                        type=str,
                        help="Syzygy tablebase path")
    parser.add_argument("-t",
                        "--time",
                        type=int,
                        help="search time in seconds")
    parser.add_argument("-H",
                        "--hash",
                        type=int,
                        help="hash size in kilobytes")
    parser.add_argument('filename', help="filename to analyze")
    parser.parse_args(namespace=options)

    try:
        engine = chess.engine.SimpleEngine.popen_uci(options.engine)
    except FileNotFoundError:
        print("engine executable " + options.engine + " not found",
              file=sys.stderr)
        return
    except:
        print(traceback.format_exc(), file=sys.stderr)
        print("failed to start child process " + options.engine,
              file=sys.stderr)
        return

    init(engine, options)

    pat = re.compile(
        '^(([pnbrqkPNBRQK1-8])+\/)+([pnbrqkPNBRQK1-8])+ [wb]+ [\-kqKQ]+ [\-a-h1-8]+'
    )

    with open(options.filename) as f:
        for line in f:
            # skip blank lines
            if len(line.strip()) == 0:
                continue
            m = pat.search(line)
            if m == None:
                print("error: invalid FEN in line: %s" % line,
                      file=sys.stderr,
                      flush=True)
            else:
                print()
                print(line)
                position.board = chess.Board(fen=m.group() + ' 0 1')
                position.ops = position.board.set_epd(line)
                # set_epd returns moves as Move objects, convert to SAN:
                for key in position.ops:
                    i = 1
                    if (key == 'bm' or key == 'am'):
                        san_moves = []
                        for move in position.ops[key]:
                            try:
                                san_moves.append(position.board.san(move))
                            except:
                                print(key + " solution move " + str(i) +
                                      " could not be parsed",
                                      file=sys.stderr,
                                      flush=True)
                        i = i + 1
                        position.ops[key] = san_moves

                results.solution_time = 0
                results.solution_nodes = 0
                results.bestmove = None
                results.infos = {}
                with engine.analysis(
                        board=position.board,
                        limit=chess.engine.Limit(time=options.time),
                        multipv=options.multipv) as analysis:
                    for info in analysis:
                        process_info(info, results)
                # print last group of results
                for i in range(1, options.multipv + 1):
                    group = 0
                    infos = results.infos[i]
                    for key in [
                            "depth", "seldepth", "multipv", "score", "nodes",
                            "nps", "hashfull", "tbhits", "time", "pv"
                    ]:
                        if key in infos:
                            if (group != 0):
                                print(" ", end="")
                            print(key + " ", end="")
                            if (key == "pv"):
                                fen = position.board.fen()
                                for m in infos[key]:
                                    san = position.board.san(m)
                                    print(san, end="")
                                    print(" ", end="")
                                    position.board.push(m)
                                # restore board
                                position.board.set_fen(fen)
                            else:
                                print(infos[key], end="")
                        group = group + 1
                    print()

                pv = results.infos[1].get("pv")
                if (pv != None):
                    results.bestmove = pv[0]
                tried = tried + 1
                if (results.bestmove != None):
                    if correct(results.bestmove, position):
                        solved = solved + 1
                        print("++ solved in " +
                              str(results.solution_time / 1000.0) +
                              " seconds (" + str(results.solution_nodes) +
                              " nodes)" + " (" + str(solved) + " out of " +
                              str(tried) + " solved, " +
                              str(round(solved * 100 / tried, 1)) + "%)",
                              flush=True)

                    else:
                        print("-- not solved" + " (" + str(solved) +
                              " out of " + str(tried) + " solved, " +
                              str(round(solved * 100 / tried, 1)) + "%)",
                              flush=True)
                else:
                    print("warning: best move == None for position " +
                          str(tried),
                          file=sys.stderr)
    engine.quit()
    print()
    print("RUN COMPLETED - " + str(solved) + " out of " + str(tried) +
          " solved (" + str(round(solved * 100 / tried, 1)) + "%)")
예제 #10
0
def runengine(engine_file, engineoption, enginename, epdfile, movetimems,
              outputpgn, outputepd):
    """
    Run engine, save search info and output game in pgn format, and epd format.
    """
    num_pos = 0
    folder = Path(engine_file).parents[0]
    engine = chess.engine.SimpleEngine.popen_uci(engine_file, cwd=folder)

    # Set engine option
    if engineoption is not None:
        for opt in engineoption.split(','):
            optname = opt.split('=')[0].strip()
            optvalue = opt.split('=')[1].strip()
            engine.configure({optname: optvalue})

    limit = chess.engine.Limit(time=movetimems/1000)
    engine_name = engine.id['name'] if enginename is None else enginename

    # Open epd file to get epd lines, analyze, and save it.
    with open(epdfile) as f:
        for lines in f:
            epdline = lines.strip()
            board, epdinfo = chess.Board().from_epd(epdline)
            orig_board = board.copy()

            # Get epd id
            posid = None
            try:
                posid = epdinfo['id']
            except KeyError:
                pass
            except Exception:
                print('Unexpected exception:')
                raise

            pv, depth, score = '', None, None
            with engine.analysis(board, limit) as analysis:
                for info in analysis:
                    if 'score' in info:
                        score = info['score'].relative.score(mate_score=32000)
                    if 'depth' in info:
                        depth = int(info['depth'])
                    if ('pv' in info and 'upperbound' not in info and
                            'lowerbound' not in info):
                        pv = info['pv']

            num_pos += 1
            print(f'pos: {num_pos}\r', end='')

            nboard, sanpv, pm = board.copy(), [], None
            for i, m in enumerate(pv):
                sanpv.append(nboard.san(m))
                if i == 0:
                    pm = nboard.san(m)
                nboard.push(m)

            for m in pv:
                board.push(m)

            game = chess.pgn.Game().from_board(board)
            game.headers['Annotator'] = engine_name
            game.headers['AnalysisMovetimeMs'] = str(movetimems)
            if posid is not None:
                game.headers['EPDId'] = posid

            # Save to pgn output
            with open(outputpgn, 'a') as s:
                s.write(f'{game}\n\n')

            # Save to epd output
            with open(outputepd, 'a') as s:
                acs = int(movetimems / 1000)
                save_epd = orig_board.epd()
                if posid is None:
                    s.write(f'{save_epd} acd {depth}; acs {acs}; '
                            f'ce {score}; pm {pm}; pv {" ".join(sanpv)}; '
                            f'c0 "analyzed by {engine_name}";\n')
                else:
                    s.write(f'{save_epd} acd {depth}; acs {acs}; '
                            f'ce {score}; id "{posid}"; pm {pm}; '
                            f'pv {" ".join(sanpv)}; c0 "analyzed by {engine_name}";\n')

    engine.quit()
예제 #11
0
def runengine(engine_file, engineoption, enginename, epdfile, movetimems,
              outputpgn, outputepd, extend_search, sdepth):
    """
    Run engine, save search info and output game in pgn format, and epd format.
    """
    # Read existing epd output file, if position is present don't analyze it.
    existing_epds = get_epd(outputepd)

    pos_num = 0
    folder = Path(engine_file).parents[0]
    engine = chess.engine.SimpleEngine.popen_uci(engine_file, cwd=folder)

    # Set engine option
    if engineoption is not None:
        for opt in engineoption.split(','):
            optname = opt.split('=')[0].strip()
            optvalue = opt.split('=')[1].strip()
            engine.configure({optname: optvalue})

    limit = search_limit(extend_search, movetimems, sdepth)

    engine_name = engine.id['name'] if enginename is None else enginename

    # Open epd file to get epd lines, analyze, and save it.
    with open(epdfile) as f:
        for lines in f:
            epdline = lines.strip()
            logging.info(epdline)
            board, epdinfo = chess.Board().from_epd(epdline)
            epd = board.epd()
            orig_board = board.copy()
            pos_num += 1

            if epd in existing_epds:
                logging.info(f'{epd} is already analyzed.')
                continue

            # Get epd id from input epd file
            posid = None if 'id' not in epdinfo else epdinfo['id']

            pv, depth, score, dm, elapse_ns = '', None, None, None, 0
            t1 = time.perf_counter_ns()
            with engine.analysis(board, limit) as analysis:
                for info in analysis:
                    if ('upperbound' not in info and 'lowerbound' not in info
                            and 'score' in info and 'pv' in info
                            and 'depth' in info):
                        score = info['score'].relative.score(mate_score=32000)
                        pv = info['pv']
                        depth = int(info['depth'])

                        if info['score'].is_mate() and score > 0:
                            dm = int(str(info['score']).split('#')[1])

                        # If moves in the pv is only 1, extend the search if
                        # the score is not a mate and extend_search is set.
                        if extend_search:
                            elapse_ns = time.perf_counter_ns() - t1
                            if elapse_ns >= movetimems * 1000000:
                                if info['score'].is_mate():
                                    break
                                if len(pv) > 1:
                                    break

            if not extend_search:
                elapse_ns = time.perf_counter_ns() - t1

            print(f'pos: {pos_num}\r', end='')

            # Update board for pgn output
            for m in pv:
                board.push(m)

            game = chess.pgn.Game().from_board(board)
            game.headers['Event'] = f'Position no. {pos_num}'
            game.headers['Annotator'] = engine_name
            game.headers['AnalysisMovetimeMs'] = str(movetimems)
            if posid is not None:
                game.headers['EPDId'] = posid
            game.headers['CentipawnEvaluation'] = str(score)

            save_output(game, orig_board, depth, movetimems, score, pv,
                        engine_name, posid, dm, outputpgn, outputepd,
                        elapse_ns)

    engine.quit()
예제 #12
0
def getAIMove(turn):
    global board
    global root
    global canvasSize
    global gameStateVar
    global pvmove

    lastpvmovestr = ""
    # if (p1 == "AI" and board.turn): engine = engine1
    # elif (p2 == "AI" and not board.turn): engine = engine2
    if False:
        with engine.analysis(board,
                             chess.engine.Limit(time=(1.5))) as analysis:
            for info in analysis:
                if (info.get("pv") != None):
                    pvmove = info.get("pv")[0]
                    pvmovestr = str(pvmove)
                    if lastpvmovestr != pvmovestr:
                        startfile = ord(pvmovestr[0]) - 97
                        startrank = ord(pvmovestr[1]) - 49
                        endfile = ord(pvmovestr[2]) - 97
                        endrank = ord(pvmovestr[3]) - 49
                        startrank = 7 - startrank
                        endrank = 7 - endrank
                        squareSize = canvasSize / 8
                        (startScrX, startScrY) = convertBoardIndextoXY(
                            startfile, startrank)
                        (endScrX,
                         endScrY) = convertBoardIndextoXY(endfile, endrank)
                        drawBoard()
                        drawPieces()
                        boardCanvas.create_rectangle(
                            startScrX + 3,
                            startScrY + 3, (startScrX + squareSize - 2),
                            (startScrY + squareSize - 2),
                            outline="#0000FF",
                            width=6)
                        boardCanvas.create_rectangle(
                            endScrX + 3,
                            endScrY + 3, (endScrX + squareSize - 2),
                            (endScrY + squareSize - 2),
                            outline="#0000FF",
                            width=6)
                        root.update()
                    lastpvmovestr = pvmovestr
    # pvmove = analysis.info['pv'][0]

    if turn == 'white':
        move = AI_4(board)
    else:
        move = AI_4(board)

    if move == 'No' or move is None:
        gameStateVar.set("End of game.")
        root.update()
        gameinprogress = False
    else:
        #print(move)
        #board.push_uci(str(move))
        board.make_move(move)
        drawBoard()
        drawPieces()

    #legalmoves = board.legal_moves
    legalmoves = board.get_all_legal_moves()
    #legalmoves = board.possible_moves

    count = 0

    for x in legalmoves:
        count += 1
    # print(count, legalmoves)
    if (count == 0):
        gameStateVar.set("End of game.")
        root.update()
        gameinprogress = False
        print('no legal')

    else:
        if (board.turn):
            gameStateVar.set("White to move.")
        else:
            gameStateVar.set("Black to move.")
        root.update()
        if (p1 == "AI" and board.turn):
            getAIMove(turn='white')
        elif (p2 == "AI" and not board.turn):
            getAIMove(turn='black')
예제 #13
0
def grab_line(fen: str, turn: str) -> str or float:
    '''
    This function takes a FEN and a string identifying
    whether it is white's or black's turn. It then outputs
    whether white or black has the advantage in the chess
    position represented by the FEN, the advantage, and the
    best continuation as judged by Stockfish 10.
    '''
    # The engine used is Stockfish 10
    engine = chess.engine.SimpleEngine.popen_uci("stockfish_10_x64.exe")
    player = ''
    line = ''

    # Stockfish analyzes the FEN at a depth of 20 nodes and stores the
    # analysis in an object named 'analysis'
    with engine.analysis(chess.Board(fen),
                         chess.engine.Limit(depth=30)) as analysis:

        # Retrieving the best continuation line of moves from 'analysis'.
        for info in analysis:
            line = info.get('pv')

        # 'advantage' stores the advantage in number of pawns from
        # White's perspective.
        if str(info.get('score').white()) == '0':
            advantage = 0
        else:
            advantage = int(str(info.get('score').white())[1:]) / 100

        if str(info.get('score').white())[0] == '+':
            # If White has the advantage in the FEN...
            player = 'White'
        elif str(info.get('score').white())[0] == '-':
            # If Black has the advantage in the FEN...
            player = 'Black'
        elif str(info.get('score').white())[0] == '#' and str(
                info.get('score').white())[1] == '+':
            # If White has a checkmating advantage in the FEN...
            player = 'White'
            advantage = 'checkmate in %d' % (abs(int(advantage * 100)))
        elif str(info.get('score').white())[0] == '#' and str(
                info.get('score').white())[1] == '-':
            # If Black has a checkmating advantage in the FEN...
            player = 'Black'
            advantage = 'checkmate in %d' % (abs(int(advantage * 100)))

    engine.quit()

    # The best continuation line is split up by move
    line = [
        move for move in re.split('( )(?=\d)',
                                  chess.Board(fen).variation_san(line))
        if move != ' '
    ]

    # The comment to be posted is constructed, with spoiler tags and
    # new lines. If there is a checkmate in 5 moves or less, the whole
    # line is posted. If not, only the first 3 moves is posted.
    comment = ''
    if type(advantage) == str and int(advantage[13:]) <= 7:
        for move in line[:int(advantage[13:])]:
            comment += f"\n\n>!{move}!<"
    else:
        for move in line[:3]:
            comment += f"\n\n>!{move}!<"

    return player, advantage, comment
예제 #14
0
def runengine(engine_file, engineoption, epdfile, movetimems, outputepd, pvlen,
              scoremargin, use_static_eval, lowpvfn):
    pos_num = 0

    # Get epd's that were previously evaluated.
    evaluated_epds = get_epd('evaluated.epd')

    folder = Path(engine_file).parents[0]
    engine = chess.engine.SimpleEngine.popen_uci(engine_file, cwd=folder)

    # Set engine option
    if engineoption is not None:
        for opt in engineoption.split(','):
            optname = opt.split('=')[0].strip()
            optvalue = opt.split('=')[1].strip()
            engine.configure({optname: optvalue})

    limit = chess.engine.Limit(time=movetimems / 1000)
    engineprocess = subprocess.Popen(engine_file,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.STDOUT,
                                     universal_newlines=True,
                                     bufsize=1)

    # Open epd file to get epd lines, analyze, and save it.
    with open(epdfile) as f:
        for lines in f:
            ismate = False

            epdline = lines.strip()
            logging.info(epdline)
            board, epdinfo = chess.Board().from_epd(epdline)
            epd = board.epd()
            pos_num += 1

            print(f'pos: {pos_num}')

            # Don't evaluate current epd if it is already in evaluated.epd
            if epd in evaluated_epds:
                print(f'This epd {epdline} is already evaluated.')
                continue

            # Skip if side to move is in check.
            if board.is_attacked_by(not board.turn, board.king(board.turn)):
                print(f'Skip, the side to move is incheck in {epdline}.')
                save_evaluated_epd(epdline)
                continue

            # Skip if EPD bm is a tactical move.
            if tactical_move(board=board, epdinfo=epdinfo, move=None):
                print(f'Skip, the bm in {epdline} is tactical.')
                save_evaluated_epd(epdline)
                continue

            pv, score, mate_score = '', None, None
            with engine.analysis(board, limit) as analysis:
                for info in analysis:
                    if ('upperbound' not in info and 'lowerbound' not in info
                            and 'score' in info and 'pv' in info):
                        pv = info['pv']

                        if info['score'].is_mate():
                            ismate = True
                            mate_score = info['score']
                        else:
                            ismate = False
                            mate_score = None

                        score = info['score'].relative.score(mate_score=32000)

            save_evaluated_epd(epdline)

            # Don't extract if score is mate or mated
            if ismate:
                print(f'score: {mate_score}')
                print('Skip, score is a mate.')
                continue

            # Compare Stockfish static eval and search score.
            if (use_static_eval and score is not None
                    and 'stockfish' in engine.id['name'].lower()):
                staticeval = stockfish_staticeval(engineprocess, board)
                absdiff = abs(score - staticeval)
                if absdiff > scoremargin:
                    print(f'static scorecp: {staticeval}, '
                          f'search scorecp: {score}, '
                          f'abs({score} - ({staticeval})): {absdiff}')
                    print('Skip, abs score difference between static and '
                          f'search score is above {scoremargin} score margin.')
                    continue

            # Skip if required pvlen is not meet.
            if len(pv) < pvlen:
                ucipv = [str(m) for m in pv]
                print(ucipv)
                print(f'Skip, pv length is below {pvlen} plies.')

                # Save to file the position where engine could not
                # create the required pv length. This file can be used
                # to investigate the issue.
                if pvlen >= 2:
                    with open(lowpvfn, 'a') as s:
                        s.write(f'{epdline} Acms {movetimems}; '
                                f'C0 "status: pvlength is below requirement, '
                                f'ucipv: {ucipv}, ucipvlen: {len(ucipv)}, '
                                f'pvlength required: {pvlen}"; '
                                f'Anno "{engine.id["name"]}";\n')
                continue

            # Don't extract if there is capture or promote, or a check
            # move in the first pvlen plies of the pv

            # Evaluate pv
            sanpv, istactical = [], False
            for i, m in enumerate(pv):
                if i > pvlen - 1:
                    break

                sanmove = board.san(m)
                sanpv.append(sanmove)

                if tactical_move(board=None, epdinfo=None, move=sanmove):
                    istactical = True
                    break

                board.push(m)

            if istactical:
                print(sanpv)
                print('Skip, move in the pv has a tactical move')
                continue

            print('saving ...')
            print(epdline)
            print(sanpv)
            with open(outputepd, 'a') as s:
                s.write(f'{epdline}\n')

    engine.quit()

    engineprocess.stdin.write('quit\n')
    print('quit engineprocess')
예제 #15
0
def main(argv = None):
    global options
    global results
    global position
    global solved
    global tried

    engine = None

    if argv is None:
        argv = sys.argv[1:]

    arg = 0
    while ((arg < len(argv)) and (argv[arg][0:1] == '-')):
        if (argv[arg][1] == 'c'):
            arg = arg + 1
            if (arg < len(argv)):
                try:
                    options.cores = int(argv[arg])
                except exceptions.ValueError:
                    print(('Invalid value for parameter %s: %s' % (argv[i], argv[i + 1])),file=sys.stderr)
                    return 2
        elif (argv[arg][1] == 't'):
            arg = arg + 1
            if (arg < len(argv)):
                try:
                    options.search_time = int(argv[arg])
                except exceptions.ValueError:
                    print(('Invalid value for parameter %s: %s' % (argv[i], argv[i + 1])),file=sys.stderr)
                    return 2
        elif (argv[arg][1] == 'e'):
            arg = arg + 1
            if (arg < len(argv)):
                options.engine_name = argv[arg]
        elif (argv[arg][1] == 'H'):
            arg = arg + 1
            if (arg < len(argv)):
                try:
                    options.hash_size = int(argv[arg])
                except exceptions.ValueError:
                    print(('Invalid value for parameter %s: %s' % (argv[i], argv[i + 1])),file=sys.stderr)
                    return 2
        elif (argv[arg][1] == 'm'):
            arg = arg + 1
            if (arg < len(argv)):
                try:
                    options.multi_pv_value = int(argv[arg])
                except exceptions.ValueError:
                    print(('Invalid value for parameter %s: %s' % (argv[i], argv[i + 1])),file=sys.stderr)
                    return 2
        else:
            print("Unrecognized switch: " + argv[arg], file=sys.stderr)
            return
        arg = arg + 1

    time = options.search_time*1000

    if (arg >= len(argv)):
        print("Expected a filename to analyze.", file=sys.stderr)
        return

    try:
        engine = chess.engine.SimpleEngine.popen_uci(options.engine_name)
    except FileNotFoundError:
       print("engine executable " + options.engine_name + " not found", file=sys.stderr)
       return
    except:
       print(traceback.format_exc(), file=sys.stderr)
       print("failed to start child process " + options.engine_name, file=sys.stderr)
       return

    init(engine,options)

    pat = re.compile('^(([pnbrqkPNBRQK1-8])+\/)+([pnbrqkPNBRQK1-8])+ [wb]+ [\-kqKQ]+ [\-a-h1-8]+')

    with open(argv[arg]) as f:
        for line in f:
           # skip blank lines
           if len(line.strip())==0:
               continue
           m = pat.search(line)
           if m == None:
               print("error: invalid FEN in line: %s" % line, file=sys.stderr, flush=True)
           else:
               print()
               print(line)
               position.board = chess.Board(fen=m.group()+' 0 1')
               position.ops = position.board.set_epd(line)
               # set_epd returns moves as Move objects, convert to SAN:
               for key in position.ops:
                  i = 1
                  if (key == 'bm' or key == 'am'):
                     san_moves = []
                     for move in position.ops[key]:
                        try:
                           san_moves.append(position.board.san(move))
                        except:
                           print(key + " solution move " + str(i) + " could not be parsed",file=sys.stderr, flush=True)
                     i = i + 1
                     position.ops[key] = san_moves

               results.solution_time = 0
               results.solution_nodes = 0
               results.bestmove = None
               results.infos = {}
               with engine.analysis(board=position.board,
                                    limit=chess.engine.Limit(time=options.search_time),multipv=options.multi_pv_value) as analysis:
                  for info in analysis:
                     process_info(info,results)
               # print last group of results
               for i in range(1,options.multi_pv_value+1):
                   group = 0
                   infos = results.infos[i]
                   for key in ["depth","seldepth","multipv","score","nodes",
                               "nps","hashfull","tbhits","time","pv"]:
                       if key in infos:
                           if (group != 0):
                              print(" ",end="")
                           print(key + " ",end="")
                           if (key == "pv"):
                              fen = position.board.fen()
                              for m in infos[key]:
                                  san = position.board.san(m)
                                  print(san,end="")
                                  print(" ",end="")
                                  position.board.push(m)
                              # restore board
                              position.board.set_fen(fen)
                           else:
                              print(infos[key],end="")
                       group = group + 1
                   print()

               pv = results.infos[1].get("pv")
               if (pv != None):
                  results.bestmove = pv[0]
               if (results.bestmove != None):
                  tried = tried + 1
                  if correct(results.bestmove,position):
                     solved = solved + 1
                     print("++ solved in " + str(results.solution_time/1000.0) + " seconds ("
                            + str(results.solution_nodes)
                            + " nodes)" + " (" + str(solved) + " out of " + str(tried) + " solved, "
                            + str(round(solved * 100 /tried,1)) + "%)", flush=True)

                  else:
                     print("-- not solved" + " (" + str(solved) + " out of " + str(tried) + " solved, "
                            + str(round(solved * 100 /tried,1)) + "%)", flush=True)
    engine.quit()
    print()
    print("RUN COMPLETED - " + str(solved) + " out of " + str(tried) + " solved (" + str(round(solved * 100 /tried,1)) + "%)")