MoKy

Autoplayer

Aug 7th, 2025 (edited)
221
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.59 KB | None | 0 0
  1. # Copy to the same folder
  2. # executable file of Fairy Stockfish, https://github.com/fairy-stockfish/Fairy-Stockfish/releases
  3. # variants.ini containing your defined variant, https://github.com/fairy-stockfish/Fairy-Stockfish/blob/master/src/variants.ini
  4. # autoplayer.ini created from the commented out block below, change the variant name
  5. """
  6. [Engine]
  7. path = stockfish
  8. variant = chess
  9. threads = 1
  10. hash = 1024
  11.  
  12. [Game]
  13. games = 100
  14. depth = 20
  15. limit_moves = 100
  16. equal_phase = 10
  17. draw_threshold = 10
  18. """
  19. # and autoplayer.py
  20. import subprocess
  21. import configparser
  22. import re
  23. import os
  24. import platform
  25. from datetime import datetime
  26.  
  27. class UCIEngine:
  28.     def __init__(self, name, common_config):
  29.         self.name = name
  30.        
  31.         engine_path = common_config.get('path', 'stockfish')
  32.         if '.' not in engine_path:
  33.             if platform.system() == 'Windows':
  34.                 self.path = engine_path + '.exe'
  35.             else:
  36.                 self.path = engine_path
  37.         else:
  38.             self.path = engine_path
  39.        
  40.         self.process = subprocess.Popen(
  41.             self.path,
  42.             stdin=subprocess.PIPE,
  43.             stdout=subprocess.PIPE,
  44.             stderr=subprocess.PIPE,
  45.             universal_newlines=True,
  46.             bufsize=1
  47.         )
  48.        
  49.         self.send("uci")
  50.         self.wait_for("uciok")
  51.        
  52.         variant = common_config.get('variant')
  53.         if variant:
  54.             self.send(f"load variants.ini")
  55.             self.send(f"setoption name UCI_Variant value {variant}")
  56.        
  57.         for option, value in common_config.items():
  58.             if option == 'threads':
  59.                 self.send(f"setoption name Threads value {value}")
  60.             elif option == 'hash':
  61.                 self.send(f"setoption name Hash value {value}")
  62.        
  63.         self.send("setoption name Ponder value true")
  64.        
  65.         self.send("isready")
  66.         self.wait_for("readyok")
  67.    
  68.     def send(self, command):
  69.         self.process.stdin.write(command + "\n")
  70.         self.process.stdin.flush()
  71.    
  72.     def wait_for(self, response):
  73.         while True:
  74.             line = self.process.stdout.readline().strip()
  75.             if line == response:
  76.                 break
  77.    
  78.     def get_diagram(self, moves):
  79.         position = "startpos" if not moves else f"startpos moves {' '.join(moves)}"
  80.         self.send(f"position {position}")
  81.         self.send("d")
  82.        
  83.         diagram_lines = []
  84.         capture = False
  85.         while True:
  86.             line = self.process.stdout.readline().strip()
  87.             if line.startswith("+---"):
  88.                 capture = True
  89.             if capture:
  90.                 if line and len(line) > 0 and line[0].isalpha() and "   " in line:
  91.                     line = "  " + line
  92.                 diagram_lines.append(line)
  93.                 if not line or line.startswith("Fen:"):
  94.                     if diagram_lines and not diagram_lines[-1]:
  95.                         diagram_lines.pop()
  96.                     break
  97.        
  98.         self.send("isready")
  99.         self.wait_for("readyok")
  100.        
  101.         return diagram_lines
  102.    
  103.     def get_move(self, moves, depth):
  104.         position = "startpos" if not moves else f"startpos moves {' '.join(moves)}"
  105.         self.send(f"position {position}")
  106.        
  107.         self.send(f"go depth {depth}")
  108.         score = "?"
  109.         ponder = None
  110.        
  111.         while True:
  112.             line = self.process.stdout.readline().strip()
  113.             if "currmove" in line or "lowerbound" in line or "upperbound" in line:
  114.                 continue
  115.                
  116.             if "score cp" in line:
  117.                 match = re.search(r'score cp (-?\d+)', line)
  118.                 if match:
  119.                     score = match.group(1)
  120.             elif "score mate" in line:
  121.                 match = re.search(r'score mate (-?\d+)', line)
  122.                 if match:
  123.                     score = f"M{match.group(1)}"
  124.                    
  125.             if line.startswith("bestmove"):
  126.                 parts = line.split()
  127.                 move = parts[1]
  128.                 if len(parts) > 3 and parts[2] == "ponder":
  129.                     ponder = parts[3]
  130.                 break
  131.        
  132.         return move, score, ponder
  133.    
  134.     def quit(self):
  135.         self.send("quit")
  136.         self.process.terminate()
  137.  
  138. def play_game(engine1, engine2, game_config, game_number):
  139.     print(f"\n=== GAME {game_number} ===\n")
  140.    
  141.     print("Starting position:")
  142.     diagram = engine1.get_diagram([])
  143.     for line in diagram:
  144.         print(line)
  145.     print()
  146.    
  147.     moves = []
  148.     scores = []
  149.     engines = [engine1, engine2]
  150.     current = 0
  151.     ponder_move = None
  152.     opponent_last_score = None
  153.     balanced_count = 0
  154.    
  155.     depth = game_config.get('depth', '25')
  156.     limit_moves = int(game_config.get('limit_moves', '100'))
  157.     equal_phase = int(game_config.get('equal_phase', '10'))
  158.     draw_threshold = int(game_config.get('draw_threshold', '10'))
  159.    
  160.     while True:
  161.         if ponder_move:
  162.             if len(moves) > 0 and moves[-1] == ponder_move:
  163.                 engines[current].send("ponderhit")
  164.             else:
  165.                 engines[current].send("stop")
  166.                 while True:
  167.                     line = engines[current].process.stdout.readline().strip()
  168.                     if line.startswith("bestmove"):
  169.                         break
  170.        
  171.         move, score, ponder = engines[current].get_move(moves, depth)
  172.        
  173.         if not move or move == "(none)":
  174.             if opponent_last_score and str(opponent_last_score).startswith("M") and not str(opponent_last_score).startswith("M-"):
  175.                 winner = engines[1-current].name
  176.                 print(f"\n{winner} wins!")
  177.             elif score and str(score).startswith("M-"):
  178.                 winner = engines[1-current].name
  179.                 print(f"\n{winner} wins!")
  180.             elif opponent_last_score and str(opponent_last_score).startswith("M-"):
  181.                 winner = engines[current].name
  182.                 print(f"\n{winner} wins!")
  183.             else:
  184.                 print("\nDraw!")
  185.                 winner = "Draw"
  186.            
  187.             if ponder_move:
  188.                 engines[1-current].send("stop")
  189.                 while True:
  190.                     line = engines[1-current].process.stdout.readline().strip()
  191.                     if line.startswith("bestmove"):
  192.                         break
  193.             return winner, moves, scores
  194.        
  195.         moves.append(move)
  196.         scores.append(score)
  197.        
  198.         try:
  199.             score_value = int(score) if not score.startswith('M') else 999
  200.             if abs(score_value) <= draw_threshold:
  201.                 balanced_count += 1
  202.             else:
  203.                 balanced_count = 0
  204.         except:
  205.             balanced_count = 0
  206.        
  207.         if len(moves) >= limit_moves and balanced_count >= equal_phase:
  208.             print(f"\nDraw by termination")
  209.             if ponder_move:
  210.                 engines[1-current].send("stop")
  211.                 while True:
  212.                     line = engines[1-current].process.stdout.readline().strip()
  213.                     if line.startswith("bestmove"):
  214.                         break
  215.             return "Draw", moves, scores
  216.        
  217.         print(f"Move {len(moves)}: {move} ({score})")
  218.         print()
  219.        
  220.         diagram = engines[current].get_diagram(moves)
  221.         for line in diagram:
  222.             print(line)
  223.         print()
  224.        
  225.         if ponder and ponder != "(none)":
  226.             ponder_position = f"startpos moves {' '.join(moves)} {ponder}"
  227.             engines[1-current].send(f"position {ponder_position}")
  228.             engines[1-current].send("go ponder")
  229.             ponder_move = ponder
  230.         else:
  231.             ponder_move = None
  232.        
  233.         opponent_last_score = score
  234.         current = 1 - current
  235.    
  236.     return "Draw", moves, scores
  237.  
  238. def save_game(game_number, moves, scores, result, filename):
  239.     timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
  240.     with open(filename, "a", encoding="utf-8") as f:
  241.         f.write(f"Game {game_number} {timestamp}\n")
  242.         moves_with_scores = []
  243.         for i, move in enumerate(moves):
  244.             if i < len(scores):
  245.                 moves_with_scores.append(f"{move}({scores[i]})")
  246.             else:
  247.                 moves_with_scores.append(move)
  248.         f.write(f"{' '.join(moves_with_scores)}\n")
  249.         f.write(f"{result}\n")
  250.         f.write("\n")
  251.  
  252. def main():
  253.     config = configparser.ConfigParser()
  254.     config.read('Autoplayer.ini')
  255.    
  256.     common_config = dict(config['Engine'])
  257.     game_config = dict(config['Game'])
  258.    
  259.     num_games = int(game_config.get('games', '1'))
  260.    
  261.     variant = common_config.get('variant', 'games')
  262.     output_file = f"{variant}.txt"
  263.    
  264.     engine1 = UCIEngine("White", common_config)
  265.     engine2 = UCIEngine("Black", common_config)
  266.    
  267.     results = {
  268.         engine1.name: 0,
  269.         engine2.name: 0,
  270.         "Draw": 0
  271.     }
  272.    
  273.     for game_num in range(1, num_games + 1):
  274.         result, moves, scores = play_game(engine1, engine2, game_config, game_num)
  275.        
  276.         save_game(game_num, moves, scores, result, output_file)
  277.        
  278.         if result in [engine1.name, engine2.name]:
  279.             results[result] += 1
  280.         else:
  281.             results["Draw"] += 1
  282.        
  283.         print(f"\nScore after {game_num} games:")
  284.         print(f"{engine1.name}: {results[engine1.name]} - {engine2.name}: {results[engine2.name]} - Draws: {results['Draw']}")
  285.    
  286.     engine1.quit()
  287.     engine2.quit()
  288.    
  289. if __name__ == "__main__":
  290.     main()
  291.  
Advertisement
Add Comment
Please, Sign In to add comment