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