From c1946454644d9300609bb62499fecd8a7b0131e2 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Tue, 8 Nov 2016 22:27:45 +0100 Subject: [PATCH 01/16] Define strcasestr for Windows The MinGW compiler apparently does not have this function. --- UCI2WB.c | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 2370615..9e34c7c 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -51,6 +51,8 @@ FILE *toE, *fromE, *fromF; int pid; #ifdef WIN32 +char *strcasestr (char *p, char *q) { char *r = p; while(*r) *r = tolower(*r), r++; return strstr(p, q); } + WinPipe(HANDLE *hRd, HANDLE *hWr) { SECURITY_ATTRIBUTES saAttr; -- 1.7.0.4 From bdbb7ca818c7bad56686832eba6706458aae88bd Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Tue, 8 Nov 2016 22:29:32 +0100 Subject: [PATCH 02/16] Bump version to 2.3 --- UCI2WB.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 9e34c7c..079960d 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -9,7 +9,7 @@ /* of which you should have received a copy together with this file. */ /****************************************************************************/ -#define VERSION "2.2" +#define VERSION "2.3" #include #include -- 1.7.0.4 From 95996bd376f95541530f66cf27ad54949cb46d3c Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Thu, 10 Nov 2016 09:55:40 +0100 Subject: [PATCH 03/16] Suppress 'setup' command for some standard variants Receiving an 'info string variant' from the engine now not automatically leads to emission of a 'setup' command to the GUI. Because currently such a setup command contains defaults for info not supplied in the 'info' command, such as holdings size and pieceToChar, we suppress this in some standard variants where this would be harmful (such as crazyhouse). Such that the GUI does not get wrongly configured when playing these variants with legality testing off. Before, the setup command was only suppressed in variant normal. --- UCI2WB.c | 5 +++-- 1 files changed, 3 insertions(+), 2 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 079960d..dddb073 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -30,7 +30,8 @@ #include // Set VARIANTS for in WinBoard variant feature. (With -s option this will always be reset to use "shogi".) -# define VARIANTS "normal,xiangqi" +#define VARIANTS "normal,xiangqi" +#define STDVARS "chess,chess960,crazyhouse,threecheck,giveaway,atomic,seirawan,shogi,xiangqi" #define DPRINT if(debug) printf @@ -281,7 +282,7 @@ Engine2GUI() char *pv, varName[80]; if(sscanf(line+5, "string times @ %c", &dummy) == 1) { printf("# %s", line+12); continue; } if(sscanf(line+5, "string variant %s", varName) == 1) { - if(strcmp(varName,"chess") && (p = strstr(line+18, " startpos "))) printf("setup (-) 8x8+0_fairy %s", p+10); + if(!strstr(STDVARS, varName) && (p = strstr(line+18, " startpos "))) printf("setup (-) 8x8+0_fairy %s", p+10); continue; } if(collect && (pv = strstr(line+5, "currmove "))) { -- 1.7.0.4 From 05c7d739a244a923c0f3e90bcb10f3db9f5e670c Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Thu, 10 Nov 2016 10:12:11 +0100 Subject: [PATCH 04/16] Fix commands during analysis (MultiPV option!) UCI2WB could not handle spurious commands while analyzing: these would start a new search without first terminating the old one. Only the commands allowed in analysis mode (exit, usermove, and in/exclude) were handled correctly. Unfortunately XBoard sometimes sends spurious 'accepted' commands during analysis, in violation of the CECP specs. All such non-compliant commands are now ignored. And the 'option' command will also abort an ongoing analysis search, to allow altering the MultiPV setting during analysis. (Something XBoard also does.) --- README.txt | 1 + UCI2WB.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.txt b/README.txt index 41e2d80..09d94ae 100644 --- a/README.txt +++ b/README.txt @@ -54,6 +54,7 @@ Change log: Implement handling of 'UCI_Variant' option for variant announcement and selection Pass 'info string variant' line as 'setup' command to allow engine-defined variants Set 'UCI_Opponent' option in accordance with CECP 'name' and 'computer' commands +Fix option setting during analysis (MultiPV!) 22/11/2016 2.2 Use USI gameover command to relay game result diff --git a/UCI2WB.c b/UCI2WB.c index dddb073..dffac3b 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -395,12 +395,12 @@ Move4Engine(char *m) void GUI2Engine() { - char line[256], command[256], *p, *q, *r, mySide; + char line[256], command[256], *p, *q, *r, mySide, searching = 0; while(1) { int i, x; - if((computer == stm || computer == ANALYZE) && !suspended) { + if((computer == stm || computer == ANALYZE && !searching) && !suspended) { DPRINT("# start search\n"); LoadPos(moveNr); fflush(stdout); // load position // and set engine thinking (note USI swaps colors!) @@ -411,8 +411,7 @@ GUI2Engine() fprintf(toE, " searchmoves"); DPRINT(" searchmoves"); for(i=1; i 0) fprintf(toE, "setoption name UCI_Chess960 value true\n"); } } else if(!strcmp(command, "undo") && (i=1) || !strcmp(command, "remove") && (i=2)) { - if(pondering || computer == ANALYZE) StopPonder(1); + if(pondering || computer == ANALYZE) StopPonder(1), searching = 0; moveNr = moveNr > i ? moveNr - i : 0; collect = (computer == ANALYZE); sm = 0; } else if(!strcmp(command, ".")) { @@ -510,7 +510,7 @@ GUI2Engine() inex = 1; line[strlen(line)-1] = sm = 0; // strip LF and clear sm flag for(i=1; i Date: Thu, 10 Nov 2016 10:55:41 +0100 Subject: [PATCH 05/16] Print newline after 'position moves' instead of before 'go' The '\n' between position-moves and go was printed with the latter to avoid a separate printf for it after the move list, but this obfuscated the code, and was a show stopper for more elegant printing of debug info. --- UCI2WB.c | 7 ++++--- 1 files changed, 4 insertions(+), 3 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index dffac3b..cf2f8e4 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -147,8 +147,8 @@ StartSearch(char *ponder) int t = (flob ? inc + myTime/40 : 1000*byo*(byo>0)); // byoyomi time if(sc == 'x') black = 1; else drawOffer = 0;// in UCCI 'black' refers to us and 'white' to opponent if(!x && drawOffer) ponder = " draw", drawOffer = 0; //pass draw offer only when not pondering - fprintf(toE, "\ngo%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t); - DPRINT( "\n# go%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t); + fprintf(toE, "go%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t); + DPRINT( "# go%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t); if(sTime > 0) { fprintf(toE, " movetime %d", sTime); DPRINT(" movetime %d", sTime); } else if(mps) { fprintf(toE, " movestogo %d", mps*(nr/(2*mps)+1)-nr/2); DPRINT(" movestogo %d", mps*(nr/(2*mps)+1)-nr/2); } if(flob || byo >= 0) sprintf(suffix, " byoyomi %d", t); // for engines running purely on byoyomi @@ -180,6 +180,7 @@ LoadPos(int moveNr) fprintf(toE, "%s moves", pos); DPRINT( "# %s moves", pos); for(j=lastCapt; j Date: Thu, 10 Nov 2016 11:13:19 +0100 Subject: [PATCH 06/16] Simplify debug printing The duplicats of each print statement to the engine to (optionally) print debug information are now generated automatically by a single macro EPRINT. --- UCI2WB.c | 39 +++++++++++++++++++-------------------- 1 files changed, 19 insertions(+), 20 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index cf2f8e4..ebbc301 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -34,6 +34,7 @@ #define STDVARS "chess,chess960,crazyhouse,threecheck,giveaway,atomic,seirawan,shogi,xiangqi" #define DPRINT if(debug) printf +#define EPRINT(X) { char f[999]; sprintf X; DPRINT("%s", f); fprintf(toE, "%s", f + 2*(*f == '#')); /* strip optional # prefix */ } #define WHITE 0 #define BLACK 1 @@ -147,15 +148,14 @@ StartSearch(char *ponder) int t = (flob ? inc + myTime/40 : 1000*byo*(byo>0)); // byoyomi time if(sc == 'x') black = 1; else drawOffer = 0;// in UCCI 'black' refers to us and 'white' to opponent if(!x && drawOffer) ponder = " draw", drawOffer = 0; //pass draw offer only when not pondering - fprintf(toE, "go%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t); - DPRINT( "# go%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t); - if(sTime > 0) { fprintf(toE, " movetime %d", sTime); DPRINT(" movetime %d", sTime); } else - if(mps) { fprintf(toE, " movestogo %d", mps*(nr/(2*mps)+1)-nr/2); DPRINT(" movestogo %d", mps*(nr/(2*mps)+1)-nr/2); } + EPRINT((f, "# go%s %stime %d %stime %d", ponder, bTime, (black ? myTime : hisTime) - t, wTime, (!black ? myTime : hisTime) - t)) + if(sTime > 0) EPRINT((f, " movetime %d", sTime)) else + if(mps) EPRINT((f, " movestogo %d", mps*(nr/(2*mps)+1)-nr/2)) if(flob || byo >= 0) sprintf(suffix, " byoyomi %d", t); // for engines running purely on byoyomi - if(inc && !*suffix) { fprintf(toE, " %s %d %s %d", wInc, inc, bInc, inc); DPRINT(" %s %d %s %d", wInc, inc, bInc, inc); } - if(depth > 0) { fprintf(toE, " depth %d", depth); DPRINT(" depth %d", depth); } - if(*suffix) { fprintf(toE, suffix, inc); DPRINT(suffix, inc); } - fprintf(toE, "\n"); DPRINT("\n"); + if(inc && !*suffix) EPRINT((f, " %s %d %s %d", wInc, inc, bInc, inc)) + if(depth > 0) EPRINT((f, " depth %d", depth)) + if(*suffix) EPRINT((f, suffix, inc)) + EPRINT((f, "\n")) } void @@ -163,7 +163,7 @@ StopPonder(int pondering) { if(!pondering) return; pause = 1; - fprintf(toE, "stop\n"); fflush(toE); DPRINT("# stop\n"); // note: 'pondering' remains set until engine acknowledges 'stop' with 'bestmove' + EPRINT((f, "# stop\n")) fflush(toE); // note: 'pondering' remains set until engine acknowledges 'stop' with 'bestmove' Sync(PAUSE); // wait for engine to acknowledge 'stop' with 'bestmove'. } @@ -177,10 +177,9 @@ LoadPos(int moveNr) stm = (!strstr(iniPos+4, " b ") ^ lastCapt & 1 ? 'w' : 'b'); sprintf(buf, "position fen %s", ToFEN(stm)); pos = buf; // send it as FEN (with "position" in UCCI!) } - fprintf(toE, "%s moves", pos); - DPRINT( "# %s moves", pos); - for(j=lastCapt; j Date: Thu, 10 Nov 2016 11:38:08 +0100 Subject: [PATCH 07/16] Print everything sent to engine in debug mode Now the macro EPRINT makes this easy we make sure that everything sent to the engine is also reported in a comment to the GUI, when the debug option is on. --- UCI2WB.c | 34 +++++++++++++++++----------------- 1 files changed, 17 insertions(+), 17 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index ebbc301..c13ea34 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -322,7 +322,7 @@ Engine2GUI() if(!strcasecmp(name, "UCI_Variant")) { if(p = strstr(line+6, " var ")) strcpy(varList, p); varOpt = 1; continue; } if(!strcasecmp(name, "UCI_Opponent")) { namOpt = 1; continue; } if(frc< 0 && (strstr(name, "960") || strcasestr(name, "frc")) && !strcmp(type, "check")) { - fprintf(toE, "setoption name %s value true\n", name); strcpy(val, "true"); // set non-standard suspected FRC options + EPRINT((f, "# setoption name %s value true\n", name)) strcpy(val, "true"); // set non-standard suspected FRC options } if(!strcasecmp(name, "Threads")) { strcpy(threadOpt, name); continue; } if(!strcasecmp(name, "Ponder") || !strcasecmp(name, "USI_Ponder")) { strcpy(canPonder, name); continue; } @@ -368,7 +368,7 @@ Engine2GUI() if(frc) sprintf(p, ",normal,fischerandom"), printf("feature oocastle=%d\n", frc<0); // unannounced FRC uses O-O castling if(*varList) printf("feature variants=\"%s\"\n", varList+1); // from UCI_Variant combo and/or UCI_Chess960 check options printf("feature smp=1 memory=%d done=1\n", hasHash); - if(unit == 2) unit = 1, fprintf(toE, "setoption usemillisec true\n"); + if(unit == 2) { unit = 1; EPRINT((f, "# setoption usemillisec true\n")) } Sync(WAKEUP); // done with options } } @@ -417,20 +417,20 @@ GUI2Engine() nomove: fflush(toE); fflush(stdout); i = 0; while((x = getchar()) != EOF && (line[i] = x) != '\n') i++; - line[++i] = 0; if(x == EOF) { printf("# EOF\n"); fprintf(toE, "quit\n"); exit(-1); } + line[++i] = 0; if(x == EOF) { printf("# EOF\n"); EPRINT((f, "# quit\n")) exit(-1); } sscanf(line, "%s", command); if(!strcmp(command, "new")) { computer = BLACK; moveNr = 0; depth = -1; move[0][0] = 0; stm = WHITE; strcpy(iniPos, "position startpos"); frc &= ~1; - if(memory != oldMem && hasHash) fprintf(toE, "setoption %s%s %s%d\n", nameWord, hashOpt, valueWord, memory); + if(memory != oldMem && hasHash) EPRINT((f, "# setoption %s%s %s%d\n", nameWord, hashOpt, valueWord, memory)) oldMem = memory; // we can set other options here - if(sc == 'x') { if(newGame) fprintf(toE, "setoption newgame\n"); } else // optional in UCCI - if(varOpt) fprintf(toE, "setoption name UCI_Variant value chess\n"); + if(sc == 'x') { if(newGame) EPRINT((f, "# setoption newgame\n")) } else // optional in UCCI + if(varOpt) EPRINT((f, "# setoption name UCI_Variant value chess\n")) pause = 1; // wait for option settings to take effect - fprintf(toE, "isready\n"); fflush(toE); + EPRINT((f, "# isready\n")) fflush(toE); Sync(PAUSE); // wait for readyok - fprintf(toE, "u%cinewgame\n", sc); fflush(toE); + EPRINT((f, "# u%cinewgame\n", sc)) fflush(toE); } else if(!strcmp(command, "usermove")) { sscanf(line, "usermove %s", command); // strips off linefeed @@ -472,7 +472,7 @@ GUI2Engine() printf("feature variants=\"%s\" setboard=1 usermove=1 debug=1 ping=1 name=1 reuse=0 exclude=1 pause=1 sigint=0 sigterm=0 done=0\n", variants); printf("feature option=\"UCI2WB debug output -check %d\"\n", debug); if(sc == 's') printf("feature option=\"Floating Byoyomi -check %d\"\nfeature option=\"Byoyomi -spin %d -1 1000\"\n", flob, byo); - fprintf(toE, sc == 'x' ? "ucci\n" : "u%ci\n", sc); fflush(toE); // prompt UCI engine for options + EPRINT((f, sc == 'x' ? "# ucci\n" : "u%ci\n", sc)) fflush(toE); // prompt UCI engine for options Sync(PAUSE); // wait for uciok } else if(!strcmp(command, "setboard")) { @@ -489,13 +489,13 @@ GUI2Engine() } else if(!strcmp(command, "variant")) { if(varOpt) { - fprintf(toE, "setoption name UCI_Variant value %sucinewgame\nisready\n", strcmp(line+8, "3check\n") ? line+8 : "threecheck\n"); + EPRINT((f, "# setoption name UCI_Variant value %sucinewgame\nisready\n", strcmp(line+8, "3check\n") ? line+8 : "threecheck\n")) fflush(toE); Sync(PAUSE); } if(!strcmp(line+8, "shogi\n")) size = 9, strcpy(iniPos, "position startpos"); if(!strcmp(line+8, "5x5+5_shogi\n")) size = 5, strcpy(iniPos, "position startpos"); if(!strcmp(line+8, "xiangqi\n")) strcpy(iniPos, "fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR r"); - if(!strcmp(line+8, "fischerandom\n")) { frc |= 1; if(frc > 0) fprintf(toE, "setoption name UCI_Chess960 value true\n"); } + if(!strcmp(line+8, "fischerandom\n")) { frc |= 1; if(frc > 0) EPRINT((f, "# setoption name UCI_Chess960 value true\n")) } } else if(!strcmp(command, "undo") && (i=1) || !strcmp(command, "remove") && (i=2)) { if(pondering || computer == ANALYZE) StopPonder(1), searching = 0; @@ -530,18 +530,18 @@ GUI2Engine() else if(!strcmp(command, "otim")) sscanf(line+4, "%d", &hisTime), hisTime = (10*hisTime)/unit; else if(!strcmp(command, "post")) post = 1; else if(!strcmp(command, "nopost")) post = 0; - else if(!strcmp(command, "easy") && !!*canPonder) ponder = 0, StopPonder(pondering), fprintf(toE, "setoption %s%s %sfalse\n", nameWord, canPonder, valueWord); - else if(!strcmp(command, "hard") && !!*canPonder) ponder = 1, fprintf(toE, "setoption %s%s %strue\n", nameWord, canPonder, valueWord), StartPonder(); + else if(!strcmp(command, "easy") && !!*canPonder) { ponder = 0; StopPonder(pondering); EPRINT((f, "# setoption %s%s %sfalse\n", nameWord, canPonder, valueWord)) } + else if(!strcmp(command, "hard") && !!*canPonder) { ponder = 1; EPRINT((f, "# setoption %s%s %strue\n", nameWord, canPonder, valueWord)) StartPonder(); } else if(!strcmp(command, "ping")) { /* static int done; if(!done) pause = 1, fprintf(toE, "isready\n"), fflush(toE), printf("# send isready\n"), fflush(stdout), Sync(PAUSE); done = 1;*/ printf("po%s", line+2); } else if(!strcmp(command, "memory")) sscanf(line, "memory %d", &memory); - else if(!strcmp(command, "cores")&& !!*threadOpt) sscanf(line, "cores %d", &cores), fprintf(toE, "setoption %s%s %s%d\n", nameWord, threadOpt, valueWord, cores); + else if(!strcmp(command, "cores")&& !!*threadOpt) { sscanf(line, "cores %d", &cores); EPRINT((f, "# setoption %s%s %s%d\n", nameWord, threadOpt, valueWord, cores)) } else if(!strcmp(command, "sd")) sscanf(line, "sd %d", &depth); else if(!strcmp(command, "st")) sscanf(line, "st %d", &sTime), sTime = 1000*sTime - 30, inc = 0, sTime /= unit; - else if(!strcmp(command, "name")) { if(namOpt) fprintf(toE, "setoption name UCI_Opponent value none none %s %s", comp ? "computer" : "human", line+5); } + else if(!strcmp(command, "name")) { if(namOpt) EPRINT((f, "# setoption name UCI_Opponent value none none %s %s", comp ? "computer" : "human", line+5)) } else if(!strcmp(command, "computer")) comp = 1; else if(!strcmp(command, "offer")) drawOffer = 1; - else if(!strcmp(command, "result")) { if(sc == 's') fprintf(toE, "gameover %s\n", line[8] == '/' ? "draw" : (line[7] == '0') == mySide ? "win" : "lose"); } - else if(!strcmp(command, "quit")) fprintf(toE, "quit\n"), fflush(toE), exit(0); + else if(!strcmp(command, "result")) { if(sc == 's') EPRINT((f, "# gameover %s\n", line[8] == '/' ? "draw" : (line[7] == '0') == mySide ? "win" : "lose")) } + else if(!strcmp(command, "quit")) { EPRINT((f, "# quit\n")) fflush(toE), exit(0); } } } -- 1.7.0.4 From 85a82d8e5887b236ac8c6bad257301b4d30b948d Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Wed, 30 Nov 2016 09:02:50 +0100 Subject: [PATCH 08/16] Treat 'quit', 'force' and '?' commands immediately The blocking of input during thinking made it also insensitive to 'quit'. This would cause hanging engine processes when the GUI was closed and would kill the adapter because it did not respond to 'quit' in time. The Sync() call that waits for the engine's 'bestmove' is now deferred until after reading input (executed conditionally through a 'think' flag, so that it only happens during thinking), so that we can peek at the first command that arrives during thinking. If it is one of the 'immediate' commands (?, quit or force), a 'stop' command is sent to the engine before we start waiting for the 'bestmove', which then never should take long. Draw offers could potentially arrive during thinking as well, interefering with this mechanism by eclipsing a following ?/quit/force. As these would just set the drawOffer flag for processing during the next move, these are now fully processed immediately. --- UCI2WB.c | 10 +++++++--- 1 files changed, 7 insertions(+), 3 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index c13ea34..2971c3f 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -398,7 +398,7 @@ GUI2Engine() char line[256], command[256], *p, *q, *r, mySide, searching = 0; while(1) { - int i, x; + int i, x, think=0; if((computer == stm || computer == ANALYZE && !searching) && !suspended) { DPRINT("# start search\n"); @@ -412,13 +412,18 @@ GUI2Engine() for(i=1; i Date: Wed, 30 Nov 2016 09:22:55 +0100 Subject: [PATCH 09/16] Treat EOF same as quit command A EOF during reading from the GUI caused UCI2WB to exit through a different path as a 'quit' command. Now the EOF detection just fakes a 'quit -1' command was received, and then processes that as usual. This means it also causes proper termination of the engine when it occurs during thinking (sending 'stop'). The '-1' appendage to the command allows the 'quit' handling to return an error code. --- UCI2WB.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 2971c3f..73a55c6 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -417,7 +417,7 @@ GUI2Engine() nomove: fflush(toE); fflush(stdout); i = 0; while((x = getchar()) != EOF && (line[i] = x) != '\n') i++; - line[++i] = 0; if(x == EOF) { printf("# EOF\n"); EPRINT((f, "# quit\n")) exit(-1); } + line[++i] = 0; if(x == EOF) { printf("# EOF\n"); sprintf(line, "quit -1\n"); } sscanf(line, "%s", command); if(!strcmp(command, "offer")) { drawOffer = 1; goto nomove; } // backlogged anyway, so this can be done instantly if(think) { // command arrived during thinking; order abort for 'instant commands' @@ -545,7 +545,7 @@ GUI2Engine() else if(!strcmp(command, "name")) { if(namOpt) EPRINT((f, "# setoption name UCI_Opponent value none none %s %s", comp ? "computer" : "human", line+5)) } else if(!strcmp(command, "computer")) comp = 1; else if(!strcmp(command, "result")) { if(sc == 's') EPRINT((f, "# gameover %s\n", line[8] == '/' ? "draw" : (line[7] == '0') == mySide ? "win" : "lose")) } - else if(!strcmp(command, "quit")) { EPRINT((f, "# quit\n")) fflush(toE), exit(0); } + else if(!strcmp(command, "quit")) { EPRINT((f, "# quit\n")) fflush(toE), exit(atoi(line+4)); } } } -- 1.7.0.4 From 1e8b2a6e137452edec15c70394d22bf864a5d54f Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Wed, 30 Nov 2016 12:09:00 +0100 Subject: [PATCH 10/16] Implement UCI_AnalyseMode option An engine option of the form "??I_AnalyseMode" is now assumed to be the standard check option UCI_... or USI_... (OK, that is a bit flaky). It will not be reported as option feature, and will be automatically set during analysis. --- UCI2WB.c | 13 ++++++++++--- 1 files changed, 10 insertions(+), 3 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 73a55c6..7d915e8 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -44,7 +44,7 @@ char move[2000][10], checkOptions[8192], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt; int mps, tc, inc, sTime, depth, myTime, hisTime, stm, computer = NONE, memory, oldMem=0, cores, moveNr, lastDepth, lastScore, startTime, debug, flob; int statDepth, statScore, statNodes, statTime, currNr, size, collect, nr, sm, inex, on[500], frc, byo = -1, namOpt, comp; -char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000]; +char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000], anaOpt[20]; char board[100]; // XQ board for UCCI char *nameWord = "name ", *valueWord = "value ", *wTime = "w", *bTime = "b", *wInc = "winc", *bInc = "binc", newGame; // keywords that differ in UCCI int unit = 1, drawOffer; @@ -191,6 +191,12 @@ StartPonder() StartSearch(" ponder"); } +void +Analyze(char *val) +{ + if(*anaOpt) EPRINT((f, "# setoption %s%s %s%s\n", nameWord, anaOpt, valueWord, val)); +} + char *Convert(char *pv) { // convert Shogi coordinates to WB char *p, *q, c; @@ -321,6 +327,7 @@ Engine2GUI() if(!strcasecmp(name, "UCI_Chess960")) { frc=2; continue; } if(!strcasecmp(name, "UCI_Variant")) { if(p = strstr(line+6, " var ")) strcpy(varList, p); varOpt = 1; continue; } if(!strcasecmp(name, "UCI_Opponent")) { namOpt = 1; continue; } + if(!strcasecmp(name+2, "I_AnalyseMode")) { strcpy(anaOpt, name); continue; } if(frc< 0 && (strstr(name, "960") || strcasestr(name, "frc")) && !strcmp(type, "check")) { EPRINT((f, "# setoption name %s value true\n", name)) strcpy(val, "true"); // set non-standard suspected FRC options } @@ -527,8 +534,8 @@ GUI2Engine() suspended = 0; // causes thinking to start in normal way if on move or analyzing } else if(!strcmp(command, "xboard")) ; - else if(!strcmp(command, "analyze"))computer = ANALYZE, collect = 1, sm = 0; - else if(!strcmp(command, "exit")) computer = NONE, StopPonder(1), searching = 0; + else if(!strcmp(command, "analyze"))computer = ANALYZE, collect = 1, sm = 0, Analyze("true"); + else if(!strcmp(command, "exit")) computer = NONE, StopPonder(1), searching = 0, Analyze("false"); else if(!strcmp(command, "force")) computer = NONE, StopPonder(pondering); else if(!strcmp(command, "go")) computer = stm; else if(!strcmp(command, "time")) sscanf(line+4, "%d", &myTime), myTime = (10*myTime)/unit; -- 1.7.0.4 From e4503efed01f5923d70288d6a7f50de29c27f0b7 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Wed, 30 Nov 2016 14:17:55 +0100 Subject: [PATCH 11/16] Also treat 'result' command immediately WinBoard has the nasty habit of sending a 'result' command just before 'quit' when it is closed while the engine is thinking, without putting the engine in force mode first. So this command has to abort thinking too. To prevent this will start a ponder search, 'result' now puts us in force mode. --- UCI2WB.c | 8 ++++++-- 1 files changed, 6 insertions(+), 2 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 7d915e8..adfb3d6 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -428,7 +428,8 @@ GUI2Engine() sscanf(line, "%s", command); if(!strcmp(command, "offer")) { drawOffer = 1; goto nomove; } // backlogged anyway, so this can be done instantly if(think) { // command arrived during thinking; order abort for 'instant commands' - if(!strcmp(command, "quit") || !strcmp(command, "force") || !strcmp(command, "?")) { EPRINT((f, "# stop\n")); fflush(toE); } + if(!strcmp(command, "?") || !strcmp(command, "quit") || + !strcmp(command, "force") || !strcmp(command, "result")) { EPRINT((f, "# stop\n")); fflush(toE); } Sync(PAUSE); // block processing of input during thinking } if(!strcmp(command, "new")) { @@ -551,7 +552,10 @@ GUI2Engine() else if(!strcmp(command, "st")) sscanf(line, "st %d", &sTime), sTime = 1000*sTime - 30, inc = 0, sTime /= unit; else if(!strcmp(command, "name")) { if(namOpt) EPRINT((f, "# setoption name UCI_Opponent value none none %s %s", comp ? "computer" : "human", line+5)) } else if(!strcmp(command, "computer")) comp = 1; - else if(!strcmp(command, "result")) { if(sc == 's') EPRINT((f, "# gameover %s\n", line[8] == '/' ? "draw" : (line[7] == '0') == mySide ? "win" : "lose")) } + else if(!strcmp(command, "result")) { + if(sc == 's') EPRINT((f, "# gameover %s\n", line[8] == '/' ? "draw" : (line[7] == '0') == mySide ? "win" : "lose")) + computer = NONE; + } else if(!strcmp(command, "quit")) { EPRINT((f, "# quit\n")) fflush(toE), exit(atoi(line+4)); } } } -- 1.7.0.4 From 5e15f187c0e73082935dce3fef09a436e3467c35 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Thu, 1 Dec 2016 14:41:05 +0100 Subject: [PATCH 12/16] Process all innocent commands immediately (fixes pause/resume) To prevent that commands received during thinking will halt command processing,eclipsing later instant commands, all commands that merely alter a settings value in adapter or engine are now immediately executed. UCI engines should be able to handle 'setoption' commands even during thinking, so explicit or implicit (easy/hard) settings changes are immediately relayed to the engine as well. The 'pause' and 'resume' commands are now also treated immediately; waiting with that until the engine is done thinking subverted the intention of the 'pause' command. Commands that would never be sent when an engine is thinking are still left with the commands that require thinking to complete, as for those thisdoes not matter. --- UCI2WB.c | 62 +++++++++++++++++++++++++++++++++++--------------------------- 1 files changed, 35 insertions(+), 27 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index adfb3d6..5ebe5ef 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -405,7 +405,7 @@ GUI2Engine() char line[256], command[256], *p, *q, *r, mySide, searching = 0; while(1) { - int i, x, think=0; + int i, x, difficult, think=0; if((computer == stm || computer == ANALYZE && !searching) && !suspended) { DPRINT("# start search\n"); @@ -422,11 +422,44 @@ GUI2Engine() } else pause = think = 2, StartSearch(""); // request suspending of input processing while thinking } nomove: + for(difficult=0; !difficult; ) { // read and handle commands that can (or must) be handled during thinking fflush(toE); fflush(stdout); i = 0; while((x = getchar()) != EOF && (line[i] = x) != '\n') i++; line[++i] = 0; if(x == EOF) { printf("# EOF\n"); sprintf(line, "quit -1\n"); } sscanf(line, "%s", command); - if(!strcmp(command, "offer")) { drawOffer = 1; goto nomove; } // backlogged anyway, so this can be done instantly + if(!strcmp(command, "offer")) drawOffer = 1; // backlogged anyway, so this can be done instantly + else if(!strcmp(command, "post")) post = 1; + else if(!strcmp(command, "nopost"))post = 0; + else if(!strcmp(command, "option")) { + char name[80], *p; + if(searching) StopPonder(1), searching = 0; // force new search if settings change during analysis (multi-PV!) + if(sscanf(line+7, "UCI2WB debug output=%d", &debug) == 1) ; else + if(sscanf(line+7, "Floating Byoyomi=%d", &flob) == 1) ; else + if(sscanf(line+7, "Byoyomi=%d", &byo) == 1) ; else + if(p = strchr(line, '=')) { + *p++ = 0; + if(strstr(checkOptions, line+7)) sprintf(p, "%s\n", atoi(p) ? "true" : "false"); + EPRINT((f, "# setoption %s%s %s%s", nameWord, line+7, valueWord, p)) + } else EPRINT((f, "# setoption %s%s\n", nameWord, line+7)) + } + else if(!strcmp(command, "pause")) { + if(computer == stm) myTime -= GetTickCount() - startTime; + suspended = 1 + pondering; // remember if we were pondering, and stop search ignoring bestmove + StopPonder(pondering || computer == stm); + } + else if(!strcmp(command, "easy")) { + if(*canPonder) { ponder = 0; StopPonder(pondering); EPRINT((f, "# setoption %s%s %sfalse\n", nameWord, canPonder, valueWord)) } + } + else if(!strcmp(command, "hard")) { + if(*canPonder) { ponder = 1; EPRINT((f, "# setoption %s%s %strue\n", nameWord, canPonder, valueWord)) StartPonder(); } + } + else difficult = 1; // difficult command; terminate loop for easy ones + } // next command + + if(!strcmp(command, "resume")) { + if(suspended == 2) StartPonder(); // restart interrupted ponder search + suspended = think = 0; continue; // causes thinking to start in normal way if on move or analyzing + } if(think) { // command arrived during thinking; order abort for 'instant commands' if(!strcmp(command, "?") || !strcmp(command, "quit") || !strcmp(command, "force") || !strcmp(command, "result")) { EPRINT((f, "# stop\n")); fflush(toE); } @@ -468,18 +501,6 @@ GUI2Engine() sscanf(line, "level %d %d %d", &mps, &tc, &inc); tc = (60*tc + sec)*1000; inc *= 1000; sTime = 0; tc /= unit; inc /= unit; } - else if(!strcmp(command, "option")) { - char name[80], *p; - if(searching) StopPonder(1), searching = 0; // force new search if settings change during analysis (multi-PV!) - if(sscanf(line+7, "UCI2WB debug output=%d", &debug) == 1) ; else - if(sscanf(line+7, "Floating Byoyomi=%d", &flob) == 1) ; else - if(sscanf(line+7, "Byoyomi=%d", &byo) == 1) ; else - if(p = strchr(line, '=')) { - *p++ = 0; - if(strstr(checkOptions, line+7)) sprintf(p, "%s\n", atoi(p) ? "true" : "false"); - EPRINT((f, "# setoption %s%s %s%s", nameWord, line+7, valueWord, p)) - } else EPRINT((f, "# setoption %s%s\n", nameWord, line+7)) - } else if(!strcmp(command, "protover")) { if(!variants) variants = sc=='s' ? "shogi,5x5+5_shogi" : VARIANTS; printf("feature variants=\"%s\" setboard=1 usermove=1 debug=1 ping=1 name=1 reuse=0 exclude=1 pause=1 sigint=0 sigterm=0 done=0\n", variants); @@ -525,15 +546,6 @@ GUI2Engine() if(!(sm & 2)) goto nomove; // no moves enabled; continue current search if(computer == ANALYZE) StopPonder(1), searching = 0; // abort old analysis } - else if(!strcmp(command, "pause")) { - if(computer == stm) myTime -= GetTickCount() - startTime; - suspended = 1 + pondering; // remember if we were pondering, and stop search ignoring bestmove - StopPonder(pondering || computer == stm); - } - else if(!strcmp(command, "resume")) { - if(suspended == 2) StartPonder(); // restart interrupted ponder search - suspended = 0; // causes thinking to start in normal way if on move or analyzing - } else if(!strcmp(command, "xboard")) ; else if(!strcmp(command, "analyze"))computer = ANALYZE, collect = 1, sm = 0, Analyze("true"); else if(!strcmp(command, "exit")) computer = NONE, StopPonder(1), searching = 0, Analyze("false"); @@ -541,10 +553,6 @@ GUI2Engine() else if(!strcmp(command, "go")) computer = stm; else if(!strcmp(command, "time")) sscanf(line+4, "%d", &myTime), myTime = (10*myTime)/unit; else if(!strcmp(command, "otim")) sscanf(line+4, "%d", &hisTime), hisTime = (10*hisTime)/unit; - else if(!strcmp(command, "post")) post = 1; - else if(!strcmp(command, "nopost")) post = 0; - else if(!strcmp(command, "easy") && !!*canPonder) { ponder = 0; StopPonder(pondering); EPRINT((f, "# setoption %s%s %sfalse\n", nameWord, canPonder, valueWord)) } - else if(!strcmp(command, "hard") && !!*canPonder) { ponder = 1; EPRINT((f, "# setoption %s%s %strue\n", nameWord, canPonder, valueWord)) StartPonder(); } else if(!strcmp(command, "ping")) { /* static int done; if(!done) pause = 1, fprintf(toE, "isready\n"), fflush(toE), printf("# send isready\n"), fflush(stdout), Sync(PAUSE); done = 1;*/ printf("po%s", line+2); } else if(!strcmp(command, "memory")) sscanf(line, "memory %d", &memory); else if(!strcmp(command, "cores")&& !!*threadOpt) { sscanf(line, "cores %d", &cores); EPRINT((f, "# setoption %s%s %s%d\n", nameWord, threadOpt, valueWord, cores)) } -- 1.7.0.4 From 04a35064522994a86ff436cc1705f0ae768bb23f Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Thu, 1 Dec 2016 14:53:48 +0100 Subject: [PATCH 13/16] Fix instant commands during think after ponder hit The new method of suspending command processing only after peeking at the next command to see if it should terminate the search was not yet working after a ponder hit turned a ponder search into thinking. --- UCI2WB.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 5ebe5ef..7f0223e 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -488,7 +488,7 @@ GUI2Engine() char *draw = drawOffer ? " draw" : ""; drawOffer = 0; pondering = 0; pause = 2; moveNr++; startTime = GetTickCount(); // clock starts running now EPRINT((f, "# ponderhit%s\n", draw)) fflush(toE); fflush(stdout); - Sync(PAUSE); // block input during thinking + think = 2; // request blocking input during thinking goto nomove; } StopPonder(1); searching = 0; -- 1.7.0.4 From 8d74aed8f6fb583c14bf2f6622811221672b8d23 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Thu, 1 Dec 2016 21:17:11 +0100 Subject: [PATCH 14/16] Buffer 'setoption' commands until engine done thinking UCI does not allow setting of options during search after all. So all 'setoption' commands are now first collected in a memory buffer. During ponder or analysis the search is then stopped immediately to flush that buffer to the engine, in a new routine Release(). This routine will also (re)start a ponder search if one is called for. During thinking, finishing the search has priority. So only Release() the buffer contents when a search finishes (immediately after receiving 'bestmove'). To deal with race conditions we also flush the buffer in the GUI thread, after waiting for thinking to finish, just in case. The WB 'easy' and 'hard' command implicitly set the Ponder option, and this is treated by the hack of injecting fake 'option' commands in the input stream, which then are subjected to the same buffering scheme. Because waiting for thinking termination might now not happen at all (if we only receive commands that can be handled instantly), a test for search termination was added immediately after reading the command, so that every command can now be aware whether thinking is in progress. --- UCI2WB.c | 50 +++++++++++++++++++++++++++++++++----------------- 1 files changed, 33 insertions(+), 17 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 7f0223e..997423f 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -41,13 +41,14 @@ #define NONE 2 #define ANALYZE 3 -char move[2000][10], checkOptions[8192], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt; +char move[2000][10], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt, searching; int mps, tc, inc, sTime, depth, myTime, hisTime, stm, computer = NONE, memory, oldMem=0, cores, moveNr, lastDepth, lastScore, startTime, debug, flob; int statDepth, statScore, statNodes, statTime, currNr, size, collect, nr, sm, inex, on[500], frc, byo = -1, namOpt, comp; -char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000], anaOpt[20]; +char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000], anaOpt[20], backLog[10000], checkOptions[8192] = "Ponder"; char board[100]; // XQ board for UCCI char *nameWord = "name ", *valueWord = "value ", *wTime = "w", *bTime = "b", *wInc = "winc", *bInc = "binc", newGame; // keywords that differ in UCCI int unit = 1, drawOffer; +volatile int logLen, sentLen; FILE *toE, *fromE, *fromF; int pid; @@ -197,6 +198,17 @@ Analyze(char *val) if(*anaOpt) EPRINT((f, "# setoption %s%s %s%s\n", nameWord, anaOpt, valueWord, val)); } +int +Release() +{ // send setoption commands backlogged during thinking to engine, aborting ponder or analysis search if necessary + int len = logLen - sentLen, analyse = searching; + if(len <= 0) return 0; + StopPonder(pondering | searching); pondering = searching = 0; // force new search if settings change during analysis (multi-PV!) + fwrite(backLog + sentLen, 1, len, toE); sentLen += len; DPRINT("# release %d\n", len); + if(ponder && computer == 1 - stm) StartPonder(); // (re)start ponder search + return analyse; // return 1 if analysis search should be restarted +} + char *Convert(char *pv) { // convert Shogi coordinates to WB char *p, *q, c; @@ -247,7 +259,7 @@ Engine2GUI() if(fromF = fopen("DefectiveEngineOptions.ini", "r")) printf("# fake engine input\n"); while(1) { - int i=0, x; char *p, dummy; + int i=0, x; char *p, dummy, len; fflush(stdout); fflush(toE); while((line[i] = x = GetChar()) != EOF && line[i] != '\n') i++; @@ -265,6 +277,7 @@ Engine2GUI() if(strstr(line+9, "(none)") || strstr(line+9, "null") || strstr(line+9, "0000")) { printf("%s\n", lastScore < -99999 ? "resign" : "1/2-1/2 {stalemate}"); computer = NONE; } sscanf(line, "bestmove %s", move[moveNr++]); + Release(); // send setoption commands that arrived during search myTime -= (GetTickCount() - startTime)*1.02 + inc; // update own clock, so we can give correct wtime, btime with ponder if(mps && ((moveNr+1)/2) % mps == 0) myTime += tc; if(sTime) myTime = sTime; // new session or move starts stm = WHITE+BLACK - stm; @@ -402,7 +415,7 @@ Move4Engine(char *m) void GUI2Engine() { - char line[256], command[256], *p, *q, *r, mySide, searching = 0; + char line[256], command[256], *p, *q, *r, mySide; while(1) { int i, x, difficult, think=0; @@ -426,33 +439,36 @@ GUI2Engine() fflush(toE); fflush(stdout); i = 0; while((x = getchar()) != EOF && (line[i] = x) != '\n') i++; line[++i] = 0; if(x == EOF) { printf("# EOF\n"); sprintf(line, "quit -1\n"); } - sscanf(line, "%s", command); + if(think && !pause) Sync(PAUSE), think = 0, Release(); // if no longer thinking, take dummy pause + sscanf(line, "%s", command); DPRINT("# '%s' think=%d pause=%d log=%d sent=%d\n", command, think, pause, logLen, sentLen); + if(!strcmp(command, "easy")) { + if(*canPonder) ponder = 0, sprintf(command, "option"), sprintf(line, "option %s=0\n", canPonder); else continue; + } + else if(!strcmp(command, "hard")) { + if(*canPonder) ponder = 1, sprintf(command, "option"), sprintf(line, "option %s=1\n", canPonder); else continue; + } if(!strcmp(command, "offer")) drawOffer = 1; // backlogged anyway, so this can be done instantly else if(!strcmp(command, "post")) post = 1; else if(!strcmp(command, "nopost"))post = 0; else if(!strcmp(command, "option")) { - char name[80], *p; - if(searching) StopPonder(1), searching = 0; // force new search if settings change during analysis (multi-PV!) + char *p; + if(logLen == sentLen) logLen = 0, sentLen = 0; // engine is up to date; reset buffer if(sscanf(line+7, "UCI2WB debug output=%d", &debug) == 1) ; else if(sscanf(line+7, "Floating Byoyomi=%d", &flob) == 1) ; else if(sscanf(line+7, "Byoyomi=%d", &byo) == 1) ; else if(p = strchr(line, '=')) { *p++ = 0; if(strstr(checkOptions, line+7)) sprintf(p, "%s\n", atoi(p) ? "true" : "false"); - EPRINT((f, "# setoption %s%s %s%s", nameWord, line+7, valueWord, p)) - } else EPRINT((f, "# setoption %s%s\n", nameWord, line+7)) + snprintf(backLog+logLen, 9999-logLen, "setoption %s%s %s%s", nameWord, line+7, valueWord, p); + } else snprintf(backLog+logLen, 9999-logLen, "setoption %s%s\n", nameWord, line+7); + DPRINT("# backlog: %s", backLog+logLen); logLen += strlen(backLog+logLen); + if(!think && Release()) break; // break will restart analysis; pondering is restarted by Release itself } else if(!strcmp(command, "pause")) { if(computer == stm) myTime -= GetTickCount() - startTime; suspended = 1 + pondering; // remember if we were pondering, and stop search ignoring bestmove StopPonder(pondering || computer == stm); } - else if(!strcmp(command, "easy")) { - if(*canPonder) { ponder = 0; StopPonder(pondering); EPRINT((f, "# setoption %s%s %sfalse\n", nameWord, canPonder, valueWord)) } - } - else if(!strcmp(command, "hard")) { - if(*canPonder) { ponder = 1; EPRINT((f, "# setoption %s%s %strue\n", nameWord, canPonder, valueWord)) StartPonder(); } - } else difficult = 1; // difficult command; terminate loop for easy ones } // next command @@ -460,10 +476,10 @@ GUI2Engine() if(suspended == 2) StartPonder(); // restart interrupted ponder search suspended = think = 0; continue; // causes thinking to start in normal way if on move or analyzing } - if(think) { // command arrived during thinking; order abort for 'instant commands' + if(think) { // command arrived during thinking; order abort for 'instant commands' if(!strcmp(command, "?") || !strcmp(command, "quit") || !strcmp(command, "force") || !strcmp(command, "result")) { EPRINT((f, "# stop\n")); fflush(toE); } - Sync(PAUSE); // block processing of input during thinking + Sync(PAUSE); Release(); // block processing of difficult commands during thinking; send backlog left because of race } if(!strcmp(command, "new")) { computer = BLACK; moveNr = 0; depth = -1; move[0][0] = 0; -- 1.7.0.4 From ac79f2335f0fde3a37a4905ae05fa81baddc779c Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Sat, 3 Dec 2016 11:42:41 +0100 Subject: [PATCH 15/16] Improve efficiency The commands usermove, time and otim are now matched first, because these are the commands used during game play, when response time and CPU usage are relevant concerns. --- UCI2WB.c | 71 +++++++++++++++++++++++++++++++++---------------------------- 1 files changed, 38 insertions(+), 33 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 997423f..9b5e70c 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -441,16 +441,25 @@ GUI2Engine() line[++i] = 0; if(x == EOF) { printf("# EOF\n"); sprintf(line, "quit -1\n"); } if(think && !pause) Sync(PAUSE), think = 0, Release(); // if no longer thinking, take dummy pause sscanf(line, "%s", command); DPRINT("# '%s' think=%d pause=%d log=%d sent=%d\n", command, think, pause, logLen, sentLen); - if(!strcmp(command, "easy")) { - if(*canPonder) ponder = 0, sprintf(command, "option"), sprintf(line, "option %s=0\n", canPonder); else continue; - } - else if(!strcmp(command, "hard")) { - if(*canPonder) ponder = 1, sprintf(command, "option"), sprintf(line, "option %s=1\n", canPonder); else continue; - } - if(!strcmp(command, "offer")) drawOffer = 1; // backlogged anyway, so this can be done instantly + if(!strcmp(command, "usermove")) { difficult--; break; } // for efficiency during game play, moves, time & otim are tried first + else if(!strcmp(command, "time")) sscanf(line+4, "%d", &myTime), myTime = (10*myTime)/unit; + else if(!strcmp(command, "otim")) sscanf(line+4, "%d", &hisTime), hisTime = (10*hisTime)/unit; + else if(!strcmp(command, "offer")) drawOffer = 1; // backlogged anyway, so this can be done instantly else if(!strcmp(command, "post")) post = 1; else if(!strcmp(command, "nopost"))post = 0; - else if(!strcmp(command, "option")) { + else if(!strcmp(command, "pause")) { + if(computer == stm) myTime -= GetTickCount() - startTime; + suspended = 1 + pondering; // remember if we were pondering, and stop search ignoring bestmove + StopPonder(pondering || computer == stm); + } + else { //convert easy & hard to "option" after treating their effect on the adapter + if(!strcmp(command, "easy")) { + if(*canPonder) ponder = 0, sprintf(command, "option"), sprintf(line, "option %s=0\n", canPonder); else continue; + } + else if(!strcmp(command, "hard")) { + if(*canPonder) ponder = 1, sprintf(command, "option"), sprintf(line, "option %s=1\n", canPonder); else continue; + } + if(!strcmp(command, "option")) { char *p; if(logLen == sentLen) logLen = 0, sentLen = 0; // engine is up to date; reset buffer if(sscanf(line+7, "UCI2WB debug output=%d", &debug) == 1) ; else @@ -463,15 +472,30 @@ GUI2Engine() } else snprintf(backLog+logLen, 9999-logLen, "setoption %s%s\n", nameWord, line+7); DPRINT("# backlog: %s", backLog+logLen); logLen += strlen(backLog+logLen); if(!think && Release()) break; // break will restart analysis; pondering is restarted by Release itself + } + else difficult = 1; // difficult command; terminate loop for easy ones } - else if(!strcmp(command, "pause")) { - if(computer == stm) myTime -= GetTickCount() - startTime; - suspended = 1 + pondering; // remember if we were pondering, and stop search ignoring bestmove - StopPonder(pondering || computer == stm); - } - else difficult = 1; // difficult command; terminate loop for easy ones } // next command + // some commands that should never come during thinking can be safely processed here + if(difficult < 0) { // used as kludge to signal "usermove" was already matched + sscanf(line, "usermove %s", command); // strips off linefeed + Move4Engine(command); + stm = WHITE+BLACK - stm; collect = (computer == ANALYZE); sm = 0; + // when pondering we either continue the ponder search as normal search, or abort it + if(pondering || computer == ANALYZE) { + if(pondering && !strcmp(command, move[moveNr])) { // ponder hit + char *draw = drawOffer ? " draw" : ""; drawOffer = 0; + pondering = 0; pause = 2; moveNr++; startTime = GetTickCount(); // clock starts running now + EPRINT((f, "# ponderhit%s\n", draw)) fflush(toE); fflush(stdout); + think = 2; // request blocking input during thinking + goto nomove; + } + StopPonder(1); searching = 0; + } + strcpy(move[moveNr++], command); // possibly overwrites ponder move + continue; + } if(!strcmp(command, "resume")) { if(suspended == 2) StartPonder(); // restart interrupted ponder search suspended = think = 0; continue; // causes thinking to start in normal way if on move or analyzing @@ -494,23 +518,6 @@ GUI2Engine() Sync(PAUSE); // wait for readyok EPRINT((f, "# u%cinewgame\n", sc)) fflush(toE); } - else if(!strcmp(command, "usermove")) { - sscanf(line, "usermove %s", command); // strips off linefeed - Move4Engine(command); - stm = WHITE+BLACK - stm; collect = (computer == ANALYZE); sm = 0; - // when pondering we either continue the ponder search as normal search, or abort it - if(pondering || computer == ANALYZE) { - if(pondering && !strcmp(command, move[moveNr])) { // ponder hit - char *draw = drawOffer ? " draw" : ""; drawOffer = 0; - pondering = 0; pause = 2; moveNr++; startTime = GetTickCount(); // clock starts running now - EPRINT((f, "# ponderhit%s\n", draw)) fflush(toE); fflush(stdout); - think = 2; // request blocking input during thinking - goto nomove; - } - StopPonder(1); searching = 0; - } - strcpy(move[moveNr++], command); // possibly overwrites ponder move - } else if(!strcmp(command, "level")) { int sec = 0; sscanf(line, "level %d %d:%d %d", &mps, &tc, &sec, &inc) == 4 || @@ -567,8 +574,6 @@ GUI2Engine() else if(!strcmp(command, "exit")) computer = NONE, StopPonder(1), searching = 0, Analyze("false"); else if(!strcmp(command, "force")) computer = NONE, StopPonder(pondering); else if(!strcmp(command, "go")) computer = stm; - else if(!strcmp(command, "time")) sscanf(line+4, "%d", &myTime), myTime = (10*myTime)/unit; - else if(!strcmp(command, "otim")) sscanf(line+4, "%d", &hisTime), hisTime = (10*hisTime)/unit; else if(!strcmp(command, "ping")) { /* static int done; if(!done) pause = 1, fprintf(toE, "isready\n"), fflush(toE), printf("# send isready\n"), fflush(stdout), Sync(PAUSE); done = 1;*/ printf("po%s", line+2); } else if(!strcmp(command, "memory")) sscanf(line, "memory %d", &memory); else if(!strcmp(command, "cores")&& !!*threadOpt) { sscanf(line, "cores %d", &cores); EPRINT((f, "# setoption %s%s %s%d\n", nameWord, threadOpt, valueWord, cores)) } -- 1.7.0.4 From f26559fcc563015f7049626337121d1b448bfee5 Mon Sep 17 00:00:00 2001 From: H.G.Muller Date: Mon, 5 Dec 2016 09:29:13 +0100 Subject: [PATCH 16/16] Display error message when engine dies UCI2WB used to exit silently when reading from the engine produced an EOF response (indicating the engine process died). This then evoked the standard XBoard fatal error "... engine exited uexpectedly". Now it sends a 'tellusererror' command to the GUI to suppress the standard popup, and to clearly indicate that it was because of a crash in the engine rather than in the adapter. --- UCI2WB.c | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diff --git a/UCI2WB.c b/UCI2WB.c index 9b5e70c..f094d10 100644 --- a/UCI2WB.c +++ b/UCI2WB.c @@ -41,7 +41,7 @@ #define NONE 2 #define ANALYZE 3 -char move[2000][10], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt, searching; +char move[2000][10], iniPos[256], hashOpt[20], pause, pondering, suspended, ponder, post, hasHash, c, sc='c', suffix[81], *variants, varOpt, searching, *binary; int mps, tc, inc, sTime, depth, myTime, hisTime, stm, computer = NONE, memory, oldMem=0, cores, moveNr, lastDepth, lastScore, startTime, debug, flob; int statDepth, statScore, statNodes, statTime, currNr, size, collect, nr, sm, inex, on[500], frc, byo = -1, namOpt, comp; char currMove[20], moveMap[500][10], /* for analyze mode */ canPonder[20], threadOpt[20], varList[8000], anaOpt[20], backLog[10000], checkOptions[8192] = "Ponder"; @@ -264,7 +264,7 @@ Engine2GUI() fflush(stdout); fflush(toE); while((line[i] = x = GetChar()) != EOF && line[i] != '\n') i++; line[++i] = 0; - if(x == EOF) exit(0); + if(x == EOF) printf("tellusererror UCI2WB: %s died on me\n", binary), exit(0); DPRINT("# engine said: %s", line), fflush(stdout); if(sscanf(line, "%s", command) != 1) continue; if(!strcmp(command, "bestmove")) { @@ -691,7 +691,7 @@ main(int argc, char **argv) else if(sc == 'n') sc = 'c'; // UCI for normal Chess // spawn engine proc - if(StartEngine(argv[1], dir) != NO_ERROR) { perror(argv[1]), exit(-1); } + if(StartEngine(binary = argv[1], dir) != NO_ERROR) { perror(argv[1]), exit(-1); } Sync(INIT); -- 1.7.0.4