/*
 * gnubg.c
 *
 * by Gary Wong <gtw@gnu.org>, 1998, 1999, 2000, 2001, 2002, 2003.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 3 or later of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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
 *
 * $Id: gnubg.c,v 1.917 2013/03/21 23:27:49 plm Exp $
 */

#include "config.h"
#include "gnubgmodule.h"

#include <sys/types.h>
#include <stdlib.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <signal.h>
#include <ctype.h>
#ifdef WIN32
#include <io.h>
#endif

#if HAVE_LIBREADLINE
static char *gnubg_histfile;
#include <readline/history.h>
#include <readline/readline.h>
static int fReadingOther;
static char szCommandSeparators[] = " \t\n\r\v\f";
#endif

#include "analysis.h"
#include "backgammon.h"
#include "dice.h"
#include "drawboard.h"
#include "eval.h"
#include "sgf.h"
#include "export.h"
#include "import.h"
#include <glib/gstdio.h>
#include <locale.h>
#include "matchequity.h"
#include "matchid.h"
#include "positionid.h"
#include "render.h"
#include "renderprefs.h"
#include "rollout.h"
#include "sound.h"
#include "progress.h"
#include "osr.h"
#include "format.h"
#include "relational.h"
#include "credits.h"
#include "external.h"
#include "neuralnet.h"
#include "util.h"

#if HAVE_SOCKETS
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/un.h>
#endif /* #if HAVE_SYS_SOCKET_H */
#ifndef WIN32
#include <stdio.h>
#else /* #ifndef WIN32 */
#include <winsock2.h>
#include <ws2tcpip.h>

#define EWOULDBLOCK             WSAEWOULDBLOCK
#define EINPROGRESS             WSAEINPROGRESS
#define EALREADY                WSAEALREADY
#define ENOTSOCK                WSAENOTSOCK
#define EDESTADDRREQ            WSAEDESTADDRREQ
#define EMSGSIZE                WSAEMSGSIZE
#define EPROTOTYPE              WSAEPROTOTYPE
#define ENOPROTOOPT             WSAENOPROTOOPT
#define EPROTONOSUPPORT         WSAEPROTONOSUPPORT
#define ESOCKTNOSUPPORT         WSAESOCKTNOSUPPORT
#define EOPNOTSUPP              WSAEOPNOTSUPP
#define EPFNOSUPPORT            WSAEPFNOSUPPORT
#define EAFNOSUPPORT            WSAEAFNOSUPPORT
#define EADDRINUSE              WSAEADDRINUSE
#define EADDRNOTAVAIL           WSAEADDRNOTAVAIL
#define ENETDOWN                WSAENETDOWN
#define ENETUNREACH             WSAENETUNREACH
#define ENETRESET               WSAENETRESET
#define ECONNABORTED            WSAECONNABORTED
#define ECONNRESET              WSAECONNRESET
#define ENOBUFS                 WSAENOBUFS
#define EISCONN                 WSAEISCONN
#define ENOTCONN                WSAENOTCONN
#define ESHUTDOWN               WSAESHUTDOWN
#define ETOOMANYREFS            WSAETOOMANYREFS
#define ETIMEDOUT               WSAETIMEDOUT
#define ECONNREFUSED            WSAECONNREFUSED
#define ELOOP                   WSAELOOP
#define EHOSTDOWN               WSAEHOSTDOWN
#define EHOSTUNREACH            WSAEHOSTUNREACH
#define EPROCLIM                WSAEPROCLIM
#define EUSERS                  WSAEUSERS
#define EDQUOT                  WSAEDQUOT
#define ESTALE                  WSAESTALE
#define EREMOTE                 WSAEREMOTE

#define inet_aton(ip,addr)  (addr)->s_addr = inet_addr(ip), 1
#define inet_pton(fam,ip,addr) (addr)->s_addr = inet_addr(ip), 1

#endif /* #ifndef WIN32 */

#endif


#if USE_GTK
#include <gtk/gtk.h>
#include "gtkboard.h"
#include "gtkgame.h"
#include "gtkprefs.h"
#include "gtksplash.h"
#include "gtkchequer.h"
#include "gtkwindows.h"
#endif

#if USE_BOARD3D
#include "fun3d.h"
#endif
#include "multithread.h"
#include "openurl.h"

#if defined(MSDOS) || defined(__MSDOS__) || defined(WIN32)
#define NO_BACKSLASH_ESCAPES 1
#endif

#if USE_GTK
int fX = FALSE; /* use X display */
unsigned int nDelay = 300;
int fNeedPrompt = FALSE;
#if HAVE_LIBREADLINE
int fReadingCommand;
#endif
#endif

static char *autosave = NULL;
static int loading_rc = FALSE;
static int foutput_on = TRUE;
const char *intro_string =
    N_("This program comes with ABSOLUTELY NO WARRANTY; for details type `show warranty'.\n"
       "This is free software, and you are welcome to redistribute it under certain conditions; type `show copying' for details.\n");
char *szLang=NULL;

const char szDefaultPrompt[] = "(\\p) ",
    *szPrompt = szDefaultPrompt;
static int fInteractive, cOutputDisabled, cOutputPostponed;

matchstate ms = {
    {{0}, {0}}, /* anBoard */
    {0}, /* anDice */
    -1, /* fTurn */
    0, /* fResigned */
    0, /* fResignationDeclined */
    FALSE, /* fDoubled */
    0, /* cGames */
    -1, /* fMove */
    -1, /* fCubeOwner */
    FALSE, /* fCrawford */
    FALSE, /* fPostCrawford */
    0, /* nMatchTo */
    { 0, 0 }, /* anScore */
    1, /* nCube */
    0, /* cBeavers */
    VARIATION_STANDARD, /*bgv */
    TRUE, /* fCubeUse */
    TRUE, /* fJacoby */
    GAME_NONE /* gs */
};
ConstTanBoard msBoard(void) {return (ConstTanBoard)ms.anBoard;}

matchinfo mi;

float rRatingOffset = 2050;
int fAnalyseCube = TRUE;
int fAnalyseDice = TRUE;
int fAnalyseMove = TRUE;
int fAutoBearoff = FALSE;
int fAutoCrawford = 1;
int fAutoGame = TRUE;
int fAutoMove = FALSE;
int fAutoRoll = TRUE;
int fCheat = FALSE;
int fConfirmNew = TRUE;
int fConfirmSave = TRUE;
int nAutoSaveTime = 15;
int fAutoSaveRollout = FALSE;
int fAutoSaveAnalysis = FALSE;
int fAutoSaveConfirmDelete = TRUE;

/* FIXME: This is at best useless, at worst misleading, as a global flag.
          It should be deduced from the rollout context when needed.
          See for instance show.c:ShowRollout() around line 270 */
int fCubeEqualChequer = FALSE;

int fCubeUse = TRUE;
int fDisplay = TRUE;
int fFullScreen = FALSE;
int fGotoFirstGame = FALSE;
int fInvertMET = FALSE;
int fJacoby = TRUE;
int fOutputRawboard = FALSE;
int fPlayersAreSame = TRUE;
int fRecord = TRUE;
int fShowProgress;
int fStyledGamelist = TRUE;
int fTruncEqualPlayer0 =TRUE;
int fTutorChequer = TRUE;
int fTutorCube = TRUE;
int fTutor = FALSE;
int fEvalSameAsAnalysis = FALSE;
int fJustSwappedPlayers = FALSE;
int nConfirmDefault = -1;
int nThreadPriority = 0;
int nToolbarStyle = 2;
unsigned int afCheatRoll[ 2 ] = { 0, 0 };
unsigned int cAutoDoubles = 0;
unsigned int nBeavers = 3;
unsigned int nDefaultLength = 7;

#if USE_BOARD3D
int fSync = -1;	/* Not set */
int fResetSync = FALSE;	/* May need to wait for main window */
#endif

skilltype TutorSkill = SKILL_DOUBTFUL;
int nTutorSkillCurrent = 0;

char *szCurrentFileName = NULL;
char *szCurrentFolder = NULL;


int fNextTurn = FALSE, fComputing = FALSE;

float rEvalsPerSec = -1.0f;
float    arLuckLevel[] = {
	0.6f, /* LUCK_VERYBAD */
	0.3f, /* LUCK_BAD */
	0, /* LUCK_NONE */
	0.3f, /* LUCK_GOOD */
	0.6f /* LUCK_VERYGOOD */
    }, arSkillLevel[] = {
	0.16f, /* SKILL_VERYBAD */
	0.08f, /* SKILL_BAD */
	0.04f, /* SKILL_DOUBTFUL */
	0,     /* SKILL_NONE */
    };

#include "movefilters.inc"

rngcontext *rngctxRollout = NULL;

rolloutcontext rcRollout =
{ 
  {
	/* player 0/1 cube decision */
        { TRUE, 2, TRUE, TRUE, 0.0 },
	{ TRUE, 2, TRUE, TRUE, 0.0 }
  }, 
  {
	/* player 0/1 chequerplay */
	{ TRUE, 0, TRUE, TRUE, 0.0 },
	{ TRUE, 0, TRUE, TRUE, 0.0 }
  }, 

  {
	/* player 0/1 late cube decision */
	{ TRUE, 2, TRUE, TRUE, 0.0 },
	{ TRUE, 2, TRUE, TRUE, 0.0 }
  }, 
  {
	/* player 0/1 late chequerplay */
	{ TRUE, 0, TRUE, TRUE, 0.0 },
	{ TRUE, 0, TRUE, TRUE, 0.0 } 
  }, 
  /* truncation point cube and chequerplay */
  { TRUE, 2, TRUE, TRUE, 0.0 },
  { TRUE, 2, TRUE, TRUE, 0.0 },

  /* move filters */
  { MOVEFILTER_NORMAL, MOVEFILTER_NORMAL },
  { MOVEFILTER_NORMAL, MOVEFILTER_NORMAL },

  TRUE, /* cubeful */
  TRUE, /* variance reduction */
  FALSE, /* initial position */
  TRUE, /* rotate */
  TRUE, /* truncate at BEAROFF2 for cubeless rollouts */
  TRUE, /* truncate at BEAROFF2_OS for cubeless rollouts */
  FALSE, /* late evaluations */
  FALSE,  /* Truncation enabled */
  FALSE,  /* no stop on STD */
  FALSE,  /* no stop on JSD */
  FALSE,  /* no move stop on JSD */
  10, /* truncation */
  1296, /* number of trials */
  5,  /* late evals start here */
  RNG_MERSENNE, /* RNG */
  0,  /* seed */
  324,    /* minimum games  */
  0.01,	  /* stop when std's are lower than 0.01 */
  324,    /* minimum games  */
  2.33,   /* stop when best has j.s.d. for 99% confidence */
  0,      /* nGamesDone */
  0,      /* nSkip */
};

/* parameters for `eval' and `hint' */

#define EVALSETUP_WORLDCLASS  { \
  /* evaltype */ \
  EVAL_EVAL, \
  /* evalcontext */ \
  { TRUE, 2, TRUE, TRUE, 0.0 }, \
  /* rolloutcontext */ \
  { \
    { \
      { FALSE, 2, TRUE, TRUE, 0.0 }, /* player 0 cube decision */ \
      { FALSE, 2, TRUE, TRUE, 0.0 } /* player 1 cube decision */ \
    }, \
    { \
      { FALSE, 0, TRUE, TRUE, 0.0 }, /* player 0 chequerplay */ \
      { FALSE, 0, TRUE, TRUE, 0.0 } /* player 1 chequerplay */ \
    }, \
    { \
      { FALSE, 2, TRUE, TRUE, 0.0 }, /* p 0 late cube decision */ \
      { FALSE, 2, TRUE, TRUE, 0.0 } /* p 1 late cube decision */ \
    }, \
    { \
      { FALSE, 0, TRUE, TRUE, 0.0 }, /* p 0 late chequerplay */ \
      { FALSE, 0, TRUE, TRUE, 0.0 } /* p 1 late chequerplay */ \
    }, \
    { FALSE, 2, TRUE, TRUE, 0.0 }, /* truncate cube decision */ \
    { FALSE, 2, TRUE, TRUE, 0.0 }, /* truncate chequerplay */ \
    { MOVEFILTER_NORMAL, MOVEFILTER_NORMAL }, \
    { MOVEFILTER_NORMAL, MOVEFILTER_NORMAL }, \
  FALSE, /* cubeful */ \
  TRUE, /* variance reduction */ \
  FALSE, /* initial position */ \
  TRUE, /* rotate */ \
  TRUE, /* truncate at BEAROFF2 for cubeless rollouts */ \
  TRUE, /* truncate at BEAROFF2_OS for cubeless rollouts */ \
  FALSE, /* late evaluations */ \
  TRUE,  /* Truncation enabled */ \
  FALSE,  /* no stop on STD */ \
  FALSE,  /* no stop on JSD */ \
  FALSE,  /* no move stop on JSD */ \
  10, /* truncation */ \
  1296, /* number of trials */ \
  5,  /* late evals start here */ \
  RNG_MERSENNE, /* RNG */ \
  0,  /* seed */ \
  324,    /* minimum games  */ \
  0.01,	  /* stop when std's are lower than 0.01 */ \
  324,    /* minimum games  */ \
  2.33,   /* stop when best has j.s.d. for 99% confidence */ \
  0, \
  0 \
  } \
} 

/* parameters for analysis */

#define EVALSETUP_SUPREMO  { \
  /* evaltype */ \
  EVAL_EVAL, \
  /* evalcontext */ \
  { TRUE, 2, TRUE, TRUE, 0.0 }, \
  /* rolloutcontext */ \
  { \
    { \
      { FALSE, 2, TRUE, TRUE, 0.0 }, /* player 0 cube decision */ \
      { FALSE, 2, TRUE, TRUE, 0.0 } /* player 1 cube decision */ \
    }, \
    { \
      { FALSE, 0, TRUE, TRUE, 0.0 }, /* player 0 chequerplay */ \
      { FALSE, 0, TRUE, TRUE, 0.0 } /* player 1 chequerplay */ \
    }, \
    { \
      { FALSE, 2, TRUE, TRUE, 0.0 }, /* p 0 late cube decision */ \
      { FALSE, 2, TRUE, TRUE, 0.0 } /* p 1 late cube decision */ \
    }, \
    { \
      { FALSE, 0, TRUE, TRUE, 0.0 }, /* p 0 late chequerplay */ \
      { FALSE, 0, TRUE, TRUE, 0.0 } /* p 1 late chequerplay */ \
    }, \
    { FALSE, 2, TRUE, TRUE, 0.0 }, /* truncate cube decision */ \
    { FALSE, 2, TRUE, TRUE, 0.0 }, /* truncate chequerplay */ \
    { MOVEFILTER_NORMAL, MOVEFILTER_NORMAL }, \
    { MOVEFILTER_NORMAL, MOVEFILTER_NORMAL }, \
  FALSE, /* cubeful */ \
  TRUE, /* variance reduction */ \
  FALSE, /* initial position */ \
  TRUE, /* rotate */ \
  TRUE, /* truncate at BEAROFF2 for cubeless rollouts */ \
  TRUE, /* truncate at BEAROFF2_OS for cubeless rollouts */ \
  FALSE, /* late evaluations */ \
  TRUE,  /* Truncation enabled */ \
  FALSE,  /* no stop on STD */ \
  FALSE,  /* no stop on JSD */ \
  FALSE,  /* no move stop on JSD */ \
  10, /* truncation */ \
  1296, /* number of trials */ \
  5,  /* late evals start here */ \
  RNG_MERSENNE, /* RNG */ \
  0,  /* seed */ \
  324,    /* minimum games  */ \
  0.01,	  /* stop when std's are lower than 0.01 */ \
  324,    /* minimum games  */ \
  2.33,   /* stop when best has j.s.d. for 99% confidence */ \
  0, \
  0 \
  } \
} 

evalsetup esEvalChequer = EVALSETUP_WORLDCLASS;
evalsetup esEvalCube = EVALSETUP_WORLDCLASS;
evalsetup esAnalysisChequer = EVALSETUP_SUPREMO;
evalsetup esAnalysisCube = EVALSETUP_SUPREMO;

movefilter aamfEval[ MAX_FILTER_PLIES ][ MAX_FILTER_PLIES ] = MOVEFILTER_NORMAL;
movefilter aamfAnalysis[ MAX_FILTER_PLIES ][ MAX_FILTER_PLIES ] = MOVEFILTER_LARGE;

extern evalsetup *GetEvalChequer(void)
{
	return fEvalSameAsAnalysis ? &esAnalysisChequer : &esEvalChequer;
}
extern evalsetup *GetEvalCube(void)
{
	return fEvalSameAsAnalysis ? &esAnalysisCube : &esEvalCube;
}
extern TmoveFilter *GetEvalMoveFilter(void)
{
	return fEvalSameAsAnalysis ? &aamfAnalysis : &aamfEval;
}

exportsetup exsExport = {
  TRUE, /* include annotations */
  TRUE, /* include analysis */
  TRUE, /* include statistics */
  TRUE, /* include match information */

  1, /* display board for all moves */
  -1, /* both players */

  5, /* display max 5 moves */
  TRUE, /* show detailed probabilities */
  /* do not show move parameters */
  { FALSE, TRUE },
  /* display all moves */
  { TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE },

  TRUE, /* show detailed prob. for cube decisions */
  { FALSE, TRUE }, /* do not show move parameters */
  /* display all cube decisions */
  { TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE },

  NULL, /* HTML url to pictures */
  HTML_EXPORT_TYPE_GNU,
  NULL,  /* HTML extension */
  HTML_EXPORT_CSS_HEAD, /* write CSS stylesheet in <head> */

  4, /* PNG size */
  4 /* Html size */
};

  
#define DEFAULT_NET_SIZE 128

player ap[ 2 ] = {
    { "gnubg", PLAYER_GNU, EVALSETUP_WORLDCLASS, EVALSETUP_WORLDCLASS, MOVEFILTER_NORMAL, 0, NULL },
    { "user", PLAYER_HUMAN, EVALSETUP_WORLDCLASS, EVALSETUP_WORLDCLASS, MOVEFILTER_NORMAL, 0, NULL } 
};

char default_names[2][31] = {"gnubg", "user"};

/* Usage strings */
static char szDICE[] = N_("<die> <die>"),
    szCOMMAND[] = N_("<command>"),
    szCOMMENT[] = N_("<comment>"),
    szER[] = N_("evaluation|rollout"), 
    szFILENAME[] = N_("<filename>"),
    szKEYVALUE[] = N_("[<key>=<value> ...]"),
    szLENGTH[] = N_("<length>"),
    szLIMIT[] = N_("<limit>"),
    szMILLISECONDS[] = N_("<milliseconds>"),
    szMOVE[] = N_("<from> <to> ..."),
    szFILTER[] = N_( "<ply> <num.xjoin to accept (0 = skip)> "
                      "[<num. of extra moves to accept> <tolerance>]"),
    szNAME[] = N_("<name>"),
    szQUIET[] = N_("[quiet]"),
    szLANG[] = N_("system|<language code>"),
    szONOFF[] = N_("on|off"),
    szOPTCOMMAND[] = N_("[command]"),
    szOPTDATE[] = N_("[yyyy-mm-dd]"),
    szOPTDEPTH[] = N_("[depth]"),
    szOPTFILENAME[] = N_("[filename]"),
    szOPTLENGTH[] = N_("[length]"),
    szOPTMODULUSOPTSEED[] = N_("[modulus <modulus>|factors <factor> <factor>] "
			       "[seed]"),
    szOPTNAME[] = N_("[name]"),
    szOPTPOSITION[] = N_("[position]"),
    szOPTSEED[] = N_("[seed]"),
    szOPTVALUE[] = N_("[value]"),
    szPLAYER[] = N_("<player>"),
    szPLAYEROPTRATING[] = N_("<player> [rating]"),
    szPLIES[] = N_("<plies>"),
    szPOSITION[] = N_("<position>"),
    szPRIORITY[] = N_("<priority>"),
    szPROMPT[] = N_("<prompt>"),
    szSCORE[] = N_("<score>"),
    szSIZE[] = N_("<size>"),
    szSTEP[] = N_("[game|roll|rolled|marked] <count>"),
    szTRIALS[] = N_("<trials>"),
    szVALUE[] = N_("<value>"),
    szMATCHID[] = N_("<matchid>"),
    szGNUBGID[] = N_("<gnubgid>"),
    szXGID[] = N_("<xgid>"),
    szURL[] = N_("<URL>"),
    szMAXERR[] = N_("<fraction>"),
    szMINGAMES[] = N_("<minimum games to rollout>"),
    szFOLDER[] = N_("<folder>"),
#if USE_GTK
	szWARN[] = N_("[<warning>]"),
	szWARNYN[] = N_("<warning> on|off"),
#endif
    szJSDS[] = N_("<joint standard deviations>"),
    szSTDDEV[] = N_("<std dev>");

/* Command defines moved into separate file */
#include "commands.inc"

static int iProgressMax, iProgressValue, fInProgress;
static char *pcProgress;
static psighandler shInterruptOld;
char *default_import_folder = NULL;
char *default_export_folder = NULL;
char *default_sgf_folder = NULL;

const char *szHomeDirectory;

char const *aszBuildInfo[] = {
#if USE_PYTHON
    N_("Python supported."),
#endif
#if USE_SQLITE
    N_("SQLite database supported."),
#endif
#if USE_GTK
    N_("GTK graphical interface supported."),
#endif
#if HAVE_SOCKETS
    N_("External players supported."),
#endif
#if HAVE_LIBGMP
    N_("Long RNG seeds supported."),
#endif
#if USE_BOARD3D
    N_("3d Boards supported."),
#endif
#if HAVE_SOCKETS
    N_("External commands supported."),
#endif
#if defined(WIN32)
    N_("Windows sound system supported."),
#elif HAVE_APPLE_COREAUDIO
    N_("Apple CoreAudio sound system supported."),
#elif HAVE_APPLE_QUICKTIME
    N_("Apple QuickTime sound system supported."),
#elif HAVE_CANBERRA
    N_("libcanberra sound system supported."),
#endif
#if USE_MULTITHREAD
    N_("Multiple threads supported."),
#endif
#if USE_SSE_VECTORIZE
#if USE_SSE2
	N_("SSE/SSE2 supported."),
#else
	N_("SSE supported."),
#endif
#endif
    NULL
};

