/*
 * zippy.c -- Implements Zippy the Pinhead chess player on ICS in XBoard
 * $Id: zippy.c,v 1.56 2000/03/04 01:27:51 mann Exp $
 *
 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
 * Enhancements Copyright 1992-95 Free Software Foundation, Inc.
 *
 * The following terms apply to Digital Equipment Corporation's copyright
 * interest in XBoard:
 * ------------------------------------------------------------------------
 * All Rights Reserved
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of Digital not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 * ------------------------------------------------------------------------
 *
 * The following terms apply to the enhanced version of XBoard distributed
 * by the Free Software Foundation:
 * ------------------------------------------------------------------------
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * ------------------------------------------------------------------------
 */

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>

#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else /* not STDC_HEADERS */
extern char *getenv();
# if HAVE_STRING_H
#  include <string.h>
# else /* not HAVE_STRING_H */
#  include <strings.h>
# endif /* not HAVE_STRING_H */
#endif /* not STDC_HEADERS */
#define HI "hlelo "

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#if !HAVE_RANDOM
# if HAVE_RAND48
#  define srandom srand48
#  define random lrand48
# else /* not HAVE_RAND48 */
#  define srandom srand
#  define random rand
# endif /* not HAVE_RAND48 */
#endif /* !HAVE_RANDOM */

#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#include "common.h"
#include "zippy.h"
#include "frontend.h"
#include "backend.h"
#include "backendz.h"

static char zippyPartner[MSG_SIZ];
static char zippyLastOpp[MSG_SIZ];
static int zippyConsecGames;
static time_t zippyLastGameEnd;

void ZippyInit()
{
    char *p;

    /* Get name of Zippy lines file */
    p = getenv("ZIPPYLINES");
    if (p != NULL) {
      appData.zippyLines = p;
    }

    /* Get word that Zippy thinks is insulting */
    p = getenv("ZIPPYPINHEAD");
    if (p != NULL) {
      appData.zippyPinhead = p;
    }

    /* What password is used for remote control? */
    p = getenv("ZIPPYPASSWORD");
    if (p != NULL) {
      appData.zippyPassword = p;
    }

    /* What password is used for remote commands to gnuchess? */
    p = getenv("ZIPPYPASSWORD2");
    if (p != NULL) {
      appData.zippyPassword2 = p;
    }

    /* Joke feature for people who try an old password */
    p = getenv("ZIPPYWRONGPASSWORD");
    if (p != NULL) {
      appData.zippyWrongPassword = p;
    }

    /* While testing, I want to accept challenges from only one person
       (namely, my "anonymous" account), so I set an environment
       variable ZIPPYACCEPTONLY. */
    p = getenv("ZIPPYACCEPTONLY");
    if ( p != NULL ) {
      appData.zippyAcceptOnly = p;
    }
    
    /* Should Zippy use "i" command? */
    /* Defaults to 1=true */
    p = getenv("ZIPPYUSEI");
    if (p != NULL) {
      appData.zippyUseI = atoi(p);
    }

    /* How does Zippy handle bughouse partnering? */
    /* 0=say we can't play, 1=manual partnering, 2=auto partnering */
    p = getenv("ZIPPYBUGHOUSE");
    if (p != NULL) {
      appData.zippyBughouse = atoi(p);
    }

    /* Does Zippy abort games with Crafty? */
    /* Defaults to 0=false */
    p = getenv("ZIPPYNOPLAYCRAFTY");
    if (p != NULL) {
      appData.zippyNoplayCrafty = atoi(p);
    }

    /* What ICS command does Zippy send at game end?  Default: "gameend". */
    p = getenv("ZIPPYGAMEEND");
    if (p != NULL) {
      appData.zippyGameEnd = p;
    }

    /* What ICS command does Zippy send at game start?  Default: none. */
    p = getenv("ZIPPYGAMESTART");
    if (p != NULL) {
      appData.zippyGameStart = p;
    }

    /* Should Zippy accept adjourns? */
    /* Defaults to 0=false */
    p = getenv("ZIPPYADJOURN");
    if (p != NULL) {
      appData.zippyAdjourn = atoi(p);
    }

    /* Should Zippy accept aborts? */
    /* Defaults to 0=false */
    p = getenv("ZIPPYABORT");
    if (p != NULL) {
      appData.zippyAbort = atoi(p);
    }

    /* Should Zippy play chess variants (besides bughouse)? */
    p = getenv("ZIPPYVARIANTS");
    if (p != NULL) {
      appData.zippyVariants = p;
    }

    srandom(time(NULL));
}

