/*
 * Copyright (C) 1998-2003 Gary Wong <gtw@gnu.org>
 * Copyright (C) 1999-2021 the AUTHORS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * $Id: gnubg.c,v 1.1026 2022/03/27 16:43:01 plm Exp $
 */

#include "config.h"

#include "gnubgmodule.h"
#include "glib-ext.h"

#include <sys/types.h>
#include <stdlib.h>
#if defined(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>
#include <locale.h>
#ifdef WIN32
#include <direct.h>
#include <io.h>
#endif

#if defined(HAVE_LIB_READLINE)
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 "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 defined(LIBCURL_PROTOCOL_HTTPS)
#include <curl/curl.h>
#endif

#if defined(HAVE_SOCKETS)
#if defined(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 */
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#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                          /* #ifdef WIN32 */
#endif                          /* #if HAVE_SOCKETS */

#if defined(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 defined(USE_BOARD3D)
#include "inc3d.h"
#endif
#include "multithread.h"
#include "openurl.h"

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

#if defined(USE_GTK)
int fX = FALSE;                 /* use X display */
unsigned int nDelay = 300;
int fNeedPrompt = FALSE;
#if defined(HAVE_LIB_READLINE)
int fReadingCommand;
#endif
#endif

static int fNoRC = FALSE;
static char *autosave = NULL;
static int loading_rc = FALSE;

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;
int fInteractive;

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 defined(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.12f,                      /* SKILL_VERYBAD */
    0.06f,                      /* SKILL_BAD */
    0.03f,                      /* 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.01f,                      /* stop when std's are lower than 0.01 */
    324,                        /* minimum games  */
    2.33f,                      /* stop when best has j.s.d. for 99% confidence */
    0,                          /* nGamesDone */
    0.0,                        /* rStoppedOnJSD */
    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.01f,  /* stop when std's are lower than 0.01 */ \
  324,    /* minimum games  */ \
  2.33f,  /* stop when best has j.s.d. for 99% confidence */ \
  0, \
  0.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.01f,  /* stop when std's are lower than 0.01 */ \
  324,    /* minimum games  */ \
  2.33f,  /* stop when best has j.s.d. for 99% confidence */ \
  0, \
  0.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,                       /* show detailed prob. for cube decisions */
    {FALSE, TRUE}
    ,                           /* do not show move parameters */
    /* display all cube decisions */
    {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 */
};


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" };

char player1aliases[256] = "";

/* Usage strings */
static char szDICE[] = N_("<die> <die>"),
    szCOMMAND[] = N_("<command>"),
    szCOMMENT[] = N_("<comment>"),
    szER[] = "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[] = "[quiet]",
    szLANG[] = N_("system|<language code>"),
    szONOFF[] = "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> [length]"),
    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[] = "<URL>",
    szMAXERR[] = N_("<fraction>"), szMINGAMES[] = N_("<minimum games to rollout>"), szFOLDER[] = N_("<folder>"),
#if defined(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;

