/* * Copyright (C) 2003-2004 Joern Thyssen * Copyright (C) 2003-2022 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 . */ #include "config.h" #if defined(USE_PYTHON) #include "gnubgmodule.h" #endif #include #include #include #include "backgammon.h" #include "drawboard.h" #include "eval.h" #include "matchequity.h" #include "positionid.h" #include "matchid.h" #include "multithread.h" #include "util.h" #include "lib/gnubg-types.h" #include "lib/simd.h" #if defined(USE_GTK) #include "gtkgame.h" #include #endif #if defined(USE_PYTHON) static PyObject *RolloutContextToPy(const rolloutcontext * rc); static PyObject *PythonGnubgID(PyObject * self, PyObject * args); static void DictSetItemSteal(PyObject * dict, const char *key, PyObject * val) { int const s = PyDict_SetItemString(dict, key, val); { g_assert(s == 0); } (void) dict; /* silence compiler warning */ (void) key; /* when assertions are disabled */ (void) s; Py_DECREF(val); } static PyObject * BoardToPy(const TanBoard anBoard) { PyObject *b = PyTuple_New(2); PyObject *b0 = PyTuple_New(25); PyObject *b1 = PyTuple_New(25); unsigned int k; for (k = 0; k < 25; ++k) { PyTuple_SET_ITEM(b0, k, PyInt_FromLong(anBoard[0][k])); PyTuple_SET_ITEM(b1, k, PyInt_FromLong(anBoard[1][k])); } PyTuple_SET_ITEM(b, 0, b0); PyTuple_SET_ITEM(b, 1, b1); return b; } static PyObject * Board1ToPy(unsigned int anBoard[25]) { unsigned int k; PyObject *b = PyTuple_New(25); for (k = 0; k < 25; ++k) { PyTuple_SET_ITEM(b, k, PyInt_FromLong(anBoard[k])); } return b; } /* Convert Move tuple to an anMove structure. * A tuple can be expressed in two forms. * form returned by findbestmove ie. (21,18,11,5) * form returned by parsemove ie. ((21,18),(11,5)) * * on return populates the passed anMove structure */ static int PyToMove(PyObject * p, signed int anMove[8]) { Py_ssize_t tuplelen; if (!PySequence_Check(p)) return 0; tuplelen = PySequence_Size(p); if (tuplelen > 0) { int j; int anIndex = 0; /* Process outter tuple */ for (j = 0; j < tuplelen && anIndex < 8; ++j) { PyObject *pi = PySequence_Fast_GET_ITEM(p, j); if (!PySequence_Check(pi)) if (PyInt_Check(pi)) /* Found value like findbestmove returns */ anMove[anIndex++] = (int) PyInt_AsLong(pi) - 1; else /* Value not an integer */ return 0; else { /* Found inner tuple like parsemove returns */ if (PySequence_Check(pi) && PySequence_Size(pi) == 2) { int k; /* Process inner tuple */ for (k = 0; k < 2 && anIndex < 8; ++k, anIndex++) { PyObject *pii = PySequence_Fast_GET_ITEM(pi, k); if (PyInt_Check(pii)) anMove[anIndex] = (int) PyInt_AsLong(pii) - 1; else /* Value not an integer */ return 0; } } /*if an inner tuple doesn't have exactly 2 elements there is an error */ else return 0; } } /* If we have found more items than the maximum */ if (anIndex >= 8 && j < tuplelen) return 0; return 1; } else { /* No tuples equivalent to no legal moves */ return 1; } } static int PyToBoard1(PyObject * p, unsigned int anBoard[25]) { if (PySequence_Check(p) && PySequence_Size(p) == 25) { int j; for (j = 0; j < 25; ++j) { PyObject *pi = PySequence_Fast_GET_ITEM(p, j); anBoard[j] = (int) PyInt_AsLong(pi); } return 1; } return 0; } static int PyToBoard(PyObject * p, TanBoard anBoard) { if (PySequence_Check(p) && PySequence_Size(p) == 2) { int i; for (i = 0; i < 2; ++i) { PyObject *py = PySequence_Fast_GET_ITEM(p, i); if (!PyToBoard1(py, anBoard[i])) { return 0; } } return 1; } return 0; } static int PyToDice(PyObject * p, int anDice[2]) { if (PySequence_Check(p) && PySequence_Size(p) == 2) { int j; for (j = 0; j < 2; ++j) { PyObject *pi = PySequence_Fast_GET_ITEM(p, j); anDice[j] = (int) PyInt_AsLong(pi); } return 1; } return 0; } static PyObject * CubeInfoToPy(const cubeinfo * pci) { return Py_BuildValue("{s:i,s:i,s:i,s:i,s:(i,i)," "s:i,s:i,s:i,s:((f,f),(f,f)),s:i}", "cube", pci->nCube, "cubeowner", pci->fCubeOwner, "move", pci->fMove, "matchto", pci->nMatchTo, "score", pci->anScore[0], pci->anScore[1], "crawford", pci->fCrawford, "jacoby", pci->fJacoby, "beavers", pci->fBeavers, "gammonprice", pci->arGammonPrice[0], pci->arGammonPrice[2], pci->arGammonPrice[1], pci->arGammonPrice[3], "bgv", pci->bgv); } static int PyToCubeInfo(PyObject * p, cubeinfo * pci) { PyObject *pyKey, *pyValue; Py_ssize_t iPos = 0; static const char *aszKeys[] = { "jacoby", "crawford", "move", "beavers", "cube", "matchto", "bgv", "cubeowner", "score", "gammonprice", NULL }; void *apv[10]; int *pi; float *pf; int i = 0; apv[i++] = &pci->fJacoby; apv[i++] = &pci->fCrawford; apv[i++] = &pci->fMove; apv[i++] = &pci->fBeavers; apv[i++] = &pci->nCube; apv[i++] = &pci->nMatchTo; apv[i++] = &pci->bgv; apv[i++] = &pci->fCubeOwner; apv[i++] = pci->anScore; apv[i] = pci->arGammonPrice; while (PyDict_Next(p, &iPos, &pyKey, &pyValue)) { char *pchKey; int iKey; #if (PY_MAJOR_VERSION >= 3) if (!(pchKey = PyBytes_AsString(PyUnicode_AsUTF8String(pyKey)))) #else if (!(pchKey = PyBytes_AsString(pyKey))) #endif return -1; iKey = -1; for (i = 0; aszKeys[i] && iKey < 0; ++i) if (!strcmp(aszKeys[i], pchKey)) iKey = i; if (iKey < 0) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid key in cubeinfo " "(see gnubg.cubeinfo() for an example)")); return -1; } switch (iKey) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: /* simple integer */ if (!PyInt_Check(pyValue)) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid value in cubeinfo " "(see gnubg.cubeinfo() for an example)")); return -1; } *((int *) apv[iKey]) = (int) PyInt_AsLong(pyValue); break; case 8: /* score */ pi = (int *) apv[iKey]; if (!PyArg_ParseTuple(pyValue, "ii", pi, pi + 1)) return -1; break; case 9: /* gammon price */ pf = (float *) apv[iKey]; if (!PyArg_ParseTuple(pyValue, "(ff)(ff)", pf, pf + 2, pf + 1, pf + 3)) return -1; break; default: g_assert_not_reached(); } } return 0; } static int PyToMoveFilter(PyObject * p, movefilter * pmf, int *ply, int *level) { PyObject *pyKey, *pyValue; Py_ssize_t iPos = 0; static const char *aszKeys[] = { "ply", "level", "acceptmoves", "extramoves", "threshold", NULL }; void *apv[5]; int i = 0; apv[i++] = ply; apv[i++] = level; apv[i++] = &pmf->Accept; apv[i++] = &pmf->Extra; apv[i] = &pmf->Threshold; if (!PyDict_Check(p)) return -1; while (PyDict_Next(p, &iPos, &pyKey, &pyValue)) { char *pchKey; int iKey; #if (PY_MAJOR_VERSION >= 3) if (!(pchKey = PyBytes_AsString(PyUnicode_AsUTF8String(pyKey)))) #else if (!(pchKey = PyBytes_AsString(pyKey))) #endif return -1; iKey = -1; for (i = 0; aszKeys[i] && iKey < 0; ++i) if (!strcmp(aszKeys[i], pchKey)) iKey = i; if (iKey < 0) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid key in movefilter " "(see gnubg.getevalhintfilter() for an example)")); return -1; } switch (iKey) { case 0: case 1: case 2: case 3: /* simple integer */ if (!PyInt_Check(pyValue)) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid value in movefilter " "(see gnubg.getevalhintfilter() for an example)")); return -1; } *((int *) apv[iKey]) = (int) PyInt_AsLong(pyValue); break; case 4: /* simple float */ if (!PyFloat_Check(pyValue)) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid value in movefilter " "(see gnubg.getevalhintfilter() for an example)")); return -1; } *((float *) apv[iKey]) = (float) PyFloat_AsDouble(pyValue); break; default: g_assert_not_reached(); } } return 0; } static int PyToMoveFilters(PyObject * p, TmoveFilter aamf) { Py_ssize_t iSeqLen; movefilter newFilter; TmoveFilter aamftmp; movefilter defaultFilter = { -1, 0, 0.0 }; int iPly, iLevel; int entry, i, j; if (!PySequence_Check(p)) { PyErr_SetString(PyExc_ValueError, _("invalid movefilter list " "(see gnubg.getevalhintfilter() for an example)")); return -1; } /* Set the defaults for each filter */ for (i = 0; i < MAX_FILTER_PLIES; ++i) for (j = 0; j < MAX_FILTER_PLIES; ++j) { aamftmp[i][j] = defaultFilter; } iSeqLen = PySequence_Size(p); for (entry = 0; entry < iSeqLen; ++entry) { PyObject *pObj = PySequence_Fast_GET_ITEM(p, entry); iPly = iLevel = 0; if (PyToMoveFilter(pObj, &newFilter, &iPly, &iLevel) < 0) { PyErr_SetString(PyExc_ValueError, _("invalid movefilter in list " "(see gnubg.getevalhintfilter() for an example)")); return -1; } if ((iPly < 1 || iPly > MAX_FILTER_PLIES) || (iLevel < 0 || iLevel >= iPly)) { PyErr_SetString(PyExc_ValueError, _("invalid movefilter list " "(see gnubg.getevalhintfilter() for an example)")); return -1; } aamftmp[iPly - 1][iLevel] = newFilter; } /* If success then copy the temporary filter to the original */ memcpy(*aamf, *aamftmp, sizeof(TmoveFilter)); return 0; } static PyObject * MoveFilterToPy(int ply, int level, const movefilter * pmf) { return Py_BuildValue("{s:i,s:i,s:i,s:i,s:f}", "ply", ply, "level", level, "acceptmoves", pmf->Accept, "extramoves", pmf->Extra, "threshold", pmf->Threshold); } static PyObject * MoveFiltersToPy(const TmoveFilter aamf) { int ply, level; PyObject *pyMoveFilterList = PyList_New(0); PyObject *pyMoveFilter = NULL; for (ply = 0; ply < MAX_FILTER_PLIES; ++ply) for (level = 0; level <= ply; ++level) { pyMoveFilter = MoveFilterToPy(ply + 1, level, &aamf[ply][level]); PyList_Append(pyMoveFilterList, pyMoveFilter); Py_DECREF(pyMoveFilter); } return pyMoveFilterList; } static PyObject * PythonGetEvalHintFilter(PyObject * UNUSED(self), PyObject * args) { if (!PyArg_ParseTuple(args, ":movefilter")) return NULL; return MoveFiltersToPy((ConstTmoveFilter) (*GetEvalMoveFilter())); } static PyObject * PythonSetEvalHintFilter(PyObject * UNUSED(self), PyObject * args) { TmoveFilter *aamf = GetEvalMoveFilter(); PyObject *pyMoveFilters = NULL; if (!PyArg_ParseTuple(args, "|O", &pyMoveFilters)) { return NULL; } if (!pyMoveFilters) { PyErr_SetString(PyExc_StandardError, _("requires 1 argument: a list of move filters " "(see gnubg.getevalhintfilter() for an example))")); return NULL; } if (!PyToMoveFilters(pyMoveFilters, *aamf)) { return NULL; } Py_INCREF(Py_None); return Py_None; } static int SetPosInfo(posinfo * ppi, const int fTurn, const int fResigned, const int fDoubled, const gamestate gs, const int anDice[2]) { if (fTurn < 0 || fTurn > 1 || fResigned < 0 || fResigned > 3 || fDoubled < 0 || fDoubled > 1 || anDice[0] > 6 || anDice[0] < 0 || anDice[1] > 6 || anDice[1] < 0 || gs > GAME_DROP) { memset(ppi, 0, sizeof(posinfo)); return -1; } ppi->fTurn = fTurn; ppi->fResigned = fResigned; ppi->fDoubled = fDoubled; ppi->gs = gs; ppi->anDice[0] = anDice[0]; ppi->anDice[1] = anDice[1]; return 0; } static PyObject * PosInfoToPy(const posinfo * ppi) { return Py_BuildValue("{s:(i,i),s:i,s:i,s:i,s:i}", "dice", ppi->anDice[0], ppi->anDice[1], "turn", ppi->fTurn, "resigned", ppi->fResigned, "doubled", ppi->fDoubled, "gamestate", ppi->gs); } static int PyToPosInfo(PyObject * p, posinfo * ppi) { PyObject *pyKey, *pyValue; Py_ssize_t iPos = 0; static const char *aszKeys[] = { "turn", "resigned", "doubled", "gamestate", "dice", NULL }; void *apv[5]; int *pi; int i = 0; apv[i++] = &ppi->fTurn; apv[i++] = &ppi->fResigned; apv[i++] = &ppi->fDoubled; apv[i++] = &ppi->gs; apv[i] = &ppi->anDice; while (PyDict_Next(p, &iPos, &pyKey, &pyValue)) { char *pchKey; int iKey; #if (PY_MAJOR_VERSION >= 3) if (!(pchKey = PyBytes_AsString(PyUnicode_AsUTF8String(pyKey)))) #else if (!(pchKey = PyBytes_AsString(pyKey))) #endif return -1; iKey = -1; for (i = 0; aszKeys[i] && iKey < 0; ++i) if (!strcmp(aszKeys[i], pchKey)) iKey = i; if (iKey < 0) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid key in posinfo " "(see gnubg.posinfo() for an example)")); return -1; } switch (iKey) { case 0: case 1: case 2: /* simple integer */ if (!PyInt_Check(pyValue)) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid value in posinfo " "(see gnubg.posinfo() for an example)")); return -1; } *((int *) apv[iKey]) = (int) PyInt_AsLong(pyValue); break; case 3: /* simple unsigned integer (gamestate) */ if (!PyInt_Check(pyValue)) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid value in posinfo " "(see gnubg.posinfo() for an example)")); return -1; } *((gamestate *) apv[iKey]) = (gamestate) PyInt_AsLong(pyValue); break; case 4: /* Dice */ pi = (int *) apv[iKey]; if (!PyArg_ParseTuple(pyValue, "ii", pi, pi + 1)) return -1; break; default: g_assert_not_reached(); } } return 0; } static PyObject * EvalContextToPy(const evalcontext * pec) { return Py_BuildValue("{s:i,s:i,s:i,s:i,s:f}", "cubeful", pec->fCubeful, "plies", pec->nPlies, "deterministic", pec->fDeterministic, "prune", pec->fUsePrune, "noise", pec->rNoise); } static int PyToEvalContext(PyObject * p, evalcontext * pec) { PyObject *pyKey, *pyValue; Py_ssize_t iPos = 0; static const char *aszKeys[] = { "cubeful", "plies", "deterministic", "prune", "noise", NULL }; int i; while (PyDict_Next(p, &iPos, &pyKey, &pyValue)) { char *pchKey; int iKey; #if (PY_MAJOR_VERSION >= 3) if (!(pchKey = PyBytes_AsString(PyUnicode_AsUTF8String(pyKey)))) #else if (!(pchKey = PyBytes_AsString(pyKey))) #endif return -1; iKey = -1; for (i = 0; aszKeys[i] && iKey < 0; ++i) if (!strcmp(aszKeys[i], pchKey)) iKey = i; if (iKey < 0) { /* unknown dict value */ PyErr_SetString(PyExc_ValueError, _("invalid key in evalcontext " "(see gnubg.evalcontext() for an example)")); return -1; } switch (iKey) { case 0: case 1: case 2: case 3: /* simple integer */ if (!PyInt_Check(pyValue)) { /* not an integer */ PyErr_SetString(PyExc_ValueError, _("invalid value in evalcontext " "(see gnubg.evalcontext() for an example)")); return -1; } i = (int) PyInt_AsLong(pyValue); if (!iKey) pec->fCubeful = i ? 1 : 0; else if (iKey == 1) pec->nPlies = (i < 8) ? i : 7; else if (iKey == 2) { pec->fDeterministic = i ? 1 : 0; } else pec->fUsePrune = i ? 1 : 0; break; case 4: /* float */ if (!PyFloat_Check(pyValue)) { /* not a float */ PyErr_SetString(PyExc_ValueError, _("invalid value in evalcontext " "(see gnubg.evalcontext() for an example)")); return -1; } pec->rNoise = (float) PyFloat_AsDouble(pyValue); break; default: g_assert_not_reached(); } } return 0; } static PyObject * PythonCubeInfo(PyObject * UNUSED(self), PyObject * args) { cubeinfo ci; int nCube = ms.nCube; int fCubeOwner = ms.fCubeOwner; int fMove = ms.fMove; int nMatchTo = ms.nMatchTo; int anScore[2]; int fCrawford = ms.fCrawford; int fJacobyRule = ms.fJacoby; int fBeavers = ms.cBeavers; bgvariation bgv = ms.bgv; anScore[0] = ms.anScore[0]; anScore[1] = ms.anScore[1]; if (!PyArg_ParseTuple(args, "|iiii(ii)iiii:cubeinfo", &nCube, &fCubeOwner, &fMove, &nMatchTo, &anScore[0], &anScore[1], &fCrawford, &bgv)) return NULL; if (SetCubeInfo(&ci, nCube, fCubeOwner, fMove, nMatchTo, anScore, fCrawford, fJacobyRule, fBeavers, bgv)) { PyErr_SetString(PyExc_StandardError, _("error in SetCubeInfo\n")); return NULL; } return CubeInfoToPy(&ci); } static PyObject * PythonPosInfo(PyObject * UNUSED(self), PyObject * args) { static posinfo pi; int fTurn = ms.fTurn; int fResigned = ms.fResigned; int fDoubled = ms.fDoubled; gamestate gs = ms.gs; int anDice[2]; anDice[0] = ms.anDice[0]; anDice[1] = ms.anDice[1]; if (!PyArg_ParseTuple(args, "|iiii(ii):posinfo", &fTurn, &fResigned, &fDoubled, &gs, &anDice[0], &anDice[1])) return NULL; if (SetPosInfo(&pi, fTurn, fResigned, fDoubled, gs, anDice)) { PyErr_SetString(PyExc_StandardError, _("error in SetPosInfo\n")); return NULL; } return PosInfoToPy(&pi); } static PyObject * PythonNextTurn(PyObject * UNUSED(self), PyObject * UNUSED(args)) { fNextTurn = TRUE; while (fNextTurn) { if (NextTurn(TRUE) == -1) fNextTurn = FALSE; } Py_INCREF(Py_None); return Py_None; } static int PythonHint_Callback(procrecorddata * pr) { char szMove[FORMATEDMOVESIZE]; PyObject *list = (PyObject *) pr->pvUserData; PyObject *hintdict = NULL, *ctxdict = NULL, *details = NULL; int index = (int) (ptrdiff_t) pr->avOutputData[PROCREC_HINT_ARGOUT_INDEX]; const matchstate *pms = pr->avOutputData[PROCREC_HINT_ARGOUT_MATCHSTATE]; const movelist *pml = pr->avOutputData[PROCREC_HINT_ARGOUT_MOVELIST]; const evalsetup *pes = &pml->amMoves[index].esMove; float rEq = pml->amMoves[index].rScore; float rEqTop = pml->amMoves[0].rScore; float rEqDiff = rEq - rEqTop; FormatMove(szMove, (ConstTanBoard) pms->anBoard, pml->amMoves[index].anMove); switch (pes->et) { case EVAL_NONE: hintdict = Py_BuildValue("{s:i,s:s,s:f,s:f}", "movenum", index + 1, "type", "none", "equity", rEq, "eqdiff", rEqDiff); break; case EVAL_EVAL:{ const move *pmi = &pml->amMoves[index]; details = Py_BuildValue("{s:(fffff),s:f}", "probs", pmi->arEvalMove[0], pmi->arEvalMove[1], pmi->arEvalMove[2], pmi->arEvalMove[3], pmi->arEvalMove[4], "score", pmi->rScore); ctxdict = EvalContextToPy(&pes->ec); hintdict = Py_BuildValue("{s:i,s:s,s:s,s:f,s:f,s:N,s:N}", "movenum", index + 1, "type", "eval", "move", szMove, "equity", rEq, "eqdiff", rEqDiff, "context", ctxdict, "details", details); break; } case EVAL_ROLLOUT:{ const move *pmi = &pml->amMoves[index]; const float *p = pmi->arEvalMove; const float *s = pmi->arEvalStdDev; details = Py_BuildValue("{s:(fffff),s:(fffff),s:f,s:f,s:f,s:f,s:i,s:f}", "probs", p[0], p[1], p[2], p[3], p[4], "probs-std", s[0], s[1], s[2], s[3], s[4], "match-eq", p[OUTPUT_EQUITY], "cubeful-eq", p[OUTPUT_CUBEFUL_EQUITY], "score", pmi->rScore, "score2", pmi->rScore2, "trials", pes->rc.nGamesDone, "stopped-on-jsd", pes->rc.rStoppedOnJSD); ctxdict = RolloutContextToPy(&pes->rc); hintdict = Py_BuildValue("{s:i,s:s,s:s,s:f,s:f,s:N,s:N}", "movenum", index + 1, "type", "rollout", "move", szMove, "equity", rEq, "eqdiff", rEqDiff, "context", ctxdict, "details", details); break; } default: hintdict = Py_BuildValue("{s:i,s:s,s:i}", "movenum", index + 1, "type", "unknown", "typenum", pes->et); break; } if (hintdict) { PyList_Append(list, hintdict); Py_DECREF(hintdict); } return TRUE; } SIMD_STACKALIGN static PyObject * PythonHint(PyObject * UNUSED(self), PyObject * args) { PyObject *retval = NULL, *gnubgid = NULL, *tmpobj; procrecorddata prochint; char szNumber[11]; char *szHintType = NULL; int nMaxMoves = -1; if (!PyArg_ParseTuple(args, "|i", &nMaxMoves)) return NULL; if (nMaxMoves < 0) nMaxMoves = MAX_MOVES; if ((ms.gs != GAME_PLAYING)) { PyErr_SetString(PyExc_StandardError, _("You must set up a board first.")); } else /* hint on cube decision */ if (!ms.anDice[0] && !ms.fDoubled && !ms.fResigned) { PyErr_SetString(PyExc_StandardError, _("Hints for cube actions not yet implemented")); /* hint_double(TRUE, -1); */ } else /* Give hint on resignation */ if (ms.fResigned) { PyErr_SetString(PyExc_StandardError, _("Hints for resignations not yet implemented")); /* HintResigned(); */ } else /* Give hint on take decision */ if (ms.fDoubled) { PyErr_SetString(PyExc_StandardError, _("Hints for take actions not yet implemented")); /* hint_take(TRUE, -1); */ } else if (ms.anDice[0]) { sprintf(szNumber, "%d", nMaxMoves); memset(&prochint, 0, sizeof(prochint)); prochint.pvUserData = PyList_New(0); prochint.pfProcessRecord = PythonHint_Callback; prochint.avInputData[PROCREC_HINT_ARGIN_SHOWPROGRESS] = (void *) (long) 0; prochint.avInputData[PROCREC_HINT_ARGIN_MAXMOVES] = (void *) (ptrdiff_t) nMaxMoves; hint_move(szNumber, FALSE, (void *) &prochint); if (MT_SafeGet(&fInterrupt)) { ResetInterrupt(); PyErr_SetString(PyExc_StandardError, _("interrupted/errno in hint_move")); return NULL; } szHintType = "chequer"; retval = prochint.pvUserData; } if (retval) { tmpobj = Py_BuildValue("()"); gnubgid = PythonGnubgID(NULL, tmpobj); Py_DECREF(tmpobj); tmpobj = Py_BuildValue("{s:s,s:N,s:N}", "hinttype", szHintType, "gnubgid", gnubgid, "hint", retval); retval = tmpobj; } return retval; } static PyObject * PythonUpdateUI(PyObject * UNUSED(self), PyObject * UNUSED(args)) { #if defined(USE_GTK) if (fX) { while (gtk_events_pending()) gtk_main_iteration(); } #endif Py_INCREF(Py_None); return Py_None; } static PyObject * PythonEvalContext(PyObject * UNUSED(self), PyObject * args) { evalcontext ec; evalcontext *gec = &GetEvalChequer()->ec; int fCubeful = gec->fCubeful, nPlies = gec->nPlies, fDeterministic = gec->fDeterministic, fPrune = gec->fUsePrune; float rNoise = gec->rNoise; if (!PyArg_ParseTuple(args, "|iiiif", &fCubeful, &nPlies, &fDeterministic, &fPrune, &rNoise)) return NULL; ec.fCubeful = fCubeful ? 1 : 0; ec.nPlies = (nPlies < 8) ? nPlies : 7; ec.fDeterministic = fDeterministic ? 1 : 0; ec.fUsePrune = fPrune ? 1 : 0; ec.rNoise = rNoise; return EvalContextToPy(&ec); } static PyObject * PythonCommand(PyObject * UNUSED(self), PyObject * args) { char *pch; char *sz; psighandler sh; if (!PyArg_ParseTuple(args, "s:command", &pch)) return NULL; sz = g_strdup(pch); PortableSignal(SIGINT, HandleInterrupt, &sh, FALSE); HandleCommand(sz, acTop); /* this is what the cl interface does. Let's try that. */ while (fNextTurn) NextTurn(TRUE); /* before we had * if( ms.gs != GAME_NONE ) * PythonNextTurn(0, 0); * which always run NextTurn(TRUE), and that isn't right */ outputx(); g_free(sz); PortableSignalRestore(SIGINT, &sh); if (MT_SafeGet(&fInterrupt)) { raise(SIGINT); MT_SafeSet(&fInterrupt, FALSE); } PythonUpdateUI(NULL, Py_None); return Py_None; } static PyObject * PythonShow(PyObject * UNUSED(self), PyObject * args) { PyObject *p; char *pch; char *sz; if (!PyArg_ParseTuple(args, "s:argument", &pch)) return NULL; sz = g_strdup(pch); foutput_to_mem = TRUE; HandleCommand(sz, acShow); foutput_to_mem = FALSE; g_free(sz); while (szMemOutput[strlen(szMemOutput)-1] == '\n') szMemOutput[strlen(szMemOutput)-1] = 0; p = PyUnicode_FromString(szMemOutput); g_free(szMemOutput); szMemOutput = NULL; return p; } static PyObject * PythonSetGNUbgID(PyObject * UNUSED(self), PyObject * args) { char *pch; if (!PyArg_ParseTuple(args, "s:argument", &pch)) return NULL; SetGNUbgID(pch); Py_INCREF(Py_None); return Py_None; } static PyObject * PythonParseMove(PyObject * UNUSED(self), PyObject * args) { PyObject *pyMoves; char *pch; char *sz; int an[8]; int nummoves; int movesidx; if (!PyArg_ParseTuple(args, "s:moves", &pch)) return NULL; sz = g_strdup(pch); for (pch = sz; *pch; pch++) if (*pch == '-') *pch = '/'; nummoves = ParseMove(sz, an); g_free(sz); if (nummoves < 0) return NULL; if (!(pyMoves = PyTuple_New(nummoves))) return NULL; for (movesidx = 0; movesidx < nummoves; movesidx++) { PyObject *pyMove; PyObject *pyMoveL; PyObject *pyMoveR; int moveidx; pyMoveL = PyInt_FromLong(an[movesidx * 2]); pyMoveR = PyInt_FromLong(an[movesidx * 2 + 1]); if (!(pyMove = PyTuple_New(2))) return NULL; moveidx = 0; if (PyTuple_SetItem(pyMove, moveidx, pyMoveL) < 0) return NULL; if (PyTuple_SetItem(pyMove, moveidx + 1, pyMoveR) < 0) return NULL; if (PyTuple_SetItem(pyMoves, movesidx, pyMove) < 0) return NULL; } return pyMoves; } static PyObject * PythonBoard(PyObject * UNUSED(self), PyObject * args) { if (!PyArg_ParseTuple(args, ":board")) return NULL; if (ms.gs == GAME_NONE) { /* no board available */ Py_INCREF(Py_None); return Py_None; } return BoardToPy(msBoard()); } static PyObject * PythonLuckRating(PyObject * UNUSED(self), PyObject * args) { float r; if (!PyArg_ParseTuple(args, "f", &r)) return NULL; return PyInt_FromLong(getLuckRating(r)); } static PyObject * PythonClassifyPosition(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; TanBoard anBoard; bgvariation iVariant = ms.bgv; memcpy(anBoard, msBoard(), sizeof(TanBoard)); if (!PyArg_ParseTuple(args, "|Oi", &pyBoard, &iVariant)) return NULL; if (pyBoard && !PyToBoard(pyBoard, anBoard)) { PyErr_SetString(PyExc_StandardError, _("Invalid board as argument ")); return NULL; } if (iVariant >= NUM_VARIATIONS) { PyErr_SetString(PyExc_StandardError, _("Variation unknown ")); return NULL; } return PyInt_FromLong(ClassifyPosition((ConstTanBoard) anBoard, iVariant)); } static PyObject * PythonErrorRating(PyObject * UNUSED(self), PyObject * args) { float r; if (!PyArg_ParseTuple(args, "f", &r)) return NULL; return PyInt_FromLong(GetRating(r)); } /* Convert Move tuple to a string * A tuple can be expressed in two forms. * form returned by findbestmove ie. (21,18,11,5) * form returned by parsemove ie. ((21,18),(11,5)) * * returns a string representing the move tuple. */ static PyObject * PythonMoveTuple2String(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; PyObject *pyMove = NULL; char szMove[FORMATEDMOVESIZE]; signed int anMove[8]; TanBoard anBoard; memset(anBoard, 0, sizeof(TanBoard)); memset(anMove, -1, sizeof(anMove)); if (!PyArg_ParseTuple(args, "|OO", &pyMove, &pyBoard)) return NULL; if (!pyMove || !pyBoard) { PyErr_SetString(PyExc_TypeError, _("requires 2 arguments (MoveTuple, Board). " "(see gnubg.findbestmove() and gnubg.board() for examples)")); return NULL; } if (!PyToMove(pyMove, anMove)) { PyErr_SetString(PyExc_StandardError, _("Invalid move tuple as argument")); return NULL; } if (!PyToBoard(pyBoard, anBoard)) { PyErr_SetString(PyExc_StandardError, _("Invalid board as argument ")); return NULL; } szMove[0] = '\0'; FormatMove(szMove, (ConstTanBoard) anBoard, anMove); return PyUnicode_FromString(szMove); } static PyObject * PythonCalculateGammonPrice(PyObject * UNUSED(self), PyObject * pyCubeInfo) { cubeinfo ci = { 0 }; PyObject *gvObj = NULL; if (PyToCubeInfo(pyCubeInfo, &ci) < 0) return NULL; if (SetCubeInfo(&ci, ci.nCube, ci.fCubeOwner, ci.fMove, ci.nMatchTo, ci.anScore, ci.fCrawford, ci.fJacoby, ci.fBeavers, ci.bgv)) { PyErr_SetString(PyExc_StandardError, _("error in SetCubeInfo\n")); return NULL; } gvObj = Py_BuildValue("(ff)(ff)", ci.arGammonPrice[0], ci.arGammonPrice[2], ci.arGammonPrice[1], ci.arGammonPrice[3]); DictSetItemSteal(pyCubeInfo, "gammonprice", gvObj); Py_INCREF(pyCubeInfo); return (pyCubeInfo); } SIMD_STACKALIGN static PyObject * PythonEvaluate(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; PyObject *pyCubeInfo = NULL; PyObject *pyEvalContext = NULL; int fSaveShowProg; decisionData dd; TanBoard anBoard; cubeinfo ci; evalcontext ec; memcpy(&ec, &GetEvalChequer()->ec, sizeof(evalcontext)); memcpy(anBoard, msBoard(), sizeof(TanBoard)); GetMatchStateCubeInfo(&ci, &ms); if (!PyArg_ParseTuple(args, "|OOO", &pyBoard, &pyCubeInfo, &pyEvalContext)) return NULL; if (pyBoard && !PyToBoard(pyBoard, anBoard)) return NULL; if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; if (pyEvalContext && PyToEvalContext(pyEvalContext, &ec)) return NULL; dd.pboard = (ConstTanBoard) anBoard; dd.pci = &ci; dd.pec = &ec; fSaveShowProg = fShowProgress; fShowProgress = FALSE; if ((RunAsyncProcess((AsyncFun) asyncMoveDecisionE, &dd, _("Considering move...")) != 0) || MT_SafeGet(&fInterrupt)) { fShowProgress = fSaveShowProg; ResetInterrupt(); PyErr_SetString(PyExc_StandardError, _("interrupted/errno in asyncMoveDecisionE")); return NULL; } fShowProgress = fSaveShowProg; { PyObject *p = PyTuple_New(6); int k; for (k = 0; k < 6; ++k) { PyTuple_SET_ITEM(p, k, PyFloat_FromDouble(dd.aarOutput[0][k])); } return p; } } SIMD_STACKALIGN static PyObject * PythonEvaluateCubeful(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; PyObject *pyCubeInfo = NULL; PyObject *pyEvalContext = NULL; int fSaveShowProg; decisionData dd; TanBoard anBoard; float arCube[NUM_CUBEFUL_OUTPUTS]; cubeinfo ci; evalcontext ec; cubedecision cp; memcpy(&ec, &GetEvalCube()->ec, sizeof(evalcontext)); memcpy(anBoard, msBoard(), sizeof(TanBoard)); GetMatchStateCubeInfo(&ci, &ms); if (!PyArg_ParseTuple(args, "|OOO", &pyBoard, &pyCubeInfo, &pyEvalContext)) return NULL; if (pyBoard && !PyToBoard(pyBoard, anBoard)) return NULL; if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; if (pyEvalContext && PyToEvalContext(pyEvalContext, &ec)) return NULL; dd.pboard = (ConstTanBoard) anBoard; dd.pci = &ci; dd.pec = &ec; dd.pes = NULL; fSaveShowProg = fShowProgress; fShowProgress = FALSE; if ((RunAsyncProcess((AsyncFun) asyncCubeDecisionE, &dd, _("Considering cube decision...")) != 0) || MT_SafeGet(&fInterrupt)) { fShowProgress = fSaveShowProg; ResetInterrupt(); PyErr_SetString(PyExc_StandardError, _("interrupted/errno in asyncCubeDecisionE")); return NULL; } fShowProgress = fSaveShowProg; cp = FindCubeDecision(arCube, dd.aarOutput, &ci); { PyObject *p = PyTuple_New(NUM_CUBEFUL_OUTPUTS + 2); int k; for (k = 0; k < NUM_CUBEFUL_OUTPUTS; ++k) { PyTuple_SET_ITEM(p, k, PyFloat_FromDouble(arCube[k])); } PyTuple_SET_ITEM(p, NUM_CUBEFUL_OUTPUTS, PyInt_FromLong(cp)); PyTuple_SET_ITEM(p, NUM_CUBEFUL_OUTPUTS + 1, PyUnicode_FromString(GetCubeRecommendation(cp))); return p; } } SIMD_STACKALIGN static PyObject * PythonFindBestMove(PyObject * UNUSED(self), PyObject * args) { PyObject *pyDice = NULL; PyObject *pyBoard = NULL; PyObject *pyCubeInfo = NULL; PyObject *pyEvalContext = NULL; PyObject *pyMoveFilters = NULL; TanBoard anBoard; cubeinfo ci; evalcontext ec; movelist ml; findData fd; int fSaveShowProg; memcpy(&ec, &GetEvalChequer()->ec, sizeof(evalcontext)); memcpy(anBoard, msBoard(), sizeof(TanBoard)); memcpy(&fd.anDice, ms.anDice, sizeof(ms.anDice)); fd.aamf = *GetEvalMoveFilter(); GetMatchStateCubeInfo(&ci, &ms); if (!PyArg_ParseTuple(args, "|OOOOO", &pyBoard, &pyCubeInfo, &pyEvalContext, &pyDice, &pyMoveFilters)) return NULL; if (pyDice && !PyToDice(pyDice, fd.anDice)) return NULL; if (fd.anDice[0] == 0) { PyErr_SetString(PyExc_StandardError, _("What? No dice?\n")); return NULL; } if (pyBoard && !PyToBoard(pyBoard, anBoard)) return NULL; if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; if (pyEvalContext && PyToEvalContext(pyEvalContext, &ec)) return NULL; if (pyMoveFilters && PyToMoveFilters(pyMoveFilters, fd.aamf)) return NULL; fd.pml = &ml; fd.pboard = (ConstTanBoard) anBoard; fd.keyMove = NULL; fd.rThr = 0.0f; fd.pci = &ci; fd.pec = &ec; fSaveShowProg = fShowProgress; fShowProgress = FALSE; if ((RunAsyncProcess((AsyncFun) asyncFindBestMoves, &fd, _("Considering move...")) != 0) || MT_SafeGet(&fInterrupt)) { fShowProgress = fSaveShowProg; ResetInterrupt(); PyErr_SetString(PyExc_StandardError, _("interrupted/errno in asyncFindBestMoves")); return NULL; } fShowProgress = fSaveShowProg; { PyObject *p; if (fd.pml->cMoves == 0) p = PyTuple_New(0); else { Py_ssize_t k; p = PyTuple_New(8); for (k = 0; k < 8; ++k) { int nMove = ml.amMoves[0].anMove[k]; if ((nMove == -1) && ((k % 2) == 0)) break; PyTuple_SET_ITEM(p, k, PyInt_FromLong(nMove + 1)); } if (k < 8) _PyTuple_Resize(&p, k); } return p; } } static PyObject * METRow(float ar[MAXSCORE], const int n) { int i; PyObject *pyList; if (!(pyList = PyList_New(n))) return NULL; for (i = 0; i < n; ++i) { PyObject *pyf; if (!(pyf = PyFloat_FromDouble(ar[i]))) return NULL; if (PyList_SetItem(pyList, i, pyf) < 0) return NULL; } return pyList; } static PyObject * METPre(float aar[MAXSCORE][MAXSCORE], const int n) { int i; PyObject *pyList; if (!(pyList = PyList_New(n))) return NULL; for (i = 0; i < n; ++i) { PyObject *pyRow; if (!(pyRow = METRow(aar[i], n))) return NULL; if (PyList_SetItem(pyList, i, pyRow) < 0) return NULL; } return pyList; } static PyObject * PythonDiceRolls(PyObject * UNUSED(self), PyObject * args) { PyObject *pyDiceRolls; long n; unsigned int anDice[2]; int rollsidx; if (!PyArg_ParseTuple(args, "l:dicerolls", &n)) return NULL; if (n <= 0) { PyErr_SetString(PyExc_ValueError, _("number of rolls must be greater than 0")); return NULL; } if (!(pyDiceRolls = PyTuple_New(n))) return NULL; rollsidx = 0; while (n-- > 0) { PyObject *pyDiceRoll; PyObject *pyDie1; PyObject *pyDie2; int dieidx; RollDice(anDice, &rngCurrent, rngctxCurrent); pyDie1 = PyInt_FromLong(anDice[0]); pyDie2 = PyInt_FromLong(anDice[1]); if (!(pyDiceRoll = PyTuple_New(2))) return NULL; dieidx = 0; if (PyTuple_SetItem(pyDiceRoll, dieidx, pyDie1) < 0) return NULL; if (PyTuple_SetItem(pyDiceRoll, dieidx + 1, pyDie2) < 0) return NULL; if (PyTuple_SetItem(pyDiceRolls, rollsidx++, pyDiceRoll) < 0) return NULL; } return pyDiceRolls; } static PyObject * PythonMET(PyObject * UNUSED(self), PyObject * args) { int n = ms.nMatchTo ? ms.nMatchTo : MAXSCORE; int i; PyObject *pyMET; PyObject *pyList; if (!PyArg_ParseTuple(args, "|i:matchequiytable", &n)) return NULL; if (n < 0 || n > MAXSCORE) { PyErr_SetString(PyExc_ValueError, _("invalid matchlength")); return NULL; } if (!(pyMET = PyList_New(3))) return NULL; /* pre-Crawford scores */ if (!(pyList = METPre(aafMET, n))) return NULL; if (PyList_SetItem(pyMET, 0, pyList) < 0) return NULL; /* post-Crawford scores */ for (i = 0; i < 2; ++i) { if (!(pyList = METRow(aafMETPostCrawford[i], n))) return NULL; if (PyList_SetItem(pyMET, i + 1, pyList) < 0) return NULL; } return pyMET; } SIMD_STACKALIGN static PyObject * PythonEq2mwcStdErr(PyObject * UNUSED(self), PyObject * args) { PyObject *pyCubeInfo = NULL; float r = 0.0f; cubeinfo ci; if (!PyArg_ParseTuple(args, "|fO:eq2mwc_stderr", &r, &pyCubeInfo)) return NULL; GetMatchStateCubeInfo(&ci, &ms); if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; return PyFloat_FromDouble(se_eq2mwc(r, &ci)); } SIMD_STACKALIGN static PyObject * PythonEq2mwc(PyObject * UNUSED(self), PyObject * args) { PyObject *pyCubeInfo = NULL; float r = 0.0f; cubeinfo ci; if (!PyArg_ParseTuple(args, "|fO:eq2mwc", &r, &pyCubeInfo)) return NULL; GetMatchStateCubeInfo(&ci, &ms); if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; return PyFloat_FromDouble(eq2mwc(r, &ci)); } static PyObject * PythonMatchChecksum(PyObject * UNUSED(self), PyObject * UNUSED(args)) { return PyUnicode_FromString(GetMatchCheckSum()); } SIMD_STACKALIGN static PyObject * PythonMwc2eq(PyObject * UNUSED(self), PyObject * args) { PyObject *pyCubeInfo = NULL; float r = 0.0f; cubeinfo ci; if (!PyArg_ParseTuple(args, "|fO:mwc2eq", &r, &pyCubeInfo)) return NULL; GetMatchStateCubeInfo(&ci, &ms); if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; return PyFloat_FromDouble(mwc2eq(r, &ci)); } SIMD_STACKALIGN static PyObject * PythonMwc2eqStdErr(PyObject * UNUSED(self), PyObject * args) { PyObject *pyCubeInfo = NULL; float r = 0.0f; cubeinfo ci; if (!PyArg_ParseTuple(args, "|fO:mwc2eq_stderr", &r, &pyCubeInfo)) return NULL; GetMatchStateCubeInfo(&ci, &ms); if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; return PyFloat_FromDouble(se_mwc2eq(r, &ci)); } static PyObject * PythonPositionID(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; TanBoard anBoard; memcpy(anBoard, msBoard(), sizeof(TanBoard)); if (!PyArg_ParseTuple(args, "|O:positionid", &pyBoard)) return NULL; if (pyBoard && !PyToBoard(pyBoard, anBoard)) return NULL; return PyUnicode_FromString(PositionID((ConstTanBoard) anBoard)); } static PyObject * PythonGnubgID(PyObject * UNUSED(self), PyObject * args) { char *szGnubgID = NULL; char *szPosID = NULL; char *szMatchID = NULL; PyObject *pyRetVal = NULL; PyObject *pyCubeInfo = NULL; PyObject *pyPosInfo = NULL; PyObject *pyBoard = NULL; cubeinfo ci; posinfo pi; TanBoard anBoard; memcpy(anBoard, msBoard(), sizeof(TanBoard)); pi.anDice[0] = ms.anDice[0]; pi.anDice[1] = ms.anDice[1]; pi.fTurn = ms.fTurn; pi.fResigned = ms.fResigned; pi.fDoubled = ms.fDoubled; pi.gs = ms.gs; ci.fMove = ms.fMove; ci.fCubeOwner = ms.fCubeOwner; ci.fCrawford = ms.fCrawford; ci.nMatchTo = ms.nMatchTo; ci.anScore[0] = ms.anScore[0]; ci.anScore[1] = ms.anScore[1]; ci.nCube = ms.nCube; ci.fJacoby = ms.fJacoby; if (!PyArg_ParseTuple(args, "|OOO:gnubgid", &pyBoard, &pyCubeInfo, &pyPosInfo)) return NULL; if (pyBoard && (!pyPosInfo || !pyCubeInfo)) { PyErr_SetString(PyExc_TypeError, _("requires 0 or exactly 3 arguments (Board, Cube-Info dict, Pos-Info dict). " "(see gnubg.board(), gnubg.cubeinfo(), gnubg.posinfo() for examples)")); return NULL; } if (pyBoard && !PyToBoard(pyBoard, anBoard)) return NULL; if (!pyBoard && ms.gs == GAME_NONE) { PyErr_SetString(PyExc_ValueError, _("no current position available")); return NULL; } if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; if (pyPosInfo && PyToPosInfo(pyPosInfo, &pi)) return NULL; szPosID = g_strdup(PositionID((ConstTanBoard) anBoard)); szMatchID = g_strdup(MatchID((unsigned int *) pi.anDice, pi.fTurn, pi.fResigned, pi.fDoubled, ci.fMove, ci.fCubeOwner, ci.fCrawford, ci.nMatchTo, ci.anScore, ci.nCube, ci.fJacoby, pi.gs)); szGnubgID = g_strjoin(":", szPosID, szMatchID, NULL); pyRetVal = PyUnicode_FromString(szGnubgID); g_free(szPosID); g_free(szMatchID); g_free(szGnubgID); return (pyRetVal); } static PyObject * PythonMatchID(PyObject * UNUSED(self), PyObject * args) { PyObject *pyCubeInfo = NULL; PyObject *pyPosInfo = NULL; cubeinfo ci = { 1, 0, 0, 0, {0, 0}, FALSE, TRUE, FALSE, {1.0f, 1.0f, 1.0f, 1.0f}, VARIATION_STANDARD }; posinfo pi = { {0, 0}, 0, 0, 0, GAME_NONE }; if (!PyArg_ParseTuple(args, "|OO:matchid", &pyCubeInfo, &pyPosInfo)) return NULL; if (!pyCubeInfo && ms.gs == GAME_NONE) { PyErr_SetString(PyExc_ValueError, _("no current position available")); return NULL; } if (pyCubeInfo && PyToCubeInfo(pyCubeInfo, &ci)) return NULL; if (pyPosInfo && PyToPosInfo(pyPosInfo, &pi)) return NULL; if (pyCubeInfo && !pyPosInfo) { PyErr_SetString(PyExc_TypeError, _("a cube-info argument requires a pos-info dictionary as an argument " "(see gnubg.matchid() for an example)")); return NULL; } if (!pyCubeInfo) return PyUnicode_FromString(MatchIDFromMatchState(&ms)); else return PyUnicode_FromString(MatchID((unsigned int *) pi.anDice, pi.fTurn, pi.fResigned, pi.fDoubled, ci.fMove, ci.fCubeOwner, ci.fCrawford, ci.nMatchTo, ci.anScore, ci.nCube, ci.fJacoby, pi.gs)); } static PyObject * PythonPositionFromID(PyObject * UNUSED(self), PyObject * args) { char *sz = NULL; TanBoard anBoard; if (!PyArg_ParseTuple(args, "|s:positionfromid", &sz)) { return NULL; } if (sz) { if (!PositionFromID(anBoard, sz)) { PyErr_SetString(PyExc_ValueError, _("invalid positionid")); return NULL; } } else { memcpy(anBoard, msBoard(), sizeof(TanBoard)); } return BoardToPy((ConstTanBoard) anBoard); } static PyObject * PythonPositionKey(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; TanBoard anBoard; oldpositionkey key; memcpy(anBoard, msBoard(), sizeof(TanBoard)); if (!PyArg_ParseTuple(args, "|O!:positionkey", &PyList_Type, &pyBoard)) return NULL; if (pyBoard && !PyToBoard(pyBoard, anBoard)) return NULL; oldPositionKey((ConstTanBoard) anBoard, &key); { PyObject *a = PyTuple_New(10); int i; for (i = 0; i < 10; ++i) { PyTuple_SET_ITEM(a, i, PyInt_FromLong(key.auch[i])); } return a; } } static PyObject * PythonPositionFromKey(PyObject * UNUSED(self), PyObject * args) { TanBoard anBoard; int i; PyObject *pyKey = NULL; oldpositionkey key; if (!PyArg_ParseTuple(args, "|O!:positionfromkey", &PyList_Type, &pyKey)) return NULL; if (pyKey) { for (i = 0; i < 10; ++i) { PyObject *py; if (!(py = PyList_GetItem(pyKey, i))) return NULL; key.auch[i] = (unsigned char) PyInt_AsLong(py); } } else { for (i = 0; i < 10; ++i) key.auch[i] = 0; } oldPositionFromKey(anBoard, &key); return BoardToPy((ConstTanBoard) anBoard); } static PyObject * PythonPositionBearoff(PyObject * UNUSED(self), PyObject * args) { PyObject *pyBoard = NULL; int nChequers = 15; int nPoints = 6; TanBoard anBoard; memcpy(anBoard, msBoard(), sizeof(anBoard)); if (!PyArg_ParseTuple(args, "|Oii:positionbearoff", &pyBoard, &nPoints, &nChequers)) return NULL; if (pyBoard && !PyToBoard1(pyBoard, anBoard[0])) return NULL; return PyInt_FromLong(PositionBearoff(anBoard[0], nPoints, nChequers)); } static PyObject * PythonPositionFromBearoff(PyObject * UNUSED(self), PyObject * args) { TanBoard anBoard; int iPos = 0; int nChequers = 15; int nPoints = 6; int n; if (!PyArg_ParseTuple(args, "|iii:positionfrombearoff", &iPos, nChequers, &nPoints)) return NULL; if (nChequers < 1 || nChequers > 15 || nPoints < 1 || nPoints > 25) { PyErr_SetString(PyExc_ValueError, _("invalid number of chequers or points")); return NULL; } n = Combination(nChequers + nPoints, nPoints); if (iPos < 0 || iPos >= n) { PyErr_SetString(PyExc_ValueError, _("invalid position number")); return NULL; } memset(anBoard, 0, sizeof(anBoard)); PositionFromBearoff(anBoard[0], iPos, nPoints, nChequers); return Board1ToPy(anBoard[0]); } typedef struct { const evalcontext *ec; const rolloutcontext *rc; } PyMatchState; static PyObject * diffContext(const evalcontext * c, PyMatchState * ms) { const evalcontext *s = ms->ec; if (!s) { ms->ec = c; return NULL; } if (cmp_evalcontext(s, c) == 0) { return NULL; } { PyObject *context = PyDict_New(); if (c->fCubeful != s->fCubeful) { DictSetItemSteal(context, "cubeful", PyBool_FromLong(c->fCubeful)); } if (c->nPlies != s->nPlies) { DictSetItemSteal(context, "plies", PyInt_FromLong(c->nPlies)); } if (c->fDeterministic != s->fDeterministic) { DictSetItemSteal(context, "deterministic", PyBool_FromLong(c->fDeterministic)); } if (c->rNoise != s->rNoise) { DictSetItemSteal(context, "noise", PyFloat_FromDouble(c->rNoise)); } if (c->fUsePrune != s->fUsePrune) { DictSetItemSteal(context, "prune", PyBool_FromLong(c->fUsePrune)); } return context; } } static PyObject * diffRolloutContext(const rolloutcontext * c, PyMatchState * ms) { const rolloutcontext *s = ms->rc; if (!s) { ms->rc = c; return NULL; } { PyObject *context = PyDict_New(); if (c->fCubeful != s->fCubeful) { DictSetItemSteal(context, "cubeful", PyInt_FromLong(c->fCubeful)); } if (c->fVarRedn != s->fVarRedn) { DictSetItemSteal(context, "variance-reduction", PyInt_FromLong(c->fVarRedn)); } if (c->fInitial != s->fInitial) { DictSetItemSteal(context, "initial-position", PyInt_FromLong(c->fInitial)); } if (c->fRotate != s->fRotate) { DictSetItemSteal(context, "quasi-random-dice", PyInt_FromLong(c->fRotate)); } if (c->fLateEvals != s->fLateEvals) { DictSetItemSteal(context, "late-eval", PyInt_FromLong(c->fLateEvals)); } if (c->fDoTruncate != s->fDoTruncate) { DictSetItemSteal(context, "truncated-rollouts", PyInt_FromLong(c->fDoTruncate)); } if (c->fTruncBearoff2 != s->fTruncBearoff2) { DictSetItemSteal(context, "truncate-bearoff2", PyInt_FromLong(c->fTruncBearoff2)); } if (c->fTruncBearoffOS != s->fTruncBearoffOS) { DictSetItemSteal(context, "truncate-bearoffOS", PyInt_FromLong(c->fTruncBearoffOS)); } if (c->fStopOnSTD != s->fStopOnSTD) { DictSetItemSteal(context, "stop-on-std", PyInt_FromLong(c->fStopOnSTD)); } if (c->fStopMoveOnJsd != s->fStopMoveOnJsd) { DictSetItemSteal(context, "stop-on-jsd", PyInt_FromLong(c->fStopMoveOnJsd)); } if (c->nTruncate != s->nTruncate) { DictSetItemSteal(context, "n-truncation", PyInt_FromLong(c->nTruncate)); } if (c->nTrials != s->nTrials) { DictSetItemSteal(context, "trials", PyInt_FromLong(c->nTrials)); } if (c->nSeed != s->nSeed) { DictSetItemSteal(context, "seed", PyInt_FromLong(c->nSeed)); } if (c->nLate != s->nLate) { DictSetItemSteal(context, "late-on-move-n", PyInt_FromLong(c->nLate)); } if (c->nMinimumGames != s->nMinimumGames) { DictSetItemSteal(context, "minimum-games", PyInt_FromLong(c->nMinimumGames)); } if (c->rStdLimit != s->rStdLimit) { DictSetItemSteal(context, "std-limit", PyFloat_FromDouble(c->rStdLimit)); } if (c->nMinimumJsdGames != s->nMinimumJsdGames) { DictSetItemSteal(context, "minimum-jsd-games", PyInt_FromLong(c->nMinimumJsdGames)); } if (c->rJsdLimit != s->rJsdLimit) { DictSetItemSteal(context, "jsd-limit", PyFloat_FromDouble(c->rJsdLimit)); } if (PyDict_Size(context) == 0) { Py_DECREF(context); context = NULL; } return context; } } static PyObject * RolloutContextToPy(const rolloutcontext * rc) { PyObject *dict = Py_BuildValue("{s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i," "s:i,s:i,s:i,s:i,s:i,s:i,s:f,s:f}", "cubeful", rc->fCubeful, "variance-reduction", rc->fVarRedn, "initial-position", rc->fInitial, "quasi-random-dice", rc->fRotate, "late-eval", rc->fLateEvals, "truncated-rollouts", rc->fDoTruncate, "n-truncation", rc->nTruncate, "truncate-bearoff2", rc->fTruncBearoff2, "truncate-bearoffOS", rc->fTruncBearoffOS, "stop-on-std", rc->fStopOnSTD, "trials", rc->nTrials, "seed", rc->nSeed, "minimum-games", rc->nMinimumGames, "stop-on-jsd", rc->fStopOnJsd, "late-on-move-n", rc->nLate, "minimum-jsd-games", rc->nMinimumJsdGames, "std-limit", rc->rStdLimit, "jsd-limit", rc->rJsdLimit); return dict; } static PyObject * PythonRolloutContext(PyObject * UNUSED(self), PyObject * args) { rolloutcontext *grc = &rcRollout; rolloutcontext rc; int fCubeful = grc->fCubeful, fVarRedn = grc->fVarRedn, fInitial = grc->fInitial; int fRotate = grc->fRotate, fLateEvals = grc->fLateEvals, fDoTruncate = grc->fDoTruncate; int nTruncate = grc->nTruncate, fTruncBearoff2 = grc->fTruncBearoff2; int fTruncBearoffOS = grc->fTruncBearoffOS, fStopOnSTD = grc->fStopOnSTD; int nTrials = grc->nTrials, nSeed = (int) grc->nSeed, nMinimumGames = grc->nMinimumGames; int fStopOnJsd = grc->fStopOnJsd, nLate = grc->nLate, nMinimumJsdGames = grc->nMinimumJsdGames; float rStdLimit = (float) grc->rStdLimit, rJsdLimit = (float) grc->rJsdLimit; if (!PyArg_ParseTuple(args, "|iiiiiiiiiiiiiiiiff", &fCubeful, &fVarRedn, &fInitial, &fRotate, &fLateEvals, &fDoTruncate, &nTruncate, &fTruncBearoff2, &fTruncBearoffOS, &fStopOnSTD, &nTrials, &nSeed, &nMinimumGames, &fStopOnJsd, &nLate, &nMinimumJsdGames, &rStdLimit, &rJsdLimit)) return NULL; rc.fCubeful = fCubeful ? 1 : 0; rc.fVarRedn = fVarRedn ? 1 : 0; rc.fInitial = fInitial ? 1 : 0; rc.fRotate = fRotate ? 1 : 0; rc.fLateEvals = fLateEvals ? 1 : 0; rc.fDoTruncate = fDoTruncate ? 1 : 0; rc.nTruncate = (unsigned short) nTruncate; rc.fTruncBearoff2 = fTruncBearoff2 ? 1 : 0; rc.fTruncBearoffOS = fTruncBearoffOS ? 1 : 0; rc.fStopOnSTD = fStopOnSTD ? 1 : 0; rc.nTrials = nTrials; rc.nSeed = nSeed; rc.nMinimumGames = nMinimumGames; rc.fStopOnJsd = fStopOnJsd ? 1 : 0; rc.nLate = (unsigned short) nLate; rc.nMinimumJsdGames = nMinimumJsdGames; rc.rStdLimit = rStdLimit; rc.rJsdLimit = rJsdLimit; return RolloutContextToPy(&rc); } static PyObject * PyMove(const int move[8]) { /* Lazy. Allocate full move, resize later */ PyObject *moveTuple = PyTuple_New(4); int i; for (i = 0; i < 4; ++i) { if (move[2 * i] < 0) { break; } { PyObject *c = Py_BuildValue("(ii)", move[2 * i] + 1, move[2 * i + 1] + 1); PyTuple_SET_ITEM(moveTuple, i, c); } } if (i < 4) { int s = _PyTuple_Resize(&moveTuple, i); g_assert(s != -1); (void) s; } return moveTuple; } static const char * luckString(lucktype const lt, int const ignoreNone) { switch (lt) { case LUCK_VERYBAD: return "verybad"; case LUCK_BAD: return "bad"; case LUCK_NONE: return ignoreNone ? 0 : "unmarked"; case LUCK_GOOD: return "good"; case LUCK_VERYGOOD: return "verygood"; } g_assert_not_reached(); return NULL; } static const char * skillString(skilltype const st, int const ignoreNone) { switch (st) { case SKILL_VERYBAD: return "very bad"; case SKILL_BAD: return "bad"; case SKILL_DOUBTFUL: return "doubtful"; case SKILL_NONE: return ignoreNone ? 0 : "unmarked"; } g_assert_not_reached(); return NULL; } static void addSkill(PyObject * dict, skilltype const st, const char *name) { if (dict) { const char *s = skillString(st, 1); if (s) { if (!name) name = "skill"; DictSetItemSteal(dict, name, PyUnicode_FromString(s)); } } } static void addLuck(PyObject * dict, float const rLuck, lucktype const lt) { if (dict) { const char *l = luckString(lt, 1); if (rLuck != ERR_VAL) { DictSetItemSteal(dict, "luck-value", PyFloat_FromDouble(rLuck)); } if (l) { DictSetItemSteal(dict, "luck", PyUnicode_FromString(l)); } } } SIMD_STACKALIGN static PyObject * PyMoveAnalysis(const movelist * pml, PyMatchState * ms) { unsigned int i; unsigned int n = 0; for (i = 0; i < pml->cMoves; i++) { const move *pmi = &pml->amMoves[i]; switch (pmi->esMove.et) { case EVAL_EVAL: case EVAL_ROLLOUT: { ++n; break; } default: break; } } if (!n) { return NULL; } { PyObject *l = PyTuple_New(n); n = 0; for (i = 0; i < pml->cMoves; i++) { const move *pmi = &pml->amMoves[i]; PyObject *v = 0; switch (pmi->esMove.et) { case EVAL_EVAL: { PyObject *m = PyMove(pmi->anMove); v = Py_BuildValue("{s:s,s:O,s:(fffff),s:f}", "type", "eval", "move", m, "probs", pmi->arEvalMove[0], pmi->arEvalMove[1], pmi->arEvalMove[2], pmi->arEvalMove[3], pmi->arEvalMove[4], "score", pmi->rScore); Py_DECREF(m); { PyObject *c = diffContext(&pmi->esMove.ec, ms); if (c) { DictSetItemSteal(v, "evalcontext", c); } } break; } case EVAL_ROLLOUT: { PyObject *m = PyMove(pmi->anMove); const evalsetup *pes = &pmi->esMove; const float *p = pmi->arEvalMove; const float *s = pmi->arEvalStdDev; v = Py_BuildValue("{s:s,s:O,s:i,s:(fffff),s:f,s:f" ",s:(fffff),s:f,s:f}", "type", "rollout", "move", m, "trials", pes->rc.nGamesDone, "probs", p[0], p[1], p[2], p[3], p[4], "match-eq", p[OUTPUT_EQUITY], "cubeful-eq", p[OUTPUT_CUBEFUL_EQUITY], "probs-std", s[0], s[1], s[2], s[3], s[4], "match-eq-std", s[OUTPUT_EQUITY], "cubeful-eq-std", s[OUTPUT_CUBEFUL_EQUITY]); Py_DECREF(m); { PyObject *c = diffRolloutContext(&pes->rc, ms); if (c) { DictSetItemSteal(v, "rollout-context", c); } } break; } case EVAL_NONE: break; default: g_assert(0); } if (v) { PyTuple_SET_ITEM(l, n++, v); } } return l; } } SIMD_STACKALIGN static PyObject * PyDoubleAnalysis(const evalsetup * pes, float aarOutput[][NUM_ROLLOUT_OUTPUTS], float aarStdDev[][NUM_ROLLOUT_OUTPUTS], PyMatchState * ms, const int verbose) { PyObject *dict = 0; switch (pes->et) { case EVAL_EVAL: { const float *p = aarOutput[0]; dict = Py_BuildValue("{s:s,s:(fffff),s:f,s:f}", "type", "eval", "probs", p[0], p[1], p[2], p[3], p[4], "nd-cubeful-eq", p[OUTPUT_CUBEFUL_EQUITY], "dt-cubeful-eq", aarOutput[1][OUTPUT_CUBEFUL_EQUITY]); if (verbose) { DictSetItemSteal(dict, "nd-match-eq", PyFloat_FromDouble(p[OUTPUT_EQUITY])); DictSetItemSteal(dict, "dt-match-eq", PyFloat_FromDouble(aarOutput[1][OUTPUT_EQUITY])); } { PyObject *c = diffContext(&pes->ec, ms); if (c) { DictSetItemSteal(dict, "eval-context", c); } } break; } case EVAL_ROLLOUT: { const float *nd = aarOutput[0]; const float *nds = aarStdDev[0]; const float *dt = aarOutput[1]; const float *dts = aarStdDev[1]; dict = Py_BuildValue("{s:s,s:i," "s:(fffff),s:f,s:f," "s:(fffff),s:f,s:f," "s:(fffff),s:f,s:f," "s:(fffff),s:f,s:f}", "type", "rollout", "trials", pes->rc.nGamesDone, "nd-probs", nd[0], nd[1], nd[2], nd[3], nd[4], "nd-match-eq", nd[OUTPUT_EQUITY], "nd-cubeful-eq", nd[OUTPUT_CUBEFUL_EQUITY], "nd-probs-std", nds[0], nds[1], nds[2], nds[3], nds[4], "nd-match-eq-std", nds[OUTPUT_EQUITY], "nd-cubeful-eq-std", nds[OUTPUT_CUBEFUL_EQUITY], "dt-probs", dt[0], dt[1], dt[2], dt[3], dt[4], "dt-match-eq", dt[OUTPUT_EQUITY], "dt-cubeful-eq", dt[OUTPUT_CUBEFUL_EQUITY], "dt-probs-std", dts[0], dts[1], dts[2], dts[3], dts[4], "dt-match-eq-std", dts[OUTPUT_EQUITY], "dt-cubeful-eq-std", dts[OUTPUT_CUBEFUL_EQUITY]); { PyObject *c = diffRolloutContext(&pes->rc, ms); if (c) { DictSetItemSteal(dict, "rollout-context", c); } } break; } default: g_assert(0); } return dict; } static PyObject * PyGameStats(const statcontext * sc, const int fIsMatch, const int nMatchTo) { PyObject *p[2]; float aaaar[3][2][2][2]; if (!(sc->fMoves || sc->fCube || sc->fDice)) { return NULL; } getMWCFromError(sc, aaaar); p[0] = PyDict_New(); p[1] = PyDict_New(); if (sc->fMoves) { int side; for (side = 0; side < 2; ++side) { PyObject *d = Py_BuildValue("{s:i,s:i,s:f,s:f}", "unforced-moves", sc->anUnforcedMoves[side], "total-moves", sc->anTotalMoves[side], "error-skill", sc->arErrorCheckerplay[side][0], "error-cost", sc->arErrorCheckerplay[side][1]); PyObject *m = PyDict_New(); { skilltype st; for (st = SKILL_VERYBAD; st <= SKILL_NONE; st++) { DictSetItemSteal(m, skillString(st, 0), PyInt_FromLong(sc->anMoves[side][st])); } } DictSetItemSteal(d, "marked", m); DictSetItemSteal(p[side], "moves", d); } } if (sc->fCube) { int side; for (side = 0; side < 2; ++side) { float errorskill = aaaar[CUBEDECISION][PERMOVE][side][NORMALISED]; float errorcost = aaaar[CUBEDECISION][PERMOVE][side][UNNORMALISED]; PyObject *d = Py_BuildValue("{s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i,s:i" "s:f,s:f,s:f,s:f,s:f,s:f,s:f,s:f,s:f,s:f,s:f,s:f," "s:f,s:f}", "total-cube", sc->anTotalCube[side], "close-cube", sc->anCloseCube[side], "n-doubles", sc->anDouble[side], "n-takes", sc->anTake[side], "n-drops", sc->anPass[side], "missed-double-below-cp", sc->anCubeMissedDoubleDP[side], "missed-double-above-cp", sc->anCubeMissedDoubleTG[side], "wrong-double-below-dp", sc->anCubeWrongDoubleDP[side], "wrong-double-above-tg", sc->anCubeWrongDoubleTG[side], "wrong-take", sc->anCubeWrongTake[side], "wrong-drop", sc->anCubeWrongPass[side], "err-missed-double-below-cp-skill", sc->arErrorMissedDoubleDP[side][0], "err-missed-double-below-cp-cost", sc->arErrorMissedDoubleDP[side][1], "err-missed-double-above-cp-skill", sc->arErrorMissedDoubleTG[side][0], "err-missed-double-above-cp-cost", sc->arErrorMissedDoubleTG[side][1], "err-wrong-double-below-dp-skill", sc->arErrorWrongDoubleDP[side][0], "err-wrong-double-below-dp-cost", sc->arErrorWrongDoubleDP[side][1], "err-wrong-double-above-tg-skill", sc->arErrorWrongDoubleTG[side][0], "err-wrong-double-above-tg-cost", sc->arErrorWrongDoubleTG[side][1], "err-wrong-take-skill", sc->arErrorWrongTake[side][0], "err-wrong-take-cost", sc->arErrorWrongTake[side][1], "err-wrong-drop-skill", sc->arErrorWrongPass[side][0], "err-wrong-drop-cost", sc->arErrorWrongPass[side][1], "error-skill", errorskill, "error-cost", errorcost); DictSetItemSteal(p[side], "cube", d); } } if (sc->fDice) { int side; for (side = 0; side < 2; ++side) { PyObject *d = Py_BuildValue("{s:f,s:f,s:f,s:f}", "luck", sc->arLuck[side][0], "luck-cost", sc->arLuck[side][1], "actual-result", sc->arActualResult[side], "luck-adjusted-result", sc->arLuckAdj[side]); PyObject *m = PyDict_New(); { lucktype lt; for (lt = LUCK_VERYBAD; lt <= LUCK_VERYGOOD; lt++) { DictSetItemSteal(m, luckString(lt, 0), PyInt_FromLong(sc->anLuck[0][lt])); } } /* luck based fibs rating difference */ if (nMatchTo && fIsMatch) { float r = 0.5f + sc->arActualResult[side] - sc->arLuck[side][1] + sc->arLuck[!side][1]; if (r > 0.0f && r < 1.0f) DictSetItemSteal(d, "fibs-rating-difference", PyFloat_FromDouble(relativeFibsRating(r, nMatchTo))); } DictSetItemSteal(d, "marked-rolls", m); DictSetItemSteal(p[side], "dice", d); } } /* advantage */ if (sc->fDice && fIsMatch && !nMatchTo && sc->nGames > 1) { int side; for (side = 0; side < 2; ++side) { PyObject *d = Py_BuildValue("{s:f,s:f,s:f,s:f}", "actual", sc->arActualResult[side] / (float) sc->nGames, "actual-ci", 1.95996f * sqrtf(sc->arVarianceActual[side] / (float) sc->nGames), "luck-adjusted", sc->arLuckAdj[side] / (float) sc->nGames, "luck-adjusted-ci", 1.95996f * sqrtf(sc->arVarianceLuckAdj[side] / (float) sc->nGames)); DictSetItemSteal(p[side], "ppg-advantage", d); } } /* error based fibs ratings */ if ((sc->fCube || sc->fMoves) && fIsMatch && nMatchTo) { int side; for (side = 0; side < 2; ++side) { PyObject *d = PyDict_New(); DictSetItemSteal(d, "total", PyFloat_FromDouble(absoluteFibsRating(aaaar[CHEQUERPLAY][PERMOVE][side][NORMALISED], aaaar[CUBEDECISION][PERMOVE][side][NORMALISED], nMatchTo, rRatingOffset))); if (sc->anUnforcedMoves[side]) { DictSetItemSteal(d, "chequer", PyFloat_FromDouble(absoluteFibsRatingChequer (aaaar[CHEQUERPLAY][PERMOVE][side][NORMALISED], nMatchTo))); } if (sc->anCloseCube[side]) { DictSetItemSteal(d, "cube", PyFloat_FromDouble(absoluteFibsRatingCube (aaaar[CUBEDECISION][PERMOVE][side][NORMALISED], nMatchTo))); } DictSetItemSteal(p[side], "error-based-fibs-rating", d); } } /* */ { PyObject *d = PyDict_New(); DictSetItemSteal(d, "X", p[0]); DictSetItemSteal(d, "O", p[1]); return d; } } /* plGame: Game as a list of records. * doAnalysis: if true, add analysis info. * verbose: if true, add derived analysis data. * scMatch: if non-0, add game & match statistics. */ static PyObject * PythonGame(const listOLD * plGame, int const doAnalysis, int const verbose, statcontext * scMatch, int const includeBoards, PyMatchState * ms) { const listOLD *pl = plGame->plNext; const moverecord *pmr = pl->p; const xmovegameinfo *g = &pmr->g; PyObject *gameDict = PyDict_New(); PyObject *gameInfoDict = PyDict_New(); g_assert(pmr->mt == MOVE_GAMEINFO); if (!(gameDict && gameInfoDict)) { PyErr_SetString(PyExc_MemoryError, ""); return NULL; } DictSetItemSteal(gameInfoDict, "score-X", PyInt_FromLong(g->anScore[0])); DictSetItemSteal(gameInfoDict, "score-O", PyInt_FromLong(g->anScore[1])); /* Set Crawford info only if it is relevant, i.e. if any side is 1 point away * from winning the match */ if (g->anScore[0] + 1 == g->nMatch || g->anScore[1] + 1 == g->nMatch) { DictSetItemSteal(gameInfoDict, "crawford", PyBool_FromLong(g->fCrawfordGame)); } if (g->fWinner >= 0) { DictSetItemSteal(gameInfoDict, "winner", PyUnicode_FromString(g->fWinner ? "O" : "X")); DictSetItemSteal(gameInfoDict, "points-won", PyInt_FromLong(g->nPoints)); DictSetItemSteal(gameInfoDict, "resigned", PyBool_FromLong(g->fResigned)); } else { Py_INCREF(Py_None); DictSetItemSteal(gameInfoDict, "winner", Py_None); } if (g->nAutoDoubles) { DictSetItemSteal(gameInfoDict, "initial-cube", PyInt_FromLong(1 << g->nAutoDoubles)); } DictSetItemSteal(gameDict, "info", gameInfoDict); if (scMatch) { updateStatisticsGame(plGame); AddStatcontext(&g->sc, scMatch); { PyObject *s = PyGameStats(&g->sc, FALSE, g->nMatch); if (s) { DictSetItemSteal(gameDict, "stats", s); } } } { int nRecords = 0; TanBoard anBoard; PyObject *gameTuple; { listOLD *t; for (t = pl->plNext; t != plGame; t = t->plNext) { ++nRecords; } } gameTuple = PyTuple_New(nRecords); nRecords = 0; if (includeBoards) { InitBoard(anBoard, g->bgv); } for (pl = pl->plNext; pl != plGame; pl = pl->plNext) { const char *action = 0; int player = -1; long points = -1; PyObject *recordDict = PyDict_New(); PyObject *analysis = doAnalysis ? PyDict_New() : NULL; pmr = pl->p; switch (pmr->mt) { case MOVE_NORMAL: { action = "move"; player = pmr->fPlayer; { PyObject *dice = Py_BuildValue("(ii)", pmr->anDice[0], pmr->anDice[1]); DictSetItemSteal(recordDict, "dice", dice); } DictSetItemSteal(recordDict, "move", PyMove(pmr->n.anMove)); if (includeBoards) { DictSetItemSteal(recordDict, "board", PyUnicode_FromString(PositionID((ConstTanBoard) anBoard))); ApplyMove(anBoard, pmr->n.anMove, 0); SwapSides(anBoard); } if (analysis) { if (pmr->CubeDecPtr->esDouble.et != EVAL_NONE) { PyObject *d = PyDoubleAnalysis(&pmr->CubeDecPtr->esDouble, pmr->CubeDecPtr->aarOutput, pmr->CubeDecPtr->aarStdDev, ms, verbose); { int s = PyDict_Merge(analysis, d, 1); g_assert(s != -1); (void) s; } Py_DECREF(d); } if (pmr->ml.cMoves) { PyObject *a = PyMoveAnalysis(&pmr->ml, ms); if (a) { DictSetItemSteal(analysis, "moves", a); DictSetItemSteal(analysis, "imove", PyInt_FromLong(pmr->n.iMove)); } } addLuck(analysis, pmr->rLuck, pmr->lt); addSkill(analysis, pmr->n.stMove, 0); addSkill(analysis, pmr->stCube, "cube-skill"); } break; } case MOVE_DOUBLE: { action = "double"; player = pmr->fPlayer; if (includeBoards) { DictSetItemSteal(recordDict, "board", PyUnicode_FromString(PositionID((ConstTanBoard) anBoard))); } if (analysis) { cubedecisiondata *c = pmr->CubeDecPtr; if (c->esDouble.et != EVAL_NONE) { PyObject *d = PyDoubleAnalysis(&c->esDouble, c->aarOutput, c->aarStdDev, ms, verbose); { int s = PyDict_Merge(analysis, d, 1); g_assert(s != -1); (void) s; } Py_DECREF(d); } addSkill(analysis, pmr->stCube, 0); } break; } case MOVE_TAKE: { action = "take"; player = pmr->fPlayer; /* use nAnimals to point to double analysis ? */ addSkill(analysis, pmr->stCube, 0); break; } case MOVE_DROP: { action = "drop"; player = pmr->fPlayer; addSkill(analysis, pmr->stCube, 0); break; } case MOVE_RESIGN: { action = "resign"; player = pmr->fPlayer; points = pmr->r.nResigned; if (points < 1) points = 1; else if (points > 3) points = 3; break; } case MOVE_SETBOARD: { PyObject *id = PyUnicode_FromString(PositionIDFromKey(&pmr->sb.key)); action = "set"; DictSetItemSteal(recordDict, "board", id); if (includeBoards) { /* (FIXME) what about side? */ /* JTH: the board is always stored as if player 0 was on roll */ PositionFromKey(anBoard, &pmr->sb.key); } break; } case MOVE_SETDICE: { PyObject *dice; player = pmr->fPlayer; action = "set"; dice = Py_BuildValue("(ii)", pmr->anDice[0], pmr->anDice[1]); DictSetItemSteal(recordDict, "dice", dice); addLuck(analysis, pmr->rLuck, pmr->lt); break; } case MOVE_SETCUBEVAL: { action = "set"; DictSetItemSteal(recordDict, "cube", PyInt_FromLong(pmr->scv.nCube)); break; } case MOVE_SETCUBEPOS: { const char *s[] = { "centered", "X", "O" }; const char *o = s[pmr->scp.fCubeOwner + 1]; action = "set"; DictSetItemSteal(recordDict, "cube-owner", PyUnicode_FromString(o)); break; } default: { g_assert_not_reached(); } } if (action) { DictSetItemSteal(recordDict, "action", PyUnicode_FromString(action)); } if (player != -1) { DictSetItemSteal(recordDict, "player", PyUnicode_FromString(player ? "O" : "X")); } if (points != -1) { DictSetItemSteal(recordDict, "points", PyInt_FromLong(points)); } if (analysis) { if (PyDict_Size(analysis) > 0) { DictSetItemSteal(recordDict, "analysis", analysis); } else { Py_DECREF(analysis); } } if (pmr->sz) { DictSetItemSteal(recordDict, "comment", PyUnicode_FromString(pmr->sz)); } PyTuple_SET_ITEM(gameTuple, nRecords, recordDict); ++nRecords; } /* for (pl ...) */ DictSetItemSteal(gameDict, "game", gameTuple); } return gameDict; } static void addProperty(PyObject * dict, const char *name, const char *val) { if (!val) { return; } DictSetItemSteal(dict, name, PyUnicode_FromString(val)); } static PyObject * PythonMatch(PyObject * UNUSED(self), PyObject * args, PyObject * keywds) { /* take match info from first game */ const listOLD *firstGame = lMatch.plNext->p; const moverecord *pmr; const xmovegameinfo *g; int includeAnalysis = 1; int verboseAnalysis = 0; int statistics = 0; int boards = 1; PyObject *matchDict; PyObject *matchInfoDict; PyMatchState s; static char *kwlist[] = { "analysis", "boards", "statistics", "verbose", 0 }; if (!firstGame) { Py_INCREF(Py_None); return Py_None; } pmr = firstGame->plNext->p; g_assert(pmr->mt == MOVE_GAMEINFO); g = &pmr->g; if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iiii", kwlist, &includeAnalysis, &boards, &statistics, &verboseAnalysis)) return NULL; matchDict = PyDict_New(); matchInfoDict = PyDict_New(); if (!matchDict && !matchInfoDict) { PyErr_SetString(PyExc_MemoryError, ""); return NULL; } if (g->i != 0) { PyErr_SetString(PyExc_StandardError, _("First game missing from match")); return NULL; } /* W,X,0 * B,O,1 */ { int side; for (side = 0; side < 2; ++side) { PyObject *d = PyDict_New(); addProperty(d, "rating", mi.pchRating[side]); addProperty(d, "name", ap[side].szName); DictSetItemSteal(matchInfoDict, side == 0 ? "X" : "O", d); } } DictSetItemSteal(matchInfoDict, "match-length", PyInt_FromLong(g->nMatch)); if (mi.nYear) { PyObject *date = Py_BuildValue("(iii)", mi.nDay, mi.nMonth, mi.nYear); DictSetItemSteal(matchInfoDict, "date", date); } addProperty(matchInfoDict, "event", mi.pchEvent); addProperty(matchInfoDict, "round", mi.pchRound); addProperty(matchInfoDict, "place", mi.pchPlace); addProperty(matchInfoDict, "annotator", mi.pchAnnotator); addProperty(matchInfoDict, "comment", mi.pchComment); { /* Work out the result (-1,0,1) - (p0 win, unfinished, p1 win) */ int result = 0; int anFinalScore[2]; if (getFinalScore(anFinalScore)) { if (anFinalScore[0] > g->nMatch) result = -1; else if (anFinalScore[1] > g->nMatch) result = 1; } DictSetItemSteal(matchInfoDict, "result", PyInt_FromLong(result)); } { const char *v[] = { "Standard", "Nackgammon", "Hypergammon1", "Hypergammon2", "Hypergammon3" }; addProperty(matchInfoDict, "variation", v[g->bgv]); } { unsigned int n = !g->fCubeUse + !!g->fCrawford + !!g->fJacoby; if (n) { PyObject *rules = PyTuple_New(n); n = 0; if (!g->fCubeUse) { PyTuple_SET_ITEM(rules, n++, PyUnicode_FromString("NoCube")); } if (g->fCrawford) { PyTuple_SET_ITEM(rules, n++, PyUnicode_FromString("Crawford")); } if (g->fJacoby) { PyTuple_SET_ITEM(rules, n, PyUnicode_FromString("Jacoby")); } DictSetItemSteal(matchInfoDict, "rules", rules); } } DictSetItemSteal(matchDict, "match-info", matchInfoDict); s.ec = 0; s.rc = 0; { int nGames = 0; const listOLD *pl; PyObject *matchTuple; statcontext scm; for (pl = lMatch.plNext; pl != &lMatch; pl = pl->plNext) { ++nGames; } if (statistics) { IniStatcontext(&scm); } matchTuple = PyTuple_New(nGames); nGames = 0; for (pl = lMatch.plNext; pl != &lMatch; pl = pl->plNext) { PyObject *pg = PythonGame(pl->p, includeAnalysis, verboseAnalysis, statistics ? &scm : 0, boards, &s); if (!pg) { /* Memory leaked. out of memory anyway */ return NULL; } PyTuple_SET_ITEM(matchTuple, nGames, pg); ++nGames; } DictSetItemSteal(matchDict, "games", matchTuple); if (statistics) { PyObject *pgs = PyGameStats(&scm, TRUE, g->nMatch); if (pgs) { DictSetItemSteal(matchDict, "stats", pgs); } } } if (s.ec) { PyObject *e = EvalContextToPy(s.ec); DictSetItemSteal(matchInfoDict, "default-eval-context", e); } if (s.rc) { PyObject *e = RolloutContextToPy(s.rc); DictSetItemSteal(matchInfoDict, "default-rollout-context", e); /* No need for that, I think */ /* DictSetItemSteal(matchInfoDict, "sgf-rollout-version", */ /* PyInt_FromLong(SGF_ROLLOUT_VER)); */ } return matchDict; } static PyObject * PythonNavigate(PyObject * UNUSED(self), PyObject * args, PyObject * keywds) { int nextRecord = INT_MIN; int nextGame = INT_MIN; PyObject *r; static char *kwlist[] = { "next", "game", 0 }; if (!lMatch.plNext) { PyErr_SetString(PyExc_RuntimeError, "no active match"); return NULL; } if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ii", kwlist, &nextRecord, &nextGame)) return NULL; r = NULL; if (nextRecord == INT_MIN && nextGame == INT_MIN) { /* no args, go to start */ if (lMatch.plNext->p) /* Match not empty */ ChangeGame(lMatch.plNext->p); } else { int gamesDif = 0; int recordsDiff = 0; if (nextRecord != INT_MIN && nextRecord < 0) { PyErr_SetString(PyExc_ValueError, "negative next record"); return NULL; } if (nextGame != INT_MIN && nextGame != 0) { listOLD *pl = lMatch.plNext; for (; pl->p != plGame && pl != &lMatch; pl = pl->plNext); g_assert(pl->p == plGame); { int n = nextGame; if (n > 0) { while (n > 0 && pl->plNext->p) { pl = pl->plNext; --n; } } else { /* n < 0 since n = nextGame != 0 */ while (n < 0 && pl->plPrev->p) { pl = pl->plPrev; ++n; } } ChangeGame(pl->p); gamesDif = abs(nextGame) - n; } } if (nextRecord != INT_MIN) { recordsDiff = nextRecord - InternalCommandNext(0, 0, nextRecord); } /* (HACK) * If normal move, set global dice for export and such. */ if (plLastMove->plNext && plLastMove->plNext->p) { const moverecord *pmr = (const moverecord *) (plLastMove->plNext->p); if (pmr->mt == MOVE_NORMAL) { memcpy(ms.anDice, pmr->anDice, sizeof(ms.anDice)); } } if (recordsDiff || gamesDif) { r = Py_BuildValue("(ii)", recordsDiff, gamesDif); } } /* (HACK) */ if (ms.gs == GAME_NONE) { ms.gs = GAME_PLAYING; } if (!r) { Py_INCREF(Py_None); r = Py_None; } else { PyObject *tmpobj; Py_INCREF(Py_None); tmpobj = PythonUpdateUI(NULL, Py_None); Py_DECREF(tmpobj); Py_DECREF(Py_None); } return r; } static PyMethodDef gnubgMethods[] = { {"board", PythonBoard, METH_VARARGS, "Get the current board\n" " arguments: none\n" " returns: tuple of two lists of 25 ints:\n" " pieces on points 1..24 and the bar"} , {"calcgammonprice", (PyCFunction) PythonCalculateGammonPrice, METH_O, "return cube-info with updated gammon prices\n" " arguments: [cube-info dictionary]\n" " cube-info: see 'cfevaluate'\n" " returns: cube-info dictionary"} , {"command", PythonCommand, METH_VARARGS, "Execute a command\n" " arguments: string containing command\n" " returns: None"} , {"show", PythonShow, METH_VARARGS, "Execute the 'show arguments' command\n" " arguments: string containing arguments\n" " returns: result, with final newline(s) stripped, as string"} , {"setgnubgid", PythonSetGNUbgID, METH_VARARGS, "Set current board and matchid\n" " arguments: string containing a GNUbgID or XGID\n" " returns: None"} , {"cfevaluate", PythonEvaluateCubeful, METH_VARARGS, "Cubeful evaluation\n" " arguments: [board] [cube-info] [eval-context]\n" " board = tuple ( see \"board\" )\n" " cube-info = dictionary: 'jacoby'=>0/1, 'crawford'=>0/1\n" " 'move'=>0/1, 'beavers'=>0/1, 'cube'=>cube val (int)\n" " 'matchto'=>length (0 for money), 'bgv'=>0..4\n" " 'score'=>(int, int), 'gammonprice'=(float[4])\n" " eval-context = dictionary: 'cubeful'=>0/1, 'plies'=>int,\n" " 'deterministic'=> 0/1, 'noise'->float\n" " returns: evaluation = tuple (floats optimal, nodouble, take, drop, int recommendation, String recommendationtext)"} , {"classifypos", (PyCFunction) PythonClassifyPosition, METH_VARARGS, "classify a position for a given backammon variant and board\n" " arguments: [board], [int variant]\n" " returns: int posclass"} , {"dicerolls", PythonDiceRolls, METH_VARARGS, "return a list of dice rolls from current RNG\n" " arguments: number of rolls\n" " returns: list of tuples (2 elements each, one for each die)\n"} , {"evaluate", PythonEvaluate, METH_VARARGS, "Cubeless evaluation\n" " arguments: [board] [cube-info] [eval context]\n" " see 'cfevaluate'\n" " returns tuple(floats P(win), P(win gammon), P(win backgammnon)\n" " P(lose gammon), P(lose backgammon), cubeless equity)"} , {"evalcontext", PythonEvalContext, METH_VARARGS, "make an evalcontext\n" " argument: [tuple ( 5 int, float )]\n" " returns: eval-context ( see 'cfevaluate' )"} , {"rolloutcontext", PythonRolloutContext, METH_VARARGS, "make a rolloutcontext\n" " argument: [tuple ( 16 int, 2 float )]\n" " returns: rollout-context"} , {"eq2mwc", PythonEq2mwc, METH_VARARGS, "convert equity to MWC\n" " argument: [float equity], [cube-info]\n" " defaults equity = 0.0, cube-info see 'cfevaluate'\n" " return float mwc"} , {"eq2mwc_stderr", PythonEq2mwcStdErr, METH_VARARGS, "convert equity standard error to MWC\n" " argument: [float equity], [cube-info]\n" " defaults equity = 0.0, cube-info see 'cfevaluate'\n" " return float mwc"} , {"findbestmove", PythonFindBestMove, METH_VARARGS, "Find the best move\n" " arguments: [board] [cube-info] [eval-context]\n" " see 'cfevaluate'\n" " returns: tuple( ints point from, point to, \n" " unused moves are set to zero"} , {"hint", PythonHint, METH_VARARGS, " arguments: [max moves]\n" " returns: hint dictionary\n"} , {"mwc2eq", PythonMwc2eq, METH_VARARGS, "convert MWC to equity\n" " argument: [float match-winning-chance], [cube-info]\n" " defaults mwc = 0.0, cube-info see 'cfevaluate'\n" " returns: float equity"} , {"mwc2eq_stderr", PythonMwc2eqStdErr, METH_VARARGS, "convert standard error MWC to equity\n" " argument: [float match-winning-chance], [cube-info]\n" " defaults mwc = 0.0, cube-info see 'cfevaluate'\n" " returns: float equity"} , {"matchchecksum", PythonMatchChecksum, METH_VARARGS, "Calculate checksum for current match\n" " arguments: none\n" " returns: MD5 digest as 32 char hex string"} , {"cubeinfo", PythonCubeInfo, METH_VARARGS, "Make a cubeinfo\n" " arguments: [cube value, cube owner = 0/1, player on move = 0/1, \n" " match length (0 = money), score (tuple int, int), \n" " is crawford = 0/1, bg variant = 0/5]\n" " returns pos-info dictionary ( see 'cfevaluate' )"} , {"posinfo", PythonPosInfo, METH_VARARGS, "Make a posinfo dictionary\n" " arguments: [player on roll = 0/1, player resigned = 0/1, \n" " player doubled = 0/1, gamestate = 0..7, dice = tuple(0..6, 0..6)] \n" " returns pos-info dictionary\n" " pos-info = dictionary: 'dice'=>tuple (int,int), 'turn'=>0/1\n" " 'resigned'=>0/1, 'doubled'=>0/1, 'gamestate'=>int (0..7)\n"} , {"met", PythonMET, METH_VARARGS, "return the current match equity table\n" " arguments: [max score]\n" " returns: list of list n of list n (rows of pre-crawford table\n" " list 2 of list n of post-crawford for player 0/player 1"} , {"positionid", PythonPositionID, METH_VARARGS, "return position ID from board\n" " arguments: [board] ( see 'cfevaluate' )\n" " returns: position ID as string"} , {"matchid", PythonMatchID, METH_VARARGS, "return MatchID from current position, or from cube-info, pos-info\n" " arguments: [cube-info dictionary], [pos-info dictionary] \n" " cube-info: see 'cfevaluate'\n" " pos-info: see 'posinfo'\n" " returns: Match ID as string"} , {"getevalhintfilter", PythonGetEvalHintFilter, METH_VARARGS, "return hint/eval move filters \n" " arguments: none\n" " returns: list of movefilters"} , {"setevalhintfilter", PythonSetEvalHintFilter, METH_VARARGS, "return none \n" " arguments: a list of movefilters\n" " returns: none"} , {"gnubgid", PythonGnubgID, METH_VARARGS, "return GNUBGID from current position, or from board, cube-info, pos-info\n" " arguments: [board, cube-info dictionary, pos-info dictionary]\n" " board, cube-info: see 'cfevaluate'\n" " pos-info: see 'posinfo'\n" " returns: GNUBGID as string"} , {"movetupletostring", PythonMoveTuple2String, METH_VARARGS, "Convert a move tuple to a move string\n" " arguments: tuple of 8 ints\n" " returns: String representation of move"} , {"parsemove", PythonParseMove, METH_VARARGS, "Parse move\n" " arguments: string containing move to parse\n" " returns: tuple of (tuple (int, int)) representing each move "} , {"positionfromid", PythonPositionFromID, METH_VARARGS, "return board from position ID\n" " arguments: [position ID as string]\n" " returns: board ( see 'cfevaluate' )"} , {"positionbearoff", PythonPositionBearoff, METH_VARARGS, "return the bearoff id for the given position\n"} , {"positionfrombearoff", PythonPositionFromBearoff, METH_VARARGS, "return the board from the given bearoff id\n" " arguments: [bearoff id] [no. chequers] [no. points]\n" " returns: board ( see 'cfevaluate' )"} , {"positionkey", PythonPositionKey, METH_VARARGS, "return key for position\n" " arguments: [ board ] ( see 'cfevaluate' )\n" " returns: tuple (10 ints)"} , {"positionfromkey", PythonPositionFromKey, METH_VARARGS, "return position from key\n" " arguments: [ list of 10 ints] \n" " returns: board ( see 'cfevaluate' )"} , {"match", (PyCFunction) (void (*)(void)) (PyCFunctionWithKeywords) PythonMatch, METH_VARARGS | METH_KEYWORDS, "Get the current match\n" " arguments: [ include-analysis = 0/1, include-boards = 0/1,\n" " include-statistics = 0/1, verbose = 0/1 ]\n" " returns: dictionary of match info:\n" " 'games' => list of dictionaries, one per game\n" " 'info' => dictionary\n" " 'points-won'=>int, 'score-X'=> int, 'winner'=>'X'/'O'\n" " 'resigned'=> 0/1, 'score-O'=> int\n" " 'stats' => dictionary\n" " 'X' => player 0 dictionary of stats\n" " 'cube'=>dictionary\n" " 'close-cube'=>int, 'err-missed-double-above-cp-cost'=>float\n" " 'err-missed-double-above-cp-skill'=>float\n" " 'err-missed-double-below-cp-cost'=>float\n" " 'err-missed-double-below-cp-skill'=>float\n" " 'err-wrong-double-above-tg-cost'=>float\n" " 'err-wrong-double-above-tg-skill'=>float\n" " 'err-wrong-double-below-dp-cost'=>float\n" " 'err-wrong-double-below-dp-skill'=>float\n" " 'err-wrong-drop-cost'=>float, 'err-wrong-drop-skill'=>float\n" " 'err-wrong-take-cost'=>float, 'err-wrong-take-skill'=>float\n" " 'error-cost'=>float, 'error-skill'=>float\n" " 'missed-double-above-cp'=>int, 'missed-double-below-cp'=>int\n" " 'n-doubles'=>int, 'n-drops'=>int, 'n-takes': =>int,\n" " 'total-cube'=>int, 'wrong-double-above-tg'=>int\n" " 'wrong-double-below-dp'=>int, 'wrong-drop'=>int,\n" " 'wrong-take'=>int\n" " 'moves'=>dictionary\n" " 'marked'=>dictionarly\n" " 'good'=> int, 'unmarked'->int, 'doubtful'=>int,\n" " 'bad'=>int, 'very bad'=>int\n" " 'total-moves'=>int, 'unforced-moves'=>int,\n" " 'error-cost'=>float, 'error-skill'=>float\n" " 'dice'=>dictionary\n" " 'actual-result;=>float, 'cube'=>float, 'luck'=>float,\n" " 'luck-cost'=>float, 'luck-adjusted-result'=>float\n" " 'marked-rolls'=>dictionary\n" " 'verygood'=>int, 'good'=>int, 'unmarked'=>int,\n" " 'bad'=>int, 'verybad'=>int\n" " 'time'=>dictionary\n" " 'time-penalty'=>int, 'time-penalty-cost'=>float,\n" " 'time-penalty-skill'=>float\n" " 'O'=> player 1 dicrtionary of stats - see 'X; above\n" " 'game'=>list of dictionaries, one per move\n" " 'dice'=>(int, int), move=>((int, int),[(int, int),...])\n" " 'player'=>'X'/'O', 'board'=>board-id-string,\n" " 'action'=>'move', 'double', 'take', 'drop', 'resign'\n" " 'analysis'=>dictionary\n" " 'imove'=>int index of move in list of analysed moves\n" " 'moves'=>list of dictionaries, one per analysed move\n" " 'score'=>equity for move, 'type'=>'eval''rollout'\n" " 'move'=>((int, int),[(int, int),...])\n" " 'probs'=> tuple (5 floats - P(win),\n" " P(win gammon)..P(lose bkgammon)\n" " [ 'evalcontext' = dictionary describing eval context\n" " if not default\n" " 'match-info' = dictionary \n" " 'X' => player-0 dictionary\n" " 'rating' = rating if known\n" " 'name' = player name\n" " 'O' => player-1 dictionary, as 'X', above\n" " 'date'=>(tuple dd, mm, yyyy)\n" " 'default-eval-context' = dictionary\n" " 'plies'=> int, 'deterministic'=>0/1, 'noise'=>float,\n" " 'cubeful'=>0/1, 'prune'=>0/1\n" " 'match_length' = int\n" " 'result' =>0/1\n" " 'rules' = 'Crawford'/whatever\n" " 'variation' => 'Standard' or whatever\n"} , {"navigate", (PyCFunction) (void (*)(void)) (PyCFunctionWithKeywords) PythonNavigate, METH_VARARGS | METH_KEYWORDS, "go to a position in a match or session'n" " arguments: no args = go to start of match/session\n" " [ game=offset] go forward/backward n games\n" " [record=offset] go gorward/backward n moves'n" " returns: None if no change, tuple( games moved, records moved)"} , {"nextturn", (PyCFunction) PythonNextTurn, METH_VARARGS, "play one turn\n" " arguments: none\n" " returns: None"} , {"luckrating", (PyCFunction) PythonLuckRating, METH_VARARGS, "convert a luck per move amount into a rating 0..5 for very unlucky to very lucky\n" " arguments: float luck per move\n" " returns: int 0..5"} , {"errorrating", (PyCFunction) PythonErrorRating, METH_VARARGS, "convert an error per move amount to a rating 0 = awful..7=supernatural\n" " arguments: float error per move\n" " returns: int\n"} , {"updateui", (PyCFunction) PythonUpdateUI, METH_VARARGS, "Allows the UI to update itself\n" " arguments: none\n" " returns: None"} , {NULL, NULL, 0, NULL} }; #if defined(WIN32) static char *python_dir = NULL; #endif static PyObject *py_gnubg_module = NULL; #if !defined(WIN32) extern gint python_run_file(gpointer file) { char *pch; PyObject *py_dict, *py_ret; g_assert(file); pch = g_strdup_printf("gnubg_InteractivePyShell_gui(['','-n', '%s'])\n", (char *) file); py_dict = PyModule_GetDict(PythonGnubgModule()); g_assert(py_dict); py_ret = PyRun_String(pch, Py_eval_input, PythonGnubgModule(), py_dict); if (py_ret) { Py_DECREF(py_ret); } g_free(pch); g_free(file); /* FALSE: to be discarded as event source after use by g_idle_add() */ return FALSE; } #endif extern PyObject * PythonGnubgModule(void) { return py_gnubg_module; } extern MOD_INIT(gnubg) { PyObject *module; MOD_DEF(module, "gnubg", NULL, gnubgMethods); if (module == NULL) return MOD_ERROR_VAL; return MOD_SUCCESS_VAL(module); } extern void PythonInitialise(char *argv0) { #if PY_MAJOR_VERSION >= 3 wchar_t progname[FILENAME_MAX + 1]; mbstowcs(progname, argv0, strlen(argv0) + 1); #else char *progname = argv0; #endif #if defined(WIN32) /* Setup python to look in the pythonlib directory if present */ char *working_dir = g_get_current_dir(); python_dir = g_build_filename(working_dir, "PythonLib", NULL); if (access(python_dir, F_OK) == 0) { /* Set Python to use this directory */ #if PY_MAJOR_VERSION >= 3 Py_SetPythonHome(Py_DecodeLocale(python_dir, NULL)); #else Py_SetPythonHome(python_dir); #endif } g_free(working_dir); #endif #if PY_VERSION_HEX < 0x030b0000 Py_SetProgramName(progname); #else /* * Py_SetProgramName() is deprecated but from my reading of * https://docs.python.org/3/c-api/init.html * its alternative is not actually needed for the few Py*() * functions we use later. */ (void)progname; #endif #if PY_MAJOR_VERSION >= 3 PyImport_AppendInittab("gnubg", &PyInit_gnubg); #else PyImport_AppendInittab("gnubg", &initgnubg); #endif Py_Initialize(); /* ensure that python know about our gnubg module */ PyRun_SimpleString("import gnubg\n"); /* run gnubg.py start up script */ LoadPythonFile("gnubg.py", FALSE); LoadPythonFile("gnubg_user.py", TRUE); py_gnubg_module = PyImport_AddModule("__main__"); } extern void PythonShutdown(void) { #if defined(WIN32) if (python_dir) { g_free(python_dir); python_dir = NULL; } #endif py_gnubg_module = NULL; Py_Finalize(); } extern void PythonRun(const char *sz) { #if defined(USE_GTK) PyObject *py_ret = NULL, *py_dict = NULL; int success = FALSE; #endif if (sz && *sz) { PyRun_SimpleString(sz); } else { /* Run python interactively with history and auto completion * if available */ #if defined(USE_GTK) py_dict = PyModule_GetDict(PythonGnubgModule()); Py_INCREF(py_dict); if (fX && (py_ret = PyRun_String("gnubg_InteractivePyShell_gui()", Py_eval_input, PythonGnubgModule(), py_dict))) if (PyInt_Check(py_ret) && PyInt_AsLong(py_ret)) success = TRUE; if (py_ret) { Py_DECREF(py_ret); } if (py_dict) { Py_DECREF(py_dict); } if (success) return; #endif PyRun_SimpleString("gnubg_InteractivePyShell_tui()"); } } extern int LoadPythonFile(const char *sz, int fQuiet) { char *path = NULL; char *cmd = NULL; char *escpath = NULL; int ret = FALSE; if (g_file_test(sz, G_FILE_TEST_EXISTS)) path = g_strdup(sz); else { path = BuildFilename2("/scripts", sz); if (!g_file_test(path, G_FILE_TEST_EXISTS)) { g_free(path); path = g_build_filename("scripts", sz, NULL); } } if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) { g_free(path); if (!fQuiet) outputerrf(_("Python file (%s) not found\n"), sz); return FALSE; } escpath = g_strescape(path, NULL); cmd = g_strdup_printf("exec(compile(open('%s', 'rb').read(), '%s', 'exec'))", escpath, escpath); PyRun_SimpleString(cmd); g_free(escpath); g_free(path); g_free(cmd); return ret; } #endif