Files
Pierre MARTINandClaude Opus 5.5 792f6395bf Copie figée du cœur de GNU Backgammon (commit 14b6798)
Fichiers repris sans modification dans engine/gnubg/, poids et table
d'équité Kazaross-XG2 dans engine/data/, provenance détaillée dans
engine/gnubg/PROVENANCE.md. Le lint ne vérifie plus les espaces de ces
copies, pour qu'elles restent identiques à l'original.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:56:23 +01:00

6397 lines
173 KiB
C

/*
* Copyright (C) 1998-2003 Gary Wong <gtw@gnu.org>
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "backgammon.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <locale.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#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;
}