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

1230 lines
34 KiB
C

/*
* Copyright (C) 2002-2004 Joern Thyssen <jthyssen@dk.ibm.com>
* Copyright (C) 2003-2019 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"
/*must be first here because of strange warning from mingw */
#include "multithread.h"
#include "backgammon.h"
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "bearoffgammon.h"
#include "positionid.h"
#include <glib/gstdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define HEURISTIC_C 15
#define HEURISTIC_P 6
static int
setGammonProb(const TanBoard anBoard, unsigned int bp0, unsigned int bp1, float *g0, float *g1)
{
int i;
unsigned short int prob[32];
unsigned int tot0 = 0;
unsigned int tot1 = 0;
for (i = 5; i >= 0; --i) {
tot0 += anBoard[0][i];
tot1 += anBoard[1][i];
}
{
g_assert(tot0 == 15 || tot1 == 15);
}
*g0 = 0.0;
*g1 = 0.0;
if (tot0 == 15) {
struct GammonProbs *gp = getBearoffGammonProbs(anBoard[0]);
double make[3];
if (BearoffDist(pbc1, bp1, NULL, NULL, NULL, prob, NULL))
return -1;
make[0] = gp->p0 / 36.0;
make[1] = (make[0] + gp->p1 / (36.0 * 36.0));
make[2] = (make[1] + gp->p2 / (36.0 * 36.0 * 36.0));
*g1 = (float) ((prob[1] / 65535.0) +
(1 - make[0]) * (prob[2] / 65535.0) +
(1 - make[1]) * (prob[3] / 65535.0) + (1 - make[2]) * (prob[4] / 65535.0));
}
if (tot1 == 15) {
struct GammonProbs *gp = getBearoffGammonProbs(anBoard[1]);
double make[3];
if (BearoffDist(pbc1, bp0, NULL, NULL, NULL, prob, NULL))
return -1;
make[0] = gp->p0 / 36.0;
make[1] = (make[0] + gp->p1 / (36.0 * 36.0));
make[2] = (make[1] + gp->p2 / (36.0 * 36.0 * 36.0));
*g0 = (float) ((prob[1] / 65535.0) * (1 - make[0]) + (prob[2] / 65535.0) * (1 - make[1])
+ (prob[3] / 65535.0) * (1 - make[2]));
}
return 0;
}
static void
AverageRolls(const float arProb[32], float *ar)
{
float sx;
float sx2;
int i;
sx = sx2 = 0.0f;
for (i = 1; i < 32; i++) {
float p = (float) i * arProb[i];
sx += p;
sx2 += (float) i * p;
}
ar[0] = sx;
ar[1] = sqrtf(sx2 - sx * sx);
}
/* Make a plausible bearoff move (used to create approximate bearoff database). */
static unsigned int
HeuristicBearoff(unsigned int anBoard[6], const unsigned int anRoll[2])
{
unsigned int i, /* current die being played */
c, /* number of dice to play */
nMax, /* highest occupied point */
anDice[4], j, iSearch, nTotal;
int n; /* point to play from */
if (anRoll[0] == anRoll[1]) {
/* doubles */
anDice[0] = anDice[1] = anDice[2] = anDice[3] = anRoll[0];
c = 4;
} else {
/* non-doubles */
g_assert(anRoll[0] > anRoll[1]);
anDice[0] = anRoll[0];
anDice[1] = anRoll[1];
c = 2;
}
for (i = 0; i < c; i++) {
for (nMax = 5; nMax > 0; nMax--) {
if (anBoard[nMax])
break;
}
if (!anBoard[nMax])
/* finished bearoff */
break;
do {
if (anBoard[anDice[i] - 1]) {
/* bear off exactly */
n = (int) anDice[i] - 1;
break;
}
if (anDice[i] - 1 > nMax) {
/* bear off highest chequer */
n = (int) nMax;
break;
}
nTotal = anDice[i] - 1;
for (n = -1, j = i + 1; j < c; j++) {
nTotal += anDice[j];
if (nTotal < 6 && anBoard[nTotal]) {
/* there's a chequer we can bear off with subsequent dice;
* do it */
n = (int) nTotal;
break;
}
}
if (n >= 0)
break;
for (n = -1, iSearch = anDice[i]; iSearch <= nMax; iSearch++) {
if (anBoard[iSearch] >= 2 && /* at least 2 on source point */
!anBoard[iSearch - anDice[i]] && /* dest empty */
(n == -1 || anBoard[iSearch] > anBoard[n]))
n = (int) iSearch;
}
if (n >= 0)
break;
/* find the point with the most on it (or least on dest) */
for (iSearch = anDice[i]; iSearch <= nMax; iSearch++)
if (n == -1 || anBoard[iSearch] > anBoard[n] ||
(anBoard[iSearch] == anBoard[n] &&
anBoard[iSearch - anDice[i]] < anBoard[(unsigned int) n - anDice[i]]))
n = (int) iSearch;
g_assert(n >= 0);
} while (n < 0); /* Dummy loop to remove goto's */
g_assert(anBoard[n]);
anBoard[n]--;
if (n >= (int) anDice[i])
anBoard[n - (int) anDice[i]]++;
}
return PositionBearoff(anBoard, HEURISTIC_P, HEURISTIC_C);
}
static void
GenerateBearoff(unsigned char *p, unsigned int nId)
{
unsigned int anRoll[2], anBoard[6], aProb[32] = { 0 };
unsigned int i, iBest;
for (anRoll[0] = 1; anRoll[0] <= 6; anRoll[0]++)
for (anRoll[1] = 1; anRoll[1] <= anRoll[0]; anRoll[1]++) {
PositionFromBearoff(anBoard, nId, HEURISTIC_P, HEURISTIC_C);
iBest = HeuristicBearoff(anBoard, anRoll);
g_assert(iBest < nId);
if (anRoll[0] == anRoll[1])
for (i = 0; i < 31; i++)
aProb[i + 1] += p[(iBest << 6) | (i << 1)] + (p[(iBest << 6) | (i << 1) | 1] << 8);
else
for (i = 0; i < 31; i++)
aProb[i + 1] += (p[(iBest << 6) | (i << 1)] +
((unsigned int) p[(iBest << 6) | (i << 1) | 1] << 8)) << 1;
}
for (i = 0; i < 32; i++) {
unsigned short us = (unsigned short) ((aProb[i] + 18) / 36);
p[(nId << 6) | (i << 1)] = us & 0xFF;
p[(nId << 6) | (i << 1) | 1] = us >> 8;
}
}
static unsigned char *
HeuristicDatabase(void (*pfProgress) (unsigned int))
{
unsigned char *pm = malloc(40 + 54264 * 64);
unsigned char *p;
unsigned int i;
if (!pm)
return NULL;
p = pm + 40;
p[0] = p[1] = 0xFF;
for (i = 2; i < 64; i++)
p[i] = 0;
for (i = 1; i < 54264; i++) {
GenerateBearoff(p, i);
if (pfProgress && !(i % 1000))
pfProgress(i);
}
return pm;
}
static void
ReadBearoffFile(const bearoffcontext * pbc, unsigned int offset, unsigned char *buf, unsigned int nBytes)
{
MT_Exclusive();
if ((fseek(pbc->pf, (long) offset, SEEK_SET) != 0) || (fread(buf, 1, nBytes, pbc->pf) < nBytes)) {
if (errno)
perror(_("bearoff database"));
else
fprintf(stderr, _("Error reading bearoff database"));
memset(buf, 0, nBytes);
}
MT_Release();
}
/* BEAROFF_GNUBG: read two sided bearoff database */
static void
ReadTwoSidedBearoff(const bearoffcontext * pbc, const unsigned int iPos, float ar[4], unsigned short int aus[4])
{
unsigned int i, k = (pbc->fCubeful) ? 4 : 1;
unsigned char ac[8];
unsigned char *pc = NULL;
if (pbc->p)
pc = pbc->p + 40 + 2 * iPos * k;
else {
ReadBearoffFile(pbc, 40 + 2 * iPos * k, ac, k * 2);
pc = ac;
}
/* add to cache */
for (i = 0; i < k; ++i) {
unsigned short int us = pc[2 * i] | (unsigned short) (pc[2 * i + 1] << 8);
if (aus)
aus[i] = us;
if (ar)
ar[i] = us / 32767.5f - 1.0f;
}
}
extern int
BearoffCubeful(const bearoffcontext * pbc, const unsigned int iPos, float ar[4], unsigned short int aus[4])
{
g_return_val_if_fail(pbc, -1);
g_return_val_if_fail(pbc->fCubeful, -1);
ReadTwoSidedBearoff(pbc, iPos, ar, aus);
return 0;
}
static int
BearoffEvalTwoSided(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{
unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
unsigned int iPos = nUs * n + nThem;
float ar[4];
ReadTwoSidedBearoff(pbc, iPos, ar, NULL);
memset(arOutput, 0, 5 * sizeof(float));
arOutput[OUTPUT_WIN] = ar[0] / 2.0f + 0.5f;
return 0;
}
static int
ReadHypergammon(const bearoffcontext * pbc, const unsigned int iPos, float arOutput[NUM_OUTPUTS], float arEquity[4])
{
unsigned char ac[28];
unsigned char *pc = NULL;
unsigned int us;
int i;
const int x = 28;
if (pbc->p)
pc = pbc->p + 40 + x * iPos;
else {
ReadBearoffFile(pbc, 40 + x * iPos, ac, x);
pc = ac;
}
if (arOutput)
for (i = 0; i < NUM_OUTPUTS; ++i) {
us = pc[3 * i] | (pc[3 * i + 1]) << 8 | (pc[3 * i + 2]) << 16;
arOutput[i] = (float) ((double) us / 16777215.0);
}
if (arEquity)
for (i = 0; i < 4; ++i) {
us = pc[15 + 3 * i] | (pc[15 + 3 * i + 1]) << 8 | (pc[15 + 3 * i + 2]) << 16;
arEquity[i] = ((float) ((double) us / 16777215.0) - 0.5f) * 6.0f;
}
return 0;
}
static int
BearoffEvalOneSided(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{
int i, j;
float aarProb[2][32];
float aarGammonProb[2][32];
float r;
unsigned int anOn[2] = { 0 };
unsigned int an[2];
float ar[2][4];
/* get bearoff probabilities */
for (i = 0; i < 2; ++i) {
an[i] = PositionBearoff(anBoard[i], pbc->nPoints, pbc->nChequers);
if (BearoffDist(pbc, an[i], aarProb[i], aarGammonProb[i], ar[i], NULL, NULL))
return -1;
}
/* calculate winning chance */
r = 0.0;
for (i = 0; i < 32; ++i)
for (j = i; j < 32; ++j)
r += aarProb[1][i] * aarProb[0][j];
arOutput[OUTPUT_WIN] = r;
/* calculate gammon chances */
for (i = 0; i < 2; ++i)
for (j = 0; j < 25; ++j)
anOn[i] += anBoard[i][j];
if (anOn[0] == 15 || anOn[1] == 15) {
if (pbc->fGammon) {
/* my gammon chance: I'm out in i rolls and my opponent isn't inside
* home quadrant in less than i rolls */
r = 0;
for (i = 0; i < 32; i++)
for (j = i; j < 32; j++)
r += aarProb[1][i] * aarGammonProb[0][j];
arOutput[OUTPUT_WINGAMMON] = r;
/* opp gammon chance */
r = 0;
for (i = 0; i < 32; i++)
for (j = i + 1; j < 32; j++)
r += aarProb[0][i] * aarGammonProb[1][j];
arOutput[OUTPUT_LOSEGAMMON] = r;
} else {
if (setGammonProb(anBoard, an[0], an[1], &arOutput[OUTPUT_LOSEGAMMON], &arOutput[OUTPUT_WINGAMMON]))
return -1;
}
} else {
/* no gammons possible */
arOutput[OUTPUT_WINGAMMON] = 0.0f;
arOutput[OUTPUT_LOSEGAMMON] = 0.0f;
}
/* no backgammons possible */
arOutput[OUTPUT_LOSEBACKGAMMON] = 0.0f;
arOutput[OUTPUT_WINBACKGAMMON] = 0.0f;
return 0;
}
extern int
BearoffHyper(const bearoffcontext * pbc, const unsigned int iPos, float arOutput[], float arEquity[])
{
return ReadHypergammon(pbc, iPos, arOutput, arEquity);
}
static int
BearoffEvalHypergammon(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{
unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
unsigned int iPos = nUs * n + nThem;
return ReadHypergammon(pbc, iPos, arOutput, NULL);
}
extern int
BearoffEval(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[])
{
g_return_val_if_fail(pbc, 0);
switch (pbc->bt) {
case BEAROFF_TWOSIDED:
return BearoffEvalTwoSided(pbc, anBoard, arOutput);
case BEAROFF_ONESIDED:
return BearoffEvalOneSided(pbc, anBoard, arOutput);
case BEAROFF_HYPERGAMMON:
return BearoffEvalHypergammon(pbc, anBoard, arOutput);
case BEAROFF_INVALID:
default:
g_warning(_("Invalid bearoff database type"));
g_assert_not_reached();
return 0;
}
}
extern void
BearoffStatus(const bearoffcontext * pbc, char *sz)
{
char buf[256];
if (!pbc || (pbc->bt != BEAROFF_TWOSIDED && pbc->bt != BEAROFF_ONESIDED && pbc->bt != BEAROFF_HYPERGAMMON))
return;
/* Title */
if (pbc->bt == BEAROFF_HYPERGAMMON) {
if (pbc->p)
sprintf(buf, _("In memory 2-sided exact %u-chequer Hypergammon database evaluator"), pbc->nChequers);
else
sprintf(buf, _("On disk 2-sided exact %u-chequer Hypergammon database evaluator"), pbc->nChequers);
} else {
if (pbc->p)
sprintf(buf, _("In memory %u-sided bearoff database evaluator"), pbc->bt);
else
sprintf(buf, _("On disk %u-sided bearoff database evaluator"), pbc->bt);
}
sz += sprintf(sz, " * %s\n", buf);
sz += sprintf(sz, " - %s\n", _("generated by GNU Backgammon"));
sprintf(buf, _("up to %u chequers on %u points (%u positions) per player"), pbc->nChequers, pbc->nPoints,
Combination(pbc->nChequers + pbc->nPoints, pbc->nPoints));
sz += sprintf(sz, " - %s\n", buf);
switch (pbc->bt) {
case BEAROFF_TWOSIDED:
sz += sprintf(sz, " - %s\n", pbc->fCubeful ? _("database includes both cubeful and cubeless equities")
: _("cubeless database"));
break;
case BEAROFF_ONESIDED:
if (pbc->fND)
sz += sprintf(sz, " - %s\n", _("distributions are approximated with a normal distribution"));
if (pbc->fHeuristic)
sz += sprintf(sz, " - %s\n", _("with heuristic moves"));
sz += sprintf(sz, " - %s\n", pbc->fGammon ? _("database includes gammon distributions")
: _("database does not include gammon distributions"));
break;
case BEAROFF_HYPERGAMMON:
case BEAROFF_INVALID:
default:
break;
}
sprintf(sz, "\n");
}
static int
BearoffDumpTwoSided(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
unsigned int iPos = nUs * n + nThem;
float ar[4];
const char *aszEquity[] = {
N_("Cubeless equity"),
N_("Owned cube"),
N_("Centered cube"),
N_("Opponent owns cube")
};
sprintf(sz + strlen(sz), "%19s %14s\n%s %12u %12u\n\n", _("Player"), _("Opponent"), _("Position"), nUs, nThem);
ReadTwoSidedBearoff(pbc, iPos, ar, NULL);
if (pbc->fCubeful)
for (int i = 0; i < 4; ++i)
sprintf(sz + strlen(sz), "%-30.30s: %+7.4f\n", gettext(aszEquity[i]), ar[i]);
else
sprintf(sz + strlen(sz), "%-30.30s: %+7.4f\n", gettext(aszEquity[0]), 2.0f * ar[0] - 1.0f);
strcat(sz, "\n");
return 0;
}
static int
BearoffDumpOneSided(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
float ar[2][4];
unsigned int i;
float aarProb[2][32], aarGammonProb[2][32];
int f0, f1, f2, f3;
unsigned int anPips[2];
const float x = (2 * 3 + 3 * 4 + 4 * 5 + 4 * 6 + 6 * 7 +
5 * 8 + 4 * 9 + 2 * 10 + 2 * 11 + 1 * 12 + 1 * 16 + 1 * 20 + 1 * 24) / 36.0f;
if (BearoffDist(pbc, nUs, aarProb[0], aarGammonProb[0], ar[0], NULL, NULL))
return -1;
if (BearoffDist(pbc, nThem, aarProb[1], aarGammonProb[1], ar[1], NULL, NULL))
return -1;
sz += sprintf(sz, "%19s %14s\n%s %12u %12u\n\n", _("Player"), _("Opponent"), _("Position"), nUs, nThem);
sz += sprintf(sz, "%s \t\t\t\t%s\n", _("Bearing off"), _("Bearing at least one chequer off"));
sz += sprintf(sz, "%s\t%s\t%s\t%s\t%s\n", _("Rolls"), _("Player"), _("Opponent"), _("Player"), _("Opponent"));
f0 = f1 = f2 = f3 = FALSE;
for (i = 0; i < 32; i++) {
if (aarProb[0][i] > 0.0f)
f0 = TRUE;
if (aarProb[1][i] > 0.0f)
f1 = TRUE;
if (aarGammonProb[0][i] > 0.0f)
f2 = TRUE;
if (aarGammonProb[1][i] > 0.0f)
f3 = TRUE;
if (f0 || f1 || f2 || f3) {
if (f0 && f1 && ((f2 && f3) || !pbc->fGammon) &&
aarProb[0][i] == 0.0f && aarProb[1][i] == 0.0f &&
((aarGammonProb[0][i] == 0.0f && aarGammonProb[1][i] == 0.0f) || !pbc->fGammon))
break;
sprintf(sz = sz + strlen(sz),
"%5u\t%7.3f\t%7.3f" "\t\t", i, aarProb[0][i] * 100.0f, aarProb[1][i] * 100.0f);
if (pbc->fGammon)
sprintf(sz = sz + strlen(sz),
"%7.3f\t%7.3f\n", aarGammonProb[0][i] * 100.0f, aarGammonProb[1][i] * 100.0f);
else
sprintf(sz = sz + strlen(sz), "%-7.7s\t%-7.7s\n", _("n/a"), _("n/a"));
}
}
sz += sprintf(sz, "\n%s\n", _("Average rolls"));
sz += sprintf(sz, "%s\t\t\t\t%s\n", _("Bearing off"), _("Saving gammon"));
sz += sprintf(sz, "\t%s\t%s\t%s\t%s\n", _("Player"), _("Opponent"), _("Player"), _("Opponent"));
/* mean rolls */
sz += sprintf(sz, "%s\t%7.3f\t%7.3f\t\t", _("Mean"), ar[0][0], ar[1][0]);
if (pbc->fGammon)
sz += sprintf(sz, "%7.3f\t%7.3f\n", ar[0][2], ar[1][2]);
else
sz += sprintf(sz, "%-7.7s\t%-7.7s\n", _("n/a"), _("n/a"));
/* std. dev */
sz += sprintf(sz, "%s\t%7.3f\t%7.3f\t\t", _("Std dev"), ar[0][1], ar[1][1]);
if (pbc->fGammon)
sz += sprintf(sz, "%7.3f\t%7.3f\n", ar[0][3], ar[1][3]);
else
sz += sprintf(sz, "%-7.7s\t%-7.7s\n", _("n/a"), _("n/a"));
/* effective pip count */
PipCount(anBoard, anPips);
sz += sprintf(sz, "\n%s:\n", _("Effective pip count"));
sz += sprintf(sz, "\t%s\t%s\n", _("Player"), _("Opponent"));
sz += sprintf(sz, "%s\t%7.3f\t%7.3f\n%s\t%7.3f\t%7.3f\n\n", _("EPC"),
ar[0][0] * x, ar[1][0] * x, _("Wastage"), ar[0][0] * x - (float) anPips[1],
ar[1][0] * x - (float) anPips[0]);
sprintf(sz, "%s = %5.3f * %s\n%s = %s - %s\n\n",
_("EPC"), x, _("Average rolls"), _("Wastage"), _("EPC"), _("pips"));
return 0;
}
static int
BearoffDumpHyper(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
unsigned int nUs = PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
unsigned int nThem = PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
unsigned int n = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
unsigned int iPos = nUs * n + nThem;
float ar[4];
unsigned int i;
const char *aszEquity[] = {
N_("Owned cube"),
N_("Centered cube"),
N_("Centered cube (Jacoby rule)"),
N_("Opponent owns cube")
};
if (BearoffHyper(pbc, iPos, NULL, ar))
return -1;
sprintf(sz + strlen(sz), "%19s %14s\n%s %12u %12u\n\n", _("Player"), _("Opponent"), _("Position"), nUs, nThem);
for (i = 0; i < 4; ++i)
sprintf(sz + strlen(sz), "%-30.30s: %+7.4f\n", gettext(aszEquity[i]), ar[i]);
return 0;
}
extern int
BearoffDump(const bearoffcontext * pbc, const TanBoard anBoard, char *sz)
{
g_return_val_if_fail(pbc, -1);
switch (pbc->bt) {
case BEAROFF_TWOSIDED:
return BearoffDumpTwoSided(pbc, anBoard, sz);
case BEAROFF_ONESIDED:
return BearoffDumpOneSided(pbc, anBoard, sz);
case BEAROFF_HYPERGAMMON:
return BearoffDumpHyper(pbc, anBoard, sz);
case BEAROFF_INVALID:
default:
g_warning(_("Invalid bearoff database type"));
return -1;
}
}
extern void
BearoffClose(bearoffcontext * pbc)
{
if (!pbc)
return;
if (pbc->pf)
fclose(pbc->pf);
if (pbc->map) {
g_mapped_file_unref(pbc->map);
pbc->p = NULL;
}
if (pbc->p)
free(pbc->p);
if (pbc->szFilename)
g_free(pbc->szFilename);
g_free(pbc);
}
static unsigned char *
ReadIntoMemory(bearoffcontext * pbc)
{
GError *error = NULL;
pbc->map = g_mapped_file_new(pbc->szFilename, FALSE, &error);
if (!pbc->map) {
g_printerr(_("%s: Failed to map bearoff database %s\n"), pbc->szFilename, error->message);
g_error_free(error);
return NULL;
}
pbc->p = (unsigned char *) g_mapped_file_get_contents(pbc->map);
return pbc->p;
}
/*
* Check whether this is a exact bearoff file
*
* The first long must be 73457356
* The second long must be 100
*
*/
static inline unsigned int
MakeInt(unsigned char a, unsigned char b, unsigned char c, unsigned char d)
{
return ((unsigned int) a |
(unsigned int) b << 8 |
(unsigned int) c << 16 |
(unsigned int) d << 24);
}
static void
InvalidDb(bearoffcontext * pbc)
{
if (errno) {
const char *fn = pbc->szFilename ? pbc->szFilename : "";
g_printerr("%s(%s): %s\n", _("Bearoff Database"), fn, g_strerror(errno));
}
BearoffClose(pbc);
}
/*
* Initialise bearoff database
*
* Input:
* szFilename: the filename of the database to open
*
* Returns:
* pointer to bearoff context on succes; NULL on error
*
* Garbage collect:
* caller must free returned pointer if not NULL.
*
*/
extern bearoffcontext *
BearoffInit(const char *szFilename, const unsigned int bo, void (*p) (unsigned int))
{
bearoffcontext *pbc;
char sz[41];
pbc = g_new0(bearoffcontext, 1);
if (bo & BO_HEURISTIC) {
pbc->bt = BEAROFF_ONESIDED;
pbc->nPoints = HEURISTIC_P;
pbc->nChequers = HEURISTIC_C;
pbc->fHeuristic = TRUE;
pbc->p = HeuristicDatabase(p);
return pbc;
}
errno = 0;
if (!szFilename || !*szFilename) {
g_printerr("%s\n", _("No database filename provided"));
InvalidDb(pbc);
return NULL;
}
pbc->szFilename = g_strdup(szFilename);
if (!g_file_test(szFilename, G_FILE_TEST_IS_REGULAR)) {
/* fail silently */
errno = 0;
InvalidDb(pbc);
return NULL;
}
if ((pbc->pf = g_fopen(szFilename, "rb")) == 0) {
g_printerr("%s\n", _("Invalid or nonexistent database"));
InvalidDb(pbc);
return NULL;
}
/*
* Read header bearoff file
*/
/* read header */
if (fread(sz, 1, 40, pbc->pf) < 40) {
g_printerr("%s\n", _("Database read failed"));
InvalidDb(pbc);
return NULL;
}
/* detect bearoff program */
if (strncmp(sz, "gnubg", 5) != 0) {
g_printerr("%s\n", _("Unknown bearoff database"));
InvalidDb(pbc);
return NULL;
}
/* one sided or two sided? */
if (!strncmp(sz + 6, "TS", 2))
pbc->bt = BEAROFF_TWOSIDED;
else if (!strncmp(sz + 6, "OS", 2))
pbc->bt = BEAROFF_ONESIDED;
else if (*(sz + 6) == 'H')
pbc->bt = BEAROFF_HYPERGAMMON;
else {
g_printerr("%s: %s\n (%s: '%2s')\n", szFilename, _("incomplete bearoff database"), _("illegal bearoff type"),
sz + 6);
InvalidDb(pbc);
return NULL;
}
if (((bo & BO_MUST_BE_ONE_SIDED) && (pbc->bt != BEAROFF_ONESIDED))
|| ((bo & BO_MUST_BE_TWO_SIDED) && (pbc->bt != BEAROFF_TWOSIDED))) {
g_printerr("%s: %s\n (%s: '%2s')\n", szFilename, _("incorrect bearoff database"), _("wrong bearoff type"),
sz + 6);
InvalidDb(pbc);
return NULL;
}
if (pbc->bt == BEAROFF_TWOSIDED || pbc->bt == BEAROFF_ONESIDED) {
/* normal onesided or twosided bearoff database */
/* number of points */
pbc->nPoints = (unsigned) atoi(sz + 9);
if (pbc->nPoints < 1 || pbc->nPoints >= 24) {
g_printerr("%s: %s\n (%s: %u)\n", szFilename, _("incomplete bearoff database"),
_("illegal number of points"), pbc->nPoints);
InvalidDb(pbc);
return NULL;
}
/* number of chequers */
pbc->nChequers = (unsigned) atoi(sz + 12);
if (pbc->nChequers < 1 || pbc->nChequers > 15) {
g_printerr("%s: %s\n (%s: %u)", szFilename, _("incomplete bearoff database"),
_("illegal number of chequers"), pbc->nChequers);
InvalidDb(pbc);
return NULL;
}
} else {
/* hypergammon database */
pbc->nPoints = 25;
pbc->nChequers = (unsigned) atoi(sz + 7);
}
switch (pbc->bt) {
case BEAROFF_TWOSIDED:
/* options for two-sided dbs */
pbc->fCubeful = atoi(sz + 15);
break;
case BEAROFF_ONESIDED:
/* options for one-sided dbs */
pbc->fGammon = atoi(sz + 15);
pbc->fCompressed = atoi(sz + 17);
pbc->fND = atoi(sz + 19);
break;
case BEAROFF_HYPERGAMMON:
case BEAROFF_INVALID:
default:
break;
}
/*
* read database into memory if requested
*/
if (bo & BO_IN_MEMORY) {
fclose(pbc->pf);
pbc->pf = NULL;
if ((ReadIntoMemory(pbc) == NULL))
if ((pbc->pf = g_fopen(szFilename, "rb")) == 0) {
g_printerr("%s\n", _("Invalid or nonexistent database"));
InvalidDb(pbc);
return NULL;
}
}
return pbc;
}
extern float
fnd(const float x, const float mu, const float sigma)
{
const float epsilon = 1.0e-7f;
if (sigma <= epsilon)
/* dirac delta function */
return (fabsf(mu - x) < epsilon) ? 1.0f : 0.0f;
else {
float xm = (x - mu) / sigma;
return 1.0f / (sigma * sqrtf(2.0f * F_PI)) * expf(-xm * xm / 2.0f);
}
}
static int
ReadBearoffOneSidedND(const bearoffcontext * pbc,
const unsigned int nPosID,
float arProb[32], float arGammonProb[32],
float *ar, unsigned short int ausProb[32], unsigned short int ausGammonProb[32])
{
unsigned char ac[16];
float arx[4];
int i;
float r;
ReadBearoffFile(pbc, 40 + nPosID * 16, ac, 16);
memcpy(arx, ac, 16);
if (arProb || ausProb)
for (i = 0; i < 32; ++i) {
r = fnd((float) i, arx[0], arx[1]);
if (arProb)
arProb[i] = r;
if (ausProb)
ausProb[i] = (unsigned short) (r * 65535.0f);
}
if (arGammonProb || ausGammonProb)
for (i = 0; i < 32; ++i) {
r = fnd((float) i, arx[2], arx[3]);
if (arGammonProb)
arGammonProb[i] = r;
if (ausGammonProb)
ausGammonProb[i] = (unsigned short) (r * 65535.0f);
}
if (ar)
memcpy(ar, arx, 16);
return 0;
}
static void
AssignOneSided(float arProb[32], float arGammonProb[32],
float ar[4],
unsigned short int ausProb[32],
unsigned short int ausGammonProb[32],
const unsigned short int ausProbx[32], const unsigned short int ausGammonProbx[32])
{
if (ausProb)
memcpy(ausProb, ausProbx, 32 * sizeof(ausProb[0]));
if (ausGammonProb)
memcpy(ausGammonProb, ausGammonProbx, 32 * sizeof(ausGammonProbx[0]));
if (ar || arProb || arGammonProb) {
float arx[64];
int i;
for (i = 0; i < 32; ++i)
arx[i] = ausProbx[i] / 65535.0f;
for (i = 0; i < 32; ++i)
arx[32 + i] = ausGammonProbx[i] / 65535.0f;
if (arProb)
memcpy(arProb, arx, 32 * sizeof(float));
if (arGammonProb)
memcpy(arGammonProb, arx + 32, 32 * sizeof(float));
if (ar) {
AverageRolls(arx, ar);
AverageRolls(arx + 32, ar + 2);
}
}
}
static void
CopyBytes(unsigned short int aus[64],
const unsigned char ac[128],
const unsigned int nz, const unsigned int ioff, const unsigned int nzg, const unsigned int ioffg)
{
unsigned int i, j;
i = 0;
memset(aus, 0, 64 * sizeof(unsigned short int));
for (j = 0; j < nz; ++j, i += 2)
aus[ioff + j] = ac[i] | (unsigned short int) (ac[i + 1] << 8);
for (j = 0; j < nzg; ++j, i += 2)
aus[32 + ioffg + j] = (unsigned short int) (ac[i] | ac[i + 1] << 8);
}
static unsigned short int *
GetDistCompressed(unsigned short int aus[64], const bearoffcontext * pbc, const unsigned int nPosID)
{
unsigned char *puch;
unsigned char ac[128];
unsigned int iOffset;
unsigned int nBytes;
unsigned int ioff, nz, ioffg = 0, nzg = 0;
unsigned int nPos = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
unsigned int index_entry_size = pbc->fGammon ? 8 : 6;
/* find offsets and no. of non-zero elements */
if (pbc->p)
/* database is in memory */
puch = pbc->p + 40 + nPosID * index_entry_size;
else {
ReadBearoffFile(pbc, 40 + nPosID * index_entry_size, ac, index_entry_size);
puch = ac;
}
/* find offset */
iOffset = MakeInt(puch[0], puch[1], puch[2], puch[3]);
nz = puch[4];
ioff = puch[5];
if (pbc->fGammon) {
nzg = puch[6];
ioffg = puch[7];
}
/* Sanity checks */
if ((iOffset > 64 * nPos && 64 * nPos > 0) || nz > 32 || ioff > 32 || nzg > 32 || ioffg > 32) {
fprintf(stderr, _("The bearoff file '%s' is likely to be corrupted.\n"), pbc->szFilename);
#if !defined(G_DISABLE_ASSERT)
fprintf(stderr,
"Offset %lu, dist size %u (offset %u), "
"gammon dist size %u (offset %u)\n", (unsigned long) iOffset, nz, ioff, nzg, ioffg);
#endif
g_assert_not_reached();
}
/* read prob + gammon probs */
iOffset = 40 /* the header */
+ nPos * index_entry_size /* the offset data */
+ 2 * iOffset; /* offset to current position */
/* read values */
nBytes = 2 * (nz + nzg);
/* get distribution */
if (pbc->p)
/* from memory */
puch = pbc->p + iOffset;
else {
/* from disk */
ReadBearoffFile(pbc, iOffset, ac, nBytes);
puch = ac;
}
CopyBytes(aus, puch, nz, ioff, nzg, ioffg);
return aus;
}
static unsigned short int *
GetDistUncompressed(unsigned short int aus[64], const bearoffcontext * pbc, const unsigned int nPosID)
{
unsigned char ac[128];
unsigned char *puch;
unsigned int iOffset;
/* read from file */
iOffset = 40 + 64 * nPosID * (pbc->fGammon ? 2 : 1);
if (pbc->p)
/* from memory */
puch = pbc->p + iOffset;
else {
/* from disk */
ReadBearoffFile(pbc, iOffset, ac, pbc->fGammon ? 128 : 64);
puch = ac;
}
CopyBytes(aus, puch, 32, 0, 32, 0);
return aus;
}
static int
ReadBearoffOneSidedExact(const bearoffcontext * pbc, const unsigned int nPosID,
float arProb[32], float arGammonProb[32],
float ar[4], unsigned short int ausProb[32], unsigned short int ausGammonProb[32])
{
unsigned short int aus[64];
unsigned short int *pus = NULL;
/* get distribution */
if (pbc->fCompressed)
pus = GetDistCompressed(aus, pbc, nPosID);
else
pus = GetDistUncompressed(aus, pbc, nPosID);
if (!pus) {
printf(_("Error decoding one-sided bearoff database entry; position %u\n"), nPosID);
return -1;
}
AssignOneSided(arProb, arGammonProb, ar, ausProb, ausGammonProb, pus, pus + 32);
return 0;
}
extern int
BearoffDist(const bearoffcontext * pbc, const unsigned int nPosID,
float arProb[32], float arGammonProb[32],
float ar[4], unsigned short int ausProb[32], unsigned short int ausGammonProb[32])
{
g_return_val_if_fail(pbc, -1);
g_return_val_if_fail(pbc->bt == BEAROFF_ONESIDED, -1);
if (pbc->fND)
return ReadBearoffOneSidedND(pbc, nPosID, arProb, arGammonProb, ar, ausProb, ausGammonProb);
else
return ReadBearoffOneSidedExact(pbc, nPosID, arProb, arGammonProb, ar, ausProb, ausGammonProb);
}
extern int
isBearoff(const bearoffcontext * pbc, const TanBoard anBoard)
{
unsigned int i, nOppBack, nBack;
unsigned int n = 0, nOpp = 0;
if (!pbc)
return FALSE;
for (nOppBack = 24; nOppBack > 0; nOppBack--) {
if (anBoard[0][nOppBack])
break;
}
for (nBack = 24; nBack > 0; nBack--) {
if (anBoard[1][nBack])
break;
}
if (!anBoard[0][nOppBack] || !anBoard[1][nBack])
/* the game is over */
return FALSE;
if ((nBack + nOppBack > 22) && !(pbc->bt == BEAROFF_HYPERGAMMON))
/* contact position */
return FALSE;
for (i = 0; i <= nOppBack; ++i)
nOpp += anBoard[0][i];
for (i = 0; i <= nBack; ++i)
n += anBoard[1][i];
if (n <= pbc->nChequers && nOpp <= pbc->nChequers && nBack < pbc->nPoints && nOppBack < pbc->nPoints)
return TRUE;
else
return FALSE;
}