extern const char *GetBuildInfoString(void)
{
	static const char **ppch = aszBuildInfo;

	if (!*ppch)
	{
		ppch = aszBuildInfo;
		return NULL;
	}
	return *ppch++;
}

/*
 * general token extraction
   input: ppch pointer to pointer to command
          szToekns - string of token separators
   output: NULL if no token found
           ptr to extracted token if found. Token is in original location
               in input string, but null terminated if not quoted, token
               will have been moved forward over quote character when quoted
           ie: 
           input:  '  abcd efgh'
           output  '  abcd\0efgh'
                   return value points to abcd, ppch points to efgh
           input   '  "jklm" nopq'
           output  ;  jklm\0 nopq'
                   return value points to jklm, ppch points to space before 
                   the 'n'
          ppch points past null terminator

   ignores leading whitespace, advances ppch over token and trailing
          whitespace

   matching single or double quotes are allowed, any character outside
   of quotes or in doubly quoated strings can be escaped with a
   backslash and will be taken as literal.  Backslashes within single
   quoted strings are taken literally. Multiple quoted strins can be
   concatentated.  

   For example: input ' abc\"d"e f\"g h i"jk'l m n \" o p q'rst uvwzyz'
   with the terminator list ' \t\r\n\v\f'
   The returned token will be the string
   <abc"de f"g h j ijkl m n \" o p qrst>
   ppch will point to the 'u'
   The \" between c and d is not in a single quoted string, so is reduced to 
   a double quote and is *not* the start of a quoted string.
   The " before the 'd' begins a double quoted string, so spaces and tabs are
   not terminators. The \" between f and g is reduced to a double quote and 
   does not teminate the quoted string. which ends with the double quote 
   between i and j. The \" between n and o is taken as a pair of literal
   characters because they are within the single quoted string beginning
   before l and ending after q.
   It is not possible to put a single quote within a single quoted string. 
   You can have single quotes unescaped withing double quoted strings and
   double quotes unescaped within single quoted strings.
 */
extern char *
NextTokenGeneral( char **ppch, const char *szTokens ) {


    char *pch, *pchSave, chQuote = 0;
    int fEnd = FALSE;
#ifndef NDEBUG
    char *pchEnd;
#endif
    
    if( !*ppch )
	return NULL;

#ifndef NDEBUG
    pchEnd = strchr( *ppch, 0 );
#endif
    
    /* skip leading whitespace */
    while( isspace( **ppch ) )
	( *ppch )++;

    if( !*( pch = pchSave = *ppch ) )
	/* nothing left */
	return NULL;

    while( !fEnd ) {

      if ( **ppch && strchr( szTokens, **ppch ) ) {
        /* this character ends token */
        if( !chQuote ) {
          fEnd = TRUE;
          (*ppch)++; /* step over token */
        }
        else
          *pchSave++ = **ppch;
      }
      else {
	switch( **ppch ) {
	case '\'':
	case '"':
	    /* quote mark */
	    if( !chQuote )
		/* start quoting */
		chQuote = **ppch;
	    else if( chQuote == **ppch )
		/* end quoting */
		chQuote = 0;
	    else
		/* literal */
		*pchSave++ = **ppch;
	    break;

#ifdef NO_BACKSLASH_ESCAPES
	case '%':
#else
	case '\\':
#endif
	    /* backslash */
	    if( chQuote == '\'' )
		/* literal */
		*pchSave++ = **ppch;
	    else {
		( *ppch )++;

		if( **ppch )
		    /* next character is literal */
		    *pchSave++ = **ppch;
		else {
		    /* end of string -- the backlash doesn't quote anything */
#ifdef NO_BACKSLASH_ESCAPES
		    *pchSave++ = '%';
#else
		    *pchSave++ = '\\';
#endif
		    fEnd = TRUE;
		}
	    }
	    break;
	    
	case 0:
	    /* end of string -- always ends token */
	    fEnd = TRUE;
	    break;
	    
	default:
	    *pchSave++ = **ppch;
	}

      }

      if( !fEnd )
        ( *ppch )++;
    }

    while( isspace( **ppch ) )
      ( *ppch )++;

    *pchSave = 0;

#ifndef NDEBUG
    g_assert( pchSave <= pchEnd );
    g_assert( *ppch <= pchEnd );
    g_assert( pch <= pchEnd );
#endif

    return pch;

}

/* extrace a token from a string. Tokens are terminated by tab, newline, 
   carriage return, vertical tab or form feed.
   Input:

     ppch = pointer to pointer to input string. This will be updated
        to point past any token found. If the string is exhausetd, 
        the pointer at ppch will point to the terminating NULL, so it is
        safe to call this function repeatedly after failure
    
   Output:
       null terminated token if found or NULL if no tokens present.
*/
extern char *NextToken( char **ppch )
{

  return NextTokenGeneral( ppch, " \t\n\r\v\f" ); 

}

/* return a count of the number of separate runs of one or more
   non-whitespace characters. This is the number of tokens that
   NextToken() will return. It may not be the number of tokens
   NextTokenGeneral() will return, as it does not count quoted strings
   containing whitespace as single tokens
*/
static int CountTokens( char *pch ) {

    int c = 0;

    do {
	while( isspace( *pch ) )
	    pch++;

	if( *pch ) {
	    c++;

	    while( *pch && !isspace( *pch ) )
		pch++;
	}
    } while( *pch );
    
    return c;
}

/* extract a token and convert to double. On error or no token, return 
   ERR_VAL (a very large negative double.
*/

extern double ParseReal( char **ppch )
{

    char *pch, *pchOrig;
    double r;
    
    if( !ppch || !( pchOrig = NextToken( ppch ) ) )
	return ERR_VAL;

    r = g_ascii_strtod( pchOrig, &pch );

    return *pch ? ERR_VAL : r;
}

/* get the next token from the input and convert as an
   integer. Returns INT_MIN on empty input or non-numerics found. Does
   handle negative integers. On failure, one token (if any were available
   will have been consumed, it is not pushed back into the input.
*/
extern int ParseNumber( char **ppch )
{

    char *pch, *pchOrig;

    if( !ppch || !( pchOrig = NextToken( ppch ) ) )
	return INT_MIN;

    for( pch = pchOrig; *pch; pch++ )
	if( !isdigit( *pch ) && *pch != '-' )
	    return INT_MIN;

    return atoi( pchOrig );
}

/* get the next token from the input and convert as an
   integer. Returns 0 on empty input or non-numerics found, and
   1 on success. On failure, one token (if any were available)
   will have been consumed, it is not pushed back into the input.
   Unsigned long is returned in pretVal
*/
extern gboolean ParseULong( char **ppch, unsigned long *pretVal )
{

    char *pch, *pchOrig;

    if( !ppch || !( pchOrig = NextToken( ppch ) ) )
	return FALSE;

    for( pch = pchOrig; *pch; pch++ )
	if( !isdigit( *pch ) )
	    return FALSE;

    errno = 0;    /* To distinguish success/failure after call */
    *pretVal = strtol(pchOrig, NULL, 10);

   /* Check for various possible errors */
   if ((errno == ERANGE && (*pretVal == LONG_MAX || *pretVal == (unsigned long)LONG_MIN))
            || (errno != 0 && pretVal == 0)) 
	return FALSE;

   return TRUE;

}

/* get a player either by name or as player 0 or 1 (indicated by the single
   character '0' or '1'. Returns -1 on no input, 2 if not a recoginsed name
   Note - this is not a token extracting routine, it expects to be handed
   an already extracted token
*/
extern int ParsePlayer( char *sz )
{

    int i;

    if( !sz )
	return -1;
    
    if( ( *sz == '0' || *sz == '1' ) && !sz[ 1 ] )
	return *sz - '0';

    for( i = 0; i < 2; i++ )
	if( !CompareNames( sz, ap[ i ].szName ) )
	    return i;

    if( !StrNCaseCmp( sz, "both", strlen( sz ) ) )
	return 2;

    return -1;
}


/* Convert a string to a board array.  Currently allows the string to
   be a position ID, a gnubg-nn position string, "=n" notation,
   or empty (in which case the current board is used).

   The input string should be specified in *ppch; this string must be
   modifiable, and the pointer will be updated to point to the token
   following a board specification if possible (see NextToken()).  The
   board will be returned in an, and if pchDesc is non-NULL, then
   descriptive text (the position ID, formatted move, or "Current
   position", depending on the input) will be stored there.
   
   Returns -1 on failure, 0 on success, or 1 on success if the position
   specified has the opponent on roll (e.g. because it used "=n" notation). */
extern int ParsePosition( TanBoard an, char **ppch, char *pchDesc )
{
    int i;
    char *pch;
    
    /* FIXME allow more formats, e.g. FIBS "boardstyle 3" */

    if( !ppch || !( pch = NextToken( ppch ) ) ) { 
	memcpy( an, msBoard(), sizeof(TanBoard) );

	if( pchDesc )
	    strcpy( pchDesc, _("Current position") );
	
	return 0;
    }

    if ( ! strcmp ( pch, "simple" ) ) {
   
       /* board given as 26 integers.
        * integer 1   : # of my chequers on the bar
        * integer 2-25: number of chequers on point 1-24
        *               positive ints: my chequers 
        *               negative ints: opp chequers 
        * integer 26  : # of opp chequers on the bar
        */
 
       int n;

       for ( i = 0; i < 26; i++ ) {

          if ( ( n = ParseNumber ( ppch ) ) == INT_MIN ) {
             outputf (_("`simple' must be followed by 26 integers; "
                      "found only %d\n"), i );
             return -1;
          }

          if ( i == 0 ) {
             /* my chequers on the bar */
             an[ 1 ][ 24 ] = abs(n);
          }
          else if ( i == 25 ) {
             /* opp chequers on the bar */
             an[ 0 ][ 24 ] = abs(n);
          } else {

             an[ 1 ][ i - 1 ] = 0;
             an[ 0 ][ 24 - i ] = 0;

             if ( n < 0 )
                an[ 0 ][ 24 - i ] = -n;
             else if ( n > 0 )
                an[ 1 ][ i - 1 ] = n;
             
          }
      
       }

       if( pchDesc )
          strcpy( pchDesc, *ppch );

       *ppch = NULL;
       
       return CheckPosition((ConstTanBoard)an) ? 0 : -1;
    }

    if( strlen(pch) == 20) { /* gnubg-nn position string */
	static oldpositionkey key;

	for(i = 0; i < 10; ++i) {
	   key.auch[i] = ((pch[2*i+0] - 'A') << 4) +  (pch[2*i+1] - 'A');
	}

	oldPositionFromKey( an, &key);

	return 0;
    }
	
    if( !PositionFromID( an, pch ) ) {
	outputl( _("Illegal position.") );
	return -1;
    }

    if( pchDesc )
	strcpy( pchDesc, pch );
    
    return 0;
}

/* Parse "key=value" pairs on a command line.  PPCH takes a pointer to
   a command line on input, and returns a pointer to the next parameter.
   The key is returned in apch[ 0 ], and the value in apch[ 1 ].
   The function return value is the number of parts successfully read
   (0 = no key was found, 1 = key only, 2 = both key and value). */
extern int ParseKeyValue( char **ppch, char *apch[ 2 ] )
{

    if( !ppch || !( apch[ 0 ] = NextToken( ppch ) ) )
	return 0;

    if( !( apch[ 1 ] = strchr( apch[ 0 ], '=' ) ) )
	return 1;

    *apch[ 1 ] = 0;
    apch[ 1 ]++;
    return 2;
}

/* Compare player names.  Performed case insensitively, and with all
   whitespace characters and underscore considered identical. */
extern int CompareNames( char *sz0, char *sz1 )
{

    static char ach[] = " \t\r\n\f\v_";
    
    for( ; *sz0 || *sz1; sz0++, sz1++ )
	if( toupper( *sz0 ) != toupper( *sz1 ) &&
	    ( !strchr( ach, *sz0 ) || !strchr( ach, *sz1 ) ) )
	    return toupper( *sz0 ) - toupper( *sz1 );
    
    return 0;
}

extern void UpdateSetting( void *p )
{
#if USE_GTK
    if( fX )
	GTKSet( p );
#else
    (void)p;	/* suppress unused parameter compiler warning */
#endif
}

extern void UpdateSettings( void )
{

    UpdateSetting( &ms.nCube );
    UpdateSetting( &ms.fCubeOwner );
    UpdateSetting( &ms.fTurn );
    UpdateSetting( &ms.nMatchTo );
    UpdateSetting( &ms.fCrawford );
    UpdateSetting( &ms.fJacoby );
    UpdateSetting( &ms.gs );
    
    ShowBoard();

#if USE_BOARD3D
	RestrictiveRedraw();
#endif
}

/* handle turning a setting on / off
   inputs: szName - the setting being adjusted
           pf = pointer to current on/off state (will be updated)
           sz = pointer to command line - a token will be extracted, 
                but furhter calls to NextToken will return only the on/off
                value, so you can't have commands in the form
                set something on <more tokens>
           szOn - text to output when turning setting on
           szOff - text to output when turning setting off
    output: -1 on error
             0 setting is now off
             1 setting is now on
    acceptable tokens are on/off yes/no true/false
 */
extern int SetToggle( const char *szName, int *pf, char *sz, const char *szOn, const char *szOff )
{
    char *pch = NextToken( &sz );
    int cch;
    
    if( !pch ) {
	outputf( _("You must specify whether to set '%s' on or off.\n"), szName );

	return -1;
    }

    cch = strlen( pch );
    
    if( !StrCaseCmp( "on", pch ) || !StrNCaseCmp( "yes", pch, cch ) ||
	!StrNCaseCmp( "true", pch, cch ) ) {
	outputl( szOn );

	if( *pf != TRUE ) {
	    *pf = TRUE;
	    UpdateSetting( pf );
	}

	return TRUE;
    }

    if( !StrCaseCmp( "off", pch ) || !StrNCaseCmp( "no", pch, cch ) ||
	!StrNCaseCmp( "false", pch, cch ) ) {
	outputl( szOff );

	if( *pf != FALSE ) {
	    *pf = FALSE;
	    UpdateSetting( pf );
	}
	
	return FALSE;
    }

    outputf( _("Illegal keyword `%s'.\n"), pch );

    return -1;
}

extern void PortableSignal( int nSignal, void (*p)(int),
			    psighandler *pOld, int fRestart ) {
#if HAVE_SIGACTION
    struct sigaction sa;

    sa.sa_handler = p;
    sigemptyset( &sa.sa_mask );
    sa.sa_flags =
#if SA_RESTART
	( fRestart ? SA_RESTART : 0 ) |
#endif
#if SA_NOCLDSTOP
	SA_NOCLDSTOP |
#endif
	0;
    sigaction( nSignal, p ? &sa : NULL, pOld );
#elif HAVE_SIGVEC
    struct sigvec sv;

    sv.sv_handler = p;
    sigemptyset( &sv.sv_mask );
    sv.sv_flags = nSignal == SIGINT || nSignal == SIGIO ? SV_INTERRUPT : 0;

    sigvec( nSignal, p ? &sv : NULL, pOld );
#else
    if( pOld )
	*pOld = signal( nSignal, p );
    else if( p )
	signal( nSignal, p );
#endif
}

extern void PortableSignalRestore( int nSignal, psighandler *p )
{
#if HAVE_SIGACTION
    sigaction( nSignal, p, NULL );
#elif HAVE_SIGVEC
    sigvec( nSignal, p, NULL );
#else
    signal( nSignal, *p );
#endif
}

/* Reset the SIGINT handler, on return to the main command loop.  Notify
   the user if processing had been interrupted. */
extern void ResetInterrupt( void )
{
    if( fInterrupt ) {
	{
	outputf("(%s)", _("Interrupted") );
	outputx();
	}
	
	fInterrupt = FALSE;
	
	/* if  a batch was running signal a cancellation of current match */
	fMatchCancelled = TRUE;

#if USE_GTK
	if( nNextTurn ) {
	    g_source_remove( nNextTurn );
	    nNextTurn = 0;
	}
#endif
    }
}


extern void HandleCommand( char *sz, command *ac )
{

    command *pc;
    char *pch;
    int cch;
    
    if( ac == acTop ) {
	outputnew();
    
        if( *sz == '#' ) /* Comment */
                return;
        else if( *sz == ':' ) {
                return;
        }
        else if ( *sz == '>' ) {

          while ( *sz == '>' )
            ++sz;

          /* leading white space confuses Python :-) */

          while ( isspace( *sz ) )
            ++sz;

#if USE_PYTHON
		PythonRun(sz);
#else
	    outputl( _("This installation of GNU Backgammon was compiled without Python support.") );
	    outputx();
#endif
		return;
        }

    }
    
    if( !( pch = NextToken( &sz ) ) ) {
	if( ac != acTop )
	    outputl( _("Incomplete command.") );

	outputx();
	return;
    }

    cch = strlen( pch );

    if( ac == acTop && ( isdigit( *pch ) ||
			 !StrNCaseCmp( pch, "bar/", cch > 4 ? 4 : cch ) ) ) {
	if( pch + cch < sz )
	    pch[ cch ] = ' ';
	
	CommandMove( pch );

	outputx();
	return;
    }

    for( pc = ac; pc->sz; pc++ )
	if( !StrNCaseCmp( pch, pc->sz, cch ) )
	    break;

    if( !pc->sz) {
	    if (!loading_rc)
		    outputerrf( _("Unknown keyword `%s'.\n"), pch );
	    return;
    }

    if( pc->pf ) {
	pc->pf( sz );
	
	outputx();
    } else
	HandleCommand( sz, pc->pc );
}

extern void InitBoard( TanBoard anBoard, const bgvariation bgv )
{

  unsigned int i;
  unsigned int j;

  for( i = 0; i < 25; i++ )
    anBoard[ 0 ][ i ] = anBoard[ 1 ][ i ] = 0;

  switch( bgv ) {
  case VARIATION_STANDARD:
  case VARIATION_NACKGAMMON:
    
    anBoard[ 0 ][ 5 ] = anBoard[ 1 ][ 5 ] =
	anBoard[ 0 ][ 12 ] = anBoard[ 1 ][ 12 ] = 
      ( bgv == VARIATION_NACKGAMMON ) ? 4 : 5;
    anBoard[ 0 ][ 7 ] = anBoard[ 1 ][ 7 ] = 3;
    anBoard[ 0 ][ 23 ] = anBoard[ 1 ][ 23 ] = 2;

    if( bgv == VARIATION_NACKGAMMON )
	anBoard[ 0 ][ 22 ] = anBoard[ 1 ][ 22 ] = 2;

    break;

  case VARIATION_HYPERGAMMON_1:
  case VARIATION_HYPERGAMMON_2:
  case VARIATION_HYPERGAMMON_3:

    for ( i = 0; i < 2; ++i )
      for ( j = 0; j < (unsigned int)( bgv - VARIATION_HYPERGAMMON_1 + 1 ); ++j )
        anBoard[ i ][ 23 - j ] = 1;
    
    break;
    
  default:

    g_assert_not_reached();

  }

}


extern void GetMatchStateCubeInfo( cubeinfo* pci, const matchstate* pms )
{

    SetCubeInfo( pci, pms->nCube, pms->fCubeOwner, pms->fMove,
			pms->nMatchTo, pms->anScore, pms->fCrawford,
			pms->fJacoby, nBeavers, pms->bgv );
}

static void
DisplayCubeAnalysis( float aarOutput[ 2 ][ NUM_ROLLOUT_OUTPUTS ], 
		     float aarStdDev[ 2 ][ NUM_ROLLOUT_OUTPUTS ], 
		     const evalsetup* pes ) {

    cubeinfo ci;

    if( pes->et == EVAL_NONE )
	return;

    GetMatchStateCubeInfo( &ci, &ms );
    
    outputl( OutputCubeAnalysis( aarOutput, aarStdDev, pes, &ci ) );
}

extern char *GetLuckAnalysis( const matchstate *pms, float rLuck )
{

    static char sz[ 16 ];
    cubeinfo ci;
    
    if( fOutputMWC && pms->nMatchTo ) {
	GetMatchStateCubeInfo( &ci, pms );
	
	sprintf( sz, "%+0.3f%%", 100.0f * ( eq2mwc( rLuck, &ci ) -
					    eq2mwc( 0.0f, &ci ) ) );
    } else
	sprintf( sz, "%+0.3f", rLuck );

    return sz;
}

static void DisplayAnalysis( moverecord *pmr ) {

    unsigned int i;
    char szBuf[ 1024 ];
    
    switch( pmr->mt ) {
    case MOVE_NORMAL:
        DisplayCubeAnalysis(  pmr->CubeDecPtr->aarOutput,
			      pmr->CubeDecPtr->aarStdDev,
                             &pmr->CubeDecPtr->esDouble );

	outputf( "%s %d%d", _("Rolled"), pmr->anDice[ 0 ], pmr->anDice[ 1 ] );

	if( pmr->rLuck != ERR_VAL )
	    outputf( " (%s):\n", GetLuckAnalysis( &ms, pmr->rLuck ) );
	else
	    outputl( ":" );
	
	for( i = 0; i < pmr->ml.cMoves; i++ ) {
	    if( i >= 10 /* FIXME allow user to choose limit */ &&
		i != pmr->n.iMove )
		continue;
	    outputc( i == pmr->n.iMove ? '*' : ' ' );
	    output( FormatMoveHint( szBuf, &ms, &pmr->ml, i,
				    i != pmr->n.iMove ||
				    i != pmr->ml.cMoves - 1 ||
                                    pmr->ml.cMoves == 1 ||
                                    i < exsExport.nMoves, TRUE, TRUE ) );

	}
	
	break;

    case MOVE_DOUBLE:
      DisplayCubeAnalysis(  pmr->CubeDecPtr->aarOutput,
                            pmr->CubeDecPtr->aarStdDev,
                           &pmr->CubeDecPtr->esDouble );
	break;

    case MOVE_TAKE:
    case MOVE_DROP:
	/* FIXME */
	break;
	
    case MOVE_SETDICE:
	if( pmr->rLuck != ERR_VAL )
	    outputf( "%s %d%d (%s):\n", _("Rolled"),
                     pmr->anDice[ 0 ], pmr->anDice[ 1 ], 
                     GetLuckAnalysis( &ms, pmr->rLuck ) );
	break;

    default:
	break;
    }
}

