Schachcomputer.info Community

Schachcomputer.info Community (https://www.schachcomputer.info/forum/index.php)
-   Oldie & Retro Schachprogramme / Chess Programs (https://www.schachcomputer.info/forum/forumdisplay.php?f=52)
-   -   Idee: Weakening an UCI engine (Windows, Python) (https://www.schachcomputer.info/forum/showthread.php?t=7453)

Tibono 09.02.2026 14:35

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 :D

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 :yummy:

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.

spacious_mind 09.02.2026 15:35

Re: Weakening an UCI engine (Windows, Python)
 
Hi Eric

Thanks, once I finish with SenseRobot I will try it out.

Regards

Nick

Tibono 09.02.2026 18:26

AW: Weakening an UCI engine (Windows, Python)
 
I watched two games with swapped colors Absurd 1.0 vs Patricia 5, both throttled to depth 4. I know I could have run this "bullet-like" using the normal engines and selecting depth 4 as the common GUI level, but I wasn't interested in. I wanted to be the kibitzer, and I was. Funny enough, Patricia 5 always increased depth one by one, resulting in 10secs /move (4 PVs, 2.5 secs/PV display for the setting I chose). On another hand, Absurd is more on the chatterbox side, frequently changing PV on intermediate depths. Yet overall he moved slightly under 15secs/move on average. And lost both games.

I also watched two games with swapped colors Absurd 1.0 vs ACI Boris set to 15secs/move. Two wins for Absurd. But YES, such matches can happen.

Main (header) outcomes from CARA analysis:

Code:

Absurd 1.0 NNUE SW
Overview
========
Total Games: 4
Win Rate: 50.0%
Record: 2-0-2
Average Accuracy: 78.9%
Estimated Elo: 1131
Average CPL: 87.8
Top 3 Move %: 53.4%

Patricia 5 NNUE SW
Overview
========
Total Games: 2
Win Rate: 100.0%
Record: 2-0-0
Average Accuracy: 85.8%
Estimated Elo: 1323
Average CPL: 25.2
Top 3 Move %: 61.8%

MC ACI Boris
Overview
========
Total Games: 2
Win Rate: 0.0%
Record: 0-0-2
Average Accuracy: 67.4%
Estimated Elo: 1033
Average CPL: 121.6
Top 3 Move %: 40.9%

I am very happy with the CARA estimate for Boris Elo: 1033. It scores 1036 in my private rating list. Only two analyzed games, though. I worked hard to help the author normalize the CARA Elo estimates.

In the names of the engines, SW stands for 'SlowWrapper' version; MC for MessChess.
One can easily duplicate engines to keep one full-speed, full strength version alongside a "SlowWrapped" one.

Last but not least: using SlowWrapper, the CPU stays mostly idle for the throttled down engine... Ecological, isn't it?
All the best,
Tibono

Tibono 10.02.2026 15:48

AW: Weakening an UCI engine (Windows, Python)
 
Hi all,

I simplified the triggering logic (for the GUI to apply the engine move) and made it more compliant with some UCI engines using internal books. Also, I made sure all the PV lines before bestmove sent by the engine were displayed by the SlowWrapper.

As I used the reported depth for halting the display and actually send the bestmove data to the GUI, sometimes additional PVs still at same target depth could be lacking on display, whilst the engine had considered these before reporting its bestmove. There was no impact on the game, yet now the display on the GUI is more reliable, always complete as the engine did report.

New version (v4) attached. Don't forget to adapt the engine exe file name, and the depth_limit and info_delay you want. By the way, info_delay in seconds can use decimals (eg 0.05 would be 50ms - even if such a low value would not help much slowing the engine down).

Regards,
Tibono

ps: new release v4.1, please see hereunder.

DirkS 10.02.2026 16:08

AW: Weakening an UCI engine (Windows, Python)
 
Hi Tibono,

this is a very cool short aproach about a proxy engine. I worked on the same years ago to fix a blunderproblem for the DGTplasticwaste.
If you want a engine that play's alway's on your own level, you must search for a move in some variant's that plays close to equal. The effect is a curiously playing engine that always puts you in the endgame, no matter how badly you play.

Dirk

Tibono 10.02.2026 22:42

AW: Weakening an UCI engine (Windows, Python)
 
Hi,
a quick win: now that the triggering is smarter, engine weakening can be chosen as limiting either depth (as before) or nodes.

In the first version, reaching a specific depth was rather easy to check from the info lines provided by the engine, but reaching a target nodes number was not reliable. The engine could decide to move early, after a lower number of nodes reached (maybe forecasting any further search would too much overstep the target, maybe considering the move obvious or without any decent alternative). Since I now use a smarter triggering, this tricky case with nodes limit is no more any issue.

The change in the code is very light; now you have to edit the Wrapper header to set either LIMIT_TYPE = "depth" or LIMIT_TYPE = "nodes", and that's pretty all. Then, as usual, enter values for the chosen limit and delay.

Attachment provided. Hope this helps,
Tibono
ps: with more tests I saw some engines don't understand uppercase values for "go depth" or "go nodes"; hence please set limits as fixed above, using lowercase for values. I'll re attach the fixed zip file in a following post.

Tibono 11.02.2026 08:10

AW: Weakening an UCI engine (Windows, Python)
 
By the way: the much smarter triggering idea came from my old brain, not from ChatGPT's :D

Tibono 11.02.2026 08:58

AW: Weakening an UCI engine (Windows, Python)
 
1 Anhang/Anhänge
Fixed v4.1 (with more tests I saw some engines don't understand uppercase values for "go depth" or "go nodes"; hence please set limits using lowercase for values). No change to the code, just the comments and example setting, to make it clear.
example:
Code:

LIMIT_TYPE = "nodes" # choose "nodes" or "depth"
LIMIT_VALUE = 1000
INFO_DELAY = 1.75    # in seconds


Tibono 11.02.2026 21:52

AW: Weakening an UCI engine (Windows, Python)
 
1 Anhang/Anhänge
Hi, I wanted to consider those who don't have Python on the PC and maybe are reluctant to install it.

I got some fine help from "Le Chat" (French AI) to consider solutions and I chose to install and use PyInstaller. It can build an exe from the .py script. Of course this prevents the user to enter the required parameters straight into the script, so for this exe version I use a SlowWrapper.ini file (this more and more looks like polyglot or inbetween architecture :) ).

Example SlowWrapper.ini:
Code:

# this file must be stored in the same directory as the engine

[Parametres]

# please set here the .exe file name of the engine
ENGINE = Absurd-1.0.exe

# please set either depth or nodes (do not use uppercase)
LIMIT_TYPE = depth

# please set the limit value (integer)
LIMIT_VALUE = 4

# please set the throttling delay for display of info lines (in seconds; 1.75 = 1750 ms)
INFO_DELAY = 1.75

Now there is no more a .bat file; locate both SlowWrapper.ini and Wrapper_v5.exe in the engine directory; edit the .ini file to your taste, and substitute Wrapper_v5.exe to the engine original exe in the GUI. I trust this solution is easier to adopt.

Of course, those who have Python installed and maybe want to experiment can keep on using the Python script and .bat, setting the parameters directly in the script (check v4.1 attachment previously posted).

MfG,
Tibono

Mark 1 12.02.2026 06:47

AW: Weakening an UCI engine (Windows, Python)
 
Hi Tibono,,,

Great Tool :). Thanx you..

Mark 1


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:48 Uhr.

Powered by vBulletin (Deutsch)
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
©Schachcomputer.info