/*
 * Routines to implement Zippy talking
 */


char *swifties[] = { 
    "i acclaims:", "i admonishes:", "i advertises:", "i advises:",
    "i advocates:", "i affirms:", "i alleges:", "i anathematizes:",
    "i animadverts:", "i announces:", "i apostrophizes:",
    "i appeals:", "i applauds:", "i approves:", "i argues:",
    "i articulates:", "i asserts:", "i asseverates:", "i attests:",
    "i avers:", "i avows:", "i baas:", "i babbles:", "i banters:",
    "i barks:", "i bawls:", "i bays:", "i begs:", "i belches:",
    "i bellows:", "i belts out:", "i berates:", "i beshrews:",
    "i blabbers:", "i blabs:", "i blares:", "i blasphemes:",
    "i blasts:", "i blathers:", "i bleats:", "i blithers:",
    "i blubbers:", "i blurts out:", "i blusters:", "i boasts:",
    "i brags:", "i brays:", "i broadcasts:", "i burbles:",
    "i buzzes:", "i cachinnates:", "i cackles:", "i caterwauls:",
    "i calumniates:", "i caws:", "i censures:", "i chants:",
    "i chatters:", "i cheeps:", "i cheers:", "i chides:", "i chins:",
    "i chirps:", "i chortles:", "i chuckles:", "i claims:",
    "i clamors:", "i clucks:", "i commands:", "i commends:",
    "i comments:", "i commiserates:", "i communicates:",
    "i complains:", "i concludes:", "i confabulates:", "i confesses:",
    "i coos:", "i coughs:", "i counsels:", "i cries:", "i croaks:",
    "i crows:", "i curses:", "i daydreams:", "i debates:",
    "i declaims:", "i declares:", "i delivers:", "i denounces:",
    "i deposes:", "i directs:", "i discloses:", "i disparages:",
    "i discourses:", "i divulges:", "i documents:", "i drawls:",
    "i dreams:", "i drivels:", "i drones:", "i effuses:",
    /*"i ejaculates:",*/ "i elucidates:", "i emotes:", "i endorses:",
    "i enthuses:", "i entreats:", "i enunciates:", "i eulogizes:",
    "i exclaims:", "i execrates:", "i exhorts:", "i expatiates:",
    "i explains:", "i explicates:", "i explodes:", "i exposes:",
    "i exposits:", "i expounds:", "i expresses:", "i extols:",
    "i exults:", "i fantasizes:", "i fibs:", "i filibusters:",
    "i flatters:", "i flutes:", "i fools:", "i free-associates:",
    "i fulminates:", "i gabbles:", "i gabs:", "i gasps:",
    "i giggles:", "i gossips:", "i gripes:", "i groans:", "i growls:",
    "i grunts:", "i guesses:", "i guffaws:", "i gushes:", "i hails:",
    "i hallucinates:", "i harangues:", "i harmonizes:", "i hectors:",
    "i hints:", "i hisses:", "i hollers:", "i honks:", "i hoots:",
    "i hosannas:", "i howls:", "i hums:", "i hypothecates:",
    "i hypothesizes:", "i imagines:", "i implies:", "i implores:",
    "i imprecates:", "i indicates:", "i infers:",
    "i informs everyone:",  "i instructs:", "i interjects:", 
    "i interposes:", "i intimates:", "i intones:", "i introspects:",
    "i inveighs:", "i jabbers:", "i japes:", "i jests:", "i jibes:",
    "i jives:", "i jokes:", "i joshes:", "i keens:", "i laments:",
    "i lauds:", "i laughs:", "i lectures:", "i lies:", "i lilts:",
    "i lisps:", "i maintains:", "i maledicts:", "i maunders:",
    "i meows:", "i mewls:", "i mimes:", "i minces:", "i moans:",
    "i moos:", "i mourns:", "i mouths:", "i mumbles:", "i murmurs:",
    "i muses:", "i mutters:", "i nags:", "i natters:", "i neighs:",
    "i notes:", "i nuncupates:", "i objurgates:", "i observes:",
    "i offers:", "i oinks:", "i opines:", "i orates:", "i orders:",
    "i panegyrizes:", "i pantomimes:", "i pants:", "i peals:",
    "i peeps:", "i perorates:", "i persuades:", "i petitions:",
    "i phonates:", "i pipes up:", "i pitches:", "i pleads:",
    "i points out:", "i pontificates:", "i postulates:", "i praises:",
    "i prates:", "i prattles:", "i preaches:", "i prescribes:",
    "i prevaricates:", "i proclaims:", "i projects:", "i pronounces:",
    "i proposes:", "i proscribes:", "i quacks:", "i queries:",
    "i questions:", "i quips:", "i quotes:", "i rages:", "i rambles:",
    "i rants:", "i raps:", "i rasps:", "i rattles:", "i raves:",
    "i reacts:", "i recites:", "i recommends:", "i records:",
    "i reiterates:", "i rejoins:", "i releases:", "i remarks:",
    "i reminisces:", "i remonstrates:", "i repeats:", "i replies:",
    "i reports:", "i reprimands:", "i reproaches:", "i reproves:",
    "i resounds:", "i responds:", "i retorts:", "i reveals:",
    "i reviles:", "i roars:", "i rumbles:", "i sanctions:",
    "i satirizes:", "i sauces:", "i scolds:", "i screams:",
    "i screeches:", "i semaphores:", "i sends:", "i sermonizes:",
    "i shrieks:", "i sibilates:", "i sighs:", "i signals:",
    "i signifies:", "i signs:", "i sings:", "i slurs:", "i snaps:",
    "i snarls:", "i sneezes:", "i snickers:", "i sniggers:",
    "i snivels:", "i snores:", "i snorts:", "i sobs:",
    "i soliloquizes:", "i sounds off:", "i sounds out:", "i speaks:",
    "i spews:", "i spits out:", "i splutters:", "i spoofs:",
    "i spouts:", "i sputters:", "i squalls:", "i squawks:",
    "i squeaks:", "i squeals:", "i stammers:", "i states:",
    "i stresses:", "i stutters:", "i submits:", "i suggests:",
    "i summarizes:", "i sums up:", "i swears:", "i talks:",
    "i tattles:", "i teases:", "i telegraphs:", "i testifies:",
    "i threatens:", "i thunders:", "i titters:", "i tongue-lashes:",
    "i toots:", "i transcribes:", "i transmits:", "i trills:",
    "i trumpets:", "i twaddles:", "i tweets:", "i twitters:",
    "i types:", "i upbraids:", "i urges:", "i utters:", "i ventures:",
    "i vibrates:", "i vilifies:", "i vituperates:", "i vocalizes:",
    "i vociferates:", "i voices:", "i waffles:", "i wails:",
    "i warbles:", "i warns:", "i weeps:", "i wheezes:", "i whimpers:",
    "i whines:", "i whinnies:", "i whistles:", "i wisecracks:",
    "i witnesses:", "i woofs:", "i writes:", "i yammers:", "i yawps:",
    "i yells:", "i yelps:", "i yodels:", "i yowls:", "i zings:",
};

