interfacing with UCI

Discussion of chess software programming and technical issues.

Moderator: Ras

Whiskers
Posts: 243
Joined: Tue Jan 31, 2023 4:34 pm
Full name: Adam Kulju

interfacing with UCI

Post by Whiskers »

Hello, I'm trying to get my chess engine Willow to be UCI-compatible. I'm not very good at interfacing with front end stuff, and am quite embarrassed to say that doing this has been a pain - I've gotten it to be able to set up positions both from an fen string and a list of moves in UCI format, and output and take in moves in UCI format, but am rather confused on how to put it all together. I've been looking at the CPW engine to try and help me out but for some reason it's still not clicking.

I'm sorry I don't know why I'm struggling with this, but can someone perhaps give me a simple example of how to get UCI working? How do I read in stuff from the UCI caller and am I supposed to periodically check for new commands coming in or something? (my code is written in C not C++, I don't know how much of a difference that makes) I don't want to try anything fancy yet and only want at this point to be able to load the engine, analyze for a period of time, and make moves. (though how would actually playing a game work with that?)
lingfors
Posts: 4
Joined: Sun Feb 05, 2023 7:31 pm
Full name: Anders Lingfors

Re: interfacing with UCI

Post by lingfors »

The biggest "issue" with interfacing with UCI is that you're supposed to read commands at the same time as you're thinking on your next move. E.g. the GUI sends you a position "position startpos moves ...", and then tells you to think about the next move forever "go infinite". Then you're supposed to be able to receive the command "stop" while thinking, which should cause the engine to stop thinking and return the best move found so far.

This implies that you need to make your engine multi-threaded: One thread to handle input from UCI, and another thread to do the actual thinking. Then, you need to make these threads communicate with each other.

As for "playing a game", the UCI protocol is stateless and thus doesn't really have any notion of a "game". All there is is a command to set up a new position, and a command to think about what the best move is, given some position. This will repeat itself over and over: The GUI tells the engine what the position is given the opponent's last move, and then the engine is asked to think about what the best move is given the current position.
User avatar
hgm
Posts: 28353
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: interfacing with UCI

Post by hgm »

If you are already periodicaly checking the time in your search, to see if you should abort it, the simplest way is to also test for input at that point. If the input is 'isready' or 'ponderhit' you process it immediately (by either outputting 'readyok' or switching the search mode from infinite to timed), when it is 'stop' you abort the search (like it alrady timed out). Perhaps you should handle 'quit' too. No other commands are supposed to come during search, so you can ignore those.

After a search finishes you can just wait for a command to arrive on stdin, and execute it as it comes. You can ignore 'stop' and 'ponderhit' there.
JoAnnP38
Posts: 253
Joined: Mon Aug 26, 2019 4:34 pm
Location: Clearwater, Florida USA
Full name: JoAnn Peeler

Re: interfacing with UCI

Post by JoAnnP38 »

My understanding of how to best integrate with UCI didn't come together until I reviewed lithander's MinimalChess engine. It might help you too. The search is conducted in a background thread while the main thread continues to read/process input.

Take a look at https://github.com/lithander/MinimalChessEngine
User avatar
algerbrex
Posts: 608
Joined: Sun May 30, 2021 5:03 am
Location: United States
Full name: Christian Dean

Re: interfacing with UCI

Post by algerbrex »

As others have said the usual design, I think is to have one thread so the search can continue and another that can continue to monitor stdin. Lucky for me Golang makes this very easy with goroutines. But it should be straightforward enough in any fairly modern language as they all have support for multithreading.

You're more than welcome to look at Blunder's UCI interface code, though I have to be honest it's not my best coding work: https://github.com/algerbrex/blunder/bl ... ine/uci.go
User avatar
Roland Chastain
Posts: 679
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: interfacing with UCI

Post by Roland Chastain »

The others have already said everything. I add a very simple example.

A UCI engine is something like this:

Code: Select all

#include <stdio.h>
#include <string.h>
#include <limits.h> // LINE_MAX

char bestmove[5];

