/*
 *
 * botutil2.c -- utilities for the robot, part 2
 *
 * This code is copyright 1995 by James A. Cherry, jac@doe.carleton.ca.
 * It is not to be redistributed or modified without his permission.
 *
 */

#include "funddefs.h"
#include "globals.h"
#include "socket.h"
#include "botutil.h"
#include "proto.h"

void ProcessOpenBracket( string )
  char *string;
/* Deal with a `(' received from the DOoM. */
{
    char *p1, *p2;
    int t;

    p1 = strstr( string, "now hold " );
    if( p1 == NULL ) return;
    p1 += 9;
    t = 0;
    p2 = &(NewTiles[0]);
    while( *p1 != '.' ) {
	*p2++ = *p1++;
	t++;
    }
    *p2 = '\0';
    NumTiles = t;
}

void ProcessGreaterThan( string )
    char *string;
/* Deal with a `>' received from the DOoM. */
{
    char *p1;
    int i;

/* If ACBot has just withdrawn after losing a challenge, don't reset. */
    if( strstr( string, Opponent ) == NULL &&
	( strstr( string, PlrName ) == NULL || strstr( string, "--" ) == NULL
	  || strstr( string, "forfeits turn" ) == NULL ) ) {
	QueueCommand( "\"Uh oh...I saw a move that wasn't from you.  Resetting.", 0 );
	GoHome();
	return;
    }
    if( strstr( string, "--" ) != NULL
	|| strstr( string, "forfeits turn" ) != NULL ) {
	TheyMove = THEY_FORFEIT;
    } else if( strstr( string, "-_" ) != NULL
	       || strstr( string, "exchange" ) != NULL ) {
	TheyMove = THEY_EXCHANGE;
    } else if( strstr( string, "- " ) != NULL
	       || strstr( string, "passes turn" ) != NULL ) {
	TheyMove = THEY_PASS;
    } else {
/* Parse play */
	p1 = string;
	while( *p1 != ':' ) p1++;
	p1++;
	while( *p1 == ' ' ) p1++;
	if( *p1 == '-' || *p1 == 'u' ) {
	    TheyMove = THEY_UNDO;
	} else {
	    if( *p1 >= '0' && *p1 <= '9' ) {
		TheyDir = 0;
		TheyY = *p1++ - '0';
		if( *p1 >= '0' && *p1 <= '9' )
		    TheyY = TheyY * 10 + *p1++ - '0';
		TheyX = *p1++ - 'a' + 1;
	    } else {
		TheyDir = 1;
		TheyX = *p1++ - 'a' + 1;
		TheyY = *p1++ - '0';
		if( *p1 >= '0' && *p1 <= '9' )
		    TheyY = TheyY * 10 + *p1++ - '0';
	    }
	    while( *p1 == ' ' ) p1++;
	    i = 0;
	    while( ( *p1 >= 'A' && *p1 <= 'Z' ) || ( *p1 >= 'a' && *p1 <= 'z' ) )
		TheyWord[i++] = *p1++;
	    TheyWord[i] = '\0';
	    TheyMove = THEY_PLAY;
	}
    }
}

void ProcessLogLine( string )
    char *string;
/* Deal with a line from a `log' command. */
{
    char *p1, they_x;
    int i, j;

    if( strstr( string, "--" ) != NULL
	|| strstr( string, "forfeits turn" ) != NULL ) {
	TheyMove = THEY_FORFEIT;
    } else if( strstr( string, "-_" ) != NULL
	       || strstr( string, "exchange" ) != NULL ) {
	TheyMove = THEY_EXCHANGE;
    } else if( strstr( string, "- " ) != NULL
	       || strstr( string, "passes turn" ) != NULL ) {
	TheyMove = THEY_PASS;
    } else {
/* Parse play */
	for( p1 = string; *p1 != ':'; p1++ );
	for( p1++; *p1 == ' '; p1++ );
	for( ; *p1 == '_' || *p1 == ' '; p1++ );
	if( *p1 >= '0' && *p1 <= '9' ) {
	    TheyDir = 0;
	    sscanf( p1, "%d%c", &TheyY, &they_x );
	    TheyX = they_x - 'a' + 1;
	} else {
	    TheyDir = 1;
	    sscanf( p1, "%c%d", &they_x, &TheyY );
	    TheyX = they_x - 'a' + 1;
	}
	for( ; *p1 != ' '; p1++ );
	for( ; *p1 == ' '; p1++ );
	sscanf( p1, "%s", &(NewWords[0].letters[0]) );
	MyToUpper( &(NewWords[0].letters[0]) );
	p1 += strlen( &(NewWords[0].letters[0]) );
	for( ; *p1 == ' '; p1++ );
	for( ; *p1 != ' '; p1++ );  /* skip turn score */
	for( ; *p1 == ' '; p1++ );
	for( ; *p1 != ' '; p1++ );  /* skip total score */
	for( ; *p1 == ' '; p1++ );
	for( j = 1; *p1 != '\n'; ) {
	    for( i = 0; ( *p1 >= 'A' && *p1 <= 'Z' ) ||
			    ( *p1 >= 'a' && *p1 <= 'z' ); p1++, i++ ) {
		NewWords[j].letters[i] = *p1;
	    }
	    NewWords[j].letters[i] = '\0';
	    MyToUpper( &(NewWords[j++].letters[0]) );
	    if( *p1 != '\n' ) {
		p1 += 2; /* skip comma and space */
	    }
	}
	if( j < 8 ) NewWords[j].letters[0] = '\0';
	TheyMove = THEY_PLAY;
    }
}