#define MAX_SPEECH 250

void Speak(how, whom) 
     char *how, *whom;
{
    static FILE *zipfile = NULL;
    static struct stat zipstat;
    char zipbuf[MAX_SPEECH + 1];
    static time_t lastShout = 0;
    time_t now;
    char  *p;
    int c, speechlen;
    Boolean done;
		
    if (strcmp(how, "shout") == 0) {
	now = time((time_t *) NULL);
	if (now - lastShout < 1*60) return;
	lastShout = now;
	if (appData.zippyUseI) {
	    how = swifties[random() % (sizeof(swifties)/sizeof(char *))];
	}
    }

    if (zipfile == NULL) {
	zipfile = fopen(appData.zippyLines, "r");
	if (zipfile == NULL) {
	    DisplayFatalError("Can't open Zippy lines file", errno, 1);
	    return;
	}
	fstat(fileno(zipfile), &zipstat);
    }
		
    for (;;) {
	fseek(zipfile, random() % zipstat.st_size, 0);
	do {
	  c = getc(zipfile);
	} while (c != NULLCHAR && c != '^' && c != EOF);
	if (c == EOF) continue;
	while ((c = getc(zipfile)) == '\n') ;
	if (c == EOF) continue;
	break;
    }
    done = FALSE;

    strcpy(zipbuf, how);
    strcat(zipbuf, " ");
    if (whom != NULL) {
	strcat(zipbuf, whom);
	strcat(zipbuf, " ");
    }
    speechlen = strlen(zipbuf);
    p = zipbuf + speechlen;

    while (++speechlen < MAX_SPEECH) {
	if (c == NULLCHAR || c == '^') {
	    *p++ = '\n';
	    *p = '\0';
	    SendToICS(zipbuf);
	    return;
	} else if (c == '\n') {
	    *p++ = ' ';
	    do {
		c = getc(zipfile);
	    } while (c == ' ');
	} else if (c == EOF) {
	    break;
	} else {
	    *p++ = c;
	    c = getc(zipfile);
	}
    }
    /* Tried to say something too long, or junk at the end of the
       file.  Try something else. */
    Speak(how, whom);  /* tail recursion */
}

