/* * Copyright (C) 1999-2004 Joern Thyssen * 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 . */ #include "config.h" #include #include #include #include #include #include #include #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; } } }