extern void ShowBoard( void )
{
    char szBoard[ 2048 ];
    char sz[ 50 ], szCube[ 50 ], szPlayer0[ MAX_NAME_LEN + 3 ], szPlayer1[ MAX_NAME_LEN + 3 ],
	szScore0[ 50 ], szScore1[ 50 ], szMatch[ 50 ];
    char *apch[ 7 ] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
    moverecord *pmr;
	TanBoard an;

	if( cOutputDisabled || !foutput_on)
		return;

    if( ms.gs == GAME_NONE ) {
#if USE_GTK
	if( fX ) {
	    TanBoard anBoardTemp;
	    InitBoard( anBoardTemp, ms.bgv );
	    game_set( BOARD( pwBoard ), anBoardTemp, 0, ap[ 1 ].szName,
		      ap[ 0 ].szName, ms.nMatchTo, ms.anScore[ 1 ],
		      ms.anScore[ 0 ], 0, 0, FALSE, anChequers[ ms.bgv ] );
	} else
#endif

	    outputl( _("No game in progress.") );
	
	return;
    }

	memcpy( an, msBoard(), sizeof(TanBoard) );
	if( !ms.fMove )
		SwapSides( an );

#if USE_GTK
    if( !fX ) {
#endif
	if( fOutputRawboard )
	{
	    outputl( FIBSBoard( szBoard, an, ms.fMove, ap[ 1 ].szName,
				ap[ 0 ].szName, ms.nMatchTo, ms.anScore[ 1 ],
				ms.anScore[ 0 ], ms.anDice[ 0 ],
				ms.anDice[ 1 ], ms.nCube,
				ms.fCubeOwner, ms.fDoubled, ms.fTurn,
				ms.fCrawford, anChequers[ ms.bgv ] ) );
	    
	    return;
	}
	
	apch[ 0 ] = szPlayer0;
	apch[ 6 ] = szPlayer1;

	sprintf( apch[ 1 ] = szScore0,
		 ngettext( "%d point", "%d points", ms.anScore[ 0 ] ),
		 ms.anScore[ 0 ] );

	sprintf( apch[ 5 ] = szScore1,
		 ngettext( "%d point", "%d points", ms.anScore[ 1 ] ),
		 ms.anScore[ 1 ] );

	if( ms.fDoubled ) {
	    apch[ ms.fTurn ? 4 : 2 ] = szCube;

	    sprintf( szPlayer0, "O: %s", ap[ 0 ].szName );
	    sprintf( szPlayer1, "X: %s", ap[ 1 ].szName );
	    sprintf( szCube, _("Cube offered at %d"), ms.nCube << 1 );
	} else {
	    sprintf( szPlayer0, "O: %s", ap[ 0 ].szName );
	    sprintf( szPlayer1, "X: %s", ap[ 1 ].szName );

	    apch[ ms.fMove ? 4 : 2 ] = sz;
	
	    if( ms.anDice[ 0 ] )
		sprintf( sz, 
                         _("%s %d%d"), _("Rolled"), ms.anDice[ 0 ], ms.anDice[ 1 ] );
	    else if( !GameStatus( msBoard(), ms.bgv ) )
		strcpy( sz, _("On roll") );
	    else
		sz[ 0 ] = 0;
	    
	    if( ms.fCubeOwner < 0 ) {
		apch[ 3 ] = szCube;

		if( ms.nMatchTo )
		    sprintf( szCube, 
                             _("%d point match (Cube: %d)"), ms.nMatchTo,
			     ms.nCube );
		else
		    sprintf (szCube, "(%s: %d)", _("Cube"), ms.nCube);
	    } else {
		int cch = strlen( ap[ ms.fCubeOwner ].szName );
		
		if( cch > 20 )
		    cch = 20;
		
		sprintf( szCube, "%c: %*s (%s: %d)", ms.fCubeOwner ? 'X' : 'O',
			cch, ap[ ms.fCubeOwner ].szName, _("Cube"), ms.nCube );

		apch[ ms.fCubeOwner ? 6 : 0 ] = szCube;

		if( ms.nMatchTo )
		    sprintf( apch[ 3 ] = szMatch, _("%d point match"),
			     ms.nMatchTo );
	    }
	}
	if( ms.fResigned > 0 )
	    /* FIXME it's not necessarily the player on roll that resigned */
	    sprintf( strchr( sz, 0 ), ", %s %s", _("resigns"),
		     gettext ( aszGameResult[ ms.fResigned - 1 ] ) );
	
	outputl( DrawBoard( szBoard, (ConstTanBoard)an, ms.fMove, apch,
                            MatchIDFromMatchState ( &ms ), 
                            anChequers[ ms.bgv ] ) );

	if (
#if USE_GTK
		PanelShowing(WINDOW_ANALYSIS) &&
#endif
		plLastMove && ( pmr = plLastMove->plNext->p ) ) {
	    DisplayAnalysis( pmr );
	    if( pmr->sz )
		outputl( pmr->sz ); /* FIXME word wrap */
	}
#if USE_GTK
    }
	else
	{
		game_set( BOARD( pwBoard ), an, ms.fMove, ap[ 1 ].szName,
		ap[ 0 ].szName, ms.nMatchTo, ms.anScore[ 1 ],
		ms.anScore[ 0 ], ms.anDice[ 0 ], ms.anDice[ 1 ],
		ap[ ms.fTurn ].pt != PLAYER_HUMAN && !fComputing &&
		!nNextTurn, anChequers[ ms.bgv ] );
	}
#endif    

#ifdef UNDEF
    {
      char *pc;
      printf ( "%s: %s\n", _("MatchID"), pc = MatchIDFromMatchState ( &ms ) );
      MatchStateFromID ( &ms, pc );
    }
#endif
}

extern char *FormatPrompt( void )
{

    static char sz[ 128 ]; /* FIXME check for overflow in rest of function */
    const char *pch = szPrompt;
    char *pchDest = sz;
    unsigned int anPips[ 2 ];

    while( *pch )
	if( *pch == '\\' ) {
	    pch++;
	    switch( *pch ) {
	    case 0:
		goto done;
		
	    case 'c':
	    case 'C':
		/* Pip count */
		if( ms.gs == GAME_NONE )
		    strcpy( pchDest, _("No game") );
		else {
		    PipCount( msBoard(), anPips );
		    sprintf( pchDest, "%d:%d", anPips[ 1 ], anPips[ 0 ] );
		}
		break;

	    case 'p':
	    case 'P':
		/* Player on roll */
		switch( ms.gs ) {
		case GAME_NONE:
		    strcpy( pchDest, _("No game") );
		    break;

		case GAME_PLAYING:
		    strcpy( pchDest, ap[ ms.fTurn ].szName );
		    break;

		case GAME_OVER:
		case GAME_RESIGNED:
		case GAME_DROP:
		    strcpy( pchDest, _("Game over") );
		    break;
		}
		break;
		
	    case 's':
	    case 'S':
		/* Match score */
		sprintf( pchDest, "%d:%d", ms.anScore[ 0 ], ms.anScore[ 1 ] );
		break;

	    case 'v':
	    case 'V':
		/* Version */
		strcpy( pchDest, VERSION );
		break;
		
	    default:
		*pchDest++ = *pch;
		*pchDest = 0;
	    }

	    pchDest = strchr( pchDest, 0 );
	    pch++;
	} else
	    *pchDest++ = *pch++;
    
 done:
    *pchDest = 0;

    return sz;
}

extern void CommandEval( char *sz )
{

    char szOutput[ 4096 ];
    int n;
	TanBoard an;
    cubeinfo ci;
	decisionData dd;
    
    if( !*sz && ms.gs == GAME_NONE ) {
	outputl( _("No position specified and no game in progress.") );
	return;
    }

    if( ( n = ParsePosition( an, &sz, NULL ) ) < 0 )
	return;

    if( n && ms.fMove )
	/* =n notation used; the opponent is on roll in the position given. */
	SwapSides( an );

    if( ms.gs == GAME_NONE )
	memcpy( &ci, &ciCubeless, sizeof( ci ) );
    else
	SetCubeInfo( &ci, ms.nCube, ms.fCubeOwner, n ? !ms.fMove : ms.fMove,
		     ms.nMatchTo, ms.anScore, ms.fCrawford, ms.fJacoby,
		     nBeavers, ms.bgv );    

    /* Consider cube action */
	dd.pboard = (ConstTanBoard)an;
	dd.pec = &GetEvalCube()->ec;
	dd.pci = &ci;
	dd.szOutput = szOutput;
	dd.n = n;
	if (RunAsyncProcess((AsyncFun)asyncDumpDecision, &dd, _("Evaluating position...")) == 0)
	{
#if USE_GTK
	if( fX )
	    GTKEval( szOutput );
	else
#endif
	    outputl( szOutput );
    }
}

extern command *FindHelpCommand( command *pcBase, char *sz,
				 char *pchCommand, char *pchUsage ) {

    command *pc;
    const char *pch;
    int cch;
    
    if( !( pch = NextToken( &sz ) ) )
	return pcBase;

    cch = strlen( pch );

    for( pc = pcBase->pc; pc && pc->sz; pc++ )
	if( !StrNCaseCmp( pch, pc->sz, cch ) )
	    break;

    if( !pc || !pc->sz )
	return NULL;

    pch = pc->sz;
    while( *pch )
	*pchCommand++ = *pchUsage++ = *pch++;
    *pchCommand++ = ' '; *pchCommand = 0;
    *pchUsage++ = ' '; *pchUsage = 0;

    if( pc->szUsage ) {
	pch = gettext ( pc->szUsage );
	while( *pch )
	    *pchUsage++ = *pch++;
	*pchUsage++ = ' '; *pchUsage = 0;	
    }
    
    if( pc->pc )
	/* subcommand */
	return FindHelpCommand( pc, sz, pchCommand, pchUsage );
    else
	/* terminal command */
	return pc;
}

extern char* CheckCommand(char *sz, command *ac)
{
	command *pc;
	int cch;
    char *pch = NextToken(&sz);
	if (!pch)
		return 0;

    cch = strlen( pch );
	for (pc = ac; pc->sz; pc++)
		if (!StrNCaseCmp(pch, pc->sz, cch))
			break;
    if (!pc->sz)
		return pch;

    if (pc->pf)
	{
		return 0;
    }
	else
	{
		return CheckCommand(sz, pc->pc);
	}
}

extern void CommandHelp( char *sz )
{

    command *pc, *pcFull;
    char szCommand[ 128 ], szUsage[ 128 ], *szHelp;
    
#if USE_GTK 
    if( fX ){
        GTKHelp( sz );
        return;
    }
#endif
    
    if( !( pc = FindHelpCommand( &cTop, sz, szCommand, szUsage ) ) ) {
	outputf( _("No help available for topic `%s'"), sz );
	output("\n");

	return;
    }

    if( pc->szHelp )
	/* the command has its own help text */
	szHelp = gettext ( pc->szHelp );
    else if( pc == &cTop )
	/* top-level help isn't for any command */
	szHelp = NULL;
    else {
	/* perhaps the command is an abbreviation; search for the full
	   version */
	szHelp = NULL;

	for( pcFull = acTop; pcFull->sz; pcFull++ )
	    if( pcFull->pf == pc->pf && pcFull->szHelp ) {
		szHelp = gettext ( pcFull->szHelp );
		break;
	    }
    }

    if( szHelp ) {
	outputf( "%s- %s\n\n%s: %s", szCommand, szHelp, _("Usage"), szUsage);

	if( pc->pc && pc->pc->sz )
	    outputf( "<%s>\n", _("subcommand") );
	else
	    outputc( '\n' );
    }

    if( pc->pc && pc->pc->sz ) {
	outputl( pc == &cTop ? _("Available commands:") :
		 _("Available subcommands:") );

	pc = pc->pc;
	
	for( ; pc->sz; pc++ )
	    if( pc->szHelp )
		outputf( "%-15s\t%s\n", pc->sz, gettext ( pc->szHelp ) );
    }
}


extern char *FormatMoveHint( char *sz, const matchstate *pms, movelist *pml,
			     int i, int fRankKnown,
                             int fDetailProb, int fShowParameters ) {
    
    cubeinfo ci;
    char szTemp[ 2048 ], szMove[ 32 ];
    char *pc;
    float *ar, *arStdDev;
    float rEq, rEqTop;

    GetMatchStateCubeInfo( &ci, pms );

    strcpy ( sz, "" );

    /* number */

    if ( i && ! fRankKnown )
      strcat( sz, "   ??  " );
    else
      sprintf ( pc = strchr ( sz, 0 ),
                " %4i. ", i + 1 );

    /* eval */

    sprintf ( pc = strchr ( sz, 0 ),
              "%-14s   %-28s %s: ",
              FormatEval ( szTemp, &pml->amMoves[ i ].esMove ),
              FormatMove( szMove, pms->anBoard, 
                          pml->amMoves[ i ].anMove ),
              ( !pms->nMatchTo || ( pms->nMatchTo && ! fOutputMWC ) ) ?
              _("Eq.") : _("MWC") );

    /* equity or mwc for move */

    ar = pml->amMoves[ i ].arEvalMove;
    arStdDev = pml->amMoves[ i ].arEvalStdDev;
    rEq = pml->amMoves[ i ].rScore;
    rEqTop = pml->amMoves[ 0 ].rScore;
    
    strcat ( sz, OutputEquity ( rEq, &ci, TRUE ) );

    /* difference */
   
    if ( i ) 
      sprintf ( pc = strchr ( sz, 0 ),
                " (%s)\n",
                OutputEquityDiff ( rEq, rEqTop, &ci ) );
    else
      strcat ( sz, "\n" );

    /* percentages */

    if ( fDetailProb ) {

      switch ( pml->amMoves[ i ].esMove.et ) {
      case EVAL_EVAL:
        /* FIXME: add cubeless and cubeful equities */
        strcat ( sz, "       " );
        strcat ( sz, OutputPercents ( ar, TRUE ) );
        strcat ( sz, "\n" );
        break;
      case EVAL_ROLLOUT:
        strcat ( sz, 
                 OutputRolloutResult ( "     ",
                                       NULL,
                                       ( float (*)[NUM_ROLLOUT_OUTPUTS] )
                                       ar,
                                       ( float (*)[NUM_ROLLOUT_OUTPUTS] )
                                       arStdDev,
                                       &ci,
                                       0,
                                       1, 
                                       pml->amMoves[ i ].esMove.rc.fCubeful ) );
        break;
      default:
        break;

      }
    }

    /* eval parameters */

    if ( fShowParameters ) {

      switch ( pml->amMoves[ i ].esMove.et ) {
      case EVAL_EVAL:
        strcat ( sz, "        " );
        strcat ( sz, 
                 OutputEvalContext ( &pml->amMoves[ i ].esMove.ec, TRUE ) );
        strcat ( sz, "\n" );
        break;
      case EVAL_ROLLOUT:
        strcat ( sz,
                 OutputRolloutContext ( "        ",
                                        &pml->amMoves[ i ].esMove.rc ) );
        break;

      default:
        break;

      }

    }

    return sz;

#if 0

    
    if ( !pms->nMatchTo || ( pms->nMatchTo && ! fOutputMWC ) ) {
	/* output in equity */
        float *ar, rEq, rEqTop;

	ar = pml->amMoves[ 0 ].arEvalMove;
	rEqTop = pml->amMoves[ 0 ].rScore;

	if( !i ) {
	    if( fOutputWinPC )
		sprintf( sz, " %4i. %-14s   %-28s Eq.: %+6.3f\n"
			 "       %5.1f%% %5.1f%% %5.1f%%  -"
			 " %5.1f%% %5.1f%% %5.1f%%\n",
			 1, FormatEval ( szTemp, &pml->amMoves[ 0 ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ 0 ].anMove ),
			 rEqTop, 
			 100.0 * ar[ 0 ], 100.0 * ar[ 1 ], 100.0 * ar[ 2 ],
			 100.0 * ( 1.0 - ar[ 0 ] ) , 100.0 * ar[ 3 ], 
			 100.0 * ar[ 4 ] );
	    else
		sprintf( sz, " %4i. %-14s   %-28s Eq.: %+6.3f\n"
			 "       %5.3f %5.3f %5.3f  -"
			 " %5.3f %5.3f %5.3f\n",
			 1, FormatEval ( szTemp, &pml->amMoves[ 0 ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ 0 ].anMove ),
			 rEqTop, 
			 ar[ 0 ], ar[ 1 ], ar[ 2 ],
			 ( 1.0 - ar[ 0 ] ) , ar[ 3 ], 
			 ar[ 4 ] );
	} else {
	    ar = pml->amMoves[ i ].arEvalMove;
	    rEq = pml->amMoves[ i ].rScore;

	    if( fRankKnown )
		sprintf( sz, " %4i.", i + 1 );
	    else
		strcpy( sz, "   ?? " );
	    
	    if( fOutputWinPC )
		sprintf( sz + 6, " %-14s   %-28s Eq.: %+6.3f (%+6.3f)\n"
			 "       %5.1f%% %5.1f%% %5.1f%%  -"
			 " %5.1f%% %5.1f%% %5.1f%%\n",
			 FormatEval ( szTemp, &pml->amMoves[ i ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ i ].anMove ),
			 rEq, rEq - rEqTop,
			 100.0 * ar[ 0 ], 100.0 * ar[ 1 ], 100.0 * ar[ 2 ],
			 100.0 * ( 1.0 - ar[ 0 ] ) , 100.0 * ar[ 3 ], 
			 100.0 * ar[ 4 ] );
	    else
		sprintf( sz + 6, " %-14s   %-28s Eq.: %+6.3f (%+6.3f)\n"
			 "       %5.3f %5.3f %5.3f  -"
			 " %5.3f %5.3f %5.3f\n",
			 FormatEval ( szTemp, &pml->amMoves[ i ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ i ].anMove ),
			 rEq, rEq - rEqTop,
			 ar[ 0 ], ar[ 1 ], ar[ 2 ],
			 ( 1.0 - ar[ 0 ] ) , ar[ 3 ], 
			 ar[ 4 ] );
	}
    } else {
 	/* output in mwc */

        float *ar, rMWC, rMWCTop;
	
	ar = pml->amMoves[ 0 ].arEvalMove;
	rMWCTop = 100.0 * eq2mwc ( pml->amMoves[ 0 ].rScore, &ci );

	if( !i ) {
	    if( fOutputWinPC )
		sprintf( sz, " %4i. %-14s   %-28s MWC: %7.3f%%\n"
			 "       %5.1f%% %5.1f%% %5.1f%%  -"
			 " %5.1f%% %5.1f%% %5.1f%%\n",
			 1, FormatEval ( szTemp, &pml->amMoves[ 0 ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ 0 ].anMove ),
			 rMWCTop, 
			 100.0 * ar[ 0 ], 100.0 * ar[ 1 ], 100.0 * ar[ 2 ],
			 100.0 * ( 1.0 - ar[ 0 ] ) , 100.0 * ar[ 3 ], 
			 100.0 * ar[ 4 ] );
	    else
		sprintf( sz, " %4i. %-14s   %-28s MWC: %7.3f%%\n"
			 "       %5.3f %5.3f %5.3f  -"
			 " %5.3f %5.3f %5.3f\n",
			 1, FormatEval ( szTemp, &pml->amMoves[ 0 ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ 0 ].anMove ),
			 rMWCTop, 
			 ar[ 0 ], ar[ 1 ], ar[ 2 ],
			 ( 1.0 - ar[ 0 ] ) , ar[ 3 ], 
			 ar[ 4 ] );
        } else {
	    ar = pml->amMoves[ i ].arEvalMove;
	    rMWC = 100.0 * eq2mwc ( pml->amMoves[ i ].rScore, &ci );

	    if( fRankKnown )
		sprintf( sz, " %4i.", i + 1 );
	    else
		strcpy( sz, "   ?? " );
	    
	    if( fOutputWinPC )
		sprintf( sz + 6, " %-14s   %-28s MWC: %7.3f%% (%+7.3f%%)\n"
			 "       %5.1f%% %5.1f%% %5.1f%%  -"
			 " %5.1f%% %5.1f%% %5.1f%%\n",
			 FormatEval ( szTemp, &pml->amMoves[ i ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ i ].anMove ),
			 rMWC, rMWC - rMWCTop,
			 100.0 * ar[ 0 ], 100.0 * ar[ 1 ], 100.0 * ar[ 2 ],
			 100.0 * ( 1.0 - ar[ 0 ] ) , 100.0 * ar[ 3 ], 
			 100.0 * ar[ 4 ] );
	    else
		sprintf( sz + 6, " %-14s   %-28s MWC: %7.3f%% (%+7.3f%%)\n"
			 "       %5.3f %5.3f %5.3f  -"
			 " %5.3f %5.3f %5.3f\n",
			 FormatEval ( szTemp, &pml->amMoves[ i ].esMove ), 
			 FormatMove( szMove, pms->anBoard, 
				     pml->amMoves[ i ].anMove ),
			 rMWC, rMWC - rMWCTop,
			 ar[ 0 ], ar[ 1 ], ar[ 2 ],
			 ( 1.0 - ar[ 0 ] ) , ar[ 3 ], 
			 ar[ 4 ] );
	}
    }

    return sz;
#endif
}