int main(void)
{
  char command[LINE_MAX];
  
  while (fgets(command, LINE_MAX, stdin) != NULL)
  {
    /* Remove line ending character */
    size_t ln = strlen(command) - 1;
    if (command[ln] == '\n')
      command[ln] = '\0';
    
    if (strcmp(command, "quit") == 0)
    {
      break;
    }
    else if (strcmp(command, "uci") == 0)
    {
      printf("id name NAME\n");
      printf("id author AUTHOR\n");
      printf("uciok\n");
    }
    else if (strncmp(command, "position", 8) == 0)
    {
      /* Load position */
    }
    else if (strncmp(command, "go", 2) == 0)
    {
      /* Compute best move */
      strcpy(bestmove, "e2e4");
      
      /* Send best move to user */
      printf("bestmove %s\n", bestmove);
    }
    else
    {
      printf("Unknown command: '%s'\n", command);
    }
  }
  
  return 0;
}
What is missing in this example is the separate execution thread for calculating the best move. This is what ChessPuter (another example) also lacks.

The approach described by H.G.Muller is a bit different and maybe (IMHO) a little less easy to master.
Qui trop embrasse mal étreint.
User avatar
hgm
Posts: 28353
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: interfacing with UCI

Post by hgm »

You think so? I always thought it was much easier. To get an engine that is free of time forfeits you have to implement a 'cold-turkey timeout' in the search thread anyway, and what I sugegsted is just a very small extension of that. And you don't have to deal with the complexity of multi-threading and thread synchronization.

Code: Select all

int QuickCommand() { // process commands that can come during search
  char *command = ReadLine(stdin);
  if(!strcmp(command, "isready\n")) printf("readyOK\n"); else
  if(!strcmp(command, "ponderhit\n")) timedSearch = TRUE; else
  if(!strcmp(command, "stop\n")) abortFlag = TRUE; else
  if(!strcmp(command, "quit\n")) exit();
}

// in your Search() routine
if(abortFlag) return 0; // before writing to the hash table!
if((nodeCount & 0x3FF) == 0) { // poll only once every 1024 nodes for efficiency
  if(timedSearch && CurrentTime() - startTime > coldTurkeyTimeout) abortFlag = TRUE;
  if(InputPending()) QuickCommand();
}
In the command-interpreter loop of the main program your would have something like:

Code: Select all

char *command = ReadLine();
if(!strncmp(command, "go", 2) {
  timedSearch = ...; // depending on 'go' parameters; FALSE for 'go ponder' or 'go infinite'.
  abortFlag = FALSE;
  Search(...); // find best move
  while(!timedSearch && !abortFlag) QuickCommand(); // wait till non-timed search has been ordered to terminate
  printf("bestmove %s\n", MoveToText(rootMove));
} else ... // other commands
Note this also takes care of the case where a ponder or infinite search would terminate early because it runs into a checkmate.
User avatar
Roland Chastain
Posts: 679
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: interfacing with UCI

Post by Roland Chastain »

@H.G.Muller

This is very clear, thanks. I will try that model if I start a new project.

What seems a bit complicated to me are the functions InputPending() and ReadLine(stdin). If I am not wrong, we have to use OS specific functions for that. With the other model, we can use a standard multiplatform function.
Qui trop embrasse mal étreint.
User avatar
hgm
Posts: 28353
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: interfacing with UCI

Post by hgm »

ReadLine() isn't anything special; I just did not elaborate on that because I assumed it is something that any implementation of a chess program (be it UCI or using any other command set) would already have. You could use fgets(command, 256, stdin) for this when command was declared as char[256].

InputPending() is indeed platform dependent. But not very complex. On Windows I use

Code: Select all

     int InputPending()
     {  // checks for waiting input in pipe
	static int init; static HANDLE inp; DWORD cnt;
	if(!init) inp = GetStdHandle(STD_INPUT_HANDLE);
	if(!PeekNamedPipe(inp, NULL, 0, NULL, &cnt, NULL)) return 1;
	return cnt;
    }
And on Linux:

Code: Select all

     int InputPending()
     {
	int cnt;
	if(ioctl(0, FIONREAD, &cnt)) return 1;
	return cnt;
     }
User avatar
Roland Chastain
Posts: 679
Joined: Sat Jun 08, 2013 10:07 am
Location: France
Full name: Roland Chastain

Re: interfacing with UCI

Post by Roland Chastain »

@H.G.Muller

Very useful informations. Thank you.
hgm wrote: Thu Mar 02, 2023 11:05 amAnd you don't have to deal with the complexity of multi-threading and thread synchronization.
This is something to take into account, indeed.
Qui trop embrasse mal étreint.