void ProcessTrackLine( string )
    char *string;
/* Process the results of a `track -tagged' command. */
{
    char *p1;
    int found = 0;

    if( string[0] == 'U' ) {
	for( p1 = string + 13; *p1 != '\n'; p1++ ) {
	    if( *p1 == CH_BL || ( *p1 >= 'A' && *p1 <= 'Z' ) ) found++;
	}
	if( found == 0 ) {
	    ThemOut = 1;
	} else {
	    ThemOut = 0;
	}
    } else {
	ThemOut = 0;
    }
}

void EndTaggedTrack()
/* After doing a tagged `log' and a `track', decide what the next state should
   be. */
{
    int i;
    int phony = 0;

    if( TheyMove == THEY_PLAY ) {
	for( i = 0; i < 8 && NewWords[i].letters[0] != '\0'; i++ ) {
	    if( BinSearch( &(NewWords[i].letters[0]) ) == 0 ) {
		phony = 1;
	    }
	}
	if( phony ) {
	    ChallWords = i;
	    ChangeState( INITIATE_CHALLENGE );
	    DoneTaggedTrack = 1;
	} else {
	    ChangeState( MY_MOVE_F );
	    DoneTaggedTrack = 0;
	}
    } else {
	ChangeState( MY_MOVE_F );
	DoneTaggedTrack = 0;
    }
}

int OspdRuling( string )
    char *string;
/* Figure out result of a dictionary lookup command. */
{
    if( strcmp( string, "The word is acceptable.\n" ) == 0
	|| strcmp( string, "Both words are acceptable.\n" ) == 0
	|| strcmp( string, "All words are acceptable.\n" ) == 0 ) {
	return( RULING_ACC );
    } else if( strcmp( string, "The word is unacceptable.\n" ) == 0
	       || strcmp( string, "At least one word is not acceptable.\n" ) == 0 ) {
	return( RULING_UNACC );
    } else return( RULING_NONE );
}

void CopyMove( sourcelet, destlet, placed )
    struct plrlet *sourcelet, *destlet;
    int placed;
/* Copy a move between plrlet structures. */
{
    int i;
    
    for( i = 0; i < placed; i++ ) {
	destlet[i].x = sourcelet[i].x;
	destlet[i].y = sourcelet[i].y;
	destlet[i].letter = sourcelet[i].letter;
	destlet[i].blankchar = sourcelet[i].blankchar;
    }
}

void save_game_state()
/* Save game state for restoring if forfeit must be made. */
{
    int i, j, k;

    for( i = 0; i < 16; i++ )
	for( j = 0; j < 16; j++ )
	    TBoard[i][j] = Board[i][j];
    for( i = 0; i < 27; i++ ) {
	for( j = 0; j < 16; j++ )
	    for( k = 0; k < 16; k++ ) {
		THHook[j][k][i] = HHook[j][k][i];
		TVHook[j][k][i] = VHook[j][k][i];
	    }
	TTilesLeft[i] = TilesLeft[i];
	TTrack[i] = Track[i];
    }
}