static void HintResigned( void )
{
  float rEqBefore, rEqAfter;
  static cubeinfo ci;
  static decisionData dd;

  GetMatchStateCubeInfo( &ci, &ms );

#if USE_BOARD3D
    if (fX)
	{	/* Stop waving flag, otherwise hangs */
		BoardData* bd = BOARD(pwBoard)->board_data;
		StopIdle3d(bd, bd->bd3d);
	}
#endif

  /* evaluate current position */

  dd.pboard = msBoard();
  dd.pci = &ci;
  dd.pec = &GetEvalCube()->ec;
  if (RunAsyncProcess((AsyncFun)asyncMoveDecisionE, &dd, _("Considering resignation...")) != 0)
	return;
  
  getResignEquities ( dd.aarOutput[0], &ci, ms.fResigned, 
                      &rEqBefore, &rEqAfter );
  
#if USE_GTK
  if ( fX ) {
    
    GTKResignHint ( dd.aarOutput[0], rEqBefore, rEqAfter, &ci, 
                    ms.nMatchTo && fOutputMWC );
    
    return;
    
  }
#endif
  
  if ( ! ms.nMatchTo || ( ms.nMatchTo && ! fOutputMWC ) ) {
    
    outputf ( "%s : %+6.3f\n", _("Equity before resignation"),
              - rEqBefore );
    outputf ( "%s : %+6.3f (%+6.3f)\n\n", _("Equity after resignation"),
              - rEqAfter, rEqBefore - rEqAfter );
    outputf ( "%s : %s\n\n", _("Correct resign decision"),
              ( rEqBefore - rEqAfter >= 0 ) ?
              _("Accept") : _("Reject") );
    
  }
  else {
    
    rEqBefore = eq2mwc ( - rEqBefore, &ci );
    rEqAfter  = eq2mwc ( - rEqAfter, &ci );
    
    outputf ( "%s : %6.2f%%\n", _("Equity before resignation"),
              rEqBefore * 100.0f );
    outputf ( "%s : %6.2f%% (%6.2f%%)\n\n", _("Equity after resignation"),
              rEqAfter * 100.0f,
              100.0f * ( rEqAfter - rEqBefore ) );
    outputf ( "%s : %s\n\n", _("Correct resign decision"),
              ( rEqAfter - rEqBefore >= 0 ) ?
              _("Accept") : _("Reject") );
  }
}

static int hint_cube(moverecord *pmr, cubeinfo *pci)
{
	static decisionData dd;
	if (pmr->CubeDecPtr->esDouble.et == EVAL_NONE) {
		/* no analysis performed yet */
		dd.pboard = msBoard();
		dd.pci = pci;
		dd.pes = GetEvalCube();
		if (RunAsyncProcess
		    ((AsyncFun) asyncCubeDecision, &dd,
		     _("Considering cube action...")) != 0)
			return -1;

		pmr_cubedata_set(pmr, dd.pes, dd.aarOutput, dd.aarStdDev);
	}
	return 0;
}

static skilltype no_double_skill(moverecord *pmr, cubeinfo *pci)
{
	float arDouble[4];
	float eq = 0.0f;
	cubedecision cd;
	if (pmr->CubeDecPtr->esDouble.et == EVAL_NONE)
		return SKILL_NONE;
	cd = FindCubeDecision(arDouble, pmr->CubeDecPtr->aarOutput, pci);
	switch (cd) {
	case DOUBLE_TAKE:
	case DOUBLE_BEAVER:
	case REDOUBLE_TAKE:
		eq = arDouble[OUTPUT_NODOUBLE] - arDouble[OUTPUT_TAKE];
		break;
	case DOUBLE_PASS:
	case REDOUBLE_PASS:
		eq = arDouble[OUTPUT_NODOUBLE] - arDouble[OUTPUT_DROP];
		break;
	default:
		break;
	}

	return Skill(eq);
}

static skilltype double_skill(moverecord *pmr, cubeinfo *pci)
{
	float arDouble[4];
	float eq = 0.0f;
	cubedecision cd;
	if (pmr->CubeDecPtr->esDouble.et == EVAL_NONE)
		return SKILL_NONE;
	cd = FindCubeDecision(arDouble, pmr->CubeDecPtr->aarOutput, pci);

	switch (cd) {
	case NODOUBLE_TAKE:
	case NODOUBLE_BEAVER:
	case NO_REDOUBLE_TAKE:
	case NO_REDOUBLE_BEAVER:
	case TOOGOOD_TAKE:
	case TOOGOODRE_TAKE:
		eq = arDouble[OUTPUT_TAKE] - arDouble[OUTPUT_NODOUBLE];
		break;

	case TOOGOOD_PASS:
	case TOOGOODRE_PASS:
		eq = arDouble[OUTPUT_DROP] - arDouble[OUTPUT_NODOUBLE];
		break;
	default:
		break;
	}
	return Skill(eq);
}

static skilltype drop_skill(moverecord *pmr, cubeinfo *pci)
{
	float arDouble[4];
	float eq = 0.0f;
	cubedecision cd;
	if (pmr->CubeDecPtr->esDouble.et == EVAL_NONE)
		return SKILL_NONE;
	cd = FindCubeDecision(arDouble, pmr->CubeDecPtr->aarOutput, pci);
	switch (cd) {
	case DOUBLE_TAKE:
	case DOUBLE_BEAVER:
	case REDOUBLE_TAKE:
	case NODOUBLE_TAKE:
	case NODOUBLE_BEAVER:
	case NO_REDOUBLE_TAKE:
	case NO_REDOUBLE_BEAVER:
	case TOOGOOD_TAKE:
	case TOOGOODRE_TAKE:
	case OPTIONAL_DOUBLE_BEAVER:
	case OPTIONAL_DOUBLE_TAKE:
	case OPTIONAL_REDOUBLE_TAKE:
		/* equity is for doubling player, invert for response */
		eq = arDouble[OUTPUT_TAKE] - arDouble[OUTPUT_DROP];
		break;
	default:
		break;
	}
	return Skill(eq);
}

static skilltype take_skill(moverecord *pmr, cubeinfo *pci)
{
	float arDouble[4];
	float eq = 0.0f;
	cubedecision cd;
	if (pmr->CubeDecPtr->esDouble.et == EVAL_NONE)
		return SKILL_NONE;
	cd = FindCubeDecision(arDouble, pmr->CubeDecPtr->aarOutput, pci);
	switch (cd) {
	case DOUBLE_PASS:
	case REDOUBLE_PASS:
	case TOOGOOD_PASS:
	case TOOGOODRE_PASS:
	case OPTIONAL_DOUBLE_PASS:
	case OPTIONAL_REDOUBLE_PASS:
		/* equity is for doubling player, invert for response */
		eq = arDouble[OUTPUT_DROP] - arDouble[OUTPUT_TAKE];
		break;
	default:
		break;
	}
	return Skill(eq);
}

static skilltype move_skill(moverecord *pmr)
{
	move *move_i;
	move *move_0;
	if (pmr->n.iMove >= pmr->ml.cMoves || !pmr->ml.amMoves)
		return SKILL_NONE;
	move_i = &pmr->ml.amMoves[pmr->n.iMove];
	move_0 = &pmr->ml.amMoves[0];
	if (move_i->esMove.et == EVAL_NONE || move_0->esMove.et == EVAL_NONE)
		return SKILL_NONE;
	else
		return Skill(move_i->rScore - move_0->rScore);
}

extern void find_skills(moverecord *pmr, const matchstate *pms, int did_double,
			int did_take)
{
	cubeinfo ci;
	doubletype dt = DoubleType(pms->fDoubled, pms->fMove, pms->fTurn);
	taketype tt = (taketype) dt;
	GetMatchStateCubeInfo(&ci, pms);

	if (pmr->mt != MOVE_NORMAL && pmr->mt != MOVE_DOUBLE
	    && pmr->mt != MOVE_TAKE && pmr->mt != MOVE_DROP) {
		pmr->n.stMove = SKILL_NONE;
		pmr->stCube = SKILL_NONE;
		return;
	}
	if (pmr->mt == MOVE_DOUBLE && dt != DT_NORMAL) {
		pmr->stCube = SKILL_NONE;
		return;
	}
	if (pmr->mt == MOVE_TAKE && tt > TT_NORMAL) {
		pmr->stCube = SKILL_NONE;
		return;
	} 

	if (did_double == FALSE)
		pmr->stCube = no_double_skill(pmr, &ci);
	else if (did_double == TRUE)
		pmr->stCube = double_skill(pmr, &ci);
	else if (did_take == FALSE)
		pmr->stCube = drop_skill(pmr, &ci);
	else if (did_take == TRUE)
		pmr->stCube = take_skill(pmr, &ci);
	else
		pmr->stCube = SKILL_NONE;

	if (pmr->mt == MOVE_NORMAL && pmr->ml.cMoves > 0
	    && pmr->n.iMove < pmr->ml.cMoves)
		pmr->n.stMove = move_skill(pmr);

}

extern void hint_double(int show, int did_double)
{
	static cubeinfo ci;
	moverecord *pmr;
	int hist;
	doubletype dt = DoubleType ( ms.fDoubled, ms.fMove, ms.fTurn );
	if (dt != DT_NORMAL)
	{
		if (show)
			outputerrf( _("This decision is part of beaver/raccoon sequence and cannot be hinted"));
		return;
	}

	GetMatchStateCubeInfo(&ci, &ms);

	if (!GetDPEq(NULL, NULL, &ci)) {
		outputerrf(_("You cannot double."));
		return;
	}

	pmr = get_current_moverecord(&hist);

	if (!pmr)
		return;

	if (hint_cube(pmr, &ci) < 0)
		return;

	if (hist && did_double == -1)
		did_double = (pmr->mt == MOVE_DOUBLE) ? TRUE : FALSE;

	find_skills(pmr, &ms, did_double, -1);

#if USE_GTK
	if (fX) {
		if (hist && show)
			ChangeGame(NULL);
		if (show)
			GTKCubeHint(pmr, &ms, did_double, -1, hist);
		return;
	}
#endif
	outputl(OutputCubeAnalysis(pmr->CubeDecPtr->aarOutput, pmr->CubeDecPtr->aarStdDev, &pmr->CubeDecPtr->esDouble, &ci));
}

extern void hint_take(int show, int did_take)
{
	static cubeinfo ci;
	moverecord *pmr;
	int hist;
	taketype tt = (taketype) DoubleType ( ms.fDoubled, ms.fMove, ms.fTurn );
        if ( tt > TT_NORMAL )
	{
		if (show)
			outputerrf( _("This decision is part of beaver/raccoon sequence and cannot be hinted"));
		return;
	}

	GetMatchStateCubeInfo(&ci, &ms);
	pmr = get_current_moverecord(&hist);
	if (!pmr)
		return;
	if (hint_cube(pmr, &ci) < 0)
		return;

	if (hist && did_take == -1)
		did_take = (pmr->mt == MOVE_TAKE) ? TRUE : FALSE;

	find_skills(pmr, &ms, -1, did_take);

#if USE_GTK
	if (fX) {
		if (hist && show)
			ChangeGame(NULL);
		if (show)
			GTKCubeHint(pmr, &ms, -1, did_take, hist);
		return;
	}
#endif

	outputl(OutputCubeAnalysis(pmr->CubeDecPtr->aarOutput, pmr->CubeDecPtr->aarStdDev, &pmr->CubeDecPtr->esDouble, &ci));
}

extern void hint_move(char *sz, gboolean show)
{
	unsigned int i;
	char szBuf[1024];
	int parse_n = ParseNumber(&sz);
	unsigned int n = (parse_n <= 0) ? 10 : parse_n;
	moverecord *pmr;
	cubeinfo ci;
	int hist;
	movelist ml;
	findData fd;

	GetMatchStateCubeInfo(&ci, &ms);

	pmr = get_current_moverecord(&hist);
	if(!pmr)
		return;

	if (pmr->esChequer.et == EVAL_NONE) {
		fd.pml = &ml;
		fd.pboard = msBoard();
		fd.keyMove = NULL;
		fd.rThr = arSkillLevel[SKILL_DOUBTFUL];
		fd.pci = &ci;
		fd.pec = &GetEvalChequer()->ec;
		fd.aamf = *GetEvalMoveFilter();
		if ((RunAsyncProcess
		     ((AsyncFun) asyncFindMove, &fd,
		      _("Considering move...")) != 0) || fInterrupt)
			return;
		pmr_movelist_set(pmr, GetEvalChequer(), &ml);
	}
#if USE_GTK
	if (!hist && fX)
		GTKGetMove(pmr->n.anMove);
#endif
	if (pmr->n.anMove[0] == -1)
	{
		pmr->n.iMove = UINT_MAX;
		pmr->n.stMove = SKILL_NONE;
	}
	else if (pmr->n.anMove[0] != -1)
	{
		pmr->n.iMove = locateMove(msBoard(), pmr->n.anMove, &pmr->ml);
		/* Tutor mode may have called asyncFindMove() above before
		   n.iMove was known. Do it again, ensuring that the actual
		   move is evaluated at the best ply. */
		fd.pml = &ml;
		fd.pboard = msBoard();
		fd.keyMove = &(pmr->ml.amMoves[pmr->n.iMove].key);
		fd.rThr = arSkillLevel[SKILL_DOUBTFUL];
		fd.pci = &ci;
		fd.pec = &GetEvalChequer()->ec;
		fd.aamf = *GetEvalMoveFilter();
		asyncFindMove(&fd);
		pmr_movelist_set(pmr, GetEvalChequer(), &ml);
		find_skills(pmr, &ms, FALSE, -1);
	}

#if USE_GTK
	if (fX) {
		if (hist && show)
		ChangeGame(NULL);
		if (show)
			GTKHint(pmr, hist);
		return;
	} else
#endif
	if (!show)
		return;

	if (!pmr->ml.cMoves) {
		outputl(_("There are no legal moves."));
		return;
	}

	n = MIN(pmr->ml.cMoves, n);
	for (i = 0; i < n; i++)
		output(FormatMoveHint
		       (szBuf, &ms, &pmr->ml, i, TRUE, TRUE, TRUE));

}

extern void CommandHint( char *sz )
{

  if( ms.gs != GAME_PLAYING ) {
    outputl( _("You must set up a board first.") );
    
    return;
  }

  /* hint on cube decision */
  
  if( !ms.anDice[ 0 ] && !ms.fDoubled && ! ms.fResigned ) {
    hint_double(TRUE, -1);
    return;
  }

  /* Give hint on resignation */

  if ( ms.fResigned ) {
    HintResigned();
    return;
  }

  /* Give hint on take decision */

  if ( ms.fDoubled ) {
    hint_take(TRUE, -1);
    return;
  }

  /* Give hint on chequer play decision */

  if ( ms.anDice[ 0 ] ) {
    hint_move( sz, TRUE );
    return;
  }

}

static void
Shutdown( void ) {

  RenderFinalise();

  free_rngctx(rngctxCurrent);
  free_rngctx(rngctxRollout);

  FreeMatch();
  ClearMatch();

#if USE_GTK
  MoveListDestroy();
#endif

#if USE_MULTITHREAD
  MT_Close();
#endif
  EvalShutdown();

#if USE_PYTHON
  PythonShutdown();
#endif

#if HAVE_SOCKETS
#ifdef WIN32
  WSACleanup();
#endif
#endif

  SoundWait();
}

/* Called on various exit commands -- e.g. EOF on stdin, "quit" command,
   etc.  If stdin is not a TTY, this should always exit immediately (to
   avoid enless loops on EOF).  If stdin is a TTY, and fConfirmNew is set,
   and a game is in progress, then we ask the user if they're sure. */
extern void PromptForExit( void )
{

	static int fExiting = FALSE;
#if USE_GTK
	BoardData* bd = NULL;

	if (fX)
		bd = BOARD(pwBoard)->board_data;
#endif
	if (fExiting)
		return;

	fExiting = TRUE;

	if( fInteractive) {
		fInterrupt = FALSE;
		if (!get_input_discard())
		{
			fInterrupt = FALSE;
			fExiting = FALSE;
			return;
		}
	}

#if USE_BOARD3D
    if (fX && (display_is_3d(bd->rd)))
	{	/* Stop any 3d animations */
		StopIdle3d(bd, bd->bd3d);
	}
#endif
#if HAVE_SOCKETS
	/* Close any open connections */
	if( ap[0].pt == PLAYER_EXTERNAL )
		closesocket( ap[0].h );
	if( ap[1].pt == PLAYER_EXTERNAL )
		closesocket( ap[1].h );
#endif

	playSound ( SOUND_EXIT );

#if USE_BOARD3D
	if (fX && display_is_3d(bd->rd) && bd->rd->closeBoardOnExit && bd->rd->fHinges3d)
		CloseBoard3d(bd, bd->bd3d, bd->rd);
#endif
	ProcessEvents();

	SoundWait();    /* Wait for sound to finish before final close */

    if( fInteractive )
	PortableSignalRestore( SIGINT, &shInterruptOld );
    
#if USE_GTK
	if (fX)
	{
		stop_board_expose(bd);
		board_free_pixmaps(bd);
	}

#if USE_BOARD3D
	if (fX && gtk_gl_init_success)
		Tidy3dObjects(bd->bd3d, bd->rd);
#endif
#endif

#if HAVE_LIBREADLINE
        write_history( gnubg_histfile );
#endif /* HAVE_READLINE */

#if USE_GTK
	if (gtk_main_level() == 1)
		gtk_main_quit();
	else
#endif
	{
		Shutdown();
		exit( EXIT_SUCCESS );
	}
}

extern void CommandNotImplemented( char *UNUSED(sz) )
{
    outputl( _("That command is not yet implemented.") );
}

extern void CommandQuit( char *UNUSED(sz) )
{
    PromptForExit();
}


extern void CommandRollout(char *sz)
{
	float arOutput [ NUM_ROLLOUT_OUTPUTS ];
	float arStdDev [ NUM_ROLLOUT_OUTPUTS ];
	rolloutstat arsStatistics[ 2 ];
	TanBoard anBoard;
	cubeinfo ci;
	char asz[1][40];
	void *p;

	if (CountTokens(sz) > 0) {
		outputerrf("%s", _("The rollout command takes no arguments and only rollouts the current position"));
		return;
	}
	if (ms.gs != GAME_PLAYING) {
		outputerrf("%s", _("No position specified and no game in progress."));
		return;
	}
#if USE_GTK
	if (fX)
		GTKShowWarning(WARN_ROLLOUT, NULL);
#endif
	sprintf(asz[0], _("Current Position"));
	memcpy(anBoard, msBoard(), sizeof(TanBoard));
	SetCubeInfo(&ci, ms.nCube, ms.fCubeOwner, ms.fMove, ms.nMatchTo, ms.anScore, ms.fCrawford, ms.fJacoby, nBeavers, ms.bgv);
	RolloutProgressStart(&ci, 1, NULL, &rcRollout, asz, FALSE, &p);
	GeneralEvaluationR(arOutput, arStdDev, arsStatistics, (ConstTanBoard)anBoard, &ci, &rcRollout, RolloutProgress, p);
	RolloutProgressEnd(&p, FALSE);

}

static void LoadCommands(FILE * pf, char *szFile)
{
	char sz[2048], *pch;

	outputpostpone();

	/* FIXME shouldn't restart sys calls on signals during this fgets */
	while (fgets(sz, sizeof(sz), pf) != NULL) {

		if ((pch = strchr(sz, '\n')))
			*pch = 0;
		if ((pch = strchr(sz, '\r')))
			*pch = 0;

		if (fInterrupt) {
			outputresume();
			return;
		}

		if (*sz == '#')	/* Comment */
			continue;

#if USE_PYTHON

		if (!strcmp(sz, ">")) {

			/* Python escape. */

			/* Ideally we should be able to handle both > print 1+1 sys.exit() and > print 1+1
			   currently we only handle the latter... */

			outputerrf("%s", _("Only Python commands supported, not multiline code"));

			continue;

		}
#endif				/* USE_PYTHON */

		HandleCommand(sz, acTop);

		/* FIXME handle NextTurn events? */
	}
	if (ferror(pf)) {
		outputerr(szFile);
	}

	outputresume();
}

extern void CommandLoadPython(char *sz)
{
	sz = NextToken(&sz);

	if (sz && *sz)
#if USE_PYTHON
		LoadPythonFile(sz);
#else
	outputl(_("This build of GNU Backgammon does not support Python"));
#endif
	else
		outputl(_("You must specify a file to load from."));
}

extern void CommandLoadCommands( char *sz )
{
    FILE *pf;

#if defined(WIN32)	
    /* Make sure unquoted filenames are quoted before processing 
       to allow proper support for filenames with spaces */
    char *szz = NULL;
    if (sz[0] != '"' && sz[0] != '\'') {
      szz = g_strdup_printf("'%s'", sz);
      sz = NextToken( &szz );
    }
    else
      sz = NextToken( &sz );
#else
    sz = NextToken( &sz );
#endif    
    
    if( !sz || !*sz ) {
	outputl( _("You must specify a file to load from.") );
	return;
    }

    if( ( pf = g_fopen( sz, "r" ) ) ) {
	LoadCommands( pf, sz );
	fclose( pf );
    } else
	outputerr( sz );

#if defined(WIN32)
    if (szz)	
      g_free(szz);
#endif
}


