GNU Shogi (WinBoard version)

Discussion of chess software programming and technical issues.

Moderator: Ras

TonyJH
Posts: 183
Joined: Tue Jun 20, 2006 4:41 am
Location: USA

Re: USI2WB

Post by TonyJH »

This is a great start on a USI-WinBoard adapter!
hgm wrote:There might be a discrepancy between the USI way of writing moves, and the standard Shogi way. Normally moves that can promote have either a '+' suffix (if they do promote) or an '=' suffix (if they defer promotion). It might be that USI never writes the '=', and that the absense of '+' implies it. In other words, that deferral is default. WinBoard, however, always writes one or the other. But on reading, when the promotion character is missing, promotion is default.
Yes, for USI, '+' is required for promotions, while '=' is assumed and not required for deferring promotion. (I don't think the USI spec even mentions '=', so it would be safer to omit '='.) For WB, '=' is required to defer promotion, while '+' is assumed and not required to promote.
There is both a USI and a WB version of TJshogi, so I had to implement both methods.

I tried using USI2WB to make TJshogi(WB) play vs TJshogi(USI) under WinBoard. The game lasted until move 53 before it reported an illegal move.

Something didn't seem right during the game after several moves were played. Since both TJshogis have the exact same evaluation function, I expect the evals to mirror each other, but eventually they went out of sync.

Code: Select all

[Event "Computer Chess Game"]
[Site "TONY"]
[Date "2010.07.15"]
[Round "-"]
[White "TJshogi 0.03b"]
[Black "TJshogi"]
[Result "1-0"]
[TimeControl "120"]
[Variant "shogi"]
[Annotator "1. +0.04   1... -0.04"]

1. c4 {+0.04/9 0.9} c6 {-0.04/8 3} 2. a4 {+0.04/9 1.5} a6 {-0.04/8 0.4} 3.
f4 {+0.00/8 0.6} i6 {+0.00/8 0.5} 4. f5 {+0.00/7 0.5} i5 {+0.00/8 0.7} 5.
Be5 {+0.00/8 0.6} Ra8 {-0.05/8 1.0} 6. b4 {+0.00/8 1.4} e6 {+0.00/8 1.8} 7.
Bd4 {+0.00/9 1.0} Nc7 {+0.00/8 1.0} 8. c5 {+0.05/9 1.3} cxc5 {-1.55/9 0.6}
9. Rc2 {+1.55/8 1.9} g6 {-1.50/8 0.8} 10. Rxc5 {+1.60/8 2.6} Bxd4
{-1.60/8 1.1} 11. dxd4 {+1.60/7 1.9} B@d6 {-1.80/7 0.7} 12. Rc4
{+1.85/7 1.0} P@c6 {-2.40/7 0.6} 13. Rxc6 {+2.40/7 1.2} Bxb4 {-2.60/7 0.7}
14. Sd2 {+2.60/7 1.4} Bd6 {-3.80/7 0.8} 15. Nc3 {+3.80/6 0.8} Re8
{-3.80/7 1.1} 16. Rxd6 {+3.90/7 0.7} dxd6 {-3.90/7 1.6} 17. P@c6
{+3.80/7 2.0} P@c4 {-4.10/7 1.6} 18. cxc7+ {+4.60/7 2.0} cxc3+
{-4.60/7 1.5} 19. N@d7 {+4.60/6} Kf8 {-4.60/3 1.7} 20. Sxc3 {+5.10/7 2.2}
N@f3 {-5.10/7 0.7} 21. Ke2 {+5.60/7 1.5} Nxg1 {-5.60/7 0.6} 22. Gxg1
{+5.60/6 4} Sd8 {-5.55/6 0.7} 23. +Pxd8 {+5.55/5 0.9} Gxd8 {-3.40/5 1.6}
24. B@b8 {+4.40/5 0.6} Gxd7 {-3.80/5 1.1} 25. Bxa9+ {+3.80/5 4} R@c9
{-3.99/5 1.7} 26. B@b9 {+4.90/5 1.3} Rxc3+ {-4.90/5 1.0} 27. Bxd7=
{+5.30/5 1.3} +Rb2 {-5.30/5 3} 28. Kf1 {+5.30/6 1.0} +Rxa1 {-2.80/5 1.3}
29. +Bxd6 {+2.80/5 3} S@e7 {+2.80/6 1.9} 30. Bxe8+ {-3.80/5 0.7} Gxe8
{-4.60/4 1.2} 31. +Bxe7 {-3.80/4 0.7} Gxe7 {-4.60/5 5} 32. R@c8 {-3.80/5 4}
Ge8 {-4.40/5 4} 33. Rc2+ {-4.50/5 4} +Rxa4 {-4.10/5 0.9} 34. P@c6
{-4.30/4 3} +Rxd4 {-2.70/5 6} 35. G@d3 {-5.20/5 0.9} L@f4 {-3.20/4 2.2} 36.
L@f2 {-4.90/6 0.4} Lxf2+ {-3.20/4 0.9} 37. Gxf2 {-4.90/5 1.0} P@c3
{-3.50/4 1.4} 38. Gxc3 {-5.00/5 0.6} +Rd7 {-3.40/4 0.5} 39. L@d2
{-5.40/4 0.6} P@d6 {-1.86/4 0.5} 40. N@f4 {-6.59/4 1.4} L@h2 {-1.41/5 3}
41. S@g2 {-8.40/6 0.5} +Rxc6 {+0.00/5 0.5} 42. S@d9 {-8.90/5 0.3} Ge7
{+2.00/5 2.5} 43. P@d8 {-11.60/6 1.3} Ke9 {+4.44/6 1.2} 44. Gd3
{-12.84/6 1.2} +Rxc2 {+5.94/6 1.5} 45. Gxc2 {-13.84/6 1.0} Lxh1
{+5.44/5 0.5} 46. Sxh1 {-13.44/5 1.7} Kxd8 {+5.04/5 1.4} 47. R@f9
{-12.74/5 0.4} R@d1 {+4.34/4 1.5} 48. L@e1 {-11.30/6 1.2} B@i7
{+2.90/5 2.4} 49. P@c7 {-12.80/6 0.7} P@c9 {+4.70/6 0.3} 50. Nxe6
{-13.10/5 1.1} Gxe6 {+3.85/7 1.3} 51. Rxf7+ {-12.25/6 3} Ge7 {+3.70/6 1.5}
52. P@d7 {-3.30/6 0.5} Kxd7 {-5.10/6 0.8} 53. Se8= {-3.10/6 1.1}
{Xboard: Forfeit due to invalid move: e7f7 (e7f7) res=35} 1-0

TonyJH
Posts: 183
Joined: Tue Jun 20, 2006 4:41 am
Location: USA

Re: USI2WB

Post by TonyJH »

hgm wrote: I just tried to add the '=' to any move in the promotion zone received from the USI engine that does not have a '+' suffix. (This would lead to a few unnecessary '=' on moves of pieces that cannot promote, or are already promoted, but I think WB is resistant to that, and if not, I can make it such.)
I guess the version of your USI2WB that I used did not have this change yet.

Looking at the TJshogi pgn, it appears that the evals go out of sync after a promotion deferred '=' move was played by the native WB engine.
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: USI2WB

Post by hgm »

OK, try this one. It also corrects for the WB bug, which now end then appends 'q' or 'l' to the moves, in stead of '+'. This might have been the problem in my previous Blunder games.

Code: Select all

/*********************** USI2WB 0.2 by H.G.Muller **************************/
#include <stdio.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>

#ifdef _MSC_VER 
#define SLEEP() Sleep(1) 
#else 
#define SLEEP() usleep(10) 
#endif 

#define WHITE 0
#define BLACK 1
#define NONE  2

char pause, pondering, ponder, post;
int mps, tc, inc, stime, depth, myTime, hisTime, stm, computer = NONE, memory, oldMem=0;
char move[2000][10];
int moveNr;

FILE *toE, *fromE;
HANDLE process;
int pid;
DWORD thread_id;

void
Engine2GUI()
{
    char line[256], command[256];
printf("# engine thread started\n");
    while(1) {
	int i=0, x; char *p;

	fflush(stdout);
	while((line[i] = x = fgetc(fromE)) != EOF && line[i] != '\n') i++;
	line[++i] = 0;
	if(x == EOF) exit(0);
printf("# engine said: %s", line);
	sscanf(line, "%s", command);
	if(!strcmp(command, "bestmove")) {
	    if(pondering) { pondering = 0; continue; } // bestmove was reply to ponder miss; ignore.
	    // move was a move to be played
	    if(strstr(line+9, "resign")) { printf("resign\n"); computer = NONE; }
	    sscanf(line, "bestmove %s", move[moveNr++]);
	    // first start a new ponder search, if pondering is on and we have a move to ponder on
	    if(computer != NONE && ponder && (p = strstr(line+9, "ponder")) ) {
		sscanf(p+7, "%s", move[moveNr]);
		int j;
		// load position
		fprintf(toE, "position startpos");
		printf("# position startpos");
		for(j=0; j<=moveNr; j++) fprintf(toE, " %s", move[j]),printf(" %s", move[j]);
		// and set engine pondering
		pondering = 1;
		fprintf(toE, "\ngo ponder\n");		
		printf("\n# go ponder\n");		
	    }
	    // convert USI move to WB format
	    line[11] = 'a' + '9' - line[11];
	    line[12] = 'a' + '9' - line[12];
	    if(line[10] == '*') { // drop
		line[10] = '@';
	    } else {
		line[9]  = 'a' + '9' - line[9];
		line[10] = 'a' + '9' - line[10];
		if((stm == WHITE ? (line[10]>'6' || line[12]>'6') : (line[10] < '4' || line[12] < '4')) && line[13] != '+')
		     line[13] = '=', line[14] = 0;
	    }
	    printf("move %s\n", line+9); // send move to GUI
	    stm = WHITE+BLACK - stm;
	}
	else if(!strcmp(command, "info")) {
	    int d=0, s=0, t=0, n=0;
	    char *pv;
	    if(!post) continue;
	    if(pv = strstr(line+4, " pv ")) { // convert PV info to WB thinking output
		if(p = strstr(line+4, " depth ")) sscanf(p+7, "%d", &d);
		if(p = strstr(line+4, " score cp ")) sscanf(p+10, "%d", &s);
		if(p = strstr(line+4, " nodes ")) sscanf(p+7, "%d", &n);
		if(p = strstr(line+4, " time ")) sscanf(p+6, "%d", &t);
		printf("%3d  %6d %6d %10d %s", d, s, t, n, pv+4);
	    }
	}
	else if(!strcmp(command, "option")) { //pass option on as WB feature
	    char name[80], buf[80];
//	    printf("feature option=\"%s -%s\"", name, );
	}
	else if(!strcmp(command, "id")) {
	    char name[256];
	    if(sscanf(line, "id name %s", name) == 1) printf("feature myname=\"%s (USI2WB)\"\n", name);
	    continue;
	}
	else if(!strcmp(command, "readyok")) pause = 0;
	else if(!strcmp(command, "usiok"))   printf("feature done=1\n"); // done with options
    }
}

void
GUI2Engine()
{
    char line[256], command[256];

    while(1) {
	int i=0;

	if(computer == stm) {
	    int j;
printf("# start search\n");
	    // load position
	    fprintf(toE, "position startpos moves");
	    printf("# position startpos moves");
	    for(j=0; j<moveNr; j++) fprintf(toE, " %s", move[j]),printf(" %s", move[j]);
	    // and set engine thinking
	    fprintf(toE, "\ngo btime %d wtime %d", stm == WHITE ? 10*myTime : 10*hisTime, stm == BLACK ? 10*myTime : 10*hisTime);
	    printf("\n# go btime %d wtime %d", stm == WHITE ? 10*myTime : 10*hisTime, stm == BLACK ? 10*myTime : 10*hisTime);
	    if(stime > 0) fprintf(toE, " movetime %d", stime),printf(" movetime %d", stime); else
	    if(mps) fprintf(toE, " movestogo %d", mps*(moveNr/(2*mps)+1)-moveNr/2),printf(" movestogo %d", mps*(moveNr/(2*mps)+1)-moveNr/2);
	    if(inc) fprintf(toE, " winc %d binc %d", 10*inc, 10*inc),printf(" winc %d binc %d", 10*inc, 10*inc);
	    if(depth > 0) fprintf(toE, " depth %d", depth),printf(" depth %d", depth);
	    fprintf(toE, "\n");
	    printf("\n");
	}
      nomove:
	fflush(toE);
	while((line[i] = getchar()) != EOF && line[i] != '\n') i++;
	line[++i] = 0;
	sscanf(line, "%s", command);
	while(pause) SLEEP(); // wait for readyok
	if(!strcmp(command, "protover")) {
	    printf("feature colors=0, variants=\"shogi\" setboard=1 ping=0 memory=1 smp=1 usermove=1 debug=1 reuse=0 done=0\n");
	    fprintf(toE, "usi\n"); // this prompts USI engine for options
	}
	else if(!strcmp(command, "new")) {
	    computer = BLACK; stm = WHITE;
	    moveNr = 0; depth = -1;
	    if(memory != oldMem) fprintf(toE, "setoption name USI_Hash value %d\n", memory);
	    oldMem = memory;
	    // we can set other options here
	    pause = 1; // wait for option settings to take effect
	    fprintf(toE, "isready\n");
	    fprintf(toE, "usinewgame\n");
	}
	else if(!strcmp(command, "usermove")) {
	    // convert input move to USI format
	    if(line[10] == '@') { // drop
		line[10] = '*';
	    } else {
		line[9]  = 'a' + '9' - line[9];
		line[10] = 'a' + '9' - line[10];
	    }
	    line[11] = 'a' + '9' - line[11];
	    line[12] = 'a' + '9' - line[12];
	    if(line[13] == '=') line[13] = 0; // no '=' in USI format!
	    else if(line[13] != '\n') line[13] = '+'; // cater to WB 4.4 bug :-(
	    sscanf(line, "usermove %s", command); // strips off linefeed
	    stm = WHITE+BLACK - stm;
	    // when pondering we either continue the ponder search as normal search, or abort it
	    if(pondering) {
		if(!strcmp(command, move[moveNr])) { // ponder hit
		    pondering = 0; moveNr++;
		    fprintf(toE, "ponderhit\n");
		    goto nomove;
		}
		fprintf(toE, "stop\n"); // note: 'pondering' remains set until engine acknowledges 'stop' with 'bestmove'
	    }
	    sscanf(line, "usermove %s", move[moveNr++]); // possibly overwrites ponder move
	}
	else if(!strcmp(command, "level")) {
	    int sec = 0;
	    sscanf(line, "level %d %d:%d %d", &mps, &tc, &sec, &inc) == 4 ||
	    sscanf(line, "level %d %d %d", &mps, &tc, &inc);
	    tc = (60*tc + sec)*100; inc *= 100; stime = 0;
	}
	else if(!strcmp(command, "xboard")) ;
	else if(!strcmp(command, "force"))  computer = NONE;
	else if(!strcmp(command, "go"))     computer = stm;
	else if(!strcmp(command, "time"))   sscanf(line+4, "%d", &myTime);
	else if(!strcmp(command, "otim"))   sscanf(line+4, "%d", &hisTime);
	else if(!strcmp(command, "post"))   post = 1;
	else if(!strcmp(command, "nopost")) post = 0;
	else if(!strcmp(command, "easy"))   ponder = 0;
	else if(!strcmp(command, "hard"))   ponder = 1;
	else if(!strcmp(command, "memory")) sscanf(line, "memory %d", &memory);
	else if(!strcmp(command, "sd"))     sscanf(line, "sd %d", &depth);
	else if(!strcmp(command, "st"))     sscanf(line, "st %d", &stime), stime *= 100, inc = 0;
	else if(!strcmp(command, "quit"))   fprintf(toE, "quit\n"), fflush(toE), exit(0);
    }
}

int
StartEngine(char *cmdLine, char *dir)
{
  HANDLE hChildStdinRd, hChildStdinWr,
    hChildStdoutRd, hChildStdoutWr;
  SECURITY_ATTRIBUTES saAttr;
  BOOL fSuccess;
  PROCESS_INFORMATION piProcInfo;
  STARTUPINFO siStartInfo;
  DWORD err;

  /* Set the bInheritHandle flag so pipe handles are inherited. */
  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  saAttr.bInheritHandle = TRUE;
  saAttr.lpSecurityDescriptor = NULL;

  /* Create a pipe for the child's STDOUT. */
  if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) return GetLastError();

  /* Create a pipe for the child's STDIN. */
  if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) return GetLastError();

  SetCurrentDirectory(dir); // go to engine directory

  /* Now create the child process. */
  siStartInfo.cb = sizeof(STARTUPINFO);
  siStartInfo.lpReserved = NULL;
  siStartInfo.lpDesktop = NULL;
  siStartInfo.lpTitle = NULL;
  siStartInfo.dwFlags = STARTF_USESTDHANDLES;
  siStartInfo.cbReserved2 = 0;
  siStartInfo.lpReserved2 = NULL;
  siStartInfo.hStdInput = hChildStdinRd;
  siStartInfo.hStdOutput = hChildStdoutWr;
  siStartInfo.hStdError = hChildStdoutWr;

  fSuccess = CreateProcess(NULL,
			   cmdLine,	   /* command line */
			   NULL,	   /* process security attributes */
			   NULL,	   /* primary thread security attrs */
			   TRUE,	   /* handles are inherited */
			   DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP,
			   NULL,	   /* use parent's environment */
			   NULL,
			   &siStartInfo, /* STARTUPINFO pointer */
			   &piProcInfo); /* receives PROCESS_INFORMATION */

  if (! fSuccess) return GetLastError();

