Why not a linkable interface?

Discussion of chess software programming and technical issues.

Moderators: hgm, Dann Corbit, Harvey Williamson

User avatar
hgm
Posts: 27701
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Why not a linkable interface?

Post by hgm »

It sounds like you want engines to directly invoke other engines. I am not sure whether this is a good idea no matter what method you use to do that. You also seem to suppose that these other engines would supply all other kind of services that are useful for engine developers (as opposed to users), like perft etc. I wouldnot expect most engine authors to be willing provide such services, if these are not needed for normal game play.
User avatar
stegemma
Posts: 859
Joined: Mon Aug 10, 2009 10:05 pm
Location: Italy
Full name: Stefano Gemma

Re: Why not a linkable interface?

Post by stegemma »

hgm wrote:It sounds like you want engines to directly invoke other engines. I am not sure whether this is a good idea no matter what method you use to do that. You also seem to suppose that these other engines would supply all other kind of services that are useful for engine developers (as opposed to users), like perft etc. I wouldnot expect most engine authors to be willing provide such services, if these are not needed for normal game play.
Yes, I know the problem: if you give your evaluation function output, for sample, it would be easy to "indirectly clone" the software. Any function can return an error code, if the programmer doesn't want to share some capability of its engine: ERR_NOT_SUPPORTED. As at present anybody can choose to use xBoard or UCI interface, so somebody can add a socket interface (or whatever) limited to want he/she wants. For open-source softwares (Crafty, StockFish and so on...) there would be no problem at all.
Author of Drago, Raffaela, Freccia, Satana, Sabrina.
http://www.linformatica.com
kbhearn
Posts: 411
Joined: Thu Dec 30, 2010 4:48 am

Re: Why not a linkable interface?

Post by kbhearn »

hgm wrote:@Michel:
That reasoning uses properties of the high-level protocol to judge the lower-level connection method. How you connect is an issue that is quite separate from what you send over the connection. stdin/stdout-based connections could also just start every command they send with the number of a routine, followed by a list of parameters that should be passed to that routine. Either as text or in binary. And direct linking could be used to call a routine with a text-string argument that contains a command to be parsed.

I never found the task of parsing the command line a real burdon. It just looks like

Code: Select all

sscanf(inBuf, "%s", command);
if(!strcmp(command, "new")) {} else
if(!strcmp(command, "force")) {} else
if(!strcmp(command, "go")) {} else
if(!strcmp(command, "time")) { t= atoi(inBuf+5); } else
...
This does not seem essentially different from writing

Code: Select all

void DoNew() {}
void DoForce() {}
void DoGo () {}
void DoTime(int t) {}
...
with the same {} parts, as you would need in the DLL case.
that's a very shallow look at things when most of the work in a parser is in things like:
fen parsers
move parsers
context-based argument separators that require you to do tokenising one step at a time
handling syntax errors at least somewhat gracefully

there are sizeable advantages to a well defined library interface using binary formats - not that i'm suggesting it's a good idea (i have no idea what would be needed to counter the safety issues mentioned elsewhere, and we would need a popular interface to adopt it and publicly release it as a standard first probably before anyone would agree to use it).

I would sooner like to see a textstream protocol that's
- more parsing-friendly than CECP (the features that make it so may well just wind up being 'rejected' so you windup needing a) to track your accepted/rejected messages instead of just firing off your features and forgetting and b) a fallback implementation anyways so the feature may as well not exist - this may perhaps be alleviated if someone kept track of which features are supported by which interfaces somewhere so one could at least see 'oh good, i CAN just 'feature sigint = 0' and not have to bother with overloading signal handlers for the majority of interfaces - how many of them even support protover 2?)