int ZippyCalled(str)
     char *str;
{
    return ics_handle[0] != NULLCHAR && StrCaseStr(str, ics_handle) != NULL;
}

static char opp_name[128][32];
static int num_opps=0;

int ZippyControl(buf, i)
     char *buf;
     int *i;
{
    char *player, *p;
    char reply[MSG_SIZ];

#if TRIVIA
#include "trivia.c"
#endif

    /* Possibly reject Crafty as opponent */
    if (appData.zippyPlay && appData.zippyNoplayCrafty && forwardMostMove < 4
	&& looking_at(buf, i, "* kibitzes: Hello from Crafty")) {
        player = StripHighlightAndTitle(star_match[0]);
	if ((gameMode == IcsPlayingWhite &&
	     StrCaseCmp(player, gameInfo.black) == 0) ||
	    (gameMode == IcsPlayingBlack &&
	     StrCaseCmp(player, gameInfo.white) == 0)) {
	    SendToICS("say This computer does not play Crafty clones\n");
	    SendToICS("abort\n");
	    sprintf(reply, "+noplay %s\n", player);
	    SendToICS(reply);
	}
	return TRUE;
    }

    /* If this is a computer, save the name.  Then later, once the */
    /* game is really started, we will send the "computer" notice to */
    /* the engine.  */ 
    if (appData.zippyPlay &&
	looking_at(buf, i, "* is in the computer list")) {
	int i;
	for (i=0;i<num_opps;i++)
	  if (!strcmp(opp_name[i],star_match[0])) break;
	if (i >= num_opps) strcpy(opp_name[num_opps++],star_match[0]);
    }
    if (appData.zippyPlay && looking_at(buf, i, "* * is a computer *")) {
	int i;
	for (i=0;i<num_opps;i++)
	  if (!strcmp(opp_name[i],star_match[1])) break;
	if (i >= num_opps) strcpy(opp_name[num_opps++],star_match[1]);
    }

    /* Tells and says */
    if (appData.zippyPlay &&
	(looking_at(buf, i, "* offers to be your bughouse partner") ||
	 looking_at(buf, i, "* tells you: [automatic message] I chose you"))) {
	player = StripHighlightAndTitle(star_match[0]);
	if (appData.zippyBughouse > 1) {
	    sprintf(reply, "partner %s\n", player);
	    SendToICS(reply);
	    if (strcmp(zippyPartner, player) != 0) {
		strcpy(zippyPartner, player);
		SendToProgram(reply, &first);
	    }
	} else if (appData.zippyBughouse > 0) {
	    sprintf(reply, "decline %s\n", player);
	    SendToICS(reply);
	} else {
	    sprintf(reply, "tE %s This computer cannot play bughouse\n",
		    player);
	    SendToICS(reply);
	}
	return TRUE;
    }

    if (appData.zippyPlay && appData.zippyBughouse &&
	looking_at(buf, i, "* agrees to be your partner")) {
	player = StripHighlightAndTitle(star_match[0]);
	sprintf(reply, "partner %s\n", player);
	if (strcmp(zippyPartner, player) != 0) {
	    strcpy(zippyPartner, player);
	    SendToProgram(reply, &first);
	}
	return TRUE;
    }

    if (appData.zippyPlay && appData.zippyBughouse &&
	(looking_at(buf, i, "are no longer *'s partner") ||
	 looking_at(buf, i,
		    "* tells you: [automatic message] I'm no longer your"))) {
	player = StripHighlightAndTitle(star_match[0]);
	if (strcmp(zippyPartner, player) == 0) {
	    zippyPartner[0] = NULLCHAR;
	    SendToProgram("partner\n", &first);
	}
	return TRUE;
    }

    if (appData.zippyPlay && appData.zippyBughouse &&
	(looking_at(buf, i, "no longer have a bughouse partner") ||
	 looking_at(buf, i, "partner has disconnected") ||
	 looking_at(buf, i, "partner has just chosen a new partner"))) {
      zippyPartner[0] = NULLCHAR;
      SendToProgram("partner\n", &first);
      return TRUE;
    }

    if (appData.zippyPlay && appData.zippyBughouse &&
	looking_at(buf, i, "* (your partner) tells you: *")) {
	/* This pattern works on FICS but not ICC */
	player = StripHighlightAndTitle(star_match[0]);
	if (strcmp(zippyPartner, player) != 0) {
	    strcpy(zippyPartner, player);
	    sprintf(reply, "partner %s\n", player);
	    SendToProgram(reply, &first);
	}
	sprintf(reply, "ptell %s\n", star_match[1]);
	SendToProgram(reply, &first);
	return TRUE;
    }

    if (looking_at(buf, i, "* tells you: *") ||
	looking_at(buf, i, "* says: *")) {
	player = StripHighlightAndTitle(star_match[0]);
	if (appData.zippyPassword[0] != NULLCHAR &&
	    strncmp(star_match[1], appData.zippyPassword,
		    strlen(appData.zippyPassword)) == 0) {
	    p = star_match[1] + strlen(appData.zippyPassword);
	    while (*p == ' ') p++;
	    SendToICS(p);
	    SendToICS("\n");
	} else if (appData.zippyPassword2[0] != NULLCHAR &&
	    strncmp(star_match[1], appData.zippyPassword2,
		    strlen(appData.zippyPassword2)) == 0) {
	    p = star_match[1] + strlen(appData.zippyPassword2);
	    while (*p == ' ') p++;
	    SendToProgram(p, &first);
	    SendToProgram("\n", &first);
	} else if (appData.zippyWrongPassword[0] != NULLCHAR &&
	    strncmp(star_match[1], appData.zippyWrongPassword,
		    strlen(appData.zippyWrongPassword)) == 0) {
	    p = star_match[1] + strlen(appData.zippyWrongPassword);
	    while (*p == ' ') p++;
	    sprintf(reply, "wrong %s\n", player);
	    SendToICS(reply);
	} else if (appData.zippyBughouse &&
		   strcmp(player, zippyPartner) == 0) {
	    SendToProgram("ptell ", &first);
	    SendToProgram(star_match[1], &first);
	    SendToProgram("\n", &first);
	} else if (strncmp(star_match[1], HI, 6) == 0) {
	    extern char* programVersion;
	    sprintf(reply, "teL %s %s\n", player, programVersion);
	    SendToICS(reply);
	} else if (strncmp(star_match[1], "W0W!! ", 6) == 0) {
	    extern char* programVersion;
	    sprintf(reply, "Tel %s %s\n", player, programVersion);
	    SendToICS(reply);
	} else if (appData.zippyTalk && ((random() % 10) < 9)) {
	    if (strcmp(player, ics_handle) != 0) {
		Speak("tell", player);
	    }
	}
	return TRUE;
    }

    if (looking_at(buf, i, "* spoofs you:")) {
        player = StripHighlightAndTitle(star_match[0]);
        sprintf(reply, "spoofedby %s\n", player);
        SendToICS(reply);
    }
    return FALSE;
}