//  if (0) { // in the future we could trigger this by an argument
//    SetPriorityClass(piProcInfo.hProcess, GetWin32Priority(appData.niceEngines));
//  }

  /* Close the handles we don't need in the parent */
  CloseHandle(piProcInfo.hThread);
  CloseHandle(hChildStdinRd);
  CloseHandle(hChildStdoutWr);

  process = piProcInfo.hProcess;
  pid = piProcInfo.dwProcessId;
  fromE = (FILE*) _fdopen( _open_osfhandle((long)hChildStdoutRd, _O_TEXT|_O_RDONLY), "r");
  toE   = (FILE*) _fdopen( _open_osfhandle((long)hChildStdinWr, _O_WRONLY), "w");

  return NO_ERROR;
}

main(int argc, char **argv)
{
	char *dir = NULL;

	if(argc < 2) { printf("usage is: USI2WB <engine.exe> [<engine directory>]"); exit(-1); }
	if(argc > 2) dir = argv[2];

	// spawn engine proc
	StartEngine(argv[1], dir);

	// create separate thread to handle engine->GUI traffic
	CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Engine2GUI,
		     (LPVOID) NULL, 0, &thread_id);

	// handle GUI->engine traffic in original thread
	GUI2Engine();
}
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: USI2WB

Post by hgm »

