MoKy

Autoplayer

Aug 7th, 2025 (edited)
580
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.12 KB | None | 0 0
  1. ### Save as autoplayer.md
  2.  
  3. #### Fairy Stockfish
  4. https://github.com/fairy-stockfish/Fairy-Stockfish/releases
  5.  
  6. #### variants.ini
  7. https://github.com/fairy-stockfish/Fairy-Stockfish/blob/master/src/variants.ini
  8. ```
  9. [Snails]
  10. maxRank = 8
  11. maxFile = h
  12. customPiece1 = X:fmWfceWifmnD
  13. customPiece2 = Y:fmFfceFifmnA
  14. customPiece3 = P:fmWfceFifmnD
  15. customPiece4 = B:fmFfceWifmnA
  16. customPiece5 = S:fmKfceKifmnDifmnA
  17. customPiece6 = G:gR
  18. enPassantTypes = XYPBS
  19. doubleStepRegionWhite = *2 *3 *4 *5 *6
  20. doubleStepRegionBlack = *3 *4 *5 *6 *7
  21. enPassantRegion = *3 *4 *5 *6
  22. capturesToHand = true
  23. pieceDrops = true
  24. whiteDropRegion = *2
  25. blackDropRegion = *7
  26. flagPiece = G
  27. flagRegionWhite = d1 e1 f1 g1 h1
  28. flagRegionBlack = d8 e8 f8 g8 h8
  29. extinctionPieceTypes = G
  30. extinctionPseudoRoyal = true
  31. startFen = g7/***5/***5/***5/***5/***5/***5/G7[XYPBSxypbs] w - - 0 1
  32. ```
  33.  
  34. #### autoplayer.ini
  35. ```
  36. [Engine0]
  37. name = Donald
  38. path = stockfish
  39.  
  40. [Engine1]
  41. name = Waldo
  42. path = stockfish
  43.  
  44. [Common]
  45. variant = Snails
  46. threads = 1
  47. hash = 1024
  48.  
  49. [Game]
  50. movetime_ms = 2000
  51. limit_moves = 200
  52. equal_phase = 20
  53. draw_threshold = 20
  54. ```
  55.  
  56. #### autoplayer.py
  57. ```
  58. import subprocess
  59. import configparser
  60. import re
  61. import time
  62. from datetime import datetime
  63.  
  64. class UCIEngine:
  65.     def __init__(self, name, path, threads, tt_size, variant):
  66.         self.name = name
  67.         self.path = path
  68.         self.process = subprocess.Popen(
  69.             self.path,
  70.             stdin=subprocess.PIPE,
  71.             stdout=subprocess.PIPE,
  72.             stderr=subprocess.PIPE,
  73.             universal_newlines=True,
  74.             bufsize=1,
  75.         )
  76.         self.process.stdout.readline()
  77.         self.send(f"setoption name Threads value {threads}")
  78.         self.send(f"setoption name Hash value {tt_size}")
  79.         self.send("load variants.ini")
  80.         self.send(f"setoption name UCI_Variant value {variant}")
  81.         self.process.stdout.readline()
  82.  
  83.  
  84.     def send(self, command):
  85.         self.process.stdin.write(command + "\n")
  86.         self.process.stdin.flush()
  87.  
  88.     def diagram(self, moves, ranks):
  89.         pos = "startpos" if not moves else f"startpos moves {' '.join(moves)}"
  90.         self.send(f"position {pos}")
  91.         self.send("d")
  92.         diagram = [self.process.stdout.readline().rstrip('\n') for _ in range(2 * ranks + 3)]
  93.         for _ in range(6):
  94.             line = self.process.stdout.readline()
  95.            
  96.         return diagram
  97.  
  98.     def move(self, moves, movetime_ms):
  99.         self.send("position startpos" if not moves else f"position startpos moves {' '.join(moves)}")
  100.         self.send(f"go movetime {movetime_ms}")
  101.  
  102.         start_time = time.time()
  103.         timeout = (movetime_ms / 1000.0) + 0.1
  104.         while time.time() - start_time < timeout:
  105.             line = self.process.stdout.readline()
  106.             if not line:
  107.                 break
  108.             line = line.strip()
  109.  
  110.             if line.startswith("info"):
  111.                 m = re.search(r"score\s+(cp|mate)\s+(-?\d+)", line)
  112.                 if m:
  113.                     kind = m.group(1)
  114.                     value = int(m.group(2))
  115.  
  116.             if line.startswith("bestmove"):
  117.                 parts = line.split()
  118.                 if len(parts) >= 2:
  119.                     best = parts[1]
  120.                 return best, kind, value
  121.  
  122.         return best, kind, value
  123.  
  124.     def quit(self):
  125.         try:
  126.             self.send("quit")
  127.             self.process.wait(timeout=1)
  128.         except subprocess.TimeoutExpired:
  129.             self.process.terminate()
  130.         finally:
  131.             self.process.terminate()
  132.  
  133. def play(engine, white_engine, movetime_ms, limit_moves, equal_phase, draw_threshold, output_file, ranks):
  134.     engine[0].send("stop")
  135.     engine[1].send("stop")
  136.    
  137.     print(f"\n {engine[white_engine].name} vs {engine[1 - white_engine].name}")
  138.     for line in engine[white_engine].diagram([], ranks):
  139.         print(line)
  140.  
  141.     moves, scores = [], []
  142.     current = white_engine
  143.     balanced_count = 0
  144.  
  145.     while True:
  146.         move, kind, value = engine[current].move(moves, movetime_ms)
  147.  
  148.         if move == "(none)":
  149.             if kind == "mate":
  150.                 if scores[-1] == "mate -1":
  151.                     winner = current
  152.                 else:
  153.                     winner = 1 - current
  154.                 print(f"\n {'White' if white_engine == winner else 'Black'} [{engine[winner].name}]")
  155.                 save(moves, scores, f"{'White' if white_engine == winner else 'Black'}[{engine[winner].name}]", output_file, engine[white_engine], engine[1 - white_engine])
  156.                 return engine[winner].name
  157.             else:
  158.                 print(f"\n Draw")
  159.                 save(moves, scores, "Draw", output_file, engine[white_engine], engine[1 - white_engine])
  160.                 return "Draw"
  161.  
  162.         if kind == "cp" and abs(value) <= draw_threshold:
  163.             balanced_count += 1
  164.         else:
  165.             balanced_count = 0
  166.  
  167.         if len(moves) >= limit_moves and balanced_count >= equal_phase:
  168.             print("\n Draw by termination")
  169.             save(moves, scores, "Draw", output_file, engine[white_engine], engine[1 - white_engine])
  170.             return "Draw"
  171.        
  172.         score = f"{kind} {value}"
  173.         moves.append(move)
  174.         scores.append(score)
  175.  
  176.         print(f"\n {engine[current].name} {(len(moves)+1)//2}: {move} ({score})")
  177.         for line in engine[current].diagram(moves, ranks):
  178.             print(line)
  179.  
  180.         current ^= 1
  181.  
  182. def save(moves, scores, result, filename, white, black):
  183.     timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
  184.     with open(filename, "a", encoding="utf-8") as f:
  185.         f.write(f"Game {timestamp}\n")
  186.         f.write(f"{white.name} vs {black.name}\n")
  187.         f.write(f"{' '.join(f'{m} ({s})' for m, s in zip(moves, scores))}\n")
  188.         f.write(f"{result}\n\n")
  189.  
  190. def main():
  191.     config = configparser.ConfigParser()
  192.     config.read('autoplayer.ini')
  193.    
  194.     variant = config['Common']['variant']
  195.    
  196.     engine = [None, None]
  197.     try:
  198.         engine[0] = UCIEngine(
  199.             config['Engine0']['name'],
  200.             config['Engine0']['path'],
  201.             config['Common']['threads'],
  202.             config['Common']['hash'],
  203.             variant,
  204.         )
  205.         engine[1] = UCIEngine(
  206.             config['Engine1']['name'],
  207.             config['Engine1']['path'],
  208.             config['Common']['threads'],
  209.             config['Common']['hash'],
  210.             variant,
  211.         )
  212.  
  213.         movetime_ms = int(config['Game']['movetime_ms'])
  214.         limit_moves = int(config['Game']['limit_moves'])
  215.         equal_phase = int(config['Game']['equal_phase'])
  216.         draw_threshold = int(config['Game']['draw_threshold'])
  217.  
  218.         output_file = f"{variant}.txt"
  219.  
  220.         config = configparser.ConfigParser()
  221.         config.read('variants.ini')
  222.         ranks = int(config[variant]['maxRank'])
  223.        
  224.         results = {engine[0].name: 0, engine[1].name: 0, "Draw": 0}
  225.         white_engine = 0
  226.         games_played = 0
  227.  
  228.         while True:
  229.             games_played += 1
  230.  
  231.             result = play(engine, white_engine, movetime_ms, limit_moves, equal_phase, draw_threshold, output_file, ranks)
  232.            
  233.             if result == "Draw":
  234.                 results["Draw"] += 1
  235.             elif result == engine[white_engine].name:
  236.                 results[engine[white_engine].name] += 1
  237.             else:
  238.                 results[engine[1 - white_engine].name] += 1
  239.  
  240.             print(f" Score after {games_played} games:")
  241.             print(f" {engine[0].name}: {results[engine[0].name]} wins")
  242.             print(f" {engine[1].name}: {results[engine[1].name]} wins")
  243.             print(f" Draws: {results['Draw']}")
  244.  
  245.             print("\n" * (2 * ranks - 1), end="")
  246.  
  247.             white_engine ^= 1
  248.            
  249.     except KeyboardInterrupt:
  250.         print("\n\nStopping autoplayer")
  251.     except Exception as e:
  252.         print(f"\nError occurred {e}")
  253.     finally:
  254.         for e in engine:
  255.             if e is not None:
  256.                 try:
  257.                     e.quit()
  258.                 except:
  259.                     pass
  260.         print("Engines terminated")
  261.  
  262. if __name__ == "__main__":
  263.     main()
  264. ```
Advertisement
Add Comment
Please, Sign In to add comment