int ZippyConverse(buf, i)
     char *buf;
     int *i;
{
    static char lastgreet[MSG_SIZ];
    int oldi;

    /* Shouts and emotes */
    if (looking_at(buf, i, "--> * *") ||
	looking_at(buf, i, "* shouts: *")) {
	char *player = StripHighlightAndTitle(star_match[0]);
	if (strcmp(player, ics_handle) == 0) {
	    return TRUE;
	} else if (appData.zippyPinhead[0] != NULLCHAR &&
		   StrCaseStr(star_match[1], appData.zippyPinhead) != NULL) {
	    SendToICS("insult ");
	    SendToICS(player);
	    SendToICS("\n");
	} else if (ZippyCalled(star_match[1])) {
	    Speak("shout", NULL);
	}
	return TRUE;
    }

    if (looking_at(buf, i, "* kibitzes: *") && ((random() % 10) < 9)) {
	char *player = StripHighlightAndTitle(star_match[0]);

	if (strcmp(player, ics_handle) != 0) {
	    Speak("kibitz", NULL);
	    return TRUE;
	}
    }

    if (looking_at(buf, i, "* whispers: *") && ((random() % 10) < 9)) {
	char *player = StripHighlightAndTitle(star_match[0]);

	if (strcmp(player, ics_handle) != 0) {
	    Speak("whisper", NULL);
	    return TRUE;
	}
    }

    if ((looking_at(buf, i, "You have * message") &&
	 atoi(star_match[0]) != 0) ||
	looking_at(buf, i, "* has left a message for you") ||
	looking_at(buf, i, "* just sent you a message")) {
	SendToICS("messages\nclearmessages\nclearmessages *\n");
	return TRUE;
    }

    /* Messages */
    if ((looking_at(buf, i, ". * (*:*): *") && isdigit(star_match[1][0])) ||
	 looking_at(buf, i, ". * at *:*: *")) {
	FILE *f;
	char *player = StripHighlightAndTitle(star_match[0]);

	if (strcmp(player, ics_handle) != 0) {
	    if ((random() % 10) < 9)
	      Speak("message", player);
	    f = fopen("zippy.messagelog", "a");
	    fprintf(f, "%s (%s:%s): %s\n", player,
		    star_match[1], star_match[2], star_match[3]);
	    fclose(f);
	}
	return TRUE;
    }

    /* Channel tells */
    oldi = *i;
    if (looking_at(buf, i, "*(*: *")) {
	char *player;
	char *channel;
	if (star_match[0][0] == NULLCHAR  ||
	    strchr(star_match[0], ' ') ||
	    strchr(star_match[1], ' ')) {
	    /* Oops, did not want to match this; probably a message */
	    *i = oldi;
	    return FALSE;
	}
	player = StripHighlightAndTitle(star_match[0]);
	channel = strrchr(star_match[1], '(');
	if (channel == NULL) {
	  channel = star_match[1];
	} else {
	  channel++;
	}
	channel[strlen(channel)-1] = NULLCHAR;
#if 0
	/* Always tell to the channel (probability 90%) */
	if (strcmp(player, ics_handle) != 0 && (random() % 10) < 9) {
	    Speak("tell", channel);
	}
#else
	/* Tell to the channel only if someone mentions our name */
	if (ZippyCalled(star_match[2])) {
	    Speak("tell", channel);
	}
#endif
	return TRUE;
    }

    if (looking_at(buf, i, "Notification: * has arrived")) {
	if ((random() % 3) == 0) {
	    char *player = StripHighlightAndTitle(star_match[0]);
	    strcpy(lastgreet, player);
	    SendToICS("greet ");
	    SendToICS(player);
	    SendToICS("\n");
	    Speak("tell", player);
	}
    }	

    if (looking_at(buf, i, "Notification: * has departed")) {
	if ((random() % 3) == 0) {
	    char *player = StripHighlightAndTitle(star_match[0]);
	    SendToICS("farewell ");
	    SendToICS(player);
	    SendToICS("\n");
	}
    }	

    if (looking_at(buf, i, "Not sent -- * is censoring you")) {
	char *player = StripHighlightAndTitle(star_match[0]);
	if (strcmp(player, lastgreet) == 0) {
	    SendToICS("-notify ");
	    SendToICS(player);
	    SendToICS("\n");
	}
    }	

    if (looking_at(buf, i, "command is currently turned off")) {
	appData.zippyUseI = 0;
    }

    return FALSE;
}