void RestoreGameState()
/* Restore game state after a save. */
{
    int i, j, k;

    for( i = 0; i < 16; i++ )
	for( j = 0; j < 16; j++ )
	    Board[i][j] = TBoard[i][j];
    for( i = 0; i < 27; i++ ) {
	for( j = 0; j < 16; j++ )
	    for( k = 0; k < 16; k++ ) {
		HHook[j][k][i] = THHook[j][k][i];
		VHook[j][k][i] = TVHook[j][k][i];
	    }
	TilesLeft[i] = TTilesLeft[i];
	Track[i] = TTrack[i];
    }
}

void CommitLastMove( plr, newlet, placed, dx, dy )
    int plr;
    struct plrlet *newlet;
    int placed, dx, dy;
/* If the opponent doesn't challenge our play, assume it's good, and update
   internal data structures. */
{
    int w, i;

    if( placed == -1 ) return;
    if( plr == TheirPlrNum )
	save_game_state();
    w = FindNewWords( newlet, placed, dx, dy );
    DisplayWords( plr, newlet, w, placed );
    UpdateHooks( newlet, placed, dx, dy );
    if( plr == MyPlrNum ) {
	for( i = 0; i < w; i++ ) {
	    strcpy( MyWList[MyWLen], &(NewWords[i].letters[0]) );
	    MyWLen++;
	}
    } else {
	for( i = 0; i < w; i++ ) {
	    strcpy( TheirWList[TheirWLen], &(NewWords[i].letters[0]) );
	    TheirWLen++;
	}
    }
    if( plr == TheirPlrNum ) {
	TheirWords = w;
	TheirPlaced = placed;
	TheirDX = dx;
	TheirDY = dy;
    }
}

void set_ct( ct )
    int ct;
/* Set the ChallengeType variable. */
{
    if( ChallengeType == CHALLENGE_NONE )
	ChallengeType = ct;
    else if( ChallengeType != ct )
	ChallengeType = CHALLENGE_BAD;
}

void LookForStartChallenge()
/* Determine if a challenge happens. */
{
    char *p1, *p2, *p3;
    char word[16];
    int i, j, inword;

    if( RecHead == NULL ) return;
/*  if( ( p2 = InRecH( "in OSPD2.\n" ) ) != NULL ) { */
    if( ( p2 = InRecH( "in TWL96.\n" ) ) != NULL ) { /* jjc 1996-02-01
*/
	ChallDict = DICT_OSPD;
    } else if( ( p2 = InRecH( "in OSW3.\n" ) ) != NULL ) {
	ChallDict = DICT_OSW;
/*  } else if( ( p2 = InRecH( "in OSPD2+OSW3.\n" ) ) != NULL ) { */
    } else if( ( p2 = InRecH( "in TWL96+OSW3.\n" ) ) != NULL ) { /* jjc 1996-02-01 */
	ChallDict = DICT_SOWPODS;
    } else {
	return;
    }
    p1 = InRecH( " checks " );
    if( p1 == NULL ) return;
    p3 = RecHead->string;
    if( p1 - p3 > 18 ) return;
    while( p3 < p1 ) {
	if( *p3++ == ' ' ) return;
    }

    p3 = RecHead->string;
    strncpy( CName, RecHead->string, p1 - p3 );
    CName[p1 - p3] = '\0';
/* Handle ACBot's challenges in state.c instead */
    if( strcmp( CName, PlrName ) == 0 ) return;
	LPrint( "Looks like %s challenged.\n", CName ); 
    p1 = p1 + 8;
    inword = 1;
    ChallengeType = CHALLENGE_NONE;
    for( i = 0; p1 < p2; p1++ ) {
	if( *p1 >= 'A' && *p1 <= 'Z' ) {
	    if( inword == 0 ) {
		inword = 1;
		i = 0;
	    }
	    if( i < 15 ) {
		word[i++] = *p1;
	    }
	} else if( inword == 1 ) {
	    inword = 0;
	    word[i] = '\0';
	    LPrint( "Comparing %s.  ", word ); 
/* Look to see if they look up a word already played by one of us; if so,
   challenge is no big deal */
	    for( j = 0; j < MyWords; j++ )
		if( strcmp( word, &(NewWords[j].letters[0]) ) == 0 )
		    break;
	    if( j != MyWords ) {
		LPrint( "I just played it.\n" ); 
		set_ct( CHALLENGE_GOOD );
		continue;
	    }
	    for( j = 0; j < MyWLen; j++ )
		if( strcmp( word, MyWList[j] ) == 0 ) break;
	    if( j != MyWLen ) {
		LPrint( "I already played it.\n" ); 
		set_ct( CHALLENGE_OLD );
		continue;
	    }
	    for( j = 0; j < TheirWLen; j++ )
		if( strcmp( word, TheirWList[j] ) == 0 ) break;
	    if( j != TheirWLen ) {
		LPrint( "They already played it.\n" ); 
		set_ct( CHALLENGE_OLD );
		continue;
	    }
	    LPrint( "Illicit challenge.\n" ); 
	    ChallengeType = CHALLENGE_BAD;
	}
    }
    RemoveReceivedString();
}

