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