void ZippyGameStart(white, black)
     char *white, *black;
{
    if (appData.zippyGameStart[0] != NULLCHAR) {
      SendToICS(appData.zippyGameStart);
      SendToICS("\n");
    }
}

void ZippyGameEnd(result, resultDetails)
     ChessMove result;
     char *resultDetails;
{
    if (appData.zippyAcceptOnly[0] == NULLCHAR) {
      SendToICS(appData.zippyGameEnd);
      SendToICS("\n");
    }
    zippyLastGameEnd = time(0);
}

/*
 * Routines to implement Zippy playing chess
 */

void ZippyHandleChallenge(srated, swild, sbase, sincrement, opponent)
     char *srated, *swild, *sbase, *sincrement, *opponent;
{
    char buf[MSG_SIZ];
    int base, increment;
    char rated;
    VariantClass variant;
    char *varname;

    rated = srated[0];
    variant = StringToVariant(swild);
    varname = VariantName(variant);
    base = atoi(sbase);
    increment = atoi(sincrement);

    /* If desired, you can insert more code here to decline matches based
       on rated, variant, base, and increment.  Otherwise they are mostly
       not used.  It is probably best to do this with formulas anyway... */

    if ((variant == VariantLoadable) || 
	(StrStr(appData.zippyVariants, varname) == NULL)) {
        sprintf(buf,
	    "telL %s This computer cannot play %s [%s], only %s\ndecline %s\n",
		opponent, swild, varname, appData.zippyVariants, opponent);
	SendToICS(buf);
	return;
    }

    /* Are we blocking match requests from all but one person? */
    if (appData.zippyAcceptOnly[0] != NULLCHAR &&
	StrCaseCmp(opponent, appData.zippyAcceptOnly)) {
        /* Yes, and this isn't him.  Ignore challenge. */
	return;
    }
    
    /* Too many consecutive games with same opponent?  If so, make him
       wait until someone else has played or a timeout has elapsed. */
    if (appData.zippyMaxGames &&
	strcmp(opponent, zippyLastOpp) == 0 &&
	zippyConsecGames >= appData.zippyMaxGames &&
	difftime(time(0), zippyLastGameEnd) < appData.zippyReplayTimeout) {
      sprintf(buf, "tell %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.\ndecline %s\n", opponent, zippyConsecGames, ics_handle, appData.zippyReplayTimeout, opponent);
      SendToICS(buf);
      return;
    }

    sprintf(buf, "accept %s\n", opponent);
    SendToICS(buf);
    if (appData.zippyTalk) {
      Speak("tell", opponent);
    }
}


