Einzelnen Beitrag anzeigen
  #1  
Alt 09.02.2026, 14:35
Tibono Tibono ist offline
Revelation
 
Registriert seit: 22.05.2018
Ort: Frankreich
Alter: 64
Land:
Beiträge: 687
Abgegebene Danke: 5.342
Erhielt 1.580 Danke für 551 Beiträge
Aktivitäten Langlebigkeit
9/20 7/20
Heute Beiträge
2/3 ssssss687
Weakening an UCI engine (Windows, Python)

Hello dear all,

inspired by the ChessSystemTal2-v21 invincible? thread and as well by the birth of Absurd-1.0 engine from the same authors (C. Whittington & E. Schröder), mentioned in the very same thread, I wanted to achieve a much weaker version. Currently these engines are pretty useless for human players and chess computers, maybe except the SenseRobot Chess. On another hand, they do provide wild games when facing other mighty chess engines, and that's already an achievement - both authors deserve many thanks for sharing these.

Time ago I learnt IT development, I have been pretty efficient writing Cobol but this doesn't help here

So, I asked ChatGPT for some help. This provided me with the basics which nevertheless required many tests and manual improvements.

This solution is based on Python; if you don't have it installed check here. The code I use is pretty basic, hence I don't suspect any particular version requirement.

It is a "wrapper" meaning the Python code will act between the GUI and the UCI engine. It handles both channel queues, the one the GUI uses to talk to the engine, and the one the engine uses to talk to the GUI. It is so quite similar to known tools such as InBetween and Polyglot, just made for the specific goal of slowing down and limiting the strength of the engine.

Here is the Python code:
Code:
import subprocess
import threading
import sys
import time
import queue
import re

ENGINE = ["Absurd-1.0.exe"]

DEPTH_LIMIT = 4
INFO_DELAY = 2.5

info_queue = queue.Queue()

bestmove_buffer = None
bestmove_sent = False

engine = subprocess.Popen(
    ENGINE,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    text=True,
    bufsize=1
)

# -------- GUI → engine --------
def gui_to_engine():
    global bestmove_buffer, bestmove_sent

    for line in sys.stdin:
        line = line.strip()

        # reset state for each new move
        if line.startswith("position"):
            bestmove_buffer = None
            bestmove_sent = False

        # enforce ponder=false
        if line.lower().startswith("setoption name ponder"):
            engine.stdin.write("setoption name Ponder value false\n")
            engine.stdin.flush()
            sys.stdout.write("info Ponder forced to false" + "\n")
            sys.stdout.flush()
            continue

        # replace any go with go depth
        if line.startswith("go"):
            engine.stdin.write(f"go depth {DEPTH_LIMIT}\n")
            engine.stdin.flush()
            sys.stdout.write(f"info go depth forced to {DEPTH_LIMIT}\n")
            sys.stdout.flush()
            continue

        engine.stdin.write(line + "\n")
        engine.stdin.flush()

# -------- listening to engine --------
def engine_reader():
    global bestmove_buffer

    for line in engine.stdout:
        line = line.strip()

        if line.startswith("info") and not line.startswith("info string"):
            info_queue.put(line)

        elif line.startswith("bestmove"):
            # store without posting
            bestmove_buffer = line

        else:
            # uciok, readyok, ...
            sys.stdout.write(line + "\n")
            sys.stdout.flush()

# -------- slowed down info + triggering logic --------
def info_emitter():
    global bestmove_sent, bestmove_buffer

    while True:
        line = info_queue.get()
        if not bestmove_sent:
           time.sleep(INFO_DELAY)

           trigger = False

           # --- A condition: opening move (Polyglot syntax) ---
           if line == "info depth 1 time 0 nodes 0 nps 0 cpuload 0":
               trigger = True
               sys.stdout.write("info move played from opening library\n")
               sys.stdout.flush()

           # --- B condition: max depth reached ---
           else:
               m = re.search(r"\bdepth\s+(\d+)", line)
               if m:
                   reached = int(m.group(1))
                   if reached >= DEPTH_LIMIT:
                       trigger = True

           # display line
           if not bestmove_sent:
               sys.stdout.write(line + "\n")
               sys.stdout.flush()

           # post bestmove once max depth PV displayed
           if trigger and bestmove_buffer and not bestmove_sent:
               sys.stdout.write(bestmove_buffer + "\n")
               sys.stdout.flush()
               bestmove_sent = True
               bestmove_buffer = None

# -------- Starting up --------
threading.Thread(target=gui_to_engine, daemon=True).start()
threading.Thread(target=engine_reader, daemon=True).start()
info_emitter()
3 values need to be set: the engine exe file name, a depth limiter, and a slowing down delay. In the above example, Absurd-1.0 is throttled down to four plies depth and each PV display will include a 2.5 secs wait. Most often, 4 to 5 PVs will be displayed, resulting in 4x2.5 to 5x2.5 = 10 to 12 secs thinking time.
Adjust the Depth & Delay to your taste, and to the expected game time setting. Sometimes, in complex positions, more PVs will be displayed (with selective depth increasing along), resulting in more time used to move, which is quite natural. Some moves will also be played much faster; hence a human-like pace. Ponder is disabled (but the opponent may ponder, if wanted, and make good use of the slowed engine time to think). Opening moves should still be played a tempo.

How to use?
I assume Python is installed on your PC; in the GUI (tested using Arena and HIARCS Chess Explorer Pro) you may choose the command line engine target as a .bat file instead of a .exe; so we need a tiny .bat file including only one line: python Wrapper_v2.py
The above assumes you saved the Python code under the name Wrapper_v2.py (feel free to change that).
I also provide a .zip file including both Python code and bat file. They must be located in the same directory where the wrapped engine is.

The Wrapper ignores GUI instructions about pondering and time control; hence you can have the engine face any opponent granted any time control (great for playing as a human or launching a tournament including MessChess engines).

Please let me know if specific issues occur; and if the solution is useful to you. I tested it with a couple of engines, including SF18 and polyglot (and of course Absurd and CSTal2_v21 as well). I also want to test Patricia 5 soon

MfG,
Tibono

p.s.: I provided a new, enhanced version (SlowWrapper v4), please read down a couple of posts for more information, and get the new attachment.

Geändert von Tibono (10.02.2026 um 21:00 Uhr)
Mit Zitat antworten
Folgende 3 Benutzer sagen Danke zu Tibono für den nützlichen Beitrag:
kamoj (09.02.2026), mclane (09.02.2026), spacious_mind (09.02.2026)