static char const *aszBuildInfo[] = {
#if defined(USE_PYTHON)
#if (PY_MAJOR_VERSION == 2)
    N_("Python 2 supported."),
#elif (PY_MAJOR_VERSION == 3)
    N_("Python 3 supported."),
#else
    #error "Unsupported python version"
#endif
#endif
#if defined(USE_SQLITE)
    N_("SQLite database supported."),
#endif
#if defined(USE_GTK)
#if GTK_CHECK_VERSION(3,0,0)
    N_("GTK3 graphical interface supported."),
#else
    N_("GTK2 graphical interface supported."),
#endif
#endif
#if defined(HAVE_SOCKETS)
    N_("External players supported."),
#endif
#if defined(HAVE_LIBGMP)
    N_("Long RNG seeds supported."),
#endif
#if defined(USE_BOARD3D)
    N_("3D boards supported."),
#endif
#if defined(WIN32)
    N_("Windows sound system supported."),
#elif defined(HAVE_APPLE_COREAUDIO)
    N_("Apple CoreAudio sound system supported."),
#elif defined(HAVE_APPLE_QUICKTIME)
    N_("Apple QuickTime sound system supported."),
#elif defined(HAVE_CANBERRA)
    N_("libcanberra sound system supported."),
#endif
#if defined(USE_MULTITHREAD)
    N_("Multiple threads supported."),
#endif
#if defined(USE_SIMD_INSTRUCTIONS)
#if defined(USE_SSE2)
    N_("SSE/SSE2 supported."),
#elif defined(USE_AVX)
    N_("AVX supported."),
#elif defined(USE_SSE)
    N_("SSE supported."),
#else
    N_("NEON 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 quoted strings can be escaped with a
 backslash and will be taken as literal.  Backslashes within single
 quoted strings are taken literally. Multiple quoted strings can be
 concatenated.  
 
 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 terminate 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 within 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;
#if !defined(G_DISABLE_ASSERT)
    char *pchEnd;
#endif

    if (!*ppch)
        return NULL;

#if !defined(G_DISABLE_ASSERT)
    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 '"':
                /* 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;

#if !defined(G_DISABLE_ASSERT)
    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 float
ParseReal(char **ppch)
{

    char *pch, *pchOrig;
    float r;

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

    r = (float) 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 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
         */

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

            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) {
            if (pch[2 * i + 0] >= 'A' && pch[2 * i + 0] <= 'P' && pch[2 * i + 1] >= 'A' && pch[2 * i + 1] <= 'P')
                key.auch[i] = (unsigned char) (((pch[2 * i + 0] - 'A') << 4)
                                               + (pch[2 * i + 1] - 'A'));
            else {
                outputl(_("Illegal position."));
                return -1;
            }
        }

        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 defined(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();
}

/* 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 further 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);
    size_t 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;
}

/* FIXME? This is only used with fRestart == FALSE */
extern void
PortableSignal(int nSignal, void (*p) (int), psighandler * pOld, int fRestart)
{
#if defined(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 defined(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
    (void) fRestart;            /* silence compiler warning */
    if (pOld)
        *pOld = signal(nSignal, p);
    else if (p)
        signal(nSignal, p);
#endif
}

extern void
PortableSignalRestore(int nSignal, psighandler * p)
{
#if defined(HAVE_SIGACTION)
    sigaction(nSignal, p, NULL);
#elif defined(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 defined(USE_GTK)
        if (nNextTurn) {
            g_source_remove(nNextTurn);
            nNextTurn = 0;
        }
#endif
    }
}


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

    command *pc;
    char *pch;
    size_t 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 defined(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)
{

    switch (bgv) {

    case VARIATION_STANDARD:
        PositionFromID(anBoard, "4HPwATDgc/ABMA");
        break;
    case VARIATION_NACKGAMMON:
        PositionFromID(anBoard, "4Dl4ADbgOXgANg");
        break;

    case VARIATION_HYPERGAMMON_1:
        PositionFromID(anBoard, "AACAAAAAAgAAAA");
        break;
    case VARIATION_HYPERGAMMON_2:
        PositionFromID(anBoard, "AABAAQAACgAAAA");
        break;
    case VARIATION_HYPERGAMMON_3:
        PositionFromID(anBoard, "AACgAgAAKgAAAA");
        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, -1));
}

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 %u%u", _("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 %u%u (%s):\n", _("Rolled"), pmr->anDice[0], pmr->anDice[1], GetLuckAnalysis(&ms, pmr->rLuck));
        break;

    default:
        break;
    }
}