- less verbose/more cleanly extendable than UCI (the gist i got from reading it is if you had an interface/engine pair and wanted to add new functionality you're supposed to just rely on the 'ignore anything you don't understand' behavior - does that in general hold true or would a line that says 'hey i can do this if you're interested' from the engine to the interface cause interfaces that have no idea what that means to choke?) also minor annoyances: parsing the entire game state every move, the assumption that the gui should be responsible for telling the engine what to ponder, gui responsible for handling all decisions in regards to resignation/draw offers
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

ChessChat

Post by sje »

There are two issues here:

1. The format and semantics of the data to be transferred.
2. The means of data transfer.

For the first, we could chose binary or readable text. If a binary format is chosen, then there is a need to present the data in a portable manner. For readable text, we could pick single text lines or some standard format like XML or a property list representation like the one used in Mac OS/X. Regardless of choice, there must be uniformity and not any ad hoc treatments dependent on the particular command or status being transferred.

For the second, it's obvious that a socket-based listen/accept/connect approach is needed, as only that can provide the portability, uniformity, and flexibility.

I'd like to see a solution where all the low level work is done by open source, free library code which works on any Unix-style host and Windows as well. With a socket approach, a program can have multiple simultaneous connections; it can operate as a client or server; it can operate as a peer with one or more connected peers.

----

I will be writing something like the above for my own use and will share it. But this will take a significant amount of time to complete. It's called ChessChat and will be useful for any event driven chess program (engine, interface, ICS, etc.); it's first use will be in further automating the distributed perft() effort and the second will be in implementing Symbolic's distributed search.

A ChessChat program can be implemented as a standalone application or as a server/service program. A compatible program will announce its presence on a system or network and will work with other compatible programs, each according to its capabilities.

A ChessChat engine could connect to other engines and use them to assist with a search. The ensemble of engines need not be on the same machine or be the same code or use the same host architecture. One engine might be a general searcher; another could look for mating sequences; another could be the tablebase endgame expert, etc.

A ChessChat tournament director program could automatically discover and connect with compatible engines and run an event without the need for human supervision. A ChessChat database program could listen to an event and track all the games and results. An entire ICS could be built based on ChessChat, as could as ICS client used for human interaction.

The big idea here is to partition global functionality into separate, relatively small programs instead of combining all the functionality into a single, hard-to-maintain program. This also encourages the development of new and better components; programs which can be quickly produced by a small developer team.
Dann Corbit
Posts: 12482
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: ChessChat

Post by Dann Corbit »

http would be a logical transfer method.
It would work locally or across the planet.
And almost every machine in the world has the http port open.
So configuration would be a lot easier.
User avatar
hgm
Posts: 27701
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Why not a linkable interface?

Post by hgm »

kbhearn wrote:that's a very shallow look at things when most of the work in a parser is in things like:
fen parsers
move parsers
context-based argument separators that require you to do tokenising one step at a time
handling syntax errors at least somewhat gracefully
I sort of assumed that a DLL entry point to set up a position would also pass a FEN, so that you would need a FEN parser anyway. You would need to represent the position somehow, and it is unlikely to be presented in the exact same representation as your engine uses internally. So you would need to do a lot of stuff there no matter what.

Moves are sort of trivial to parse, especially if you don't worry about boards with more than 10 ranks. There really isn't much difference between passing a move as 4 ints + 1 char (fromX, fromY, toX, toY and promoChar) argument to a function, or as text string.

I am not sure what you mean by the context-based separators. Does CECP have any of that in GUI->engine traffic? I never really tokenize anything; just use an sscanf on the entire input line when a command has arguments.

Handling syntax errors is a somewhat dubious goal. It is not the engine's task to be a debugging tool for the GUI. Counting on the input to be 100% correct, and causing nasal demons otherwise is a perfectly acceptable approach. E.g. see the KingSlayer protocol parser below.

I agree that this whole feature handshaking looks good on paper, but doesn't really work well when the GUI is not omnipotent and omniscient. Features that suppress or alter default behavior of a GUI are the most troublesome, and often quite useless. If the engine has to be prepared for their rejection, it might as well have not used them.

Code: Select all

int
ParseMove (char *s)
{
  int m = &#40;s&#91;0&#93; - 'a') << 8 | &#40;s&#91;1&#93; - '1') << 12 | &#40;s&#91;2&#93; - 'a') | &#40;s&#91;3&#93; - '1') << 4; // pack from & to-square into lowest 2 bytes
  if&#40;s&#91;4&#93;) ; // TODO&#58; promotion
  return m;
&#125;

int
DoCommand &#40;int searching&#41;
&#123;
  while&#40;1&#41; &#123; // usually we break out of this loop after treating one command

    ReadLine&#40;);                   // read one line into inBuf &#40;or retrieve backlogged&#41;
    if&#40;!*inBuf&#41; exit&#40;0&#41;;          // EOF, terminate
    sscanf&#40;inBuf, "%s", command&#41;; // extract the first word
    *inBuf = 0;                   // and already mark the buffer as empty

printf&#40;"# command = %s s=%d\n", command, shelters&#41;, fflush&#40;stdout&#41;;
    // recognize and execute 'easy' commands, that can be done during search
    if&#40;!strcmp&#40;command, "quit"))    &#123; exit&#40;0&#41;; &#125;  // exit immediately
    if&#40;!strcmp&#40;command, "otim"))    &#123; continue; &#125; // move will follow immediately, wait for it
    if&#40;!strcmp&#40;command, "time"))    &#123; sscanf&#40;inBuf+4, "%d", &timeLeft&#41;; continue; &#125;
    if&#40;!strcmp&#40;command, "easy"))    &#123; ponder = OFF; return 0; &#125;
    if&#40;!strcmp&#40;command, "hard"))    &#123; ponder = ON;  return 0; &#125;
    if&#40;!strcmp&#40;command, "post"))    &#123; postThinking = ON; return 0; &#125;
    if&#40;!strcmp&#40;command, "nopost"))  &#123; postThinking = OFF;return 0; &#125;
    if&#40;!strcmp&#40;command, "random"))  &#123; randomize = ON;    return 0; &#125;
    if&#40;!strcmp&#40;command, "."))       &#123; return 0; &#125; // periodic update request; ignore for now
    if&#40;!strcmp&#40;command, "option")) &#123; // setting of engine-define option; find out which
      if&#40;sscanf&#40;inBuf+7, "Evaluate piece mobility=%d",   &mobEval&#41;     == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Evaluate trapped pieces=%d",   &patterns&#41;    == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Evaluate drawish material=%d", &drawishness&#41; == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Evaluate specific endings=%d", &recognizers&#41; == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Evaluate pawn structure=%d",   &pawnStruct&#41;  == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Evaluate king shelter=%d",     &shelters&#41;    == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Evaluate king seige=%d",       &kingSafety&#41;  == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Resign=%d",   &resign&#41;         == 1&#41; return 0;
      if&#40;sscanf&#40;inBuf+7, "Contempt=%d", &contemptFactor&#41; == 1&#41; return 0;
      return 1;
    &#125;

    if&#40;searching&#41; &#123;
      if&#40;!strcmp&#40;command, "usermove")) &#123; return 1; &#125; // TODO during search we just test for ponder hit
      *inBuf = *command;                             // backlog command &#40;by repairing inBuf&#41;
      return 1;                                      // and request search abort
    &#125;

    // the following commands can &#40;or need&#41; only be done when not searching
    if&#40;!strcmp&#40;command, "force"))   &#123; engineSide = NONE;    return 1; &#125;
    if&#40;!strcmp&#40;command, "analyze")) &#123; engineSide = ANALYZE; return 1; &#125;
    if&#40;!strcmp&#40;command, "exit"))    &#123; engineSide = NONE;    return 1; &#125;
    if&#40;!strcmp&#40;command, "level"))   &#123;
      int min, sec=0;
      sscanf&#40;inBuf, "level %d %d %d", &mps, &min, &inc&#41; == 3 ||  // if this does not work, it must be min&#58;sec format
      sscanf&#40;inBuf, "level %d %d&#58;%d %d", &mps, &min, &sec, &inc&#41;;
      timeControl = 60*min + sec; timePerMove = -1;
      return 1;
    &#125;
    if&#40;!strcmp&#40;command, "protover"))&#123;
      printf&#40;"feature ping=1 setboard=1 colors=0 usermove=1 memory=1 debug=1 reuse=0 myname="QueenSlayer " VERSION ""\n");
      printf&#40;"feature option="Resign -check 0"\n");           // example of an engine-defined option
      printf&#40;"feature option="Contempt -spin 0 -200 200"\n"); // and another one
      printf&#40;"feature option="Evaluate piece mobility -check %d"\n", mobEval&#41;; // options to enable eval features
      printf&#40;"feature option="Evaluate trapped pieces -check %d"\n", patterns&#41;;
      printf&#40;"feature option="Evaluate drawish material -check %d"\n", drawishness&#41;;
      printf&#40;"feature option="Evaluate specific endings -check %d"\n", recognizers&#41;; 
      printf&#40;"feature option="Evaluate pawn structure -check %d"\n", pawnStruct&#41;;
      printf&#40;"feature option="Evaluate king shelter -check %d"\n", shelters&#41;;
      printf&#40;"feature option="Evaluate king seige -check %d"\n", kingSafety&#41;;
      printf&#40;"feature done=1\n");
      return 1;
    &#125;
    if&#40;!strcmp&#40;command, "sd"))      &#123; sscanf&#40;inBuf+2, "%d", &maxDepth&#41;;    return 1; &#125;
    if&#40;!strcmp&#40;command, "st"))      &#123; sscanf&#40;inBuf+2, "%d", &timePerMove&#41;; return 1; &#125;
    if&#40;!strcmp&#40;command, "memory"))  &#123; if&#40;SetMemorySize&#40;atoi&#40;inBuf+7&#41;)) printf&#40;"tellusererror Not enough memory\n"), exit&#40;-1&#41;; return 1; &#125;
    if&#40;!strcmp&#40;command, "ping"))    &#123; printf&#40;"pong%s", inBuf+4&#41;; return 1; &#125;
//  if&#40;!strcmp&#40;command, ""))        &#123; sscanf&#40;inBuf, " %d", &); return 1; &#125;
    if&#40;!strcmp&#40;command, "new"))     &#123; engineSide = BLACK; stm = Setup&#40;DEFAULT_FEN&#41;; maxDepth = MAXPLY; randomize = OFF; return 1; &#125;
    if&#40;!strcmp&#40;command, "setboard"))&#123; engineSide = NONE;  stm = Setup&#40;inBuf+9&#41;; return 1; &#125;
    if&#40;!strcmp&#40;command, "undo"))    &#123; stm = TakeBack&#40;1&#41;; return 1; &#125;
    if&#40;!strcmp&#40;command, "remove"))  &#123; stm = TakeBack&#40;2&#41;; return 1; &#125;
    if&#40;!strcmp&#40;command, "go"))      &#123; engineSide = stm;  return 1; &#125;
    if&#40;!strcmp&#40;command, "hint"))    &#123; if&#40;ponderMove != INVALID&#41; printf&#40;"Hint&#58; %s\n", MoveToText&#40;ponderMove&#41;); return 1; &#125;
    if&#40;!strcmp&#40;command, "book"))    &#123;  return 1; &#125;
    // completely ignored commands&#58;
    if&#40;!strcmp&#40;command, "xboard"))  &#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "computer"))&#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "name"))    &#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "ics"))     &#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "accepted"))&#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "rejected"))&#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "variant")) &#123; return 1; &#125;
    if&#40;!strcmp&#40;command, "?"))       &#123; return 1; &#125; // 'move now'
    if&#40;!strcmp&#40;command, ""))  &#123;  return 1; &#125;
    if&#40;!strcmp&#40;command, "usermove"))&#123;
      int move = ParseMove&#40;inBuf+9&#41;;
      if&#40;move == INVALID&#41; printf&#40;"Illegal move\n");
      else if&#40;!MakeMove&#40;move&#41;) printf&#40;"Illegal move\n");
      else &#123;
        ponderMove = INVALID;
      &#125;
      return 1;
    &#125;
    printf&#40;"Error&#58; unknown command\n");
  &#125;
  return 0;
&#125;
Last edited by hgm on Fri Sep 25, 2015 11:59 pm, edited 2 times in total.
Henk
Posts: 7210
Joined: Mon May 27, 2013 10:31 am

Re: ChessChat

Post by Henk »

Web API, RESTful API, RESTful service. Please don't ask me what it is.
Dann Corbit
Posts: 12482
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: ChessChat

Post by Dann Corbit »

Henk wrote:Web API, RESTful API, RESTful service. Please don't ask me what it is.
Representational state transfer is just a slightly formalized method of web transfer.
mvk
Posts: 589
Joined: Tue Jun 04, 2013 10:15 pm

Re: Why not a linkable interface?

Post by mvk »

I have good experience with xboard/UCI over TCP/IP for distributed systems. When routing over the Internet, add a SSH layer for encryption and compression. You can also listen on localhost when running locally, or even on a unix domain socket to bypass the networking layer.

And process-local, for direct access to search and eval for low overhead when tuning, the python C-API, using FEN and UCI move notation in arguments (avoid binary schemes).

If I were to redesign a line protocol, I would use streams of JSON-encoded messages as I have very good experience with that at work. And, again, FEN+UCI moves notation. But we already have enough line protocols and UCI is pretty good.

Shared libraries poses a problem on unix like system, because LDPATH doesn't have a user-local component in it by default. So it is always a hassle to install software that comes with it, unless done system wide. cutechess-cli comes to mind: it is much easier to link that statically than to bother with the so.
[Account deleted]
kbhearn
Posts: 411
Joined: Thu Dec 30, 2010 4:48 am

Re: Why not a linkable interface?

Post by kbhearn »

hgm wrote: I sort of assumed that a DLL entry point to set up a position would also pass a FEN, so that you would need a FEN parser anyway. You would need to represent the position somehow, and it is unlikely to be presented in the exact same representation as your engine uses internally. So you would need to do a lot of stuff there no matter what.
Wouldn't need to be. Could be a pointer to a 64 byte array and a flags integer. Something that can be converted to a position in your engine's native format in < 10 lines.
Moves are sort of trivial to parse, especially if you don't worry about boards with more than 10 ranks. There really isn't much difference between passing a move as 4 ints + 1 char (fromX, fromY, toX, toY and promoChar) argument to a function, or as text string.
Depends on move format. UCI move format being very rigid as to exactly what it's going to send you and you just have to pick out the two oddball o-o and o-o-o cases first is reasonable. CECP i think is in the same boat but i think i remembered some vagueness that led me to think a generic 'any-algebraic' notation parser might wind up being necessary as opposed to a mostly fixed with move with optional promotion character.
I am not sure what you mean by the context-based separators. Does CECP have any of that in GUI->engine traffic? I never really tokenize anything; just use an sscanf on the entire input line when a command has arguments.
both protocols have it. it's driven by the desire to use space as the separator some of the time and then use space as an allowed mid-string character other times. in my dream protocol one character (say 'tab') would be reserved as a universal argument seperator such that the first step could merely be to feed the string into a tokeniser and get a list of strings which thereafter could be treated as discrete arguments. As it stands it's strip off the command, and then depending on the command the next thing you might need to use as a delimiter might be an '=', it might be a ' ', it might be ' value '(for a uci example) causing you to have to write per-command tokenising.
Handling syntax errors is a somewhat dubious goal. It is not the engine's task to be a debugging tool for the GUI. Counting on the input to be 100% correct, and causing nasal demons otherwise is a perfectly acceptable approach. E.g. see the KingSlayer protocol parser below.
Move parsing! specifically the part of the CECP protocol where if you haven't set the (rejectable) feature usermove=1 you don't know if what you're left with is a move or a command you didn't remember to catch or what. Accordingly at the very least your move parser needs to be able to return false to you to say no this isn't a move you idiot. In general handling unexpected syntax gracefully is useful in order to write a log entry or send an error to the gui that 'hey, i don't know what this command was' to aid in debugging (probably i should've been able to handle it so it's good to see what it was).