extern void CommandCopy (char *UNUSED(sz))
{
  char *aps[ 7 ] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
  char szOut[2048];
  char szCube[32], szPlayer0[MAX_NAME_LEN + 3], szPlayer1[MAX_NAME_LEN + 3],
    szScore0[35], szScore1[35], szMatch[35];
  char szRolled[ 32 ];
  TanBoard anBoardTemp;
    
  aps[0] = szPlayer0;
  aps[6] = szPlayer1;

  sprintf (aps[1] = szScore0,
	   ngettext("%d point", "%d points", ms.anScore[0]),
	   ms.anScore[0]);

  sprintf (aps[5] = szScore1,
	   ngettext("%d point", "%d points", ms.anScore[1]),
	   ms.anScore[1]);

  if (ms.fDoubled)
    {
      aps[ms.fTurn ? 4 : 2] = szCube;

      sprintf (szPlayer0, "O: %s", ap[0].szName);
      sprintf (szPlayer1, "X: %s", ap[1].szName);
      sprintf (szCube, _("Cube offered at %d"), ms.nCube << 1);
    }
  else
    {
      sprintf (szPlayer0, "O: %s", ap[0].szName);
      sprintf (szPlayer1, "X: %s", ap[1].szName);

      aps[ms.fMove ? 4 : 2] = szRolled;

      if (ms.anDice[0])
	sprintf (szRolled, "%s %d%d", _("Rolled"), ms.anDice[0], ms.anDice[1]);
      else if (!GameStatus (msBoard(), ms.bgv))
	strcpy (szRolled, _("On roll"));
      else
	szRolled[0] = 0;

      if (ms.fCubeOwner < 0)
	{
	  aps[3] = szCube;

	  if (ms.nMatchTo)
	    sprintf (szCube, _("%d point match (Cube: %d)"), ms.nMatchTo,
		     ms.nCube);
	  else
	    sprintf (szCube, "(%s: %d)", _("Cube"), ms.nCube);
	}
      else
	{
	  int cch = strlen (ap[ms.fCubeOwner].szName);

	  if (cch > 20)
	    cch = 20;

	  sprintf (szCube, "%c: %*s (%s: %d)", ms.fCubeOwner ? 'X' :
		   'O', cch, ap[ms.fCubeOwner].szName, _("Cube"), ms.nCube);

	  aps[ms.fCubeOwner ? 6 : 0] = szCube;

	  if (ms.nMatchTo)
	    sprintf (aps[3] = szMatch, _("%d point match"), ms.nMatchTo);
	}
    }

  memcpy ( anBoardTemp, msBoard(), sizeof(TanBoard) );

  if ( ! ms.fMove )
    SwapSides ( anBoardTemp );

  DrawBoard (szOut, (ConstTanBoard)anBoardTemp, ms.fMove, aps, MatchIDFromMatchState (&ms),
             anChequers[ ms.bgv ] );
  strcat (szOut, "\n");
  TextToClipboard (szOut);
}

static void LoadRCFiles(void)
{
	char *sz, *szz;

	loading_rc = TRUE;
	outputoff();
	sz = g_build_filename(szHomeDirectory, "gnubgautorc", NULL);
	szz = g_strdup_printf("'%s'", sz);
	if (g_file_test(sz, G_FILE_TEST_EXISTS))
		CommandLoadCommands(szz);
	
	UpdateSettings();
	g_free(sz);
	g_free(szz);

	sz = g_build_filename(szHomeDirectory, "gnubgrc", NULL);
	szz = g_strdup_printf("'%s'", sz);
	if (g_file_test(sz, G_FILE_TEST_EXISTS))
		CommandLoadCommands(szz);
	g_free(sz);
	g_free(szz);

	outputon();
	loading_rc = FALSE;
}


static void
SaveRNGSettings ( FILE *pf, const char *sz, rng rngCurrent, rngcontext *rngctx ) {

    switch( rngCurrent ) {
    case RNG_ANSI:
	fprintf( pf, "%s rng ansi\n", sz );
	break;
    case RNG_BBS:
	fprintf( pf, "%s rng bbs\n", sz ); /* FIXME save modulus */
	break;
    case RNG_BSD:
	fprintf( pf, "%s rng bsd\n", sz );
	break;
    case RNG_ISAAC:
	fprintf( pf, "%s rng isaac\n", sz );
	break;
    case RNG_MANUAL:
	fprintf( pf, "%s rng manual\n", sz );
	break;
    case RNG_MD5:
	fprintf( pf, "%s rng md5\n", sz );
	break;
    case RNG_MERSENNE:
	fprintf( pf, "%s rng mersenne\n", sz );
	break;
    case RNG_RANDOM_DOT_ORG:
        fprintf( pf, "%s rng random.org\n", sz );
	break;
    case RNG_FILE:
        fprintf( pf, "%s rng file \"%s\"\n", sz, GetDiceFileName( rngctx ) );
	break;
    default:
        break;
    }

}


static void
SaveMoveFilterSettings ( FILE *pf, 
                         const char *sz,
                         movefilter aamf [ MAX_FILTER_PLIES ][ MAX_FILTER_PLIES ] ) {

    int i, j;
    gchar buf[G_ASCII_DTOSTR_BUF_SIZE];

    for (i = 0; i < MAX_FILTER_PLIES; ++i) 
      for (j = 0; j <= i; ++j) {
	fprintf (pf, "%s %d  %d  %d %d %s\n",
                 sz, 
		 i+1, j, 
		 aamf[i][j].Accept,
		 aamf[i][j].Extra,
	         g_ascii_formatd(buf, G_ASCII_DTOSTR_BUF_SIZE,
			 "%0.3g", aamf[i][j].Threshold));
      }
}




static void 
SaveEvalSettings( FILE *pf, const char *sz, evalcontext *pec ) {

    gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
    gchar *szNoise = g_ascii_formatd(buf, G_ASCII_DTOSTR_BUF_SIZE, "%0.3f",  pec->rNoise);
    fprintf( pf, "%s plies %d\n"
	     "%s prune %s\n"
	     "%s cubeful %s\n"
	     "%s noise %s\n"
	     "%s deterministic %s\n",
	     sz, pec->nPlies, 
	     sz, pec->fUsePrune ? "on" : "off",
	     sz, pec->fCubeful ? "on" : "off",
	     sz, szNoise,
             sz, pec->fDeterministic ? "on" : "off" );
}


extern void
SaveRolloutSettings ( FILE *pf, const char *sz, rolloutcontext *prc ) {

  char *pch;
  int i; /* flags and stuff */
  gchar szTemp1[G_ASCII_DTOSTR_BUF_SIZE];
  gchar szTemp2[G_ASCII_DTOSTR_BUF_SIZE];

  g_ascii_formatd(szTemp1, G_ASCII_DTOSTR_BUF_SIZE, "%05.4f",  prc->rStdLimit);
  g_ascii_formatd(szTemp2, G_ASCII_DTOSTR_BUF_SIZE, "%05.4f",  prc->rJsdLimit);

  fprintf ( pf,
            "%s cubeful %s\n"
            "%s varredn %s\n"
            "%s quasirandom %s\n"
            "%s initial %s\n"
            "%s truncation enable %s\n"
            "%s truncation plies %d\n"
            "%s bearofftruncation exact %s\n"
            "%s bearofftruncation onesided %s\n"
            "%s later enable %s\n"
            "%s later plies %d\n"
            "%s trials %d\n"
            "%s cube-equal-chequer %s\n"
            "%s players-are-same %s\n"
            "%s truncate-equal-player0 %s\n"
            "%s limit enable %s\n"
            "%s limit minimumgames %d\n"
            "%s limit maxerror %s\n"
	    "%s jsd stop %s\n"
            "%s jsd minimumgames %d\n"
            "%s jsd limit %s\n",
            sz, prc->fCubeful ? "on" : "off",
            sz, prc->fVarRedn ? "on" : "off",
            sz, prc->fRotate ? "on" : "off",
            sz, prc->fInitial ? "on" : "off",
            sz, prc->fDoTruncate ? "on" : "off",
            sz, prc->nTruncate,
            sz, prc->fTruncBearoff2 ? "on" : "off",
            sz, prc->fTruncBearoffOS ? "on" : "off", 
            sz, prc->fLateEvals ? "on" : "off",
            sz, prc->nLate,
            sz, prc->nTrials,
            sz, fCubeEqualChequer ? "on" : "off",
            sz, fPlayersAreSame ? "on" : "off",
            sz, fTruncEqualPlayer0 ? "on" : "off",
	    sz, prc->fStopOnSTD ? "on" : "off",
	    sz, prc->nMinimumGames,
	    sz, szTemp1,
            sz, prc->fStopOnJsd ? "on" : "off",
            sz, prc->nMinimumJsdGames,
	    sz, szTemp2
            );
  
  SaveRNGSettings ( pf, sz, prc->rngRollout, rngctxRollout );

  /* chequer play and cube decision evalcontexts */

  pch = malloc ( strlen ( sz ) + 50 );

  strcpy ( pch, sz );

  for ( i = 0; i < 2; i++ ) {

    sprintf ( pch, "%s player %i chequerplay", sz, i );
    SaveEvalSettings ( pf, pch, &prc->aecChequer[ i ] );

    sprintf ( pch, "%s player %i cubedecision", sz, i );
    SaveEvalSettings ( pf, pch, &prc->aecCube[ i ] );

    sprintf ( pch, "%s player %i movefilter", sz, i );
    SaveMoveFilterSettings ( pf, pch, prc->aaamfChequer[ i ] );

  }

  for ( i = 0; i < 2; i++ ) {

    sprintf ( pch, "%s later player %i chequerplay", sz, i );
    SaveEvalSettings ( pf, pch, &prc->aecChequerLate[ i ] );

    sprintf ( pch, "%s later player %i cubedecision", sz, i );
    SaveEvalSettings ( pf, pch, &prc->aecCubeLate[ i ] );

    sprintf ( pch, "%s later player %i movefilter", sz, i );
    SaveMoveFilterSettings ( pf, pch, prc->aaamfLate[ i ] );

  }

  sprintf (pch, "%s truncation cubedecision", sz);
  SaveEvalSettings ( pf, pch, &prc->aecCubeTrunc );
  sprintf (pch, "%s truncation chequerplay", sz );
  SaveEvalSettings ( pf, pch, &prc->aecChequerTrunc);

  free ( pch );

}

static void SaveEvalSetupSettings(FILE * pf, const char *sz, evalsetup *pes)
{

	char szTemp[1024];

	switch (pes->et) {
	case EVAL_EVAL:
		fprintf(pf, "%s type evaluation\n", sz);
		break;
	case EVAL_ROLLOUT:
		fprintf(pf, "%s type rollout\n", sz);
		break;
	default:
		break;
	}

	strcpy(szTemp, sz);
	SaveEvalSettings(pf, strcat(szTemp, " evaluation"), &pes->ec);

	strcpy(szTemp, sz);
	SaveRolloutSettings(pf, strcat(szTemp, " rollout"), &pes->rc);

}

static void SaveAnalysisSettings(FILE * pf)
{
	gchar aszThr[7][G_ASCII_DTOSTR_BUF_SIZE];
	g_ascii_formatd(aszThr[0], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arSkillLevel[SKILL_BAD]);
	g_ascii_formatd(aszThr[1], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arSkillLevel[SKILL_DOUBTFUL]);
	g_ascii_formatd(aszThr[2], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arLuckLevel[LUCK_GOOD]);
	g_ascii_formatd(aszThr[3], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arLuckLevel[LUCK_BAD]);
	g_ascii_formatd(aszThr[4], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arSkillLevel[SKILL_VERYBAD]);
	g_ascii_formatd(aszThr[5], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arLuckLevel[LUCK_VERYGOOD]);
	g_ascii_formatd(aszThr[6], G_ASCII_DTOSTR_BUF_SIZE, "%0.3f", arLuckLevel[LUCK_VERYBAD]);

	SaveEvalSetupSettings(pf, "set analysis chequerplay", &esAnalysisChequer);
	SaveEvalSetupSettings(pf, "set analysis cubedecision", &esAnalysisCube);
	SaveMoveFilterSettings(pf, "set analysis movefilter", aamfAnalysis);
	SaveEvalSettings(pf, "set analysis luckanalysis", &ecLuck);
	fprintf(pf, "set analysis threshold bad %s\n", aszThr[0]);
	fprintf(pf, "set analysis threshold doubtful %s\n", aszThr[1]);
	fprintf(pf, "set analysis threshold lucky %s\n", aszThr[2]);
	fprintf(pf, "set analysis threshold unlucky %s\n", aszThr[3]);
	fprintf(pf, "set analysis threshold verybad %s\n", aszThr[4]);
	fprintf(pf, "set analysis threshold verylucky %s\n", aszThr[5]);
	fprintf(pf, "set analysis threshold veryunlucky %s\n", aszThr[6]);
	fprintf(pf, "set analysis cube %s\n", fAnalyseCube ? "on" : "off");
	fprintf(pf, "set analysis luck %s\n", fAnalyseDice ? "on" : "off");
	fprintf(pf, "set analysis moves %s\n", fAnalyseMove ? "on" : "off");
	fprintf(pf, "set analysis player 0 analyse %s\n", afAnalysePlayers[0] ? "yes" : "no");
	fprintf(pf, "set analysis player 1 analyse %s\n", afAnalysePlayers[1] ? "yes" : "no");
}

static void SaveImportExportSettings(FILE * pf)
{
	int i;
	if (default_import_folder && *default_import_folder)
		fprintf(pf, "set import folder \"%s\"\n", default_import_folder);
	if (default_export_folder && *default_export_folder)
		fprintf(pf, "set export folder \"%s\"\n", default_export_folder);
	if (default_sgf_folder && *default_sgf_folder)
		fprintf(pf, "set sgf folder \"%s\"\n", default_sgf_folder);

	fprintf(pf, "set export include annotations %s\n",
		exsExport.fIncludeAnnotation ? "yes" : "no");
	fprintf(pf, "set export include analysis %s\n",
	        exsExport.fIncludeAnalysis ? "yes" : "no");
	fprintf(pf, "set export include statistics %s\n",
		exsExport.fIncludeStatistics ? "yes" : "no");
	fprintf(pf, "set export include matchinfo %s\n",
		exsExport.fIncludeMatchInfo ? "yes" : "no");
	fprintf(pf, "set export show board %d\n", exsExport.fDisplayBoard);

	if (exsExport.fSide == 3)
		fprintf(pf, "set export show player both\n");
	else if (exsExport.fSide)
		fprintf(pf, "set export show player %d\n", exsExport.fSide - 1);

	fprintf(pf, "set export move number %d\n", exsExport.nMoves);
	fprintf(pf, "set export moves parameters evaluation %s\n",
		exsExport.afMovesParameters[0] ? "yes" : "no");
	fprintf(pf, "set export moves parameters rollout %s\n",
		exsExport.afMovesParameters[1] ? "yes" : "no");
	fprintf(pf, "set export moves probabilities %s\n",
		exsExport.fMovesDetailProb ? "yes" : "no");

	for (i = 0; i < N_SKILLS; i++) {
		if (i == SKILL_NONE)
			fprintf(pf, "set export moves display unmarked %s\n",
				exsExport.afMovesDisplay[i] ? "yes" : "no");
		else
			fprintf(pf, "set export moves display %s %s\n",
				aszSkillTypeCommand[i], exsExport.afMovesDisplay[i] ? "yes" : "no");
	}

	fprintf(pf, "set export cube parameters evaluation %s\n",
		exsExport.afCubeParameters[0] ? "yes" : "no");
	fprintf(pf, "set export cube parameters rollout %s\n",
		exsExport.afCubeParameters[1] ? "yes" : "no");
	fprintf(pf, "set export cube probabilities %s\n", exsExport.fCubeDetailProb ? "yes" : "no");

	for (i = 0; i < N_SKILLS; i++) {
		if (i == SKILL_NONE)
			fprintf(pf, "set export cube display unmarked %s\n",
				exsExport.afCubeDisplay[i] ? "yes" : "no");
		else
			fprintf(pf, "set export cube display %s %s\n",
				aszSkillTypeCommand[i], exsExport.afCubeDisplay[i] ? "yes" : "no");
	}

	fprintf(pf, "set export cube display actual %s\n",
		exsExport.afCubeDisplay[EXPORT_CUBE_ACTUAL] ? "yes" : "no");
	fprintf(pf, "set export cube display missed %s\n",
		exsExport.afCubeDisplay[EXPORT_CUBE_MISSED] ? "yes" : "no");
	fprintf(pf, "set export cube display close %s\n",
		exsExport.afCubeDisplay[EXPORT_CUBE_CLOSE] ? "yes" : "no");
	fprintf(pf, "set export html pictureurl \"%s\"\n", exsExport.szHTMLPictureURL);
	fprintf(pf, "set export html type \"%s\"\n", aszHTMLExportType[exsExport.het]);
	fprintf(pf, "set export html css %s\n", aszHTMLExportCSSCommand[exsExport.hecss]);
	fprintf(pf, "set export png size %d\n", exsExport.nPNGSize);
	fprintf(pf, "set export html size %d\n", exsExport.nHtmlSize);

}

#if USE_GTK
static void SaveGUISettings(FILE * pf)
{
	const char *aszAnimation[] = { "none", "blink", "slide" };
	const char *aszShowPips[N_GUI_SHOW_PIPS] = { "none", "pips", "epc", "wastage" };
	int dummy;

	SaveWindowSettings(pf);
	WriteWarnings(pf);
	if (fX)
		GTKSaveSettings();

	fprintf(pf, "set fullscreen %s\n", fFullScreen ? "on" : "off");
	if (fFullScreen)
	{
		GetFullscreenWindowSettings(&dummy, &fShowIDs, &dummy);
		fprintf(pf, "set gui showids %s\n", fShowIDs ? "on" : "off");
		fShowIDs = FALSE;
	}
	else
		fprintf(pf, "set gui showids %s\n", fShowIDs ? "on" : "off");

	fprintf(pf, "set gui animation %s\n", aszAnimation[animGUI]);
	fprintf(pf, "set gui animation speed %u\n", nGUIAnimSpeed);
	fprintf(pf, "set gui beep %s\n", fGUIBeep ? "on" : "off");
	fprintf(pf, "set gui dicearea %s\n", GetMainAppearance()->fDiceArea ? "on" : "off");
	fprintf(pf, "set gui highdiefirst %s\n", fGUIHighDieFirst ? "on" : "off");
	fprintf(pf, "set gui illegal %s\n", fGUIIllegal ? "on" : "off");
	fprintf(pf, "set gui showpips %s\n", aszShowPips[gui_show_pips]);
	fprintf(pf, "set gui dragtargethelp %s\n", fGUIDragTargetHelp ? "on" : "off");
	fprintf(pf, "set gui usestatspanel %s\n", fGUIUseStatsPanel ? "on" : "off");
	fprintf(pf, "set gui movelistdetail %s\n", showMoveListDetail ? "on" : "off");
	fprintf(pf, "set gui grayedit %s\n", fGUIGrayEdit ? "on" : "off");
	fprintf(pf, "set gui windowpositions %s\n", fGUISetWindowPos ? "on" : "off");

	fprintf(pf, "set styledgamelist %s\n", fStyledGamelist ? "on" : "off");
	fprintf(pf, "set delay %u\n", nDelay);

	fprintf(pf, "set toolbar %d\n", nToolbarStyle);
	if (!fToolbarShowing)
		fputs("set toolbar off\n", pf);

#if USE_BOARD3D
	if (fSync != -1)
		fprintf(pf, "set vsync3d %s\n", fSync ? "yes" : "no");
#endif
}
#endif

static void SaveSoundSettings(FILE * pf)
{
	int i;
	fprintf(pf, "set sound enable %s\n", fSound ? "yes" : "no");
	fprintf(pf, "set sound system command %s\n", sound_get_command());

	for (i = 0; i < NUM_SOUNDS; ++i) {
		char *file = GetSoundFile(i);
		fprintf(pf, "set sound sound %s \"%s\"\n", sound_command[i], file);
		g_free(file);
	}
}

static void SavePlayerSettings(FILE * pf)
{
	int i;
	char szTemp[4096];
	fprintf(pf, "set defaultnames \"%s\" \"%s\"\n", default_names[0], default_names[1]);
	for (i = 0; i < 2; i++) {
		fprintf(pf, "set player %d name %s\n", i, ap[i].szName);

		switch (ap[i].pt) {
		case PLAYER_GNU:
			fprintf(pf, "set player %d gnubg\n", i);
			sprintf(szTemp, "set player %d chequerplay", i);
			SaveEvalSetupSettings(pf, szTemp, &ap[i].esChequer);
			sprintf(szTemp, "set player %d cubedecision", i);
			SaveEvalSetupSettings(pf, szTemp, &ap[i].esCube);
			sprintf(szTemp, "set player %d movefilter", i);
			SaveMoveFilterSettings(pf, szTemp, ap[i].aamf);
			break;

		case PLAYER_HUMAN:
			fprintf(pf, "set player %d human\n", i);
			break;

		case PLAYER_EXTERNAL:
			/* don't save external players */
			break;
		}
	}
	fprintf(pf, "set cheat enable %s\n", fCheat ? "on" : "off");
	for (i = 0; i < 2; ++i)
		fprintf(pf, "set cheat player %d roll %d\n", i, afCheatRoll[i] + 1);

}

static void SavePlayingSettings(FILE * pf)
{
	fprintf(pf, "set automatic bearoff %s\n", fAutoBearoff ? "on" : "off");
	fprintf(pf, "set automatic crawford %s\n", fAutoCrawford ? "on" : "off");
	fprintf(pf, "set automatic game %s\n", fAutoGame ? "on" : "off");
	fprintf(pf, "set automatic move %s\n", fAutoMove ? "on" : "off");
	fprintf(pf, "set automatic roll %s\n", fAutoRoll ? "on" : "off");
}

static void SaveRuleSettings(FILE * pf)
{
	fprintf(pf, "set automatic doubles %u\n", cAutoDoubles);
	fprintf(pf, "set beavers %u\n", nBeavers);
	fprintf(pf, "set jacoby %s\n", fJacoby ? "on" : "off");
	fprintf(pf, "set matchlength %u\n", nDefaultLength);
	fprintf(pf, "set variation %s\n", aszVariationCommands[bgvDefault]);
}