extern void
ShowBoard(void)
{
    char sz[50], szCube[50], szPlayer0[MAX_NAME_LEN + 3], szPlayer1[MAX_NAME_LEN + 3],
        szScore0[50], szScore1[50], szMatch[50];
    TanBoard an;

    if (cOutputDisabled || !foutput_on)
        return;

    if (ms.gs == GAME_NONE) {
#if defined(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 defined(USE_GTK)
    if (!fX)
#endif
    {
        char szBoard[2048];
        char *apch[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
        moverecord *pmr;

        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],
                              ms.fPostCrawford));

            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 %u%u", _("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;

                /* Using ngettext() below looks awkward, but it matters in case of multiple plurals, as in many eastern european languages */

                if (ms.nMatchTo)
                    if (ms.nMatchTo == 1)
                        sprintf(szCube, ngettext("%d point match", "%d points match", ms.nMatchTo), ms.nMatchTo);
                    else if (ms.fCrawford)
                        sprintf(szCube, ngettext("%d point match (Crawford game)", "%d points match (Crawford game)", ms.nMatchTo), ms.nMatchTo);
                    else
                        sprintf(szCube, ngettext("%d point match (Cube: %d)", "%d points match (Cube: %d)", ms.nMatchTo), ms.nMatchTo, ms.nCube);
                else
                    sprintf(szCube, "(%s: %d)", _("Cube"), ms.nCube);
            } else {
                size_t cch = strlen(ap[ms.fCubeOwner].szName);

                if (cch > 20)
                    cch = 20;

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

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

                if (ms.nMatchTo)
                    sprintf(apch[3] = szMatch, ngettext("%d point match", "%d points match", ms.nMatchTo), ms.nMatchTo);
            }
        }
        if (ms.fResigned > 0)
            /* FIXME it's not necessarily the player on roll who 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 defined(USE_GTK)
               PanelShowing(WINDOW_ANALYSIS) &&
#endif
               plLastMove && (pmr = plLastMove->plNext->p)) {
            DisplayAnalysis(pmr);
            if (pmr->sz)
                outputl(pmr->sz);       /* FIXME word wrap */
        }
    }
#if defined(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, "%u:%u", 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 defined(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;
    size_t 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;
    size_t 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 defined(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[FORMATEDMOVESIZE];
    float *ar, *arStdDev;
    float rEq, rEqTop;

    GetMatchStateCubeInfo(&ci, pms);

    strcpy(sz, "");

    /* number */

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

    /* eval */

    sprintf(strchr(sz, 0),
            "%-14s   %-28s %s: ",
            FormatEval(szTemp, &pml->amMoves[i].esMove),
            FormatMove(szMove, pms->anBoard,
                       pml->amMoves[i].anMove), (!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(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 defined(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 defined(USE_GTK)
    if (fX) {

        GTKResignHint(dd.aarOutput[0], rEqBefore, rEqAfter, &ci, ms.nMatchTo && fOutputMWC);

        return;

    }
#endif

    if (!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);

    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 defined(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, -1));
}

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 defined(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, 1));
}

extern void
hint_move(char *sz, gboolean show, procrecorddata * procdatarec)
{
    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;
    int fSaveShowProg = fShowProgress;

    if (!ms.anDice[0])
        return;

    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 (procdatarec) {
            show = FALSE;
            fShowProgress = (procdatarec->avInputData[PROCREC_HINT_ARGIN_SHOWPROGRESS] != NULL);
        }
        if ((RunAsyncProcess((AsyncFun) asyncFindMove, &fd, _("Considering move...")) != 0) || fInterrupt) {
            fShowProgress = fSaveShowProg;
            return;
        }
        fShowProgress = fSaveShowProg;

        pmr_movelist_set(pmr, GetEvalChequer(), &ml);
    }
#if defined(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 {
        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);
    }

    /* resort the moves according to cubeful (if applicable),
     * cubeless equities and tie-breaking heuristics to avoid
     * suggesting some silly looking moves */

    if (pmr->ml.cMoves > 0) {
        int *ai = (int *) malloc(pmr->ml.cMoves * sizeof(int));
        RefreshMoveList(&pmr->ml, ai);
        free(ai);
    }

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

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

    if (procdatarec) {
        procdatarec->avOutputData[PROCREC_HINT_ARGOUT_MATCHSTATE] = (void *) &ms;
        procdatarec->avOutputData[PROCREC_HINT_ARGOUT_CUBEINFO] = (void *) &ci;
        procdatarec->avOutputData[PROCREC_HINT_ARGOUT_MOVELIST] = (void *) &pmr->ml;
        procdatarec->avOutputData[PROCREC_HINT_ARGOUT_MOVERECORD] = (void *) &pmr;
    }

    n = MIN(pmr->ml.cMoves, n);
    for (i = 0; i < n; i++) {
        if (show)
            output(FormatMoveHint(szBuf, &ms, &pmr->ml, i, TRUE, TRUE, TRUE));
        else if (procdatarec && procdatarec->pfProcessRecord) {
            procdatarec->avOutputData[PROCREC_HINT_ARGOUT_INDEX] = (void *) (ptrdiff_t) i;
            if (!procdatarec->pfProcessRecord(procdatarec))
                break;
        }
    }
}