This one seems to work really well. I have played several games with Blunder now, and they all continue to the end, when Blunder resigns. (Checkmate detection of WB does not work in Shogi, but fortunately Blunder resigns when he is checkmated, if not earlier. I thought UCI engines could not resign, btw?)

In the mean time I added some option handling as well, (not posted yet), which seems to work, except that I still have to do combo options. I am not sure what to do with the USI_Ponder option. Haven't tested pondering anyway. The WB memory command should work, but Blunder does not have a USI_Hash option??? There also seems to be no standard option for setting the number of search threads in USI? So I cannot do much with the WB cores command...

Anyway, it seems this version of USI2WB is completely satisfactory for what I want to do (playing single-CPU ponder-off games in gauntlets for my engine). So for now I will probably leave it at this, and start working on my engine.
Dann Corbit
Posts: 12790
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: USI2WB

Post by Dann Corbit »

One more tiny thing I forgot to mention:
Declarartion of j (near line 74) needs to be before first exectuted statement of a block unless you have a C99 compiler.

Code: Select all

/*********************** USI2WB 0.2 by H.G.Muller **************************/
#include <stdio.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>

#ifdef _MSC_VER
#define SLEEP() Sleep(1)
#else
#define SLEEP() usleep(10)
#endif

#define WHITE 0
#define BLACK 1
#define NONE  2

