GNU Shogi (WinBoard version)

Discussion of chess software programming and technical issues.

Moderator: Ras

Dann Corbit
Posts: 12790
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: GNU Shogi (WinBoard version)

Post by Dann Corbit »

hgm wrote:No, there is no adapter for that yet.

Perhaps there will be, when I feel the need to test my own Shogi engine (Shokidoki) against stonger opponents. But I doubt if I will get to that stage before the Olmpiad, in September. I am not so sure there are that many USI engines anyway, although I heard rumors that Spear has converted now. And Bonanza already supported it through an adapter. But these engines are in the stratosphere, and I still will have a very long way to go before they could become useful as opponents.
I have several USI engines that are open source.
Laramie, Blunder, Ponanza and Booze spring to mind.
GPS Shogi is very strong.
I guess that SSP and TJSHOGI would be good opponents for your engine.
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: GNU Shogi (WinBoard version)

Post by hgm »

Well, I guess that USI is simply a UCI dialect. So it should not be too difficult to convert Polyglot to a USI2WB adapter; this would only affect the move generator and board stuff. So it would not be too much of a problem that I don't know anything about UCI, I can leave that all as it is.
Dann Corbit
Posts: 12790
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: GNU Shogi (WinBoard version)

Post by Dann Corbit »

hgm wrote:Well, I guess that USI is simply a UCI dialect. So it should not be too difficult to convert Polyglot to a USI2WB adapter; this would only affect the move generator and board stuff. So it would not be too much of a problem that I don't know anything about UCI, I can leave that all as it is.
Tord is the originator:
http://www.glaurungchess.com/shogi/usi.html
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: GNU Shogi (WinBoard version)

Post by hgm »

Indeed, I know, the only black mark we have on him. That was a really bad move, to make a new protocol, where already sufficiently many existing protocols could have done the job... :cry:

Now if he also makes a Shogi engine, perhaps he can be forgiven. :wink:
User avatar
hgm
Posts: 28378
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

USI2WB

Post by hgm »

Dann Corbit wrote:I have several USI engines that are open source.
Laramie, Blunder, Ponanza and Booze spring to mind.
GPS Shogi is very strong.
I guess that SSP and TJSHOGI would be good opponents for your engine.
OK, on request, then:

Below is the source of my attempt at USI2WB. I am sure it will still need a lot of polishing. But I was able to have Blunder play a game against itself under WinBoard! At least, it was moving, and most of the moves seemed to make sense. (Well, I am no Shogi player, but if an obvious attack on a valuable piece was made, the other side moved it away...) So the basics seem to work.

At some point it starts doing illegal moves, though, and I have even seen it capture the King! :cry: Not sure if theis is a Blunder Problem or a USI problem. There might be something in Shogi move notation I don't understand.

I have tried to run SSP using it, but ssp.exe croaks as soon as I give it a 'go' command, even when I run it from the command line. This seems a clear SSP problem. I did try to program pondering, but I have not tested it at all.

The code below can be compiled on Cygwin with the line:

gcc -mno-cygwin -O2 -s USI2WB.c -o USI2WB.exe

It must then be run with the command line

USI2WB ENGINE.exe ENGINE_FOLDER

