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