int ZippyMatch(buf, i)
     char *buf;
     int *i;
{
    /* Accept matches */
    if (looking_at(buf, i, "* * match * * requested with * (*)")) {

	ZippyHandleChallenge(star_match[0], star_match[1],
			     star_match[2], star_match[3],
			     StripHighlightAndTitle(star_match[4]));
	return TRUE;
    }

    /* Old FICS 0-increment form */
    if (looking_at(buf, i, "* * match * requested with * (*)")) {

	ZippyHandleChallenge(star_match[0], star_match[1],
			     star_match[2], "0",
			     StripHighlightAndTitle(star_match[3]));
	return TRUE;
    }

    if (looking_at(buf, i,
		   "* has made an alternate proposal of * * match * *.")) {

	ZippyHandleChallenge(star_match[1], star_match[2],
			     star_match[3], star_match[4],
			     StripHighlightAndTitle(star_match[0]));
	return TRUE;
    }

    /* FICS wild/nonstandard forms */
    if (looking_at(buf, i, "Challenge: * (*) *(*) * * * * Loaded from *")) {
	/* note: star_match[2] can include "[white] " or "[black] "
	   before our own name. */
	ZippyHandleChallenge(star_match[4], star_match[8],
			     star_match[6], star_match[7],
			     StripHighlightAndTitle(star_match[0]));
	return TRUE;
    }

    if (looking_at(buf, i,
		   "Challenge: * (*) *(*) * * * * : * * Loaded from *")) {
	/* note: star_match[2] can include "[white] " or "[black] "
	   before our own name. */
	ZippyHandleChallenge(star_match[4], star_match[10],
			     star_match[8], star_match[9],
			     StripHighlightAndTitle(star_match[0]));
	return TRUE;
    }

    /* Regular forms */
    if (looking_at(buf, i, "Challenge: * (*) *(*) * * * * : * *") |
	looking_at(buf, i, "Challenge: * (*) *(*) * * * * * *")) {
	/* note: star_match[2] can include "[white] " or "[black] "
	   before our own name. */
	ZippyHandleChallenge(star_match[4], star_match[5],
			     star_match[8], star_match[9],
			     StripHighlightAndTitle(star_match[0]));
	return TRUE;
    }

    if (looking_at(buf, i, "Challenge: * (*) *(*) * * * *")) {
	/* note: star_match[2] can include "[white] " or "[black] "
	   before our own name. */
	ZippyHandleChallenge(star_match[4], star_match[5],
			     star_match[6], star_match[7],
			     StripHighlightAndTitle(star_match[0]));
	return TRUE;
    }

    if (looking_at(buf, i, "offers you a draw")) {
        if (first.sendDrawOffers) {
	    SendToProgram("draw\n", &first);
	}
	return TRUE;
    }

    if (looking_at(buf, i, "requests that the game be aborted") ||
        looking_at(buf, i, "would like to abort")) {
	if (appData.zippyAbort ||
	    (gameMode == IcsPlayingWhite && whiteTimeRemaining < 0) ||
	    (gameMode == IcsPlayingBlack && blackTimeRemaining < 0)) {
	    SendToICS("abort\n");
	} else {
	    SendToICS("decline abort\n");
	    if (appData.zippyTalk)
	      SendToICS("say Whoa no!  I am having FUN!!\n");
	    else
	      SendToICS("say Sorry, this computer doesn't accept aborts.\n");
	}
	return TRUE;
    }

    if (looking_at(buf, i, "requests adjournment") ||
	looking_at(buf, i, "would like to adjourn")) {
      if (appData.zippyAdjourn) {
	SendToICS("adjourn\n");
      } else {
	SendToICS("decline adjourn\n");
	if (appData.zippyTalk)
	  SendToICS("say Whoa no!  I am having FUN playing NOW!!\n");
	else
	  SendToICS("say Sorry, this computer doesn't accept adjourns.\n");
      }
      return TRUE;
    }

    return FALSE;
}

