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