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