char            pause,
                pondering,
                ponder,
                post;
int             mps,
                tc,
                inc,
                stime,
                depth,
                myTime,
                hisTime,
                stm,
                computer = NONE,
                memory,
                oldMem = 0;
char            move[2000][10];
int             moveNr;

FILE           *toE,
               *fromE;
HANDLE          process;
int             pid;
DWORD           thread_id;

void            Engine2GUI()
{
    char            line[256],
                    command[256];
    printf("# engine thread started\n");
    while (1) {
        int             i = 0,
                        x;
        char           *p;

        fflush(stdout);
        while ((line[i] = x = fgetc(fromE)) != EOF && line[i] != '\n')
            i++;
        line[++i] = 0;
        if (x == EOF)
            exit(0);
        printf("# engine said: %s", line);
        sscanf(line, "%s", command);
        if (!strcmp(command, "bestmove")) {
            if (pondering) {
                pondering = 0;  // bestmove was reply to ponder miss; ignore.
                continue;
            }
            // move was a move to be played
            if (strstr(line + 9, "resign")) {
                printf("resign\n");
                computer = NONE;
            }
            sscanf(line, "bestmove %s", move[moveNr++]);
            // first start a new ponder search, if pondering is on and we
            // have a move to ponder on
            if (computer != NONE && ponder && (p = strstr(line + 9, "ponder"))) {
                int             j;
                sscanf(p + 7, "%s", move[moveNr]);
                // load position
                fprintf(toE, "position startpos");
                printf("# position startpos");
                for (j = 0; j <= moveNr; j++)
                    fprintf(toE, " %s", move[j]), printf(" %s", move[j]);
                // and set engine pondering
                pondering = 1;
                fprintf(toE, "\ngo ponder\n");
                printf("\n# go ponder\n");
            }
            // convert USI move to WB format
            line[11] = 'a' + '9' - line[11];
            line[12] = 'a' + '9' - line[12];
            if (line[10] == '*') {      // drop
                line[10] = '@';
            } else {
                line[9] = 'a' + '9' - line[9];
                line[10] = 'a' + '9' - line[10];
                if ((stm == WHITE ? (line[10] > '6' || line[12] > '6') : (line[10] < '4' || line[12] < '4')) && line[13] != '+')
                    line[13] = '=', line[14] = 0;
            }
            printf("move %s\n", line + 9);      // send move to GUI
            stm = WHITE + BLACK - stm;
        } else if (!strcmp(command, "info")) {
            int             d = 0,
                            s = 0,
                            t = 0,
                            n = 0;
            char           *pv;
            if (!post)
                continue;
            if (pv = strstr(line + 4, " pv ")) {        // convert PV info to WB
                                                        // thinking output
                if (p = strstr(line + 4, " depth "))
                    sscanf(p + 7, "%d", &d);
                if (p = strstr(line + 4, " score cp "))
                    sscanf(p + 10, "%d", &s);
                if (p = strstr(line + 4, " nodes "))
                    sscanf(p + 7, "%d", &n);
                if (p = strstr(line + 4, " time "))
                    sscanf(p + 6, "%d", &t);
                printf("%3d  %6d %6d %10d %s", d, s, t, n, pv + 4);
            }
        } else if (!strcmp(command, "option")) {        // pass option on as WB
                                                        // feature
            char            name[80],
                            buf[80];
//       printf("feature option=\"%s -%s\"", name, );
        } else if (!strcmp(command, "id")) {
            char            name[256];
            if (sscanf(line, "id name %s", name) == 1)
                printf("feature myname=\"%s (USI2WB)\"\n", name);
            continue;
        } else if (!strcmp(command, "readyok"))
            pause = 0;
        else if (!strcmp(command, "usiok"))
            printf("feature done=1\n"); // done with options
    }
}