static void SaveEvaluationSettings(FILE * pf)
{
	fprintf(pf, "set eval sameasanalysis %s\n", fEvalSameAsAnalysis ? "on" : "off");
	SaveEvalSetupSettings(pf, "set evaluation chequerplay", &esEvalChequer);
	SaveEvalSetupSettings(pf, "set evaluation cubedecision", &esEvalCube);
	SaveMoveFilterSettings(pf, "set evaluation movefilter", aamfEval);
	fprintf(pf, "set cache %d\n", GetEvalCacheEntries());
	fprintf(pf, "set matchequitytable \"%s\"\n", miCurrent.szFileName);
	fprintf(pf, "set invert matchequitytable %s\n", fInvertMET ? "on" : "off");
#if USE_MULTITHREAD
	fprintf(pf, "set threads %d\n", MT_GetNumThreads());
#endif
}

static void SaveAutoSaveSettings(FILE * pf)
{
	fprintf(pf, "set autosave time %d\n", nAutoSaveTime);
	fprintf(pf, "set autosave rollout %s\n", fAutoSaveRollout ? "on" : "off");
	fprintf(pf, "set autosave analysis %s\n", fAutoSaveAnalysis ? "on" : "off");
	fprintf(pf, "set autosave confirm %s\n", fAutoSaveConfirmDelete ? "on" : "off");
}

static void SaveMiscSettings(FILE * pf)
{
	gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
	fprintf(pf, "set tutor mode %s\n", fTutor ? "on" : "off");
	fprintf(pf, "set tutor cube %s\n", fTutorCube ? "on" : "off");
	fprintf(pf, "set tutor chequer %s\n", fTutorChequer ? "on" : "off");
	fprintf(pf, "set tutor skill ");
	if (TutorSkill == SKILL_VERYBAD)
		fprintf(pf, "very bad\n");
	else if (TutorSkill == SKILL_BAD)
		fprintf(pf, "bad\n");
	else
		fprintf(pf, "doubtful\n");

	fprintf(pf, "set clockwise %s\n", fClockwise ? "on" : "off");
	fprintf(pf, "set confirm new %s\n", fConfirmNew ? "on" : "off");
	fprintf(pf, "set confirm save %s\n", fConfirmSave ? "on" : "off");
	fprintf(pf, "set cube use %s\n", fCubeUse ? "on" : "off");
	fprintf(pf, "set display %s\n", fDisplay ? "on" : "off");

	fprintf(pf, "set confirm default ");
	if (nConfirmDefault == 1)
		fprintf(pf, "yes\n");
	else if (nConfirmDefault == 0)
		fprintf(pf, "no\n");
	else
		fprintf(pf, "ask\n");

	fprintf(pf, "set gotofirstgame %s\n", fGotoFirstGame ? "on" : "off");
	fprintf(pf, "set output matchpc %s\n", fOutputMatchPC ? "on" : "off");
	fprintf(pf, "set output mwc %s\n", fOutputMWC ? "on" : "off");
	fprintf(pf, "set output rawboard %s\n", fOutputRawboard ? "on" : "off");
	fprintf(pf, "set output winpc %s\n", fOutputWinPC ? "on" : "off");
	fprintf(pf, "set output digits %u\n", fOutputDigits);
	fprintf(pf, "set output errorratefactor %s\n",
		g_ascii_formatd(buf, G_ASCII_DTOSTR_BUF_SIZE, "%f", rErrorRateFactor));
	fprintf(pf, "set prompt %s\n", szPrompt);
	fprintf(pf, "set browser \"%s\"\n", get_web_browser());
	fprintf(pf, "set priority nice %d\n", nThreadPriority);
	fprintf(pf, "set ratingoffset %s\n",
		g_ascii_formatd(buf, G_ASCII_DTOSTR_BUF_SIZE, "%f", rRatingOffset));
}

extern void CommandSaveSettings(char *szParam)
{
	FILE *pf;
	char *szFile;

	szParam = NextToken(&szParam);

	if (!szParam || !*szParam) {
		/* no filename parameter given -- save to default location */
		szFile = g_build_filename(szHomeDirectory, "gnubgautorc", NULL);
	} else
		szFile = g_strdup(szParam);

	if (!strcmp(szFile, "-"))
		pf = stdout;
	else
		pf = g_fopen(szFile, "w");

	if (!pf) {
		outputerr(szFile);
		g_free(szFile);
		return;
	}
#if USE_GTK
	/* the following may set errno because of a gtk bug so we do it
	 * first and reset errno afterwards */
	if (fX)
		RefreshGeometries();
#endif

	errno = 0;

	fprintf(pf,
		"#\n"
		"# GNU Backgammon command file\n"
		"#   generated by %s\n"
		"#\n"
		"# WARNING: The file 'gnubgautorc' is automatically "
		"generated the first time\n"
		"# settings are changed and will be overwritten with "
		"every subsequent change.\n"
		"# If you want to add startup commands manually, you should "
		"use 'gnubgrc'\n"
		"# instead.\n" "\n", VERSION_STRING);

	/* language preference */
	fprintf(pf, "set lang %s\n", szLang);

#if USE_GTK
	SaveGUISettings(pf);
#endif
	SaveAnalysisSettings(pf);
	SaveRenderingSettings(pf);
	SavePlayingSettings(pf);
	SaveRuleSettings(pf);
	SaveEvaluationSettings(pf);
	SavePlayerSettings(pf);
	SaveRNGSettings(pf, "set", rngCurrent, rngctxCurrent);
	SaveRolloutSettings(pf, "set rollout", &rcRollout);
	SaveImportExportSettings(pf);
	SaveSoundSettings(pf);
	RelationalSaveSettings(pf);

	SaveMiscSettings(pf);
	SaveAutoSaveSettings(pf);
	if (pf != stdout)
		fclose(pf);

	if (errno)
		outputerr(szFile);
	else {
		outputf(_("Settings saved to %s."),
			(!strcmp(szFile, "-")) ? _("standard output stream") : szFile);
		output("\n");
	}
	g_free(szFile);

}

#if HAVE_LIBREADLINE
static command *pcCompleteContext;

static char *NullGenerator( const char *UNUSED(sz), int UNUSED(nState) )
{
  return NULL;
}

static char *GenerateKeywords( const char *sz, int nState )
{
    static int cch;
    static command *pc;
    char *szDup;

    if( !nState ) {
      cch = strlen( sz );
      pc = pcCompleteContext;
    }

    while( pc && pc->sz ) {
      if( !StrNCaseCmp( sz, pc->sz, cch ) && pc->szHelp ) {
        if( !( szDup = malloc( strlen( pc->sz ) + 1 ) ) )
          return NULL;

        strcpy( szDup, pc->sz );
        
        pc++;
	    
        return szDup;
      }
	
      pc++;
    }
    
    return NULL;
}

static char *ERCompletion( const char *sz, int nState )
{
    static int i, cch;
    const char *pch;
    char *szDup;

    if( !nState ) {
	cch = strlen( sz );
	i = 0;
    }

    while( i < 2 ) {
	pch = i++ ? "rollout" : "evaluation";
	
	if( !StrNCaseCmp( sz, pch, cch ) ) {
	    if( !( szDup = malloc( strlen( pch ) + 1 ) ) )
		return NULL;
	    
	    return strcpy( szDup, pch );
	}
	++i;
    }

    return NULL;
}

static char *OnOffCompletion( const char *sz, int nState )
{
    static unsigned int i;
    static int cch;
    static const char *asz[] = { "false", "no", "off", "on", "true", "yes" };
    const char *pch;
    char *szDup;
    
    if( !nState ) {
	cch = strlen( sz );
	i = 0;
    }

    while( i < sizeof(asz)/sizeof(asz[0]) ) {
	pch = asz[ i++ ];

	if( !StrNCaseCmp( sz, pch, cch ) ) {
	    if( !( szDup = malloc( strlen( pch ) + 1 ) ) )
		return NULL;

	    return strcpy( szDup, pch );
	}
    }
    
    return NULL;
}

static char *PlayerCompletionGen( const char *sz, int nState, int fBoth )
{
    static int i, cch;
    const char *pch;
    char *szDup;
    
    if( !nState ) {
	cch = strlen( sz );
	i = 0;
    }

    while( i < ( fBoth ? 5 : 4 ) ) {
	switch( i ) {
	case 0:
	    pch = "0";
	    break;
	case 1:
	    pch = "1";
	    break;
	case 2:
	    pch = ap[ 0 ].szName;
	    break;
	case 3:
	    pch = ap[ 1 ].szName;
	    break;
	case 4:
	    pch = "both";
	    break;
	default:
	    abort();
	}

	i++;

	if( !StrNCaseCmp( sz, pch, cch ) ) {
	    if( !( szDup = malloc( strlen( pch ) + 1 ) ) )
		return NULL;

	    return strcpy( szDup, pch );
	}
    }
    
    return NULL;
}

static char *PlayerCompletion( const char *sz, int nState )
{
    return PlayerCompletionGen( sz, nState, FALSE );
}

static char *PlayerCompletionBoth( const char *sz, int nState )
{
    return PlayerCompletionGen( sz, nState, TRUE );
}


static command *FindContext( command *pc, char *szOrig, int ich )
{
    char *sz = (char*) g_alloca(strlen( szOrig )  * sizeof(char) + 1);
    char *pch, *pchCurrent;
    command *pcResume = NULL;
    
    pch = strcpy( sz, szOrig );
    pch[ ich ] = 0;
    
    do {
	if( !( pchCurrent = NextToken( &pch ) ) )
	    /* no command */
	    return pc;

	if( !pch )
	    /* incomplete command */
	    return pc;

	if( pcResume ) {
	    pc = pcResume;
	    pcResume = NULL;
	    continue;
	}
	
        while( pc && pc->sz ) {
            if( !StrNCaseCmp( pchCurrent, pc->sz, strlen( pchCurrent ) ) ) {
		pc = pc->pc;

		if( pc == acSetPlayer || pc == acSetRolloutPlayer || 
                    pc == acSetRolloutLatePlayer ||
                    pc == acSetAnalysisPlayer || pc == acSetCheatPlayer ) {
		    pcResume = pc;
		    pc = &cPlayerBoth;
		}
		
                break;
            }

            pc++;
        }
    } while( pcResume || ( pc && pc->sz ) );

    if( pc && pc->pc ) {
	/* dummy command for parameter completion */
	if( !NextToken( &pch ) )
	    return pc;
    }

    /* the command is already complete */
    return NULL;
}

static char **CompleteKeyword( const char *szText, int iStart, int UNUSED(iEnd))
{

    if( fReadingOther )
	return rl_completion_matches( szText, NullGenerator );
    
    pcCompleteContext = FindContext( acTop, rl_line_buffer, iStart );

    if( !pcCompleteContext )
	return NULL;

    if( pcCompleteContext == &cER )
	return rl_completion_matches( szText, ERCompletion );
    else if( pcCompleteContext == &cFilename ) {
	rl_filename_completion_desired = TRUE;
	return rl_completion_matches( szText,
				      rl_filename_completion_function );
    } else if( pcCompleteContext == &cOnOff )
	return rl_completion_matches( szText, OnOffCompletion );
    else if( pcCompleteContext == &cPlayer )
	return rl_completion_matches( szText, PlayerCompletion );
    else if( pcCompleteContext == &cPlayerBoth )
	return rl_completion_matches( szText, PlayerCompletionBoth );
    else
	return rl_completion_matches( szText, GenerateKeywords );
}
#endif

extern void Prompt( void )
{

#if HAVE_LIBREADLINE
    if( !fInteractive || !isatty( STDIN_FILENO ) )
	return;
#endif

    g_print("%s", FormatPrompt() );
    fflush( stdout );    
}

#if USE_GTK
#if HAVE_LIBREADLINE
extern void ProcessInput(char *sz)
{

    char *pchExpanded;

    rl_callback_handler_remove();
    fReadingCommand = FALSE;
    
    if( !sz ) {
	outputc( '\n' );
	PromptForExit();
    }
    else
    {

	    fInterrupt = FALSE;

	    /* expand history */

	    history_expand( sz, &pchExpanded );

	    if( *pchExpanded )
		    add_history( pchExpanded );

	    if( fX )
		    GTKDisallowStdin();

	    HandleCommand( pchExpanded, acTop );
	    free( pchExpanded );
    }
    
    if( fX )
	GTKAllowStdin();

    ResetInterrupt();


    /* Recalculate prompt -- if we call nothing, then readline will
       redisplay the old prompt.  This isn't what we want: we either
       want no prompt at all, yet (if NextTurn is going to be called),
       or if we do want to prompt immediately, we recalculate it in
       case the information in the old one is out of date. */
    if( nNextTurn )
	fNeedPrompt = TRUE;
    else {
	    char *sz = locale_from_utf8(FormatPrompt());
	    rl_callback_handler_install( sz, ProcessInput );
	    g_free(sz);
	    fReadingCommand = TRUE;
    }
}

#endif

/* Handle a command as if it had been typed by the user. */
extern void UserCommand(const char *szCommand)
{
	char *sz;

	g_return_if_fail(szCommand);
	g_return_if_fail(*szCommand);
	/* Unfortunately we need to copy the command, because it might be in
	   read-only storage and HandleCommand might want to modify it. */
	sz = g_strdup(szCommand);
#ifndef WIN32
	if (!fTTY || !fInteractive)
#endif
       	{
		fInterrupt = FALSE;
		HandleCommand(sz, acTop);
		g_free(sz);
		ResetInterrupt();
		return;
	}

	/* Note that the command is always echoed to stdout; the output*()
	   functions are bypassed. */
#if HAVE_LIBREADLINE
	rl_end = 0; /* crashes without this line */
	rl_redisplay();
	g_print("%s\n", sz);
	ProcessInput(sz);
	g_free(sz);
	return;
#elif !defined(WIN32)
	g_print("\n");
	Prompt();
	g_print("%s\n", sz);
	fInterrupt = FALSE;
	HandleCommand(sz, acTop);
	g_free(sz);
	ResetInterrupt();
	if (nNextTurn)
		Prompt();
	else
		fNeedPrompt = TRUE;
	return;
#endif
}

extern gint NextTurnNotify( gpointer UNUSED(p) )
{
    NextTurn( TRUE );

    ResetInterrupt();

    if( fNeedPrompt )
    {
#if HAVE_LIBREADLINE
	if( fInteractive ) {
	    char *sz = locale_from_utf8(FormatPrompt());
	    rl_callback_handler_install( sz, ProcessInput );
	    g_free(sz);
	    fReadingCommand = TRUE;
	} else
#endif
	    Prompt();
	
	fNeedPrompt = FALSE;
    }
    
    return FALSE; /* remove idle handler, if GTK */
}
#endif

/* Read a line from stdin, and handle X and readline input if
 * appropriate.  This function blocks until a line is ready, and does
 * not call HandleEvents(), and because fBusy will be set some X
 * commands will not work.  Therefore, it should not be used for
 * reading top level commands.  The line it returns has been allocated
 * with malloc (as with readline()). */
extern char *GetInput( char *szPrompt )
{

    char *sz;
    char *pch;
    char *pchConverted;
#if USE_GTK
    g_assert( fTTY && !fX );
#endif

#if HAVE_LIBREADLINE
    if( fInteractive ) {
	char *prompt;
	/* Using readline, but not X. */
	if( fInterrupt )
	    return NULL;
	
	fReadingOther = TRUE;
	
	prompt =  locale_from_utf8(szPrompt);
	while( !( sz = readline( prompt ) ) ) {
	    outputc( '\n' );
	    PromptForExit();
	}
	g_free(prompt);
	
	fReadingOther = FALSE;
	
	if( fInterrupt )
	    return NULL;
	
	pchConverted = locale_to_utf8(sz);
	free( sz );
	return pchConverted;
    }
#endif
    /* Not using readline or X. */
    if( fInterrupt )
	return NULL;

    g_print("%s", szPrompt );
    fflush( stdout );

    sz = malloc( 256 ); /* FIXME it would be nice to handle longer strings */

    clearerr( stdin );
    pch = fgets( sz, 256, stdin );

    if( fInterrupt ) {
	free( sz );
	return NULL;
    }
    
    if( !pch ) {
	if( !isatty( STDIN_FILENO ) )
	    exit( EXIT_SUCCESS );
	
	outputc( '\n' );
	PromptForExit();
    }
    
    if( ( pch = strchr( sz, '\n' ) ) ) *pch = 0;
    if( ( pch = strchr( sz, '\r' ) ) ) *pch = 0;
    
    pchConverted = locale_to_utf8(sz);
    free( sz );
    return pchConverted;
}

/* Ask a yes/no question.  Interrupting the question is considered a "no"
   answer. */
extern int GetInputYN( char *szPrompt )
{

    char *pch;

    if (nConfirmDefault != -1)
    {
	    outputf("%s %s\n", szPrompt, nConfirmDefault ? _("yes") : _("no"));
	    return nConfirmDefault;
    }

    if (!fInteractive)
	    return TRUE;

#if USE_GTK
    if( fX )
	return GTKGetInputYN( szPrompt );
#endif
    
    if( fInterrupt )
	return FALSE;

    while( (pch = GetInput( szPrompt )) != 0 )
	{
	if( pch )
	    switch( *pch ) {
	    case 'y':
	    case 'Y':
		g_free( pch );
		return TRUE;
	    case 'n':
	    case 'N':
		g_free( pch );
		return FALSE;
	    default:
		g_free( pch );
	    }

	outputl( _("Please answer `y' or `n'.") );
    }
    return FALSE;
}

/* Like strncpy, except it does the right thing */
extern char *strcpyn( char *szDest, const char *szSrc, int cch )
{

    char *pchDest = szDest;
    const char *pchSrc = szSrc;

    if( cch-- < 1 )
	return szDest;
    
    while( cch-- )
	if( !( *pchDest++ = *pchSrc++ ) )
	    return szDest;

    *pchDest = 0;

    return szDest;
}

/* Write a string to stdout/status bar/popup window */
extern void output( const char *sz )
{

    if( cOutputDisabled || !foutput_on)
	return;
    
#if USE_GTK
    if( fX ) {
	GTKOutput( sz );
	return;
    }
#endif
    fprintf(stdout, "%s", sz);
    if( !isatty( STDOUT_FILENO ) ) 
       fflush( stdout );

}

/* Write a string to stdout/status bar/popup window, and append \n */
extern void outputl( const char *sz )
{

    
    if( cOutputDisabled || !foutput_on)
	return;
    
#if USE_GTK
    if( fX ) {
	    char *szOut = g_strdup_printf("%s\n", sz);
	GTKOutput( szOut  );
	g_free(szOut);
	return;
    }
#endif
    g_print("%s\n", sz);
    if( !isatty( STDOUT_FILENO ) ) 
       fflush( stdout );
}
    
/* Write a character to stdout/status bar/popup window */
extern void outputc( const char ch )
{

    char sz[2];
	sz[0] = ch;
	sz[1] = '\0';
    
    output( sz );
}
    
/* Write a string to stdout/status bar/popup window, printf style */
extern void outputf( const char *sz, ... )
{

    va_list val;

    va_start( val, sz );
    outputv( sz, val );
    va_end( val );
}

/* Write a string to stdout/status bar/popup window, vprintf style */
extern void outputv( const char *sz, va_list val )
{

    char *szFormatted;
    if( cOutputDisabled || !foutput_on)
	return;
    szFormatted = g_strdup_vprintf( sz, val );
    output( szFormatted );
    g_free( szFormatted );
}

/* Write an error message, perror() style */
extern void outputerr( const char *sz )
{

    /* FIXME we probably shouldn't convert the charset of strerror() - yuck! */
    
    outputerrf( "%s: %s", sz, strerror( errno ) );
}

/* Write an error message, fprintf() style */
extern void outputerrf( const char *sz, ... )
{

    va_list val;

    va_start( val, sz );
    outputerrv( sz, val );
    va_end( val );
}

/* Write an error message, vfprintf() style */
extern void outputerrv(const char *sz, va_list val)
{

	char *szFormatted;
	szFormatted = g_strdup_vprintf(sz, val);

#if USE_GTK
	if (fX)
		GTKOutputErr(szFormatted);
#endif
	fprintf(stderr, "%s", szFormatted);
	if (!isatty(STDOUT_FILENO))
		fflush(stdout);
	putc('\n', stderr);
	g_free(szFormatted);
}

/* Signifies that all output for the current command is complete */
extern void outputx( void )
{
    
    if( cOutputDisabled || cOutputPostponed || !foutput_on)
	return;

#if USE_GTK
    if( fX )
	GTKOutputX();
#endif
}

/* Signifies that subsequent output is for a new command */
extern void outputnew( void )
{
    
    if( cOutputDisabled || !foutput_on)
	return;
    
#if USE_GTK
    if( fX )
	GTKOutputNew();
#endif
}

/* Disable output */
extern void outputoff( void )
{

    cOutputDisabled++;
}

/* Enable output */
extern void outputon( void )
{

    g_assert( cOutputDisabled );

    cOutputDisabled--;
}

extern void CommandSetOutputOutput(char *sz)
{
	SetToggle("output", &foutput_on, sz, _("output will be shown"), _("output will not be shown"));
		return;
}

/* Temporarily disable outputx() calls */
extern void outputpostpone( void )
{

    cOutputPostponed++;
}

/* Re-enable outputx() calls */
extern void outputresume( void )
{

    g_assert( cOutputPostponed );

    if( !--cOutputPostponed )
    {
	outputx();
    }
}

static GTimeVal tvProgress;

static int ProgressThrottle( void ) {
    GTimeVal tv, tvDiff;
    g_get_current_time(&tv);
    
    tvDiff.tv_sec = tv.tv_sec - tvProgress.tv_sec;
    if( ( tvDiff.tv_usec = tv.tv_usec + 1000000 - tvProgress.tv_usec ) >=
	1000000 )
	tvDiff.tv_usec -= 1000000;
    else
	tvDiff.tv_sec--;

    if( tvDiff.tv_sec || tvDiff.tv_usec >= 100000 ) {
	/* sufficient time elapsed; record current time */
	tvProgress.tv_sec = tv.tv_sec;
	tvProgress.tv_usec = tv.tv_usec;
	return 0;
    }

    /* insufficient time elapsed */
    return -1;
}