void ZippyFirstBoard(moveNum, basetime, increment)
     int moveNum, basetime, increment;
{
    char buf[MSG_SIZ];
    int w, b;
    char *opp = (gameMode==IcsPlayingWhite ? gameInfo.black : gameInfo.white);

    /* Initialize chess program with data from the first board 
       of a new or resumed game */

    /* Send the variant command if needed */
    if (gameInfo.variant != VariantNormal) {
      sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
      SendToProgram(buf, &first);
    }

    if (startedFromSetupPosition || (!appData.getMoveList && moveNum > 0)) {
	SendBoard(&first, boards[moveNum], !WhiteOnMove(moveNum));
    }

    sprintf(buf, "level 0 %d %d\n", basetime, increment);
    SendToProgram(buf, &first);

    /* Count consecutive games from one opponent */
    if (strcmp(opp, zippyLastOpp) == 0) {
      zippyConsecGames++;
    } else {
      zippyConsecGames = 1;
      strcpy(zippyLastOpp, opp);
    }

    /* Send the "computer" command if the opponent is in the list
       we've been gathering. */
    for (w=0; w<num_opps; w++) {
	if (!strcmp(opp_name[w], opp)) {
	    SendToProgram("computer\n",&first);
	    break;
	}
    }

    /* Ratings might be < 0 which means "we haven't seen a ratings
       message from ICS." Send Crafty 0 in that case */
    w = (gameInfo.whiteRating >= 0) ? gameInfo.whiteRating : 0;
    b = (gameInfo.blackRating >= 0) ? gameInfo.blackRating : 0;
    
    firstMove = FALSE;
    if (gameMode == IcsPlayingWhite) {
	sprintf(buf, "name %s\n", gameInfo.black);  /* for Crafty */
	SendToProgram(buf, &first);
	strcpy(ics_handle, gameInfo.white);
	sprintf(buf, "rating %d %d\n", w, b);
	SendToProgram(buf, &first);
	if (appData.getMoveList) {
	    /* Resume from adjournments with move list, to be sent later */
	    if (moveNum == 0) {
		/* Not an adjournment */
		SendToProgram("black\n", &first); /*gnu kludge*/
		SendTimeRemaining(&first, TRUE);
		SendToProgram("white\ngo\n", &first);
	    }
	} else {
	    /* Resume from adjournments with position only, sent above */
	    if (WhiteOnMove(moveNum)) {
		SendToProgram("black\n", &first); /*gnu kludge*/
		SendTimeRemaining(&first, TRUE);
		SendToProgram("white\ngo\n", &first);
	    } else {
		firstMove = TRUE; /* need to send whiteString later */
	    }
	}
    } else if (gameMode == IcsPlayingBlack) {
	sprintf(buf, "name %s\n", gameInfo.white);
	SendToProgram(buf, &first);
	strcpy(ics_handle, gameInfo.black);
	sprintf(buf, "rating %d %d\n", b, w);
	SendToProgram(buf, &first);
	if (appData.getMoveList) {
	    /* Resume from adjournments with move list, to be sent later */
	    /* Nothing to do here */
	} else {
	    /* Resume from adjournments with position only, sent above */
	    if (!WhiteOnMove(moveNum)) {
		SendToProgram("white\n", &first); /*gnu kludge*/
		SendTimeRemaining(&first, FALSE);
		SendToProgram("black\ngo\n", &first);
	    } else {
		/* nothing to do here */
	    }
	}
    }
}


void
ZippyHoldings(white_holding, black_holding, new_piece)
     char *white_holding, *black_holding, *new_piece;
{
    char buf[MSG_SIZ];
    if (gameMode != IcsPlayingBlack && gameMode != IcsPlayingWhite) return;
    sprintf(buf, "holding [%s] [%s] %s\n",
	    white_holding, black_holding, new_piece);
    SendToProgram(buf, &first);
}