void            GUI2Engine()
{
    char            line[256],
                    command[256];

    while (1) {
        int             i = 0;

        if (computer == stm) {
            int             j;
            printf("# start search\n");
            // load position
            fprintf(toE, "position startpos moves");
            printf("# position startpos moves");
            for (j = 0; j < moveNr; j++)
                fprintf(toE, " %s", move[j]), printf(" %s", move[j]);
            // and set engine thinking
            fprintf(toE, "\ngo btime %d wtime %d", stm == WHITE ? 10 * myTime : 10 * hisTime, stm == BLACK ? 10 * myTime : 10 * hisTime);
            printf("\n# go btime %d wtime %d", stm == WHITE ? 10 * myTime : 10 * hisTime, stm == BLACK ? 10 * myTime : 10 * hisTime);
            if (stime > 0)
                fprintf(toE, " movetime %d", stime), printf(" movetime %d", stime);
            else if (mps)
                fprintf(toE, " movestogo %d", mps * (moveNr / (2 * mps) + 1) - moveNr / 2), printf(" movestogo %d", mps * (moveNr / (2 * mps) + 1) - moveNr / 2);
            if (inc)
                fprintf(toE, " winc %d binc %d", 10 * inc, 10 * inc), printf(" winc %d binc %d", 10 * inc, 10 * inc);
            if (depth > 0)
                fprintf(toE, " depth %d", depth), printf(" depth %d", depth);
            fprintf(toE, "\n");
            printf("\n");
        }
nomove:
        fflush(toE);
        while ((line[i] = getchar()) != EOF && line[i] != '\n')
            i++;
        line[++i] = 0;
        sscanf(line, "%s", command);
        while (pause)
            SLEEP();            // wait for readyok
        if (!strcmp(command, "protover")) {
            printf("feature colors=0, variants=\"shogi\" setboard=1 ping=0 memory=1 smp=1 usermove=1 debug=1 reuse=0 done=0\n");
            fprintf(toE, "usi\n");      // this prompts USI engine for
                                        // options
        } else if (!strcmp(command, "new")) {
            computer = BLACK;
            stm = WHITE;
            moveNr = 0;
            depth = -1;
            if (memory != oldMem)
                fprintf(toE, "setoption name USI_Hash value %d\n", memory);
            oldMem = memory;
            // we can set other options here
            pause = 1;          // wait for option settings to take effect
            fprintf(toE, "isready\n");
            fprintf(toE, "usinewgame\n");
        } else if (!strcmp(command, "usermove")) {
            // convert input move to USI format
            if (line[10] == '@') {      // drop
                line[10] = '*';
            } else {
                line[9] = 'a' + '9' - line[9];
                line[10] = 'a' + '9' - line[10];
            }
            line[11] = 'a' + '9' - line[11];
            line[12] = 'a' + '9' - line[12];
            if (line[13] == '=')
                line[13] = 0;   // no '=' in USI format!
            else if (line[13] != '\n')
                line[13] = '+'; // cater to WB 4.4 bug :-(
            sscanf(line, "usermove %s", command);       // strips off linefeed
            stm = WHITE + BLACK - stm;
            // when pondering we either continue the ponder search as normal
            // search, or abort it
            if (pondering) {
                if (!strcmp(command, move[moveNr])) {   // ponder hit
                    pondering = 0;
                    moveNr++;
                    fprintf(toE, "ponderhit\n");
                    goto nomove;
                }
                fprintf(toE, "stop\n"); // note: 'pondering' remains set
                                        // until engine acknowledges 'stop'
                                        // with 'bestmove'
            }
            sscanf(line, "usermove %s", move[moveNr++]);        // possibly overwrites
                                                                // ponder move
        } else if (!strcmp(command, "level")) {
            int             sec = 0;
            sscanf(line, "level %d %d:%d %d", &mps, &tc, &sec, &inc) == 4 ||
                sscanf(line, "level %d %d %d", &mps, &tc, &inc);
            tc = (60 * tc + sec) * 100;
            inc *= 100;
            stime = 0;
        } else if (!strcmp(command, "xboard"));
        else if (!strcmp(command, "force"))
            computer = NONE;
        else if (!strcmp(command, "go"))
            computer = stm;
        else if (!strcmp(command, "time"))
            sscanf(line + 4, "%d", &myTime);
        else if (!strcmp(command, "otim"))
            sscanf(line + 4, "%d", &hisTime);
        else if (!strcmp(command, "post"))
            post = 1;
        else if (!strcmp(command, "nopost"))
            post = 0;
        else if (!strcmp(command, "easy"))
            ponder = 0;
        else if (!strcmp(command, "hard"))
            ponder = 1;
        else if (!strcmp(command, "memory"))
            sscanf(line, "memory %d", &memory);
        else if (!strcmp(command, "sd"))
            sscanf(line, "sd %d", &depth);
        else if (!strcmp(command, "st"))
            sscanf(line, "st %d", &stime), stime *= 100, inc = 0;
        else if (!strcmp(command, "quit"))
            fprintf(toE, "quit\n"), fflush(toE), exit(0);
    }
}

