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