extern void
CommandHint(char *sz)
{
    listOLD *pl;
    moverecord *pmr;

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

        return;
    }

    /* The code further below handles only the case of asking a hint
     * on a resignation by gnubg while playing against it. When
     * clicking on a "resign" entry in the moves panel while reviewing
     * a game, the match state ms is not up to date and we have to
     * look at the moves list */

    pl = plLastMove->plNext;
    if ((pmr = pl->p) && pmr->mt == MOVE_RESIGN) {
        HintResigned();
        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, NULL);
        return;
    }

}

static void
Shutdown(void)
{

    RenderFinalise();

    free_rngctx(rngctxCurrent);
    free_rngctx(rngctxRollout);

    FreeMatch();
    ClearMatch();

#if defined(USE_GTK)
    MoveListDestroy();
#endif

    MT_Close();

    EvalShutdown();

#if defined(USE_PYTHON)
    PythonShutdown();
#endif

#if defined(LIBCURL_PROTOCOL_HTTPS)
    /* Cleanup curl */
    curl_global_cleanup();
#endif

#if defined(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 defined(USE_GTK)
    BoardData *bd = NULL;

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

    fExiting = TRUE;

    if (fInteractive) {
        fInterrupt = FALSE;
        if (!get_input_discard()) {
            fInterrupt = FALSE;
            fExiting = FALSE;
            return;
        }
    }
#if defined(USE_BOARD3D)
    if (fX && bd && (display_is_3d(bd->rd))) {  /* Stop any 3d animations */
        StopIdle3d(bd, bd->bd3d);
    }
#endif
#if defined(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);

    ProcessEvents();

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

    if (fInteractive)
        PortableSignalRestore(SIGINT, &shInterruptOld);

#if defined(USE_GTK)
    if (fX) {
        stop_board_expose(bd);
        board_free_pixmaps(bd);
    }
#if defined(USE_BOARD3D)
    if (fX && bd)
        Tidy3dObjects(bd->bd3d, bd->rd);
#endif
#endif

#if defined(HAVE_LIB_READLINE)
    write_history(gnubg_histfile);
#endif                          /* HAVE_LIB_READLINE */

#if defined(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][FORMATEDMOVESIZE];
    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 defined(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];

    outputpostpone();

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

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

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

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

#if defined(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 defined(USE_PYTHON)
        LoadPythonFile(sz, FALSE);
#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 *szQuoted = NULL;
    char *szQuotedTemp = NULL;
    if (sz[0] != '"') {
        szQuoted = szQuotedTemp = g_strdup_printf("\"%s\"", sz);
        sz = NextToken(&szQuoted);
    } 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 (szQuotedTemp)
        g_free(szQuotedTemp);
#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 %u%u", _("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;

            /* Using ngettext() below looks awkward, but it matters in case of multiple plurals, as in many eastern european languages */

            if (ms.nMatchTo)
                if (ms.nMatchTo == 1)
                    sprintf(szCube, ngettext("%d point match", "%d points match", ms.nMatchTo), ms.nMatchTo);
                else if (ms.fCrawford)
                    sprintf(szCube, ngettext("%d point match (Crawford game)", "%d points match (Crawford game)", ms.nMatchTo), ms.nMatchTo);
                else
                    sprintf(szCube, ngettext("%d point match (Cube: %d)", "%d points match (Cube: %d)", ms.nMatchTo), ms.nMatchTo, ms.nCube);
            else
                sprintf(szCube, "(%s: %d)", _("Cube"), ms.nCube);
        } else {
            size_t cch = strlen(ap[ms.fCubeOwner].szName);

            if (cch > 20)
                cch = 20;

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

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

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

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

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

    DrawBoard(szOut, (ConstTanBoard) anBoardTemp, ms.fMove, aps, MatchIDFromMatchState(&ms), anChequers[ms.bgv]);

    {
        unsigned int anPips[2];
        char szPipCount[32];

        PipCount((ConstTanBoard) anBoardTemp, anPips);
        sprintf(szPipCount, "%s O %u, X %u\n", _("Pip counts:"), anPips[0], anPips[1]);

        strcat(szOut, "                    ");
        strcat(szOut, szPipCount);
    }

    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_BBS:
        fprintf(pf, "%s rng bbs\n", sz);        /* FIXME save modulus */
        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 %u\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 %u\n"
            "%s bearofftruncation exact %s\n"
            "%s bearofftruncation onesided %s\n"
            "%s later enable %s\n"
            "%s later plies %u\n"
            "%s trials %u\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 %u\n"
            "%s limit maxerror %s\n"
            "%s jsd stop %s\n"
            "%s jsd minimumgames %u\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 = g_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);

    g_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 %u\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 defined(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 defined(USE_BOARD3D)
    if (fSync != -1)
        fprintf(pf, "set vsync3d %s\n", fSync ? "yes" : "no");
#endif
}
#endif

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

    for (i = (gnubgsound) 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]);
    if (strlen(player1aliases) > 0)
        fprintf(pf, "set aliases %s\n", player1aliases);

    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 %u\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 %u\n", GetEvalCacheEntries());
    fprintf(pf, "set matchequitytable \"%s\"\n", miCurrent.szFileName);
    fprintf(pf, "set invert matchequitytable %s\n", fInvertMET ? "on" : "off");
