Port UCI2WB to Linux
[uci2wb.git] / UCI2WB.c
blob6b454deddb1bde972a51da169818ac284db18220
1 /************************* UCI2WB by H.G.Muller ****************************/
3 #define VERSION "1.5"
5 #include <stdio.h>
6 #include <stdlib.h>
7 #ifdef WIN32
8 # include <windows.h>
9 # include <io.h>
10 HANDLE process;
11 DWORD thread_id;
12 #else
13 # include <pthread.h>
14 # include <signal.h>
15 # define NO_ERROR 0
16 # include <sys/time.h>
17 int GetTickCount() // with thanks to Tord
18 { struct timeval t; gettimeofday(&t, NULL); return t.tv_sec*1000 + t.tv_usec/1000; }
19 #endif
20 #include <fcntl.h>
21 #include <string.h>
23 #ifdef _MSC_VER
24 #define SLEEP() Sleep(1)
25 #else
26 #define SLEEP() usleep(10)
27 #endif
29 // Set VARIANTS for in WinBoard variant feature. (With -s option this will always be reset to use "shogi".)
30 # define VARIANTS "normal,xiangqi"
32 #define WHITE 0
33 #define BLACK 1
34 #define NONE 2
35 #define ANALYZE 3
37 char move[2000][10], checkOptions[8192], iniPos[256], hashOpt[20], pause, pondering, ponder, post, hasHash, c, sc='c', *suffix;
38 int mps, tc, inc, sTime, depth, myTime, hisTime, stm, computer = NONE, memory, oldMem=0, cores, moveNr, lastDepth, lastScore, startTime;
39 int statDepth, statScore, statNodes, statTime, currNr, size; char currMove[20]; // for analyze mode
41 FILE *toE, *fromE;
42 int pid;
44 void
45 StartSearch(char *ponder)
46 { // send the 'go' command to engine. Suffix by ponder.
47 int nr = moveNr + (ponder[0] != 0); // we ponder for one move ahead!
48 fprintf(toE, "\ngo btime %d wtime %d", stm == BLACK ^ sc=='s' ? myTime : hisTime, stm == WHITE ^ sc=='s' ? myTime : hisTime);
49 printf( "\n# go btime %d wtime %d", stm == BLACK ^ sc=='s' ? myTime : hisTime, stm == WHITE ^ sc=='s' ? myTime : hisTime);
50 if(sTime > 0) fprintf(toE, " movetime %d", sTime),printf(" movetime %d", sTime); else
51 if(mps) fprintf(toE, " movestogo %d", mps*(nr/(2*mps)+1)-nr/2),printf(" movestogo %d", mps*(nr/(2*mps)+1)-nr/2);
52 if(inc && !suffix) fprintf(toE, " winc %d binc %d", inc, inc),printf(" winc %d binc %d", inc, inc);
53 if(depth > 0) fprintf(toE, " depth %d", depth),printf(" depth %d", depth);
54 if(suffix) fprintf(toE, suffix, inc),printf(suffix, inc);
55 fprintf(toE, "%s\n", ponder);
56 printf("%s\n", ponder);
59 void
60 StopPonder(int pondering)
62 if(!pondering) return;
63 pause = 1;
64 fprintf(toE, "stop\n");printf("# stop\n"); // note: 'pondering' remains set until engine acknowledges 'stop' with 'bestmove'
65 while(pause) SLEEP(); // wait for engine to acknowledge 'stop' with 'bestmove'.
68 void
69 LoadPos(int moveNr)
71 int j;
72 fprintf(toE, "%s moves", iniPos);
73 printf( "# %s moves", iniPos);
74 for(j=0; j<moveNr; j++) fprintf(toE, " %s", move[j]),printf(" %s", move[j]);
77 char *Convert(char *pv)
78 { // convert Shogi coordinates to WB
79 char *p, *q, c;
80 static char buf[10000];
81 if(sc != 's') return pv;
82 p = pv; q = buf;
83 while(c = *p++) {
84 if(c >= '0' && c <= '9' || c >= 'a' && c <= 'z') *q++ = 'a'+'0'+size - c; else *q++ = c;
86 *q++ = 0;
87 return buf;
90 void *
91 Engine2GUI()
93 char line[1024], command[256];
95 while(1) {
96 int i=0, x; char *p;
98 fflush(stdout); fflush(toE);
99 while((line[i] = x = fgetc(fromE)) != EOF && line[i] != '\n') i++;
100 line[++i] = 0;
101 if(x == EOF) exit(0);
102 printf("# engine said: %s", line);
103 sscanf(line, "%s", command);
104 if(!strcmp(command, "bestmove")) {
105 if(pause) { pondering = pause = 0; continue; } // bestmove was reply to ponder miss or analysis result; ignore.
106 // move was a move to be played
107 if(strstr(line+9, "resign")) { printf("resign\n"); computer = NONE; }
108 if(strstr(line+9, "(none)") || strstr(line+9, "null") ||
109 strstr(line+9, "0000")) { printf("%s\n", lastScore < -99999 ? "resign" : "1/2-1/2 {stalemate}"); computer = NONE; }
110 sscanf(line, "bestmove %s", move[moveNr++]);
111 myTime -= (GetTickCount() - startTime)*1.02 + inc; // update own clock, so we can give correct wtime, btime with ponder
112 if(mps && ((moveNr+1)/2) % mps == 0) myTime += tc; if(sTime) myTime = sTime; // new session or move starts
113 // first start a new ponder search, if pondering is on and we have a move to ponder on
114 if(p = strstr(line+9, "ponder")) {
115 if(computer != NONE && ponder) {
116 sscanf(p+7, "%s", move[moveNr]);
117 printf("# ponder on %s\n", move[moveNr]);
118 LoadPos(moveNr+1); // load position
119 // and set engine pondering
120 pondering = 1; lastDepth = 1;
121 StartSearch(" ponder");
123 p[-1] = '\n'; *p = 0; // strip off ponder move
125 if(sc == 's') {
126 // convert USI move to WB format
127 line[11] = 'a'+'0'+size - line[11];
128 line[12] = 'a'+'0'+size - line[12];
129 if(line[10] == '*') { // drop
130 line[10] = '@';
131 } else {
132 line[9] = 'a'+'0'+size - line[9];
133 line[10] = 'a'+'0'+size - line[10];
134 if((stm == WHITE ? (line[10]>'0'+size-size/3 || line[12]>'0'+size-size/3)
135 : (line[10] <= '0'+size/3 || line[12] <= '0'+size/3)) && line[13] != '+')
136 line[13] = '=', line[14] = 0;
139 printf("move %s\n", line+9); // send move to GUI
140 if(lastScore == 100001 && iniPos[0] != 'f') { printf("%s {mate}\n", stm == WHITE ? "1-0" : "0-1"); computer = NONE; }
141 stm = WHITE+BLACK - stm;
143 else if(!strcmp(command, "info")) {
144 int d=0, s=0, t=0, n=0;
145 char *pv;
146 if(!post) continue;
147 if(strstr(line, "info string ") == line) printf("%d 0 0 0 %s", lastDepth, line+12); else {
148 if(p = strstr(line+4, " depth ")) sscanf(p+7, "%d", &d), statDepth = d;
149 if(p = strstr(line+4, " score cp ")) sscanf(p+10, "%d", &s), statScore = s; else
150 if(p = strstr(line+4, " score mate ")) sscanf(p+12, "%d", &s), s += s>0 ? 100000 : -100000, statScore = s; else
151 if(p = strstr(line+4, " score ")) sscanf(p+7, "%d", &s), statScore = s;
152 if(p = strstr(line+4, " nodes ")) sscanf(p+7, "%d", &n), statNodes = n;
153 if(p = strstr(line+4, " time ")) sscanf(p+6, "%d", &t), t /= 10, statTime = t;
154 if(p = strstr(line+4, " currmove ")) sscanf(p+10,"%s", currMove);
155 if(p = strstr(line+4, " currmovenumber ")) sscanf(p+16,"%d", &currNr);
156 if(pv = strstr(line+4, " pv ")) // convert PV info to WB thinking output
157 printf("%3d %6d %6d %10d %s", lastDepth=d, lastScore=s, t, n, Convert(pv+4));
160 else if(!strcmp(command, "option")) { // USI option: extract data fields
161 char name[80], type[80], buf[1024], val[256], *q;
162 int min=0, max=1e9;
163 if(p = strstr(line+6, " type ")) sscanf(p+1, "type %s", type), *p = '\n';
164 if(p = strstr(line+6, " min ")) sscanf(p+1, "min %d", &min), *p = '\n';
165 if(p = strstr(line+6, " max ")) sscanf(p+1, "max %d", &max), *p = '\n';
166 if(p = strstr(line+6, " default ")) sscanf(p+1, "default %[^\n]*", val), *p = '\n';
167 if(p = strstr(line+6, " name ")) sscanf(p+1, "name %[^\n]*", name);
168 if(!strcmp(name, "Hash") || !strcmp(name, "USI_Hash")) {
169 memory = oldMem = atoi(val); hasHash = 1;
170 strcpy(hashOpt, name);
171 continue;
173 // pass on engine-defined option as WB option feature
174 if(!strcmp(type, "filename")) type[4] = 0;
175 sprintf(buf, "feature option=\"%s -%s", name, type); q = buf + strlen(buf);
176 if( !strcmp(type, "file")
177 || !strcmp(type, "string")) sprintf(q, " %s\"\n", val);
178 else if(!strcmp(type, "spin")) sprintf(q, " %d %d %d\"\n", atoi(val), min, max);
179 else if(!strcmp(type, "check")) sprintf(q, " %d\"\n", strcmp(val, "true") ? 0 : 1), strcat(checkOptions, name);
180 else if(!strcmp(type, "button")) sprintf(q, "\"\n");
181 else if(!strcmp(type, "combo")) {
182 if(p = strstr(line+6, " default ")) sscanf(p+1, "default %s", type); // current setting
183 min = 0; p = line+6;
184 while(p = strstr(p, " var ")) {
185 sscanf(p += 5, "%s", val); // next choice
186 sprintf(buf + strlen(buf), "%s%s%s", min++ ? " /// " : " ", strcmp(type, val) ? "" : "*", val);
188 strcat(q, "\"\n");
190 else buf[0] = 0; // ignore unrecognized option types
191 if(buf[0]) printf("%s", buf);
193 else if(!strcmp(command, "id")) {
194 char name[256];
195 if(sscanf(line, "id name %s", name) == 1) printf("feature myname=\"%s (U%cI2WB)\"\n", name, sc-32);
197 else if(!strcmp(command, "readyok")) pause = 0; // resume processing of GUI commands
198 else if(sscanf(command, "u%ciok", &c)==1 && c==sc) printf("feature smp=1 memory=%d done=1\n", hasHash); // done with options
202 void
203 GUI2Engine()
205 char line[256], command[256], *p, *q;
207 while(1) {
208 int i, x;
210 if(computer == stm || computer == ANALYZE) {
211 printf("# start search\n");
212 LoadPos(moveNr); // load position
213 // and set engine thinking (note USI swaps colors!)
214 startTime = GetTickCount();
215 if(computer == ANALYZE) fprintf(toE, "\ngo infinite\n"), printf("\ngo infinite\n");
216 else StartSearch("");
218 nomove:
219 fflush(toE); fflush(stdout);
220 i = 0; while((x = getchar()) != EOF && (line[i] = x) != '\n') i++;
221 line[++i] = 0; if(x == EOF) { printf("# EOF\n"); exit(-1); }
222 sscanf(line, "%s", command);
223 while(pause) SLEEP(); // wait for readyok
224 if(!strcmp(command, "new")) {
225 computer = BLACK; moveNr = 0; depth = -1;
226 stm = WHITE; strcpy(iniPos, "position startpos");
227 if(memory != oldMem && hasHash) fprintf(toE, "setoption name %s value %d\n", hashOpt, memory);
228 oldMem = memory;
229 // we can set other options here
230 pause = 1; // wait for option settings to take effect
231 fprintf(toE, "isready\n");
232 fprintf(toE, "u%cinewgame\n", sc);
234 else if(!strcmp(command, "usermove")) {
235 if(sc == 's') {
236 // convert input move to USI format
237 if(line[10] == '@') { // drop
238 line[10] = '*';
239 } else {
240 line[9] = 'a'+'0'+size - line[9];
241 line[10] = 'a'+'0'+size - line[10];
243 line[11] = 'a'+'0'+size - line[11];
244 line[12] = 'a'+'0'+size - line[12];
245 if(line[13] == '=') line[13] = 0; // no '=' in USI format!
246 else if(line[13] != '\n') line[13] = '+'; // cater to WB 4.4 bug :-(
248 sscanf(line, "usermove %s", command); // strips off linefeed
249 stm = WHITE+BLACK - stm;
250 // when pondering we either continue the ponder search as normal search, or abort it
251 if(pondering || computer == ANALYZE) {
252 if(pondering && !strcmp(command, move[moveNr])) { // ponder hit
253 pondering = 0; moveNr++; startTime = GetTickCount(); // clock starts running now
254 fprintf(toE, "ponderhit\n"); printf("# ponderhit\n");
255 goto nomove;
257 StopPonder(1);
259 sscanf(line, "usermove %s", move[moveNr++]); // possibly overwrites ponder move
261 else if(!strcmp(command, "level")) {
262 int sec = 0;
263 sscanf(line, "level %d %d:%d %d", &mps, &tc, &sec, &inc) == 4 ||
264 sscanf(line, "level %d %d %d", &mps, &tc, &inc);
265 tc = (60*tc + sec)*1000; inc *= 1000; sTime = 0;
267 else if(!strcmp(command, "option")) {
268 char name[80], *p;
269 if(p = strchr(line, '=')) {
270 *p++ = 0;
271 if(strstr(checkOptions, line+7)) sprintf(p, "%s\n", atoi(p) ? "true" : "false");
272 fprintf(toE, "setoption name %s value %s", line+7, p), printf("# setoption name %s value %s", line+7, p);
273 } else fprintf(toE, "setoption name %s\n", line+7), printf("# setoption name %s\n", line+7);
275 else if(!strcmp(command, "protover")) {
276 printf("feature variants=\"%s\" setboard=1 usermove=1 debug=1 reuse=0 done=0\n", sc=='s' ? "shogi,5x5+5_shogi" : VARIANTS);
277 fprintf(toE, "u%ci\n", sc); // this prompts UCI engine for options
279 else if(!strcmp(command, "setboard")) {
280 if(strstr(line+9, " b ")) stm = BLACK;
281 if(sc == 's' && (p = strchr(line+9, '['))) { char c;
282 *p++ = ' '; q = strchr(p, ']'); c = 'w' + 'b' - q[2]; strcpy(q+2, " 1\n"); while(*--q != ' ') q[2] = *q; *p = c; p[1] = ' ';
284 sprintf(iniPos, "%s%sfen %s", iniPos[0]=='p' ? "position " : "", sc=='s' ? "s" : "", line+9);
285 iniPos[strlen(iniPos)-1] = 0;
287 else if(!strcmp(command, "variant")) {
288 if(!strcmp(line+8, "shogi\n")) size = 9, strcpy(iniPos, "position startpos");
289 if(!strcmp(line+8, "5x5+5_shogi\n")) size = 5, strcpy(iniPos, "position startpos");
290 if(!strcmp(line+8, "xiangqi\n")) strcpy(iniPos, "fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR r");
292 else if(!strcmp(command, "undo") && (i=1) || !strcmp(command, "remove") && (i=2)) {
293 if(pondering || computer == ANALYZE) StopPonder(1);
294 moveNr = moveNr > i ? moveNr - i : 0;
296 else if(!strcmp(command, ".")) {
297 printf("stat01: %d %d %d %d 100 %s\n", statTime, statNodes, statDepth, 100-currNr, currMove);
298 goto nomove;
300 else if(!strcmp(command, "xboard")) ;
301 else if(!strcmp(command, "analyze"))computer = ANALYZE;
302 else if(!strcmp(command, "exit")) computer = NONE, StopPonder(1);
303 else if(!strcmp(command, "force")) computer = NONE, StopPonder(pondering);
304 else if(!strcmp(command, "go")) computer = stm;
305 else if(!strcmp(command, "time")) sscanf(line+4, "%d", &myTime), myTime *= 10;
306 else if(!strcmp(command, "otim")) sscanf(line+4, "%d", &hisTime), hisTime *= 10;
307 else if(!strcmp(command, "post")) post = 1;
308 else if(!strcmp(command, "nopost")) post = 0;
309 else if(!strcmp(command, "easy")) ponder = 0;
310 else if(!strcmp(command, "hard")) ponder = 1;
311 else if(!strcmp(command, "memory")) sscanf(line, "memory %d", &memory);
312 else if(!strcmp(command, "cores")) sscanf(line, "cores %d", &cores);
313 else if(!strcmp(command, "sd")) sscanf(line, "sd %d", &depth);
314 else if(!strcmp(command, "st")) sscanf(line, "st %d", &sTime), sTime *= 1000, inc = 0;
315 else if(!strcmp(command, "quit")) fprintf(toE, "quit\n"), fflush(toE), exit(0);
320 StartEngine(char *cmdLine, char *dir)
322 #ifdef WIN32
323 HANDLE hChildStdinRd, hChildStdinWr,
324 hChildStdoutRd, hChildStdoutWr;
325 SECURITY_ATTRIBUTES saAttr;
326 BOOL fSuccess;
327 PROCESS_INFORMATION piProcInfo;
328 STARTUPINFO siStartInfo;
329 DWORD err;
331 /* Set the bInheritHandle flag so pipe handles are inherited. */
332 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
333 saAttr.bInheritHandle = TRUE;
334 saAttr.lpSecurityDescriptor = NULL;
336 /* Create a pipe for the child's STDOUT. */
337 if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) return GetLastError();
339 /* Create a pipe for the child's STDIN. */
340 if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) return GetLastError();
342 SetCurrentDirectory(dir); // go to engine directory
344 /* Now create the child process. */
345 siStartInfo.cb = sizeof(STARTUPINFO);
346 siStartInfo.lpReserved = NULL;
347 siStartInfo.lpDesktop = NULL;
348 siStartInfo.lpTitle = NULL;
349 siStartInfo.dwFlags = STARTF_USESTDHANDLES;
350 siStartInfo.cbReserved2 = 0;
351 siStartInfo.lpReserved2 = NULL;
352 siStartInfo.hStdInput = hChildStdinRd;
353 siStartInfo.hStdOutput = hChildStdoutWr;
354 siStartInfo.hStdError = hChildStdoutWr;
356 fSuccess = CreateProcess(NULL,
357 cmdLine, /* command line */
358 NULL, /* process security attributes */
359 NULL, /* primary thread security attrs */
360 TRUE, /* handles are inherited */
361 DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP,
362 NULL, /* use parent's environment */
363 NULL,
364 &siStartInfo, /* STARTUPINFO pointer */
365 &piProcInfo); /* receives PROCESS_INFORMATION */
367 if (! fSuccess) return GetLastError();
369 // if (0) { // in the future we could trigger this by an argument
370 // SetPriorityClass(piProcInfo.hProcess, GetWin32Priority(appData.niceEngines));
371 // }
373 /* Close the handles we don't need in the parent */
374 CloseHandle(piProcInfo.hThread);
375 CloseHandle(hChildStdinRd);
376 CloseHandle(hChildStdoutWr);
378 process = piProcInfo.hProcess;
379 pid = piProcInfo.dwProcessId;
380 fromE = (FILE*) _fdopen( _open_osfhandle((long)hChildStdoutRd, _O_TEXT|_O_RDONLY), "r");
381 toE = (FILE*) _fdopen( _open_osfhandle((long)hChildStdinWr, _O_WRONLY), "w");
382 #else
383 char *argv[10], *p, buf[200];
384 int i, toEngine[2], fromEngine[2];
386 if (dir && dir[0] && chdir(dir)) { perror(dir); exit(1); }
387 pipe(toEngine); pipe(fromEngine); // create two pipes
389 if ((pid = fork()) == 0) { // Child
390 dup2(toEngine[0], 0); close(toEngine[0]); close(toEngine[1]); // stdin from toE pipe
391 dup2(fromEngine[1], 1); close(fromEngine[0]); close(fromEngine[1]); // stdout into fromE pipe
392 dup2(1, fileno(stderr)); // stderr into frome pipe
394 strcpy(buf, cmdLine); p = buf;
395 for (i=0;;) { argv[i++] = p; p = strchr(p, ' '); if (p == NULL) break; *p++ = 0; }
396 argv[i] = NULL;
397 execvp(argv[0], argv); // startup engine
399 perror(argv[0]); exit(1); // could not start engine; quit.
401 signal(SIGPIPE, SIG_IGN);
402 close(toEngine[0]); close(fromEngine[1]); // close engine ends of pipes in adapter
404 fromE = (FILE*) fdopen(fromEngine[0], "r"); // make into high-level I/O
405 toE = (FILE*) fdopen(toEngine[1], "w");
406 #endif
407 return NO_ERROR;
410 main(int argc, char **argv)
412 char *dir = NULL, *p, *q; int e;
414 if(argc == 2 && !strcmp(argv[1], "-v")) { printf("UCI2WB " VERSION " by H.G.Muller\n"); exit(0); }
415 if(argc > 1 && argv[1][0] == '-') { sc = argv[1][1]; argc--; argv++; }
416 if(argc < 2) { printf("usage is: U%cI2WB [-s] <engine.exe> [<engine directory>]\n", sc-32); exit(-1); }
417 if(argc > 2) dir = argv[2];
418 if(argc > 3) suffix = argv[3];
420 // spawn engine proc
421 if(StartEngine(argv[1], dir) != NO_ERROR) { perror(argv[1]), exit(-1); }
423 // create separate thread to handle engine->GUI traffic
424 #ifdef WIN32
425 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Engine2GUI, (LPVOID) NULL, 0, &thread_id);
426 #else
427 { pthread_t t; signal(SIGINT, SIG_IGN); pthread_create(&t, NULL, Engine2GUI, NULL); }
428 #endif
430 // handle GUI->engine traffic in original thread
431 GUI2Engine();