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

507 lines
15 KiB
C

/*
* mec - match equity calculator for backgammon. calculate equity table
* given match length, gammon rate and winning probabilities.
*
* Copyright (C) 1996 Claes Tornberg <claest@it.kth.se>
* Copyright (C) 2004-2013 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/>.
*
*
* Modified for usage with GNUbg by Joern Thyssen <jth@gnubg.org>:
*
* (1) make external entry for usage in GNUbg
* (2) change "double" to "metentry", and typedef metentry to float
* or double depending on MEC_STANDALONE
* (3) Compile with MEC_STANDALONE to get Tornberg's original program, e.g.,
*
* gcc -DMEC_STANDALONE mec.c -o mec
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#ifndef MEC_STANDALONE
#include "mec.h"
#endif
struct dp {
double e;
double w;
};
typedef struct dp dp;
static void post_crawford(double gr, double wpf, int ml, double **E, double fd2, double fd4);
static void crawford(double gr, double wpf, int ml, double **E);
static void pre_crawford(double gr, double wpf, int ml, double **E);
static dp dpt(int p, int o, int c, double gr, double wpp, double **E);
/*
* Arguments are (in this order):
* match length
* gammon rate
* winning percentage (favorite)
*/
#ifdef MEC_STANDALONE
int
main(int argc, char **argv)
{
/* match length */
int ml = argc > 1 ? atoi(argv[1]) : 9;
/* gammon rate, i.e. how many of games won/lost will be gammons */
double gr = argc > 2 ? atof(argv[2]) : 0;
/* Here one could argue for different approaches. Does the underdog
* in match (current score) have different gammon rate. Or does the
* underdog in match (equity at current score) have different gammon
* rate. This could be solved, but for now, we assume same rates. */
/* winning percentage for favorite in one game. NB: when one player
* is favorite, there is no symmetry in the equity table! I.e.
* E[i][1] = 1 - E[1][i] doesn't necessarily hold. This figure must
* be higher than or equal to .5, i.e. 50% */
double wpf = argc > 3 ? atof(argv[3]) : 0.5;
/* Equity chart, E[p][o] = equity for p when p is p away, and o is o away.
* If there is a game favorite, i.e. wpf > 0.5, p above is considered
* to be the favorite in each game. For now we also assume no free drop
* vigourish. This will only affect the computation of post-Crawford
* equities, and is quite easy to correct. */
double **E = (double **) malloc((ml + 1) * sizeof(double *));
double *ec = (double *) calloc((ml + 1) * (ml + 1), sizeof(double));
if (E == NULL || ec == NULL)
exit(EXIT_FAILURE);
{
int i;
/* Initialize E to point to correct positions */
for (i = 0; i <= ml; i++) {
E[i] = &ec[i * (ml + 1)];
}
/* Initialize E for 0-away scores */
for (i = 1; i <= ml; i++) {
E[0][i] = 1;
E[i][0] = 0;
}
}
/* Compute post-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length. Fill in the equity table. */
post_crawford(gr, wpf, ml, E, 0.0, 0.0);
{
int i;
printf("Post-Crawford:\n");
for (i = 1; i < ml; ++i)
printf("%6.3f", E[i][1]);
printf("\n");
for (i = 1; i < ml; ++i)
printf("%6.3f", E[1][i]);
printf("\n");
}
/* Compute Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and post-Crawford equities.
* Fill in the table. */
crawford(gr, wpf, ml, E);
/* Compute pre-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and Crawford equities.
* Fill in the table. */
pre_crawford(gr, wpf, ml, E);
/* Print the equity table. Nothing fancy. */
{
int i, j;
printf("Gammon rate %5.4f\n" "Winning %% %5.4f\n\n", gr, wpf);
printf("%3s", "");
for (i = 1; i <= ml; i++) {
printf("%6d", i);
}
printf("\n");
for (i = 1; i <= ml; i++) {
printf("%3d", i);
for (j = 1; j <= ml; j++)
printf("%6.3f", E[i][j]);
printf("\n");
}
}
free(ec);
free(E);
}
#else /* MEC_STANDALONE */
extern void
mec_pc(const float rGammonRate,
const float rFreeDrop2Away, const float rFreeDrop4Away, const float rWinRate, float arMetPC[MAXSCORE])
{
unsigned int i;
/* match length */
const unsigned int ml = 64;
/* gammon rate, i.e. how many of games won/lost will be gammons */
double gr = rGammonRate;
/* Here one could argue for different approaches. Does the underdog
* in match (current score) have different gammon rate. Or does the
* underdog in match (equity at current score) have different gammon
* rate. This could be solved, but for now, we assume same rates. */
/* winning percentage for favorite in one game. NB: when one player
* is favorite, there is no symmetry in the equity table! I.e.
* E[i][1] = 1 - E[1][i] doesn't necessarily hold. This figure must
* be higher than or equal to .5, i.e. 50% */
double wpf = rWinRate;
/* Equity chart, E[p][o] = equity for p when p is p away, and o is o away.
* If there is a game favorite, i.e. wpf > 0.5, p above is considered
* to be the favorite in each game. For now we also assume no free drop
* vigourish. This will only affect the computation of post-Crawford
* equities, and is quite easy to correct. */
double **E = (double **) malloc((ml + 1) * sizeof(double *));
double *ec = (double *) calloc((ml + 1) * (ml + 1), sizeof(double));
if (!E || !ec)
exit(-1); /* We're in trouble... */
{
/* Initialize E to point to correct positions */
for (i = 0; i <= ml; i++) {
E[i] = &ec[i * (ml + 1)];
}
/* Initialize E for 0-away scores */
for (i = 1; i <= ml; i++) {
E[0][i] = 1;
E[i][0] = 0;
}
}
/* Compute post-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length. Fill in the equity table. */
post_crawford(gr, wpf, ml, E, (double) rFreeDrop2Away, (double) rFreeDrop4Away);
/* save post Crawford equities */
for (i = 0; i < ml; ++i)
arMetPC[i] = (float) E[i + 1][1];
/* garbage collect */
free(ec);
free(E);
}
extern void
mec(const float rGammonRate, const float rWinRate,
/* const *//*lint -e{818} */ float aarMetPC[2][MAXSCORE],
float aarMet[MAXSCORE][MAXSCORE])
{
unsigned int i, j;
/* match length */
const unsigned int ml = 64;
/* gammon rate, i.e. how many of games won/lost will be gammons */
double gr = rGammonRate;
/* Here one could argue for different approaches. Does the underdog
* in match (current score) have different gammon rate. Or does the
* underdog in match (equity at current score) have different gammon
* rate. This could be solved, but for now, we assume same rates. */
/* winning percentage for favorite in one game. NB: when one player
* is favorite, there is no symmetry in the equity table! I.e.
* E[i][1] = 1 - E[1][i] doesn't necessarily hold. This figure must
* be higher than or equal to .5, i.e. 50% */
double wpf = rWinRate;
/* Equity chart, E[p][o] = equity for p when p is p away, and o is o away.
* If there is a game favorite, i.e. wpf > 0.5, p above is considered
* to be the favorite in each game. For now we also assume no free drop
* vigourish. This will only affect the computation of post-Crawford
* equities, and is quite easy to correct. */
double **E = (double **) malloc((ml + 1) * sizeof(double *));
double *ec = (double *) calloc((ml + 1) * (ml + 1), sizeof(double));
if (!E || !ec)
exit(-1); /* We're in trouble... */
{
/* Initialize E to point to correct positions */
for (i = 0; i <= ml; i++) {
E[i] = &ec[i * (ml + 1)];
}
/* Initialize E for 0-away scores */
for (i = 1; i <= ml; i++) {
E[0][i] = 1;
E[i][0] = 0;
}
}
/* Compute post-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length. Fill in the equity table. */
for (i = 0; i < ml; ++i)
E[i + 1][1] = aarMetPC[0][i];
for (i = 0; i < ml; ++i)
E[1][i + 1] = 1.0 - (double)aarMetPC[1][i];
/* Compute Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and post-Crawford equities.
* Fill in the table. */
crawford(gr, wpf, ml, E);
/* Compute pre-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and Crawford equities.
* Fill in the table. */
pre_crawford(gr, wpf, ml, E);
/* save the match equiy table */
for (i = 0; i < ml; ++i)
for (j = 0; j < ml; ++j)
aarMet[i][j] = (float) E[i + 1][j + 1];
/* garbage collect */
free(ec);
free(E);
}
#endif /* ! MEC_STANDALONE */
/* If last bit is zero, then x is even. */
#define even(x) (((x)&0x1)==0)
/* sq returns x if x is greater than zero, else it returns zero. */
#define sq(x) ((x)>0?(x):0)
static void
post_crawford(double gr, double wpf, int ml, /*lint -e{818} */ double **E,
double fd2, double fd4)
{
int i;
E[1][1] = wpf;
for (i = 2; i <= ml; i++) {
if (even(i)) {
/* Free drop condition exists */
E[1][i] = E[1][i - 1];
E[i][1] = E[i - 1][1];
/* jth: add empirical values for free drop */
if (i == 2) {
/* 2-away */
E[1][i] += fd2;
E[i][1] -= fd2;
} else if (i == 4) {
/* 4-away */
E[1][i] += fd4;
E[i][1] += fd4;
}
} else {
E[1][i] = /* Equity for favorite when 1-away, i-away */
E[0][i] * wpf /* Favorite wins */
+ E[1][sq(i - 2)] * (1 - wpf) * (1 - gr) /* Favorite loses single */
+E[1][sq(i - 4)] * (1 - wpf) * gr; /* Favorite loses gammon */
E[i][1] = /* Equity for favorite when i-away, 1-away */
E[i][0] * (1 - wpf) /* Favorite loses */
+E[sq(i - 2)][1] * wpf * (1 - gr) /* Favorite wins single */
+E[sq(i - 4)][1] * wpf * gr; /* Favorite wins gammon */
}
}
}
static void
crawford(double gr, double wpf, int ml, /*lint -e{818} */ double **E)
{
int i;
/* Compute Crawford equities. Do this backwards, since
* we overwrite post-Crawford equities with Crawford equities.
* In this way we only overwrite equities no longer needed. */
for (i = ml; i >= 2; i--) {
E[1][i] = /* Equity for favorite when 1-away,i-away */
E[0][i] * wpf /* Favorite wins */
+ E[1][i - 1] * (1 - wpf) * (1 - gr) /* Favorite loses single */
+E[1][i - 2] * (1 - wpf) * gr; /* Favorite loses gammon */
E[i][1] = /* Equity for favorite when i-away, 1-away */
E[i][0] * (1 - wpf) /* Favorite loses */
+E[i - 1][1] * wpf * (1 - gr) /* Favorite wins single */
+E[i - 2][1] * wpf * gr; /* Favorite wins gammon */
}
}
static void
pre_crawford(double gr, double wpf, int ml, double **E)
{
int i, j;
dp dpf, dpu;
double eq;
for (i = 2; i <= ml; i++)
for (j = i; j <= ml; j++) {
dpf = dpt(i, j, 2, gr, wpf, E);
dpu = dpt(j, i, 2, gr, 1 - wpf, E);
dpu.e = 1 - dpu.e;
dpu.w = 1 - dpu.w;
eq = dpu.e + (dpf.e - dpu.e) * (wpf - dpu.w) / (dpf.w - dpu.w);
E[i][j] = eq;
if (i != j) {
dpf = dpt(j, i, 2, gr, wpf, E);
dpu = dpt(i, j, 2, gr, 1 - wpf, E);
dpu.e = 1 - dpu.e;
dpu.w = 1 - dpu.w;
eq = dpu.e + (dpf.e - dpu.e) * (wpf - dpu.w) / (dpf.w - dpu.w);
E[j][i] = eq;
}
}
}
/* Compute point when p doubles o to c, assuming gammon rate gr,
* p wins wpp % of games, and equities E for favorite. At this
* point o does equally well passing the double as taking it. */
static dp
dpt(int p, int o, int c, double gr, double wpp, double **E)
{
dp dpo, dpp;
double e0, edp, wdp;
if (p <= c / 2) {
/* No reason for p to double o, since a single win
* is enough to win the match. */
dpp.e = 1;
dpp.w = 1;
return dpp;
}
/* p might double o to c since he needs more than a single
* game to win the match. */
/* Find out when o (re)doubles p to 2*c */
dpo = dpt(o, p, 2 * c, gr, 1 - wpp, E);
/* Find out equity for o if p does well and wins
* all games here (assuming no recube from o), i.e.
* o loses all games sitting on a c-cube. */
if (wpp > 0.5) {
/* o isn't game favorite, equity for o at o-away x-away is 1 - E[x][o]. */
e0 = (1 - E[sq(p - c)][o]) * (1 - gr)
+ (1 - E[sq(p - 2 * c)][o]) * gr;
} else {
/* o is game favorite, equity for o at o-away x-away is E[o][x]. */
e0 = E[o][sq(p - c)] * (1 - gr)
+ E[o][sq(p - 2 * c)] * gr;
}
/* Find out o:s equity if o passes the double to c, i.e. loses c / 2 points. */
if (wpp > 0.5) {
/* o isn't game favorite, equity for o at o-away x-away is 1 - E[x][o]. */
edp = 1 - E[sq(p - c / 2)][o];
} else {
/* o is game favorite, equity for o at o-away x-away is E[o][x]. */
edp = E[o][sq(p - c / 2)];
}
/* Find the winning percentage, which on the line from
* (w0?,e0) to (dpo.w,dpo.e) gives o an equity equal to O's
* equity passing the double to c (i.e. losing c / 2 pts) */
wdp = (edp - e0) * dpo.w / (dpo.e - e0);
/* Now we know when p should double o, expressed as
* winning percentage and equity for o. Return this
* expressed in figures for p */
dpp.e = 1 - edp;
dpp.w = 1 - wdp;
return dpp;
}