(i.e. in the winboard.ini you would have to include the line

"USI2WB ENGINE.exe ENGINE_FOLDER" /fd=USI2WB_FOLDER /variant=shogi

Happy dropping! :lol:

Code: Select all

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

#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
	    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(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
	    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];
	    printf("move %s\n", line+9); // send move to GUI
	    stm = WHITE+BLACK - stm;
	    continue;
	}
	if(!strcmp(command, "readyok")) {
	    pause = 0;
	    continue;
	}
	if(!strcmp(command, "info")) {
	    int d=0, s=0, t=0, n=0;
	    char *pv;
	    if(!post) continue;
	    if(pv = strstr(line+4, " pv ")) {
		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);
	    }
	    continue;
	}
	if(!strcmp(command, "option")) { //pass option on as WB feature
	    char name[80], buf[80];
//	    printf("feature option="%s -%s"", name, );
	    continue;
	}
	if(!strcmp(command, "usiok")) {
	    printf("feature done=1\n"); // done with options
	    continue;
	}
	if(!strcmp(command, "id")) {
	    char name[256];
	    if(sscanf(line, "id name %s", name) == 1) printf("feature myname="%s"\n", name);
	    continue;
	}
    }
}

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) usleep(10); // wait for readyok
	if(!strcmp(command, "xboard")) {
	    continue;
	}
	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
	    continue;
	}
	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");
	    continue;
	}
	if(!strcmp(command, "force")) {
	    computer = NONE;
	    continue;
	}
	if(!strcmp(command, "go")) {
	    computer = stm;
	    continue;
	}
	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];
	    sscanf(line, "usermove %s", command);
	    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
	    continue;
	}
	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;
	    continue;
	}
	if(!strcmp(command, "time")) {
	    sscanf(line+4, "%d", &myTime);
	    continue;
	}
	if(!strcmp(command, "otim")) {
	    sscanf(line+4, "%d", &hisTime);
	    continue;
	}
	if(!strcmp(command, "post")) {
	    post = 1;
	    continue;
	}
	if(!strcmp(command, "nopost")) {
	    post = 0;
	    continue;
	}
	if(!strcmp(command, "easy")) {
	    ponder = 0;
	    continue;
	}
	if(!strcmp(command, "hard")) {
	    ponder = 1;
	    continue;
	}
	if(!strcmp(command, "memory")) {
	    sscanf(line, "memory %d", &memory);
	    continue;
	}
	if(!strcmp(command, "sd")) {
	    sscanf(line, "sd %d", &depth);
	    continue;
	}
	if(!strcmp(command, "st")) {
	    sscanf(line, "st %d", &stime);
	    stime *= 100; inc = 0;
	    continue;
	}
	if(!strcmp(command, "quit")) {
	    fprintf(toE, "quit\n");
	    fflush(toE);
	    exit(0);
	    continue;
	}
    }
}

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();
  }

  /* Arrange to (1) look in dir for the child .exe file, and
   * (2) have dir be the child's working directory.  Interpret
   * dir relative to the directory WinBoard loaded from. */
  SetCurrentDirectory(dir);

  /* 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();
}
Dann Corbit
Posts: 12790
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: USI2WB

Post by Dann Corbit »

I'll fool around with it and also ask for suggestions in the shogi groups.
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 »

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.

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.)

But with Blunder self-play I still run into problems:

Code: Select all

[Event "Computer Chess Game"]
[Site "CHESS_LAPTOP"]
[Date "2010.07.15"]
[Round "-"]
[White "Blunder"]
[Black "Blunder"]
[Result "0-1"]
[TimeControl "40/60"]
[Variant "shogi"]
[Annotator "22. -3.43   21... +4.04"]

1. c4= g6 2. d4= b6 3. Rc2 b5 4. Bc3 Sd8 5. Sd2 Kf8 6. Kf2 Kg8 7. Kg2 Bg7
8. Kh2 Kh8 9. Sg2 Li8 10. Sd3 Ki9 11. c5= Sh8 12. Bd2 e6 13. e4= Se7 14.
c6= cxc6 15. Rxc6 Sd6 16. Gde2 P@c7 17. Rc1 Gg9 18. i4= Ge9 19. i5= Gef9
20. f4= Gfg8 21. Gf3 Bf8 {+4.04/8 1.5} 22. g4= {-3.43/8 1.5} Sc5
{+2.74/9 1.6} 23. P@c4 {-3.58/9 1.3} Sd6 {+2.77/9 1.4} 24. Ng3
{-2.40/9 1.3} c6 {+2.40/8 2.3} 25. f5= {-2.28/9 1.4} Bh6 {+4.05/8 1.3} 26.
Bxh6 {-2.27/9 1.3} hxh6 {+2.09/9 1.4} 27. d5= {-1.69/8 1.4} Sc7
{+1.67/8 1.3} 28. B@c3 {-0.83/8 1.3} B@g7 {+0.83/8 1.4} 29. Bxg7+ Gxg7 30.
B@c3 {+0.98/8 1.3} d6 {-0.90/8 1.3} 31. e5= {+1.88/8 1.3} Re8 32. exe6=
Rxe6 {-2.12/8 1.4} 33. Se4 b4 34. bxb4= P@b2 35. Bxb2 {+4.99/8 1.4} dxd5
36. Sxd5 Re8 {-4.46/8 1.3} 37. P@d6 {+5.93/8 1.4} Rb8 {-6.45/7 1.4} 38. d7+
{+6.14/7 1.3} Rxb4 39. Bc3 Rb5 40. Se4 Rb3+ 41. +Pxc7 {+9.50/8 2.6} P@b2
{-4.56/8 1.9} 42. Bxg7+ {+9.35/8 1.3} Nxg7 {-5.73/7 4} 43. Rd1 {+11.65/7 3}
P@e5 {-7.55/7 4} 44. Rd9+ {+12.16/7 1.3} exe4 {-0.61/6 4} 45. S@h7
{+13.32/5 5} e3+ 46. Sg8+ S@h9 {-2.46/6 2.9} 47. +Sxg7 {+18.31/7 1.7} +Pxf3
{-2.41/5 1.8} 48. +Rxg9 {+25.90/4 5} +Pxg3 {+1.56/3 33}
{Xboard: Forfeit due to invalid move: g7h8= (g7h8) res=35} 0-1
At the end white is in check by the promoted Pawn, which has indeed revealed itself as such by already making a few lateral moves. So no mistake there about the promotion. But white ignores the check, and tries to capture something elsewhere...

Hard to believe for me that this can be anything but a Blunder bug... It looks a bit like it is trying to trade Kings! :shock:
Michel
Posts: 2292
Joined: Mon Sep 29, 2008 1:50 am

Re: USI2WB

Post by Michel »

About 170 lines! Not bad.

I compiles fine in Linux but I cannot test it as I don't know anything about shogi.
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 »

Well, it still only does only a little more than the bare minimum needed to play a game. In particular, there is no handling of the engine options, so you can only play engines with their standard settings. And no setting up of positions yet. But it is a start.

And it can be easily converted to a UCI adapter for Xiangqi UCI engines! (They seem to exist.) 8-)
Dann Corbit
Posts: 12790
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: USI2WB

Post by Dann Corbit »

I added a macro so that the program will compile on non GCC compilers.

Code: Select all

/****** USI2WB 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
            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 (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
            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];
            printf("move %s\n", line + 9);      // send move to GUI
            stm = WHITE + BLACK - stm;
            continue;
        }
        if (!strcmp(command, "readyok")) {
            pause = 0;
            continue;
        }
        if (!strcmp(command, "info")) {
            int             d = 0,
                            s = 0,
                            t = 0,
                            n = 0;
            char           *pv;
            if (!post)
                continue;
            if (pv = strstr(line + 4, " pv ")) {
                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);
            }
            continue;
        }
        if (!strcmp(command, "option")) {       // pass option on as WB
            // feature
            char            name[80],
                            buf[80];
//       printf("feature option=\"%s -%s\"", name, );
            continue;
        }
        if (!strcmp(command, "usiok")) {
            printf("feature done=1\n"); // done with options
            continue;
        }
        if (!strcmp(command, "id")) {
            char            name[256];
            if (sscanf(line, "id name %s", name) == 1)
                printf("feature myname=\"%s\"\n", name);
            continue;
        }
    }
}

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, "xboard")) {
            continue;
        }
        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
            continue;
        }
        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");
            continue;
        }
        if (!strcmp(command, "force")) {
            computer = NONE;
            continue;
        }
        if (!strcmp(command, "go")) {
            computer = stm;
            continue;
        }
        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];
            sscanf(line, "usermove %s", command);
            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
            continue;
        }
        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;
            continue;
        }
        if (!strcmp(command, "time")) {
            sscanf(line + 4, "%d", &myTime);
            continue;
        }
        if (!strcmp(command, "otim")) {
            sscanf(line + 4, "%d", &hisTime);
            continue;
        }
        if (!strcmp(command, "post")) {
            post = 1;
            continue;
        }
        if (!strcmp(command, "nopost")) {
            post = 0;
            continue;
        }
        if (!strcmp(command, "easy")) {
            ponder = 0;
            continue;
        }
        if (!strcmp(command, "hard")) {
            ponder = 1;
            continue;
        }
        if (!strcmp(command, "memory")) {
            sscanf(line, "memory %d", &memory);
            continue;
        }
        if (!strcmp(command, "sd")) {
            sscanf(line, "sd %d", &depth);
            continue;
        }
        if (!strcmp(command, "st")) {
            sscanf(line, "st %d", &stime);
            stime *= 100;
            inc = 0;
            continue;
        }
        if (!strcmp(command, "quit")) {
            fprintf(toE, "quit\n");
            fflush(toE);
            exit(0);
            continue;
        }
    }
}

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();
    }
    /* Arrange to (1) look in dir for the child .exe file, and (2) have dir
     * be the child's working directory.  Interpret dir relative to the
     * directory WinBoard loaded from. */
    SetCurrentDirectory(dir);

    /* 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();
}