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