def allperft(f, depth=4, verbose=True): import gc lines = f.readlines() for d in range(1, depth+1): if verbose: print("Going to depth {}/{}".format(d, depth)) for line in lines: parts = line.split(';') if len(parts) <= d: continue if verbose: print(parts[0]) pos, score = tools.parseFEN(parts[0]), int(parts[d]) res = sum(1 for _ in tools.collect_tree_depth(tools.expand_position(pos), d)) if res != score: print('=========================================') print('ERROR at depth %d. Gave %d rather than %d' % (d, res, score)) print('=========================================') print(tools.renderFEN(pos,0)) for move in pos.gen_moves(): split = sum(1 for _ in tools.collect_tree_depth(tools.expand_position(pos.move(move)),1)) print('{}: {}'.format(tools.mrender(pos, move), split)) return False if verbose: print('') return True
def allperft(f, depth=4, verbose=True): import gc lines = f.readlines() for d in range(1, depth+1): if verbose: print("Going to depth {}/{}".format(d, depth)) for line in lines: parts = line.split(';') if verbose: print(parts[0]) pos, score = tools.parseFEN(parts[0]), int(parts[d]) res = sum(1 for _ in tools.collect_tree_depth(tools.expand_position(pos), d)) if res != score: print('=========================================') print('ERROR at depth %d. Gave %d rather than %d' % (d, res, score)) print('=========================================') print(tools.renderFEN(pos,0)) for move in pos.gen_moves(): split = sum(1 for _ in tools.collect_tree_depth(tools.expand_position(pos.move(move)),1)) print('{}: {}'.format(tools.mrender(pos, move), split)) return False if verbose: print('') return True
def _search(self, pos): """ Iterative deepening MTD-bi search """ self.nodes = 0 # In finished games, we could potentially go far enough to cause a recursion # limit exception. Hence we bound the ply. for depth in range(1, 1000): self.depth = depth # The inner loop is a binary search on the score of the position. # Inv: lower <= score <= upper # 'while lower != upper' would work, but play tests show a margin of 20 plays better. lower, upper = -MATE_UPPER, MATE_UPPER while lower < upper - EVAL_ROUGHNESS: gamma = (lower + upper + 1) // 2 score = self.bound(pos, gamma, depth) # Test for debugging search instability if not lower <= score <= upper: import tools print(__file__, 'search instability?', lower, upper, 'gamma score', gamma, score, 'depth', depth, 'pos', tools.renderFEN(pos)) if score >= gamma: lower = score if score < gamma: upper = score # We want to make sure the move to play hasn't been kicked out of the table, # So we make another call that must always fail high and thus produce a move. score = self.bound(pos, lower, depth) # Test for debugging tp_score assert score >= lower if self.tp_score.get((pos, depth, True)) is None: print("No score stored?", score) self.tp_score[(pos, depth, True)] = Entry(score, score) assert score == self.tp_score.get((pos, depth, True)).lower # Test for debugging tp_move arb_legal_move = lambda: next( (m for m in pos.gen_moves() if not any( pos.move(m).value(m1) >= MATE_LOWER for m1 in pos.move(m).gen_moves())), None) if self.tp_move.get(pos) is None: print('No move stored? Score: {}'.format(score)) self.tp_move[pos] = arb_legal_move() else: move = self.tp_move.get(pos) pos1 = pos.move(move) if any(pos1.value(m) >= MATE_LOWER for m in pos1.gen_moves()): import tools print('Returned illegal move? Score: {}'.format(score), 'move', tools.mrender(pos, move), 'pos', tools.renderFEN(pos)) self.tp_move[pos] = arb_legal_move() # Yield so the user may inspect the search yield
def selfplay(secs=1): """ Start a game sunfish vs. sunfish """ pos = tools.parseFEN(tools.FEN_INITIAL) for d in range(200): # Always print the board from the same direction board = pos.board if d % 2 == 0 else pos.rotate().board print(' '.join(board)) m, _ = sunfish.Searcher().search(pos, secs) if m is None: print("Game over") break print("\nmove", tools.mrender(pos, m)) pos = pos.move(m)
def selfplay(secs=1): """ Start a game amwafish vs. amwafish """ pos = tools.parseFEN(tools.FEN_INITIAL) for d in range(200): # Always print the board from the same direction board = pos.board if d % 2 == 0 else pos.rotate().board print(' '.join(board)) m, _, _ = tools.search(amwafish.Searcher(), pos, secs) if m is None: print("Game over") break print("\nmove", tools.mrender(pos, m)) pos = pos.move(m)
def main(): pos = tools.parseFEN(tools.FEN_INITIAL) searcher = sunfish.Searcher() forced = False color = WHITE our_time, opp_time = 1000, 1000 # time in centi-seconds show_thinking = False options = {} history = [] stack = [] while True: if stack: smove = stack.pop() else: smove = input() print('>>>', smove, file=sys.stderr) sys.stderr.flush() # For Python 2.7 support if smove == 'quit': break elif smove == 'protover 2': print('feature done=0') print('feature myname="Sunfish"') print('feature usermove=1') print('feature setboard=1') print('feature ping=1') print('feature sigint=0') print('feature variants="normal"') print('feature option="qs_limit -spin {} -100 1000"'.format( sunfish.QS_LIMIT)) print('feature option="eval_roughness -spin {} 1 1000"'.format( sunfish.EVAL_ROUGHNESS)) print('feature option="draw_test -spin {} 0 1"'.format( int(sunfish.DRAW_TEST))) print('feature done=1') elif smove == 'new': stack.append('setboard ' + tools.FEN_INITIAL) # Clear out the old searcher, including the tables searcher = sunfish.Searcher() del history[:] elif smove.startswith('setboard'): _, fen = smove.split(' ', 1) pos = tools.parseFEN(fen) color = WHITE if fen.split()[1] == 'w' else BLACK del history[:] elif smove == 'force': forced = True elif smove.startswith('option'): _, aeqb = smove.split(maxsplit=1) if '=' in aeqb: name, val = aeqb.split('=') else: name, val = aeqb, True if name == 'qs_limit': sunfish.QS_LIMIT = int(val) if name == 'eval_roughness': sunfish.EVAL_ROUGHNESS = int(val) if name == 'draw_test': sunfish.DRAW_TEST = bool(int(val)) options[name] = val elif smove == 'go': forced = False moves_remain = 40 use = our_time / moves_remain # Let's follow the clock of our opponent if our_time >= 100 and opp_time >= 100: use *= our_time / opp_time start = time.time() for ply, move, score in searcher.search(pos, history): entry = searcher.tp_score.get((pos, ply, True)) score = int(round((entry.lower + entry.upper) / 2)) if show_thinking: used = int((time.time() - start) * 100 + .5) moves = tools.pv(searcher, pos, include_scores=False) seldepth = 0 nps = int(searcher.nodes / (time.time() - start) + .5) print('{:>3} {:>8} {:>8} {:>13} {:>1} {:>4} \t{}'.format( ply, score, used, searcher.nodes, seldepth, nps, moves)) print('# Hashfull: {:.3f}%; {} <= score < {}'.format( len(searcher.tp_score) / sunfish.TABLE_SIZE * 100, entry.lower, entry.upper)) # If found mate, just stop if entry.lower >= sunfish.MATE_UPPER: break if time.time() - start > use / 100: break # We sometimes make illegal moves when we're losing, # so it's safer to just resign. if score == -sunfish.MATE_UPPER: print('resign') else: print('move', tools.mrender(pos, move)) pos = pos.move(move) history.append(pos) color = 1 - color elif smove.startswith('ping'): _, N = smove.split() print('pong', N) elif smove.startswith('usermove'): _, smove = smove.split() m = tools.mparse(color, smove) pos = pos.move(m) history.append(pos) color = 1 - color if not forced: stack.append('go') elif smove.startswith('time'): our_time = int(smove.split()[1]) elif smove.startswith('otim'): opp_time = int(smove.split()[1]) elif smove.startswith('perft'): start = time.time() for d in range(1, 10): res = sum(1 for _ in tools.collect_tree_depth( tools.expand_position(pos), d)) print('{:>8} {:>8}'.format(res, time.time() - start)) elif smove.startswith('post'): show_thinking = True elif smove.startswith('nopost'): show_thinking = False elif any( smove.startswith(x) for x in ('xboard', 'random', 'hard', 'accepted', 'level', 'easy', 'st', 'result', '?')): print('# Ignoring command {}.'.format(smove)) elif smove.startswith('reject'): _, feature = smove.split( )[:2] # split(maxsplit=2) doesnt work in python2.7 print( '# Warning ({} rejected): Might not work as expected.'.format( feature)) else: print('# Warning (unkown command): {}. Treating as move.'.format( smove)) stack.append('usermove {}'.format(smove))
def play(version1_version2_secs_plus_fen): ''' returns 1 if fish1 won, 0 for draw and -1 otherwise ''' version1, version2, secs, plus, fen = version1_version2_secs_plus_fen modules = [importlib.import_module(version1), importlib.import_module(version2)] searchers = [] for module in modules: if hasattr(module, 'Searcher'): searchers.append(module.Searcher()) else: searchers.append(module) times = [secs, secs] efactor = [1, 1] pos = tools.parseFEN(fen) seen = set() for d in range(200): moves_remain = 30 use = times[d%2]/moves_remain + plus # Use a bit more time, if we have more on the clock than our opponent use += (times[d%2] - times[(d+1)%2])/10 use = max(use, plus) t = time.time() m, score = searchers[d%2].search(pos, use*efactor[d%2]) efactor[d%2] *= (use/(time.time() - t))**.5 times[d%2] -= time.time() - t times[d%2] += plus #print('Used {:.2} rather than {:.2}. Off by {:.2}. Remaining: {}' #.format(time.time()-t, use, (time.time()-t)/use, times[d%2])) if times[d%2] < 0: print('{} ran out of time'.format(version2 if d%2 == 1 else version1)) return version1 if d%2 == 1 else version2 pass if m is None: print('Game not done, but no move? Score', score) name = version1 if d%2 == 0 else version2 print(version1, tools.renderFEN(pos)) assert False # Test move is_dead = lambda pos: any(pos.value(m) >= sunfish.MATE_LOWER for m in pos.gen_moves()) if is_dead(pos.move(m)): name = version1 if d%2 == 0 else version2 print('{} made an illegal move {} in position {}. Depth {}, Score {}'. format(name, tools.mrender(pos,m), tools.renderFEN(pos), searchers[d%2].depth, score)) assert False # Make the move pos = pos.move(m) # Test repetition draws # This is by far the most common type of draw if pos in seen: #print('Rep time at end', times) return None seen.add(pos) any_moves = not all(is_dead(pos.move(m)) for m in pos.gen_moves()) in_check = is_dead(pos.nullmove()) if not any_moves: if not in_check: # This is actually a bit interesting. Why would we ever throw away a win like this? name = version1 if d%2 == 0 else version2 print('{} stalemated? depth {} {}'.format( name, searchers[d%2].depth, tools.renderFEN(pos))) if score != 0: print('it got the wrong score: {} != 0'.format(score)) return None else: name = version1 if d%2 == 0 else version2 if score < sunfish.MATE_LOWER: print('{} mated, but did not realize. Only scored {} in position {}, depth {}'.format(name, score, tools.renderFEN(pos), searchers[d%2].depth)) return name print('Game too long', tools.renderFEN(pos)) return None
def play(version1_version2_secs_plus_fen): ''' returns 1 if fish1 won, 0 for draw and -1 otherwise ''' version1, version2, secs, plus, fen = version1_version2_secs_plus_fen modules = [importlib.import_module(version1), importlib.import_module(version2)] searchers = [] for module in modules: if hasattr(module, 'Searcher'): searchers.append(module.Searcher()) else: searchers.append(module) times = [secs, secs] efactor = [1, 1] pos = tools.parseFEN(fen) seen = set() for d in range(200): moves_remain = 30 use = times[d%2]/moves_remain + plus # Use a bit more time, if we have more on the clock than our opponent use += (times[d%2] - times[(d+1)%2])/10 use = max(use, plus) t = time.time() m, score, depth = tools.search(searchers[d%2], pos, use*efactor[d%2]) efactor[d%2] *= (use/(time.time() - t))**.5 times[d%2] -= time.time() - t times[d%2] += plus #print('Used {:.2} rather than {:.2}. Off by {:.2}. Remaining: {}' #.format(time.time()-t, use, (time.time()-t)/use, times[d%2])) if times[d%2] < 0: print('{} ran out of time'.format(version2 if d%2 == 1 else version1)) return version1 if d%2 == 1 else version2 pass if m is None: print('Game not done, but no move? Score', score) name = version1 if d%2 == 0 else version2 print(version1, tools.renderFEN(pos)) assert False # Test move is_dead = lambda pos: any(pos.value(m) >= amwafish.MATE_LOWER for m in pos.gen_moves()) if is_dead(pos.move(m)): name = version1 if d%2 == 0 else version2 print('{} made an illegal move {} in position {}. Depth {}, Score {}'. format(name, tools.mrender(pos,m), tools.renderFEN(pos), depth, score)) return version2 if d%2 == 0 else version1 #assert False # Make the move pos = pos.move(m) # Test repetition draws # This is by far the most common type of draw if pos in seen: #print('Rep time at end', times) return None seen.add(pos) any_moves = not all(is_dead(pos.move(m)) for m in pos.gen_moves()) in_check = is_dead(pos.nullmove()) if not any_moves: if not in_check: # This is actually a bit interesting. Why would we ever throw away a win like this? name = version1 if d%2 == 0 else version2 print('{} stalemated? depth {} {}'.format( name, depth, tools.renderFEN(pos))) if score != 0: print('it got the wrong score: {} != 0'.format(score)) return None else: name = version1 if d%2 == 0 else version2 if score < amwafish.MATE_LOWER: print('{} mated, but did not realize. Only scored {} in position {}, depth {}'.format(name, score, tools.renderFEN(pos), depth)) return name print('Game too long', tools.renderFEN(pos)) return None
def main(): parser = argparse.ArgumentParser() parser.add_argument('module', help='sunfish.py file (without .py)', type=str, default='sunfish', nargs='?') parser.add_argument('--tables', metavar='pst', help='alternative pst table', type=str, default=None) args = parser.parse_args() sunfish = importlib.import_module(args.module) if args.tables is not None: pst_module = importlib.import_module(args.tables) sunfish.pst = pst_module.pst sunfish.QS_LIMIT = pst_module.QS_LIMIT sunfish.EVAL_ROUGHNESS = pst_module.EVAL_ROUGHNESS sys.stdout = tools.Unbuffered(sys.stdout) now = datetime.now() path = 'sunfish-' + now.strftime("%d:%m:%Y-%H:%M:%S:%f") + '.log' sys.stderr = open(path, 'a') pos = tools.parseFEN(tools.FEN_INITIAL) searcher = sunfish.Searcher() forced = False color = WHITE our_time, opp_time = 1000, 1000 # time in centi-seconds show_thinking = False options = {} history = [] stack = [] while True: if stack: smove = stack.pop() else: smove = input() print('>>>', smove, file=sys.stderr) sys.stderr.flush() # For Python 2.7 support if smove == 'quit': break elif smove == 'protover 2': print('feature done=0') print('feature myname="Sunfish"') print('feature usermove=1') print('feature setboard=0' ) # Removing setboard because of lichess bug print('feature ping=1') print('feature sigint=0') print('feature nps=0') print('feature variants="normal"') print('feature option="qs_limit -spin {} -100 1000"'.format( sunfish.QS_LIMIT)) print('feature option="eval_roughness -spin {} 1 1000"'.format( sunfish.EVAL_ROUGHNESS)) print('feature option="draw_test -spin {} 0 1"'.format( int(sunfish.DRAW_TEST))) print('feature done=1') elif smove == 'new': stack.append('setboard ' + tools.FEN_INITIAL) # Clear out the old searcher, including the tables searcher = sunfish.Searcher() del history[:] elif smove.startswith('setboard'): _, fen = smove.split(' ', 1) pos = tools.parseFEN(fen) color = WHITE if fen.split()[1] == 'w' else BLACK del history[:] elif smove == 'force': forced = True elif smove.startswith('option'): _, aeqb = smove.split(maxsplit=1) if '=' in aeqb: name, val = aeqb.split('=') else: name, val = aeqb, True if name == 'qs_limit': sunfish.QS_LIMIT = int(val) if name == 'eval_roughness': sunfish.EVAL_ROUGHNESS = int(val) if name == 'draw_test': sunfish.DRAW_TEST = bool(int(val)) options[name] = val elif smove == 'go': forced = False moves_remain = 40 use = our_time / moves_remain # Let's follow the clock of our opponent if our_time >= 100 and opp_time >= 100: use *= our_time / opp_time start = time.time() for ply, move, score in searcher.search(pos, history): entry = searcher.tp_score.get((pos, ply, True)) score = int(round((entry.lower + entry.upper) / 2)) if show_thinking: seconds = time.time() - start used_ms = int(seconds * 100 + .5) moves = tools.pv(searcher, pos, include_scores=False) print('{:>3} {:>8} {:>8} {:>13} \t{}'.format( ply, score, used_ms, searcher.nodes, moves)) print('# {} n/s'.format(round(searcher.nodes / seconds))) print('# Hashfull: {:.3f}%; {} <= score < {}'.format( len(searcher.tp_score) / sunfish.TABLE_SIZE * 100, entry.lower, entry.upper)) # If found mate, just stop if entry.lower >= sunfish.MATE_UPPER: break if time.time() - start > use / 100: break # We sometimes make illegal moves when we're losing, # so it's safer to just resign. if score == -sunfish.MATE_UPPER: print('resign') else: print('move', tools.mrender(pos, move)) pos = pos.move(move) history.append(pos) color = 1 - color elif smove.startswith('ping'): _, N = smove.split() print('pong', N) elif smove.startswith('usermove'): _, smove = smove.split() m = tools.mparse(color, smove) pos = pos.move(m) history.append(pos) color = 1 - color if not forced: stack.append('go') elif smove.startswith('time'): our_time = int(smove.split()[1]) elif smove.startswith('otim'): opp_time = int(smove.split()[1]) elif smove.startswith('perft'): start = time.time() for d in range(1, 10): res = sum(1 for _ in tools.collect_tree_depth( tools.expand_position(pos), d)) print('{:>8} {:>8}'.format(res, time.time() - start)) elif smove.startswith('post'): show_thinking = True elif smove.startswith('nopost'): show_thinking = False elif any( smove.startswith(x) for x in ('xboard', 'random', 'hard', 'accepted', 'level', 'easy', 'st', 'result', '?', 'name', 'rating')): print('# Ignoring command {}.'.format(smove)) elif smove.startswith('reject'): _, feature = smove.split( )[:2] # split(maxsplit=2) doesnt work in python2.7 if feature == 'sigint': signal.signal(signal.SIGINT, signal.SIG_IGN) print( '# Warning ({} rejected): Might not work as expected.'.format( feature)) else: print('# Warning (unkown command): {}. Treating as move.'.format( smove)) stack.append('usermove {}'.format(smove))
def main(): pos = tools.parseFEN(tools.FEN_INITIAL) searcher = sunfish.Searcher() forced = False color = WHITE our_time, opp_time = 1000, 1000 # time in centi-seconds show_thinking = False stack = [] while True: if stack: smove = stack.pop() else: smove = input() if smove == 'quit': break elif smove == 'protover 2': print('feature done=0') print('feature myname="Sunfish"') print('feature usermove=1') print('feature setboard=1') print('feature ping=1') print('feature sigint=0') print('feature variants="normal"') print('feature done=1') elif smove == 'new': stack.append('setboard ' + tools.FEN_INITIAL) elif smove.startswith('setboard'): _, fen = smove.split(' ', 1) pos = tools.parseFEN(fen) color = WHITE if fen.split()[1] == 'w' else BLACK elif smove == 'force': forced = True elif smove == 'go': forced = False moves_remain = 40 use = our_time/moves_remain # Let's follow the clock of our opponent if our_time >= 100 and opp_time >= 100: use *= our_time/opp_time start = time.time() for _ in searcher._search(pos): if show_thinking: ply = searcher.depth entry = searcher.tp_score.get((pos, ply, True)) score = int(round((entry.lower + entry.upper)/2)) dual_score = '{}:{}'.format(entry.lower, entry.upper) used = int((time.time() - start)*100 + .5) moves = tools.pv(searcher, pos, include_scores=False) print('{:>3} {:>8} {:>8} {:>13} \t{}'.format( ply, score, used, searcher.nodes, moves)) if time.time() - start > use/100: break entry = searcher.tp_score.get((pos, searcher.depth, True)) m, s = searcher.tp_move.get(pos), entry.lower # We only resign once we are mated.. That's never? if s == -sunfish.MATE_UPPER: print('resign') else: print('move', tools.mrender(pos, m)) pos = pos.move(m) color = 1-color elif smove.startswith('ping'): _, N = smove.split() print('pong', N) elif smove.startswith('usermove'): _, smove = smove.split() m = tools.mparse(color, smove) pos = pos.move(m) color = 1-color if not forced: stack.append('go') elif smove.startswith('time'): our_time = int(smove.split()[1]) elif smove.startswith('otim'): opp_time = int(smove.split()[1]) elif smove.startswith('perft'): start = time.time() for d in range(1,10): res = sum(1 for _ in tools.collect_tree_depth(tools.expand_position(pos), d)) print('{:>8} {:>8}'.format(res, time.time()-start)) elif smove.startswith('post'): show_thinking = True elif smove.startswith('nopost'): show_thinking = False elif any(smove.startswith(x) for x in ('xboard','random','hard','accepted','level')): pass else: print("Error (unkown command):", smove)
def main(): pos = tools.parseFEN(tools.FEN_INITIAL) searcher = sunfish.Searcher() forced = False color = WHITE our_time, opp_time = 1000, 1000 # time in centi-seconds show_thinking = False stack = [] while True: if stack: smove = stack.pop() else: smove = input() if smove == 'quit': break elif smove == 'protover 2': print('feature done=0') print('feature myname="Sunfish"') print('feature usermove=1') print('feature setboard=1') print('feature ping=1') print('feature sigint=0') print('feature variants="normal"') print('feature done=1') elif smove == 'new': stack.append('setboard ' + tools.FEN_INITIAL) elif smove.startswith('setboard'): _, fen = smove.split(' ', 1) pos = tools.parseFEN(fen) color = WHITE if fen.split()[1] == 'w' else BLACK elif smove == 'force': forced = True elif smove == 'go': forced = False moves_remain = 40 use = our_time / moves_remain # Let's follow the clock of our opponent if our_time >= 100 and opp_time >= 100: use *= our_time / opp_time start = time.time() for _ in searcher._search(pos): if show_thinking: ply = searcher.depth entry = searcher.tp_score.get((pos, ply, True)) score = int(round((entry.lower + entry.upper) / 2)) dual_score = '{}:{}'.format(entry.lower, entry.upper) used = int((time.time() - start) * 100 + .5) moves = tools.pv(searcher, pos, include_scores=False) print('{:>3} {:>8} {:>8} {:>13} \t{}'.format( ply, score, used, searcher.nodes, moves)) if time.time() - start > use / 100: break entry = searcher.tp_score.get((pos, searcher.depth, True)) m, s = searcher.tp_move.get(pos), entry.lower # We only resign once we are mated.. That's never? if s == -sunfish.MATE_UPPER: print('resign') else: print('move', tools.mrender(pos, m)) pos = pos.move(m) color = 1 - color elif smove.startswith('ping'): _, N = smove.split() print('pong', N) elif smove.startswith('usermove'): _, smove = smove.split() m = tools.mparse(color, smove) pos = pos.move(m) color = 1 - color if not forced: stack.append('go') elif smove.startswith('time'): our_time = int(smove.split()[1]) elif smove.startswith('otim'): opp_time = int(smove.split()[1]) elif smove.startswith('perft'): start = time.time() for d in range(1, 10): res = sum(1 for _ in tools.collect_tree_depth( tools.expand_position(pos), d)) print('{:>8} {:>8}'.format(res, time.time() - start)) elif smove.startswith('post'): show_thinking = True elif smove.startswith('nopost'): show_thinking = False elif any( smove.startswith(x) for x in ('xboard', 'random', 'hard', 'accepted', 'level')): pass else: print("Error (unkown command):", smove)