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