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