Small changes; see ChangeLog for details.
[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[(unsigned) random() %
312                            (sizeof(swifties)/sizeof(char *))];
313         }
314     }
315
316     if (zipfile == NULL) {
317         zipfile = fopen(appData.zippyLines, "r");
318         if (zipfile == NULL) {
319             DisplayFatalError("Can't open Zippy lines file", errno, 1);
320             return;
321         }
322         fstat(fileno(zipfile), &zipstat);
323     }
324                 
325     for (;;) {
326         fseek(zipfile, (unsigned) random() % zipstat.st_size, 0);
327         do {
328           c = getc(zipfile);
329         } while (c != NULLCHAR && c != '^' && c != EOF);
330         if (c == EOF) continue;
331         while ((c = getc(zipfile)) == '\n') ;
332         if (c == EOF) continue;
333         break;
334     }
335     done = FALSE;
336
337     /* Don't use ics_prefix; we need to let FICS expand the alias i -> it,
338        but use the real command "i" on ICC */
339     strcpy(zipbuf, how);
340     strcat(zipbuf, " ");
341     if (whom != NULL) {
342         strcat(zipbuf, whom);
343         strcat(zipbuf, " ");
344     }
345     speechlen = strlen(zipbuf);
346     p = zipbuf + speechlen;
347
348     while (++speechlen < MAX_SPEECH) {
349         if (c == NULLCHAR || c == '^') {
350             *p++ = '\n';
351             *p = '\0';
352             SendToICS(zipbuf);
353             return;
354         } else if (c == '\n') {
355             *p++ = ' ';
356             do {
357                 c = getc(zipfile);
358             } while (c == ' ');
359         } else if (c == EOF) {
360             break;
361         } else {
362             *p++ = c;
363             c = getc(zipfile);
364         }
365     }
366     /* Tried to say something too long, or junk at the end of the
367        file.  Try something else. */
368     Speak(how, whom);  /* tail recursion */
369 }
370
371 int ZippyCalled(str)
372      char *str;
373 {
374     return ics_handle[0] != NULLCHAR && StrCaseStr(str, ics_handle) != NULL;
375 }
376
377 static char opp_name[128][32];
378 static int num_opps=0;
379
380 int ZippyControl(buf, i)
381      char *buf;
382      int *i;
383 {
384     char *player, *p;
385     char reply[MSG_SIZ];
386
387 #if TRIVIA
388 #include "trivia.c"
389 #endif
390
391     /* Possibly reject Crafty as opponent */
392     if (appData.zippyPlay && appData.zippyNoplayCrafty && forwardMostMove < 4
393         && looking_at(buf, i, "* kibitzes: Hello from Crafty")) {
394         player = StripHighlightAndTitle(star_match[0]);
395         if ((gameMode == IcsPlayingWhite &&
396              StrCaseCmp(player, gameInfo.black) == 0) ||
397             (gameMode == IcsPlayingBlack &&
398              StrCaseCmp(player, gameInfo.white) == 0)) {
399
400           sprintf(reply, "%ssay This computer does not play Crafty clones\n%sabort\n%s+noplay %s\n",
401                   ics_prefix, ics_prefix, ics_prefix, player);
402           SendToICS(reply);
403         }
404         return TRUE;
405     }
406
407     /* If this is a computer, save the name.  Then later, once the */
408     /* game is really started, we will send the "computer" notice to */
409     /* the engine.  */ 
410     if (appData.zippyPlay &&
411         looking_at(buf, i, "* is in the computer list")) {
412         int i;
413         for (i=0;i<num_opps;i++)
414           if (!strcmp(opp_name[i],star_match[0])) break;
415         if (i >= num_opps) strcpy(opp_name[num_opps++],star_match[0]);
416     }
417     if (appData.zippyPlay && looking_at(buf, i, "* * is a computer *")) {
418         int i;
419         for (i=0;i<num_opps;i++)
420           if (!strcmp(opp_name[i],star_match[1])) break;
421         if (i >= num_opps) strcpy(opp_name[num_opps++],star_match[1]);
422     }
423
424     /* Tells and says */
425     if (appData.zippyPlay && 
426         (looking_at(buf, i, "* offers to be your bughouse partner") ||
427          looking_at(buf, i, "* tells you: [automatic message] I chose you"))) {
428         player = StripHighlightAndTitle(star_match[0]);
429         if (appData.zippyBughouse > 1 && first.initDone) {
430             sprintf(reply, "%spartner %s\n", ics_prefix, player);
431             SendToICS(reply);
432             if (strcmp(zippyPartner, player) != 0) {
433                 strcpy(zippyPartner, player);
434                 SendToProgram(reply + strlen(ics_prefix), &first);
435             }
436         } else if (appData.zippyBughouse > 0) {
437             sprintf(reply, "%sdecline %s\n", ics_prefix, player);
438             SendToICS(reply);
439         } else {
440             sprintf(reply, "%stell %s This computer cannot play bughouse\n",
441                     ics_prefix, player);
442             SendToICS(reply);
443         }
444         return TRUE;
445     }
446
447     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&
448         looking_at(buf, i, "* agrees to be your partner")) {
449         player = StripHighlightAndTitle(star_match[0]);
450         sprintf(reply, "partner %s\n", player);
451         if (strcmp(zippyPartner, player) != 0) {
452             strcpy(zippyPartner, player);
453             SendToProgram(reply, &first);
454         }
455         return TRUE;
456     }
457
458     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&
459         (looking_at(buf, i, "are no longer *'s partner") ||
460          looking_at(buf, i,
461                     "* tells you: [automatic message] I'm no longer your"))) {
462         player = StripHighlightAndTitle(star_match[0]);
463         if (strcmp(zippyPartner, player) == 0) {
464             zippyPartner[0] = NULLCHAR;
465             SendToProgram("partner\n", &first);
466         }
467         return TRUE;
468     }
469
470     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&
471         (looking_at(buf, i, "no longer have a bughouse partner") ||
472          looking_at(buf, i, "partner has disconnected") ||
473          looking_at(buf, i, "partner has just chosen a new partner"))) {
474       zippyPartner[0] = NULLCHAR;
475       SendToProgram("partner\n", &first);
476       return TRUE;
477     }
478
479     if (appData.zippyPlay && appData.zippyBughouse && first.initDone &&
480         looking_at(buf, i, "* (your partner) tells you: *")) {
481         /* This pattern works on FICS but not ICC */
482         player = StripHighlightAndTitle(star_match[0]);
483         if (strcmp(zippyPartner, player) != 0) {
484             strcpy(zippyPartner, player);
485             sprintf(reply, "partner %s\n", player);
486             SendToProgram(reply, &first);
487         }
488         sprintf(reply, "ptell %s\n", star_match[1]);
489         SendToProgram(reply, &first);
490         return TRUE;
491     }
492
493     if (looking_at(buf, i, "* tells you: *") ||
494         looking_at(buf, i, "* says: *")) {
495         player = StripHighlightAndTitle(star_match[0]);
496         if (appData.zippyPassword[0] != NULLCHAR &&
497             strncmp(star_match[1], appData.zippyPassword,
498                     strlen(appData.zippyPassword)) == 0) {
499             p = star_match[1] + strlen(appData.zippyPassword);
500             while (*p == ' ') p++;
501             SendToICS(p);
502             SendToICS("\n");
503         } else if (appData.zippyPassword2[0] != NULLCHAR && first.initDone &&
504             strncmp(star_match[1], appData.zippyPassword2,
505                     strlen(appData.zippyPassword2)) == 0) {
506             p = star_match[1] + strlen(appData.zippyPassword2);
507             while (*p == ' ') p++;
508             SendToProgram(p, &first);
509             SendToProgram("\n", &first);
510         } else if (appData.zippyWrongPassword[0] != NULLCHAR &&
511             strncmp(star_match[1], appData.zippyWrongPassword,
512                     strlen(appData.zippyWrongPassword)) == 0) {
513             p = star_match[1] + strlen(appData.zippyWrongPassword);
514             while (*p == ' ') p++;
515             sprintf(reply, "wrong %s\n", player);
516             SendToICS(reply);
517         } else if (appData.zippyBughouse && first.initDone &&
518                    strcmp(player, zippyPartner) == 0) {
519             SendToProgram("ptell ", &first);
520             SendToProgram(star_match[1], &first);
521             SendToProgram("\n", &first);
522         } else if (strncmp(star_match[1], HI, 6) == 0) {
523             extern char* programVersion;
524             sprintf(reply, "%stell %s %s\n",
525                     ics_prefix, player, programVersion);
526             SendToICS(reply);
527         } else if (strncmp(star_match[1], "W0W!! ", 6) == 0) {
528             extern char* programVersion;
529             sprintf(reply, "%stell %s %s\n", ics_prefix,
530                     player, programVersion);
531             SendToICS(reply);
532         } else if (appData.zippyTalk && (((unsigned) random() % 10) < 9)) {
533             if (strcmp(player, ics_handle) != 0) {
534                 Speak("tell", player);
535             }
536         }
537         return TRUE;
538     }
539
540     if (looking_at(buf, i, "* spoofs you:")) {
541         player = StripHighlightAndTitle(star_match[0]);
542         sprintf(reply, "spoofedby %s\n", player);
543         SendToICS(reply);
544     }
545     return FALSE;
546 }
547
548 int ZippyConverse(buf, i)
549      char *buf;
550      int *i;
551 {
552     static char lastgreet[MSG_SIZ];
553     char reply[MSG_SIZ];
554     int oldi;
555
556     /* Shouts and emotes */
557     if (looking_at(buf, i, "--> * *") ||
558         looking_at(buf, i, "* shouts: *")) {
559       if (appData.zippyTalk) {
560         char *player = StripHighlightAndTitle(star_match[0]);
561         if (strcmp(player, ics_handle) == 0) {
562             return TRUE;
563         } else if (appData.zippyPinhead[0] != NULLCHAR &&
564                    StrCaseStr(star_match[1], appData.zippyPinhead) != NULL) {
565             sprintf(reply, "insult %s\n", player);
566             SendToICS(reply);
567         } else if (ZippyCalled(star_match[1])) {
568             Speak("shout", NULL);
569         }
570       }
571       return TRUE;
572     }
573
574     if (looking_at(buf, i, "* kibitzes: *")) {
575       if (appData.zippyTalk && ((unsigned) random() % 10) < 9) {
576         char *player = StripHighlightAndTitle(star_match[0]);
577         if (strcmp(player, ics_handle) != 0) {
578             Speak("kibitz", NULL);
579         }
580       }
581       return TRUE;
582     }
583
584     if (looking_at(buf, i, "* whispers: *")) {
585       if (appData.zippyTalk && ((unsigned) random() % 10) < 9) {
586         char *player = StripHighlightAndTitle(star_match[0]);
587         if (strcmp(player, ics_handle) != 0) {
588             Speak("whisper", NULL);
589         }
590       }
591       return TRUE;
592     }
593
594     /* Messages */
595     if ((looking_at(buf, i, ". * (*:*): *") && isdigit(star_match[1][0])) ||
596          looking_at(buf, i, ". * at *:*: *")) {
597       if (appData.zippyTalk) {
598         FILE *f;
599         char *player = StripHighlightAndTitle(star_match[0]);
600
601         if (strcmp(player, ics_handle) != 0) {
602             if (((unsigned) random() % 10) < 9)
603               Speak("message", player);
604             f = fopen("zippy.messagelog", "a");
605             fprintf(f, "%s (%s:%s): %s\n", player,
606                     star_match[1], star_match[2], star_match[3]);
607             fclose(f);
608         }
609       }
610       return TRUE;
611     }
612
613     /* Channel tells */
614     oldi = *i;
615     if (looking_at(buf, i, "*(*: *")) {
616         char *player;
617         char *channel;
618         if (star_match[0][0] == NULLCHAR  ||
619             strchr(star_match[0], ' ') ||
620             strchr(star_match[1], ' ')) {
621             /* Oops, did not want to match this; probably a message */
622             *i = oldi;
623             return FALSE;
624         }
625         if (appData.zippyTalk) {
626           player = StripHighlightAndTitle(star_match[0]);
627           channel = strrchr(star_match[1], '(');
628           if (channel == NULL) {
629             channel = star_match[1];
630           } else {
631             channel++;
632           }
633           channel[strlen(channel)-1] = NULLCHAR;
634 #if 0
635           /* Always tell to the channel (probability 90%) */
636           if (strcmp(player, ics_handle) != 0 &&
637               ((unsigned) random() % 10) < 9) {
638             Speak("tell", channel);
639           }
640 #else
641           /* Tell to the channel only if someone mentions our name */
642           if (ZippyCalled(star_match[2])) {
643             Speak("tell", channel);
644           }
645 #endif
646         }
647         return TRUE;
648     }
649
650     if (!appData.zippyTalk) return FALSE;
651
652     if ((looking_at(buf, i, "You have * message") &&
653          atoi(star_match[0]) != 0) ||
654         looking_at(buf, i, "* has left a message for you") ||
655         looking_at(buf, i, "* just sent you a message")) {
656         sprintf(reply, "%smessages\n%sclearmessages *\n",
657                 ics_prefix, ics_prefix);
658         SendToICS(reply);
659         return TRUE;
660     }
661
662     if (looking_at(buf, i, "Notification: * has arrived")) {
663         if (((unsigned) random() % 3) == 0) {
664             char *player = StripHighlightAndTitle(star_match[0]);
665             strcpy(lastgreet, player);
666             sprintf(reply, "greet %s\n", player);
667             SendToICS(reply);
668             Speak("tell", player);
669         }
670     }   
671
672     if (looking_at(buf, i, "Notification: * has departed")) {
673         if (((unsigned) random() % 3) == 0) {
674             char *player = StripHighlightAndTitle(star_match[0]);
675             sprintf(reply, "farewell %s\n", player);
676             SendToICS(reply);
677         }
678     }   
679
680     if (looking_at(buf, i, "Not sent -- * is censoring you")) {
681         char *player = StripHighlightAndTitle(star_match[0]);
682         if (strcmp(player, lastgreet) == 0) {
683             sprintf(reply, "%s-notify %s\n", ics_prefix, player);
684             SendToICS(reply);
685         }
686     }   
687
688     if (looking_at(buf, i, "command is currently turned off")) {
689         appData.zippyUseI = 0;
690     }
691
692     return FALSE;
693 }
694
695 void ZippyGameStart(white, black)
696      char *white, *black;
697 {
698     if (!first.initDone) {
699       /* Game is starting prematurely.  We can't deal with this */
700       SendToICS(ics_prefix);
701       SendToICS("abort\n");
702       SendToICS(ics_prefix);
703       SendToICS("say Sorry, the chess program is not initialized yet.\n");
704       return;
705     }
706
707     if (appData.zippyGameStart[0] != NULLCHAR) {
708       SendToICS(appData.zippyGameStart);
709       SendToICS("\n");
710     }
711 }
712
713 void ZippyGameEnd(result, resultDetails)
714      ChessMove result;
715      char *resultDetails;
716 {
717     if (appData.zippyAcceptOnly[0] == NULLCHAR &&
718         appData.zippyGameEnd[0] != NULLCHAR) {
719       SendToICS(appData.zippyGameEnd);
720       SendToICS("\n");
721     }
722     zippyLastGameEnd = time(0);
723 }
724
725 /*
726  * Routines to implement Zippy playing chess
727  */
728
729 void ZippyHandleChallenge(srated, swild, sbase, sincrement, opponent)
730      char *srated, *swild, *sbase, *sincrement, *opponent;
731 {
732     char buf[MSG_SIZ];
733     int base, increment;
734     char rated;
735     VariantClass variant;
736     char *varname;
737
738     rated = srated[0];
739     variant = StringToVariant(swild);
740     varname = VariantName(variant);
741     base = atoi(sbase);
742     increment = atoi(sincrement);
743
744     /* If desired, you can insert more code here to decline matches
745        based on rated, variant, base, and increment, but it is
746        easier to use the ICS formula feature instead. */
747
748     if (variant == VariantLoadable) {
749         sprintf(buf,
750          "%stell %s This computer can't play wild type %s\n%sdecline %s\n",
751                 ics_prefix, opponent, swild, ics_prefix, opponent);
752         SendToICS(buf);
753         return;
754     }
755     if (StrStr(appData.zippyVariants, varname) == NULL) {
756         sprintf(buf,
757          "%stell %s This computer can't play %s [%s], only %s\n%sdecline %s\n",
758                 ics_prefix, opponent, swild, varname, appData.zippyVariants,
759                 ics_prefix, opponent);
760         SendToICS(buf);
761         return;
762     }
763
764     /* Are we blocking match requests from all but one person? */
765     if (appData.zippyAcceptOnly[0] != NULLCHAR &&
766         StrCaseCmp(opponent, appData.zippyAcceptOnly)) {
767         /* Yes, and this isn't him.  Ignore challenge. */
768         return;
769     }
770     
771     /* Too many consecutive games with same opponent?  If so, make him
772        wait until someone else has played or a timeout has elapsed. */
773     if (appData.zippyMaxGames &&
774         strcmp(opponent, zippyLastOpp) == 0 &&
775         zippyConsecGames >= appData.zippyMaxGames &&
776         difftime(time(0), zippyLastGameEnd) < appData.zippyReplayTimeout) {
777       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",
778               ics_prefix, opponent, zippyConsecGames, ics_handle,
779               appData.zippyReplayTimeout, ics_prefix, opponent);
780       SendToICS(buf);
781       return;
782     }
783
784     /* Engine not yet initialized or still thinking about last game? */
785     if (!first.initDone || first.lastPing != first.lastPong) {
786       sprintf(buf, "%stell %s I'm not quite ready for a new game yet; try again soon.\n%sdecline %s\n",
787               ics_prefix, opponent, ics_prefix, opponent);
788       SendToICS(buf);
789       return;
790     }
791
792     sprintf(buf, "%saccept %s\n", ics_prefix, opponent);
793     SendToICS(buf);
794     if (appData.zippyTalk) {
795       Speak("tell", opponent);
796     }
797 }
798
799
800 /* Accept matches */
801 int ZippyMatch(buf, i)
802      char *buf;
803      int *i;
804 {
805     if (looking_at(buf, i, "* * match * * requested with * (*)")) {
806
807         ZippyHandleChallenge(star_match[0], star_match[1],
808                              star_match[2], star_match[3],
809                              StripHighlightAndTitle(star_match[4]));
810         return TRUE;
811     }
812
813     /* Old FICS 0-increment form */
814     if (looking_at(buf, i, "* * match * requested with * (*)")) {
815
816         ZippyHandleChallenge(star_match[0], star_match[1],
817                              star_match[2], "0",
818                              StripHighlightAndTitle(star_match[3]));
819         return TRUE;
820     }
821
822     if (looking_at(buf, i,
823                    "* has made an alternate proposal of * * match * *.")) {
824
825         ZippyHandleChallenge(star_match[1], star_match[2],
826                              star_match[3], star_match[4],
827                              StripHighlightAndTitle(star_match[0]));
828         return TRUE;
829     }
830
831     /* FICS wild/nonstandard forms */
832     if (looking_at(buf, i, "Challenge: * (*) *(*) * * * * Loaded from *")) {
833         /* note: star_match[2] can include "[white] " or "[black] "
834            before our own name. */
835         ZippyHandleChallenge(star_match[4], star_match[8],
836                              star_match[6], star_match[7],
837                              StripHighlightAndTitle(star_match[0]));
838         return TRUE;
839     }
840
841     if (looking_at(buf, i,
842                    "Challenge: * (*) *(*) * * * * : * * Loaded from *")) {
843         /* note: star_match[2] can include "[white] " or "[black] "
844            before our own name. */
845         ZippyHandleChallenge(star_match[4], star_match[10],
846                              star_match[8], star_match[9],
847                              StripHighlightAndTitle(star_match[0]));
848         return TRUE;
849     }
850
851     /* Regular forms */
852     if (looking_at(buf, i, "Challenge: * (*) *(*) * * * * : * *") |
853         looking_at(buf, i, "Challenge: * (*) *(*) * * * * * *")) {
854         /* note: star_match[2] can include "[white] " or "[black] "
855            before our own name. */
856         ZippyHandleChallenge(star_match[4], star_match[5],
857                              star_match[8], star_match[9],
858                              StripHighlightAndTitle(star_match[0]));
859         return TRUE;
860     }
861
862     if (looking_at(buf, i, "Challenge: * (*) *(*) * * * *")) {
863         /* note: star_match[2] can include "[white] " or "[black] "
864            before our own name. */
865         ZippyHandleChallenge(star_match[4], star_match[5],
866                              star_match[6], star_match[7],
867                              StripHighlightAndTitle(star_match[0]));
868         return TRUE;
869     }
870
871     if (looking_at(buf, i, "offers you a draw")) {
872         if (first.sendDrawOffers && first.initDone) {
873             SendToProgram("draw\n", &first);
874         }
875         return TRUE;
876     }
877
878     if (looking_at(buf, i, "requests that the game be aborted") ||
879         looking_at(buf, i, "would like to abort")) {
880         if (appData.zippyAbort ||
881             (gameMode == IcsPlayingWhite && whiteTimeRemaining < 0) ||
882             (gameMode == IcsPlayingBlack && blackTimeRemaining < 0)) {
883             SendToICS(ics_prefix);
884             SendToICS("abort\n");
885         } else {
886             SendToICS(ics_prefix);
887             if (appData.zippyTalk)
888               SendToICS("say Whoa no!  I am having FUN!!\n");
889             else
890               SendToICS("say Sorry, this computer doesn't accept aborts.\n");
891         }
892         return TRUE;
893     }
894
895     if (looking_at(buf, i, "requests adjournment") ||
896         looking_at(buf, i, "would like to adjourn")) {
897       if (appData.zippyAdjourn) {
898         SendToICS(ics_prefix);
899         SendToICS("adjourn\n");
900       } else {
901         SendToICS(ics_prefix);
902         if (appData.zippyTalk)
903           SendToICS("say Whoa no!  I am having FUN playing NOW!!\n");
904         else
905           SendToICS("say Sorry, this computer doesn't accept adjourns.\n");
906       }
907       return TRUE;
908     }
909
910     return FALSE;
911 }
912
913 /* Initialize chess program with data from the first board 
914  * of a new or resumed game.
915  */
916 void ZippyFirstBoard(moveNum, basetime, increment)
917      int moveNum, basetime, increment;
918 {
919     char buf[MSG_SIZ];
920     int w, b;
921     char *opp = (gameMode==IcsPlayingWhite ? gameInfo.black : gameInfo.white);
922     Boolean sentPos = FALSE;
923
924     if (!first.initDone) {
925       /* Game is starting prematurely.  We can't deal with this */
926       SendToICS(ics_prefix);
927       SendToICS("abort\n");
928       SendToICS(ics_prefix);
929       SendToICS("say Sorry, the chess program is not initialized yet.\n");
930       return;
931     }
932
933     /* Send the variant command if needed */
934     if (gameInfo.variant != VariantNormal) {
935       sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
936       SendToProgram(buf, &first);
937     }
938
939     if ((startedFromSetupPosition && moveNum == 0) ||
940         (!appData.getMoveList && moveNum > 0)) {
941       SendToProgram("force\n", &first);
942       SendBoard(&first, moveNum);
943       sentPos = TRUE;
944     }
945
946     sprintf(buf, "level 0 %d %d\n", basetime, increment);
947     SendToProgram(buf, &first);
948
949     /* Count consecutive games from one opponent */
950     if (strcmp(opp, zippyLastOpp) == 0) {
951       zippyConsecGames++;
952     } else {
953       zippyConsecGames = 1;
954       strcpy(zippyLastOpp, opp);
955     }
956
957     /* Send the "computer" command if the opponent is in the list
958        we've been gathering. */
959     for (w=0; w<num_opps; w++) {
960         if (!strcmp(opp_name[w], opp)) {
961             SendToProgram(first.computerString, &first);
962             break;
963         }
964     }
965
966     /* Ratings might be < 0 which means "we haven't seen a ratings
967        message from ICS." Send 0 in that case */
968     w = (gameInfo.whiteRating >= 0) ? gameInfo.whiteRating : 0;
969     b = (gameInfo.blackRating >= 0) ? gameInfo.blackRating : 0;
970     
971     firstMove = FALSE;
972     if (gameMode == IcsPlayingWhite) {
973         if (first.sendName) {
974           sprintf(buf, "name %s\n", gameInfo.black);
975           SendToProgram(buf, &first);
976         }
977         strcpy(ics_handle, gameInfo.white);
978         sprintf(buf, "rating %d %d\n", w, b);
979         SendToProgram(buf, &first);
980         if (sentPos) {
981             /* Position sent above, engine is in force mode */
982             if (WhiteOnMove(moveNum)) {
983               /* Engine is on move now */
984               if (first.sendTime) {
985                 if (first.useColors) {
986                   SendToProgram("black\n", &first); /*gnu kludge*/
987                   SendTimeRemaining(&first, TRUE);
988                   SendToProgram("white\n", &first);
989                 } else {
990                   SendTimeRemaining(&first, TRUE);
991                 }
992               }
993               SendToProgram("go\n", &first);
994             } else {
995                 /* Engine's opponent is on move now */
996                 if (first.usePlayother) {
997                   if (first.sendTime) {
998                     SendTimeRemaining(&first, TRUE);
999                   }
1000                   SendToProgram("playother\n", &first);
1001                 } else {
1002                   /* Need to send a "go" after opponent moves */
1003                   firstMove = TRUE;
1004                 }
1005             }
1006         } else {
1007             /* Position not sent above, move list might be sent later */
1008             if (moveNum == 0) {
1009                 /* No move list coming; at start of game */
1010               if (first.sendTime) {
1011                 if (first.useColors) {
1012                   SendToProgram("black\n", &first); /*gnu kludge*/
1013                   SendTimeRemaining(&first, TRUE);
1014                   SendToProgram("white\n", &first);
1015                 } else {
1016                   SendTimeRemaining(&first, TRUE);
1017                 }
1018               }
1019               SendToProgram("go\n", &first);
1020             }
1021         }
1022     } else if (gameMode == IcsPlayingBlack) {
1023         if (first.sendName) {
1024           sprintf(buf, "name %s\n", gameInfo.white);
1025           SendToProgram(buf, &first);
1026         }
1027         strcpy(ics_handle, gameInfo.black);
1028         sprintf(buf, "rating %d %d\n", b, w);
1029         SendToProgram(buf, &first);
1030         if (sentPos) {
1031             /* Position sent above, engine is in force mode */
1032             if (!WhiteOnMove(moveNum)) {
1033                 /* Engine is on move now */
1034               if (first.sendTime) {
1035                 if (first.useColors) {
1036                   SendToProgram("white\n", &first); /*gnu kludge*/
1037                   SendTimeRemaining(&first, FALSE);
1038                   SendToProgram("black\n", &first);
1039                 } else {
1040                   SendTimeRemaining(&first, FALSE);
1041                 }
1042               }
1043               SendToProgram("go\n", &first);
1044             } else {
1045                 /* Engine's opponent is on move now */
1046                 if (first.usePlayother) {
1047                   if (first.sendTime) {
1048                     SendTimeRemaining(&first, FALSE);
1049                   }
1050                   SendToProgram("playother\n", &first);
1051                 } else {
1052                   /* Need to send a "go" after opponent moves */
1053                   firstMove = TRUE;
1054                 }
1055             }
1056         } else {
1057             /* Position not sent above, move list might be sent later */
1058             /* Nothing needs to be done here */
1059         }       
1060     }
1061 }
1062
1063
1064 void
1065 ZippyHoldings(white_holding, black_holding, new_piece)
1066      char *white_holding, *black_holding, *new_piece;
1067 {
1068     char buf[MSG_SIZ];
1069     if (gameMode != IcsPlayingBlack && gameMode != IcsPlayingWhite) return;
1070     sprintf(buf, "holding [%s] [%s] %s\n",
1071             white_holding, black_holding, new_piece);
1072     SendToProgram(buf, &first);
1073 }