int             StartEngine(char *cmdLine, char *dir)
{
    HANDLE          hChildStdinRd,
                    hChildStdinWr,
                    hChildStdoutRd,
                    hChildStdoutWr;
    SECURITY_ATTRIBUTES saAttr;
    BOOL            fSuccess;
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO     siStartInfo;
    DWORD           err;

    /* Set the bInheritHandle flag so pipe handles are inherited. */
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    /* Create a pipe for the child's STDOUT. */
    if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
        return GetLastError();

    /* Create a pipe for the child's STDIN. */
    if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
        return GetLastError();

    SetCurrentDirectory(dir);   // go to engine directory

    /* Now create the child process. */
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.lpReserved = NULL;
    siStartInfo.lpDesktop = NULL;
    siStartInfo.lpTitle = NULL;
    siStartInfo.dwFlags = STARTF_USESTDHANDLES;
    siStartInfo.cbReserved2 = 0;
    siStartInfo.lpReserved2 = NULL;
    siStartInfo.hStdInput = hChildStdinRd;
    siStartInfo.hStdOutput = hChildStdoutWr;
    siStartInfo.hStdError = hChildStdoutWr;

    fSuccess = CreateProcess(NULL,
                             cmdLine,   /* command line */
                             NULL,      /* process security attributes */
                             NULL,      /* primary thread security attrs */
                             TRUE,      /* handles are inherited */
                             DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
                             NULL,      /* use parent's environment */
                             NULL,
                             &siStartInfo,      /* STARTUPINFO pointer */
                             &piProcInfo);      /* receives
                                                 * PROCESS_INFORMATION */

    if (!fSuccess)
        return GetLastError();

//  if (0) { // in the future we could trigger this by an argument
//    SetPriorityClass(piProcInfo.hProcess, GetWin32Priority(appData.niceEngines));
//  }

    /* Close the handles we don't need in the parent */
    CloseHandle(piProcInfo.hThread);
    CloseHandle(hChildStdinRd);
    CloseHandle(hChildStdoutWr);

    process = piProcInfo.hProcess;
    pid = piProcInfo.dwProcessId;
    fromE = (FILE *) _fdopen(_open_osfhandle((long) hChildStdoutRd, _O_TEXT | _O_RDONLY), "r");
    toE = (FILE *) _fdopen(_open_osfhandle((long) hChildStdinWr, _O_WRONLY), "w");

    return NO_ERROR;
}