void warn_illegal_challenge()
/* Warn of improper use of a dictionary lookup command. */
{
    int i;
    
    i = 0;
    AddToStr( "\"Please use the `", &i );
    if( DoomName == DDDOOM )
	AddToStr( "sowpods", &i );
    else
	AddToStr( "ospd", &i );
    AddToStr( "' command to challenge a play, and do not otherwise", &i );
    AddToStr( " consult dictionaries during the game.", &i );
    TermStr( i, 0 );
    if( RatedGame == RATED_YES ) {
	i = 0;
	AddToStr( "\"This is the only warning I will give.", &i );
	TermStr( i, 0 );
    }
    ChallengeWarn = 1;
}

void LookForEndChallenge()
/* Look for closure of a challenge and deal with the result. */
{
    int ruling;

    if( RecHead == NULL ) return;
    ruling = OspdRuling( RecHead->string );
    if( ruling == RULING_NONE ) return;

/* Handle ACBot's challenges in state.c instead */
    if( strcmp( CName, PlrName ) == 0 ) return;
    RemoveReceivedString();
    if( ChallengeType == CHALLENGE_OLD || strcmp( CName, PlrName ) == 0 ) {
	TheyChallenge = THEY_CHALL_NONE;
	return;
    }
    
    if( CurrState >= AWAIT_REMATCH || CurrState < START_GAME ) return;
    if( strcmp( CName, Opponent ) != 0 || ChallengeType != CHALLENGE_GOOD
	|| ( DoomName == DDDOOM && ChallDict != DICT_SOWPODS )
	|| ( DoomName != DDDOOM && ChallDict != DICT_OSPD ) ) {
	TheyChallenge = THEY_CHALL_NONE;
	if( ChallengeWarn == 0 || RatedGame != RATED_YES ) {
	    warn_illegal_challenge();
	} else {
	    LeaveInHuff( CName );
	}
    } else {
/*	if( DoomName == DDDOOM && ChallDict != DICT_SOWPODS ) {
	    if( ruling == RULING_UNACC ) {
		SayToOpp( "Please use the 'sowpods' command for challenges." );
		TheyChallenge = THEY_CHALL_NONE;
		return;
	    }
	} else if( DoomName != DDDOOM && ChallDict != DICT_OSPD ) {
	    SayToOpp( "Please use the 'ospd' command for challenges." );
	    TheyChallenge = THEY_CHALL_NONE;
	    return;
	} */
	if( ruling == RULING_ACC ) {
	    TheyChallenge = THEY_CHALL_LOSE;
	} else if( ruling == RULING_UNACC ) {
	    TheyChallenge = THEY_CHALL_WIN;
	} else {
	    SayToOpp( "Uh oh, I'm confused.  Please tell JamesAC." );
	    TheyChallenge = THEY_CHALL_NONE;
	}
    }
}

void LookForForfeit()
/* Wait for the opponent to forfeit their turn. */
{
    if( RecHead == NULL ) return;
    if( RecHead->string[0] != '>' ) return;
    if( InRecH( PlrName ) != NULL ) return;
    ProcessGreaterThan( RecHead->string );
    if( TheyMove == THEY_FORFEIT ) {
	LookForForf = FOUND_FORF_YES;
	RemoveReceivedString();
    } else
	LookForForf = FOUND_FORF_NO;
}

void MeEndGame()
/* Have ACBot end the game. */
{
    SayToOpp( "I will end the game." );
    QueueCommand( "end", 0 );
    sprintf( EndGameStr, "%s ends the game.\n", PlrName );
    WhoEndsGame = ME_END_GAME;
    ChangeState( END_OF_GAME );
}

void WarnOfExpiry()
/* Warn of an imminent watchdog timer expiry. */
{
    SayToOpp( "My internal state hasn't changed in 25 minutes.  In another five, I will reset myself." );
}

void Expire()
/* Make watchdog timer expire. */
{
    SayToOpp( "My watchdog timer has expired.  I'm resetting myself." );
    GoHome();
}
