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
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
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))
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()
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()
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
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()
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))
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)) + "%)")
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()
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()
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')
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
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')
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)) + "%)")