extern void ProgressStart( const char *sz )
{
    if( !fShowProgress )
	return;

    fInProgress = TRUE;

#if USE_GTK
    if( fX ) {
	GTKProgressStart( sz );
	return;
    }
#endif

    if( sz ) {
	fputs( sz, stdout );
	fflush( stdout );
    }
}


extern void
ProgressStartValue ( char *sz, int iMax ) {

  if ( !fShowProgress )
    return;

  iProgressMax = iMax;
  iProgressValue = 0;
  pcProgress = sz;

  fInProgress = TRUE;

#if USE_GTK
  if( fX ) {
    GTKProgressStartValue( sz, iMax );
    return;
  }
#endif

  if( sz ) {
    fputs( sz, stdout );
    fflush( stdout );
  }

}


extern void
ProgressValue ( int iValue ) {

  if ( !fShowProgress || iProgressValue == iValue )
    return;

  iProgressValue = iValue;

  if( ProgressThrottle() )
      return;
#if USE_GTK
  if( fX ) {
    GTKProgressValue( iValue, iProgressMax );
    return;
  }
#endif

  outputf ( "\r%s %d/%d\r", pcProgress, iProgressValue, iProgressMax );
  fflush ( stdout );

}


extern void
ProgressValueAdd ( int iValue ) {

  ProgressValue ( iProgressValue + iValue );

}


static gboolean Progress( gpointer UNUSED(unused) )
{
    static int i = 0;
    static char ach[ 5 ] = "/-\\|";
   
    if( !fShowProgress )
	return FALSE;

    if( ProgressThrottle() )
	return TRUE;
#if USE_GTK
    if( fX ) {
	GTKProgress();
	return TRUE;
    }
#endif

    putchar( ach[ i++ ] );
    i &= 0x03;
    putchar( '\b' );
    fflush( stdout );
    return TRUE;
}

#if !USE_MULTITHREAD
extern void CallbackProgress( void )
{
#if USE_GTK
	if( fX )
	{
		GTKDisallowStdin();
		if (fInProgress)
			GTKSuspendInput();

		while( gtk_events_pending() )
			gtk_main_iteration();

		if (fInProgress)
			GTKResumeInput();
		GTKAllowStdin();
	}
#endif

    if( fInProgress && !iProgressMax )
	Progress(NULL);
}
#endif

extern void ProgressEnd( void )
{

    int i;
    
    if( !fShowProgress )
	return;

    fInProgress = FALSE;
    iProgressMax = 0;
    iProgressValue = 0;
    pcProgress = NULL;
    
#if USE_GTK
    if( fX ) {
	GTKProgressEnd();
	return;
    }
#endif

    putchar( '\r' );
    for( i = 0; i < 79; i++ )
	putchar( ' ' );
    putchar( '\r' );
    fflush( stdout );

}

static void
move_rc_files (void)
{
  /* Move files to the new location. Remove this part when all users have had
   * their files moved.*/
  char *olddir, *oldfile, *newfile;
#ifdef WIN32
  olddir = g_strdup (getDataDir());
#else
  olddir = g_build_filename (szHomeDirectory, "..", NULL);
#endif

  newfile = g_build_filename (szHomeDirectory, "gnubgautorc", NULL);
  oldfile = g_build_filename (olddir, ".gnubgautorc", NULL);
  if (g_file_test(oldfile, G_FILE_TEST_IS_REGULAR) && !g_file_test(newfile, G_FILE_TEST_EXISTS))
	  g_rename (oldfile, newfile);
  g_free (oldfile);
  g_free (newfile);

  newfile = g_build_filename (szHomeDirectory, "gnubgrc", NULL);
  oldfile = g_build_filename (olddir, ".gnubgrc", NULL);
  if (g_file_test(oldfile, G_FILE_TEST_IS_REGULAR) && !g_file_test(newfile, G_FILE_TEST_EXISTS))
	  g_rename (oldfile, newfile);
  g_free (oldfile);
  g_free (newfile);

  g_free(olddir);

}

/*
 * Create $HOME/.gnubg if not existing
 *
 */

static int
CreateGnubgDirectory (void)
{
  char *newfile;

  if (!g_file_test (szHomeDirectory, G_FILE_TEST_IS_DIR))
    {
      if (g_mkdir (szHomeDirectory, 0700) < 0)
	{
	  outputerr (szHomeDirectory);
	  return -1;
	}
    }
  newfile = g_build_filename (szHomeDirectory, "gnubgautorc", NULL);
  if (!g_file_test (newfile, G_FILE_TEST_IS_REGULAR))
    move_rc_files ();
  g_free (newfile);
  return 0;
}


extern void HandleInterrupt( int UNUSED(idSignal) )
{
    /* NB: It is safe to write to fInterrupt even if it cannot be read
       atomically, because it is only used to hold a binary value. */
    fInterrupt = TRUE;
}

static void BearoffProgress( unsigned int i )
{
#if USE_GTK
    if( fX ) {
	GTKBearoffProgress( i );
	return;
    }
#endif
    putchar( "\\|/-"[ ( i / 1000 ) % 4 ] );
    putchar( '\r' );
    fflush( stdout );
}

static void VersionMessage(void)
{
	g_print("%s\n%s\n",_(VERSION_STRING),  _(aszCOPYRIGHT));
	g_print("%s", _(intro_string));
}

#if HAVE_LIBREADLINE
static char *get_readline(void)
{
	char *pchExpanded;
	char *szInput;
	char *sz;
	char *prompt = locale_from_utf8(FormatPrompt());
	while (!(szInput = readline(prompt))) {
		outputc('\n');
		PromptForExit();
	}
	g_free(prompt);
	sz = locale_to_utf8(szInput);
	free(szInput);
	fInterrupt = FALSE;
	history_expand(sz, &pchExpanded);
	g_free(sz);
	if (*pchExpanded)
		add_history(pchExpanded);
	return pchExpanded;
}
#endif

static char *get_stdin_line(void)
{
	char sz[2048], *pch;

	sz[0] = 0;
	Prompt();

	clearerr(stdin);

	/* FIXME shouldn't restart sys calls on signals during this
	   fgets */
	if (fgets(sz, sizeof(sz), stdin) == NULL)
	{
		if (ferror(stdin))
		{
			outputerr("stdin");
			exit(EXIT_FAILURE);
		}
		else
		{
			Shutdown();
			exit(EXIT_SUCCESS);
		}
	}

	if ((pch = strchr(sz, '\n')))
		*pch = 0;


	if (feof(stdin)) {
		if (!isatty(STDIN_FILENO)) {
			Shutdown();
			exit(EXIT_SUCCESS);
		}

		outputc('\n');

		if (!sz[0])
			PromptForExit();
		return NULL;
	}

	fInterrupt = FALSE;
    return strdup(sz);
}



static void run_cl(void)
{
	char *line;
	for (;;) {
#if HAVE_LIBREADLINE
		if (fInteractive) {
			line = get_readline();
			HandleCommand(line, acTop);
			free(line);
		} else
#endif
		{
			line = get_stdin_line();
			HandleCommand(line, acTop);
			free(line);
		}
		while (fNextTurn)
			NextTurn(TRUE);
		ResetInterrupt();
	}
}

static void init_language(char **lang)
{
	char *szFile, szTemp[4096];
	char *pch;
	FILE *pf;

	outputoff();
	szFile = g_build_filename(szHomeDirectory, "gnubgautorc", NULL);

	if (!*lang && (pf = g_fopen(szFile, "r"))) {

		while (fgets(szTemp, sizeof(szTemp), pf) != NULL)
		{
			if ((pch = strchr(szTemp, '\n'))) *pch = 0;
			if ((pch = strchr(szTemp, '\r'))) *pch = 0;

			if (!strncmp("set lang", szTemp, 8)) {
				*lang = g_strdup(szTemp+9);
				break;
			}
		}
		if (ferror(pf))
			outputerr(szFile);

		fclose(pf);
	}
	g_free(szFile);
	CommandSetLang(*lang);
	g_free(*lang);
	outputon();
}

static void setup_readline(void)
{
#if HAVE_LIBREADLINE
	char *pch;
	int i;
	gnubg_histfile = g_build_filename(szHomeDirectory, "history", NULL);
	rl_readline_name = "gnubg";
	rl_basic_word_break_characters =
	    rl_filename_quote_characters = szCommandSeparators;
	rl_completer_quote_characters = "\"'";
	/* assume readline 4.2 or later */
	rl_completion_entry_function = NullGenerator;
	rl_attempted_completion_function = CompleteKeyword;
	/* setup history */
	read_history(gnubg_histfile);
	using_history();
	if ((pch = getenv("HISTSIZE")) && ((i = atoi(pch)) > 0))
		stifle_history(i);
#endif
}

#if !USE_GTK
static void PushSplash(char *UNUSED(unused), char *UNUSED(heading), char *UNUSED(message))
{
}
#endif

static void init_nets(int fNoBearoff)
{
	char *gnubg_weights = BuildFilename("gnubg.weights");
	char *gnubg_weights_binary =  BuildFilename("gnubg.wd");
	EvalInitialise(gnubg_weights, gnubg_weights_binary, fNoBearoff, 
			   fShowProgress ? BearoffProgress : NULL);
	g_free(gnubg_weights);
	g_free(gnubg_weights_binary);
}

extern int GetManualDice(unsigned int anDice[2])
{

	char *pz;
	char *sz = NULL;
	int i;

#if USE_GTK
	if (fX) {
		if (GTKGetManualDice(anDice)) {
			fInterrupt = 1;
			return -1;
		} else
			return 0;
	}
#endif

	for (;;) {
	      TryAgain:
		if (fInterrupt) {
			anDice[0] = anDice[1] = 0;
			return -1;
		}

		sz = GetInput(_("Enter dice: "));

		if (fInterrupt) {
			g_free(sz);
			anDice[0] = anDice[1] = 0;
			return -1;
		}

		/* parse input and read a couple of dice */
		/* any string with two numbers is allowed */

		pz = sz;

		for (i = 0; i < 2; i++) {
			while (*pz && ((*pz < '1') || (*pz > '6')))
				pz++;

			if (!*pz) {
				outputl(_
					("You must enter two numbers between 1 and 6."));
				goto TryAgain;
			}

			anDice[i] = (int) (*pz - '0');
			pz++;
		}

		g_free(sz);
		return 0;
	}
}

/* 
 * Fetch random numbers from www.random.org
 *
 */

#if HAVE_SOCKETS