#if defined(USE_MULTITHREAD)
    fprintf(pf, "set threads %u\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 %d\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 we were started without a configuration file, don't
     * save anything since this will overwrite an existing file
     * with defaults */
    if (fNoRC)
        return;

    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 defined(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"
            "#\n"
            "# If you want to override them with permanent settings, "
            "put these manually \n"
            "# in a 'gnubgrc' file in the same directory.\n"),
          VERSION_STRING);

    fprintf(pf, "\n");

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

#if defined(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");
        if (cOutputPostponed) {
            outputresume();
            outputx();
            outputpostpone();
        }
    }
    g_free(szFile);

}

#if defined(HAVE_LIB_READLINE)
static command *pcCompleteContext;

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

static char *
GenerateKeywords(const char *sz, int nState)
{
    static size_t 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) {
            szDup = strdup(pc->sz);
            pc++;
            return szDup;
        }

        pc++;
    }

    return NULL;
}

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

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

    while (i < 2) {
        const char *pch = i++ ? "rollout" : "evaluation";

        if (!StrNCaseCmp(sz, pch, cch))
            return strdup(pch);

        ++i;
    }

    return NULL;
}

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

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

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

        if (!StrNCaseCmp(sz, pch, cch))
            return strdup(pch);
    }

    return NULL;
}

static char *
PlayerCompletionGen(const char *sz, int nState, int fBoth)
{
    static int i;
    static size_t cch;
    const char *pch;

    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))
            return strdup(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;
    command *pcResume = NULL;

    pch = strcpy(sz, szOrig);
    pch[ich] = 0;

    do {
        char *pchCurrent;

        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 defined(HAVE_LIB_READLINE)
    if (!fInteractive || !isatty(STDIN_FILENO))
        return;
#endif

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

#if defined(HAVE_LIB_READLINE)
static 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

static 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;
}

#if defined(USE_GTK)
#if defined(HAVE_LIB_READLINE)
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 *szp = locale_from_utf8(FormatPrompt());
        rl_callback_handler_install(szp, ProcessInput);
        g_free(szp);
        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 defined(HAVE_LIB_READLINE)
    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 defined(HAVE_LIB_READLINE)
        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 defined(USE_GTK)
    g_assert(fTTY && !fX);
#endif

#if defined(HAVE_LIB_READLINE)
    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 = g_malloc(256);           /* FIXME it would be nice to handle longer strings */

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

    if (fInterrupt) {
        g_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);
    g_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 defined(USE_GTK)
    if (fX)
        return GTKGetInputYN(szPrompt);
#endif

    if (fInterrupt)
        return FALSE;

    while ((pch = GetInput(szPrompt)) != NULL) {
        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;
}

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

#if GLIB_CHECK_VERSION (2,28,0)
static gint64 tvProgress;

static int
ProgressThrottle(void)
{
    gint64 tv;

    tv = g_get_monotonic_time();

    if (tv - tvProgress >= 100000) {
        /* sufficient time elapsed; record current time */
        tvProgress = tv;
        return 0;
    }

    /* insufficient time elapsed */
    return -1;
}
#else
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;
}
#endif

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

    fInProgress = TRUE;

