Merge commit 'v4.3.16'
[xboard.git] / zippy.c
1 /*\r
2  * zippy.c -- Implements Zippy the Pinhead chess player on ICS in XBoard\r
3  * $Id: zippy.c,v 2.2 2003/11/25 05:25:20 mann Exp $\r
4  *\r
5  * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.\r
6  * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.\r
7  *\r
8  * The following terms apply to Digital Equipment Corporation's copyright\r
9  * interest in XBoard:\r
10  * ------------------------------------------------------------------------\r
11  * All Rights Reserved\r
12  *\r
13  * Permission to use, copy, modify, and distribute this software and its\r
14  * documentation for any purpose and without fee is hereby granted,\r
15  * provided that the above copyright notice appear in all copies and that\r
16  * both that copyright notice and this permission notice appear in\r
17  * supporting documentation, and that the name of Digital not be\r
18  * used in advertising or publicity pertaining to distribution of the\r
19  * software without specific, written prior permission.\r
20  *\r
21  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
22  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
23  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
24  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
25  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
26  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
27  * SOFTWARE.\r
28  * ------------------------------------------------------------------------\r
29  *\r
30  * The following terms apply to the enhanced version of XBoard distributed\r
31  * by the Free Software Foundation:\r
32  * ------------------------------------------------------------------------\r
33  * This program is free software; you can redistribute it and/or modify\r
34  * it under the terms of the GNU General Public License as published by\r
35  * the Free Software Foundation; either version 2 of the License, or\r
36  * (at your option) any later version.\r
37  *\r
38  * This program is distributed in the hope that it will be useful,\r
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
41  * GNU General Public License for more details.\r
42  *\r
43  * You should have received a copy of the GNU General Public License\r
44  * along with this program; if not, write to the Free Software\r
45  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\r
46  * ------------------------------------------------------------------------\r
47  */\r
48 \r
49 #include "config.h"\r
50 \r
51 #include <stdio.h>\r
52 #include <errno.h>\r
53 #include <sys/types.h>\r
54 #include <sys/stat.h>\r
55 #include <ctype.h>\r
56 \r
57 #if STDC_HEADERS\r
58 # include <stdlib.h>\r
59 # include <string.h>\r
60 #else /* not STDC_HEADERS */\r
61 extern char *getenv();\r
62 # if HAVE_STRING_H\r
63 #  include <string.h>\r
64 # else /* not HAVE_STRING_H */\r
65 #  include <strings.h>\r
66 # endif /* not HAVE_STRING_H */\r
67 #endif /* not STDC_HEADERS */\r
68 \r
69 #if TIME_WITH_SYS_TIME\r
70 # include <sys/time.h>\r
71 # include <time.h>\r
72 #else\r
73 # if HAVE_SYS_TIME_H\r
74 #  include <sys/time.h>\r
75 # else\r
76 #  include <time.h>\r
77 # endif\r
78 #endif\r
79 #define HI "hlelo "\r
80 \r
81 #if HAVE_UNISTD_H\r
82 # include <unistd.h>\r
83 #endif\r
84 \r
85 #include "common.h"\r
86 #include "zippy.h"\r
87 #include "frontend.h"\r
88 #include "backend.h"\r
89 #include "backendz.h"\r
90 \r
91 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
92 \r
93 static char zippyPartner[MSG_SIZ];\r
94 static char zippyLastOpp[MSG_SIZ];\r
95 static int zippyConsecGames;\r
96 static time_t zippyLastGameEnd;\r
97 \r
98 extern void mysrandom(unsigned int seed);\r
99 extern int myrandom(void);\r
100 \r
101 void ZippyInit()\r
102 {\r
103     char *p;\r
104 \r
105     /* Get name of Zippy lines file */\r
106     p = getenv("ZIPPYLINES");\r
107     if (p != NULL) {\r
108       appData.zippyLines = p;\r
109     }\r
110 \r
111     /* Get word that Zippy thinks is insulting */\r
112     p = getenv("ZIPPYPINHEAD");\r
113     if (p != NULL) {\r
114       appData.zippyPinhead = p;\r
115     }\r
116 \r
117     /* What password is used for remote control? */\r
118     p = getenv("ZIPPYPASSWORD");\r
119     if (p != NULL) {\r
120       appData.zippyPassword = p;\r
121     }\r
122 \r
123     /* What password is used for remote commands to gnuchess? */\r
124     p = getenv("ZIPPYPASSWORD2");\r
125     if (p != NULL) {\r
126       appData.zippyPassword2 = p;\r
127     }\r
128 \r
129     /* Joke feature for people who try an old password */\r
130     p = getenv("ZIPPYWRONGPASSWORD");\r
131     if (p != NULL) {\r
132       appData.zippyWrongPassword = p;\r
133     }\r
134 \r
135     /* While testing, I want to accept challenges from only one person\r
136        (namely, my "anonymous" account), so I set an environment\r
137        variable ZIPPYACCEPTONLY. */\r
138     p = getenv("ZIPPYACCEPTONLY");\r
139     if ( p != NULL ) {\r
140       appData.zippyAcceptOnly = p;\r
141     }\r
142     \r
143     /* Should Zippy use "i" command? */\r
144     /* Defaults to 1=true */\r
145     p = getenv("ZIPPYUSEI");\r
146     if (p != NULL) {\r
147       appData.zippyUseI = atoi(p);\r
148     }\r
149 \r
150     /* How does Zippy handle bughouse partnering? */\r
151     /* 0=say we can't play, 1=manual partnering, 2=auto partnering */\r
152     p = getenv("ZIPPYBUGHOUSE");\r
153     if (p != NULL) {\r
154       appData.zippyBughouse = atoi(p);\r
155     }\r
156 \r
157     /* Does Zippy abort games with Crafty? */\r
158     /* Defaults to 0=false */\r
159     p = getenv("ZIPPYNOPLAYCRAFTY");\r
160     if (p != NULL) {\r
161       appData.zippyNoplayCrafty = atoi(p);\r
162     }\r
163 \r
164     /* What ICS command does Zippy send at game end?  Default: "gameend". */\r
165     p = getenv("ZIPPYGAMEEND");\r
166     if (p != NULL) {\r
167       appData.zippyGameEnd = p;\r
168     }\r
169 \r
170     /* What ICS command does Zippy send at game start?  Default: none. */\r
171     p = getenv("ZIPPYGAMESTART");\r
172     if (p != NULL) {\r
173       appData.zippyGameStart = p;\r
174     }\r
175 \r
176     /* Should Zippy accept adjourns? */\r
177     /* Defaults to 0=false */\r
178     p = getenv("ZIPPYADJOURN");\r
179     if (p != NULL) {\r
180       appData.zippyAdjourn = atoi(p);\r
181     }\r
182 \r
183     /* Should Zippy accept aborts? */\r
184     /* Defaults to 0=false */\r
185     p = getenv("ZIPPYABORT");\r
186     if (p != NULL) {\r
187       appData.zippyAbort = atoi(p);\r
188     }\r
189 \r
190     /* Should Zippy play chess variants (besides bughouse)? */\r
191     p = getenv("ZIPPYVARIANTS");\r
192     if (p != NULL) {\r
193       appData.zippyVariants = p;\r
194     }\r
195     strcpy(first.variants, appData.zippyVariants);\r
196 \r
197     srandom(time(NULL));\r
198 }\r
199 \r
200 /*\r
201  * Routines to implement Zippy talking\r
202  */\r
203 \r
204 \r
205 char *swifties[] = { \r
206     "i acclaims:", "i admonishes:", "i advertises:", "i advises:",\r
207     "i advocates:", "i affirms:", "i alleges:", "i anathematizes:",\r
208     "i animadverts:", "i announces:", "i apostrophizes:",\r
209     "i appeals:", "i applauds:", "i approves:", "i argues:",\r
210     "i articulates:", "i asserts:", "i asseverates:", "i attests:",\r
211     "i avers:", "i avows:", "i baas:", "i babbles:", "i banters:",\r
212     "i barks:", "i bawls:", "i bays:", "i begs:", "i belches:",\r
213     "i bellows:", "i belts out:", "i berates:", "i beshrews:",\r
214     "i blabbers:", "i blabs:", "i blares:", "i blasphemes:",\r
215     "i blasts:", "i blathers:", "i bleats:", "i blithers:",\r
216     "i blubbers:", "i blurts out:", "i blusters:", "i boasts:",\r
217     "i brags:", "i brays:", "i broadcasts:", "i burbles:",\r
218     "i buzzes:", "i cachinnates:", "i cackles:", "i caterwauls:",\r
219     "i calumniates:", "i caws:", "i censures:", "i chants:",\r
220     "i chatters:", "i cheeps:", "i cheers:", "i chides:", "i chins:",\r
221     "i chirps:", "i chortles:", "i chuckles:", "i claims:",\r
222     "i clamors:", "i clucks:", "i commands:", "i commends:",\r
223     "i comments:", "i commiserates:", "i communicates:",\r
224     "i complains:", "i concludes:", "i confabulates:", "i confesses:",\r
225     "i coos:", "i coughs:", "i counsels:", "i cries:", "i croaks:",\r
226     "i crows:", "i curses:", "i daydreams:", "i debates:",\r
227     "i declaims:", "i declares:", "i delivers:", "i denounces:",\r
228     "i deposes:", "i directs:", "i discloses:", "i disparages:",\r
229     "i discourses:", "i divulges:", "i documents:", "i drawls:",\r
230     "i dreams:", "i drivels:", "i drones:", "i effuses:",\r
231     /*"i ejaculates:",*/ "i elucidates:", "i emotes:", "i endorses:",\r
232     "i enthuses:", "i entreats:", "i enunciates:", "i eulogizes:",\r
233     "i exclaims:", "i execrates:", "i exhorts:", "i expatiates:",\r
234     "i explains:", "i explicates:", "i explodes:", "i exposes:",\r
235     "i exposits:", "i expostulates: ",\r
236     "i expounds:", "i expresses:", "i extols:",\r
237     "i exults:", "i fantasizes:", "i fibs:", "i filibusters:",\r
238     "i flatters:", "i flutes:", "i fools:", "i free-associates:",\r
239     "i fulminates:", "i gabbles:", "i gabs:", "i gasps:",\r
240     "i giggles:", "i gossips:", "i gripes:", "i groans:", "i growls:",\r
241     "i grunts:", "i guesses:", "i guffaws:", "i gushes:", "i hails:",\r
242     "i hallucinates:", "i harangues:", "i harmonizes:", "i hectors:",\r
243     "i hints:", "i hisses:", "i hollers:", "i honks:", "i hoots:",\r
244     "i hosannas:", "i howls:", "i hums:", "i hypothecates:",\r
245     "i hypothesizes:", "i imagines:", "i implies:", "i implores:",\r
246     "i imprecates:", "i indicates:", "i infers:",\r
247     "i informs everyone:",  "i instructs:", "i interjects:", \r
248     "i interposes:", "i intimates:", "i intones:", "i introspects:",\r
249     "i inveighs:", "i jabbers:", "i japes:", "i jests:", "i jibes:",\r
250     "i jives:", "i jokes:", "i joshes:", "i keens:", "i laments:",\r
251     "i lauds:", "i laughs:", "i lectures:", "i lies:", "i lilts:",\r
252     "i lisps:", "i maintains:", "i maledicts:", "i maunders:",\r
253     "i meows:", "i mewls:", "i mimes:", "i minces:", "i moans:",\r
254     "i moos:", "i mourns:", "i mouths:", "i mumbles:", "i murmurs:",\r
255     "i muses:", "i mutters:", "i nags:", "i natters:", "i neighs:",\r
256     "i notes:", "i nuncupates:", "i objurgates:", "i observes:",\r
257     "i offers:", "i oinks:", "i opines:", "i orates:", "i orders:",\r
258     "i panegyrizes:", "i pantomimes:", "i pants:", "i peals:",\r
259     "i peeps:", "i perorates:", "i persuades:", "i petitions:",\r
260     "i phonates:", "i pipes up:", "i pitches:", "i pleads:",\r
261     "i points out:", "i pontificates:", "i postulates:", "i praises:",\r
262     "i prates:", "i prattles:", "i preaches:", "i prescribes:",\r
263     "i prevaricates:", "i proclaims:", "i projects:", "i pronounces:",\r
264     "i proposes:", "i proscribes:", "i quacks:", "i queries:",\r
265     "i questions:", "i quips:", "i quotes:", "i rages:", "i rambles:",\r
266     "i rants:", "i raps:", "i rasps:", "i rattles:", "i raves:",\r
267     "i reacts:", "i recites:", "i recommends:", "i records:",\r
268     "i reiterates:", "i rejoins:", "i releases:", "i remarks:",\r
269     "i reminisces:", "i remonstrates:", "i repeats:", "i replies:",\r
270     "i reports:", "i reprimands:", "i reproaches:", "i reproves:",\r
271     "i resounds:", "i responds:", "i retorts:", "i reveals:",\r
272     "i reviles:", "i roars:", "i rumbles:", "i sanctions:",\r
273     "i satirizes:", "i sauces:", "i scolds:", "i screams:",\r
274     "i screeches:", "i semaphores:", "i sends:", "i sermonizes:",\r
275     "i shrieks:", "i sibilates:", "i sighs:", "i signals:",\r
276     "i signifies:", "i signs:", "i sings:", "i slurs:", "i snaps:",\r
277     "i snarls:", "i sneezes:", "i snickers:", "i sniggers:",\r
278     "i snivels:", "i snores:", "i snorts:", "i sobs:",\r
279     "i soliloquizes:", "i sounds off:", "i sounds out:", "i speaks:",\r
280     "i spews:", "i spits out:", "i splutters:", "i spoofs:",\r
281     "i spouts:", "i sputters:", "i squalls:", "i squawks:",\r
282     "i squeaks:", "i squeals:", "i stammers:", "i states:",\r
283     "i stresses:", "i stutters:", "i submits:", "i suggests:",\r
284     "i summarizes:", "i sums up:", "i swears:", "i talks:",\r
285     "i tattles:", "i teases:", "i telegraphs:", "i testifies:",\r
286     "i threatens:", "i thunders:", "i titters:", "i tongue-lashes:",\r
287     "i toots:", "i transcribes:", "i transmits:", "i trills:",\r
288     "i trumpets:", "i twaddles:", "i tweets:", "i twitters:",\r
289     "i types:", "i upbraids:", "i urges:", "i utters:", "i ventures:",\r
290     "i vibrates:", "i vilifies:", "i vituperates:", "i vocalizes:",\r
291     "i vociferates:", "i voices:", "i waffles:", "i wails:",\r
292     "i warbles:", "i warns:", "i weeps:", "i wheezes:", "i whimpers:",\r
293     "i whines:", "i whinnies:", "i whistles:", "i wisecracks:",\r
294     "i witnesses:", "i woofs:", "i writes:", "i yammers:", "i yawps:",\r
295     "i yells:", "i yelps:", "i yodels:", "i yowls:", "i zings:",\r
296 };\r
297 \r
298 #define MAX_SPEECH 250\r
299 \r
300 void Speak(how, whom) \r
301      char *how, *whom;\r
302 {\r
303     static FILE *zipfile = NULL;\r
304     static struct stat zipstat;\r
305     char zipbuf[MAX_SPEECH + 1];\r
306     static time_t lastShout = 0;\r
307     time_t now;\r
308     char  *p;\r
309     int c, speechlen;\r
310     Boolean done;\r
311                 \r
312     if (strcmp(how, "shout") == 0) {\r
313         now = time((time_t *) NULL);\r
314         if (now - lastShout < 1*60) return;\r
315         lastShout = now;\r
316         if (appData.zippyUseI) {\r
317             how = swifties[(unsigned) random() %\r
318                            (sizeof(swifties)/sizeof(char *))];\r
319         }\r
320     }\r
321 \r
322     if (zipfile == NULL) {\r
323         zipfile = fopen(appData.zippyLines, "r");\r
324         if (zipfile == NULL) {\r
325             DisplayFatalError("Can't open Zippy lines file", errno, 1);\r
326             return;\r
327         }\r
328         fstat(fileno(zipfile), &zipstat);\r
329     }\r
330                 \r
331     for (;;) {\r
332         fseek(zipfile, (unsigned) random() % zipstat.st_size, 0);\r
333         do {\r
334           c = getc(zipfile);\r
335         } while (c != NULLCHAR && c != '^' && c != EOF);\r
336         if (c == EOF) continue;\r
337         while ((c = getc(zipfile)) == '\n') ;\r
338         if (c == EOF) continue;\r
339         break;\r
340     }\r
341     done = FALSE;\r
342 \r
343     /* Don't use ics_prefix; we need to let FICS expand the alias i -> it,\r
344        but use the real command "i" on ICC */\r
345     strcpy(zipbuf, how);\r
346     strcat(zipbuf, " ");\r
347     if (whom != NULL) {\r
348         strcat(zipbuf, whom);\r
349         strcat(zipbuf, " ");\r
350     }\r
351     speechlen = strlen(zipbuf);\r
352     p = zipbuf + speechlen;\r
353 \r
354     while (++speechlen < MAX_SPEECH) {\r
355         if (c == NULLCHAR || c == '^') {\r
356             *p++ = '\n';\r
357             *p = '\0';\r
358             SendToICS(zipbuf);\r
359             return;\r
360         } else if (c == '\n') {\r
361             *p++ = ' ';\r
362             do {\r
363                 c = getc(zipfile);\r
364             } while (c == ' ');\r
365         } else if (c == EOF) {\r
366             break;\r
367         } else {\r
368             *p++ = c;\r
369             c = getc(zipfile);\r
370         }\r
371     }\r
372     /* Tried to say something too long, or junk at the end of the\r
373        file.  Try something else. */\r
374     Speak(how, whom);  /* tail recursion */\r
375 }\r
376 \r
377 int ZippyCalled(str)\r
378      char *str;\r
379 {\r
380     return ics_handle[0] != NULLCHAR && StrCaseStr(str, ics_handle) != NULL;\r
381 }\r
382 \r
383 static char opp_name[128][32];\r
384 static int num_opps=0;\r
385 \r
386 extern ColorClass curColor;\r
387 \r
388 static void SetCurColor( ColorClass color )\r
389 {\r
390     curColor = color;\r
391 }\r
392 \r
393 static void ColorizeEx( ColorClass color, int cont )\r
394 {\r
395     if( appData.colorize ) {\r
396         Colorize( color, cont );\r
397         SetCurColor( color );\r
398     }\r
399 }\r
400 \r
401 int ZippyControl(buf, i)\r
402      char *buf;\r
403      int *i;\r
404 {\r
405     char *player, *p;\r
406     char reply[MSG_SIZ];\r
407 \r
408 #if TRIVIA\r
409 #include "trivia.c"\r
410 #endif\r
411 \r
412     /* Possibly reject Crafty as opponent */\r
413     if (appData.zippyPlay && appData.zippyNoplayCrafty && forwardMostMove < 4\r
414         && looking_at(buf, i, "* kibitzes: Hello from Crafty")) \r
415     {\r
416         player = StripHighlightAndTitle(star_match[0]);\r
417         if ((gameMode == IcsPlayingWhite &&\r
418              StrCaseCmp(player, gameInfo.black) == 0) ||\r
419             (gameMode == IcsPlayingBlack &&\r
420              StrCaseCmp(player, gameInfo.white) == 0)) {\r
421 \r
422           sprintf(reply, "%ssay This computer does not play Crafty clones\n%sabort\n%s+noplay %s\n",\r
423                   ics_prefix, ics_prefix, ics_prefix, player);\r
424           SendToICS(reply);\r
425         }\r
426         return TRUE;\r
427     }\r
428 \r
429     /* If this is a computer, save the name.  Then later, once the */\r
430     /* game is really started, we will send the "computer" notice to */\r
431     /* the engine.  */ \r
432     if (appData.zippyPlay &&\r
433         looking_at(buf, i, "* is in the computer list")) {\r
434         int i;\r
435         for (i=0;i<num_opps;i++)\r
436           if (!strcmp(opp_name[i],star_match[0])) break;\r
437         if (i >= num_opps) strcpy(opp_name[num_opps++],star_match[0]);\r
438     }\r
439     if (appData.zippyPlay && looking_at(buf, i, "* * is a computer *")) {\r
440         int i;\r
441         for (i=0;i<num_opps;i++)\r
442           if (!strcmp(opp_name[i],star_match[1])) break;\r
443         if (i >= num_opps) strcpy(opp_name[num_opps++],star_match[1]);\r
444     }\r
445 \r
446     /* Tells and says */\r
447     if (appData.zippyPlay && \r
448         (looking_at(buf, i, "* offers to be your bughouse partner") ||\r
449          looking_at(buf, i, "* tells you: [automatic message] I chose you"))) {\r
450         player = StripHighlightAndTitle(star_match[0]);\r
451         if (appData.zippyBughouse > 1 && first.initDone) {\r
452             sprintf(reply, "%spartner %s\n", ics_prefix, player);\r
453             SendToICS(reply);\r
454             if (strcmp(zippyPartner, player) != 0) {\r
455                 strcpy(zippyPartner, player);\r
456                 SendToProgram(reply + strlen(ics_prefix), &first);\r
457             }\r
458         } else if (appData.zippyBughouse > 0) {\r
459             sprintf(reply, "%sdecline %s\n", ics_prefix, player);\r
460             SendToICS(reply);\r
461         } else {\r
462             sprintf(reply, "%stell %s This computer cannot play bughouse\n",\r
463                     ics_prefix, player);\r
464             SendToICS(reply);\r
465         }\r
466         return TRUE;\r
467     }\r
468 \r
469     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&\r
470         looking_at(buf, i, "* agrees to be your partner")) {\r
471         player = StripHighlightAndTitle(star_match[0]);\r
472         sprintf(reply, "partner %s\n", player);\r
473         if (strcmp(zippyPartner, player) != 0) {\r
474             strcpy(zippyPartner, player);\r
475             SendToProgram(reply, &first);\r
476         }\r
477         return TRUE;\r
478     }\r
479 \r
480     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&\r
481         (looking_at(buf, i, "are no longer *'s partner") ||\r
482          looking_at(buf, i,\r
483                     "* tells you: [automatic message] I'm no longer your"))) {\r
484         player = StripHighlightAndTitle(star_match[0]);\r
485         if (strcmp(zippyPartner, player) == 0) {\r
486             zippyPartner[0] = NULLCHAR;\r
487             SendToProgram("partner\n", &first);\r
488         }\r
489         return TRUE;\r
490     }\r
491 \r
492     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&\r
493         (looking_at(buf, i, "no longer have a bughouse partner") ||\r
494          looking_at(buf, i, "partner has disconnected") ||\r
495          looking_at(buf, i, "partner has just chosen a new partner"))) {\r
496       zippyPartner[0] = NULLCHAR;\r
497       SendToProgram("partner\n", &first);\r
498       return TRUE;\r
499     }\r
500 \r
501     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&\r
502         looking_at(buf, i, "* (your partner) tells you: *")) {\r
503         /* This pattern works on FICS but not ICC */\r
504         player = StripHighlightAndTitle(star_match[0]);\r
505         if (strcmp(zippyPartner, player) != 0) {\r
506             strcpy(zippyPartner, player);\r
507             sprintf(reply, "partner %s\n", player);\r
508             SendToProgram(reply, &first);\r
509         }\r
510         sprintf(reply, "ptell %s\n", star_match[1]);\r
511         SendToProgram(reply, &first);\r
512         return TRUE;\r
513     }\r
514 \r
515     if (looking_at(buf, i, "* tells you: *") ||\r
516         looking_at(buf, i, "* says: *")) \r
517     {\r
518         player = StripHighlightAndTitle(star_match[0]);\r
519         if (appData.zippyPassword[0] != NULLCHAR &&\r
520             strncmp(star_match[1], appData.zippyPassword,\r
521                     strlen(appData.zippyPassword)) == 0) {\r
522             p = star_match[1] + strlen(appData.zippyPassword);\r
523             while (*p == ' ') p++;\r
524             SendToICS(p);\r
525             SendToICS("\n");\r
526         } else if (appData.zippyPassword2[0] != NULLCHAR && first.initDone &&\r
527             strncmp(star_match[1], appData.zippyPassword2,\r
528                     strlen(appData.zippyPassword2)) == 0) {\r
529             p = star_match[1] + strlen(appData.zippyPassword2);\r
530             while (*p == ' ') p++;\r
531             SendToProgram(p, &first);\r
532             SendToProgram("\n", &first);\r
533         } else if (appData.zippyWrongPassword[0] != NULLCHAR &&\r
534             strncmp(star_match[1], appData.zippyWrongPassword,\r
535                     strlen(appData.zippyWrongPassword)) == 0) {\r
536             p = star_match[1] + strlen(appData.zippyWrongPassword);\r
537             while (*p == ' ') p++;\r
538             sprintf(reply, "wrong %s\n", player);\r
539             SendToICS(reply);\r
540         } else if (appData.zippyBughouse && first.initDone &&\r
541                    strcmp(player, zippyPartner) == 0) {\r
542             SendToProgram("ptell ", &first);\r
543             SendToProgram(star_match[1], &first);\r
544             SendToProgram("\n", &first);\r
545         } else if (strncmp(star_match[1], HI, 6) == 0) {\r
546             extern char* programVersion;\r
547             sprintf(reply, "%stell %s %s\n",\r
548                     ics_prefix, player, programVersion);\r
549             SendToICS(reply);\r
550         } else if (strncmp(star_match[1], "W0W!! ", 6) == 0) {\r
551             extern char* programVersion;\r
552             sprintf(reply, "%stell %s %s\n", ics_prefix,\r
553                     player, programVersion);\r
554             SendToICS(reply);\r
555         } else if (appData.zippyTalk && (((unsigned) random() % 10) < 9)) {\r
556             if (strcmp(player, ics_handle) != 0) {\r
557                 Speak("tell", player);\r
558             }\r
559         }\r
560 \r
561         ColorizeEx( ColorTell, FALSE );\r
562 \r
563         return TRUE;\r
564     }\r
565 \r
566     if( appData.colorize && looking_at(buf, i, "* (*) seeking") ) {\r
567         ColorizeEx(ColorSeek, FALSE);\r
568         return FALSE;\r
569     }\r
570 \r
571     if (looking_at(buf, i, "* spoofs you:")) {\r
572         player = StripHighlightAndTitle(star_match[0]);\r
573         sprintf(reply, "spoofedby %s\n", player);\r
574         SendToICS(reply);\r
575     }\r
576 \r
577     return FALSE;\r
578 }\r
579 \r
580 int ZippyConverse(buf, i)\r
581      char *buf;\r
582      int *i;\r
583 {\r
584     static char lastgreet[MSG_SIZ];\r
585     char reply[MSG_SIZ];\r
586     int oldi;\r
587 \r
588     /* Shouts and emotes */\r
589     if (looking_at(buf, i, "--> * *") ||\r
590         looking_at(buf, i, "* shouts: *")) \r
591     {\r
592       if (appData.zippyTalk) {\r
593         char *player = StripHighlightAndTitle(star_match[0]);\r
594         if (strcmp(player, ics_handle) == 0) {\r
595             return TRUE;\r
596         } else if (appData.zippyPinhead[0] != NULLCHAR &&\r
597                    StrCaseStr(star_match[1], appData.zippyPinhead) != NULL) {\r
598             sprintf(reply, "insult %s\n", player);\r
599             SendToICS(reply);\r
600         } else if (ZippyCalled(star_match[1])) {\r
601             Speak("shout", NULL);\r
602         }\r
603       }\r
604 \r
605       ColorizeEx(ColorShout, FALSE);\r
606 \r
607       return TRUE;\r
608     }\r
609 \r
610     if (looking_at(buf, i, "* kibitzes: *")) {\r
611       if (appData.zippyTalk && ((unsigned) random() % 10) < 9) {\r
612         char *player = StripHighlightAndTitle(star_match[0]);\r
613         if (strcmp(player, ics_handle) != 0) {\r
614             Speak("kibitz", NULL);\r
615         }\r
616       }\r
617 \r
618       ColorizeEx(ColorKibitz, FALSE);\r
619 \r
620       return TRUE;\r
621     }\r
622 \r
623     if (looking_at(buf, i, "* whispers: *")) {\r
624       if (appData.zippyTalk && ((unsigned) random() % 10) < 9) {\r
625         char *player = StripHighlightAndTitle(star_match[0]);\r
626         if (strcmp(player, ics_handle) != 0) {\r
627             Speak("whisper", NULL);\r
628         }\r
629       }\r
630 \r
631       ColorizeEx(ColorKibitz, FALSE);\r
632 \r
633       return TRUE;\r
634     }\r
635 \r
636     /* Messages */\r
637     if ((looking_at(buf, i, ". * (*:*): *") && isdigit(star_match[1][0])) ||\r
638          looking_at(buf, i, ". * at *:*: *")) {\r
639       if (appData.zippyTalk) {\r
640         FILE *f;\r
641         char *player = StripHighlightAndTitle(star_match[0]);\r
642 \r
643         if (strcmp(player, ics_handle) != 0) {\r
644             if (((unsigned) random() % 10) < 9)\r
645               Speak("message", player);\r
646             f = fopen("zippy.messagelog", "a");\r
647             fprintf(f, "%s (%s:%s): %s\n", player,\r
648                     star_match[1], star_match[2], star_match[3]);\r
649             fclose(f);\r
650         }\r
651       }\r
652       return TRUE;\r
653     }\r
654 \r
655     /* Channel tells */\r
656     oldi = *i;\r
657     if (looking_at(buf, i, "*(*: *")) {\r
658         char *player;\r
659         char *channel;\r
660         if (star_match[0][0] == NULLCHAR  ||\r
661             strchr(star_match[0], ' ') ||\r
662             strchr(star_match[1], ' ')) {\r
663             /* Oops, did not want to match this; probably a message */\r
664             *i = oldi;\r
665             return FALSE;\r
666         }\r
667         if (appData.zippyTalk) {\r
668           player = StripHighlightAndTitle(star_match[0]);\r
669           channel = strrchr(star_match[1], '(');\r
670           if (channel == NULL) {\r
671             channel = star_match[1];\r
672           } else {\r
673             channel++;\r
674           }\r
675           channel[strlen(channel)-1] = NULLCHAR;\r
676 #if 0\r
677           /* Always tell to the channel (probability 90%) */\r
678           if (strcmp(player, ics_handle) != 0 &&\r
679               ((unsigned) random() % 10) < 9) {\r
680             Speak("tell", channel);\r
681           }\r
682 #else\r
683           /* Tell to the channel only if someone mentions our name */\r
684           if (ZippyCalled(star_match[2])) {\r
685             Speak("tell", channel);\r
686           }\r
687 #endif\r
688 \r
689           ColorizeEx( atoi(channel) == 1 ? ColorChannel1 : ColorChannel, FALSE );\r
690         }\r
691         return TRUE;\r
692     }\r
693 \r
694     if (!appData.zippyTalk) return FALSE;\r
695 \r
696     if ((looking_at(buf, i, "You have * message") &&\r
697          atoi(star_match[0]) != 0) ||\r
698         looking_at(buf, i, "* has left a message for you") ||\r
699         looking_at(buf, i, "* just sent you a message")) {\r
700         sprintf(reply, "%smessages\n%sclearmessages *\n",\r
701                 ics_prefix, ics_prefix);\r
702         SendToICS(reply);\r
703         return TRUE;\r
704     }\r
705 \r
706     if (looking_at(buf, i, "Notification: * has arrived")) {\r
707         if (((unsigned) random() % 3) == 0) {\r
708             char *player = StripHighlightAndTitle(star_match[0]);\r
709             strcpy(lastgreet, player);\r
710             sprintf(reply, "greet %s\n", player);\r
711             SendToICS(reply);\r
712             Speak("tell", player);\r
713         }\r
714     }   \r
715 \r
716     if (looking_at(buf, i, "Notification: * has departed")) {\r
717         if (((unsigned) random() % 3) == 0) {\r
718             char *player = StripHighlightAndTitle(star_match[0]);\r
719             sprintf(reply, "farewell %s\n", player);\r
720             SendToICS(reply);\r
721         }\r
722     }   \r
723 \r
724     if (looking_at(buf, i, "Not sent -- * is censoring you")) {\r
725         char *player = StripHighlightAndTitle(star_match[0]);\r
726         if (strcmp(player, lastgreet) == 0) {\r
727             sprintf(reply, "%s-notify %s\n", ics_prefix, player);\r
728             SendToICS(reply);\r
729         }\r
730     }   \r
731 \r
732     if (looking_at(buf, i, "command is currently turned off")) {\r
733         appData.zippyUseI = 0;\r
734     }\r
735 \r
736     return FALSE;\r
737 }\r
738 \r
739 void ZippyGameStart(white, black)\r
740      char *white, *black;\r
741 {\r
742     if (!first.initDone) {\r
743       /* Game is starting prematurely.  We can't deal with this */\r
744       SendToICS(ics_prefix);\r
745       SendToICS("abort\n");\r
746       SendToICS(ics_prefix);\r
747       SendToICS("say Sorry, the chess program is not initialized yet.\n");\r
748       return;\r
749     }\r
750 \r
751     if (appData.zippyGameStart[0] != NULLCHAR) {\r
752       SendToICS(appData.zippyGameStart);\r
753       SendToICS("\n");\r
754     }\r
755 }\r
756 \r
757 void ZippyGameEnd(result, resultDetails)\r
758      ChessMove result;\r
759      char *resultDetails;\r
760 {\r
761     if (appData.zippyAcceptOnly[0] == NULLCHAR &&\r
762         appData.zippyGameEnd[0] != NULLCHAR) {\r
763       SendToICS(appData.zippyGameEnd);\r
764       SendToICS("\n");\r
765     }\r
766     zippyLastGameEnd = time(0);\r
767 }\r
768 \r
769 /*\r
770  * Routines to implement Zippy playing chess\r
771  */\r
772 \r
773 void ZippyHandleChallenge(srated, swild, sbase, sincrement, opponent)\r
774      char *srated, *swild, *sbase, *sincrement, *opponent;\r
775 {\r
776     char buf[MSG_SIZ];\r
777     int base, increment, i=0;\r
778     char rated;\r
779     VariantClass variant;\r
780     char *varname;\r
781 \r
782     rated = srated[0];\r
783     variant = StringToVariant(swild);\r
784     varname = VariantName(variant);\r
785     base = atoi(sbase);\r
786     increment = atoi(sincrement);\r
787 \r
788     /* [DM] If icsAnalyzeEngine active we don't accept automatic games */\r
789     if (appData.icsActive && appData.icsEngineAnalyze) return;\r
790 \r
791     /* If desired, you can insert more code here to decline matches\r
792        based on rated, variant, base, and increment, but it is\r
793        easier to use the ICS formula feature instead. */\r
794 \r
795     if (variant == VariantLoadable) {\r
796         sprintf(buf,\r
797          "%stell %s This computer can't play wild type %s\n%sdecline %s\n",\r
798                 ics_prefix, opponent, swild, ics_prefix, opponent);\r
799         SendToICS(buf);\r
800         return;\r
801     }\r
802     if (StrStr(appData.zippyVariants, varname) == NULL ||\r
803                 (i=first.protocolVersion) != 1 && StrStr(first.variants, varname) == NULL /* [HGM] zippyvar */\r
804                                                           ) {\r
805         sprintf(buf,\r
806          "%stell %s This computer can't play %s [%s], only %s\n%sdecline %s\n",\r
807                 ics_prefix, opponent, swild, varname, \r
808                 i ? first.variants : appData.zippyVariants,                               /* [HGM] zippyvar */\r
809                 ics_prefix, opponent);\r
810         SendToICS(buf);\r
811         return;\r
812     }\r
813 \r
814     /* Are we blocking match requests from all but one person? */\r
815     if (appData.zippyAcceptOnly[0] != NULLCHAR &&\r
816         StrCaseCmp(opponent, appData.zippyAcceptOnly)) {\r
817         /* Yes, and this isn't him.  Ignore challenge. */\r
818         return;\r
819     }\r
820     \r
821     /* Too many consecutive games with same opponent?  If so, make him\r
822        wait until someone else has played or a timeout has elapsed. */\r
823     if (appData.zippyMaxGames &&\r
824         strcmp(opponent, zippyLastOpp) == 0 &&\r
825         zippyConsecGames >= appData.zippyMaxGames &&\r
826         difftime(time(0), zippyLastGameEnd) < appData.zippyReplayTimeout) {\r
827       sprintf(buf, "%stell %s Sorry, you have just played %d consecutive games against %s.  To give others a chance, please wait %d seconds or until someone else has played.\n%sdecline %s\n",\r
828               ics_prefix, opponent, zippyConsecGames, ics_handle,\r
829               appData.zippyReplayTimeout, ics_prefix, opponent);\r
830       SendToICS(buf);\r
831       return;\r
832     }\r
833 \r
834     /* Engine not yet initialized or still thinking about last game? */\r
835     if (!first.initDone || first.lastPing != first.lastPong) {\r
836       sprintf(buf, "%stell %s I'm not quite ready for a new game yet; try again soon.\n%sdecline %s\n",\r
837               ics_prefix, opponent, ics_prefix, opponent);\r
838       SendToICS(buf);\r
839       return;\r
840     }\r
841 \r
842     sprintf(buf, "%saccept %s\n", ics_prefix, opponent);\r
843     SendToICS(buf);\r
844     if (appData.zippyTalk) {\r
845       Speak("tell", opponent);\r
846     }\r
847 }\r
848 \r
849 \r
850 /* Accept matches */\r
851 int ZippyMatch(buf, i)\r
852      char *buf;\r
853      int *i;\r
854 {\r
855     if (looking_at(buf, i, "* * match * * requested with * (*)")) {\r
856 \r
857         ZippyHandleChallenge(star_match[0], star_match[1],\r
858                              star_match[2], star_match[3],\r
859                              StripHighlightAndTitle(star_match[4]));\r
860         return TRUE;\r
861     }\r
862 \r
863     /* Old FICS 0-increment form */\r
864     if (looking_at(buf, i, "* * match * requested with * (*)")) {\r
865 \r
866         ZippyHandleChallenge(star_match[0], star_match[1],\r
867                              star_match[2], "0",\r
868                              StripHighlightAndTitle(star_match[3]));\r
869         return TRUE;\r
870     }\r
871 \r
872     if (looking_at(buf, i,\r
873                    "* has made an alternate proposal of * * match * *.")) {\r
874 \r
875         ZippyHandleChallenge(star_match[1], star_match[2],\r
876                              star_match[3], star_match[4],\r
877                              StripHighlightAndTitle(star_match[0]));\r
878         return TRUE;\r
879     }\r
880 \r
881     /* FICS wild/nonstandard forms */\r
882     if (looking_at(buf, i, "Challenge: * (*) *(*) * * * * Loaded from *")) {\r
883         /* note: star_match[2] can include "[white] " or "[black] "\r
884            before our own name. */\r
885         if(star_match[8] == NULL || star_match[8][0] == 0) // [HGM] chessd: open-source ICS has file on next line\r
886              ZippyHandleChallenge(star_match[4], star_match[5],\r
887                              star_match[6], star_match[7],                           StripHighlightAndTitle(star_match[0]));\r
888         else ZippyHandleChallenge(star_match[4], star_match[8],\r
889                              star_match[6], star_match[7],\r
890                              StripHighlightAndTitle(star_match[0]));\r
891         return TRUE;\r
892     }\r
893 \r
894     if (looking_at(buf, i,\r
895                    "Challenge: * (*) *(*) * * * * : * * Loaded from *")) {\r
896         /* note: star_match[2] can include "[white] " or "[black] "\r
897            before our own name. */\r
898         ZippyHandleChallenge(star_match[4], star_match[10],\r
899                              star_match[8], star_match[9],\r
900                              StripHighlightAndTitle(star_match[0]));\r
901         return TRUE;\r
902     }\r
903 \r
904     /* Regular forms */\r
905     if (looking_at(buf, i, "Challenge: * (*) *(*) * * * * : * *") |\r
906         looking_at(buf, i, "Challenge: * (*) *(*) * * * * * *")) {\r
907         /* note: star_match[2] can include "[white] " or "[black] "\r
908            before our own name. */\r
909         ZippyHandleChallenge(star_match[4], star_match[5],\r
910                              star_match[8], star_match[9],\r
911                              StripHighlightAndTitle(star_match[0]));\r
912         return TRUE;\r
913     }\r
914 \r
915     if (looking_at(buf, i, "Challenge: * (*) *(*) * * * *")) {\r
916         /* note: star_match[2] can include "[white] " or "[black] "\r
917            before our own name. */\r
918         ZippyHandleChallenge(star_match[4], star_match[5],\r
919                              star_match[6], star_match[7],\r
920                              StripHighlightAndTitle(star_match[0]));\r
921         return TRUE;\r
922     }\r
923 \r
924 \r
925     if (ics_type == ICS_ICC) { // [DM]\r
926         if (looking_at(buf, i, "Your opponent offers you a draw")) {\r
927             if (first.sendDrawOffers && first.initDone)\r
928                 SendToProgram("draw\n", &first);\r
929             return TRUE;\r
930         }\r
931     } else {\r
932         if (looking_at(buf, i, "offers you a draw")) {\r
933             if (first.sendDrawOffers && first.initDone) {\r
934                 SendToProgram("draw\n", &first);\r
935             }\r
936             return TRUE;\r
937         }\r
938     }\r
939 \r
940     if (looking_at(buf, i, "requests that the game be aborted") ||\r
941         looking_at(buf, i, "would like to abort")) {\r
942         if (appData.zippyAbort ||\r
943             (gameMode == IcsPlayingWhite && whiteTimeRemaining < 0) ||\r
944             (gameMode == IcsPlayingBlack && blackTimeRemaining < 0)) {\r
945             SendToICS(ics_prefix);\r
946             SendToICS("abort\n");\r
947         } else {\r
948             SendToICS(ics_prefix);\r
949             if (appData.zippyTalk)\r
950               SendToICS("say Whoa no!  I am having FUN!!\n");\r
951             else\r
952               SendToICS("say Sorry, this computer doesn't accept aborts.\n");\r
953         }\r
954         return TRUE;\r
955     }\r
956 \r
957     if (looking_at(buf, i, "requests adjournment") ||\r
958         looking_at(buf, i, "would like to adjourn")) {\r
959       if (appData.zippyAdjourn) {\r
960         SendToICS(ics_prefix);\r
961         SendToICS("adjourn\n");\r
962       } else {\r
963         SendToICS(ics_prefix);\r
964         if (appData.zippyTalk)\r
965           SendToICS("say Whoa no!  I am having FUN playing NOW!!\n");\r
966         else\r
967           SendToICS("say Sorry, this computer doesn't accept adjourns.\n");\r
968       }\r
969       return TRUE;\r
970     }\r
971 \r
972     return FALSE;\r
973 }\r
974 \r
975 /* Initialize chess program with data from the first board \r
976  * of a new or resumed game.\r
977  */\r
978 void ZippyFirstBoard(moveNum, basetime, increment)\r
979      int moveNum, basetime, increment;\r
980 {\r
981     char buf[MSG_SIZ];\r
982     int w, b;\r
983     char *opp = (gameMode==IcsPlayingWhite ? gameInfo.black : gameInfo.white);\r
984     Boolean sentPos = FALSE;\r
985     char *bookHit = NULL; // [HGM] book\r
986 \r
987     if (!first.initDone) {\r
988       /* Game is starting prematurely.  We can't deal with this */\r
989       SendToICS(ics_prefix);\r
990       SendToICS("abort\n");\r
991       SendToICS(ics_prefix);\r
992       SendToICS("say Sorry, the chess program is not initialized yet.\n");\r
993       return;\r
994     }\r
995 \r
996     /* Send the variant command if needed */\r
997     if (gameInfo.variant != VariantNormal) {\r
998       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
999       SendToProgram(buf, &first);\r
1000     }\r
1001 \r
1002     if ((startedFromSetupPosition && moveNum == 0) ||\r
1003         (!appData.getMoveList && moveNum > 0)) {\r
1004       SendToProgram("force\n", &first);\r
1005       SendBoard(&first, moveNum);\r
1006       sentPos = TRUE;\r
1007     }\r
1008 \r
1009     sprintf(buf, "level 0 %d %d\n", basetime, increment);\r
1010     SendToProgram(buf, &first);\r
1011 \r
1012     /* Count consecutive games from one opponent */\r
1013     if (strcmp(opp, zippyLastOpp) == 0) {\r
1014       zippyConsecGames++;\r
1015     } else {\r
1016       zippyConsecGames = 1;\r
1017       strcpy(zippyLastOpp, opp);\r
1018     }\r
1019 \r
1020     /* Send the "computer" command if the opponent is in the list\r
1021        we've been gathering. */\r
1022     for (w=0; w<num_opps; w++) {\r
1023         if (!strcmp(opp_name[w], opp)) {\r
1024             SendToProgram(first.computerString, &first);\r
1025             break;\r
1026         }\r
1027     }\r
1028 \r
1029     /* Ratings might be < 0 which means "we haven't seen a ratings\r
1030        message from ICS." Send 0 in that case */\r
1031     w = (gameInfo.whiteRating >= 0) ? gameInfo.whiteRating : 0;\r
1032     b = (gameInfo.blackRating >= 0) ? gameInfo.blackRating : 0;\r
1033     \r
1034     firstMove = FALSE;\r
1035     if (gameMode == IcsPlayingWhite) {\r
1036         if (first.sendName) {\r
1037           sprintf(buf, "name %s\n", gameInfo.black);\r
1038           SendToProgram(buf, &first);\r
1039         }\r
1040         strcpy(ics_handle, gameInfo.white);\r
1041         sprintf(buf, "rating %d %d\n", w, b);\r
1042         SendToProgram(buf, &first);\r
1043         if (sentPos) {\r
1044             /* Position sent above, engine is in force mode */\r
1045             if (WhiteOnMove(moveNum)) {\r
1046               /* Engine is on move now */\r
1047               if (first.sendTime) {\r
1048                 if (first.useColors) {\r
1049                   SendToProgram("black\n", &first); /*gnu kludge*/\r
1050                   SendTimeRemaining(&first, TRUE);\r
1051                   SendToProgram("white\n", &first);\r
1052                 } else {\r
1053                   SendTimeRemaining(&first, TRUE);\r
1054                 }\r
1055               }\r
1056               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
1057             } else {\r
1058                 /* Engine's opponent is on move now */\r
1059                 if (first.usePlayother) {\r
1060                   if (first.sendTime) {\r
1061                     SendTimeRemaining(&first, TRUE);\r
1062                   }\r
1063                   SendToProgram("playother\n", &first);\r
1064                 } else {\r
1065                   /* Need to send a "go" after opponent moves */\r
1066                   firstMove = TRUE;\r
1067                 }\r
1068             }\r
1069         } else {\r
1070             /* Position not sent above, move list might be sent later */\r
1071             if (moveNum == 0) {\r
1072                 /* No move list coming; at start of game */\r
1073               if (first.sendTime) {\r
1074                 if (first.useColors) {\r
1075                   SendToProgram("black\n", &first); /*gnu kludge*/\r
1076                   SendTimeRemaining(&first, TRUE);\r
1077                   SendToProgram("white\n", &first);\r
1078                 } else {\r
1079                   SendTimeRemaining(&first, TRUE);\r
1080                 }\r
1081               }\r
1082 //            SendToProgram("go\n", &first);\r
1083               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
1084             }\r
1085         }\r
1086     } else if (gameMode == IcsPlayingBlack) {\r
1087         if (first.sendName) {\r
1088           sprintf(buf, "name %s\n", gameInfo.white);\r
1089           SendToProgram(buf, &first);\r
1090         }\r
1091         strcpy(ics_handle, gameInfo.black);\r
1092         sprintf(buf, "rating %d %d\n", b, w);\r
1093         SendToProgram(buf, &first);\r
1094         if (sentPos) {\r
1095             /* Position sent above, engine is in force mode */\r
1096             if (!WhiteOnMove(moveNum)) {\r
1097                 /* Engine is on move now */\r
1098               if (first.sendTime) {\r
1099                 if (first.useColors) {\r
1100                   SendToProgram("white\n", &first); /*gnu kludge*/\r
1101                   SendTimeRemaining(&first, FALSE);\r
1102                   SendToProgram("black\n", &first);\r
1103                 } else {\r
1104                   SendTimeRemaining(&first, FALSE);\r
1105                 }\r
1106               }\r
1107 //            SendToProgram("go\n", &first);\r
1108               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
1109             } else {\r
1110                 /* Engine's opponent is on move now */\r
1111                 if (first.usePlayother) {\r
1112                   if (first.sendTime) {\r
1113                     SendTimeRemaining(&first, FALSE);\r
1114                   }\r
1115                   SendToProgram("playother\n", &first);\r
1116                 } else {\r
1117                   /* Need to send a "go" after opponent moves */\r
1118                   firstMove = TRUE;\r
1119                 }\r
1120             }\r
1121         } else {\r
1122             /* Position not sent above, move list might be sent later */\r
1123             /* Nothing needs to be done here */\r
1124         }       \r
1125     }\r
1126 \r
1127     if(bookHit) { // [HGM] book: simulate book reply\r
1128         static char bookMove[MSG_SIZ]; // a bit generous?\r
1129 \r
1130         programStats.depth = programStats.nodes = programStats.time = \r
1131         programStats.score = programStats.got_only_move = 0;\r
1132         sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
1133 \r
1134         strcpy(bookMove, "move ");\r
1135         strcat(bookMove, bookHit);\r
1136         HandleMachineMove(bookMove, &first);\r
1137     }\r
1138 }\r
1139 \r
1140 \r
1141 void\r
1142 ZippyHoldings(white_holding, black_holding, new_piece)\r
1143      char *white_holding, *black_holding, *new_piece;\r
1144 {\r
1145     char buf[MSG_SIZ];\r
1146     if (gameMode != IcsPlayingBlack && gameMode != IcsPlayingWhite) return;\r
1147     sprintf(buf, "holding [%s] [%s] %s\n",\r
1148             white_holding, black_holding, new_piece);\r
1149     SendToProgram(buf, &first);\r
1150 }\r