Files
Ustagammon/engine/gnubg/matchequity.c
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

1852 lines
52 KiB
C

/*
* Copyright (C) 1999-2004 Joern Thyssen <jth@gnubg.org>
* Copyright (C) 2000-2016 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 <glib.h>
#include <glib/gprintf.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "list.h"
#include "mec.h"
#include "util.h"
#define GAMMONRATE 0.25
#include "eval.h"
#include "matchequity.h"
#include "backgammon.h"
typedef struct {
char *szName;
float value;
} parameter;
typedef struct {
char *szName;
listOLD lParameters;
} metparameters;
typedef struct {
/* Data saved */
/* Pre crawford MET */
float aarMET[MAXSCORE][MAXSCORE];
metparameters mpPreCrawford;
/* post-crawford MET */
float aarMETPostCrawford[2][MAXSCORE];
metparameters ampPostCrawford[2];
/* MET info */
metinfo mi;
} metdata;
/*
* A1 (A2) is the match equity of player 1 (2)
* Btilde is the post-crawford match equities.
*/
float aafMET[MAXSCORE][MAXSCORE];
float aafMETPostCrawford[2][MAXSCORE];
/* gammon prices (calculated once for efficiency) */
float aaaafGammonPrices[MAXCUBELEVEL]
[MAXSCORE][MAXSCORE][4];
float aaaafGammonPricesPostCrawford[MAXCUBELEVEL]
[MAXSCORE][2][4];
metinfo miCurrent;
/*
* Calculate area under normal distribution curve (with mean rMu and
* std.dev rSigma) from rMax to rMin.
*
* Input:
* rMin, rMax: upper and lower integral limits
* rMu, rSigma: normal distribution parameters.
*
* Returns:
* area
*
*/
static float
NormalDistArea(float rMin, float rMax, float rMu, float rSigma)
{
float rtMin, rtMax;
float rInt1, rInt2;
rtMin = (rMin - rMu) / rSigma;
rtMax = (rMax - rMu) / rSigma;
rInt1 = (erff(rtMin / sqrtf(2)) + 1.0f) / 2.0f;
rInt2 = (erff(rtMax / sqrtf(2)) + 1.0f) / 2.0f;
return rInt2 - rInt1;
}
static int
GetCubePrimeValue(int i, int j, int nCubeValue)
{
if ((i < 2 * nCubeValue) && (j >= 2 * nCubeValue))
/* automatic double */
return 2 * nCubeValue;
else
return nCubeValue;
}
/*
* Calculate post-Crawford match equity table
*
* Formula taken from Zadeh, Management Science 23, 986 (1977)
*
* Input:
* rG: gammon rate for trailer
* rFD2: value of free drop at 1-away, 2-away
* rFD4: value of free drop at 1-away, 4-away
* iStart: score where met starts, e.g.
* iStart = 0 : 1-away, 1-away
* iStart = 15: 1-away, 16-away
*
*
* Output:
* afMETPostCrawford: post-Crawford match equity table
*
*/
static void
initPostCrawfordMET(float afMETPostCrawford[MAXSCORE],
const int iStart, const float rG, const float rFD2, const float rFD4)
{
int i;
/*
* Calculate post-crawford match equities
*/
for (i = iStart; i < MAXSCORE; i++) {
afMETPostCrawford[i] = rG * 0.5f * ((i - 4 >= 0) ? afMETPostCrawford[i - 4] : 1.0f)
+ (1.0f - rG) * 0.5f * ((i - 2 >= 0) ? afMETPostCrawford[i - 2] : 1.0f);
/*"insane post crawford equity" */
g_assert(afMETPostCrawford[i] >= 0.0f && afMETPostCrawford[i] <= 1.0f);
/*
* add 1.5% at 1-away, 2-away for the free drop
* add 0.4% at 1-away, 4-away for the free drop
*/
if (i == 1)
afMETPostCrawford[i] -= rFD2;
/*"insane post crawford equity(1)" */
g_assert(afMETPostCrawford[i] >= 0.0f && afMETPostCrawford[i] <= 1.0f);
if (i == 3)
afMETPostCrawford[i] -= rFD4;
/*"insane post crawford equity(2)" */
g_assert(afMETPostCrawford[i] >= 0.0f && afMETPostCrawford[i] <= 1.0f);
}
}
/*
* Calculate match equity table from Zadeh's formula published
* in Management Science 23, 986 (1977)
*
* Input:
* rG1: gammon rate for leader of match
* rG2: gammon rate for trailer of match
* rDelta, rDeltaBar: parameters in the Zadeh model
* (something to do with cube efficiencies)
* rG3: post-Crawford gammon rate for trailer of match
* afMETPostCrawford: post-Crawford match equity table
*
* Output:
* aafMET: match equity table
*
*/
static void
initMETZadeh(float aafMET[MAXSCORE][MAXSCORE],
const float afMETPostCrawford[MAXSCORE],
const float rG1, const float rG2, const float rDelta, const float rDeltaBar)
{
int i, j, k;
int nCube;
int nCubeValue, nCubePrimeValue;
/*
* D1bar (D2bar) is the equity of player 1 (2) at the drop point
* of player 2 (1) assuming dead cubes.
* D1 (D2) is the equity of player 1 (2) at the drop point
* of player 2 (1) assuming semiefficient recubes.
*/
float aaafD1[MAXSCORE][MAXSCORE][MAXCUBELEVEL];
float aaafD2[MAXSCORE][MAXSCORE][MAXCUBELEVEL];
float aaafD1bar[MAXSCORE][MAXSCORE][MAXCUBELEVEL];
float aaafD2bar[MAXSCORE][MAXSCORE][MAXCUBELEVEL];
/*
* Calculate 1-away,n-away match equities
*/
for (i = 0; i < MAXSCORE; i++) {
aafMET[i][0] = rG1 * 0.5f * ((i - 2 >= 0) ? afMETPostCrawford[i - 2] : 1.0f)
+ (1.0f - rG1) * 0.5f * ((i - 1 >= 0) ? afMETPostCrawford[i - 1] : 1.0f);
aafMET[0][i] = 1.0f - aafMET[i][0];
}
for (i = 0; i < MAXSCORE; i++) {
for (j = 0; j <= i; j++) {
for (nCube = MAXCUBELEVEL - 1; nCube >= 0; nCube--) {
nCubeValue = 1;
for (k = 0; k < nCube; k++)
nCubeValue *= 2;
/* Calculate D1bar */
nCubePrimeValue = GetCubePrimeValue(i, j, nCubeValue);
aaafD1bar[i][j][nCube] = (GET_MET(i - nCubeValue, j, aafMET)
- rG2 * GET_MET(i, j - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(i, j - 2 * nCubePrimeValue, aafMET))
/ (rG1 * GET_MET(i - 4 * nCubePrimeValue, j, aafMET)
+ (1.0f - rG1) * GET_MET(i - 2 * nCubePrimeValue, j, aafMET)
- rG2 * GET_MET(i, j - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(i, j - 2 * nCubePrimeValue, aafMET));
if (i != j) {
nCubePrimeValue = GetCubePrimeValue(j, i, nCubeValue);
aaafD1bar[j][i][nCube] = (GET_MET(j - nCubeValue, i, aafMET)
- rG2 * GET_MET(j, i - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(j, i - 2 * nCubePrimeValue, aafMET))
/ (rG1 * GET_MET(j - 4 * nCubePrimeValue, i, aafMET)
+ (1.0f - rG1) * GET_MET(j - 2 * nCubePrimeValue, i, aafMET)
- rG2 * GET_MET(j, i - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(j, i - 2 * nCubePrimeValue, aafMET));
}
/* Calculate D2bar */
nCubePrimeValue = GetCubePrimeValue(j, i, nCubeValue);
aaafD2bar[i][j][nCube] = (GET_MET(j - nCubeValue, i, aafMET)
- rG2 * GET_MET(j, i - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(j, i - 2 * nCubePrimeValue, aafMET))
/ (rG1 * GET_MET(j - 4 * nCubePrimeValue, i, aafMET)
+ (1.0f - rG1) * GET_MET(j - 2 * nCubePrimeValue, i, aafMET)
- rG2 * GET_MET(j, i - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(j, i - 2 * nCubePrimeValue, aafMET));
if (i != j) {
nCubePrimeValue = GetCubePrimeValue(i, j, nCubeValue);
aaafD2bar[j][i][nCube] = (GET_MET(i - nCubeValue, j, aafMET)
- rG2 * GET_MET(i, j - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(i, j - 2 * nCubePrimeValue, aafMET))
/ (rG1 * GET_MET(i - 4 * nCubePrimeValue, j, aafMET)
+ (1.0f - rG1) * GET_MET(i - 2 * nCubePrimeValue, j, aafMET)
- rG2 * GET_MET(i, j - 4 * nCubePrimeValue, aafMET)
- (1.0f - rG2) * GET_MET(i, j - 2 * nCubePrimeValue, aafMET));
}
/* Calculate D1 */
if ((i < 2 * nCubeValue) || (j < 2 * nCubeValue)) {
aaafD1[i][j][nCube] = aaafD1bar[i][j][nCube];
if (i != j)
aaafD1[j][i][nCube] = aaafD1bar[j][i][nCube];
} else {
aaafD1[i][j][nCube] = 1.0f + (aaafD2[i][j][nCube + 1] + rDelta)
* (aaafD1bar[i][j][nCube] - 1.0f);
if (i != j)
aaafD1[j][i][nCube] = 1.0f + (aaafD2[j][i][nCube + 1] + rDelta)
* (aaafD1bar[j][i][nCube] - 1.0f);
}
/* Calculate D2 */
if ((i < 2 * nCubeValue) || (j < 2 * nCubeValue)) {
aaafD2[i][j][nCube] = aaafD2bar[i][j][nCube];
if (i != j)
aaafD2[j][i][nCube] = aaafD2bar[j][i][nCube];
} else {
aaafD2[i][j][nCube] = 1.0f + (aaafD1[i][j][nCube + 1] + rDelta)
* (aaafD2bar[i][j][nCube] - 1.0f);
if (i != j)
aaafD2[j][i][nCube] = 1.0f + (aaafD1[j][i][nCube + 1] + rDelta)
* (aaafD2bar[j][i][nCube] - 1.0f);
}
/* Calculate A1 & A2 */
if ((nCube == 0) && (i > 0) && (j > 0)) {
aafMET[i][j] = ((aaafD2[i][j][0] + rDeltaBar - 0.5f)
* GET_MET(i - 1, j, aafMET)
+ (aaafD1[i][j][0] + rDeltaBar - 0.5f)
* GET_MET(i, j - 1, aafMET))
/ (aaafD1[i][j][0] + rDeltaBar + aaafD2[i][j][0] + rDeltaBar - 1.0f);
if (i != j)
aafMET[j][i] = 1.0f - aafMET[i][j];
}
}
}
}
}
extern int
GetPoints(float arOutput[5], const cubeinfo * pci, float arCP[2])
{
/*
* Input:
* - arOutput: we need the gammon and backgammon ratios
* (we assume arOutput is evaluate for pci -> fMove)
* - anScore: the current score.
* - nMatchTo: matchlength
* - pci: value of cube, who's turn is it
*
*
* Output:
* - arCP : cash points with live cube
* These points are necessary for the linear
* interpolation used in cubeless -> cubeful equity
* transformation.
*/
/* Match play */
/* normalize score */
int i = pci->nMatchTo - pci->anScore[0] - 1;
int j = pci->nMatchTo - pci->anScore[1] - 1;
int nCube = pci->nCube;
float arCPLive[2][MAXCUBELEVEL];
float arCPDead[2][MAXCUBELEVEL];
float arG[2], arBG[2];
float rDP, rRDP, rDTW, rDTL;
int nDead, n, nMax, nCubeValue, k;
float aarMETResults[2][DTLBP1 + 1];
/* Gammon and backgammon ratio's.
* Avoid division by zero in extreme cases. */
if (!pci->fMove) {
/* arOutput evaluated for player 0 */
if (arOutput[OUTPUT_WIN] > 0.0f) {
arG[0] = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN];
arBG[0] = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN];
} else {
arG[0] = 0.0;
arBG[0] = 0.0;
}
if (arOutput[OUTPUT_WIN] < 1.0f) {
arG[1] = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]);
arBG[1] = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]);
} else {
arG[1] = 0.0;
arBG[1] = 0.0;
}
} else {
/* arOutput evaluated for player 1 */
if (arOutput[OUTPUT_WIN] > 0.0f) {
arG[1] = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN];
arBG[1] = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN];
} else {
arG[1] = 0.0;
arBG[1] = 0.0;
}
if (arOutput[OUTPUT_WIN] < 1.0f) {
arG[0] = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]);
arBG[0] = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]);
} else {
arG[0] = 0.0;
arBG[0] = 0.0;
}
}
/* Find out what value the cube has when you or your
* opponent give a dead cube. */
nDead = nCube;
nMax = 0;
while ((i >= 2 * nDead) && (j >= 2 * nDead)) {
nMax++;
nDead *= 2;
}
for (nCubeValue = nDead, n = nMax; n >= 0; nCubeValue >>= 1, n--) {
/* Calculate dead and live cube cash points.
* See notes by me (Joern Thyssen) available from the
* 'doc' directory. (FIXME: write notes :-) ) */
/* Even though it's a dead cube we take account of the opponents
* automatic redouble. */
/* Dead cube cash point for player 0 */
getMEMultiple(pci->anScore[0], pci->anScore[1], pci->nMatchTo, nCubeValue, GetCubePrimeValue(i, j, nCubeValue), /* 0 */
GetCubePrimeValue(j, i, nCubeValue), /* 1 */
pci->fCrawford, aafMET, aafMETPostCrawford, aarMETResults[0], aarMETResults[1]);
for (k = 0; k < 2; k++) {
/* Live cube cash point for player */
if ((i < 2 * nCubeValue) || (j < 2 * nCubeValue)) {
rDTL = (1.0f - arG[!k] - arBG[!k]) * aarMETResults[k][k ? DTLP1 : DTLP0]
+ arG[!k] * aarMETResults[k][k ? DTLGP1 : DTLGP0]
+ arBG[!k] * aarMETResults[k][k ? DTLBP1 : DTLBP0];
rDP = aarMETResults[k][DP];
rDTW = (1.0f - arG[k] - arBG[k]) * aarMETResults[k][k ? DTWP1 : DTWP0]
+ arG[k] * aarMETResults[k][k ? DTWGP1 : DTWGP0]
+ arBG[k] * aarMETResults[k][k ? DTWBP1 : DTWBP0];
arCPDead[k][n] = (rDTL - rDP) / (rDTL - rDTW);
/* The doubled cube is going to be dead */
arCPLive[k][n] = arCPDead[k][n];
} else {
/* Doubled cube is alive */
/* redouble, pass */
rRDP = aarMETResults[k][DTL];
/* double, pass */
rDP = aarMETResults[k][DP];
/* double, take win */
rDTW = (1.0f - arG[k] - arBG[k]) * aarMETResults[k][DTW]
+ arG[k] * aarMETResults[k][DTWG]
+ arBG[k] * aarMETResults[k][DTWB];
arCPLive[k][n] = 1.0f - arCPLive[!k][n + 1] * (rDP - rDTW) / (rRDP - rDTW);
}
} /* loop k */
}
/* return cash point for current cube level */
arCP[0] = arCPLive[0][0];
arCP[1] = arCPLive[1][0];
#if 0
for (n = nMax; n >= 0; n--) {
printf("Cube %i\n"
"Dead cube: cash point 0 %6.3f\n"
" cash point 1 %6.3f\n"
"Live cube: cash point 0 %6.3f\n"
" cash point 1 %6.3f\n\n",
n, arCPDead[0][n], arCPDead[1][n], arCPLive[0][n], arCPLive[1][n]);
}
#endif
return 0;
}
extern float
GetDoublePointDeadCube(float arOutput[5], cubeinfo * pci)
{
/*
* Calculate double point for dead cubes
*/
int player = pci->fMove;
if (!pci->nMatchTo) {
/* Use Rick Janowski's formulas
* [insert proper reference here] */
float rW, rL;
if (arOutput[0] > 0.0f)
rW = 1.0f + (arOutput[1] + arOutput[2]) / arOutput[0];
else
rW = 1.0f;
if (arOutput[0] < 1.0f)
rL = 1.0f + (arOutput[3] + arOutput[4]) / (1.0f - arOutput[0]);
else
rL = 1.0f;
if (pci->fCubeOwner == -1 && pci->fJacoby) {
/* centered cube */
if (pci->fBeavers) {
return (rL - 0.25f) / (rW + rL - 0.5f);
} else {
return (rL - 0.5f) / (rW + rL - 1.0f);
}
} else {
/* redoubles or Jacoby rule not in effect */
return rL / (rL + rW);
}
} else {
/* Match play */
float aarMETResults[2][DTLBP1 + 1];
/* normalize score */
float rG1, rBG1, rG2, rBG2, rDTW, rNDW, rDTL, rNDL;
float rRisk, rGain;
/* FIXME: avoid division by zero */
if (arOutput[OUTPUT_WIN] > 0.0f) {
rG1 = (arOutput[OUTPUT_WINGAMMON] - arOutput[OUTPUT_WINBACKGAMMON]) / arOutput[OUTPUT_WIN];
rBG1 = arOutput[OUTPUT_WINBACKGAMMON] / arOutput[OUTPUT_WIN];
} else {
rG1 = 0.0;
rBG1 = 0.0;
}
if (arOutput[OUTPUT_WIN] < 1.0f) {
rG2 = (arOutput[OUTPUT_LOSEGAMMON] - arOutput[OUTPUT_LOSEBACKGAMMON]) / (1.0f - arOutput[OUTPUT_WIN]);
rBG2 = arOutput[OUTPUT_LOSEBACKGAMMON] / (1.0f - arOutput[OUTPUT_WIN]);
} else {
rG2 = 0.0;
rBG2 = 0.0;
}
getMEMultiple(pci->anScore[0], pci->anScore[1], pci->nMatchTo,
pci->nCube, -1, -1,
pci->fCrawford, aafMET, aafMETPostCrawford, aarMETResults[0], aarMETResults[1]);
/* double point */
/* double point */
rDTW = (1.0f - rG1 - rBG1) * aarMETResults[player][DTW]
+ rG1 * aarMETResults[player][DTWG]
+ rBG1 * aarMETResults[player][DTWB];
rNDW = (1.0f - rG1 - rBG1) * aarMETResults[player][NDW]
+ rG1 * aarMETResults[player][NDWG]
+ rBG1 * aarMETResults[player][NDWB];
rDTL = (1.0f - rG2 - rBG2) * aarMETResults[player][DTL]
+ rG2 * aarMETResults[player][DTLG]
+ rBG2 * aarMETResults[player][DTLB];
rNDL = (1.0f - rG2 - rBG2) * aarMETResults[player][NDL]
+ rG2 * aarMETResults[player][NDLG]
+ rBG2 * aarMETResults[player][NDLB];
/* risk & gain */
rRisk = rNDL - rDTL;
rGain = rDTW - rNDW;
return (rRisk == 0.0f) ? 0.0f : rRisk / (rRisk + rGain);
}
}
/*
* Extend match equity table to MAXSCORE using
* David Montgomery's extension algorithm. The formula
* is independent of the values for score < nMaxScore.
*
* Input:
* nMaxScore: the length of the native met
* aarMET: the match equity table for scores below nMaxScore
*
* Output:
* aarMET: the match equity table for scores up to MAXSCORE.
*
*/
static void
ExtendMET(float aarMET[MAXSCORE][MAXSCORE], const int nMaxScore)
{
static const float arStddevTable[] =
{ 0, 1.24f, 1.27f, 1.47f, 1.50f, 1.60f, 1.61f, 1.66f, 1.68f, 1.70f, 1.72f, 1.77f };
float rStddev0, rStddev1, rGames, rSigma;
int i, j;
/* Extend match equity table */
for (i = nMaxScore; i < MAXSCORE; i++) {
int nScore0 = i + 1;
if (nScore0 > 10)
rStddev0 = 1.77f;
else
rStddev0 = arStddevTable[nScore0];
for (j = 0; j <= i; j++) {
int nScore1 = j + 1;
rGames = (float)(nScore0 + nScore1) / 2.0f;
if (nScore1 > 10)
rStddev1 = 1.77f;
else
rStddev1 = arStddevTable[nScore1];
rSigma = sqrtf(rStddev0 * rStddev0 + rStddev1 * rStddev1) * sqrtf(rGames);
if (6.0f * rSigma > (float) (nScore0 - nScore1))
aarMET[i][j] = NormalDistArea((float) (nScore0 - nScore1), 6.0f * rSigma, 0.0f, rSigma);
else
aarMET[i][j] = 0.0f;
}
}
/* Generate j > i part of MET */
for (i = 0; i < MAXSCORE; i++)
for (j = ((i < nMaxScore) ? nMaxScore : i + 1); j < MAXSCORE; j++)
aarMET[i][j] = 1.0f - aarMET[j][i];
}
static void
initMP(metparameters * pmp)
{
pmp->szName = NULL;
ListCreate(&pmp->lParameters);
}
static void
initMD(metdata * pmd)
{
int i;
pmd->mi.szName = NULL;
pmd->mi.szFileName = NULL;
pmd->mi.szDescription = NULL;
initMP(&pmd->mpPreCrawford);
for (i = 0; i < 2; i++)
initMP(&pmd->ampPostCrawford[i]);
}
static void
freeP(parameter * pp)
{
g_free(pp->szName);
g_free(pp);
}
static void
freeMP(metparameters * pmp)
{
listOLD *pl;
g_free(pmp->szName);
pl = &pmp->lParameters;
while (pl->plNext != pl) {
freeP(pl->plNext->p);
ListDelete(pl->plNext);
}
}
static void
getDefaultMET(metdata * pmd)
{
int i, j;
parameter *pp;
static parameter apPreCrawford[] = { {"gammon-rate-leader", 0.25f},
{"gammon-rate-trailer", 0.15f},
{"delta", 0.08f},
{"deltebar", 0.06f}
};
static parameter apPostCrawford[] = { {"gammon-rate-trailer", 0.25f},
{"free-drop-2-away", 0.015f},
{"free-drop-4-away", 0.004f}
};
/* Setup default met */
initMD(pmd);
for (i = 0; i < 4; i++) {
pp = (parameter *) g_malloc(sizeof(parameter));
pp->szName = g_strdup(apPreCrawford[i].szName);
pp->value = apPreCrawford[i].value;
ListInsert(&pmd->mpPreCrawford.lParameters, pp);
}
for (j = 0; j < 2; j++) {
for (i = 0; i < 3; i++) {
pp = (parameter *) g_malloc(sizeof(parameter));
pp->szName = g_strdup(apPostCrawford[i].szName);
pp->value = apPostCrawford[i].value;
ListInsert(&pmd->ampPostCrawford[j].lParameters, pp);
}
pmd->ampPostCrawford[j].szName = g_strdup("zadeh");
}
pmd->mpPreCrawford.szName = g_strdup("zadeh");
pmd->mi.szName = g_strdup("N. Zadeh, Management Science 23, 986 (1977)");
pmd->mi.szFileName = g_strdup("met/zadeh.xml");
pmd->mi.szDescription = g_strdup("");
pmd->mi.nLength = MAXSCORE;
}
static int
initMETFromParameters(float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE], metparameters * pmp)
{
if (!strcmp((char *) pmp->szName, "mec")) {
float rG = 0.15f;
float rWR = 0.5f;
listOLD *pl;
parameter *pp;
/*
*/
/* obtain parameters */
for (pl = pmp->lParameters.plNext; pl != &pmp->lParameters; pl = pl->plNext) {
pp = pl->p;
if (!strcmp((char *) pp->szName, "gammon-rate"))
rG = pp->value;
else if (!strcmp((char *) pp->szName, "win-rate"))
rWR = pp->value;
}
/* calculate table */
mec(rG, rWR, aafMETPostCrawford, aafMET);
return 0;
} else if (!strcmp((char *) pmp->szName, "zadeh")) {
float rG1 = 0.25f;
float rG2 = 0.15f;
float rDelta = 0.08f;
float rDeltaBar = 0.06f;
listOLD *pl;
parameter *pp;
/*
* Use Zadeh's formulae
*/
/* obtain parameters */
for (pl = pmp->lParameters.plNext; pl != &pmp->lParameters; pl = pl->plNext) {
pp = pl->p;
if (!strcmp((char *) pp->szName, "gammon-rate-leader"))
rG1 = pp->value;
else if (!strcmp((char *) pp->szName, "gammon-rate-trailer"))
rG2 = pp->value;
else if (!strcmp((char *) pp->szName, "delta"))
rDelta = pp->value;
else if (!strcmp((char *) pp->szName, "delta-bar"))
rDeltaBar = pp->value;
}
/* calculate table */
initMETZadeh(aafMET, aafMETPostCrawford[0], rG1, rG2, rDelta, rDeltaBar);
return 0;
} else {
/* unknown type */
return -1;
}
}
static int
initPostCrawfordMETFromParameters(float afMETPostCrawford[MAXSCORE], metparameters * pmp)
{
if (!strcmp((char *) pmp->szName, "zadeh")) {
float rG = 0.25f;
float rFD2 = 0.015f;
float rFD4 = 0.004f;
listOLD *pl;
parameter *pp;
/*
* Use Zadeh's formulae
*/
/* obtain parameters */
for (pl = pmp->lParameters.plNext; pl != &pmp->lParameters; pl = pl->plNext) {
pp = pl->p;
if (!strcmp((char *) pp->szName, "gammon-rate-trailer"))
rG = pp->value;
else if (!strcmp((char *) pp->szName, "free-drop-2-away"))
rFD2 = pp->value;
else if (!strcmp((char *) pp->szName, "free-drop-4-away"))
rFD4 = pp->value;
}
/* calculate table */
initPostCrawfordMET(afMETPostCrawford, 0, rG, rFD2, rFD4);
return 0;
} else if (!strcmp((char *) pmp->szName, "mec")) {
float rG = 0.25f;
float rFD2 = 0.015f;
float rFD4 = 0.004f;
float rWR = 0.5f;
listOLD *pl;
parameter *pp;
/* obtain parameters */
for (pl = pmp->lParameters.plNext; pl != &pmp->lParameters; pl = pl->plNext) {
pp = pl->p;
if (!strcmp((char *) pp->szName, "gammon-rate"))
rG = pp->value;
else if (!strcmp((char *) pp->szName, "free-drop-2-away"))
rFD2 = pp->value;
else if (!strcmp((char *) pp->szName, "free-drop-4-away"))
rFD4 = pp->value;
else if (!strcmp((char *) pp->szName, "win-rate"))
rWR = pp->value;
}
/* calculate table */
mec_pc(rG, rFD2, rFD4, rWR, afMETPostCrawford);
return 0;
} else {
/* unknown type */
return -1;
}
}
typedef enum {
STATE_NONE,
STATE_MET,
STATE_INFO,
STATE_NAME,
STATE_DESCRIPTION, STATE_LENGTH,
STATE_PRECRAWFORD,
STATE_PRECRAWFORD_ROW,
STATE_PRECRAWFORD_ME,
STATE_PRECRAWFORD_PARAMS,
STATE_PRECRAWFORD_PARAM,
STATE_POSTCRAWFORD,
STATE_POSTCRAWFORD_ROW,
STATE_POSTCRAWFORD_ME,
STATE_POSTCRAWFORD_PARAMS,
STATE_POSTCRAWFORD_PARAM
} parserstate;
typedef enum {
TYPE_EXPLICIT,
TYPE_MEC,
TYPE_ZADEH
} tabletype;
typedef enum {
MP_PLAYER_0 = 0,
MP_PLAYER_1,
MP_PLAYER_BOTH = 2
} metplayer;
typedef struct {
parserstate state;
tabletype type;
metplayer player;
metdata *data;
guint i, j; /* table index */
parameter *current_param;
gchar *filename;
} MatchEquityParser;
static void met_parser_start_element(GMarkupParseContext * context,
const gchar * element_name,
const gchar ** attribute_names,
const gchar ** attribute_values, gpointer user_data, GError ** error);
static void met_parser_end_element(GMarkupParseContext * context,
const gchar * element_name, gpointer user_data, GError ** error);
static void met_parser_characters(GMarkupParseContext * context,
const gchar * text, gsize text_len, gpointer user_data, GError ** error);
static void met_parser_error(GMarkupParseContext * context, GError * error, gpointer user_data);
static GMarkupParser markup_parser = {
met_parser_start_element,
met_parser_end_element,
met_parser_characters,
NULL,
met_parser_error
};
static void
met_parser_destroy(gpointer data)
{
MatchEquityParser *parser = (MatchEquityParser *) data;
g_free(parser->filename);
g_free(parser);
}
static int
readMET(metdata * met, const char *filename)
{
GMarkupParseContext *context;
gchar *contents;
MatchEquityParser *parser;
gsize size;
GError *error = NULL;
initMD(met);
met->mi.szFileName = g_strdup(filename);
/* Make and initialize a new parser */
parser = g_new0(MatchEquityParser, 1);
parser->filename = g_strdup(filename);
parser->data = met;
parser->state = STATE_NONE;
parser->i = 0;
parser->j = 0;
if (!(context = g_markup_parse_context_new(&markup_parser, (GMarkupParseFlags) 0, parser, met_parser_destroy))) {
g_warning(_("Can't create XML parser\n"));
g_free(parser);
return -1;
}
/* Read the file */
if (!g_file_get_contents(filename, &contents, &size, &error)) {
g_warning(_("Error reading XML file: %s\n"), error->message);
goto err;
}
/* Parse the content */
if (!g_markup_parse_context_parse(context, contents, size, &error)) {
g_warning(_("Error parsing XML file: %s\n"), error->message);
goto err;
}
err:
g_markup_parse_context_free(context);
g_free(contents);
if (error) {
g_error_free(error);
return -1;
} else {
return 0;
}
}
static void
parse_attributes(MatchEquityParser * parser, const gchar ** attr_names, const gchar ** attr_values)
{
gint i = 0;
while (attr_names[i]) {
if (strcmp(attr_names[i], "type") == 0) {
if (strcmp(attr_values[i], "explicit") == 0)
parser->type = TYPE_EXPLICIT;
else if (strcmp(attr_values[i], "mec") == 0)
parser->type = TYPE_MEC;
else if (strcmp(attr_values[i], "zadeh") == 0)
parser->type = TYPE_ZADEH;
} else if (strcmp(attr_names[i], "player") == 0) {
if (strcmp(attr_values[i], "both") == 0)
parser->player = MP_PLAYER_BOTH;
if (strcmp(attr_values[i], "0") == 0)
parser->player = MP_PLAYER_0;
if (strcmp(attr_values[i], "1") == 0)
parser->player = MP_PLAYER_1;
}
i++;
}
}
static void
start_param(MatchEquityParser * parser, const gchar ** attr_names, const gchar ** attr_values)
{
gint i = 0;
while (attr_names[i]) {
if (strcmp(attr_names[i], "name") == 0) {
parser->current_param = g_new0(parameter, 1);
parser->current_param->szName = g_strdup(attr_values[i]);
break;
}
i++;
}
}
static void
SetNameFromType(char **pName, tabletype type)
{
switch (type) {
case TYPE_EXPLICIT:
*pName = g_strdup("explicit");
break;
case TYPE_MEC:
*pName = g_strdup("mec");
break;
case TYPE_ZADEH:
*pName = g_strdup("zadeh");
break;
default:
g_error(_("Invalid MET table type"));
}
}
static void
met_parser_start_element(GMarkupParseContext * UNUSED(context),
const gchar * element_name,
const gchar ** attribute_names,
const gchar ** attribute_values, gpointer user_data, GError ** UNUSED(error))
{
MatchEquityParser *parser = (MatchEquityParser *) user_data;
switch (parser->state) {
case STATE_NONE:
if (strcmp(element_name, "met") == 0)
parser->state = STATE_MET;
break;
case STATE_MET:
if (strcmp(element_name, "info") == 0) {
parser->state = STATE_INFO;
} else if (strcmp(element_name, "pre-crawford-table") == 0) {
parser->state = STATE_PRECRAWFORD;
parser->i = 0;
parser->j = 0;
parser->type = TYPE_EXPLICIT;
parse_attributes(parser, attribute_names, attribute_values);
SetNameFromType(&parser->data->mpPreCrawford.szName, parser->type);
} else if (strcmp(element_name, "post-crawford-table") == 0) {
parser->state = STATE_POSTCRAWFORD;
parser->i = 0;
parser->type = TYPE_EXPLICIT;
parser->player = MP_PLAYER_BOTH;
parse_attributes(parser, attribute_names, attribute_values);
SetNameFromType(&parser->data->ampPostCrawford[0].szName, parser->type);
SetNameFromType(&parser->data->ampPostCrawford[1].szName, parser->type);
}
break; /* STATE_MET */
case STATE_INFO:
if (strcmp(element_name, "name") == 0)
parser->state = STATE_NAME;
else if (strcmp(element_name, "description") == 0)
parser->state = STATE_DESCRIPTION;
else if (strcmp(element_name, "length") == 0)
parser->state = STATE_LENGTH;
break; /* STATE_INFO */
case STATE_PRECRAWFORD:
if (strcmp(element_name, "row") == 0)
parser->state = STATE_PRECRAWFORD_ROW;
else if (strcmp(element_name, "parameters") == 0) {
parser->state = STATE_PRECRAWFORD_PARAMS;
}
break; /* STATE_PRECRAWFORD */
case STATE_PRECRAWFORD_ROW:
if (strcmp(element_name, "me") == 0)
parser->state = STATE_PRECRAWFORD_ME;
break; /* STATE_PRECRAWFORD_ROW */
case STATE_PRECRAWFORD_PARAMS:
if (strcmp(element_name, "parameter") == 0) {
parser->state = STATE_PRECRAWFORD_PARAM;
start_param(parser, attribute_names, attribute_values);
}
break; /* STATE_PRECRAWFORD_PARAMS */
case STATE_POSTCRAWFORD:
if (strcmp(element_name, "row") == 0)
parser->state = STATE_POSTCRAWFORD_ROW;
else if (strcmp(element_name, "parameters") == 0) {
parser->state = STATE_POSTCRAWFORD_PARAMS;
}
break; /* STATE_POSTCRAWFORD */
case STATE_POSTCRAWFORD_ROW:
if (strcmp(element_name, "me") == 0)
parser->state = STATE_POSTCRAWFORD_ME;
break; /* STATE_POSTCRAWFORD_ROW */
case STATE_POSTCRAWFORD_PARAMS:
if (strcmp(element_name, "parameter") == 0) {
parser->state = STATE_POSTCRAWFORD_PARAM;
start_param(parser, attribute_names, attribute_values);
}
break; /* STATE_POSTCRAWFORD_PARAMS */
case STATE_NAME:
case STATE_DESCRIPTION:
case STATE_LENGTH:
case STATE_PRECRAWFORD_ME:
case STATE_POSTCRAWFORD_ME:
case STATE_PRECRAWFORD_PARAM:
case STATE_POSTCRAWFORD_PARAM:
default:
break;
}
}
static void
met_parser_end_element(GMarkupParseContext * UNUSED(context),
const gchar * UNUSED(element_name), gpointer user_data, GError ** UNUSED(error))
{
MatchEquityParser *parser = (MatchEquityParser *) user_data;
switch (parser->state) {
case STATE_NONE:
g_assert_not_reached();
break;
case STATE_MET:
parser->state = STATE_NONE;
break;
case STATE_INFO:
case STATE_PRECRAWFORD:
case STATE_POSTCRAWFORD:
parser->state = STATE_MET;
break;
case STATE_NAME:
case STATE_DESCRIPTION:
case STATE_LENGTH:
parser->state = STATE_INFO;
break;
case STATE_PRECRAWFORD_ROW:
parser->state = STATE_PRECRAWFORD;
parser->j++;
parser->i = 0;
break;
case STATE_PRECRAWFORD_ME:
parser->state = STATE_PRECRAWFORD_ROW;
parser->i++;
break;
case STATE_PRECRAWFORD_PARAMS:
parser->state = STATE_PRECRAWFORD;
break;
case STATE_PRECRAWFORD_PARAM:
parser->state = STATE_PRECRAWFORD_PARAMS;
ListInsert(&parser->data->mpPreCrawford.lParameters, parser->current_param);
parser->current_param = NULL;
break;
case STATE_POSTCRAWFORD_ROW:
case STATE_POSTCRAWFORD_PARAMS:
parser->state = STATE_POSTCRAWFORD;
break;
case STATE_POSTCRAWFORD_ME:
parser->state = STATE_POSTCRAWFORD_ROW;
parser->i++;
break;
case STATE_POSTCRAWFORD_PARAM:
parser->state = STATE_POSTCRAWFORD_PARAMS;
if (parser->player == MP_PLAYER_BOTH) {
ListInsert(&parser->data->ampPostCrawford[0].lParameters, parser->current_param);
} else {
ListInsert(&parser->data->ampPostCrawford[parser->player].lParameters, parser->current_param);
}
parser->current_param = NULL;
break;
default:
g_assert_not_reached();
}
}
static void
met_parser_characters(GMarkupParseContext * UNUSED(context),
const gchar * text, gsize UNUSED(text_len), gpointer user_data, GError ** UNUSED(error))
{
MatchEquityParser *parser = (MatchEquityParser *) user_data;
switch (parser->state) {
case STATE_NAME:
parser->data->mi.szName = g_strdup(text);
break;
case STATE_DESCRIPTION:
parser->data->mi.szDescription = g_strdup(text);
break;
case STATE_LENGTH:
/* bad bug in glib-2.12.x - see gnome bug 416062 */
parser->data->mi.nLength = (gint) g_ascii_strtoull(text, NULL, 10);
break;
case STATE_PRECRAWFORD_ME:
parser->data->aarMET[parser->j][parser->i] = (float) g_ascii_strtod(text, NULL);
break;
case STATE_POSTCRAWFORD_ME:
if (parser->player == MP_PLAYER_BOTH) {
gfloat r = (gfloat) g_ascii_strtod(text, NULL);
parser->data->aarMETPostCrawford[0][parser->i] = r;
parser->data->aarMETPostCrawford[1][parser->i] = r;
} else {
gfloat r = (gfloat) g_ascii_strtod(text, NULL);
parser->data->aarMETPostCrawford[parser->player][parser->i] = r;
}
break;
case STATE_PRECRAWFORD_PARAM:
case STATE_POSTCRAWFORD_PARAM:
g_assert(parser->current_param);
parser->current_param->value = (float) g_ascii_strtod(text, NULL);
default:
break;
}
}
static void
met_parser_error(GMarkupParseContext * UNUSED(context), GError * UNUSED(error), gpointer user_data)
{
MatchEquityParser *parser = (MatchEquityParser *) user_data;
g_warning(_("An error occurred while parsing file: %s\n"), parser->filename);
}
/*
* Calculate gammon and backgammon price at the specified score
* with specified cube.
*
* Input:
* aafMET, aafMETPostCrawford: match equity tables.
* nCube: value of cube
* fCrawford: is this the Crawford game
* nScore0, nScore1, nMatchTo: current score and match length.
*
* Output:
* arGammonPrice: gammon and backgammon prices.
*
*/
static void
getGammonPrice(float arGammonPrice[4],
const int nScore0, const int nScore1, const int nMatchTo,
const int nCube, const int fCrawford,
float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE])
{
const float epsilon = 1.0E-7f;
float rWin = getME(nScore0, nScore1, nMatchTo,
0, nCube, 0, fCrawford,
aafMET, aafMETPostCrawford);
float rWinGammon = getME(nScore0, nScore1, nMatchTo,
0, 2 * nCube, 0, fCrawford,
aafMET, aafMETPostCrawford);
float rWinBG = getME(nScore0, nScore1, nMatchTo,
0, 3 * nCube, 0, fCrawford,
aafMET, aafMETPostCrawford);
float rLose = getME(nScore0, nScore1, nMatchTo,
0, nCube, 1, fCrawford,
aafMET, aafMETPostCrawford);
float rLoseGammon = getME(nScore0, nScore1, nMatchTo,
0, 2 * nCube, 1, fCrawford,
aafMET, aafMETPostCrawford);
float rLoseBG = getME(nScore0, nScore1, nMatchTo,
0, 3 * nCube, 1, fCrawford,
aafMET, aafMETPostCrawford);
float rCenter = (rWin + rLose) / 2.0f;
/* FIXME: correct numerical problems in a better way, than done
* below. If cube is dead gammon or backgammon price might be a
* small negative number. For example, at -2,-3 with cube on 2
* the current code gives: 0.9090..., 0, -2.7e-8, 0 instead
* of the correct 0.9090..., 0, 0, 0. */
/* avoid division by zero */
if (fabsf(rWin - rCenter) > epsilon) {
/* this expression can be reduced to:
* 2 * ( rWinGammon - rWin ) / ( rWin - rLose )
* which is twice the "usual" gammon value */
arGammonPrice[0] = (rWinGammon - rCenter) / (rWin - rCenter) - 1.0f;
/* this expression can be reduced to:
* 2 * ( rLose - rLoseGammon ) / ( rWin - rLose )
* which is twice the "usual" gammon value */
arGammonPrice[1] = (rCenter - rLoseGammon) / (rWin - rCenter) - 1.0f;
arGammonPrice[2] = (rWinBG - rCenter) / (rWin - rCenter) - (arGammonPrice[0] + 1.0f);
arGammonPrice[3] = (rCenter - rLoseBG) / (rWin - rCenter) - (arGammonPrice[1] + 1.0f);
} else
arGammonPrice[0] = arGammonPrice[1] = arGammonPrice[2] = arGammonPrice[3] = 0.0f;
/* Correct numerical problems */
if (arGammonPrice[0] < 0.0f)
arGammonPrice[0] = 0.0f;
if (arGammonPrice[1] < 0.0f)
arGammonPrice[1] = 0.0f;
if (arGammonPrice[2] < 0.0f)
arGammonPrice[2] = 0.0f;
if (arGammonPrice[3] < 0.0f)
arGammonPrice[3] = 0.0f;
}
/*
* Calculate all gammon and backgammon prices
*
* Input:
* aafMET, aafMETPostCrawford: match equity tables
*
* Output:
* aaaafGammonPrices: all gammon prices
*
*/
static void
calcGammonPrices(float aafMET[MAXSCORE][MAXSCORE],
float aafMETPostCrawford[2][MAXSCORE], float aaaafGammonPrices[MAXCUBELEVEL]
[MAXSCORE][MAXSCORE][4], float aaaafGammonPricesPostCrawford[MAXCUBELEVEL]
[MAXSCORE][2][4])
{
int i, j, k;
int nCube;
for (i = 0, nCube = 1; i < MAXCUBELEVEL; i++, nCube *= 2)
for (j = 0; j < MAXSCORE; j++)
for (k = 0; k < MAXSCORE; k++)
getGammonPrice(aaaafGammonPrices[i][j][k],
MAXSCORE - j - 1, MAXSCORE - k - 1, MAXSCORE,
nCube, (MAXSCORE == j) || (MAXSCORE == k), aafMET, aafMETPostCrawford);
for (i = 0, nCube = 1; i < MAXCUBELEVEL; i++, nCube *= 2)
for (j = 0; j < MAXSCORE; j++) {
getGammonPrice(aaaafGammonPricesPostCrawford[i][j][0],
MAXSCORE - 1, MAXSCORE - j - 1, MAXSCORE, nCube, FALSE, aafMET, aafMETPostCrawford);
getGammonPrice(aaaafGammonPricesPostCrawford[i][j][1],
MAXSCORE - j - 1, MAXSCORE - 1, MAXSCORE, nCube, FALSE, aafMET, aafMETPostCrawford);
}
}
extern void
InitMatchEquity(const char *szFileName)
{
int i, j;
metdata md;
/* Read match equity table from XML file */
if (readMET(&md, szFileName) != 0) { /* load failed - make default as must have a met */
getDefaultMET(&md);
}
/* Copy met to current met, extend met (if needed) */
/* post-Crawford table */
for (j = 0; j < 2; j++) {
if (!strcmp((char *) md.ampPostCrawford[j].szName, "explicit")) {
/* copy and extend table */
/* FIXME: implement better extension of post-Crawford table */
/* Note that the post Crawford table is extended from
* n - 1 as the post Crawford table of a n match equity table
* might not include the post Crawford equity at n-away, since
* the first "legal" post Crawford score is n-1. */
for (i = 0; i < md.mi.nLength - 1; i++)
aafMETPostCrawford[j][i] = md.aarMETPostCrawford[j][i];
initPostCrawfordMET(aafMETPostCrawford[j], md.mi.nLength - 1, GAMMONRATE, 0.015f, 0.004f);
} else {
/* generate match equity table using Zadeh's formula */
if (initPostCrawfordMETFromParameters(aafMETPostCrawford[j], &md.ampPostCrawford[j]) < 0) {
fprintf(stderr, _("Error generating post-Crawford MET\n"));
return;
}
}
}
/* pre-Crawford table */
if (!strcmp((char *) md.mpPreCrawford.szName, "explicit")) { /* copy table */
for (i = 0; i < md.mi.nLength; i++)
for (j = 0; j < md.mi.nLength; j++)
aafMET[i][j] = md.aarMET[i][j];
} else {
/* generate match equity table using Zadeh's formula */
if (initMETFromParameters(aafMET, aafMETPostCrawford, &md.mpPreCrawford) < 0) {
fprintf(stderr, _("Error generating pre-Crawford MET\n"));
return;
}
}
/* Extend match equity table */
ExtendMET(aafMET, md.mi.nLength);
/* garbage collect */
freeMP(&md.mpPreCrawford);
for (i = 0; i < 2; i++)
freeMP(&md.ampPostCrawford[i]);
g_free(miCurrent.szName);
g_free(miCurrent.szFileName);
g_free(miCurrent.szDescription);
/* save match equity table information */
memcpy(&miCurrent, &md.mi, sizeof(metinfo));
/* initialise gammon prices */
calcGammonPrices(aafMET, aafMETPostCrawford, aaaafGammonPrices, aaaafGammonPricesPostCrawford);
}
/*
* Return match equity (mwc) assuming player fWhoWins wins nPoints points.
*
* If fCrawford then afMETPostCrawford is used, otherwise
* aafMET is used.
*
* Input:
* nAway0: points player 0 needs to win
* nAway1: points player 1 needs to win
* fPlayer: get mwc for this player
* fCrawford: is this the Crawford game
* aafMET: match equity table for player 0
* afMETPostCrawford: post-Crawford match equity table for player 0
*
*/
extern float
getME(const int nScore0, const int nScore1, const int nMatchTo,
const int fPlayer,
const int nPoints, const int fWhoWins,
const int fCrawford, float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE])
{
int n0 = nMatchTo - (nScore0 + (!fWhoWins) * nPoints) - 1;
int n1 = nMatchTo - (nScore1 + fWhoWins * nPoints) - 1;
/* check if any player has won the match */
if (unlikely(n0 < 0))
/* player 0 has won the game */
return (fPlayer) ? 0.0f : 1.0f;
else if (unlikely(n1 < 0))
/* player 1 has won the game */
return (fPlayer) ? 1.0f : 0.0f;
/* the match is not finished */
if (fCrawford || (nMatchTo - nScore0 == 1) || (nMatchTo - nScore1 == 1)) {
/* the next game will be post-Crawford */
if (!n0)
/* player 0 is leading match */
/* FIXME: use pc-MET for player 0 */
return (fPlayer) ? aafMETPostCrawford[1][n1] : 1.0f - aafMETPostCrawford[1][n1];
else
/* player 1 is leading the match */
return (fPlayer) ? 1.0f - aafMETPostCrawford[0][n0] : aafMETPostCrawford[0][n0];
} else
/* non-post-Crawford games */
return (fPlayer) ? 1.0f - aafMET[n0][n1] : aafMET[n0][n1];
}
extern float
getMEAtScore(const int nScore0, const int nScore1, const int nMatchTo,
const int fPlayer, const int fCrawford,
float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE])
{
int n0 = nMatchTo - nScore0 - 1;
int n1 = nMatchTo - nScore1 - 1;
/* check if any player has won the match */
if (n0 < 0)
/* player 0 has won the game */
return (fPlayer) ? 0.0f : 1.0f;
else if (n1 < 0)
/* player 1 has won the game */
return (fPlayer) ? 1.0f : 0.0f;
/* the match is not finished */
if (!fCrawford && ((nMatchTo - nScore0 == 1) || (nMatchTo - nScore1 == 1))) {
/* this game is post-Crawford */
if (!n0)
/* player 0 is leading match */
/* FIXME: use pc-MET for player 0 */
return (fPlayer) ? aafMETPostCrawford[1][n1] : 1.0f - aafMETPostCrawford[1][n1];
else
/* player 1 is leading the match */
return (fPlayer) ? 1.0f - aafMETPostCrawford[0][n0] : aafMETPostCrawford[0][n0];
} else
/* non-post-Crawford games */
return (fPlayer) ? 1.0f - aafMET[n0][n1] : aafMET[n0][n1];
}
extern void
invertMET(void)
{
int i, j;
for (i = 0; i < MAXSCORE; i++) {
/* post crawford entries */
float r = aafMETPostCrawford[0][i];
aafMETPostCrawford[0][i] = aafMETPostCrawford[1][i];
aafMETPostCrawford[1][i] = r;
/* diagonal */
aafMET[i][i] = 1.0f - aafMET[i][i];
/* off diagonal entries */
for (j = 0; j < i; j++) {
r = aafMET[i][j];
aafMET[i][j] = 1.0f - aafMET[j][i];
aafMET[j][i] = 1.0f - r;
}
}
calcGammonPrices(aafMET, aafMETPostCrawford, aaaafGammonPrices, aaaafGammonPricesPostCrawford);
}
/* given a match score, return a pair of arrays with the METs for
* player0 and player 1 winning/losing including gammons & backgammons
*
* if nCubePrime0 < 0, then we're only interested in the first
* values in each array, using nCube.
*
* Otherwise, if nCubePrime0 >= 0, we do another set of METs with
* both sides using nCubePrime0
*
* if nCubePrime1 >= 0, we do a third set using nCubePrime1
*
* FIXME ? It looks like if nCubePrime0 >= 0, nCubePrime1 is as well
* That could simplify the code below a little
*
* This reduces the *huge* number of calls to get equity table entries
* when analyzing matches by something like 40 times
*/
extern void
getMEMultiple(const int nScore0, const int nScore1, const int nMatchTo,
const int nCube, const int nCubePrime0, const int nCubePrime1,
const int fCrawford, float aafMET[MAXSCORE][MAXSCORE],
float aafMETPostCrawford[2][MAXSCORE], float *player0, float *player1)
{
int scores[2][DTLBP1 + 1]; /* the resulting match scores */
int i, max_res;
int *score0, *score1;
int mult[] = { 1, 2, 3, 4, 6 };
float *p0, *p1, f;
int away0, away1;
int fCrawf = fCrawford;
/* figure out how many results we'll be returning */
max_res = (nCubePrime0 < 0) ? DTLB + 1 : (nCubePrime1 < 0) ? DTLBP0 + 1 : DTLBP1 + 1;
/* set up a table of resulting match scores for all
* the results we're calculating */
score0 = scores[0];
score1 = scores[1];
away0 = nMatchTo - nScore0 - 1;
away1 = nMatchTo - nScore1 - 1;
fCrawf |= (nMatchTo - nScore0 == 1) || (nMatchTo - nScore1 == 1);
/* player 0 wins normal, doubled, gammon, backgammon */
for (i = 0; i < NDL; ++i) {
*score0++ = away0 - mult[i] * nCube;
*score1++ = away1;
}
/* player 1 wins normal, doubled, etc. */
for (i = 0; i < NDL; ++i) {
*score0++ = away0;
*score1++ = away1 - mult[i] * nCube;
}
if (max_res > DPP0) {
/* same using the second cube value */
for (i = 0; i < NDL; ++i) {
*score0++ = away0 - mult[i] * nCubePrime0;
*score1++ = away1;
}
for (i = 0; i < NDL; ++i) {
*score0++ = away0;
*score1++ = away1 - mult[i] * nCubePrime0;
}
if (max_res > DPP1) {
/* same using the third cube value */
for (i = 0; i < NDL; ++i) {
*score0++ = away0 - mult[i] * nCubePrime1;
*score1++ = away1;
}
for (i = 0; i < NDL; ++i) {
*score0++ = away0;
*score1++ = away1 - mult[i] * nCubePrime1;
}
}
}
score0 = scores[0];
score1 = scores[1];
p0 = player0;
p1 = player1;
/* now go through the resulting scores, looking up the equities */
for (i = 0; i < max_res; ++i) {
int s0 = *score0++;
int s1 = *score1++;
if (unlikely(s0 < 0)) {
/* player 0 wins */
*p0++ = 1.0f;
*p1++ = 0.0f;
} else if (unlikely(s1 < 0)) {
*p0++ = 0.0f;
*p1++ = 1.0f;
} else if (unlikely(fCrawf)) {
if (s0 == 0) { /* player 0 is leading */
*p0++ = 1.0f - aafMETPostCrawford[1][s1];
*p1++ = aafMETPostCrawford[1][s1];
} else {
*p0++ = aafMETPostCrawford[0][s0];
*p1++ = 1.0f - aafMETPostCrawford[0][s0];
}
} else { /* non-post-Crawford */
*p0++ = aafMET[s0][s1];
*p1++ = 1.0f - aafMET[s0][s1];
}
}
/* results for player 0 are done, results for player 1 have the
* losses in cols 0-4 and 8-12, but we want them to be in the same
* order as results0 - e.g wins in cols 0-4, and 8-12
*/
p0 = player1;
p1 = player1 + NDL;
for (i = 0; i < NDL; ++i) {
f = *p0;
*p0++ = *p1;
*p1++ = f;
}
if (max_res > DTLBP0) {
p0 += NDL;
p1 += NDL;
for (i = 0; i < NDL; ++i) {
f = *p0;
*p0++ = *p1;
*p1++ = f;
}
}
if (max_res > DTLBP1) {
p0 += NDL;
p1 += NDL;
for (i = 0; i < NDL; ++i) {
f = *p0;
*p0++ = *p1;
*p1++ = f;
}
}
}