#if defined(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 defined(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 defined(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 defined(USE_GTK)
    if (fX) {
        GTKProgress();
        return TRUE;
    }
#endif

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

#if !defined(USE_MULTITHREAD)
extern void
CallbackProgress(void)
{
#if defined(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 defined(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 defined(USE_GTK)
    if (fX) {
        GTKBearoffProgress(i);
        return;
    }
#endif
    putchar("\\|/-"[(i / 1000) % 4]);
    putchar('\r');
    fflush(stdout);
}

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

#if defined(HAVE_LIB_READLINE)
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 defined(HAVE_LIB_READLINE)
        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;
    FILE *pf;

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

    if (!*lang && (pf = g_fopen(szFile, "r"))) {
        char szTemp[4096];

        while (fgets(szTemp, sizeof(szTemp), pf) != NULL) {
            char *pch;

            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 defined(HAVE_LIB_READLINE)
    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 !defined(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 defined(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;
    }
}

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) && defined(HAVE_SOCKETS)
static void
init_winsock()
{
    short wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD(1, 1);
    if (WSAStartup(wVersionRequested, &wsaData) != 0) {
        outputerr(_("Windows sockets initialisation error"));
    }
}
#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))
{
}

static char *pchPythonScript = NULL;
static int fPython = FALSE;

static gboolean
callback_parse_python_option(const gchar *UNUSED(name), const gchar *value, gpointer UNUSED(data), GError **UNUSED(error))
{
    if (value)
        pchPythonScript = g_strdup(value);
    else
        pchPythonScript = NULL;

    fPython = TRUE;

    return TRUE;
}

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

    static char *pchCommands = NULL, *lang = NULL;
    static int 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', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, callback_parse_python_option,
         N_("Start in Python mode or evaluate 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},
        {"prefsdir", 's', 0, G_OPTION_ARG_STRING, &prefsdir,
         N_("Specify location of user's preferences directory"), NULL},
        {NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL}
    };
    GError *error = NULL;
    GOptionContext *context;

#if ! GLIB_CHECK_VERSION(2,36,0)
    g_type_init();
#endif

#if defined(LIBCURL_PROTOCOL_HTTPS)
    curl_global_init(CURL_GLOBAL_ALL);
#endif

    default_import_folder = g_strdup(".");
    default_export_folder = g_strdup(".");
    default_sgf_folder = g_strdup(".");

    output_initialize();

    init_defaults();

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

    g_set_print_handler(print_utf8_to_locale);
    g_set_printerr_handler(print_utf8_to_locale);

    /* parse command-line options */
    context = g_option_context_new("[file.sgf]");
    g_option_context_add_main_entries(context, ao, PACKAGE);
#if defined(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);

    if (prefsdir) {
        szHomeDirectory = prefsdir;
    }
#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 */
    /* FIXME? rc files not read yet, lang is still the default */

    VersionMessage();
    if (show_version)
        exit(EXIT_SUCCESS);

    if (CreateGnubgDirectory())
        exit(EXIT_FAILURE);

    init_autosave();

    RenderInitialise();

#ifdef WIN32
    fNoTTY = TRUE;
#endif
#if defined(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", "Kazaross-XG2.xml");
    InitMatchEquity(met);
    g_free(met);

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

    PushSplash(pwSplash, _("Initialising"), _("initialising thread data"));
    glib_ext_init();
    MT_InitThreads();

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

#if defined(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 defined(USE_MULTITHREAD)
    /* Make sure threads started */
    MT_StartThreads();
#endif

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

#if defined(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 (fPython) {
#if defined(USE_PYTHON)
        if (pchPythonScript) {
            fInteractive = FALSE;
            LoadPythonFile(pchPythonScript, FALSE);
            Shutdown();
            exit(EXIT_SUCCESS);
        } else {
            PythonRun(NULL);
            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 = 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.0f * eq2mwc(-1.0, &ci));
    outputf("%s = %+6.3f: %6.2f%%\n", _("MWC for equity"), +1.0, 100.0f * eq2mwc(+1.0, &ci));
    outputf("%s:\n", _("By linear interpolation"));
    outputf("%s = %+6.3f: %6.2f%%\n", _("MWC for equity"), rEq, 100.0f * 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 = ParseReal(&sz);

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

    if (rMwc > 1.0f)
        rMwc /= 100.0f;

    outputf("%s = %6.2f%%: %+6.3f\n", _("Equity for MWC"), 100.0f * eq2mwc(-1.0, &ci), -1.0);
    outputf("%s = %6.2f%%: %+6.3f\n", _("Equity for MWC"), 100.0f * eq2mwc(+1.0, &ci), +1.0);
    outputf("%s:\n", _("By linear interpolation"));
    outputf("%s = %6.2f%%: %+6.3f\n", _("Equity for MWC"), 100.0f * 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 defined(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)
{
    /* check for existing file */

    if (f && !access(sz, F_OK)) {
        gchar *szp;
        int i;

        szp = g_strdup_printf(_("File \"%s\" exists. Overwrite? "), sz);
        i = GetInputYN(szp);

        g_free(szp);

        return i;

    } else
        return TRUE;

}

extern void
setDefaultFileName(char *path)
{
    g_free(szCurrentFolder);
    g_free(szCurrentFileName);
    DisectPath(path, NULL, &szCurrentFileName, &szCurrentFolder);
#if defined(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 defined(USE_GTK)
    if (fX)
        return GtkTutor(sz);
#endif

    if (fInterrupt)
        return FALSE;

    while ((pch = GetInput(sz)) != NULL) {
        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 defined(USE_GTK)
    if (fX)
        GTKTextToClipboard(sz);
#else
    /* no clipboard: just write string */
    outputl(sz);
#endif
}

void
CommandDiceRolls(char *sz)
{
    char *pch;

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

        while (n-- > 0) {
            unsigned int anDice[2];

            RollDice(anDice, &rngCurrent, rngctxCurrent);

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


#if defined(HAVE_LIB_READLINE)
extern void
CommandHistory(char *UNUSED(sz))
{
    int i;

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

#endif                          /* HAVE_LIB_READLINE */

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];
        int i;

        for (i = 0; i < 2; ++i) {
            unsigned int 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];
        int i;

        for (i = 0; i < 2; ++i) {
            unsigned int 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;
}

#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
asyncFindBestMoves(findData * pfd)
{
    if (FindnSaveBestMoves(pfd->pml, pfd->anDice[0], pfd->anDice[1], pfd->pboard,
                           pfd->keyMove, pfd->rThr, pfd->pci, pfd->pec, pfd->aamf) < 0)
        MT_SetResultFailed();

    RefreshMoveList(pfd->pml, NULL);
}

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 fun, void *data, const char *msg)
{
    int ret;
#if defined(USE_MULTITHREAD)
    Task *pt = (Task *) g_malloc(sizeof(Task));

    pt->pLinkedTask = NULL;
    pt->fun = fun;
    pt->data = data;
    MT_AddTask(pt, TRUE);
#endif

    ProgressStart(msg);

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

    ProgressEnd();

    return ret;
}

extern void
ProcessEvents(void)
{
#if defined(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;
}
