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