/* * Copyright (C) 1998-2003 Gary Wong * Copyright (C) 2000-2025 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" #include "backgammon.h" #include #include #include #include #include #include #include "isaac.h" #include "md5.h" #include "bearoffgammon.h" #include "positionid.h" #include "matchid.h" #include "matchequity.h" #include "format.h" #include "simd.h" #include "multithread.h" #include "util.h" #include "lib/simd.h" typedef void (*classstatusfunc) (char *szOutput); typedef int (*cfunc) (const void *, const void *); /* Race inputs */ enum { /* In a race position, bar and the 24 points are always empty, so only */ /* 23*4 (92) are needed */ /* (0 <= k < 14), RI_OFF + k = */ /* 1 if exactly k+1 checkers are off, 0 otherwise */ RI_OFF = 92, /* Number of cross-overs by outside checkers */ RI_NCROSS = 92 + 14, HALF_RACE_INPUTS }; /* Contact inputs -- see Berliner for most of these */ enum { /* n - number of checkers off * * off1 - 1 n >= 5 * n/5 otherwise * * off2 - 1 n >= 10 * (n-5)/5 n < 5 < 10 * 0 otherwise * * off3 - (n-10)/5 n > 10 * 0 otherwise */ I_OFF1, I_OFF2, I_OFF3, /* Minimum number of pips required to break contact. * * For each checker x, N(x) is checker location, * C(x) is max({forall o : N(x) - N(o)}, 0) * * Break Contact : (sum over x of C(x)) / 152 * * 152 is degree of contact of start position. */ I_BREAK_CONTACT, /* Location of back checker (Normalized to [01]) */ I_BACK_CHEQUER, /* Location of most backward anchor (Normalized to [01]) */ I_BACK_ANCHOR, /* Forward anchor in opponents home. * * Normalized in the following way: If there is an anchor in opponents * home at point k (1 <= k <= 6), value is k/6. Otherwise, if there is an * anchor in points (7 <= k <= 12), take k/6 as well. Otherwise set to 2. * * This is an attempt for some continuity, since a 0 would be the "same" as * a forward anchor at the bar. */ I_FORWARD_ANCHOR, /* Average number of pips opponent loses from hits. * * Some heuristics are required to estimate it, since we have no idea what * the best move actually is. * * 1. If board is weak (less than 3 inner points made), don't consider * hitting on points 23 and 24. * 2. Don't break inner points to hit. */ I_PIPLOSS, /* Number of rolls that hit at least one checker. */ I_P1, /* Number of rolls that hit at least two checkers. */ I_P2, /* How many rolls permit the back checker to escape (Normalized to [01]) */ I_BACKESCAPES, /* Maximum containment of opponent checkers, from our points 9 to op back * checker. * * Value is (1 - n/36), where n is number of rolls to escape. */ I_ACONTAIN, /* Above squared */ I_ACONTAIN2, /* Maximum containment, from our point 9 to home. * Value is (1 - n/36), where n is number of rolls to escape. */ I_CONTAIN, /* Above squared */ I_CONTAIN2, /* For all checkers out of home, * sum (Number of rolls that let x escape * distance from home) * * Normalized by dividing by 3600. */ I_MOBILITY, /* One sided moment. * Let A be the point of weighted average: * A = sum of N(x) for all x) / nCheckers. * * Then for all x : A < N(x), M = (average (N(X) - A)^2) * * Diveded by 400 to normalize. */ I_MOMENT2, /* Average number of pips lost when on the bar. * Normalized to [01] */ I_ENTER, /* Probablity of one checker not entering from bar. * 1 - (1 - n/6)^2, where n is number of closed points in op home. */ I_ENTER2, I_TIMING, I_BACKBONE, I_BACKG, I_BACKG1, I_FREEPIP, I_BACKRESCAPES, MORE_INPUTS }; #define MINPPERPOINT 4 #define NUM_INPUTS ((25 * MINPPERPOINT + MORE_INPUTS) * 2) #define NUM_RACE_INPUTS ( HALF_RACE_INPUTS * 2 ) #define NUM_PRUNING_INPUTS (25 * MINPPERPOINT * 2) #if !defined(LOCKING_VERSION) f_FindnSaveBestMoves FindnSaveBestMoves = FindnSaveBestMovesNoLocking; f_FindBestMove FindBestMove = FindBestMoveNoLocking; f_EvaluatePosition EvaluatePosition = EvaluatePositionNoLocking; f_ScoreMove ScoreMove = ScoreMoveNoLocking; f_GeneralCubeDecisionE GeneralCubeDecisionE = GeneralCubeDecisionENoLocking; f_GeneralEvaluationE GeneralEvaluationE = GeneralEvaluationENoLocking; #define FindnSaveBestMoves FindnSaveBestMovesNoLocking #define FindBestMove FindBestMoveNoLocking #define EvaluatePosition EvaluatePositionNoLocking #define ScoreMove ScoreMoveNoLocking #define GeneralCubeDecisionE GeneralCubeDecisionENoLocking #define GeneralEvaluationE GeneralEvaluationENoLocking #define EvaluatePositionCache EvaluatePositionCacheNoLocking #define FindBestMovePlied FindBestMovePliedNoLocking #define GeneralEvaluationEPlied GeneralEvaluationEPliedNoLocking #define EvaluatePositionCubeful3 EvaluatePositionCubeful3NoLocking #define ScoreMoves ScoreMovesNoLocking #define ScoreMovesPruned ScoreMovesPrunedNoLocking #define FindBestMoveInEval FindBestMoveInEvalNoLocking #define GeneralEvaluationEPliedCubeful GeneralEvaluationEPliedCubefulNoLocking #define EvaluatePositionCubeful4 EvaluatePositionCubeful4NoLocking #define CacheAdd CacheAddNoLocking #define CacheLookup CacheLookupNoLocking static int EvaluatePositionCache(NNState * nnStates, const TanBoard anBoard, float arOutput[], cubeinfo * pci, const evalcontext * pecx, int nPlies, positionclass pc); static int FindBestMovePlied(int anMove[8], int nDice0, int nDice1, TanBoard anBoard, const cubeinfo * pci, const evalcontext * pec, int nPlies, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]); static int anEscapes[0x1000]; static int anEscapes1[0x1000]; static int anPoint[16] = { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; neuralnet nnContact, nnRace, nnCrashed; neuralnet nnpContact, nnpRace, nnpCrashed; bearoffcontext *pbcOS = NULL; bearoffcontext *pbcTS = NULL; bearoffcontext *pbc1 = NULL; bearoffcontext *pbc2 = NULL; bearoffcontext *apbcHyper[3] = { NULL, NULL, NULL }; evalCache cEval; evalCache cpEval; unsigned int cCache; int fInterrupt = FALSE; int fMatchCancelled = FALSE; /* variation of backgammon used by gnubg */ bgvariation bgvDefault = VARIATION_STANDARD; /* the number of chequers for the variations */ int anChequers[NUM_VARIATIONS] = { 15, 15, 1, 2, 3 }; const char *aszVariations[NUM_VARIATIONS] = { N_("Standard backgammon"), N_("Nackgammon"), N_("1-chequer hypergammon"), N_("2-chequer hypergammon"), N_("3-chequer hypergammon") }; const char *aszVariationCommands[NUM_VARIATIONS] = { "standard", "nackgammon", "1-chequer-hypergammon", "2-chequer-hypergammon", "3-chequer-hypergammon" }; cubeinfo ciCubeless = { 1, 0, 0, 0, {0, 0}, FALSE, FALSE, FALSE, {1.0f, 1.0f, 1.0f, 1.0f}, VARIATION_STANDARD }; const char *aszEvalType[] = { N_("No evaluation"), N_("Neural net evaluation"), N_("Rollout") }; evalcontext ecBasic = { .fCubeful = FALSE, .nPlies = 0, .fUsePrune = FALSE, .fDeterministic = TRUE, .rNoise = 0.0f }; /* defaults for the filters - 0 ply uses no filters */ #include "movefilters.inc" movefilter defaultFilters[MAX_FILTER_PLIES][MAX_FILTER_PLIES] = MOVEFILTER_NORMAL; /* Random context, for generating non-deterministic noisy evaluations. */ static randctx rc; #if defined(USE_MULTITHREAD) static GMutex noiseRNGMutex; #endif /* * predefined settings */ const char *aszSettings[NUM_SETTINGS] = { N_("setting|beginner"), N_("setting|casual play"), N_("setting|intermediate"), N_("setting|advanced"), N_("setting|0ply"), N_("setting|1ply"), N_("setting|2ply"), N_("setting|3ply"), N_("setting|4ply") }; /* which evaluation context does the predefined settings use */ evalcontext aecSettings[NUM_SETTINGS] = { {.fCubeful = TRUE, .nPlies = 0, .fUsePrune = FALSE, .fDeterministic = TRUE, .rNoise = 0.060f}, /* beginner */ {.fCubeful = TRUE, .nPlies = 0, .fUsePrune = FALSE, .fDeterministic = TRUE, .rNoise = 0.050f}, /* casual player */ {.fCubeful = TRUE, .nPlies = 0, .fUsePrune = FALSE, .fDeterministic = TRUE, .rNoise = 0.040f}, /* intermediate */ {.fCubeful = TRUE, .nPlies = 0, .fUsePrune = FALSE, .fDeterministic = TRUE, .rNoise = 0.015f}, /* advanced */ {.fCubeful = TRUE, .nPlies = 0, .fUsePrune = FALSE, .fDeterministic = TRUE, .rNoise = 0.0f}, /* 0 ply */ {.fCubeful = TRUE, .nPlies = 1, .fUsePrune = TRUE, .fDeterministic = TRUE, .rNoise = 0.0f}, /* 1 ply */ {.fCubeful = TRUE, .nPlies = 2, .fUsePrune = TRUE, .fDeterministic = TRUE, .rNoise = 0.0f}, /* 2 ply */ {.fCubeful = TRUE, .nPlies = 3, .fUsePrune = TRUE, .fDeterministic = TRUE, .rNoise = 0.0f}, /* 3 ply */ {.fCubeful = TRUE, .nPlies = 4, .fUsePrune = TRUE, .fDeterministic = TRUE, .rNoise = 0.0f} /* 4 ply */ }; /* which move filter does the predefined settings use */ int aiSettingsMoveFilter[NUM_SETTINGS] = { -1, /* beginner: n/a */ -1, /* casual play: n/a */ -1, /* intermediate: n/a */ -1, /* advanced: n/a */ -1, /* 0 ply: n/a */ 0, /* 1 ply: normal */ 0, /* 2 ply: normal */ 0, /* 3 ply: normal */ 0 /* 4 ply: normal */ }; /* the predefined move filters */ const char *aszMoveFilterSettings[NUM_MOVEFILTER_SETTINGS] = { N_("Normal"), N_("Large") }; movefilter aaamfMoveFilterSettings[NUM_MOVEFILTER_SETTINGS][MAX_FILTER_PLIES][MAX_FILTER_PLIES] = { MOVEFILTER_NORMAL, MOVEFILTER_LARGE }; const char *aszDoubleTypes[NUM_DOUBLE_TYPES] = { N_("doubletype|Double"), N_("doubletype|Beaver"), N_("doubletype|Raccoon") }; /* parameters for EvalEfficiency */ static float rTSCubeX = 0.6f; /* for match play only */ float rOSCubeX = 0.6f; float rRaceFactorX[2] = { 0.00125f, 0.00250f }; float rRaceCoefficientX[2] = { 0.55f, 0.60f }; float rRaceMax[2] = { 0.7f, 0.8f }; float rRaceMin[2] = { 0.6f, 0.6f }; float rCrashedX[2] = { 0.68f, 0.76f }; float rContactX[2] = { 0.68f, 0.76f }; static inline int msb32(int n) #ifdef HAVE___BUILTIN_CLZ { return 31 - __builtin_clz(n); /* or __builtin_clz() ^ 31 */ } #else /* from rosettacode.org */ { int b = 0; #define step(x) if (n >= 1 << x) b += x, n >>= x step(16); step(8); step(4); step(2); step(1); #undef step return b; } #endif static void ComputeTable0(void) { int i, n0, n1; for (i = 0; i < 0x1000; i++) { int c = 0; for (n0 = 0; n0 <= 5; n0++) for (n1 = 0; n1 <= n0; n1++) if (!(i & (1 << (n0 + n1 + 1))) && !((i & (1 << n0)) && (i & (1 << n1)))) c += (n0 == n1) ? 1 : 2; anEscapes[i] = c; } } static int Escapes(const unsigned int anBoard[25], int n) { int i, af = 0, m; m = (n < 12) ? n : 12; for (i = 0; i < m; i++) af |= (anPoint[anBoard[24 + i - n]] << i); return anEscapes[af]; } static void ComputeTable1(void) { int i, n0, n1; anEscapes1[0] = 0; for (i = 1; i < 0x1000; i++) { int c = 0; int low = 0; while (!(i & (1 << low))) { ++low; } for (n0 = 0; n0 <= 5; n0++) for (n1 = 0; n1 <= n0; n1++) { if ((n0 + n1 + 1 > low) && !(i & (1 << (n0 + n1 + 1))) && !((i & (1 << n0)) && (i & (1 << n1)))) { c += (n0 == n1) ? 1 : 2; } } anEscapes1[i] = c; } } static int Escapes1(const unsigned int anBoard[25], int n) { int i, af = 0, m; m = (n < 12) ? n : 12; for (i = 0; i < m; i++) af |= (anPoint[anBoard[24 + i - n]] << i); return anEscapes1[af]; } static void ComputeTable(void) { ComputeTable0(); ComputeTable1(); } static void DestroyWeights(void) { NeuralNetDestroy(&nnContact); NeuralNetDestroy(&nnCrashed); NeuralNetDestroy(&nnRace); NeuralNetDestroy(&nnpContact); NeuralNetDestroy(&nnpCrashed); NeuralNetDestroy(&nnpRace); } extern int EvalShutdown(void) { int i; /* close bearoff databases */ BearoffClose(pbc1); BearoffClose(pbc2); BearoffClose(pbcOS); BearoffClose(pbcTS); for (i = 0; i < 3; ++i) BearoffClose(apbcHyper[i]); /* destroy neural nets */ DestroyWeights(); /* destroy cache */ CacheDestroy(&cEval); CacheDestroy(&cpEval); return 0; } static int binary_weights_failed(char *filename, FILE * weights) { float r; if (!weights) { g_print(_("couldn't open %s"), filename); g_print("\n"); return -1; } if (fread(&r, sizeof r, 1, weights) < 1) { g_print(_("couldn't read %s"), filename); g_print("\n"); return -2; } if (r != WEIGHTS_MAGIC_BINARY) { g_print(_("%s is not a weights file"), filename); g_print("\n"); return -3; } if (fread(&r, sizeof r, 1, weights) < 1) { g_print(_("couldn't read %s"), filename); g_print("\n"); return -4; } if (r != WEIGHTS_VERSION_BINARY) { char buf[20]; sprintf(buf, "%.2f", r); g_print(_("weights file %s, has incorrect version (%s), expected (%s)"), filename, buf, WEIGHTS_VERSION); g_print("\n"); return -5; } return 0; } static int weights_failed(char *filename, FILE * weights) { char file_version[16]; if (!weights) { g_print(_("couldn't open %s"), filename); g_print("\n"); return -1; } if (fscanf(weights, "GNU Backgammon %15s\n", file_version) != 1) { g_print(_("%s is not a weights file"), filename); g_print("\n"); return -2; } if (strcmp(file_version, WEIGHTS_VERSION)) { g_print(_("weights file %s, has incorrect version (%s), expected (%s)"), filename, file_version, WEIGHTS_VERSION); g_print("\n"); return -3; } return 0; } extern void EvalInitialise(char *szWeights, char *szWeightsBinary, int fNoBearoff, void (*pfProgress) (unsigned int)) { FILE *pfWeights = NULL; int i, fReadWeights = FALSE; static int fInitialised = FALSE; #if defined(USE_SIMD_INSTRUCTIONS) int simderror = TRUE; #endif if (!fInitialised) { #if defined(USE_SIMD_INSTRUCTIONS) int result = SIMD_Supported(); switch (result) { case -1: outputerrf(_("Can't check for SIMD support\n")); break; case -2: outputerrf(_("No cpuid check available\n")); break; case 0: /* No SIMD support */ break; case 1: /* SIMD support */ simderror = FALSE; break; default: outputerrf(_("Unknown error while doing SIMD support test\n")); } if (simderror) { #if defined(USE_AVX) outputerrf(_ ("\nThis version of GNU Backgammon is compiled with AVX support but this machine does not support AVX\n")); #else outputerrf(_ ("\nThis version of GNU Backgammon is compiled with SSE support but this machine does not support SSE\n")); #endif exit(EXIT_FAILURE); } #endif cCache = 0x1 << CACHE_SIZE_DEFAULT; if (CacheCreate(&cEval, cCache)) { PrintError(_("Evaluation cache allocation failed")); CacheCreate(&cEval, 0); cCache = 0; } if (CacheCreate(&cpEval, 0x1 << 16)) { PrintError(_("Evaluation cache allocation failed")); return; } ComputeTable(); rc.randrsl[0] = (ub4) time(NULL); for (i = 0; i < RANDSIZ; i++) rc.randrsl[i] = rc.randrsl[0]; irandinit(&rc, TRUE); fInitialised = TRUE; } if (!fNoBearoff) { char *gnubg_bearoff; char *gnubg_bearoff_os; gnubg_bearoff_os = BuildFilename("gnubg_os0.bd"); if (!pbc1) pbc1 = BearoffInit(gnubg_bearoff_os, BO_IN_MEMORY | BO_MUST_BE_ONE_SIDED, NULL); g_free(gnubg_bearoff_os); if (!pbc1) pbc1 = BearoffInit(NULL, BO_HEURISTIC, pfProgress); /* read two-sided db from gnubg.bd */ gnubg_bearoff = BuildFilename("gnubg_ts0.bd"); pbc2 = BearoffInit(gnubg_bearoff, BO_IN_MEMORY | BO_MUST_BE_TWO_SIDED, NULL); g_free(gnubg_bearoff); if (!pbc2) g_printerr( _("\n***WARNING***\n\n" "GNU Backgammon will not use the two-sided bearoff\n" "database since the gnubg_ts0.bd could not be found.\n" "You should obtain this file or generate it yourself\n" "with the command: makebearoff -t 6x6 -f gnubg_ts0.bd\n" "You can also generate other bearoff databases; see\n" "README for more details\n\n")); gnubg_bearoff_os = BuildFilename("gnubg_os.bd"); /* init one-sided db */ pbcOS = BearoffInit(gnubg_bearoff_os, BO_IN_MEMORY | BO_MUST_BE_ONE_SIDED, NULL); g_free(gnubg_bearoff_os); gnubg_bearoff = BuildFilename("gnubg_ts.bd"); /* init two-sided db */ pbcTS = BearoffInit(gnubg_bearoff, BO_IN_MEMORY | BO_MUST_BE_TWO_SIDED, NULL); g_free(gnubg_bearoff); /* hyper-gammon databases */ for (i = 0; i < 3; ++i) { char *fn; char sz[10]; sprintf(sz, "hyper%c.bd", i + '1'); fn = BuildFilename(sz); apbcHyper[i] = BearoffInit(fn, BO_IN_MEMORY, NULL); g_free(fn); } } if (szWeightsBinary) { pfWeights = g_fopen(szWeightsBinary, "rb"); if (!binary_weights_failed(szWeightsBinary, pfWeights)) { if (!fReadWeights && !(fReadWeights = !NeuralNetLoadBinary(&nnContact, pfWeights) && !NeuralNetLoadBinary(&nnRace, pfWeights) && !NeuralNetLoadBinary(&nnCrashed, pfWeights) && !NeuralNetLoadBinary(&nnpContact, pfWeights) && !NeuralNetLoadBinary(&nnpCrashed, pfWeights) && !NeuralNetLoadBinary(&nnpRace, pfWeights))) { perror(szWeightsBinary); } } if (pfWeights) fclose(pfWeights); pfWeights = NULL; } if (!fReadWeights && szWeights) { pfWeights = g_fopen(szWeights, "r"); if (!weights_failed(szWeights, pfWeights)) { setlocale(LC_ALL, "C"); if (!(fReadWeights = !NeuralNetLoad(&nnContact, pfWeights) && !NeuralNetLoad(&nnRace, pfWeights) && !NeuralNetLoad(&nnCrashed, pfWeights) && !NeuralNetLoad(&nnpContact, pfWeights) && !NeuralNetLoad(&nnpCrashed, pfWeights) && !NeuralNetLoad(&nnpRace, pfWeights) )) perror(szWeights); setlocale(LC_ALL, ""); } if (pfWeights) fclose(pfWeights); pfWeights = NULL; } g_assert(fReadWeights); g_assert(nnContact.cInput == NUM_INPUTS && nnContact.cOutput == NUM_OUTPUTS); g_assert(nnCrashed.cInput == NUM_INPUTS && nnCrashed.cOutput == NUM_OUTPUTS); g_assert(nnRace.cInput == NUM_RACE_INPUTS && nnRace.cOutput == NUM_OUTPUTS); g_assert(nnpContact.cInput == NUM_PRUNING_INPUTS && nnpContact.cOutput == NUM_OUTPUTS); g_assert(nnpCrashed.cInput == NUM_PRUNING_INPUTS && nnpCrashed.cOutput == NUM_OUTPUTS); g_assert(nnpRace.cInput == NUM_PRUNING_INPUTS && nnpRace.cOutput == NUM_OUTPUTS); if (!fReadWeights) { outputerrf(_("GNU Backgammon couldn't find a weights file.")); exit(EXIT_FAILURE); } } /* Calculates inputs for any contact position, for one player only. */ static void CalculateHalfInputs(const unsigned int anBoard[25], const unsigned int anBoardOpp[25], float afInput[]) { int i, j, k, l, nOppBack, n, aHit[39], nBoard; /* aanCombination[n] - * How many ways to hit from a distance of n pips. * Each number is an index into aIntermediate below. */ static const int aanCombination[24][5] = { {0, -1, -1, -1, -1}, /* 1 */ {1, 2, -1, -1, -1}, /* 2 */ {3, 4, 5, -1, -1}, /* 3 */ {6, 7, 8, 9, -1}, /* 4 */ {10, 11, 12, -1, -1}, /* 5 */ {13, 14, 15, 16, 17}, /* 6 */ {18, 19, 20, -1, -1}, /* 7 */ {21, 22, 23, 24, -1}, /* 8 */ {25, 26, 27, -1, -1}, /* 9 */ {28, 29, -1, -1, -1}, /* 10 */ {30, -1, -1, -1, -1}, /* 11 */ {31, 32, 33, -1, -1}, /* 12 */ {-1, -1, -1, -1, -1}, /* 13 */ {-1, -1, -1, -1, -1}, /* 14 */ {34, -1, -1, -1, -1}, /* 15 */ {35, -1, -1, -1, -1}, /* 16 */ {-1, -1, -1, -1, -1}, /* 17 */ {36, -1, -1, -1, -1}, /* 18 */ {-1, -1, -1, -1, -1}, /* 19 */ {37, -1, -1, -1, -1}, /* 20 */ {-1, -1, -1, -1, -1}, /* 21 */ {-1, -1, -1, -1, -1}, /* 22 */ {-1, -1, -1, -1, -1}, /* 23 */ {38, -1, -1, -1, -1} /* 24 */ }; /* One way to hit */ typedef struct { /* if true, all intermediate points (if any) are required; * if false, one of two intermediate points are required. * Set to true for a direct hit, but that can be checked with * nFaces == 1, */ int fAll; /* Intermediate points required */ int anIntermediate[3]; /* Number of faces used in hit (1 to 4) */ int nFaces; /* Number of pips used to hit */ int nPips; } Inter; const Inter *pi; /* All ways to hit */ static const Inter aIntermediate[39] = { {1, {0, 0, 0}, 1, 1}, /* 0: 1x hits 1 */ {1, {0, 0, 0}, 1, 2}, /* 1: 2x hits 2 */ {1, {1, 0, 0}, 2, 2}, /* 2: 11 hits 2 */ {1, {0, 0, 0}, 1, 3}, /* 3: 3x hits 3 */ {0, {1, 2, 0}, 2, 3}, /* 4: 21 hits 3 */ {1, {1, 2, 0}, 3, 3}, /* 5: 11 hits 3 */ {1, {0, 0, 0}, 1, 4}, /* 6: 4x hits 4 */ {0, {1, 3, 0}, 2, 4}, /* 7: 31 hits 4 */ {1, {2, 0, 0}, 2, 4}, /* 8: 22 hits 4 */ {1, {1, 2, 3}, 4, 4}, /* 9: 11 hits 4 */ {1, {0, 0, 0}, 1, 5}, /* 10: 5x hits 5 */ {0, {1, 4, 0}, 2, 5}, /* 11: 41 hits 5 */ {0, {2, 3, 0}, 2, 5}, /* 12: 32 hits 5 */ {1, {0, 0, 0}, 1, 6}, /* 13: 6x hits 6 */ {0, {1, 5, 0}, 2, 6}, /* 14: 51 hits 6 */ {0, {2, 4, 0}, 2, 6}, /* 15: 42 hits 6 */ {1, {3, 0, 0}, 2, 6}, /* 16: 33 hits 6 */ {1, {2, 4, 0}, 3, 6}, /* 17: 22 hits 6 */ {0, {1, 6, 0}, 2, 7}, /* 18: 61 hits 7 */ {0, {2, 5, 0}, 2, 7}, /* 19: 52 hits 7 */ {0, {3, 4, 0}, 2, 7}, /* 20: 43 hits 7 */ {0, {2, 6, 0}, 2, 8}, /* 21: 62 hits 8 */ {0, {3, 5, 0}, 2, 8}, /* 22: 53 hits 8 */ {1, {4, 0, 0}, 2, 8}, /* 23: 44 hits 8 */ {1, {2, 4, 6}, 4, 8}, /* 24: 22 hits 8 */ {0, {3, 6, 0}, 2, 9}, /* 25: 63 hits 9 */ {0, {4, 5, 0}, 2, 9}, /* 26: 54 hits 9 */ {1, {3, 6, 0}, 3, 9}, /* 27: 33 hits 9 */ {0, {4, 6, 0}, 2, 10}, /* 28: 64 hits 10 */ {1, {5, 0, 0}, 2, 10}, /* 29: 55 hits 10 */ {0, {5, 6, 0}, 2, 11}, /* 30: 65 hits 11 */ {1, {6, 0, 0}, 2, 12}, /* 31: 66 hits 12 */ {1, {4, 8, 0}, 3, 12}, /* 32: 44 hits 12 */ {1, {3, 6, 9}, 4, 12}, /* 33: 33 hits 12 */ {1, {5, 10, 0}, 3, 15}, /* 34: 55 hits 15 */ {1, {4, 8, 12}, 4, 16}, /* 35: 44 hits 16 */ {1, {6, 12, 0}, 3, 18}, /* 36: 66 hits 18 */ {1, {5, 10, 15}, 4, 20}, /* 37: 55 hits 20 */ {1, {6, 12, 18}, 4, 24} /* 38: 66 hits 24 */ }; /* aaRoll[n] - All ways to hit with the n'th roll * Each entry is an index into aIntermediate above. */ static const int aaRoll[21][4] = { {0, 2, 5, 9}, /* 11 */ {1, 8, 17, 24}, /* 22 */ {3, 16, 27, 33}, /* 33 */ {6, 23, 32, 35}, /* 44 */ {10, 29, 34, 37}, /* 55 */ {13, 31, 36, 38}, /* 66 */ {0, 1, 4, -1}, /* 21 */ {0, 3, 7, -1}, /* 31 */ {1, 3, 12, -1}, /* 32 */ {0, 6, 11, -1}, /* 41 */ {1, 6, 15, -1}, /* 42 */ {3, 6, 20, -1}, /* 43 */ {0, 10, 14, -1}, /* 51 */ {1, 10, 19, -1}, /* 52 */ {3, 10, 22, -1}, /* 53 */ {6, 10, 26, -1}, /* 54 */ {0, 13, 18, -1}, /* 61 */ {1, 13, 21, -1}, /* 62 */ {3, 13, 25, -1}, /* 63 */ {6, 13, 28, -1}, /* 64 */ {10, 13, 30, -1} /* 65 */ }; /* One roll stat */ struct { /* number of chequers this roll hits */ int nChequers; /* count of pips this roll hits */ int nPips; } aRoll[21]; { int np = 0; for (nOppBack = 24; nOppBack >= 0; --nOppBack) { if (anBoardOpp[nOppBack]) { break; } } nOppBack = 23 - nOppBack; for (i = nOppBack + 1; i < 25; i++) if (anBoard[i]) np += (i + 1 - nOppBack) * anBoard[i]; afInput[I_BREAK_CONTACT] = (float) np / (15 + 152.0f); } { unsigned int p = 0; for (i = 0; i < nOppBack; i++) { if (anBoard[i]) p += (i + 1) * anBoard[i]; } afInput[I_FREEPIP] = (float) p / 100.0f; } { int t = 0; int no = 0; int m = (nOppBack >= 11) ? nOppBack : 11; t += 24 * anBoard[24]; no += anBoard[24]; for (i = 23; i > m; --i) { if (unlikely(anBoard[i] && anBoard[i] != 2)) { int ns = ((anBoard[i] > 2) ? (anBoard[i] - 2) : 1); no += ns; t += i * ns; } } for (; i >= 6; --i) { if (anBoard[i]) { int nc = anBoard[i]; no += nc; t += i * nc; } } for (i = 5; i >= 0; --i) { if (anBoard[i] > 2) { t += i * (anBoard[i] - 2); no += (anBoard[i] - 2); } else if (anBoard[i] < 2) { int nm = (2 - anBoard[i]); if (no >= nm) { t -= i * nm; no -= nm; } } } afInput[I_TIMING] = (float) t / 100.0f; } /* Back chequer */ { int nBack; for (nBack = 24; nBack >= 0; --nBack) { if (anBoard[nBack]) { break; } } afInput[I_BACK_CHEQUER] = (float) nBack / 24.0f; /* Back anchor */ for (i = ((nBack == 24) ? 23 : nBack); i >= 0; --i) { if (anBoard[i] >= 2) { break; } } afInput[I_BACK_ANCHOR] = (float) i / 24.0f; /* Forward anchor */ n = 0; for (j = 18; j <= i; ++j) { if (anBoard[j] >= 2) { n = 24 - j; break; } } if (n == 0) { for (j = 17; j >= 12; --j) { if (anBoard[j] >= 2) { n = 24 - j; break; } } } afInput[I_FORWARD_ANCHOR] = n == 0 ? 2.0f : (float) n / 6.0f; } /* Piploss */ nBoard = 0; for (i = 0; i < 6; i++) if (anBoard[i] >= 2) nBoard++; memset(aHit, 0, sizeof(aHit)); /* for every point we'd consider hitting a blot on, */ for (i = (nBoard > 2) ? 23 : 21; i >= 0; i--) /* if there's a blot there, then */ if (unlikely(anBoardOpp[i] == 1)) /* for every point beyond */ for (j = 24 - i; j < 25; j++) /* if we have a hitter and are willing to hit */ if (anBoard[j] && !(j < 6 && anBoard[j] == 2)) /* for every roll that can hit from that point */ for (n = 0; n < 5; n++) { if (aanCombination[j - 24 + i][n] == -1) break; /* find the intermediate points required to play */ pi = aIntermediate + aanCombination[j - 24 + i][n]; if (pi->fAll) { /* if nFaces is 1, there are no intermediate points */ if (pi->nFaces > 1) { /* all the intermediate points are required */ for (k = 0; k < 3 && pi->anIntermediate[k] > 0; k++) if (anBoardOpp[i - pi->anIntermediate[k]] > 1) /* point is blocked; look for other hits */ goto cannot_hit; } } else { /* either of two points are required */ if (anBoardOpp[i - pi->anIntermediate[0]] > 1 && anBoardOpp[i - pi->anIntermediate[1]] > 1) { /* both are blocked; look for other hits */ goto cannot_hit; } } /* enter this shot as available */ aHit[aanCombination[j - 24 + i][n]] |= 1 << j; cannot_hit:; } memset(aRoll, 0, sizeof(aRoll)); if (!anBoard[24]) { /* we're not on the bar; for each roll, */ for (i = 0; i < 21; i++) { n = -1; /* (hitter used) */ /* for each way that roll hits, */ for (j = 0; j < 4; j++) { int r = aaRoll[i][j]; if (unlikely(r < 0)) break; if (likely(!aHit[r])) continue; pi = aIntermediate + r; if (pi->nFaces == 1) { /* direct shot */ k = msb32(aHit[r]); /* select the most advanced blot; if we still have * a chequer that can hit there */ if (n != k || anBoard[k] > 1) aRoll[i].nChequers++; n = k; if (k - pi->nPips + 1 > aRoll[i].nPips) aRoll[i].nPips = k - pi->nPips + 1; /* if rolling doubles, check for multiple * direct shots */ if (aaRoll[i][3] >= 0 && aHit[r] & ~(1 << k)) aRoll[i].nChequers++; } else { /* indirect shot */ if (!aRoll[i].nChequers) aRoll[i].nChequers = 1; /* find the most advanced hitter */ k = msb32(aHit[r]); if (k - pi->nPips + 1 > aRoll[i].nPips) aRoll[i].nPips = k - pi->nPips + 1; /* check for blots hit on intermediate points */ for (l = 0; l < 3 && pi->anIntermediate[l] > 0; l++) if (anBoardOpp[23 - k + pi->anIntermediate[l]] == 1) { aRoll[i].nChequers++; break; } } } } } else if (anBoard[24] == 1) { /* we have one on the bar; for each roll, */ for (i = 0; i < 21; i++) { n = 0; /* (free to use either die to enter) */ for (j = 0; j < 4; j++) { int r = aaRoll[i][j]; if (r < 0) break; if (!aHit[r]) continue; pi = aIntermediate + r; if (pi->nFaces == 1) { /* direct shot */ /* FIXME: There must be a more profitable way to use * the possibility of finding the msb quickly, * but I don't understand the code below. */ #ifdef HAVE___BUILTIN_CLZ /* This shortcut is worthwhile only if msb32 is fast */ for (k = msb32(aHit[r]); k > 0; k--) { #else for (k = 24; k > 0; k--) { #endif if (aHit[r] & (1 << k)) { /* if we need this die to enter, we can't hit elsewhere */ if (n && k != 24) break; /* if this isn't a shot from the bar, the * other die must be used to enter */ if (k != 24) { int npip = aIntermediate[aaRoll[i][1 - j]].nPips; if (anBoardOpp[npip - 1] > 1) break; n = 1; } aRoll[i].nChequers++; if (k - pi->nPips + 1 > aRoll[i].nPips) aRoll[i].nPips = k - pi->nPips + 1; } } } else { /* indirect shot -- consider from the bar only */ if (!(aHit[r] & (1 << 24))) continue; if (!aRoll[i].nChequers) aRoll[i].nChequers = 1; if (25 - pi->nPips > aRoll[i].nPips) aRoll[i].nPips = 25 - pi->nPips; /* check for blots hit on intermediate points */ for (k = 0; k < 3 && pi->anIntermediate[k] > 0; k++) if (anBoardOpp[pi->anIntermediate[k] + 1] == 1) { aRoll[i].nChequers++; break; } } } } } else { /* we have more than one on the bar -- * count only direct shots from point 24 */ for (i = 0; i < 21; i++) { /* for the first two ways that hit from the bar */ for (j = 0; j < 2; j++) { int r = aaRoll[i][j]; if (!(aHit[r] & (1 << 24))) continue; pi = aIntermediate + r; /* only consider direct shots */ if (pi->nFaces != 1) continue; aRoll[i].nChequers++; if (25 - pi->nPips > aRoll[i].nPips) aRoll[i].nPips = 25 - pi->nPips; } } } { int np = 0; int n1 = 0; int n2 = 0; for (i = 0; i < 6; i++) { int nc = aRoll[i].nChequers; np += aRoll[i].nPips; if (nc > 0) { n1 += 1; if (nc > 1) { n2 += 1; } } } for (; i < 21; i++) { int nc = aRoll[i].nChequers; np += aRoll[i].nPips * 2; if (nc > 0) { n1 += 2; if (nc > 1) { n2 += 2; } } } afInput[I_PIPLOSS] = (float) np / (12.0f * 36.0f); afInput[I_P1] = (float) n1 / 36.0f; afInput[I_P2] = (float) n2 / 36.0f; } afInput[I_BACKESCAPES] = (float) Escapes(anBoard, 23 - nOppBack) / 36.0f; afInput[I_BACKRESCAPES] = (float) Escapes1(anBoard, 23 - nOppBack) / 36.0f; for (n = 36, i = 15; i < 24 - nOppBack; i++) if ((j = Escapes(anBoard, i)) < n) n = j; afInput[I_ACONTAIN] = (float) (36 - n) / 36.0f; afInput[I_ACONTAIN2] = afInput[I_ACONTAIN] * afInput[I_ACONTAIN]; if (nOppBack < 0) { /* restart loop, point 24 should not be included */ i = 15; n = 36; } for (; i < 24; i++) if ((j = Escapes(anBoard, i)) < n) n = j; afInput[I_CONTAIN] = (float) (36 - n) / 36.0f; afInput[I_CONTAIN2] = afInput[I_CONTAIN] * afInput[I_CONTAIN]; for (n = 0, i = 6; i < 25; i++) if (anBoard[i]) n += (i - 5) * anBoard[i] * Escapes(anBoardOpp, i); afInput[I_MOBILITY] = (float) n / 3600.0f; j = 0; n = 0; for (i = 0; i < 25; i++) { int ni = anBoard[i]; if (ni) { j += ni; n += i * ni; } } // cppcheck-suppress zerodiv n = (n + j - 1) / j; j = 0; for (k = 0, i = n + 1; i < 25; i++) { int ni = anBoard[i]; if (ni) { j += ni; k += ni * (i - n) * (i - n); } } if (j) { k = (k + j - 1) / j; } afInput[I_MOMENT2] = (float) k / 400.0f; if (anBoard[24] > 0) { int loss = 0; int two = anBoard[24] > 1; for (i = 0; i < 6; ++i) { if (anBoardOpp[i] > 1) { /* any double loses */ loss += 4 * (i + 1); for (j = i + 1; j < 6; ++j) { if (anBoardOpp[j] > 1) { loss += 2 * (i + j + 2); } else { if (two) { loss += 2 * (i + 1); } } } } else { if (two) { for (j = i + 1; j < 6; ++j) { if (anBoardOpp[j] > 1) { loss += 2 * (j + 1); } } } } } afInput[I_ENTER] = (float) loss / (36.0f * (49.0f / 6.0f)); } else { afInput[I_ENTER] = 0.0f; } n = 0; for (i = 0; i < 6; i++) { n += anBoardOpp[i] > 1; } afInput[I_ENTER2] = (float) (36 - (n - 6) * (n - 6)) / 36.0f; { int pa = -1; int w = 0; int tot = 0; int np; for (np = 23; np > 0; --np) { if (unlikely(anBoard[np] >= 2)) { if (pa == -1) { pa = np; continue; } { int d = pa - np; static const int ac[23] = { 11, 11, 11, 11, 11, 11, 11, 6, 5, 4, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; w += ac[d] * anBoard[pa]; tot += anBoard[pa]; } } } if (tot) { afInput[I_BACKBONE] = 1.0f - ((float) w / ((float) tot * 11.0f)); } else { afInput[I_BACKBONE] = 0.0f; } } { unsigned int nAc = 0; for (i = 18; i < 24; ++i) { if (anBoard[i] > 1) { ++nAc; } } afInput[I_BACKG] = 0.0f; afInput[I_BACKG1] = 0.0f; if (nAc >= 1) { unsigned int tot = 0; for (i = 18; i < 25; ++i) { tot += anBoard[i]; } if (nAc > 1) { /* g_assert( tot >= 4 ); */ afInput[I_BACKG] = (float) (tot - 3) / 4.0f; } else { /* nAc == 1 */ afInput[I_BACKG1] = (float) tot / 8.0f; } } } } static void CalculateRaceInputs(const TanBoard anBoard, float inputs[]) { unsigned int side; for (side = 0; side < 2; ++side) { unsigned int i, k; const unsigned int *const board = anBoard[side]; float *const afInput = inputs + side * HALF_RACE_INPUTS; unsigned int menOff = 15; { g_assert(board[23] == 0 && board[24] == 0); } /* Points */ for (i = 0; i < 23; ++i) { unsigned int const nc = board[i]; k = i * 4; menOff -= nc; afInput[k++] = (nc == 1) ? 1.0f : 0.0f; afInput[k++] = (nc == 2) ? 1.0f : 0.0f; afInput[k++] = (nc >= 3) ? 1.0f : 0.0f; afInput[k] = nc > 3 ? (float) (nc - 3) / 2.0f : 0.0f; } /* Men off */ for (k = 0; k < 14; ++k) { afInput[RI_OFF + k] = (menOff == (k + 1)) ? 1.0f : 0.0f; } { unsigned int nCross = 0; for (k = 1; k < 4; ++k) { for (i = 6 * k; i < 6 * k + 6; ++i) { unsigned int const nc = board[i]; if (nc) { nCross += nc * k; } } } afInput[RI_NCROSS] = (float) nCross / 10.0f; } } } /* baseInputs() is now in lib/inputs.c */ static void menOffAll(const unsigned int *anBoard, float *afInput) { /* Men off */ int menOff = 15; int i; for (i = 0; i < 25; i++) { menOff -= anBoard[i]; } if (menOff <= 5) { afInput[0] = menOff ? (float) menOff / 5.0f : 0.0f; afInput[1] = 0.0f; afInput[2] = 0.0f; } else if (menOff <= 10) { afInput[0] = 1.0f; afInput[1] = (float) (menOff - 5) / 5.0f; afInput[2] = 0.0f; } else { afInput[0] = 1.0f; afInput[1] = 1.0f; afInput[2] = ((float) menOff - 10) / 5.0f; } } static void menOffNonCrashed(const unsigned int *anBoard, float *afInput) { int menOff = 15; int i; for (i = 0; i < 25; ++i) { menOff -= anBoard[i]; } { g_assert(menOff <= 8); } if (menOff <= 2) { afInput[0] = menOff ? (float) menOff / 3.0f : 0.0f; afInput[1] = 0.0f; afInput[2] = 0.0f; } else if (menOff <= 5) { afInput[0] = 1.0f; afInput[1] = (float) (menOff - 3) / 3.0f; afInput[2] = 0.0f; } else { afInput[0] = 1.0f; afInput[1] = 1.0f; afInput[2] = (float) (menOff - 6) / 3.0f; } } /* Calculates contact neural net inputs from the board position. */ static void CalculateContactInputs(const TanBoard anBoard, float arInput[]) { baseInputs(anBoard, arInput); { float *b = arInput + MINPPERPOINT * 25 * 2; /* I accidentally switched sides (0 and 1) when I trained the net */ menOffNonCrashed(anBoard[0], b + I_OFF1); CalculateHalfInputs(anBoard[1], anBoard[0], b); } { float *b = arInput + (MINPPERPOINT * 25 * 2 + MORE_INPUTS); menOffNonCrashed(anBoard[1], b + I_OFF1); CalculateHalfInputs(anBoard[0], anBoard[1], b); } } /* Calculates crashed neural net inputs from the board position. */ static void CalculateCrashedInputs(const TanBoard anBoard, float arInput[]) { baseInputs(anBoard, arInput); { float *b = arInput + MINPPERPOINT * 25 * 2; menOffAll(anBoard[1], b + I_OFF1); CalculateHalfInputs(anBoard[1], anBoard[0], b); } { float *b = arInput + (MINPPERPOINT * 25 * 2 + MORE_INPUTS); menOffAll(anBoard[0], b + I_OFF1); CalculateHalfInputs(anBoard[0], anBoard[1], b); } } extern void swap_us(unsigned int *p0, unsigned int *p1) { unsigned int n = *p0; *p0 = *p1; *p1 = n; } extern void swap(int *p0, int *p1) { int n = *p0; *p0 = *p1; *p1 = n; } extern void SwapSides(TanBoard anBoard) { for (int i = 0; i < 25; i++) { int n = anBoard[0][i]; anBoard[0][i] = anBoard[1][i]; anBoard[1][i] = n; } } /* An upper bound on the number of turns it can take to complete a bearoff * from bearoff position ID i. */ static int MaxTurns(int id) { unsigned short int aus[32]; int i; BearoffDist(pbc1, id, NULL, NULL, NULL, aus, NULL); for (i = 31; i >= 0; i--) { if (aus[i]) return i; } return -1; } extern void SanityCheck(const TanBoard anBoard, float arOutput[]) { int i, j, nciq, ac[2], anBack[2], anCross[2], anGammonCross[2], anMaxTurns[2], fContact; g_assert(arOutput[OUTPUT_WIN] >= 0.0f && arOutput[OUTPUT_WIN] <= 1.0f); g_assert(arOutput[OUTPUT_WINGAMMON] >= 0.0f && arOutput[OUTPUT_WINGAMMON] <= 1.0f); g_assert(arOutput[OUTPUT_WINBACKGAMMON] >= 0.0f && arOutput[OUTPUT_WINBACKGAMMON] <= 1.0f); g_assert(arOutput[OUTPUT_LOSEGAMMON] >= 0.0f && arOutput[OUTPUT_LOSEGAMMON] <= 1.0f); g_assert(arOutput[OUTPUT_LOSEBACKGAMMON] >= 0.0f && arOutput[OUTPUT_LOSEBACKGAMMON] <= 1.0f); ac[0] = ac[1] = anBack[0] = anBack[1] = anCross[0] = anCross[1] = 0; anGammonCross[0] = anGammonCross[1] = 1; for (j = 0; j < 2; j++) { for (i = 0, nciq = 0; i < 6; i++) if (anBoard[j][i]) { anBack[j] = i; nciq += anBoard[j][i]; } ac[j] = anCross[j] = nciq; for (i = 6, nciq = 0; i < 12; i++) if (anBoard[j][i]) { anBack[j] = i; nciq += anBoard[j][i]; } ac[j] += nciq; anCross[j] += 2 * nciq; anGammonCross[j] += nciq; for (i = 12, nciq = 0; i < 18; i++) if (anBoard[j][i]) { anBack[j] = i; nciq += anBoard[j][i]; } ac[j] += nciq; anCross[j] += 3 * nciq; anGammonCross[j] += 2 * nciq; for (i = 18, nciq = 0; i < 24; i++) if (anBoard[j][i]) { anBack[j] = i; nciq += anBoard[j][i]; } ac[j] += nciq; anCross[j] += 4 * nciq; anGammonCross[j] += 3 * nciq; if (anBoard[j][24]) { anBack[j] = 24; ac[j] += anBoard[j][24]; anCross[j] += 5 * anBoard[j][24]; anGammonCross[j] += 4 * anBoard[j][24]; } } fContact = anBack[0] + anBack[1] >= 24; if (unlikely(!fContact)) { for (i = 0; i < 2; i++) if (anBack[i] < 6 && pbc1) anMaxTurns[i] = MaxTurns(PositionBearoff(anBoard[i], pbc1->nPoints, pbc1->nChequers)); else anMaxTurns[i] = anCross[i] * 2; if (unlikely(!anMaxTurns[1])) anMaxTurns[1] = 1; } if (unlikely(!fContact) && anCross[0] > 4 * (anMaxTurns[1] - 1)) /* Certain win */ arOutput[OUTPUT_WIN] = 1.0f; if (unlikely(ac[0] < 15)) /* Opponent has borne off; no gammons or backgammons possible */ arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_WINBACKGAMMON] = 0.0f; else if (unlikely(!fContact)) { if (anCross[1] > 8 * anGammonCross[0]) /* Gammon impossible */ arOutput[OUTPUT_WINGAMMON] = 0.0f; else if (anGammonCross[0] > 4 * (anMaxTurns[1] - 1)) /* Certain gammon */ arOutput[OUTPUT_WINGAMMON] = 1.0f; if (anBack[0] < 18) /* Backgammon impossible */ arOutput[OUTPUT_WINBACKGAMMON] = 0.0f; } if (unlikely(!fContact) && anCross[1] > 4 * anMaxTurns[0]) /* Certain loss */ arOutput[OUTPUT_WIN] = 0.0f; if (unlikely(ac[1] < 15)) /* Player has borne off; no gammon or backgammon losses possible */ arOutput[OUTPUT_LOSEGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; else if (unlikely(!fContact)) { if (anCross[0] > 8 * anGammonCross[1] - 4) /* Gammon loss impossible */ arOutput[OUTPUT_LOSEGAMMON] = 0.0f; else if (anGammonCross[1] > 4 * anMaxTurns[0]) /* Certain gammon loss */ arOutput[OUTPUT_LOSEGAMMON] = 1.0f; if (anBack[1] < 18) /* Backgammon impossible */ arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; } /* gammons must be less than wins */ if (unlikely(arOutput[OUTPUT_WINGAMMON] > arOutput[OUTPUT_WIN])) { arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_WIN]; } { float lose = 1.0f - arOutput[OUTPUT_WIN]; if (unlikely(arOutput[OUTPUT_LOSEGAMMON] > lose)) { arOutput[OUTPUT_LOSEGAMMON] = lose; } } /* Backgammons cannot exceed gammons */ if (unlikely(arOutput[OUTPUT_WINBACKGAMMON] > arOutput[OUTPUT_WINGAMMON])) arOutput[OUTPUT_WINBACKGAMMON] = arOutput[OUTPUT_WINGAMMON]; if (unlikely(arOutput[OUTPUT_LOSEBACKGAMMON] > arOutput[OUTPUT_LOSEGAMMON])) arOutput[OUTPUT_LOSEBACKGAMMON] = arOutput[OUTPUT_LOSEGAMMON]; if (fContact) { float noise = 1 / 10000.0f; for (i = OUTPUT_WINGAMMON; i < NUM_OUTPUTS; ++i) { if (unlikely(arOutput[i] < noise)) { arOutput[i] = 0.0f; } } } } extern positionclass ClassifyPosition(const TanBoard anBoard, const bgvariation bgv) { int nOppBack, nBack; for (nOppBack = 24; nOppBack >= 0; --nOppBack) { if (anBoard[0][nOppBack]) { break; } } for (nBack = 24; nBack >= 0; --nBack) { if (anBoard[1][nBack]) { break; } } if (unlikely(nBack < 0 || nOppBack < 0)) return CLASS_OVER; /* special classes for hypergammon variants */ switch (bgv) { case VARIATION_HYPERGAMMON_1: return CLASS_HYPERGAMMON1; case VARIATION_HYPERGAMMON_2: return CLASS_HYPERGAMMON2; case VARIATION_HYPERGAMMON_3: return CLASS_HYPERGAMMON3; case VARIATION_STANDARD: case VARIATION_NACKGAMMON: /* normal backgammon */ if (nBack + nOppBack > 22) { /* contact position */ unsigned int const N = 6; unsigned int i; unsigned int side; for (side = 0; side < 2; ++side) { unsigned int tot = 0; const unsigned int *board = anBoard[side]; for (i = 0; i < 25; ++i) { tot += board[i]; } if (unlikely(tot <= N)) { return CLASS_CRASHED; } else { if (unlikely(board[0] > 1)) { if (unlikely(tot <= (N + board[0]))) { return CLASS_CRASHED; } else { if (unlikely((1 + tot - (board[0] + board[1])) <= N) && board[1] > 1) { return CLASS_CRASHED; } } } else { if (unlikely(tot <= (N + (board[1] - 1)))) { return CLASS_CRASHED; } } } } return CLASS_CONTACT; } else { if (unlikely(isBearoff(pbc2, anBoard))) return CLASS_BEAROFF2; if (unlikely(isBearoff(pbcTS, anBoard))) return CLASS_BEAROFF_TS; if (unlikely(isBearoff(pbc1, anBoard))) return CLASS_BEAROFF1; if (unlikely(isBearoff(pbcOS, anBoard))) return CLASS_BEAROFF_OS; return CLASS_RACE; } default: g_assert_not_reached(); } return CLASS_OVER; /* for fussy compilers */ } static int EvalBearoff2(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { g_assert(pbc2); return BearoffEval(pbc2, anBoard, arOutput); } static int EvalBearoffOS(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { return BearoffEval(pbcOS, anBoard, arOutput); } static int EvalBearoffTS(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { return BearoffEval(pbcTS, anBoard, arOutput); } static int EvalHypergammon1(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { return BearoffEval(apbcHyper[0], anBoard, arOutput); } static int EvalHypergammon2(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { return BearoffEval(apbcHyper[1], anBoard, arOutput); } static int EvalHypergammon3(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { return BearoffEval(apbcHyper[2], anBoard, arOutput); } static int EvalBearoff1(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * UNUSED(nnStates)) { return BearoffEval(pbc1, anBoard, arOutput); } enum { /* gammon possible by side on roll */ G_POSSIBLE = 0x1, /* backgammon possible by side on roll */ BG_POSSIBLE = 0x2, /* gammon possible by side not on roll */ OG_POSSIBLE = 0x4, /* backgammon possible by side not on roll */ OBG_POSSIBLE = 0x8 }; /* side - side that potentially can win a backgammon */ /* Return - Probablity that side will win a backgammon */ static float raceBGprob(const TanBoard anBoard, int side, const bgvariation bgv) { int totMenHome = 0; int totPipsOp = 0; unsigned int i; TanBoard dummy; for (i = 0; i < 6; ++i) { totMenHome += anBoard[side][i]; } for (i = 22; i >= 18; --i) { totPipsOp += anBoard[1 - side][i] * (i - 17); } if (!((totMenHome + 3) / 4 - (side == 1 ? 1 : 0) <= (totPipsOp + 2) / 3)) { return 0.0f; } for (i = 0; i < 25; ++i) { dummy[side][i] = anBoard[side][i]; } for (i = 0; i < 6; ++i) { dummy[1 - side][i] = anBoard[1 - side][18 + i]; } for (i = 6; i < 25; ++i) { dummy[1 - side][i] = 0; } { float p = 0.0f; const long *bgp = getRaceBGprobs(dummy[1 - side]); if (bgp) { int k = PositionBearoff(anBoard[side], pbc1->nPoints, pbc1->nChequers); unsigned short int aProb[32]; unsigned int j; unsigned long scale = (side == 0) ? 36 : 1; BearoffDist(pbc1, k, NULL, NULL, NULL, aProb, NULL); for (j = 1 - side; j < RBG_NPROBS; j++) { unsigned long sum = 0; scale *= 36; for (i = 1; i <= j + side; ++i) { sum += aProb[i]; } p += (float) bgp[j] / (float) scale *(float) sum; } p /= 65535.0f; } else { float ar[5]; if (PositionBearoff(dummy[0], 6, 15) > 923 || PositionBearoff(dummy[1], 6, 15) > 923) { EvalBearoff1((ConstTanBoard) dummy, ar, bgv, NULL); } else { EvalBearoff2((ConstTanBoard) dummy, ar, bgv, NULL); } p = (side == 1 ? ar[0] : 1.0f - ar[0]); } return MIN(p, 1.0f); } } extern void EvalRaceBG(const TanBoard anBoard, float arOutput[], const bgvariation bgv) /* anBoard[1] is on roll */ { /* total men for side not on roll */ int totMen0 = 0; /* total men for side on roll */ int totMen1 = 0; /* a set flag for every possible outcome */ int any = 0; int i; for (i = 23; i >= 0; --i) { totMen0 += anBoard[0][i]; totMen1 += anBoard[1][i]; } if (totMen1 == 15) { any |= OG_POSSIBLE; } if (totMen0 == 15) { any |= G_POSSIBLE; } if (any) { if (any & OG_POSSIBLE) { for (i = 23; i >= 18; --i) { if (anBoard[1][i] > 0) { break; } } if (i >= 18) { any |= OBG_POSSIBLE; } } if (any & G_POSSIBLE) { for (i = 23; i >= 18; --i) { if (anBoard[0][i] > 0) { break; } } if (i >= 18) { any |= BG_POSSIBLE; } } } if (any & (BG_POSSIBLE | OBG_POSSIBLE)) { /* side that can have the backgammon */ int side = (any & BG_POSSIBLE) ? 1 : 0; float pr = raceBGprob(anBoard, side, bgv); if (pr > 0.0f) { if (side == 1) { arOutput[OUTPUT_WINBACKGAMMON] = pr; if (arOutput[OUTPUT_WINGAMMON] < arOutput[OUTPUT_WINBACKGAMMON]) { arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_WINBACKGAMMON]; } } else { arOutput[OUTPUT_LOSEBACKGAMMON] = pr; if (arOutput[OUTPUT_LOSEGAMMON] < arOutput[OUTPUT_LOSEBACKGAMMON]) { arOutput[OUTPUT_LOSEGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON]; } } } else { if (side == 1) { arOutput[OUTPUT_WINBACKGAMMON] = 0.0f; } else { arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; } } } } static int EvalRace(const TanBoard anBoard, float arOutput[], const bgvariation bgv, NNState * nnStates) { SSE_ALIGN(float arInput[NUM_RACE_INPUTS]); CalculateRaceInputs(anBoard, arInput); #if defined(USE_SIMD_INSTRUCTIONS) if (NeuralNetEvaluateSSE(&nnRace, arInput, arOutput, nnStates ? nnStates + (CLASS_RACE - CLASS_RACE) : NULL)) #else if (NeuralNetEvaluate(&nnRace, arInput, arOutput, nnStates ? nnStates + (CLASS_RACE - CLASS_RACE) : NULL)) #endif return -1; /* special evaluation of backgammons overrides net output */ EvalRaceBG(anBoard, arOutput, bgv); /* sanity check will take care of rest */ return 0; } static int EvalContact(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * nnStates) { SSE_ALIGN(float arInput[NUM_INPUTS]); CalculateContactInputs(anBoard, arInput); #if defined(USE_SIMD_INSTRUCTIONS) return NeuralNetEvaluateSSE(&nnContact, arInput, arOutput, nnStates ? nnStates + (CLASS_CONTACT - CLASS_RACE) : NULL); #else return NeuralNetEvaluate(&nnContact, arInput, arOutput, nnStates ? nnStates + (CLASS_CONTACT - CLASS_RACE) : NULL); #endif } static int EvalCrashed(const TanBoard anBoard, float arOutput[], const bgvariation UNUSED(bgv), NNState * nnStates) { SSE_ALIGN(float arInput[NUM_INPUTS]); CalculateCrashedInputs(anBoard, arInput); #if defined(USE_SIMD_INSTRUCTIONS) return NeuralNetEvaluateSSE(&nnCrashed, arInput, arOutput, nnStates ? nnStates + (CLASS_CRASHED - CLASS_RACE) : NULL); #else return NeuralNetEvaluate(&nnCrashed, arInput, arOutput, nnStates ? nnStates + (CLASS_CRASHED - CLASS_RACE) : NULL); #endif } extern int EvalOver(const TanBoard anBoard, float arOutput[], const bgvariation bgv, NNState * UNUSED(nnStates)) { int i, c; int n = anChequers[bgv]; for (i = 0; i < 25; i++) if (anBoard[0][i]) break; if (i == 25) { /* opponent has no pieces on board; player has lost */ arOutput[OUTPUT_WIN] = arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_WINBACKGAMMON] = 0.0f; for (i = 0, c = 0; i < 25; i++) c += anBoard[1][i]; if (c == n) { /* player still has all pieces on board; loses gammon */ arOutput[OUTPUT_LOSEGAMMON] = 1.0f; for (i = 18; i < 25; i++) if (anBoard[1][i]) { /* player still has pieces in opponent's home board; * loses backgammon */ arOutput[OUTPUT_LOSEBACKGAMMON] = 1.0f; return 0; } arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; return 0; } arOutput[OUTPUT_LOSEGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; return 0; } for (i = 0; i < 25; i++) if (anBoard[1][i]) break; if (i == 25) { /* player has no pieces on board; wins */ arOutput[OUTPUT_WIN] = 1.0f; arOutput[OUTPUT_LOSEGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; for (i = 0, c = 0; i < 25; i++) c += anBoard[0][i]; if (c == n) { /* opponent still has all pieces on board; win gammon */ arOutput[OUTPUT_WINGAMMON] = 1.0f; for (i = 18; i < 25; i++) if (anBoard[0][i]) { /* opponent still has pieces in player's home board; * win backgammon */ arOutput[OUTPUT_WINBACKGAMMON] = 1.0f; return 0; } arOutput[OUTPUT_WINBACKGAMMON] = 0.0f; return 0; } arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_WINBACKGAMMON] = 0.0f; } return 0; } classevalfunc acef[N_CLASSES] = { EvalOver, EvalHypergammon1, EvalHypergammon2, EvalHypergammon3, EvalBearoff2, EvalBearoffTS, EvalBearoff1, EvalBearoffOS, EvalRace, EvalCrashed, EvalContact }; extern float Noise(const evalcontext * pec, const TanBoard anBoard, int iOutput) { float r; if (pec->fDeterministic) { char auchBoard[50]; unsigned char auch[16]; int i; for (i = 0; i < 25; i++) { auchBoard[i << 1] = (char) anBoard[0][i]; auchBoard[(i << 1) + 1] = (char) anBoard[1][i]; } auchBoard[0] += (char) iOutput; md5_buffer(auchBoard, 50, auch); /* We can't use a Box-Muller transform here, because generating * a point in the unit circle requires a potentially unbounded * number of integers, and all we have is the board. So we * just take the sum of the bytes in the hash, which (by the * central limit theorem) should have a normal-ish distribution. */ r = 0.0f; for (i = 0; i < 16; i++) r += auch[i]; r -= 2040.0f; r /= 295.6f; } else { /* Box-Muller transform of a point in the unit circle. */ float x, y; #if defined(USE_MULTITHREAD) g_mutex_lock(&noiseRNGMutex); #endif do { x = (float) irand(&rc) * 2.0f / (float) UB4MAXVAL - 1.0f; y = (float) irand(&rc) * 2.0f / (float) UB4MAXVAL - 1.0f; r = x * x + y * y; } while (r > 1.0f || r == 0.0f); #if defined(USE_MULTITHREAD) g_mutex_unlock(&noiseRNGMutex); #endif r = y * sqrtf(-2.0f * logf(r) / r); (void) x; } r *= pec->rNoise; if (iOutput == OUTPUT_WINGAMMON || iOutput == OUTPUT_LOSEGAMMON) r *= 0.25f; else if (iOutput == OUTPUT_WINBACKGAMMON || iOutput == OUTPUT_LOSEBACKGAMMON) r *= 0.01f; return r; } extern int EvalKey(const evalcontext * pec, const int nPlies, const cubeinfo * pci, int fCubefulEquity) { int iKey; /* * Bit 00-03: nPlies * Bit 04 : fCubeful * Bit 05 : fMove * Bit 06 : fUsePrune * Bit 07-12: anScore[ 0 ] * Bit 13-18: anScore[ 1 ] * Bit 19-22: log2(nCube) * Bit 23-24: fCubeOwner * Bit 25 : fCrawford * Bit 26 : fJacoby * Bit 27 : fBeavers */ iKey = (nPlies | (pec->fCubeful << 4) | (pci->fMove << 5)); if (nPlies) iKey ^= ((pec->fUsePrune) << 6); if (nPlies || fCubefulEquity) { /* In match play, the score and cube value and position are important. */ if (pci->nMatchTo) iKey ^= ((pci->nMatchTo - pci->anScore[pci->fMove] - 1) << 7) ^ ((pci->nMatchTo - pci->anScore[!pci->fMove] - 1) << 13) ^ (LogCube(pci->nCube) << 19) ^ ((pci->fCubeOwner < 0 ? 2 : pci->fCubeOwner == pci->fMove) << 23) ^ (pci->fCrawford << 25); else if (pec->fCubeful || fCubefulEquity) /* in cubeful money games the cube position and rules are important. */ iKey ^= ((pci->fCubeOwner < 0 ? 2 : pci->fCubeOwner == pci->fMove) << 23) ^ (pci->fJacoby << 26) ^ (pci->fBeavers << 27); if (fCubefulEquity) iKey ^= 0x6a47b47e; } return iKey; } extern int PerfectCubeful(bearoffcontext * pbc, const TanBoard anBoard, float arEquity[]) { unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers); unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers); unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints); unsigned int iPos = nUs * n + nThem; return BearoffCubeful(pbc, iPos, arEquity, NULL); } extern int EvaluatePerfectCubeful(const TanBoard anBoard, float arEquity[], const bgvariation bgv) { positionclass pc = ClassifyPosition(anBoard, bgv); g_assert(pc <= CLASS_PERFECT); switch (pc) { case CLASS_BEAROFF2: return PerfectCubeful(pbc2, anBoard, arEquity); case CLASS_BEAROFF_TS: return PerfectCubeful(pbcTS, anBoard, arEquity); default: g_assert_not_reached(); } return -1; } extern void InvertEvaluation(float ar[NUM_OUTPUTS]) { float r; ar[OUTPUT_WIN] = 1.0f - ar[OUTPUT_WIN]; r = ar[OUTPUT_WINGAMMON]; ar[OUTPUT_WINGAMMON] = ar[OUTPUT_LOSEGAMMON]; ar[OUTPUT_LOSEGAMMON] = r; r = ar[OUTPUT_WINBACKGAMMON]; ar[OUTPUT_WINBACKGAMMON] = ar[OUTPUT_LOSEBACKGAMMON]; ar[OUTPUT_LOSEBACKGAMMON] = r; } extern void InvertEvaluationR(float ar[NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci) { /* invert win, gammon etc. */ InvertEvaluation(ar); /* invert equities */ ar[OUTPUT_EQUITY] = -ar[OUTPUT_EQUITY]; if (pci->nMatchTo) ar[OUTPUT_CUBEFUL_EQUITY] = 1.0f - ar[OUTPUT_CUBEFUL_EQUITY]; else ar[OUTPUT_CUBEFUL_EQUITY] = -ar[OUTPUT_CUBEFUL_EQUITY]; } extern int GameStatus(const TanBoard anBoard, const bgvariation bgv) { SSE_ALIGN(float ar[NUM_OUTPUTS]) = { 0.0f }; if (ClassifyPosition(anBoard, bgv) != CLASS_OVER) return 0; EvalOver(anBoard, ar, bgv, NULL); if (ar[OUTPUT_WINBACKGAMMON] > 0.0f || ar[OUTPUT_LOSEBACKGAMMON] > 0.0f) return 3; if (ar[OUTPUT_WINGAMMON] > 0.0f || ar[OUTPUT_LOSEGAMMON] > 0.0f) return 2; return 1; } /* * Utility returns the "correct" cubeless equity based on the current * gammon values. * * Use UtilityME to get the "true" money equity. */ extern float Utility(float ar[NUM_OUTPUTS], const cubeinfo * pci) { if (!pci->nMatchTo) { /* equity calculation for money game */ /* For money game the gammon price is the same for both * players, so there is no need to use pci->fMove. */ return ar[OUTPUT_WIN] * 2.0f - 1.0f + (ar[OUTPUT_WINGAMMON] - ar[OUTPUT_LOSEGAMMON]) * pci->arGammonPrice[0] + (ar[OUTPUT_WINBACKGAMMON] - ar[OUTPUT_LOSEBACKGAMMON]) * pci->arGammonPrice[1]; } else { /* equity calculation for match play */ return ar[OUTPUT_WIN] * 2.0f - 1.0f + ar[OUTPUT_WINGAMMON] * pci->arGammonPrice[pci->fMove] - ar[OUTPUT_LOSEGAMMON] * pci->arGammonPrice[!pci->fMove] + ar[OUTPUT_WINBACKGAMMON] * pci->arGammonPrice[2 + pci->fMove] - ar[OUTPUT_LOSEBACKGAMMON] * pci->arGammonPrice[2 + !pci->fMove]; } } /* * UtilityME is identical to Utility for match play. * For money play it returns the money equity instead of the * correct cubeless equity. */ extern float UtilityME(float ar[NUM_OUTPUTS], const cubeinfo * pci) { if (!pci->nMatchTo) /* calculate money equity */ return ar[OUTPUT_WIN] * 2.0f - 1.0f + (ar[OUTPUT_WINGAMMON] - ar[OUTPUT_LOSEGAMMON]) + (ar[OUTPUT_WINBACKGAMMON] - ar[OUTPUT_LOSEBACKGAMMON]); else return Utility(ar, pci); } extern float mwc2eq(const float rMwc, const cubeinfo * pci) { /* mwc if I win/lose */ float rMwcWin, rMwcLose; rMwcWin = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); rMwcLose = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, !pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); /* * make linear inter- or extrapolation: * equity mwc * -1 rMwcLose * +1 rMwcWin * * Interpolation formula: * * 2 * rMwc - ( rMwcWin + rMwcLose ) * rEq = --------------------------------- * rMwcWin - rMwcLose * * FIXME: numerical problems? * If you are trailing 30-away, 1-away the difference between * 29-away, 1-away and 30-away, 0-away is not very large, and it may * give numerical problems. * */ return (2.0f * rMwc - (rMwcWin + rMwcLose)) / (rMwcWin - rMwcLose); } extern float eq2mwc(const float rEq, const cubeinfo * pci) { /* mwc if I win/lose */ float rMwcWin, rMwcLose; rMwcWin = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); rMwcLose = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, !pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); /* * Linear inter- or extrapolation. * Solve the formula in the routine above (mwc2eq): * * rEq * ( rMwcWin - rMwcLose ) + ( rMwcWin + rMwcLose ) * rMwc = ----------------------------------------------------- * 2 */ return 0.5f * (rEq * (rMwcWin - rMwcLose) + (rMwcWin + rMwcLose)); } /* * Convert standard error MWC to standard error equity * */ extern float se_mwc2eq(const float rMwc, const cubeinfo * pci) { /* mwc if I win/lose */ float rMwcWin, rMwcLose; rMwcWin = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); rMwcLose = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, !pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); return 2.0f / (rMwcWin - rMwcLose) * rMwc; } /* * Convert standard error equity to standard error mwc * */ extern float se_eq2mwc(const float rEq, const cubeinfo * pci) { /* mwc if I win/lose */ float rMwcWin, rMwcLose; rMwcWin = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); rMwcLose = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, !pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); /* * Linear inter- or extrapolation. * Solve the formula in the routine above (mwc2eq): * * rEq * ( rMwcWin - rMwcLose ) + ( rMwcWin + rMwcLose ) * rMwc = ----------------------------------------------------- * 2 */ return 0.5f * rEq * (rMwcWin - rMwcLose); } extern int ApplySubMove(TanBoard anBoard, const int iSrc, const int nRoll, const int fCheckLegal) { int iDest = iSrc - nRoll; if (fCheckLegal && (nRoll < 1 || nRoll > 6)) { /* Invalid dice roll */ errno = EINVAL; return -1; } if (iSrc < 0 || iSrc > 24 || iDest >= iSrc || anBoard[1][iSrc] < 1) { /* Invalid point number, or source point is empty */ errno = EINVAL; return -1; } anBoard[1][iSrc]--; if (iDest < 0) return 0; if (anBoard[0][23 - iDest]) { if (anBoard[0][23 - iDest] > 1) { /* Trying to move to a point already made by the opponent */ errno = EINVAL; return -1; } anBoard[1][iDest] = 1; anBoard[0][23 - iDest] = 0; anBoard[0][24]++; } else anBoard[1][iDest]++; return 0; } extern int ApplyMove(TanBoard anBoard, const int anMove[8], const int fCheckLegal) { int i; for (i = 0; i < 8 && anMove[i] >= 0; i += 2) if (ApplySubMove(anBoard, anMove[i], anMove[i] - anMove[i + 1], fCheckLegal)) return -1; return 0; } static void SaveMoves(movelist * pml, unsigned int cMoves, unsigned int cPip, int anMoves[], const TanBoard anBoard, int fPartial) { unsigned int i, j; move *pm; positionkey key; if (fPartial) { /* Save all moves, even incomplete ones */ if (cMoves > pml->cMaxMoves) pml->cMaxMoves = cMoves; if (cPip > pml->cMaxPips) pml->cMaxPips = cPip; } else { /* Save only legal moves: if the current move moves plays less * chequers or pips than those already found, it is illegal; if * it plays more, the old moves are illegal. */ if (cMoves < pml->cMaxMoves || cPip < pml->cMaxPips) return; if (cMoves > pml->cMaxMoves || cPip > pml->cMaxPips) pml->cMoves = 0; pml->cMaxMoves = cMoves; pml->cMaxPips = cPip; } PositionKey(anBoard, &key); for (i = 0; i < pml->cMoves; i++) { pm = &(pml->amMoves[i]); if (EqualKeys(key, pm->key)) { if (cMoves > pm->cMoves || cPip > pm->cPips) { for (j = 0; j < cMoves * 2; j++) pm->anMove[j] = anMoves[j] > -1 ? anMoves[j] : -1; if (cMoves < 4) pm->anMove[cMoves * 2] = -1; pm->cMoves = cMoves; pm->cPips = cPip; } return; } } pm = pml->amMoves + pml->cMoves; for (i = 0; i < cMoves * 2; i++) pm->anMove[i] = anMoves[i] > -1 ? anMoves[i] : -1; if (cMoves < 4) pm->anMove[cMoves * 2] = -1; CopyKey(key, pm->key); pm->cMoves = cMoves; pm->cPips = cPip; pm->cmark = CMARK_NONE; for (i = 0; i < NUM_OUTPUTS; i++) pm->arEvalMove[i] = 0.0f; pml->cMoves++; g_assert(pml->cMoves < MAX_INCOMPLETE_MOVES); } static int LegalMove(const TanBoard anBoard, int iSrc, int nPips) { int nBack; const int iDest = iSrc - nPips; if (iDest >= 0) { /* Here we can do the Chris rule check */ return (anBoard[0][23 - iDest] < 2); } /* otherwise, attempting to bear off */ for (nBack = 24; nBack > 0; nBack--) if (anBoard[1][nBack] > 0) break; return (nBack <= 5 && (iSrc == nBack || iDest == -1)); } static int GenerateMovesSub(movelist * pml, int anRoll[], int nMoveDepth, int iPip, int cPip, const TanBoard anBoard, int anMoves[], int fPartial) { int i, fUsed = 0; TanBoard anBoardNew; if (nMoveDepth > 3 || !anRoll[nMoveDepth]) return TRUE; if (anBoard[1][24]) { /* on bar */ if (anBoard[0][anRoll[nMoveDepth] - 1] >= 2) return TRUE; anMoves[nMoveDepth * 2] = 24; anMoves[nMoveDepth * 2 + 1] = 24 - anRoll[nMoveDepth]; for (i = 0; i < 25; i++) { anBoardNew[0][i] = anBoard[0][i]; anBoardNew[1][i] = anBoard[1][i]; } ApplySubMove(anBoardNew, 24, anRoll[nMoveDepth], TRUE); if (GenerateMovesSub(pml, anRoll, nMoveDepth + 1, 23, cPip + anRoll[nMoveDepth], (ConstTanBoard) anBoardNew, anMoves, fPartial)) SaveMoves(pml, nMoveDepth + 1, cPip + anRoll[nMoveDepth], anMoves, (ConstTanBoard) anBoardNew, fPartial); return fPartial; } else { for (i = iPip; i >= 0; i--) if (anBoard[1][i] && LegalMove(anBoard, i, anRoll[nMoveDepth])) { anMoves[nMoveDepth * 2] = i; anMoves[nMoveDepth * 2 + 1] = i - anRoll[nMoveDepth]; memcpy(anBoardNew, anBoard, sizeof(anBoardNew)); ApplySubMove(anBoardNew, i, anRoll[nMoveDepth], TRUE); if (GenerateMovesSub(pml, anRoll, nMoveDepth + 1, anRoll[0] == anRoll[1] ? i : 23, cPip + anRoll[nMoveDepth], (ConstTanBoard) anBoardNew, anMoves, fPartial)) SaveMoves(pml, nMoveDepth + 1, cPip + anRoll[nMoveDepth], anMoves, (ConstTanBoard) anBoardNew, fPartial); fUsed = 1; } } return !fUsed || fPartial; } extern int CompareMoves(const move * pm0, const move * pm1) { /* return (pm1->rScore > pm0->rScore || (pm1->rScore == pm0->rScore && pm1->rScore2 > pm0->rScore2)) ? 1 : -1; */ /* high score first */ if (pm0->rScore > pm1->rScore) return -1; if (pm0->rScore < pm1->rScore) return 1; if (pm0->rScore2 > pm1->rScore2) return -1; if (pm0->rScore2 < pm1->rScore2) return 1; /* (pm0->rScore == pm1->rScore) && (pm0->rScore2 == pm1->rScore2) * when it is used directly as a qsort() comparator */ return 0; } static int CompareMovesGeneral(const move * pm0, const move * pm1) { TanBoard board[2]; int back[2] = { -1, -1 }; int cleft[2] = { 0, 0 }; int a, b; int i = cmp_evalsetup(&pm0->esMove, &pm1->esMove); if (i) return -i; /* sort descending */ /* find the "back" chequer and the number of chequers left */ PositionFromKey(board[0], &pm0->key); PositionFromKey(board[1], &pm1->key); for (a = 0; a < 2; a++) { for (b = 0; b <= 24 ; b++) { if (board[a][1][b] > 0) { back[a] = b; cleft[a] += board[a][1][b]; } } } /* Rounding errors when collating evaluations with lookahead make * comparing for equality unreliable. The first check below catches * situations that would be obvious and puzzling to a human opponent. * In general, the third check is probably not reached as often as it * should and, among equal moves, the most "natural" one may not * always be chosen. */ /* Winning now is always at least as good as winning later */ if (back[0] == -1 && back[1] != -1) return -1; if (back[1] == -1 && back[0] != -1) return 1; if (pm0->rScore != pm1->rScore || pm0->rScore2 != pm1->rScore2) return CompareMoves(pm0, pm1); /* If everything else is equal bear as many men off as possible */ if (cleft[0] > cleft[1]) return 1; if (cleft[0] < cleft[1]) return -1; /* If everything else is equal "back" chequer at high point bad */ if (back[0] > back[1]) return 1; if (back[0] < back[1]) return -1; return 0; } extern int GenerateMoves(movelist * pml, const TanBoard anBoard, int n0, int n1, int fPartial) { int anRoll[4], anMoves[8]; anRoll[0] = n0; anRoll[1] = n1; anRoll[2] = anRoll[3] = ((n0 == n1) ? n0 : 0); pml->cMoves = pml->cMaxMoves = pml->cMaxPips = pml->iMoveBest = 0; pml->amMoves = MT_Get_aMoves(); GenerateMovesSub(pml, anRoll, 0, 23, 0, anBoard, anMoves, fPartial); if (anRoll[0] != anRoll[1]) { swap(anRoll, anRoll + 1); GenerateMovesSub(pml, anRoll, 0, 23, 0, anBoard, anMoves, fPartial); } return pml->cMoves; } extern float KleinmanCount(int nPipOnRoll, int nPipNotOnRoll) { int nDiff, nSum; nDiff = nPipNotOnRoll - nPipOnRoll; nSum = nPipNotOnRoll + nPipOnRoll; if (nSum > 4) { float rK = (float) (nDiff + 4) / (2.0f * sqrtf((float) (nSum - 4))); return 0.5f * (1.0f + erff(rK)); } else return 0.f; } extern int KeithCount(const TanBoard anBoard, int pn[2]) { unsigned int anPips[2]; int i, x; PipCount(anBoard, anPips); for (i = 0; i < 2; i++) { pn[i] = anPips[i]; pn[i] += (MAX(1, anBoard[i][0]) - 1) * 2; pn[i] += MAX(1, anBoard[i][1]) - 1; pn[i] += MAX(3, anBoard[i][2]) - 3; for (x = 3; x < 6; x++) if (!anBoard[i][x]) pn[i]++; } return 0; } extern int IsightCount(const TanBoard anBoard, int pn[2]) { unsigned int anPips[2]; int anMenLeft[2] = { 0, 0 }, anCrossOver[2] = { 0, 0 }; int i, x; PipCount(anBoard, anPips); for (x = 0; x < 25; x++) { anMenLeft[0] += anBoard[0][x]; anMenLeft[1] += anBoard[1][x]; anCrossOver[0] += anBoard[0][x] * (x / 6); anCrossOver[1] += anBoard[1][x] * (x / 6); } for (i = 0; i < 2; i++) { pn[i] = anPips[i]; if (anMenLeft[i] > anMenLeft[1 - i]) pn[i] += (anMenLeft[i] - anMenLeft[1 - i]); pn[i] += (MAX(2, anBoard[i][0]) - 2) * 2; pn[i] += MAX(2, anBoard[i][1]) - 2; pn[i] += MAX(3, anBoard[i][2]) - 3; for (x = 3; x < 6; x++) if (!anBoard[i][x] && anBoard[1 - i][x]) pn[i]++; if (anCrossOver[i] > anCrossOver[1 - i]) pn[i] += (anCrossOver[i] - anCrossOver[1 - i]); } return 0; } extern int ThorpCount(const TanBoard anBoard, int *pnLeader, float *adjusted, int *pnTrailer) { int anCovered[2], anMenLeft[2]; int x; unsigned int anPips[2]; PipCount(anBoard, anPips); anMenLeft[0] = 0; anMenLeft[1] = 0; for (x = 0; x < 25; x++) { anMenLeft[0] += anBoard[0][x]; anMenLeft[1] += anBoard[1][x]; } anCovered[0] = 0; anCovered[1] = 0; for (x = 0; x < 6; x++) { if (anBoard[0][x]) anCovered[0]++; if (anBoard[1][x]) anCovered[1]++; } *pnLeader = anPips[1]; *pnLeader += 2 * anMenLeft[1]; *pnLeader += anBoard[1][0]; *pnLeader -= anCovered[1]; if (*pnLeader > 30) *adjusted = (float) *pnLeader * 1.1f; else *adjusted = (float) *pnLeader; *pnTrailer = anPips[0]; *pnTrailer += 2 * anMenLeft[0]; *pnTrailer += anBoard[0][0]; *pnTrailer -= anCovered[0]; return 0; } extern void PipCount(const TanBoard anBoard, unsigned int anPips[2]) { int i; anPips[0] = 0; anPips[1] = 0; for (i = 0; i < 25; i++) { anPips[0] += anBoard[0][i] * (i + 1); anPips[1] += anBoard[1][i] * (i + 1); } } static void StatusHypergammon1(char *sz) { BearoffStatus(apbcHyper[0], sz); } static void StatusHypergammon2(char *sz) { BearoffStatus(apbcHyper[1], sz); } static void StatusHypergammon3(char *sz) { BearoffStatus(apbcHyper[2], sz); } static void StatusBearoff2(char *sz) { BearoffStatus(pbc2, sz); } static void StatusBearoff1(char *sz) { BearoffStatus(pbc1, sz); } static void StatusNeuralNet(neuralnet * pnn, char *szTitle, char *sz) { char buf[200]; sz += sprintf(sz, " * %s %s:\n", szTitle, _("neural network evaluator")); sprintf(buf, _("version %s, %u inputs, %u hidden units"), WEIGHTS_VERSION, pnn->cInput, pnn->cHidden); sprintf(sz, " - %s.\n\n", buf); } static void StatusRace(char *sz) { StatusNeuralNet(&nnRace, _("Race"), sz); } static void StatusCrashed(char *sz) { StatusNeuralNet(&nnCrashed, _("Crashed"), sz); } static void StatusContact(char *sz) { StatusNeuralNet(&nnContact, _("Contact"), sz); } static void StatusOS(char *sz) { BearoffStatus(pbcOS, sz); } static void StatusTS(char *sz) { BearoffStatus(pbcTS, sz); } static classstatusfunc acsf[N_CLASSES] = { NULL, StatusHypergammon1, StatusHypergammon2, StatusHypergammon3, StatusBearoff2, StatusTS, StatusBearoff1, StatusOS, StatusRace, StatusCrashed, StatusContact }; extern void EvalStatus(char *szOutput) { int i; *szOutput = 0; for (i = N_CLASSES - 1; i >= 0; i--) if (acsf[i]) acsf[i] (strchr(szOutput, 0)); sprintf(strchr(szOutput, 0), _(" * " "Weights file and databases installed in" ":\n - %s\n"), getPkgDataDir()); } extern char* GetCubeRecommendation(const cubedecision cd) { switch (cd) { case DOUBLE_TAKE: return _("Double, take"); case DOUBLE_PASS: return _("Double, pass"); case NODOUBLE_TAKE: return _("No double, take"); case TOOGOOD_TAKE: return _("Too good to double, take"); case TOOGOOD_PASS: return _("Too good to double, pass"); case DOUBLE_BEAVER: return _("Double, beaver"); case NODOUBLE_BEAVER: return _("No double, beaver"); case REDOUBLE_TAKE: return _("Redouble, take"); case REDOUBLE_PASS: return _("Redouble, pass"); case NO_REDOUBLE_TAKE: return _("No redouble, take"); case TOOGOODRE_TAKE: return _("Too good to redouble, take"); case TOOGOODRE_PASS: return _("Too good to redouble, pass"); case NO_REDOUBLE_BEAVER: return _("No redouble, beaver"); case NODOUBLE_DEADCUBE: return _("Never double, take (dead cube)"); case NO_REDOUBLE_DEADCUBE: return _("Never redouble, take (dead cube)"); case OPTIONAL_DOUBLE_BEAVER: return _("Optional double, beaver"); case OPTIONAL_DOUBLE_TAKE: return _("Optional double, take"); case OPTIONAL_REDOUBLE_TAKE: return _("Optional redouble, take"); case OPTIONAL_DOUBLE_PASS: return _("Optional double, pass"); case OPTIONAL_REDOUBLE_PASS: return _("Optional redouble, pass"); case NOT_AVAILABLE: return _("Cube not available"); default: return _("Unknown cube decision"); } } extern void EvalCacheFlush(void) { CacheFlush(&cEval); } void CommandClearCache(char *UNUSED(sz)) { EvalCacheFlush(); } extern double GetEvalCacheSize(void) { if (cEval.size == 0) return 0; else { double value = log(cEval.size) / log(2); if (value < 15) return 0; if (value < 17) return .5; /* Special case for old default 65536 */ if (value >= CACHE_SIZE_GUIMAX) return CACHE_SIZE_GUIMAX - 16; /* Maximum value */ else return value - 16; } } extern void SetEvalCacheSize(unsigned int size) { EvalCacheResize((size == 0) ? 0 : 1U << (size + 16)); } extern unsigned int GetEvalCacheEntries(void) { return cCache; } extern int GetCacheMB(int size) { if (size <= 0) return 0; else return (1 << (size + 15)) * (int) sizeof(cacheNode) / (1024 * 1024); } extern int EvalCacheResize(unsigned int cNew) { int cActual = CacheResize(&cEval, cNew); if (cActual < 0) { outputerrf(_("Evaluation cache resize failed; keeping the previous size\n")); return -1; } cCache = (unsigned int) cActual; return cActual; } #if CACHE_STATS extern int EvalCacheStats(unsigned int *pcUsed, unsigned int *pcLookup, unsigned int *pcHit) { CacheStats(&cEval, pcLookup, pcHit, pcUsed); CacheStats(&cpEval, pcLookup + 1, pcHit + 1, pcUsed + 1); return 0; } #endif extern int SetCubeInfoMoney(cubeinfo * pci, const int nCube, const int fCubeOwner, const int fMove, const int fJacoby, const int fBeavers, const bgvariation bgv) { if (nCube < 1 || fCubeOwner < -1 || fCubeOwner > 1 || fMove < 0 || fMove > 1) { /* FIXME also illegal if nCube is not a power of 2 */ memset(pci, 0, sizeof(cubeinfo)); return -1; } pci->nCube = nCube; pci->fCubeOwner = fCubeOwner; pci->fMove = fMove; pci->fJacoby = fJacoby; pci->fBeavers = fBeavers; pci->nMatchTo = pci->anScore[0] = pci->anScore[1] = pci->fCrawford = 0; pci->bgv = bgv; pci->arGammonPrice[0] = pci->arGammonPrice[1] = pci->arGammonPrice[2] = pci->arGammonPrice[3] = (fJacoby && fCubeOwner == -1) ? 0.0f : 1.0f; return 0; } static int SetCubeInfoMatch(cubeinfo * pci, const int nCube, const int fCubeOwner, const int fMove, const int nMatchTo, const int anScore[2], const int fCrawford, const bgvariation bgv) { if (nCube < 1 || fCubeOwner < -1 || fCubeOwner > 1 || fMove < 0 || fMove > 1 || nMatchTo < 1 || anScore[0] >= nMatchTo || anScore[1] >= nMatchTo) { /* FIXME also illegal if nCube is not a power of 2 */ memset(pci, 0, sizeof(cubeinfo)); return -1; } pci->nCube = nCube; pci->fCubeOwner = fCubeOwner; pci->fMove = fMove; pci->fJacoby = pci->fBeavers = FALSE; pci->nMatchTo = nMatchTo; pci->anScore[0] = anScore[0]; pci->anScore[1] = anScore[1]; pci->fCrawford = fCrawford; pci->bgv = bgv; { int nAway0 = pci->nMatchTo - pci->anScore[0] - 1; int nAway1 = pci->nMatchTo - pci->anScore[1] - 1; int nCubeIndex = MIN(LogCube(pci->nCube), MAXCUBELEVEL - 1); if ((!nAway0 || !nAway1) && !fCrawford) { if (!nAway0) memcpy(pci->arGammonPrice, aaaafGammonPricesPostCrawford[nCubeIndex] [nAway1][0], 4 * sizeof(float)); else memcpy(pci->arGammonPrice, aaaafGammonPricesPostCrawford[nCubeIndex] [nAway0][1], 4 * sizeof(float)); } else memcpy(pci->arGammonPrice, aaaafGammonPrices[nCubeIndex] [nAway0][nAway1], 4 * sizeof(float)); } return 0; } extern int SetCubeInfo(cubeinfo * pci, const int nCube, const int fCubeOwner, const int fMove, const int nMatchTo, const int anScore[2], const int fCrawford, const int fJacoby, const int fBeavers, const bgvariation bgv) { return nMatchTo ? SetCubeInfoMatch(pci, nCube, fCubeOwner, fMove, nMatchTo, anScore, fCrawford, bgv) : SetCubeInfoMoney(pci, nCube, fCubeOwner, fMove, fJacoby, fBeavers, bgv); } static int isOptional(const float r1, const float r2) { const float epsilon = 1.0e-5f; return (fabsf(r1 - r2) <= epsilon); } static int winGammon(const float arOutput[NUM_ROLLOUT_OUTPUTS]) { return (arOutput[OUTPUT_WINGAMMON] > 0.0f); } static int winAny(const float arOutput[NUM_ROLLOUT_OUTPUTS]) { return (arOutput[OUTPUT_WIN] > 0.0f); } extern cubedecision FindBestCubeDecision(float arDouble[], float aarOutput[2][NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci) { /* * FindBestCubeDecision: * * Calculate optimal cube decision and equity/mwc for this. * * Input: * arDouble - array with equities or mwc's: * arDouble[ 1 ]: no double, * arDouble[ 2 ]: double take * arDouble[ 3 ]: double pass * pci - pointer to cube info * * Output: * arDouble - array with equities or mwc's * arDouble[ 0 ]: equity for optimal cube decision * * Returns: * cube decision * */ /* Check if cube is available */ if (!GetDPEq(NULL, NULL, pci)) { arDouble[OUTPUT_OPTIMAL] = arDouble[OUTPUT_NODOUBLE]; /* for match play distinguish between dead cube and not available cube */ if (pci->nMatchTo && (pci->fCubeOwner < 0 || pci->fCubeOwner == pci->fMove)) return (pci->fCubeOwner == -1) ? NODOUBLE_DEADCUBE : NO_REDOUBLE_DEADCUBE; else return NOT_AVAILABLE; } /* Cube is available: find optimal cube action */ if ((arDouble[OUTPUT_TAKE] >= arDouble[OUTPUT_NODOUBLE]) && (arDouble[OUTPUT_DROP] >= arDouble[OUTPUT_NODOUBLE])) { /* DT >= ND and DP >= ND */ /* we have a double */ if (arDouble[OUTPUT_DROP] > arDouble[OUTPUT_TAKE]) { /* 6. DP > DT >= ND: Double, take */ int f = isOptional(arDouble[OUTPUT_TAKE], arDouble[OUTPUT_NODOUBLE]); arDouble[OUTPUT_OPTIMAL] = arDouble[OUTPUT_TAKE]; if (!pci->nMatchTo && arDouble[OUTPUT_TAKE] >= -2.0f && arDouble[OUTPUT_TAKE] <= 0.0f && pci->fBeavers) { if (arDouble[OUTPUT_TAKE] * 2.0f < arDouble[OUTPUT_NODOUBLE]) { /*not a double if we can beaver */ return NODOUBLE_BEAVER; } else /* beaver (Jacoby paradox) */ return f ? OPTIONAL_DOUBLE_BEAVER : DOUBLE_BEAVER; } else if (winAny(aarOutput[0])) { /* ...take */ if (f) return (pci->fCubeOwner == -1) ? OPTIONAL_DOUBLE_TAKE : OPTIONAL_REDOUBLE_TAKE; else return (pci->fCubeOwner == -1) ? DOUBLE_TAKE : REDOUBLE_TAKE; } else /* no (presumably optional) double if we are sure to lose */ return (pci->fCubeOwner == -1) ? NODOUBLE_TAKE : NO_REDOUBLE_TAKE; } else { /* 4. DT >= DP >= ND: Double, pass */ /* * the double is optional if: * (1) equity(no double) = equity(drop) * (2) the player can win gammon * (3a) it's match play * or if it's money play * (3b) it's not a centered cube * or * (3c) the Jacoby rule is not in effect */ arDouble[OUTPUT_OPTIMAL] = arDouble[OUTPUT_DROP]; if (isOptional(arDouble[OUTPUT_NODOUBLE], arDouble[OUTPUT_DROP]) && (winGammon(aarOutput[0]) && (pci->nMatchTo || pci->fCubeOwner != -1 || !pci->fJacoby))) return (pci->fCubeOwner == -1) ? OPTIONAL_DOUBLE_PASS : OPTIONAL_REDOUBLE_PASS; else return (pci->fCubeOwner == -1) ? DOUBLE_PASS : REDOUBLE_PASS; } } else { /* no double */ /* ND > DT or ND > DP */ arDouble[OUTPUT_OPTIMAL] = arDouble[OUTPUT_NODOUBLE]; if (arDouble[OUTPUT_NODOUBLE] > arDouble[OUTPUT_TAKE]) { /* ND > DT */ if (arDouble[OUTPUT_TAKE] > arDouble[OUTPUT_DROP]) { /* 1. ND > DT > DP: Too good, pass */ /* sanity check: don't play on for a gammon if none is possible... */ if (winGammon(aarOutput[0])) return (pci->fCubeOwner == -1) ? TOOGOOD_PASS : TOOGOODRE_PASS; else return (pci->fCubeOwner == -1) ? DOUBLE_PASS : REDOUBLE_PASS; } else if (arDouble[OUTPUT_NODOUBLE] > arDouble[OUTPUT_DROP]) { /* 2. ND > DP > DT: Too good, take */ if (winGammon(aarOutput[0])) return (pci->fCubeOwner == -1) ? TOOGOOD_TAKE : TOOGOODRE_TAKE; else return (pci->fCubeOwner == -1) ? NODOUBLE_TAKE : NO_REDOUBLE_TAKE; } else { /* 5. DP > ND > DT: No double, {take, beaver} */ if (arDouble[OUTPUT_TAKE] >= -2.0f && arDouble[OUTPUT_TAKE] <= 0.0f && !pci->nMatchTo && pci->fBeavers) return (pci->fCubeOwner == -1) ? NODOUBLE_BEAVER : NO_REDOUBLE_BEAVER; else return (pci->fCubeOwner == -1) ? NODOUBLE_TAKE : NO_REDOUBLE_TAKE; } } else { /* 3. DT >= ND > DP: Too good, pass */ if (winGammon(aarOutput[0])) return (pci->fCubeOwner == -1) ? TOOGOOD_PASS : TOOGOODRE_PASS; else return (pci->fCubeOwner == -1) ? DOUBLE_PASS : REDOUBLE_PASS; } } } extern cubedecision FindCubeDecision(float arDouble[], float aarOutput[][NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci) { GetDPEq(NULL, &arDouble[OUTPUT_DROP], pci); arDouble[OUTPUT_NODOUBLE] = aarOutput[0][OUTPUT_CUBEFUL_EQUITY]; arDouble[OUTPUT_TAKE] = aarOutput[1][OUTPUT_CUBEFUL_EQUITY]; if (pci->nMatchTo) { /* convert to normalized money equity */ int i; for (i = 1; i < 4; i++) arDouble[i] = mwc2eq(arDouble[i], pci); } return FindBestCubeDecision(arDouble, aarOutput, pci); } static int fDoCubeful(cubeinfo * pci) { if (pci->anScore[0] + pci->nCube >= pci->nMatchTo && pci->anScore[1] + pci->nCube >= pci->nMatchTo) /* cube is dead */ return FALSE; if (pci->anScore[0] == pci->nMatchTo - 2 && pci->anScore[1] == pci->nMatchTo - 2) /* score is -2,-2 */ return FALSE; if (pci->fCrawford) /* cube is dead in Crawford game */ return FALSE; return TRUE; } extern int GetDPEq(int *pfCube, float *prDPEq, const cubeinfo * pci) { int fCube; if (!pci->nMatchTo) { /* Money game: * Double, pass equity for money game is 1.0 points, since we always * calculate equity normed to a 1-cube. * Take the double branch if the cube is centered or I own the cube. */ if (prDPEq) *prDPEq = 1.0f; fCube = (pci->fCubeOwner == -1) || (pci->fCubeOwner == pci->fMove); if (pfCube) *pfCube = fCube; } else { /* Match play: * Equity for double, pass is found from the match equity table. * Take the double branch is I can/will use cube: * - if it is not the Crawford game, * - and if the cube is not dead, * - and if it is post-Crawford and I'm trailing * - and if I have access to the cube. */ /* FIXME: equity for double, pass */ int fPostCrawford = !pci->fCrawford && (pci->anScore[0] == pci->nMatchTo - 1 || pci->anScore[1] == pci->nMatchTo - 1); fCube = (!pci->fCrawford) && (pci->anScore[pci->fMove] + pci->nCube < pci->nMatchTo) && (!(fPostCrawford && (pci->anScore[pci->fMove] == pci->nMatchTo - 1))) && ((pci->fCubeOwner == -1) || (pci->fCubeOwner == pci->fMove)); if (prDPEq) *prDPEq = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->fMove, pci->nCube, pci->fMove, pci->fCrawford, aafMET, aafMETPostCrawford); if (pfCube) *pfCube = fCube; } return fCube; } static float MoneyLive(const float rW, const float rL, const float p, const cubeinfo * pci) { if (pci->fCubeOwner == -1) { /* centered cube */ float rTP = (rL - 0.5f) / (rW + rL + 0.5f); float rCP = (rL + 1.0f) / (rW + rL + 0.5f); if (p < rTP) /* linear interpolation between * (0,-rL) and ( rTP,-1) */ return (pci->fJacoby) ? -1.0f : (-rL + (-1.0f + rL) * p / rTP); else if (p < rCP) /* linear interpolation between * (rTP,-1) and (rCP,+1) */ return -1.0f + 2.0f * (p - rTP) / (rCP - rTP); else /* linear interpolation between * (rCP,+1) and (1,+rW) */ return (pci->fJacoby) ? 1.0f : (+1.0f + (rW - 1.0f) * (p - rCP) / (1.0f - rCP)); } else if (pci->fCubeOwner == pci->fMove) { /* owned cube */ /* cash point */ float rCP = (rL + 1.0f) / (rW + rL + 0.5f); if (p < rCP) /* linear interpolation between * (0,-rL) and (rCP,+1) */ return -rL + (1.0f + rL) * p / rCP; else /* linear interpolation between * (rCP,+1) and (1,+rW) */ return +1.0f + (rW - 1.0f) * (p - rCP) / (1.0f - rCP); } else { /* unavailable cube */ /* take point */ float rTP = (rL - 0.5f) / (rW + rL + 0.5f); if (p < rTP) /* linear interpolation between * (0,-rL) and ( rTP,-1) */ return -rL + (-1.0f + rL) * p / rTP; else /* linear interpolation between * (rTP,-1) and (1,rW) */ return -1.0f + (rW + 1.0f) * (p - rTP) / (1.0f - rTP); } } extern float Cl2CfMoney(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX) { const float epsilon = 0.0000001f; const float omepsilon = 0.9999999f; float rW, rL; float rEqDead, rEqLive; /* money game */ /* Transform cubeless 0-ply equity to cubeful 0-ply equity using * Rick Janowski's formulas [insert ref here]. */ /* First calculate average win and loss W and L: */ if (arOutput[OUTPUT_WIN] > epsilon) rW = 1.0f + (arOutput[OUTPUT_WINGAMMON] + arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN]; else { /* basically a dead cube */ return Utility(arOutput, pci); } if (arOutput[OUTPUT_WIN] < omepsilon) rL = 1.0f + (arOutput[OUTPUT_LOSEGAMMON] + arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]); else { /* basically a dead cube */ return Utility(arOutput, pci); } rEqDead = Utility(arOutput, pci); rEqLive = MoneyLive(rW, rL, arOutput[OUTPUT_WIN], pci); return rEqDead * (1.0f - rCubeX) + rEqLive * rCubeX; } static float Cl2CfMatchCentered(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX) { /* normalized score */ float rG0, rBG0, rG1, rBG1; float arCP[2]; float rMWCDead, rMWCLive; float rMWCOppCash, rMWCCash, rOppTG, rTG; float aarMETResult[2][DTLBP1 + 1]; /* Centered cube */ /* Calculate normal, gammon, and backgammon ratios */ if (arOutput[OUTPUT_WIN] > 0.0f) { rG0 = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN]; rBG0 = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN]; } else { rG0 = 0.0f; rBG0 = 0.0f; } if (arOutput[OUTPUT_WIN] < 1.0f) { rG1 = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]); rBG1 = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]); } else { rG1 = 0.0f; rBG1 = 0.0f; } /* MWC(dead cube) = cubeless equity */ rMWCDead = eq2mwc(Utility(arOutput, pci), pci); /* Get live cube cash points */ GetPoints(arOutput, pci, arCP); getMEMultiple(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->nCube, -1, -1, pci->fCrawford, aafMET, aafMETPostCrawford, aarMETResult[0], aarMETResult[1]); rMWCCash = aarMETResult[pci->fMove][NDW]; rMWCOppCash = aarMETResult[pci->fMove][NDL]; rOppTG = 1.0f - arCP[!pci->fMove]; rTG = arCP[pci->fMove]; if (arOutput[OUTPUT_WIN] <= rOppTG) { /* Opp too good to double */ float rMWCLose = (1.0f - rG1 - rBG1) * aarMETResult[pci->fMove][NDL] + rG1 * aarMETResult[pci->fMove][NDLG] + rBG1 * aarMETResult[pci->fMove][NDLB]; if (rOppTG > 0.0f) /* avoid division by zero */ rMWCLive = rMWCLose + (rMWCOppCash - rMWCLose) * arOutput[OUTPUT_WIN] / rOppTG; else rMWCLive = rMWCLose; /* (1-x) MWC(dead) + x MWC(live) */ return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } else if (arOutput[OUTPUT_WIN] < rTG) { /* In doubling window */ rMWCLive = rMWCOppCash + (rMWCCash - rMWCOppCash) * (arOutput[OUTPUT_WIN] - rOppTG) / (rTG - rOppTG); return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } else { /* I'm too good to double */ /* MWC(live cube) linear interpolation between the * points: * * p = TG, MWC = I win 1 point * p = 1, MWC = I win (normal, gammon, or backgammon) * */ float rMWCWin = (1.0f - rG0 - rBG0) * aarMETResult[pci->fMove][NDW] + rG0 * aarMETResult[pci->fMove][NDWG] + rBG0 * aarMETResult[pci->fMove][NDWB]; if (rTG < 1.0f) rMWCLive = rMWCCash + (rMWCWin - rMWCCash) * (arOutput[OUTPUT_WIN] - rTG) / (1.0f - rTG); else rMWCLive = rMWCWin; /* (1-x) MWC(dead) + x MWC(live) */ return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } } static float Cl2CfMatchOwned(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX) { /* normalized score */ float rG0, rBG0, rG1, rBG1; float arCP[2]; float rMWCDead, rMWCLive; float rMWCCash, rTG; float aarMETResult[2][DTLBP1 + 1]; /* I own cube */ /* Calculate normal, gammon, and backgammon ratios */ if (arOutput[OUTPUT_WIN] > 0.0f) { rG0 = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN]; rBG0 = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN]; } else { rG0 = 0.0f; rBG0 = 0.0f; } if (arOutput[OUTPUT_WIN] < 1.0f) { rG1 = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]); rBG1 = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]); } else { rG1 = 0.0f; rBG1 = 0.0f; } /* MWC(dead cube) = cubeless equity */ rMWCDead = eq2mwc(Utility(arOutput, pci), pci); /* Get live cube cash points */ GetPoints(arOutput, pci, arCP); getMEMultiple(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->nCube, -1, -1, pci->fCrawford, aafMET, aafMETPostCrawford, aarMETResult[0], aarMETResult[1]); rMWCCash = aarMETResult[pci->fMove][NDW]; rTG = arCP[pci->fMove]; if (arOutput[OUTPUT_WIN] <= rTG) { /* MWC(live cube) linear interpolation between the * points: * * p = 0, MWC = I lose (normal, gammon, or backgammon) * p = TG, MWC = I win 1 point * */ float rMWCLose = (1.0f - rG1 - rBG1) * aarMETResult[pci->fMove][NDL] + rG1 * aarMETResult[pci->fMove][NDLG] + rBG1 * aarMETResult[pci->fMove][NDLB]; if (rTG > 0.0f) rMWCLive = rMWCLose + (rMWCCash - rMWCLose) * arOutput[OUTPUT_WIN] / rTG; else rMWCLive = rMWCLose; /* (1-x) MWC(dead) + x MWC(live) */ return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } else { /* we are too good to double */ /* MWC(live cube) linear interpolation between the * points: * * p = TG, MWC = I win 1 point * p = 1, MWC = I win (normal, gammon, or backgammon) * */ float rMWCWin = (1.0f - rG0 - rBG0) * aarMETResult[pci->fMove][NDW] + rG0 * aarMETResult[pci->fMove][NDWG] + rBG0 * aarMETResult[pci->fMove][NDWB]; if (rTG < 1.0f) rMWCLive = rMWCCash + (rMWCWin - rMWCCash) * (arOutput[OUTPUT_WIN] - rTG) / (1.0f - rTG); else rMWCLive = rMWCWin; /* (1-x) MWC(dead) + x MWC(live) */ return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } } static float Cl2CfMatchUnavailable(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX) { /* normalized score */ float rG0, rBG0, rG1, rBG1; float arCP[2]; float rMWCDead, rMWCLive; float rMWCOppCash, rOppTG; float aarMETResult[2][DTLBP1 + 1]; /* I own cube */ /* Calculate normal, gammon, and backgammon ratios */ if (arOutput[OUTPUT_WIN] > 0.0f) { rG0 = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN]; rBG0 = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN]; } else { rG0 = 0.0f; rBG0 = 0.0f; } if (arOutput[OUTPUT_WIN] < 1.0f) { rG1 = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]); rBG1 = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]); } else { rG1 = 0.0f; rBG1 = 0.0f; } /* MWC(dead cube) = cubeless equity */ rMWCDead = eq2mwc(Utility(arOutput, pci), pci); /* Get live cube cash points */ GetPoints(arOutput, pci, arCP); getMEMultiple(pci->anScore[0], pci->anScore[1], pci->nMatchTo, pci->nCube, -1, -1, pci->fCrawford, aafMET, aafMETPostCrawford, aarMETResult[0], aarMETResult[1]); rMWCOppCash = aarMETResult[pci->fMove][NDL]; rOppTG = 1.0f - arCP[!pci->fMove]; if (arOutput[OUTPUT_WIN] <= rOppTG) { /* Opponent is too good to double. * * MWC(live cube) linear interpolation between the * points: * * p = 0, MWC = opp win normal, gammon, backgammon * p = OppTG, MWC = opp cashes * */ float rMWCLose = (1.0f - rG1 - rBG1) * aarMETResult[pci->fMove][NDL] + rG1 * aarMETResult[pci->fMove][NDLG] + rBG1 * aarMETResult[pci->fMove][NDLB]; if (rOppTG > 0.0f) /* avoid division by zero */ rMWCLive = rMWCLose + (rMWCOppCash - rMWCLose) * arOutput[OUTPUT_WIN] / rOppTG; else rMWCLive = rMWCLose; /* (1-x) MWC(dead) + x MWC(live) */ return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } else { /* MWC(live cube) linear interpolation between the * points: * * p = OppTG, MWC = opponent cashes * p = 1, MWC = I win (normal, gammon, or backgammon) * */ float rMWCWin = (1.0f - rG0 - rBG0) * aarMETResult[pci->fMove][NDW] + rG0 * aarMETResult[pci->fMove][NDWG] + rBG0 * aarMETResult[pci->fMove][NDWB]; rMWCLive = rMWCOppCash + (rMWCWin - rMWCOppCash) * (arOutput[OUTPUT_WIN] - rOppTG) / (1.0f - rOppTG); /* (1-x) MWC(dead) + x MWC(live) */ return rMWCDead * (1.0f - rCubeX) + rMWCLive * rCubeX; } } extern float Cl2CfMatch(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX) { /* Check if this requires a cubeful evaluation */ if (!fDoCubeful(pci)) { /* cubeless eval */ return eq2mwc(Utility(arOutput, pci), pci); } /* fDoCubeful */ else { /* cubeful eval */ if (pci->fCubeOwner == -1) return Cl2CfMatchCentered(arOutput, pci, rCubeX); else if (pci->fCubeOwner == pci->fMove) return Cl2CfMatchOwned(arOutput, pci, rCubeX); else return Cl2CfMatchUnavailable(arOutput, pci, rCubeX); } } extern float EvalEfficiency(const TanBoard anBoard, positionclass pc, int ply) { /* Since it's somewhat costly to call CalcInputs, the * inputs should preferably be cached to save time. */ const int i = ply >= 2 ? 0 : 1; switch (pc) { case CLASS_OVER: return 0.0f; /* dead cube */ case CLASS_HYPERGAMMON1: case CLASS_HYPERGAMMON2: case CLASS_HYPERGAMMON3: /* FIXME */ return 0.60f; case CLASS_BEAROFF1: case CLASS_BEAROFF_OS: /* FIXME: calculate based on #rolls to get off. * For example, 15 rolls probably have cube eff. of * 0.7, and 1.25 rolls have cube eff. of 1.0. * * It's not so important to have cube eff. correct here as an * n-ply evaluation will take care of last-roll and 2nd-last-roll * situations. */ return rOSCubeX; case CLASS_RACE: { unsigned int anPips[2]; float rEff; PipCount(anBoard, anPips); rEff = (float) anPips[1] * rRaceFactorX[i] + rRaceCoefficientX[i]; if (rEff > rRaceMax[i]) return rRaceMax[i]; else { if (rEff < rRaceMin[i]) return rRaceMin[i]; else return rEff; } } case CLASS_CONTACT: /* FIXME: should CLASS_CRASHED be handled differently? */ /* FIXME: use Oystein's values published in rec.games.backgammon, * or work some other semiempirical values */ /* FIXME: very important: use opponents inputs as well */ return rContactX[i]; case CLASS_CRASHED: return rCrashedX[i]; case CLASS_BEAROFF2: case CLASS_BEAROFF_TS: return rTSCubeX; /* for match play only */ default: g_assert_not_reached(); } return 0; } extern char * FormatEval(char *sz, evalsetup * pes) { switch (pes->et) { case EVAL_NONE: strcpy(sz, ""); break; case EVAL_EVAL: sprintf(sz, "%s %1u-%s", pes->ec.fCubeful ? _("Cubeful") : _("Cubeless"), pes->ec.nPlies, _("ply")); break; case EVAL_ROLLOUT: sprintf(sz, "%s", _("Rollout")); break; default: sprintf(sz, _("Unknown eval type (%d)"), (int) pes->et); g_assert_not_reached(); break; } return sz; } #if 0 static void CalcCubefulEquity(positionclass pc, float arOutput[NUM_ROLLOUT_OUTPUTS], int nPlies, int fDT, cubeinfo * pci) { float rND, rDT, rDP, r; int fCube; cubeinfo ci; float ar[NUM_ROLLOUT_OUTPUTS]; int fMax = !(nPlies % 2); memcpy(&ar[0], &arOutput[0], NUM_OUTPUTS * sizeof(float)); if (!nPlies) { /* leaf node */ if (pc == CLASS_OVER || (pci->nMatchTo && !fDoCubeful(pci))) { /* cubeless */ rND = Utility(arOutput, pci); } else { /* cubeful */ rND = (pci->nMatchTo) ? mwc2eq(Cl2CfMatch(arOutput, pci), pci) : Cl2CfMoney(arOutput, pci); } } else { /* internal node; recurse */ SetCubeInfo(&ci, pci->nCube, pci->fCubeOwner, !pci->fMove, pci->nMatchTo, pci->anScore, pci->fCrawford, pci->fJacoby, pci->fBeavers, pci->bgv); CalcCubefulEquity(pc, ar, nPlies - 1, TRUE, &ci); rND = ar[OUTPUT_CUBEFUL_EQUITY]; } GetDPEq(&fCube, &rDP, pci); if (pci->nMatchTo) rDP = mwc2eq(rDP, pci); if (fCube && fDT) { /* double, take */ if (!nPlies) { SetCubeInfo(&ci, 2 * pci->nCube, !pci->fMove, pci->fMove, pci->nMatchTo, pci->anScore, pci->fCrawford, pci->fJacoby, pci->fBeavers, pci->bgv); /* leaf node */ if (pc == CLASS_OVER || (pci->nMatchTo && !fDoCubeful(&ci))) { /* cubeless */ rDT = Utility(arOutput, &ci); if (pci->nMatchTo) rDT *= 2.0; } else { /* cubeful */ rDT = (pci->nMatchTo) ? mwc2eq(Cl2CfMatch(arOutput, &ci), &ci) : 2.0 * Cl2CfMoney(arOutput, &ci); } } else { /* internal node; recurse */ SetCubeInfo(&ci, 2 * pci->nCube, !pci->fMove, !pci->fMove, pci->nMatchTo, pci->anScore, pci->fCrawford, pci->fJacoby, pci->fBeavers, pci->bgv); CalcCubefulEquity(pc, ar, nPlies - 1, TRUE, &ci); rDT = ar[OUTPUT_CUBEFUL_EQUITY]; if (!ci.nMatchTo) rDT *= 2.0; } if (fMax) { /* maximize my equity */ if (rDP > rND && rDT > rND) { /* it's a double */ if (rDT >= rDP) r = rDP; /* pass */ else r = rDT; /* take */ } else r = rND; /* no double */ } else { /* minimize my equity */ rDP = -rDP; if (rDP < rND && rDT < rND) { /* it's a double */ if (rDT < rDP) r = rDP; /* pass */ else r = rDT; /* take */ } else r = rND; /* no double */ } } else { r = rND; rDT = 0.0; } arOutput[OUTPUT_EQUITY] = UtilityME(arOutput, pci); arOutput[OUTPUT_CUBEFUL_EQUITY] = r; } #endif /* !LOCKING_VERSION */ /* * Compare two evalcontexts. * * Input: * - pec1, pec2: the two evalcontexts to compare * * Output: * None. * * Returns: * -1 if *pec1 "<" *pec2 * 0 if *pec1 "=" *pec2 * +1 if *pec1 ">" *pec2 * */ extern int cmp_evalcontext(const evalcontext * pec1, const evalcontext * pec2) { /* Check if plies are different */ if (pec1->nPlies < pec2->nPlies) return -1; else if (pec1->nPlies > pec2->nPlies) return +1; /* Check for cubeful evals */ if (pec1->fCubeful < pec2->fCubeful) return -1; else if (pec1->fCubeful > pec2->fCubeful) return +1; /* Noise */ if (pec1->rNoise > pec2->rNoise) return -1; else if (pec1->rNoise < pec2->rNoise) return +1; if (pec1->rNoise > 0) { if (pec1->fDeterministic < pec2->fDeterministic) return -1; else if (pec1->fDeterministic > pec2->fDeterministic) return +1; } if (pec1->nPlies > 0) { int nPrune1 = (pec1->fUsePrune); int nPrune2 = (pec2->fUsePrune); if (nPrune1 > nPrune2) return -1; else if (nPrune1 < nPrune2) return +1; } return 0; } /* * Compare two rolloutcontexts. * * Input: * - prc1, prc2: the two evalsetups to compare * * Output: * None. * * Returns: * -1 if *prc1 "<" *prc2 * 0 if *prc1 "=" *prc2 * +1 if *prc1 ">" *prc2 * */ static int cmp_rolloutcontext(const rolloutcontext * UNUSED(prc1), const rolloutcontext * UNUSED(prc2)) { /* FIXME: write me */ return 0; } /* * Compare two evalsetups. * * Input: * - pes1, pes2: the two evalsetups to compare * * Output: * None. * * Returns: * -1 if *pes1 "<" *pes2 * 0 if *pes1 "=" *pes2 * +1 if *pes1 ">" *pes2 * */ extern int cmp_evalsetup(const evalsetup * pes1, const evalsetup * pes2) { /* Check for different evaltypes */ if (pes1->et < pes2->et) return -1; else if (pes1->et > pes2->et) return +1; /* The two evaltypes are identical */ switch (pes1->et) { case EVAL_NONE: return 0; case EVAL_EVAL: return cmp_evalcontext(&pes1->ec, &pes2->ec); case EVAL_ROLLOUT: return cmp_rolloutcontext(&pes1->rc, &pes2->rc); default: g_assert_not_reached(); } return 0; } static void calculate_gammon_rates(float aarRates[2][2], float arOutput[], cubeinfo * pci) { if (arOutput[OUTPUT_WIN] > 0.0f) { aarRates[pci->fMove][0] = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN]; aarRates[pci->fMove][1] = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN]; } else { aarRates[pci->fMove][0] = aarRates[pci->fMove][1] = 0.0f; } if (arOutput[OUTPUT_WIN] < 1.0f) { aarRates[!pci->fMove][0] = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]); aarRates[!pci->fMove][1] = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]); } else { aarRates[!pci->fMove][0] = aarRates[!pci->fMove][1] = 0.0f; } } /* * Get current gammon rates * * Input: * anBoard: current board * pci: current cubeinfo * pec: eval context * * Output: * aarRates: gammon and backgammon rates (first index is player) * */ extern int getCurrentGammonRates(float aarRates[2][2], float arOutput[], const TanBoard anBoard, cubeinfo * pci, const evalcontext * pec) { if (EvaluatePosition(NULL, anBoard, arOutput, pci, pec) < 0) return -1; calculate_gammon_rates(aarRates, arOutput, pci); return 0; } /* * Get take, double, beaver, etc points for money game using * Rick Janowski's formulae: * https://www.bkgm.com/articles/Janowski/cubeformulae.pdf * * Input: * fJacoby, fBeavers: flags for different flavours of money game * aarRates: gammon and backgammon rates (first index is player) * * Output: * aaarPoints: the points * */ extern void getMoneyPoints(float aaarPoints[2][7][2], const int fJacoby, const int fBeavers, float aarRates[2][2]) { const float arCLV[2] = /* average cubeless value of games won */ { 1.0f + aarRates[0][0] + 2.0f * aarRates[0][1], 1.0f + aarRates[1][0] + 2.0f * aarRates[1][1] }; /* calculate average cubeless value of games won */ /* calculate points */ for (int i = 0; i < 2; i++) { /* Determine rW and rL from Rick's formulae */ const float rW = arCLV[i]; const float rL = arCLV[!i]; /* Determine points */ /* take point */ aaarPoints[i][0][0] = (rL - 0.5f) / (rW + rL); aaarPoints[i][0][1] = (rL - 0.5f) / (rW + rL + 0.5f); /* beaver point */ aaarPoints[i][1][0] = rL / (rW + rL); aaarPoints[i][1][1] = rL / (rW + rL + 0.5f); /* raccoon point */ aaarPoints[i][2][0] = rL / (rW + rL); aaarPoints[i][2][1] = (rL + 0.5f) / (rW + rL + 0.5f); /* initial double point */ if (!fJacoby) { /* without Jacoby */ aaarPoints[i][3][0] = rL / (rW + rL); } else { /* with Jacoby */ if (fBeavers) /* with beavers */ aaarPoints[i][3][0] = (rL - 0.25f) / (rL + rW - 0.5f); else /* without beavers */ aaarPoints[i][3][0] = (rL - 0.5f) / (rL + rW - 1.0f); } aaarPoints[i][3][1] = (rL + 1.0f) / (rL + rW + 0.5f); /* redouble point */ aaarPoints[i][4][0] = rL / (rW + rL); aaarPoints[i][4][1] = (rL + 1.0f) / (rL + rW + 0.5f); /* cash point */ aaarPoints[i][5][0] = (rL + 0.5f) / (rW + rL); aaarPoints[i][5][1] = (rL + 1.0f) / (rW + rL + 0.5f); /* too good point */ aaarPoints[i][6][0] = (rL + 1.0f) / (rW + rL); aaarPoints[i][6][1] = (rL + 1.0f) / (rW + rL + 0.5f); } } extern void GetECF3(float arCubeful[], int cci, float arCf[], cubeinfo aci[]) { int i, ici; float rND, rDT, rDP; for (ici = 0, i = 0; ici < cci; ici++, i += 2) { if (aci[i + 1].nCube > 0) { /* cube available */ rND = arCf[i]; if (aci[0].nMatchTo) rDT = arCf[i + 1]; else rDT = 2.0f * arCf[i + 1]; GetDPEq(NULL, &rDP, &aci[i]); if (rDT >= rND && rDP >= rND) { /* double */ if (rDT >= rDP) /* pass */ arCubeful[ici] = rDP; else /* take */ arCubeful[ici] = rDT; } else { /* no double */ arCubeful[ici] = rND; } } else { /* no cube available: always no double */ arCubeful[ici] = arCf[i]; } } } extern void MakeCubePos(const cubeinfo aciCubePos[], const int cci, const int fTop, cubeinfo aci[], const int fInvert) { int i, ici; for (ici = 0, i = 0; ici < cci; ici++) { /* no double */ if (aciCubePos[ici].nCube > 0) { SetCubeInfo(&aci[i], aciCubePos[ici].nCube, aciCubePos[ici].fCubeOwner, fInvert ? !aciCubePos[ici].fMove : aciCubePos[ici].fMove, aciCubePos[ici].nMatchTo, aciCubePos[ici].anScore, aciCubePos[ici].fCrawford, aciCubePos[ici].fJacoby, aciCubePos[ici].fBeavers, aciCubePos[ici].bgv); } else { aci[i].nCube = -1; } i++; if (!fTop && aciCubePos[ici].nCube > 0 && GetDPEq(NULL, NULL, &aciCubePos[ici])) /* we may double */ SetCubeInfo(&aci[i], 2 * aciCubePos[ici].nCube, !aciCubePos[ici].fMove, fInvert ? !aciCubePos[ici].fMove : aciCubePos[ici].fMove, aciCubePos[ici].nMatchTo, aciCubePos[ici].anScore, aciCubePos[ici].fCrawford, aciCubePos[ici].fJacoby, aciCubePos[ici].fBeavers, aciCubePos[ici].bgv); else /* mark cube position as unavailable */ aci[i].nCube = -1; i++; } /* loop cci */ } /* * Get take, double, take, and too good points for match play. * * Input: * pci: cubeinfo * aarRates: gammon and backgammon rates (first index is player) * * Output: * aaarPoints: the points * */ extern void getMatchPoints(float aaarPoints[2][4][2], int afAutoRedouble[2], int afDead[2], const cubeinfo * pci, float aarRates[2][2]) { SSE_ALIGN(float arOutput[NUM_OUTPUTS]); float arDP1[2], arDP2[2], arCP1[2], arCP2[2], arTG[2]; const int anNormScore[2] = { pci->nMatchTo - pci->anScore[0], pci->nMatchTo - pci->anScore[1] }; /* get cash points */ arOutput[OUTPUT_WIN] = 0.5f; arOutput[OUTPUT_WINGAMMON] = 0.5f * (aarRates[0][0] + aarRates[0][1]); arOutput[OUTPUT_WINBACKGAMMON] = 0.5f * aarRates[0][1]; arOutput[OUTPUT_LOSEGAMMON] = 0.5f * (aarRates[1][0] + aarRates[1][1]); arOutput[OUTPUT_LOSEBACKGAMMON] = 0.5f * aarRates[1][1]; GetPoints(arOutput, pci, arCP2); for (int i = 0; i < 2; i++) { float rDTW, rDTL, rNDW, rNDL, rDP, rRisk, rGain; afAutoRedouble[i] = (anNormScore[i] - 2 * pci->nCube <= 0) && (anNormScore[!i] - 2 * pci->nCube > 0); afDead[i] = (anNormScore[!i] - 2 * pci->nCube <= 0); /* MWC for "double, take; win" */ rDTW = (1.0f - aarRates[i][0] - aarRates[i][1]) * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 2 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[i][0] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 4 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[i][1] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 6 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford); /* MWC for "no double, take; win" */ rNDW = (1.0f - aarRates[i][0] - aarRates[i][1]) * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[i][0] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 2 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[i][1] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 3 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford); /* MWC for "Double, take; lose" */ rDTL = (1.0f - aarRates[!i][0] - aarRates[!i][1]) * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 2 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[!i][0] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 4 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[!i][1] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 6 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford); /* MWC for "No double; lose" */ rNDL = (1.0f - aarRates[!i][0] - aarRates[!i][1]) * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 1 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[!i][0] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 2 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[!i][1] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 3 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford); /* MWC for "Double, pass" */ rDP = getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford); /* Double point */ rRisk = rNDL - rDTL; rGain = rDTW - rNDW; arDP1[i] = (rRisk == 0.0f) ? 0.0f : rRisk / (rRisk + rGain); arDP2[i] = arDP1[i]; /* Dead cube take point without redouble */ rRisk = rDTW - rDP; rGain = rDP - rDTL; arCP1[i] = 1.0f - rRisk / (rRisk + rGain); /* find too good point */ rRisk = rNDW - rNDL; rGain = rNDW - rDP; arTG[i] = rRisk / (rRisk + rGain); if (afAutoRedouble[i]) { /* With redouble */ rDTW = (1.0f - aarRates[i][0] - aarRates[i][1]) * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 4 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[i][0] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 8 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[i][1] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 12 * pci->nCube, i, pci->fCrawford, aafMET, aafMETPostCrawford); rDTL = (1.0f - aarRates[!i][0] - aarRates[!i][1]) * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 4 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[!i][0] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 8 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford) + aarRates[!i][1] * getME(pci->anScore[0], pci->anScore[1], pci->nMatchTo, i, 12 * pci->nCube, !i, pci->fCrawford, aafMET, aafMETPostCrawford); rRisk = rDTW - rDP; rGain = rDP - rDTL; arCP2[i] = 1.0f - rRisk / (rRisk + rGain); /* Double point */ rRisk = rNDL - rDTL; rGain = rDTW - rNDW; arDP2[i] = (rRisk == 0.0f) ? 0.0f : rRisk / (rRisk + rGain); } } /* save points */ for (int i = 0; i < 2; i++) { /* take point */ aaarPoints[i][0][0] = 1.0f - arCP1[!i]; aaarPoints[i][0][1] = 1.0f - arCP2[!i]; /* double point */ aaarPoints[i][1][0] = arDP1[i]; aaarPoints[i][1][1] = arDP2[i]; /* cash point */ aaarPoints[i][2][0] = arCP1[i]; aaarPoints[i][2][1] = arCP2[i]; /* too good point */ aaarPoints[i][3][0] = arTG[i]; if (!afDead[i]) aaarPoints[i][3][1] = arCP2[i]; } } extern void getCubeDecisionOrdering(int aiOrder[3], float arDouble[4], float aarOutput[2][NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci) { cubedecision cd; /* Get cube decision */ cd = FindBestCubeDecision(arDouble, aarOutput, pci); switch (cd) { case DOUBLE_TAKE: case DOUBLE_BEAVER: case REDOUBLE_TAKE: /* * Optimal : Double, take * Best for me : Double, pass * Worst for me: No Double */ aiOrder[0] = OUTPUT_TAKE; aiOrder[1] = OUTPUT_DROP; aiOrder[2] = OUTPUT_NODOUBLE; break; case DOUBLE_PASS: case REDOUBLE_PASS: /* * Optimal : Double, pass * Best for me : Double, take * Worst for me: no double */ aiOrder[0] = OUTPUT_DROP; aiOrder[1] = OUTPUT_TAKE; aiOrder[2] = OUTPUT_NODOUBLE; break; case NODOUBLE_TAKE: case NODOUBLE_BEAVER: case TOOGOOD_TAKE: case NO_REDOUBLE_TAKE: case NO_REDOUBLE_BEAVER: case TOOGOODRE_TAKE: case NODOUBLE_DEADCUBE: case NO_REDOUBLE_DEADCUBE: case OPTIONAL_DOUBLE_BEAVER: case OPTIONAL_DOUBLE_TAKE: case OPTIONAL_REDOUBLE_TAKE: /* * Optimal : no double * Best for me : double, pass * Worst for me: double, take */ aiOrder[0] = OUTPUT_NODOUBLE; aiOrder[1] = OUTPUT_DROP; aiOrder[2] = OUTPUT_TAKE; break; case TOOGOOD_PASS: case TOOGOODRE_PASS: case OPTIONAL_DOUBLE_PASS: case OPTIONAL_REDOUBLE_PASS: /* * Optimal : no double * Best for me : double, take * Worst for me: double, pass */ aiOrder[0] = OUTPUT_NODOUBLE; aiOrder[1] = OUTPUT_TAKE; aiOrder[2] = OUTPUT_DROP; break; default: g_assert_not_reached(); } } extern float getPercent(const cubedecision cd, const float arDouble[]) { switch (cd) { case DOUBLE_TAKE: case DOUBLE_BEAVER: case DOUBLE_PASS: case REDOUBLE_TAKE: case REDOUBLE_PASS: case NODOUBLE_DEADCUBE: case NO_REDOUBLE_DEADCUBE: case OPTIONAL_DOUBLE_BEAVER: case OPTIONAL_DOUBLE_TAKE: case OPTIONAL_REDOUBLE_TAKE: case OPTIONAL_DOUBLE_PASS: case OPTIONAL_REDOUBLE_PASS: /* correct cube action */ return -1.0f; case TOOGOODRE_TAKE: case TOOGOOD_TAKE: /* never correct to double */ return -1.0f; case NODOUBLE_TAKE: case NODOUBLE_BEAVER: case NO_REDOUBLE_TAKE: case NO_REDOUBLE_BEAVER: /* how many doubles should be dropped before it is correct to double */ return (arDouble[OUTPUT_NODOUBLE] - arDouble[OUTPUT_TAKE]) / (arDouble[OUTPUT_DROP] - arDouble[OUTPUT_TAKE]); case TOOGOOD_PASS: case TOOGOODRE_PASS: /* how many doubles should be taken before it is correct to double */ if (arDouble[OUTPUT_NODOUBLE] > arDouble[OUTPUT_TAKE]) /* strange match play scenario * (see 3-ply eval on cAmgACAAGAAA/4HPkAUgzW8EBMA): * never correct to double! */ return -1.0f; else return (arDouble[OUTPUT_NODOUBLE] - arDouble[OUTPUT_DROP]) / (arDouble[OUTPUT_TAKE] - arDouble[OUTPUT_DROP]); default: g_assert_not_reached(); } return -1.0f; } /* * Resort movelist and recalculate best score. * * Input: * pml: movelist * * Output: * pml: update movelist * ai : the new ordering. Caller must allocate ai. * * FIXME: the construction of the ai-array is *very* ugly. * We should probably write a substitute for qsort that * updates ai on the fly. * */ extern void RefreshMoveList(movelist * pml, int *ai) { static movelist ml; if (!pml->cMoves) return; if (ai) CopyMoveList(&ml, pml); qsort(pml->amMoves, pml->cMoves, sizeof(move), (cfunc) CompareMovesGeneral); pml->rBestScore = pml->amMoves[0].rScore; if (ai) { for (unsigned int i = 0; i < pml->cMoves; i++) { for (unsigned int j = 0; j < pml->cMoves; j++) { if (!memcmp(ml.amMoves[j].anMove, pml->amMoves[i].anMove, 8 * sizeof(int))) ai[j] = i; } } g_free(ml.amMoves); } } extern void CopyMoveList(movelist * pmlDest, const movelist * pmlSrc) { if (pmlDest == pmlSrc) return; pmlDest->cMoves = pmlSrc->cMoves; pmlDest->cMaxMoves = pmlSrc->cMaxMoves; pmlDest->cMaxPips = pmlSrc->cMaxPips; pmlDest->iMoveBest = pmlSrc->iMoveBest; pmlDest->rBestScore = pmlSrc->rBestScore; #if GLIB_CHECK_VERSION (2,67,4) pmlDest->amMoves = (move *) g_memdup2(pmlSrc->amMoves, pmlSrc->cMoves * sizeof(move)); #else pmlDest->amMoves = (move *) g_memdup(pmlSrc->amMoves, pmlSrc->cMoves * sizeof(move)); #endif } /* * is this a close cubedecision? * * Input: * arDouble: equities for cube decisions * */ extern int isCloseCubedecision(const float arDouble[]) { const float rThr = 0.16f; float rDouble; rDouble = MIN(arDouble[OUTPUT_TAKE], 1.0f); /* Report if doubling is less than 0.16 */ if (arDouble[OUTPUT_OPTIMAL] - rDouble < rThr) return 1; return 0; } /* * is this a missed double? * * Input: * arDouble: equities for cube decisions * fDouble: did the player double * pci: cubeinfo * */ extern int isMissedDouble(float arDouble[], float aarOutput[2][NUM_ROLLOUT_OUTPUTS], const int fDouble, const cubeinfo * pci) { cubedecision cd = FindBestCubeDecision(arDouble, aarOutput, pci); switch (cd) { case DOUBLE_TAKE: case DOUBLE_PASS: case DOUBLE_BEAVER: case REDOUBLE_TAKE: case REDOUBLE_PASS: return !fDouble; default: return 0; } } static int MoveKey(const TanBoard anBoard, const int anMove[8], positionkey * pkey) { TanBoard anBoardMove; memcpy(anBoardMove, anBoard, sizeof(anBoardMove)); ApplyMove(anBoardMove, anMove, FALSE); PositionKey((ConstTanBoard) anBoardMove, pkey); return 0; } extern unsigned int locateMove(const TanBoard anBoard, const int anMove[8], const movelist * pml) { unsigned int i; positionkey key1, key2; MoveKey(anBoard, anMove, &key1); for (i = 0; i < pml->cMoves; ++i) { MoveKey(anBoard, pml->amMoves[i].anMove, &key2); if (EqualKeys(key2, key1)) return i; } return 0; } extern int equal_movefilter(const int i, const movefilter amf1[MAX_FILTER_PLIES], const movefilter amf2[MAX_FILTER_PLIES]) { int j; for (j = 0; j <= i; ++j) { if (amf1[j].Accept != amf2[j].Accept) return 0; if (amf1[j].Accept < 0) continue; if (amf1[j].Extra != amf2[j].Extra) return 0; if (!amf1[j].Extra) continue; if (amf1[j].Threshold != amf2[j].Threshold) return 0; } return 1; } extern int equal_movefilters(movefilter aamf1[MAX_FILTER_PLIES][MAX_FILTER_PLIES], movefilter aamf2[MAX_FILTER_PLIES][MAX_FILTER_PLIES]) { int i; for (i = 0; i < MAX_FILTER_PLIES; ++i) if (!equal_movefilter(i, aamf1[i], aamf2[i])) return 0; return 1; } /* * Categorise double into normal, beaver, or raccoon. * * The function is called before ApplyMoveRecord: * * fDoubled = FALSE: * * the previous moverecord was not a MOVE_DOUBLE, * hence this is a normal double. * * fDoubled = TRUE: * * The previous moverecord was a MOVE_DOUBLE * so it's either a beaver or a raccoon * * Beaver: fTurn != fMove (the previous doubler was the player on roll * so the redouble must be a beaver) * Raccoon: fTurn == fMove and/or fCubeOwner != fMove * * */ extern doubletype DoubleType(const int fDoubled, const int fMove, const int fTurn) { if (fDoubled) { /* beaver or raccoon */ if (fTurn != fMove) /* beaver */ return DT_BEAVER; else /* raccoon */ return DT_RACCOON; } return DT_NORMAL; } #else #define FindnSaveBestMoves FindnSaveBestMovesWithLocking #define FindBestMove FindBestMoveWithLocking #define EvaluatePosition EvaluatePositionWithLocking #define ScoreMove ScoreMoveWithLocking #define GeneralCubeDecisionE GeneralCubeDecisionEWithLocking #define GeneralEvaluationE GeneralEvaluationEWithLocking #define EvaluatePositionCache EvaluatePositionCacheWithLocking #define FindBestMovePlied FindBestMovePliedWithLocking #define GeneralEvaluationEPlied GeneralEvaluationEPliedWithLocking #define EvaluatePositionCubeful3 EvaluatePositionCubeful3WithLocking #define ScoreMoves ScoreMovesWithLocking #define ScoreMovesPruned ScoreMovesPrunedWithLocking #define FindBestMoveInEval FindBestMoveInEvalWithLocking #define GeneralEvaluationEPliedCubeful GeneralEvaluationEPliedCubefulWithLocking #define EvaluatePositionCubeful4 EvaluatePositionCubeful4WithLocking #define CacheAdd CacheAddWithLocking #define CacheLookup CacheLookupWithLocking static int EvaluatePositionCache(NNState * nnStates, const TanBoard anBoard, float arOutput[], cubeinfo * const pci, const evalcontext * pecx, int nPlies, positionclass pc); static int FindBestMovePlied(int anMove[8], int nDice0, int nDice1, TanBoard anBoard, const cubeinfo * pci, const evalcontext * pec, int nPlies, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]); #endif static int GeneralEvaluationEPlied(NNState * nnStates, float arOutput[NUM_ROLLOUT_OUTPUTS], const TanBoard anBoard, cubeinfo * pci, const evalcontext * pec, int nPlies); static int EvaluatePositionCubeful3(NNState * nnStates, const TanBoard anBoard, float arOutput[NUM_OUTPUTS], float arCubeful[], const cubeinfo aciCubePos[], int cci, cubeinfo * pciMove, const evalcontext * pec, int nPlies, int fTop); /* Functions that have both locking and non-locking versions below here */ static int ScoreMoves(movelist * pml, const cubeinfo * pci, const evalcontext * pec, int nPlies); static int ScoreMovesPruned(movelist * pml, const cubeinfo * pci, const evalcontext * pec, unsigned int *bmovesi, unsigned int prune_moves); /* * The pruning nets select the best MIN_PRUNE_MOVES + * floor(log2(number of legal moves)) moves instead of 10 as they used * to do. A value of 5 for MIN_PRUNE_MOVES brings a small speed-up * and, according to the Depreli benchmark, an insignificant strength * improvement. Using a lower value causes a measurable degradation * of play. Using a higher one doesn't significantly improve it. */ #define MIN_PRUNE_MOVES 5 #define MAX_PRUNE_MOVES (MIN_PRUNE_MOVES + 11) static SIMD_AVX_STACKALIGN void FindBestMoveInEval(NNState * nnStates, int const nDice0, int const nDice1, const TanBoard anBoardIn, TanBoard anBoardOut, cubeinfo * const pci, const evalcontext * pec) { unsigned int i; movelist ml; positionclass evalClass = CLASS_OVER; unsigned int bmovesi[MAX_PRUNE_MOVES]; unsigned int prune_moves; GenerateMoves(&ml, anBoardIn, nDice0, nDice1, FALSE); if (ml.cMoves == 0) { /* no legal moves */ return; } if (ml.cMoves == 1) { /* forced move */ ml.iMoveBest = 0; PositionFromKey(anBoardOut, &ml.amMoves[ml.iMoveBest].key); return; } /* LogCube() is floor(log2()) */ prune_moves = MIN_PRUNE_MOVES + LogCube(ml.cMoves); if (ml.cMoves <= prune_moves) { ScoreMoves(&ml, pci, pec, 0); PositionFromKey(anBoardOut, &ml.amMoves[ml.iMoveBest].key); return; } pci->fMove = !pci->fMove; bmovesi[0] = 0; for (i = 0; i < ml.cMoves; i++) { positionclass pc; SSE_ALIGN(float arOutput[NUM_OUTPUTS]); evalcache ec; uint32_t l; move *const pm = &ml.amMoves[i]; PositionFromKeySwapped(anBoardOut, &pm->key); pc = ClassifyPosition((ConstTanBoard) anBoardOut, VARIATION_STANDARD); if (i == 0) { if (pc < CLASS_RACE) break; evalClass = pc; } else if (pc != evalClass) break; CopyKey(pm->key, ec.key); ec.nEvalContext = 0; if ((l = CacheLookup(&cpEval, &ec, arOutput, NULL)) != CACHEHIT) { SSE_ALIGN(float arInput[NUM_PRUNING_INPUTS]); baseInputs((ConstTanBoard) anBoardOut, arInput); { const neuralnet *nets[] = { &nnpRace, &nnpCrashed, &nnpContact }; const neuralnet *n = nets[pc - CLASS_RACE]; #if defined(USE_SIMD_INSTRUCTIONS) (void) nnStates; /* silence compiler warning */ NeuralNetEvaluateSSE(n, arInput, arOutput, NULL); #else if (nnStates) nnStates[pc - CLASS_RACE].state = (i == 0) ? NNSTATE_INCREMENTAL : NNSTATE_DONE; NeuralNetEvaluate(n, arInput, arOutput, nnStates); #endif if (pc == CLASS_RACE) /* special evaluation of backgammons * overrides net output */ EvalRaceBG((ConstTanBoard) anBoardOut, arOutput, VARIATION_STANDARD); SanityCheck((ConstTanBoard) anBoardOut, arOutput); } memcpy(ec.ar, arOutput, sizeof(float) * NUM_OUTPUTS); ec.ar[5] = 0.f; CacheAdd(&cpEval, &ec, l); } pm->rScore = UtilityME(arOutput, pci); if (i < prune_moves) { bmovesi[i] = i; if (pm->rScore > ml.amMoves[bmovesi[0]].rScore) { bmovesi[i] = bmovesi[0]; bmovesi[0] = i; } } else if (pm->rScore < ml.amMoves[bmovesi[0]].rScore) { unsigned int m = 0, k; bmovesi[0] = i; for (k = 1; k < prune_moves; ++k) { if (ml.amMoves[bmovesi[k]].rScore > ml.amMoves[bmovesi[m]].rScore) { m = k; } } bmovesi[0] = bmovesi[m]; bmovesi[m] = i; } } pci->fMove = !pci->fMove; if (i == ml.cMoves) ScoreMovesPruned(&ml, pci, pec, bmovesi, prune_moves); else ScoreMoves(&ml, pci, pec, 0); PositionFromKey(anBoardOut, &ml.amMoves[ml.iMoveBest].key); } static int EvaluatePositionFull(NNState * nnStates, const TanBoard anBoard, float arOutput[], cubeinfo * const pci, const evalcontext * pec, unsigned int nPlies, positionclass pc) { SSE_ALIGN(float arVariationOutput[NUM_OUTPUTS]); int i; if (pc > CLASS_PERFECT && nPlies > 0) { /* internal node; recurse */ TanBoard anBoardNew; /* int anMove[ 8 ]; */ cubeinfo ciOpp; float rTemp; int n0, n1; int const usePrune = pec->fUsePrune && pec->rNoise == 0.0f && pci->bgv == VARIATION_STANDARD; for (i = 0; i < NUM_OUTPUTS; i++) arOutput[i] = 0.0f; /* loop over rolls */ for (n0 = 1; n0 <= 6; n0++) { for (n1 = 1; n1 <= n0; n1++) { float w = (n0 == n1) ? 1.0f : 2.0f; for (i = 0; i < 25; i++) { anBoardNew[0][i] = anBoard[0][i]; anBoardNew[1][i] = anBoard[1][i]; } if (MT_SafeGet(&fInterrupt)) { errno = EINTR; return -1; } if (usePrune) { FindBestMoveInEval(nnStates, n0, n1, anBoard, anBoardNew, pci, pec); } else { FindBestMovePlied(NULL, n0, n1, anBoardNew, pci, pec, 0, defaultFilters); } SwapSides(anBoardNew); SetCubeInfo(&ciOpp, pci->nCube, pci->fCubeOwner, !pci->fMove, pci->nMatchTo, pci->anScore, pci->fCrawford, pci->fJacoby, pci->fBeavers, pci->bgv); /* Evaluate at 0-ply */ if (EvaluatePositionCache(nnStates, (ConstTanBoard) anBoardNew, arVariationOutput, &ciOpp, pec, nPlies - 1, ClassifyPosition((ConstTanBoard) anBoardNew, ciOpp.bgv))) return -1; for (i = 0; i < NUM_OUTPUTS; i++) arOutput[i] += w *arVariationOutput[i]; } } /* normalize */ for (i = 0; i < NUM_OUTPUTS; i++) arOutput[i] /= 36; /* flop eval */ arOutput[OUTPUT_WIN] = 1.0f - arOutput[OUTPUT_WIN]; rTemp = arOutput[OUTPUT_WINGAMMON]; arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_LOSEGAMMON]; arOutput[OUTPUT_LOSEGAMMON] = rTemp; rTemp = arOutput[OUTPUT_WINBACKGAMMON]; arOutput[OUTPUT_WINBACKGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON]; arOutput[OUTPUT_LOSEBACKGAMMON] = rTemp; } else { /* at leaf node; use static evaluation */ if (acef[pc] (anBoard, arOutput, pci->bgv, nnStates)) return -1; if (pec->rNoise > 0.0f && pc != CLASS_OVER) { for (i = 0; i < NUM_OUTPUTS; i++) { arOutput[i] += Noise(pec, anBoard, i); arOutput[i] = MAX(arOutput[i], 0.0f); arOutput[i] = MIN(arOutput[i], 1.0f); } } if (pc > CLASS_GOOD || pec->rNoise > 0.0f) /* no sanity check needed for accurate evaluations */ SanityCheck(anBoard, arOutput); } return 0; } static int EvaluatePositionCache(NNState * nnStates, const TanBoard anBoard, float arOutput[], cubeinfo * const pci, const evalcontext * pecx, int nPlies, positionclass pc) { evalcache ec; uint32_t l; /* This should be a part of the code that is called in all * time-consuming operations at a relatively steady rate, so is a * good choice for a callback function. */ if (!cCache || pecx->rNoise != 0.0f) { /* non-deterministic noisy evaluations; cannot cache */ return EvaluatePositionFull(nnStates, anBoard, arOutput, pci, pecx, nPlies, pc); } PositionKey(anBoard, &ec.key); ec.nEvalContext = EvalKey(pecx, nPlies, pci, FALSE); if ((l = CacheLookup(&cEval, &ec, arOutput, NULL)) == CACHEHIT) { return 0; } if (EvaluatePositionFull(nnStates, anBoard, arOutput, pci, pecx, nPlies, pc)) return -1; memcpy(ec.ar, arOutput, sizeof(float) * NUM_OUTPUTS); ec.ar[5] = 0.f; CacheAdd(&cEval, &ec, l); return 0; } extern int EvaluatePosition(NNState * nnStates, const TanBoard anBoard, float arOutput[], cubeinfo * const pci, const evalcontext * pec) { positionclass pc = ClassifyPosition(anBoard, pci->bgv); return EvaluatePositionCache(nnStates, anBoard, arOutput, pci, pec ? pec : &ecBasic, pec ? pec->nPlies : 0, pc); } extern int ScoreMove(NNState * nnStates, move * pm, const cubeinfo * pci, const evalcontext * pec, int nPlies) { TanBoard anBoardTemp; SSE_ALIGN(float arEval[NUM_ROLLOUT_OUTPUTS]); cubeinfo ci; PositionFromKeySwapped(anBoardTemp, &pm->key); /* swap fMove in cubeinfo */ memcpy(&ci, pci, sizeof(ci)); ci.fMove = !ci.fMove; if (GeneralEvaluationEPlied(nnStates, arEval, (ConstTanBoard) anBoardTemp, &ci, pec, nPlies)) return -1; InvertEvaluationR(arEval, &ci); if (ci.nMatchTo) arEval[OUTPUT_CUBEFUL_EQUITY] = mwc2eq(arEval[OUTPUT_CUBEFUL_EQUITY], pci); /* Save evaluations */ memcpy(pm->arEvalMove, arEval, NUM_ROLLOUT_OUTPUTS * sizeof(float)); /* Save evaluation setup */ pm->esMove.et = EVAL_EVAL; pm->esMove.ec = *pec; pm->esMove.ec.nPlies = nPlies; /* Score for move: * rScore is the primary score (cubeful/cubeless) * rScore2 is the secondary score (cubeless) */ pm->rScore = (pec->fCubeful) ? arEval[OUTPUT_CUBEFUL_EQUITY] : arEval[OUTPUT_EQUITY]; pm->rScore2 = arEval[OUTPUT_EQUITY]; return 0; } static int ScoreMoves(movelist * pml, const cubeinfo * pci, const evalcontext * pec, int nPlies) { unsigned int i; int r = 0; /* return value */ NNState *nnStates = MT_Get_nnState(); pml->rBestScore = -99999.9f; if (nPlies == 0) { /* start incremental evaluations */ nnStates[0].state = nnStates[1].state = nnStates[2].state = NNSTATE_INCREMENTAL; } for (i = 0; i < pml->cMoves; i++) { if (ScoreMove(nnStates, pml->amMoves + i, pci, pec, nPlies) < 0) { r = -1; break; } if ((pml->amMoves[i].rScore > pml->rBestScore) || ((pml->amMoves[i].rScore == pml->rBestScore) && (pml->amMoves[i].rScore2 > pml->amMoves[pml->iMoveBest].rScore2))) { pml->iMoveBest = i; pml->rBestScore = pml->amMoves[i].rScore; } } if (nPlies == 0) { /* reset to none */ nnStates[0].state = nnStates[1].state = nnStates[2].state = NNSTATE_NONE; } return r; } static int ScoreMovesPruned(movelist * pml, const cubeinfo * pci, const evalcontext * pec, unsigned int *bmovesi, unsigned int prune_moves) { unsigned int j; int r = 0; /* return value */ NNState *nnStates = MT_Get_nnState(); pml->rBestScore = -99999.9f; /* start incremental evaluations */ nnStates[0].state = nnStates[1].state = nnStates[2].state = NNSTATE_INCREMENTAL; for (j = 0; j < prune_moves; j++) { unsigned int i = bmovesi[j]; if (ScoreMove(nnStates, pml->amMoves + i, pci, pec, 0) < 0) { r = -1; break; } if ((pml->amMoves[i].rScore > pml->rBestScore) || ((pml->amMoves[i].rScore == pml->rBestScore) && (pml->amMoves[i].rScore2 > pml->amMoves[pml->iMoveBest].rScore2))) { pml->iMoveBest = i; pml->rBestScore = pml->amMoves[i].rScore; } } nnStates[0].state = nnStates[1].state = nnStates[2].state = NNSTATE_NONE; return r; } static movefilter NullFilter = { -1, 0, 0.0f }; static int FindBestMovePlied(int anMove[8], int nDice0, int nDice1, TanBoard anBoard, const cubeinfo * pci, const evalcontext * pec, int nPlies, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]) { evalcontext ec; movelist ml; unsigned int i; memcpy(&ec, pec, sizeof(evalcontext)); ec.nPlies = nPlies; if (anMove) for (i = 0; i < 8; ++i) anMove[i] = -1; if (FindnSaveBestMoves(&ml, nDice0, nDice1, (ConstTanBoard) anBoard, NULL, FALSE, 0.0f, pci, &ec, aamf) < 0) { g_free(ml.amMoves); return -1; } if (anMove) { for (i = 0; i < ml.cMaxMoves * 2; i++) anMove[i] = ml.amMoves[ml.iMoveBest].anMove[i]; } if (ml.cMoves) PositionFromKey(anBoard, &ml.amMoves[ml.iMoveBest].key); g_free(ml.amMoves); return ml.cMaxMoves * 2; } extern int FindBestMove(int anMove[8], int nDice0, int nDice1, TanBoard anBoard, const cubeinfo * pci, evalcontext * pec, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]) { return FindBestMovePlied(anMove, nDice0, nDice1, anBoard, pci, pec ? pec : &ecBasic, pec ? pec->nPlies : 0, aamf); } extern int FindnSaveBestMoves(movelist * pml, int nDice0, int nDice1, const TanBoard anBoard, positionkey * keyMove, const int fAnalyse, float rThr, const cubeinfo * pci, const evalcontext * pec, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]) { /* Find best moves. * Ensure that keyMove is evaluated at the deepest ply. */ unsigned int i; unsigned int nMoves, iPly; move *pm; movefilter *mFilters; unsigned int nMaxPly = 0; unsigned int cOldMoves; /* Find all moves -- note that pml contains internal pointers to static * data, so we can't call GenerateMoves again (or anything that calls * it, such as ScoreMoves at more than 0 plies) until we have saved * the moves we want to keep in amCandidates. */ GenerateMoves(pml, anBoard, nDice0, nDice1, FALSE); if (pml->cMoves == 0) { /* no legal moves */ pml->amMoves = NULL; return 0; } /* Save moves */ #if GLIB_CHECK_VERSION (2,67,4) pm = (move *) g_memdup2(pml->amMoves, pml->cMoves * sizeof(move)); #else pm = (move *) g_memdup(pml->amMoves, pml->cMoves * sizeof(move)); #endif pml->amMoves = pm; nMoves = pml->cMoves; mFilters = (pec->nPlies > 0 && pec->nPlies <= MAX_FILTER_PLIES) ? aamf[pec->nPlies - 1] : aamf[MAX_FILTER_PLIES - 1]; for (iPly = 0; iPly < pec->nPlies; iPly++) { movefilter *mFilter = (iPly < MAX_FILTER_PLIES) ? &mFilters[iPly] : &NullFilter; unsigned int k; if (mFilter->Accept < 0) { continue; } if (ScoreMoves(pml, pci, pec, iPly) < 0) { g_free(pm); pml->cMoves = 0; pml->amMoves = NULL; return -1; } qsort(pml->amMoves, pml->cMoves, sizeof(move), (cfunc) CompareMoves); pml->iMoveBest = 0; k = pml->cMoves; /* we check for mFilter->Accept < 0 above */ pml->cMoves = MIN((unsigned int) mFilter->Accept, pml->cMoves); { unsigned int limit = MIN(k, pml->cMoves + mFilter->Extra); for ( /**/; pml->cMoves < limit; ++pml->cMoves) { if (pml->amMoves[pml->cMoves].rScore < pml->amMoves[0].rScore - mFilter->Threshold) { break; } } } nMaxPly = iPly; if (pml->cMoves == 1 && mFilter->Accept != 1) /* if there is only one move to evaluate there is no need to continue */ goto finished; } /* evaluate moves on top ply */ if (ScoreMoves(pml, pci, pec, pec->nPlies) < 0) { g_free(pm); pml->cMoves = 0; pml->amMoves = NULL; return -1; } nMaxPly = pec->nPlies; /* Resort the moves, in case the new evaluation reordered them. */ qsort(pml->amMoves, pml->cMoves, sizeof(move), (cfunc) CompareMoves); pml->iMoveBest = 0; /* set the proper size of the movelist */ finished: cOldMoves = pml->cMoves; pml->cMoves = nMoves; if (fAnalyse) { /* * If fAnalyse is true we don't want to have a move evaluated at a * lower ply to appear better than a one evaluated at a higher ply: * re-evaluate it at the ply of the previous move */ int fChanged; do { fChanged = 0; for (i = 1; i < pml->cMoves; i++) if (pml->amMoves[i].esMove.ec.nPlies < pml->amMoves[i-1].esMove.ec.nPlies && pml->amMoves[i].rScore > pml->amMoves[i-1].rScore) { float limit = pml->amMoves[i-1].rScore; fChanged = 1; for (unsigned int j = i; j < pml->cMoves; j++) if (pml->amMoves[j].rScore > limit) { ScoreMove(NULL, pml->amMoves + j, pci, pec, pml->amMoves[i-1].esMove.ec.nPlies); limit = MIN(limit, pml->amMoves[j].rScore); } } qsort(pml->amMoves, pml->cMoves, sizeof(move), (cfunc) CompareMoves); } while (fChanged == 1); } /* Make sure that keyMove and top move are both * evaluated at the deepest ply. */ if (keyMove) { int fResort = FALSE; for (i = 0; i < pml->cMoves; i++) if (EqualKeys((*keyMove), pml->amMoves[i].key)) { /* ensure top move is evaluted at deepest ply */ if (pml->amMoves[i].esMove.ec.nPlies < nMaxPly) { ScoreMove(NULL, pml->amMoves + i, pci, pec, nMaxPly); fResort = TRUE; } if ((fabsf(pml->amMoves[i].rScore - pml->amMoves[0].rScore) > rThr) && (nMaxPly < pec->nPlies)) { /* this is en error/blunder: re-analyse at top-ply */ ScoreMove(NULL, pml->amMoves, pci, pec, pec->nPlies); ScoreMove(NULL, pml->amMoves + i, pci, pec, pec->nPlies); cOldMoves = 1; /* only one move scored at deepest ply */ fResort = TRUE; } /* move it up to the other moves evaluated on nMaxPly */ if (fResort && pec->nPlies && i > 0) { move m; unsigned int j; memcpy(&m, pml->amMoves + i, sizeof m); for (j = i - 1; j >= cOldMoves; --j) memcpy(pml->amMoves + j + 1, pml->amMoves + j, sizeof(move)); memcpy(pml->amMoves + cOldMoves, &m, sizeof(m)); /* reorder moves evaluated on nMaxPly */ qsort(pml->amMoves, cOldMoves + 1, sizeof(move), (cfunc) CompareMoves); } break; } } return 0; } extern int GeneralCubeDecisionE(float aarOutput[2][NUM_ROLLOUT_OUTPUTS], const TanBoard anBoard, cubeinfo * const pci, const evalcontext * pec, const evalsetup * UNUSED(pes)) { SSE_ALIGN(float arOutput[NUM_OUTPUTS]); cubeinfo aciCubePos[2]; float arCubeful[2]; int i, j; /* Setup cube for "no double" and "double, take" */ memcpy(&aciCubePos[0], pci, sizeof(cubeinfo)); memcpy(&aciCubePos[1], pci, sizeof(cubeinfo)); aciCubePos[1].fCubeOwner = !aciCubePos[1].fMove; aciCubePos[1].nCube *= 2; if (EvaluatePositionCubeful3(NULL, anBoard, arOutput, arCubeful, aciCubePos, 2, pci, pec, pec->nPlies, TRUE)) return -1; /* Scale double-take equity */ if (!pci->nMatchTo) arCubeful[1] *= 2.0f; for (i = 0; i < 2; i++) { /* copy cubeless winning chances */ for (j = 0; j < NUM_OUTPUTS; j++) aarOutput[i][j] = arOutput[j]; /* equity */ aarOutput[i][OUTPUT_EQUITY] = UtilityME(arOutput, &aciCubePos[i]); aarOutput[i][OUTPUT_CUBEFUL_EQUITY] = arCubeful[i]; } return 0; } extern int GeneralEvaluationE(float arOutput[NUM_ROLLOUT_OUTPUTS], const TanBoard anBoard, cubeinfo * const pci, const evalcontext * pec) { return GeneralEvaluationEPlied(NULL, arOutput, anBoard, pci, pec, pec->nPlies); } static int GeneralEvaluationEPliedCubeful(NNState * nnStates, float arOutput[NUM_ROLLOUT_OUTPUTS], const TanBoard anBoard, cubeinfo * const pci, const evalcontext * pec, int nPlies) { float rCubeful; if (EvaluatePositionCubeful3(nnStates, anBoard, arOutput, &rCubeful, pci, 1, pci, pec, nPlies, FALSE)) return -1; arOutput[OUTPUT_EQUITY] = UtilityME(arOutput, pci); arOutput[OUTPUT_CUBEFUL_EQUITY] = rCubeful; return 0; } extern int GeneralEvaluationEPlied(NNState * nnStates, float arOutput[NUM_ROLLOUT_OUTPUTS], const TanBoard anBoard, cubeinfo * const pci, const evalcontext * pec, int nPlies) { if (pec->fCubeful) { if (GeneralEvaluationEPliedCubeful(nnStates, arOutput, anBoard, pci, pec, nPlies)) return -1; } else { if (EvaluatePositionCache(nnStates, anBoard, arOutput, pci, pec, nPlies, ClassifyPosition(anBoard, pci->bgv))) return -1; arOutput[OUTPUT_EQUITY] = UtilityME(arOutput, pci); arOutput[OUTPUT_CUBEFUL_EQUITY] = 0.0f; } return 0; } static int EvaluatePositionCubeful4(NNState * nnStates, const TanBoard anBoard, float arOutput[NUM_OUTPUTS], float arCubeful[], const cubeinfo aciCubePos[], int cci, cubeinfo * const pciMove, const evalcontext * pec, unsigned int nPlies, int fTop) { /* calculate cubeful equity */ int i; positionclass pc; SSE_ALIGN(float ar[NUM_OUTPUTS]); float arEquity[4]; cubeinfo ciMoveOpp; float *arCf = (float *) g_alloca(2 * cci * sizeof(float)); float *arCfTemp = (float *) g_alloca(2 * cci * sizeof(float)); cubeinfo *aci = (cubeinfo *) g_alloca(2 * cci * sizeof(cubeinfo)); pc = ClassifyPosition(anBoard, pciMove->bgv); if (pc > CLASS_OVER && nPlies > 0 && !(pc <= CLASS_PERFECT && !pciMove->nMatchTo)) { /* internal node; recurse */ TanBoard anBoardNew; int n0, n1; float r; int const usePrune = pec->fUsePrune && pec->rNoise == 0.0f && pciMove->bgv == VARIATION_STANDARD; for (i = 0; i < NUM_OUTPUTS; i++) arOutput[i] = 0.0f; for (i = 0; i < 2 * cci; i++) arCf[i] = 0.0f; /* construct next level cube positions */ MakeCubePos(aciCubePos, cci, fTop, aci, TRUE); /* loop over rolls */ for (n0 = 1; n0 <= 6; n0++) { for (n1 = 1; n1 <= n0; n1++) { float w = (n0 == n1) ? 1.0f : 2.0f; for (i = 0; i < 25; i++) { anBoardNew[0][i] = anBoard[0][i]; anBoardNew[1][i] = anBoard[1][i]; } if (MT_SafeGet(&fInterrupt)) { errno = EINTR; return -1; } if (usePrune) { FindBestMoveInEval(nnStates, n0, n1, anBoard, anBoardNew, pciMove, pec); } else { FindBestMovePlied(NULL, n0, n1, anBoardNew, pciMove, pec, 0, defaultFilters); } SwapSides(anBoardNew); SetCubeInfo(&ciMoveOpp, pciMove->nCube, pciMove->fCubeOwner, !pciMove->fMove, pciMove->nMatchTo, pciMove->anScore, pciMove->fCrawford, pciMove->fJacoby, pciMove->fBeavers, pciMove->bgv); /* Evaluate at 0-ply */ if (EvaluatePositionCubeful3(nnStates, (ConstTanBoard) anBoardNew, ar, arCfTemp, aci, 2 * cci, &ciMoveOpp, pec, nPlies - 1, FALSE)) return -1; /* Sum up cubeless winning chances and cubeful equities */ for (i = 0; i < NUM_OUTPUTS; i++) arOutput[i] += w *ar[i]; for (i = 0; i < 2 * cci; i++) arCf[i] += w *arCfTemp[i]; } } /* Flip evals */ #define sumW 36 arOutput[OUTPUT_WIN] = 1.0f - arOutput[OUTPUT_WIN] / sumW; r = arOutput[OUTPUT_WINGAMMON] / sumW; arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_LOSEGAMMON] / sumW; arOutput[OUTPUT_LOSEGAMMON] = r; r = arOutput[OUTPUT_WINBACKGAMMON] / sumW; arOutput[OUTPUT_WINBACKGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON] / sumW; arOutput[OUTPUT_LOSEBACKGAMMON] = r; for (i = 0; i < 2 * cci; i++) if (pciMove->nMatchTo) arCf[i] = 1.0f - arCf[i] / sumW; else arCf[i] = -arCf[i] / sumW; #undef sumW /* invert fMove */ /* Remember than fMove was inverted in the call to MakeCubePos */ for (i = 0; i < 2 * cci; i++) aci[i].fMove = !aci[i].fMove; /* get cubeful equities */ GetECF3(arCubeful, cci, arCf, aci); } else { /* at leaf node; use static evaluation */ float rCubeX; int ici; if (pc == CLASS_HYPERGAMMON1 || pc == CLASS_HYPERGAMMON2 || pc == CLASS_HYPERGAMMON3) { bearoffcontext *pbc = apbcHyper[pc - CLASS_HYPERGAMMON1]; unsigned int nUs, nThem, iPos; unsigned int n; if (!pbc) return -1; nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers); nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers); n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints); iPos = nUs * n + nThem; if (BearoffHyper(apbcHyper[pc - CLASS_HYPERGAMMON1], iPos, arOutput, arEquity)) return -1; } else if (pc > CLASS_OVER && pc <= CLASS_PERFECT /* && ! pciMove->nMatchTo */ ) { if (EvaluatePerfectCubeful(anBoard, arEquity, pciMove->bgv)) { return -1; } arOutput[OUTPUT_WIN] = (arEquity[0] + 1.0f) / 2.0f; arOutput[OUTPUT_WINGAMMON] = arOutput[OUTPUT_WINBACKGAMMON] = arOutput[OUTPUT_LOSEGAMMON] = arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f; } else { /* evaluate with neural net */ if (EvaluatePosition(nnStates, anBoard, arOutput, pciMove, NULL)) return -1; if (pec->rNoise > 0.0f && pc != CLASS_OVER) { for (i = 0; i < NUM_OUTPUTS; i++) { arOutput[i] += Noise(pec, anBoard, i); arOutput[i] = MAX(arOutput[i], 0.0f); arOutput[i] = MIN(arOutput[i], 1.0f); } } if (pc > CLASS_GOOD || pec->rNoise > 0.0f) /* no sanity check needed for accurate evaluations */ SanityCheck(anBoard, arOutput); } /* Calculate cube efficiency */ rCubeX = EvalEfficiency(anBoard, pc, pec->nPlies); /* Build all possible cube positions */ MakeCubePos(aciCubePos, cci, fTop, aci, FALSE); /* Calculate cubeful equity for each possible cube position */ for (ici = 0; ici < 2 * cci; ici++) if (aci[ici].nCube > 0) { /* cube available */ if (!aci[ici].nMatchTo) { /* money play */ switch (pc) { case CLASS_HYPERGAMMON1: case CLASS_HYPERGAMMON2: case CLASS_HYPERGAMMON3: /* exact bearoff equities & contact */ arCf[ici] = CFHYPER(arEquity, &aci[ici]); break; case CLASS_BEAROFF2: case CLASS_BEAROFF_TS: /* exact bearoff equities */ arCf[ici] = CFMONEY(arEquity, &aci[ici]); break; case CLASS_OVER: case CLASS_RACE: case CLASS_CRASHED: case CLASS_CONTACT: case CLASS_BEAROFF1: case CLASS_BEAROFF_OS: /* approximate using Janowski's formulae */ arCf[ici] = Cl2CfMoney(arOutput, &aci[ici], rCubeX); break; } } else { float rCl, rCf, rCfMoney; float X = rCubeX; cubeinfo ciMoney; /* match play */ switch (pc) { case CLASS_HYPERGAMMON1: case CLASS_HYPERGAMMON2: case CLASS_HYPERGAMMON3: /* use exact money equities to guess cube efficiency */ SetCubeInfoMoney(&ciMoney, 1, aci[ici].fCubeOwner, aci[ici].fMove, FALSE, FALSE, aci[ici].bgv); rCl = Utility(arOutput, &ciMoney); rCubeX = 1.0f; rCf = Cl2CfMoney(arOutput, &ciMoney, rCubeX); rCfMoney = CFHYPER(arEquity, &ciMoney); if (fabsf(rCl - rCf) > 0.0001f) rCubeX = (rCfMoney - rCl) / (rCf - rCl); arCf[ici] = Cl2CfMatch(arOutput, &aci[ici], rCubeX); rCubeX = X; break; case CLASS_BEAROFF2: case CLASS_BEAROFF_TS: /* use exact money equities to guess cube efficiency */ SetCubeInfoMoney(&ciMoney, 1, aci[ici].fCubeOwner, aci[ici].fMove, FALSE, FALSE, aci[ici].bgv); rCl = arEquity[0]; rCubeX = 1.0f; rCf = Cl2CfMoney(arOutput, &ciMoney, rCubeX); rCfMoney = CFMONEY(arEquity, &ciMoney); if (fabsf(rCl - rCf) > 0.0001f) rCubeX = (rCfMoney - rCl) / (rCf - rCl); else rCubeX = X; /* fabsf(...) > 0.0001 above is not enough. We still get some * nutty values for rCubeX and need more sanity checking */ if (rCubeX < 0.0f) rCubeX = 0.0f; if (rCubeX > X) rCubeX = X; arCf[ici] = Cl2CfMatch(arOutput, &aci[ici], rCubeX); rCubeX = X; break; case CLASS_OVER: case CLASS_RACE: case CLASS_CRASHED: case CLASS_CONTACT: case CLASS_BEAROFF1: case CLASS_BEAROFF_OS: /* approximate using Joern's generalisation of * Janowski's formulae */ arCf[ici] = Cl2CfMatch(arOutput, &aci[ici], rCubeX); break; } } } /* find optimal of "no double" and "double" */ GetECF3(arCubeful, cci, arCf, aci); } return 0; } /* EvaluatePositionCubeful3 is now just a wrapper for ....Cubeful4, which * first checks the cache, and then calls ...Cubeful3 */ extern int EvaluatePositionCubeful3(NNState * nnStates, const TanBoard anBoard, float arOutput[NUM_OUTPUTS], float arCubeful[], const cubeinfo aciCubePos[], int cci, cubeinfo * const pciMove, const evalcontext * pec, int nPlies, int fTop) { int ici; int fAll; evalcache ec; if (!cCache || pec->rNoise != 0.0f) /* non-deterministic evaluation; never cache */ { return EvaluatePositionCubeful4(nnStates, anBoard, arOutput, arCubeful, aciCubePos, cci, pciMove, pec, nPlies, fTop); } PositionKey(anBoard, &ec.key); /* check cache for existence for earlier calculation */ fAll = !fTop; /* FIXME: fTop should be a part of EvalKey */ for (ici = 0; ici < cci && fAll; ++ici) { if (aciCubePos[ici].nCube < 0) { arCubeful[ici] = -99999.9f; continue; } ec.nEvalContext = EvalKey(pec, nPlies, &aciCubePos[ici], TRUE); if (CacheLookup(&cEval, &ec, arOutput, arCubeful + ici) != CACHEHIT) { fAll = FALSE; } } /* get equities */ if (!fAll) { /* cache miss */ if (EvaluatePositionCubeful4(nnStates, anBoard, arOutput, arCubeful, aciCubePos, cci, pciMove, pec, nPlies, fTop)) return -1; /* add to cache */ if (!fTop) { for (ici = 0; ici < cci; ++ici) { if (aciCubePos[ici].nCube < 0) continue; memcpy(ec.ar, arOutput, sizeof(float) * NUM_OUTPUTS); ec.ar[5] = arCubeful[ici]; /* Cubeful equity stored in slot 5 */ ec.nEvalContext = EvalKey(pec, nPlies, &aciCubePos[ici], TRUE); CacheAdd(&cEval, &ec, GetHashKey(cEval.hashMask, &ec)); } } } return 0; }