int             main(int argc, char **argv)
{
    char           *dir = NULL;

    if (argc < 2) {
        printf("usage is: USI2WB <engine.exe> [<engine directory>]");
        exit(-1);
    }
    if (argc > 2)
        dir = argv[2];

    // spawn engine proc
    StartEngine(argv[1], dir);

    // create separate thread to handle engine->GUI traffic
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Engine2GUI,
                 (LPVOID) NULL, 0, &thread_id);

    // handle GUI->engine traffic in original thread
    GUI2Engine();
    return 0;
}
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: USI2WB

Post by hgm »

OK, done.

I put a package (Windows binary + source code) for download at:

http://home.hccnet.nl/h.g.muller/USI2WB.zip

It should recognize all types of USI options now, including combos. It is only partially tested, but as USI is stateless, when you have seen one state, you have seen them all. So it should always work. :wink:
TonyJH
Posts: 183
Joined: Tue Jun 20, 2006 4:41 am
Location: USA

Re: USI2WB

Post by TonyJH »

I tested the new version of USI2WB with TJshogi for just one game, and it worked well. I didn't enable pondering.
Good luck with your shogi engine. :)
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: USI2WB

Post by hgm »

Good! I did try to program pondering, by feeding the ponder move given in the USI "bestmove XXXX ponder YYYY" back into the engine, and issuing a "go ponder" (when WB pondering is on). And when the WB usermove comes in, I compare it with the move the engine is pondering on, and send "ponderhit" on a hit, and send "stop" and ignore the next "bestmove" on a miss.

There might be race conditions, though. USI2WB used separate threads for GUI->engine and engine->GUI traffic.

But Blunder never seems to give a ponder move, despite the fact that its ponder-mode combo option is set for 'USI'.