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