extern unsigned int getDiceRandomDotOrg(void)
{

#define RANDOMORGSITEPORT 80
#define RANDOMORGSITE "www.random.org"
#define BUFLENGTH 500

#define BUFLENGTH_STR STRINGIZE(BUFLENGTH)
#define RANDOMORGSITEPORT_STR STRINGIZE(RANDOMORGSITEPORT)
#define EOH "\r\n\r\n"

	static int nCurrent = -1;
	static unsigned int anBuf[BUFLENGTH];
	static int nRead;
	
	int h;
	int cb;

	int nBytesRead, i;
	struct sockaddr *psa;
	char szHostname[80];
	char szHTTP[] =
	    "GET http://" RANDOMORGSITE  "/integers/?num=" BUFLENGTH_STR "&min=0&max=5&col=1&base=10&format=plain&rnd=new HTTP/1.0\n" \
	    "User-Agent: GNUBG/" VERSION " (email: " PACKAGE_BUGREPORT ")\n\n";

	char acBuf[4096];
	char *startOfNums;

	/* 
	 * Suggestions for improvements:
	 * - use proxy
	 */

	/*
	 * Return random number
	 */

	if ((nCurrent >= 0) && (nCurrent < nRead))
		return anBuf[nCurrent++];
	else {

		outputf(_("Fetching %d random numbers from <%s>\n"), BUFLENGTH, RANDOMORGSITE);
		outputx();

		/* fetch new numbers */

		/* open socket */

		strcpy(szHostname, RANDOMORGSITE ":" RANDOMORGSITEPORT_STR);

		if ((h = ExternalSocket(&psa, &cb, szHostname)) < 0) {
			SockErr(szHostname);
			return -1;
		}

		/* connect */

#ifdef WIN32
		if (connect((SOCKET) h, (const struct sockaddr *) psa, cb)
		    < 0) {
#else
		if ((connect(h, psa, cb)) < 0) {
#endif				/* WIN32 */
			SockErr(szHostname);
			return -1;
		}

		/* read next set of numbers */

		if (ExternalWrite(h, szHTTP, strlen(szHTTP) + 1) < 0) {
			SockErr(szHTTP);
			closesocket(h);
			return -1;
		}

		/* read data from web-server */

#ifdef WIN32
		/* reading from sockets doesn't work on Windows
		   use recv instead */
		if (!
		    (nBytesRead =
		     recv((SOCKET) h, acBuf, sizeof(acBuf)-1, 0))) {
#else
		if (!(nBytesRead = read(h, acBuf, sizeof(acBuf)-1))) {
#endif
			SockErr("reading data");
			closesocket(h);
			return -1;
		}

		/* close socket */
		closesocket(h);

		/* parse string */
		outputl(_("Done."));
		outputx();

		/* Skip over the HTTP headers if they exist */
		acBuf[nBytesRead]='\0';
		startOfNums = strstr(acBuf, EOH);
		if (startOfNums)
			startOfNums = startOfNums + (sizeof(EOH)-1);
		else
			startOfNums = acBuf;

		nRead = 0;
		for (i = 0; i < nBytesRead && nRead < BUFLENGTH; i++) {

			if ((startOfNums[i] >= '0') && (startOfNums[i] <= '5')) {
				anBuf[nRead] = 1 + (unsigned int) (startOfNums[i] - '0');
				nRead++;
			}

		}

		nCurrent = 1;
		return anBuf[0];
	}

}

#else
static int (*getRandomDiceDotOrg)(void) = NULL;
#endif /* HAVE_SOCKETS */

static void init_rng(void)
{
	if (!(rngctxCurrent = InitRNG(NULL, NULL, TRUE, rngCurrent))) {
		printf("%s\n", _("Failure setting up RNG"));
		exit(EXIT_FAILURE);
	}
	if (!(rngctxRollout = InitRNG(&rcRollout.nSeed, NULL,
				      TRUE, rcRollout.rngRollout))) {
		printf("%s\n", _("Failure setting up RNG for rollout."));
		exit(EXIT_FAILURE);
	}

	/* we don't want rollouts to use the same seed as normal dice (which
	   could happen if InitRNG had to use the current time as a seed) -- mix
	   it up a little bit */
	rcRollout.nSeed ^= 0x792A584B;
}

#if defined(WIN32) && HAVE_SOCKETS
static void init_winsock()
{
		short wVersionRequested;
		WSADATA wsaData;
		wVersionRequested = MAKEWORD(1, 1);
		if (WSAStartup(wVersionRequested, &wsaData) != 0) {
			outputerr("Windows sockets initialisation");
		}
}
#endif

static char *matchfile_from_argv(char *sz)
{
	char *pchMatch;
#ifdef WIN32
	gsize br, bw;
	gchar *utf8fn;

	if (g_path_is_absolute(sz))
		pchMatch = g_strdup_printf("'%s'", sz);
	else {
		char *tmp =
		    g_build_filename(g_get_current_dir(), sz, NULL);
		pchMatch = g_strdup_printf("'%s'", tmp);
		g_free(tmp);
	}
	utf8fn = g_locale_to_utf8 (pchMatch, -1, &br, &bw, NULL);
	g_free (pchMatch);
	pchMatch = utf8fn;
#else
	pchMatch = g_strdup_printf("'%s'", sz);
#endif
	return pchMatch;
}

static void init_defaults(void)
{
	/* init some html export options */

	exsExport.szHTMLPictureURL = g_strdup("html-images/");
	exsExport.szHTMLExtension = g_strdup("png");

	SetMatchDate(&mi);

	strcpy(ap[1].szName, g_get_user_name());
	strcpy(default_names[1], g_get_user_name());

	ListCreate(&lMatch);
	IniStatcontext(&scMatch);

	szHomeDirectory =
	    g_build_filename(g_get_home_dir(), ".gnubg", NULL);

}

static void init_autosave(void)
{
	char *backupdir;
	backupdir = g_build_filename(szHomeDirectory, "backup", NULL);
	g_mkdir(backupdir, 0700);
	g_free(backupdir);
}

static void null_debug (const gchar* UNUSED(dom), GLogLevelFlags UNUSED(logflags), const gchar* UNUSED(message), gpointer UNUSED(unused))
{
}

int main(int argc, char *argv[])
{
#if USE_GTK
	GtkWidget *pwSplash = NULL;
#else
	char *pwSplash = NULL;
#endif
	char *pchMatch = NULL;
	char *met = NULL;

	static char *pchCommands = NULL, *pchPythonScript = NULL, *lang = NULL;
	static int fNoRC = FALSE, fNoBearoff = FALSE, fNoX = FALSE, fSplash = FALSE, fNoTTY =
	    FALSE, show_version = FALSE, debug = FALSE;
	GOptionEntry ao[] = {
		{"no-bearoff", 'b', 0, G_OPTION_ARG_NONE, &fNoBearoff,
		 N_("Do not use bearoff database"), NULL},
		{"commands", 'c', 0, G_OPTION_ARG_FILENAME, &pchCommands,
		 N_("Evaluate commands in FILE and exit"), "FILE"},
		{"lang", 'l', 0, G_OPTION_ARG_STRING, &lang,
		 N_("Set language to LANG"), "LANG"},
		{"python", 'p', 0, G_OPTION_ARG_FILENAME, &pchPythonScript,
		 N_("Evaluate Python code in FILE and exit"), "FILE"},
		{"quiet", 'q', 0, G_OPTION_ARG_NONE, &fQuiet,
		 N_("Disable sound effects"), NULL},
		{"no-rc", 'r', 0, G_OPTION_ARG_NONE, &fNoRC,
		 N_("Do not read .gnubgrc and .gnubgautorc commands"),
		 NULL},
		{"splash", 'S', 0, G_OPTION_ARG_NONE, &fSplash,
		 N_("Show gtk splash screen"), NULL},
		{"tty", 't', 0, G_OPTION_ARG_NONE, &fNoX,
		 N_("Start the command line instead of using the graphical interface"), NULL},
		{"version", 'v', 0, G_OPTION_ARG_NONE, &show_version,
		 N_("Show version information and exit"), NULL},
		{"window-system-only", 'w', 0, G_OPTION_ARG_NONE, &fNoTTY,
		 N_("Ignore tty input when using the graphical interface"), NULL},
		{"debug", 'd', 0, G_OPTION_ARG_NONE, &debug,
		 N_("Turn on debug"), NULL},
		{"datadir", 'D', 0, G_OPTION_ARG_STRING, &datadir,
		 N_("Specify location of general data"), NULL},
		{"pkgdatadir", 'P', 0, G_OPTION_ARG_STRING, &pkg_datadir,
		 N_("Specify location of program specific data"), NULL},
		{"docdir", 'O', 0, G_OPTION_ARG_STRING, &docdir,
		 N_("Specify location of program documentation"), NULL},
		{NULL, 0, 0, 0, NULL, NULL, NULL}
	};
	GError *error = NULL;
	GOptionContext *context;

#if NO_OVERLAYSCROLLBARS
	/* This hack exists for those platforms like Debian Ubuntu
	   that have problems with Overlay Scrollbars freezing
	   when clicking on analysed position. The real solution
	   is for the deprecated GtkCList to be removed and replaced
	   with a GtkTreeView. mdpetch is currently working on this */ 

	putenv ("LIBOVERLAY_SCROLLBAR=0");
#endif

#if USE_MULTITHREAD
	MT_InitThreads();
#endif

	/* set language */
	init_defaults();
#if USE_GTK
	gtk_disable_setlocale();
#endif
	init_language(&lang);
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	bind_textdomain_codeset(PACKAGE, GNUBG_CHARSET);

	/* parse command line options */
	context = g_option_context_new("[file.sgf]");
	g_option_context_add_main_entries(context, ao, PACKAGE);
#if USE_GTK
	g_option_context_add_group(context, gtk_get_option_group(FALSE));
#endif

	g_option_context_parse(context, &argc, &argv, &error);
	g_option_context_free(context);
	if (error) {
		outputerrf("%s\n", error->message);
		exit(EXIT_FAILURE);
	}
	if (argc > 1 && *argv[1])
		pchMatch = matchfile_from_argv(argv[1]);

	if (!debug)
		g_log_set_handler(NULL, G_LOG_LEVEL_DEBUG, &null_debug, NULL);

#ifdef WIN32
	/* data directory: initialise to the path where gnubg is installed */
	{
		const char *szDataDirectory = getDataDir();
		_chdir(szDataDirectory);
	}
	/* Create a mutex so install can check if it's running */
	CreateMutex(NULL, FALSE, "GNU Backgammon Mutex");

#endif

	/* print version and exit if -v option given */
	VersionMessage();
	if (show_version)
		exit(EXIT_SUCCESS);

	if (CreateGnubgDirectory())
		exit(EXIT_FAILURE);

	init_autosave();

	RenderInitialise();

#ifdef WIN32
	fNoTTY = TRUE;
#endif
#if USE_GTK
	/* -t option not given */
	if (!fNoX)
		InitGTK(&argc, &argv);
	if (fX) {
		fTTY = !fNoTTY && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
		fInteractive = fShowProgress = TRUE;
		if (fSplash)
			pwSplash = CreateSplash();
	} else
#endif
	{
		fInteractive = isatty(STDIN_FILENO);
		fShowProgress = isatty(STDOUT_FILENO);
	}

	if (fInteractive) {
		PortableSignal(SIGINT, HandleInterrupt, &shInterruptOld, FALSE);
		setup_readline();
	}

	PushSplash(pwSplash, _("Initialising"), _("Random number generator"));
	init_rng();

	PushSplash(pwSplash, _("Initialising"), _("match equity table"));
	met = BuildFilename2("met", "Rockwell-Kazaross.xml");
	InitMatchEquity(met);
	g_free(met);

	PushSplash(pwSplash, _("Initialising"), _("neural nets"));
	init_nets(fNoBearoff);

#if defined(WIN32) && HAVE_SOCKETS
	PushSplash(pwSplash, _("Initialising"), _("Windows sockets"));
	init_winsock();
#endif

#if USE_PYTHON
	PushSplash(pwSplash, _("Initialising"), _("Python"));
	PythonInitialise(argv[0]);
#endif

	SetExitSoundOff();

	/* -r option given */
	if (!fNoRC) {
		PushSplash(pwSplash, _("Loading"), _("User Settings"));
		LoadRCFiles();
	}

	strcpy(ap[0].szName,default_names[0]);
	strcpy(ap[1].szName,default_names[1]);

	fflush(stdout);
	fflush(stderr);

#if USE_MULTITHREAD
	/* Make sure threads started */
	MT_StartThreads();
#endif

	/* start-up sound */
	playSound(SOUND_START);

#if USE_GTK
	if (fX) {
		if (!fTTY) {
			g_set_print_handler(&GTKOutput);
			g_set_printerr_handler(&GTKOutputErr);
		}
		RunGTK(pwSplash, pchCommands, pchPythonScript, pchMatch);
		Shutdown();
		exit(EXIT_SUCCESS);
	}
#endif

	if (pchMatch)
		CommandImportAuto(pchMatch);

	/* -c option given */
	if (pchCommands) {
		fInteractive = FALSE;
		CommandLoadCommands(pchCommands);
		Shutdown();
		exit(EXIT_SUCCESS);
	}

	/* -p option given */
	if (pchPythonScript) {
#if USE_PYTHON
		fInteractive = FALSE;
		LoadPythonFile(pchPythonScript);
		Shutdown();
		exit(EXIT_SUCCESS);
#else
		outputerrf(_("GNU Backgammon build without Python."));
		exit(EXIT_FAILURE);
#endif
	}
	run_cl();
	return (EXIT_FAILURE);
}


/*
 * Command: convert normalised money equity to match winning chance.
 *
 * The result is written to stdout.
 * FIXME: implement GTK version.
 * FIXME: allow more parameters (match score, match length)
 *
 * Input: 
 *   sz: string with equity
 *
 * Output:
 *   none.
 *
 */

extern void CommandEq2MWC ( char *sz )
{

  float rEq;
  cubeinfo ci;

  if ( ! ms.nMatchTo ) {
    outputl ( _("Command only valid in match play") );
    return;
  }


  rEq = (float)ParseReal ( &sz );

  if ( rEq == ERR_VAL ) rEq = 0.0;

  GetMatchStateCubeInfo( &ci, &ms );

  outputf ( "%s = %+6.3f: %6.2f%%\n", _("MWC for equity"),
            -1.0, 100.0 * eq2mwc ( -1.0, &ci ) );
  outputf ( "%s = %+6.3f: %6.2f%%\n", _("MWC for equity"),
            +1.0, 100.0 * eq2mwc ( +1.0, &ci ) );
  outputf ( "%s:\n", _("By linear interpolation") );
  outputf ( "%s = %+6.3f: %6.2f%%\n", _("MWC for equity"),
            rEq, 100.0 * eq2mwc ( rEq, &ci ) );

}


/*
 * Command: convert match winning chance to normalised money equity.
 *
 * The result is written to stdout.
 * FIXME: implement GTK version.
 * FIXME: allow more parameters (match score, match length)
 *
 * Input: 
 *   sz: string with match winning chance
 *
 * Output:
 *   none.
 *
 */

extern void CommandMWC2Eq ( char *sz )
{

  float rMwc;
  cubeinfo ci;

  if ( ! ms.nMatchTo ) {
    outputl ( _("Command only valid in match play") );
    return;
  }

  GetMatchStateCubeInfo( &ci, &ms );

  rMwc = (float)ParseReal ( &sz );

  if ( rMwc == ERR_VAL ) rMwc = eq2mwc ( 0.0, &ci );

  if ( rMwc > 1.0 ) rMwc /= 100.0;

  outputf ( "%s = %6.2f%%: %+6.3f\n", _("Equity for MWC"),
            100.0 * eq2mwc ( -1.0, &ci ), -1.0 );
  outputf ( "%s = %6.2f%%: %+6.3f\n", _("Equity for MWC"),
            100.0 * eq2mwc ( +1.0, &ci ), +1.0 );
  outputf ( "%s:\n", _("By linear interpolation") );
  outputf ( "%s = %6.2f%%: %+6.3f\n", _("Equity for MWC"),
            100.0 * rMwc, mwc2eq ( rMwc, &ci ) );


}

static void 
swapGame ( listOLD *plGame ) {

  listOLD *pl;
  moverecord *pmr;
  int n;
  TanBoard anBoard;

  for( pl = plGame->plNext; pl != plGame; pl = pl->plNext ) {
      
    pmr = pl->p;

    switch ( pmr->mt ) {
    case MOVE_GAMEINFO:

      /* swap score */

      n = pmr->g.anScore[ 0 ];
      pmr->g.anScore[ 0 ] = pmr->g.anScore[ 1 ];
      pmr->g.anScore[ 1 ] = n;

      /* swap winner */

      if ( pmr->g.fWinner > -1 )
        pmr->g.fWinner = ! pmr->g.fWinner;

      /* swap statcontext */

      /* recalculate statcontext later ... */

      break;

    case MOVE_DOUBLE:
    case MOVE_TAKE:
    case MOVE_DROP:
    case MOVE_NORMAL:
    case MOVE_RESIGN:
    case MOVE_SETDICE:
    case MOVE_SETCUBEVAL:

      pmr->fPlayer = ! pmr->fPlayer;
      break;

    case MOVE_SETBOARD:

      PositionFromKey(anBoard, &pmr->sb.key);
      SwapSides( anBoard );
      PositionKey( (ConstTanBoard)anBoard, &pmr->sb.key );
      pmr->fPlayer = ! pmr->fPlayer;
      break;

    case MOVE_SETCUBEPOS:

      if ( pmr->scp.fCubeOwner > -1 )
        pmr->scp.fCubeOwner = ! pmr->scp.fCubeOwner;
      pmr->fPlayer = ! pmr->fPlayer;
      break;

    }

  }

}



extern void CommandSwapPlayers ( char *UNUSED(sz) )
{
  listOLD *pl;
  char *pc;
  int n;

  /* swap individual move records */

  for( pl = lMatch.plNext; pl != &lMatch; pl = pl->plNext ) {

    swapGame ( pl->p );

  }

  /* swap player names */

  pc = g_strdup ( ap[ 0 ].szName );
  strcpy ( ap[ 0 ].szName, ap[ 1 ].szName );
  strcpy ( ap[ 1 ].szName, pc );
  g_free ( pc );

  /* swap player ratings */

  pc = mi.pchRating[ 0 ];
  mi.pchRating[ 0 ] = mi.pchRating[ 1 ];
  mi.pchRating[ 1 ] = pc;

  /* swap current matchstate */

  if ( ms.fTurn > -1 )
    ms.fTurn = ! ms.fTurn;
  if ( ms.fMove > -1 )
    ms.fMove = ! ms.fMove;
  if ( ms.fCubeOwner > -1 )
    ms.fCubeOwner = ! ms.fCubeOwner;
  n = ms.anScore[ 0 ];
  ms.anScore[ 1 ] = ms.anScore[ 0 ];
  ms.anScore[ 0 ] = n;
  SwapSides ( ms.anBoard );

#if USE_GTK
  if ( fX ) {
    fJustSwappedPlayers = TRUE;
    GTKSet ( ap );
/* GTKSet(ap) already does this. This just adds flicker.
    GTKRegenerateGames();
*/
  }
#endif

  ChangeGame(NULL);

  ShowBoard();
}


extern int
confirmOverwrite ( const char *sz, const int f ) {

  char *szPrompt;
  int i;

  /* check for existing file */

  if ( f && ! access ( sz, F_OK ) ) {

    szPrompt = (char *) malloc ( 50 + strlen ( sz ) );
    sprintf ( szPrompt, _("File \"%s\" exists. Overwrite? "), sz );
    i = GetInputYN ( szPrompt );
    free ( szPrompt );
    return i;

  }
  else
    return TRUE;


}

extern void
setDefaultFileName (char *path)
{
  g_free (szCurrentFolder);
  g_free (szCurrentFileName);
  DisectPath (path, NULL, &szCurrentFileName, &szCurrentFolder);
#if USE_GTK
  if (fX)
    {
      gchar *title =
	g_strdup_printf ("%s (%s)", _("GNU Backgammon"), szCurrentFileName);
      gtk_window_set_title (GTK_WINDOW (pwMain), title);
      g_free (title);
    }
#endif
}

extern void
DisectPath (const char *path, const char *extension, char **name, char **folder)
{
  char *fnn, *pc;
  if (!path)
  {
	  *folder = NULL;
	  *name = NULL;
	  return;
  }
  *folder = g_path_get_dirname (path);
  fnn = g_path_get_basename (path);
  pc = strrchr (fnn, '.');
  if (pc)
    *pc = '\0';
  *name = g_strconcat (fnn, extension, NULL);
  g_free (fnn);
}


/* ask for confirmation if this is a sub-optimal play 
 * returns TRUE if player wants to re-think the move
 */

static int GetAdviceAnswer( char *sz ) {

  char	*pch;
#if USE_GTK
  if( fX )
	return GtkTutor ( sz );
#endif

    if( fInterrupt )
    return FALSE;

    while( (pch = GetInput( sz )) != 0 )
	{
	if( pch )
	    switch( *pch ) {
	    case 'y':
	    case 'Y':
		free( pch );
		return TRUE;
	    case 'n':
	    case 'N':
		free( pch );
		return FALSE;
	    default:
		free( pch );
	    }

	outputl( _("Please answer `y' or `n'.") );
    }
    return FALSE;
}

extern int GiveAdvice( skilltype Skill )
{

  char *sz;

  /* should never happen */
  if ( !fTutor )
	return FALSE;
  
	switch (Skill) {

	case SKILL_VERYBAD:
	  sz = _( "You may be about to make a very bad play" );
	  break;

	case SKILL_BAD:
	  sz = _( "You may be about to make a bad play" );
	  break;
	  
	case SKILL_DOUBTFUL:
	  sz = _( "You may be about to make a doubtful play" );
	  break;

	default:
	  return (TRUE);
	}

	if (Skill > TutorSkill)
	  return (TRUE);

	{
		int ret;
		char *buf = g_strdup_printf("%s. %s", sz, _("Are you sure?"));
		ret = GetAdviceAnswer( buf );
		g_free(buf);
		return ret;
	}
}

extern void TextToClipboard(const char *sz)
{
#if USE_GTK
  if (fX)
    GTKTextToClipboard(sz);
#else
  /* no clipboard: just write string */
  outputl(sz);
#endif
}

void 
CommandDiceRolls (char *sz) {
  int    n;
  char	*pch;
  unsigned int	 anDice[2];

  if ( (pch = NextToken( &sz ) ) ) {
    n = ParseNumber( &pch );

    while (n-- > 0) {
      RollDice( anDice, &rngCurrent, rngctxCurrent );

      printf( "%d %d\n", anDice[ 0 ], anDice[ 1 ] );

    }

  }
}


#if HAVE_LIBREADLINE
extern void
CommandHistory( char *UNUSED(sz) ) {

  int i;
  HIST_ENTRY *phe;

  for ( i = 0; i < history_length; ++i ) {
    phe = history_get( i + history_base );
    outputf( "%6d %s\n", i + history_base, phe->line );
  }
  

}

#endif /* HAVE_LIBREADLINE */

extern void CommandClearHint(char *UNUSED(sz))
{
	pmr_hint_destroy();
	outputl(_("Analysis used for `hint' has been cleared"));
}


/*
 * Description:  Calculate Effective Pip Counts (EPC), either
 *               by lookup in one-sided databases or by doing a
 *               one-sided rollout.  
 * Parameters :  
 *   Input       anBoard (the board)
 *               fOnlyRace: only calculate EPCs for race positions
 *   Output      arEPC (the calculate EPCs)
 *               pfSource (source of EPC; 0 = database, 1 = OSR)
 *
 * Returns:      0 on success, non-zero otherwise 
 *               
 */

extern int
EPC( const TanBoard anBoard, float *arEPC, float *arMu, float *arSigma, 
     int *pfSource, const int fOnlyBearoff ) {

  const float x = ( 2 * 3 + 3 * 4 + 4 * 5 + 4 * 6 + 6 * 7 +
                    5* 8  + 4 * 9 + 2 * 10 + 2 * 11 + 1 * 12 + 
                    1 * 16 + 1 * 20 + 1 * 24 ) / 36.0f;

  if ( isBearoff ( pbc1, anBoard ) ) {
    /* one sided in-memory database */
    float ar[ 4 ];
    unsigned int n;
    int i;

    for ( i = 0; i < 2; ++i ) {
      n = PositionBearoff( anBoard[ i ], pbc1->nPoints, pbc1->nChequers );

      if ( BearoffDist( pbc1, n, NULL, NULL, ar, NULL, NULL ) )
        return -1;

      if ( arEPC )
        arEPC[ i ] = x * ar[ 0 ];
      if ( arMu )
        arMu[ i ] = ar[ 0 ];
      if ( arSigma )
        arSigma[ i ] = ar[ 1 ];

    }

    if ( pfSource )
      *pfSource = 0;

    return 0;

  }
  else if ( isBearoff( pbcOS, anBoard ) ) {
    /* one sided in-memory database */
    float ar[ 4 ];
    unsigned int n;
    int i;

    for ( i = 0; i < 2; ++i ) {
      n = PositionBearoff( anBoard[ i ], pbcOS->nPoints, pbcOS->nChequers );

      if ( BearoffDist( pbcOS, n, NULL, NULL, ar, NULL, NULL ) )
        return -1;

      if ( arEPC )
        arEPC[ i ] = x * ar[ 0 ];
      if ( arMu )
        arMu[ i ] = ar[ 0 ];
      if ( arSigma )
        arSigma[ i ] = ar[ 1 ];

    }

    if ( pfSource )
      *pfSource = 0;

    return 0;

  }
  else if ( ! fOnlyBearoff ) {

    /* one-sided rollout */

    int nTrials = 5760;
    float arMux[ 2 ];
    float ar[ 5 ];
    int i;

    raceProbs ( anBoard, nTrials, ar, arMux );

    for ( i = 0; i < 2; ++i ) {
      if ( arEPC )
        arEPC[ i ] = x * arMux[ i ];
      if ( arMu )
        arMu[ i ] = arMux[ i ];
      if ( arSigma )
        arSigma[ i ] = -1.0f; /* raceProbs cannot calculate sigma! */
    }

    if ( pfSource )
      *pfSource = 1;

    return 0;

  }

  /* code not reachable */
  return -1;
}

#if HAVE_LIBREADLINE
extern char *locale_from_utf8(const char *sz)
{
	char *ret;
	GError *error = NULL;
	g_assert(sz);
	ret = g_locale_from_utf8(sz, strlen(sz), NULL, NULL, &error);
	if (error) {
		g_print("locale_from_utf8 failed: %s\n", error->message);
		g_error_free(error);
		ret = g_strdup(sz);
	}
	return ret;
}
#endif

extern char *locale_to_utf8(const char *sz)
{
	char *ret;
	GError *error = NULL;
	g_assert(sz);
	ret = g_locale_to_utf8(sz, strlen(sz), NULL, NULL, &error);
	if (error) {
		g_print("locale_to_utf8 failed: %s\n", error->message);
		g_error_free(error);
		ret = g_strdup(sz);
	}
	return ret;
}
#ifdef WIN32 
/* WIN32 setlocale must be manipulated through putenv to be gettext compatible */
char * SetupLanguage (const char *newLangCode)
{
	static char *org_lang=NULL;
	char *lang;
	if (!org_lang)
		org_lang = g_win32_getlocale();
	if (!newLangCode || !strcmp (newLangCode, "system") || !strcmp (newLangCode, ""))
		lang = g_strdup_printf("LANG=%s", org_lang);
	else
		lang = g_strdup_printf("LANG=%s", newLangCode);
	putenv(lang); 	 
	g_free(lang);
	return(setlocale(LC_ALL, ""));
}
#else
char *SetupLanguage(const char *newLangCode)
{
	static char *org_lang = NULL;
	char *result = NULL;

	if (!org_lang) {
		org_lang = g_strdup(setlocale(LC_ALL, ""));
		if (!org_lang) {
			outputerrf(_("Locale in your environment not supported by "
				     "C library. Falling back to C locale.\n"));
			org_lang = g_strdup("C");
		}
	}

	if (newLangCode && *newLangCode && strcmp(newLangCode, "system") != 0) {
		g_setenv("LC_ALL", newLangCode, TRUE);
		result = setlocale(LC_ALL, newLangCode);
		return (result);
	}
	g_setenv("LC_ALL", org_lang, TRUE);
	result = setlocale(LC_ALL, org_lang);
	return (result);
}
#endif

void asyncFindMove(findData *pfd)
{
	if( FindnSaveBestMoves( pfd->pml, ms.anDice[ 0 ], ms.anDice[ 1 ], pfd->pboard,
				pfd->keyMove, pfd->rThr, pfd->pci, pfd->pec, pfd->aamf) < 0)
		MT_SetResultFailed();
}

void asyncDumpDecision(decisionData *pdd)
{
	if (DumpPosition( pdd->pboard, pdd->szOutput, pdd->pec, pdd->pci,
                       fOutputMWC, fOutputWinPC, pdd->n, MatchIDFromMatchState( &ms ) ) != 0 )
		MT_SetResultFailed();
}

void asyncScoreMove(scoreData *psd)
{
    if ( ScoreMove (NULL, psd->pm, psd->pci, psd->pec, psd->pec->nPlies ) < 0 )
		MT_SetResultFailed();
}

void asyncEvalRoll(decisionData *pdd)
{
	EvaluateRoll (pdd->aarOutput[0], ms.anDice[0], ms.anDice[1], pdd->pboard, pdd->pci, pdd->pec );
}

void asyncAnalyzeMove(moveData *pmd)
{
    if (AnalyzeMove ( pmd->pmr, pmd->pms, plGame, NULL, pmd->pesChequer, pmd->pesCube, pmd->aamf, NULL, NULL ) < 0 )
		MT_SetResultFailed();
}

void asyncGammonRates(decisionData *pdd)
{
	if ( getCurrentGammonRates ( pdd->aarRates, pdd->aarOutput[0], pdd->pboard, pdd->pci, pdd->pec ) < 0 )
		MT_SetResultFailed();
}

void asyncMoveDecisionE(decisionData *pdd)
{
	if ( GeneralEvaluationE ( pdd->aarOutput[0], pdd->pboard, pdd->pci, pdd->pec) < 0 )
		MT_SetResultFailed();
}

void asyncCubeDecisionE(decisionData *pdd)
{
	if ( GeneralCubeDecisionE ( pdd->aarOutput, pdd->pboard, pdd->pci, pdd->pec, pdd->pes ) < 0 )
		MT_SetResultFailed();
}

void asyncCubeDecision(decisionData *pdd)
{
	if ( GeneralCubeDecision( pdd->aarOutput, pdd->aarStdDev, pdd->aarsStatistics,
			pdd->pboard, pdd->pci, pdd->pes, NULL, NULL ) < 0 )
		MT_SetResultFailed();
}

extern int RunAsyncProcess(AsyncFun fn, void *data, const char *msg)
{
	int ret;
#if USE_MULTITHREAD
	Task *pt = (Task*)malloc(sizeof(Task));
	pt->pLinkedTask = NULL;
	pt->fun = fn;
	pt->data = data;
	MT_AddTask(pt, FALSE);
#endif

	ProgressStart(msg);
	
#if USE_MULTITHREAD
	ret = MT_WaitForTasks(Progress, 100, FALSE);
#else
	asyncRet = 0;
	fn(data);	/* Just call function in single threaded build */
	ret = asyncRet;
#endif

	ProgressEnd();

	return ret;
}

extern void ProcessEvents(void)
{
#if USE_GTK
	if (fX)
	{
		while(gtk_events_pending())
			gtk_main_iteration();
	}
	else
#endif
	{
		while (g_main_context_pending(NULL))
			g_main_context_iteration(NULL, TRUE);
	}
}

extern int CheckGameExists(void)
{
	if (plGame)
	{
		return TRUE;
	}
	else
	{
		outputl( _("No game in progress.") );
		return FALSE;
	}
}

extern gboolean save_autosave(gpointer UNUSED(unused))
{
	int fd;
	FILE *pf;
	listOLD *pl;

	g_return_val_if_fail(plGame, FALSE);

	MT_Exclusive();
	if (autosave)
		g_unlink(autosave);

	autosave = g_build_filename(szHomeDirectory, "backup", "XXXXXX.sgf", NULL);

	fd = g_mkstemp(autosave);
	if (fd < 0) {
		g_free(autosave);
		autosave = NULL;
		MT_Release();
		return FALSE;
	}

	close(fd);
	g_unlink(autosave);

	pf = g_fopen(autosave, "w");
	if (!pf) {
		autosave = NULL;
		MT_Release();
		return FALSE;
	}

	for (pl = lMatch.plNext; pl != &lMatch; pl = pl->plNext)
		SaveGame(pf, pl->p);

	fclose(pf);
	MT_Release();
	return TRUE;
}

extern void delete_autosave(void)
{
    if (autosave)
    {
	    g_unlink(autosave);
	    g_free(autosave);
	    autosave = NULL;
    }
}

extern int get_input_discard(void)
{
	if (fInterrupt)
		return FALSE;

	if (autosave && fAutoSaveConfirmDelete)
	{
		if (!GetInputYN(_("Are you sure you want to discard the current match and your existing autosave? ")))
			return FALSE;
		g_unlink(autosave);
		g_free(autosave);
		autosave = NULL;
		return TRUE;
	}
	if (ms.gs == GAME_PLAYING && fConfirmNew)
	       return GetInputYN(_("Are you sure you want to discard the current match? "));
	
	return TRUE;
}


