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

1459 lines
36 KiB
C

/*
* Copyright (C) 1997-2003 Gary Wong <gary@cs.arizona.edu>
* Copyright (C) 2002-2024 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 <stdio.h>
#ifdef WIN32
#include <io.h>
#endif
#include <stdlib.h>
#include <string.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <math.h>
#include <errno.h>
#include <locale.h>
#include <glib/gstdio.h>
#include "eval.h"
#include "positionid.h"
#include "bearoff.h"
#include "util.h"
#include "backgammon.h"
#include "multithread.h"
void
MT_CloseThreads(void)
{
return;
}
typedef struct {
void *p;
unsigned int iKey;
} xhashent;
typedef struct {
unsigned long int nQueries, nHits, nEntries, nOverwrites;
int nHashSize;
xhashent *phe;
} xhash;
static long cLookup;
static int
XhashPosition(xhash * ph, const int iKey)
{
return iKey % ph->nHashSize;
}
static void
XhashStatus(xhash * ph)
{
g_printerr(_("Xhash status:\n"));
g_printerr(_("Size: %d elements\n"), ph->nHashSize);
g_printerr(_("Queries: %lu (hits: %lu)\n"), ph->nQueries, ph->nHits);
g_printerr(_("Entries: %lu (overwrites: %lu)\n"), ph->nEntries, ph->nOverwrites);
}
static int
XhashCreate(xhash * ph, const int nHashSize)
{
int i;
ph->phe = (xhashent *) g_malloc0(nHashSize * sizeof(xhashent));
ph->nQueries = 0;
ph->nHits = 0;
ph->nEntries = 0;
ph->nOverwrites = 0;
ph->nHashSize = nHashSize;
for (i = 0; i < nHashSize; ++i)
ph->phe[i].p = NULL;
return 0;
}
static void
XhashDestroy(xhash * ph)
{
int i;
for (i = 0; i < ph->nHashSize; ++i)
if (ph->phe[i].p)
g_free(ph->phe[i].p);
free(ph->phe);
}
static void
XhashAdd(xhash * ph, const unsigned int iKey, const void *data, const int size)
{
int l = XhashPosition(ph, iKey);
if (ph->phe[l].p) {
/* occupied */
g_free(ph->phe[l].p);
ph->nOverwrites++;
} else {
/* free */
ph->nEntries++;
}
ph->phe[l].iKey = iKey;
#if GLIB_CHECK_VERSION (2,67,4)
ph->phe[l].p = g_memdup2(data, size);
#else
ph->phe[l].p = g_memdup(data, size);
#endif
}
static void *
XhashLookup(xhash * ph, const unsigned int iKey)
{
int l = XhashPosition(ph, iKey);
++ph->nQueries;
if (ph->phe[l].p && ph->phe[l].iKey == iKey) {
/* hit */
++ph->nHits;
return (ph->phe[l].p);
} else
/* miss */
return NULL;
}
static int
OSLookup(const unsigned int iPos,
const int UNUSED(nPoints),
unsigned short int aProb[64], const int fGammon, const int fCompress, FILE * pfOutput, FILE * pfTmp)
{
unsigned int i, j;
unsigned char ac[128];
g_assert(pfOutput != stdin);
if (fCompress) {
long iOffset;
size_t nBytes;
unsigned int ioff, nz, ioffg = 0, nzg = 0;
unsigned short int us;
unsigned int index_entry_size = fGammon ? 8 : 6;
/* find offsets and no. of non-zero elements */
if (fseek(pfOutput, 40 + iPos * index_entry_size, SEEK_SET) < 0) {
perror("output file");
exit(-1);
}
if (fread(ac, 1, index_entry_size, pfOutput) < index_entry_size) {
if (errno)
perror("output file");
else
g_printerr(_("Error reading output file\n"));
exit(-1);
}
iOffset = ((unsigned int) ac[0] |
(unsigned int) ac[1] << 8 |
(unsigned int) ac[2] << 16 |
(unsigned int) ac[3] << 24);
nz = ac[4];
ioff = ac[5];
if (fGammon) {
nzg = ac[6];
ioffg = ac[7];
}
/* re-position at EOF */
if (fseek(pfOutput, 0L, SEEK_END) < 0) {
perror("output file");
exit(-1);
}
/* read distributions */
iOffset = 2 * iOffset;
nBytes = 2 * (nz + nzg);
if (fseek(pfTmp, iOffset, SEEK_SET) < 0) {
perror("fseek'ing temporary file");
exit(-1);
}
/* read data */
if (fread(ac, 1, nBytes, pfTmp) < nBytes) {
if (errno)
perror("reading temporary file");
else
g_printerr(_("Error reading temporary file"));
exit(-1);
}
memset(aProb, 0, 128);
i = 0;
for (j = 0; j < nz; ++j, i += 2) {
us = (unsigned short) (ac[i] | ac[i + 1] << 8);
aProb[ioff + j] = us;
}
/* if ( fGammon ) */
for (j = 0; j < nzg; ++j, i += 2) {
us = (unsigned short) (ac[i] | ac[i + 1] << 8);
aProb[32 + ioffg + j] = us;
}
/* re-position at EOF */
if (fseek(pfTmp, 0L, SEEK_END) < 0) {
perror("fseek'ing to EOF on temporary file");
exit(-1);
}
} else {
/* look up position by seeking */
if (fseek(pfOutput, 40 + iPos * (fGammon ? 128 : 64), SEEK_SET) < 0) {
g_printerr(_("Error seeking in pfOutput\n"));
exit(-1);
}
/* read distribution */
if (fread(ac, 1, fGammon ? 128U : 64U, pfOutput) < (fGammon ? 128U : 64U)) {
g_printerr(_("Error reading from pfOutput\n"));
exit(-1);
}
/* save distribution */
i = 0;
for (j = 0; j < 32; ++j, i += 2)
aProb[j] = (unsigned short) (ac[i] | ac[i + 1] << 8);
if (fGammon)
for (j = 0; j < 32; ++j, i += 2)
aProb[j + 32] = (unsigned short) (ac[i] | ac[i + 1] << 8);
/* position cursor at end of file */
if (fseek(pfOutput, 0L, SEEK_END) < 0) {
g_printerr(_("Error seeking to end!\n"));
exit(-1);
}
}
++cLookup;
return 0;
}
static void
CalcIndex(const unsigned short int aProb[32], unsigned int *piIdx, unsigned int *pnNonZero)
{
int i;
int j = 32;
/* find max non-zero element */
for (i = 31; i >= 0; --i)
if (aProb[i]) {
j = i;
break;
}
/* find min non-zero element */
*piIdx = 0;
for (i = 0; i <= j; ++i)
if (aProb[i]) {
*piIdx = i;
break;
}
*pnNonZero = j - *piIdx + 1;
}
static unsigned int
RollsOS(const unsigned short int aus[32])
{
int i;
unsigned int j;
for (j = 0, i = 1; i < 32; i++)
j += i * aus[i];
return j;
}
static void
BearOff(int nId, unsigned int nPoints,
unsigned short int aOutProb[64],
const int fGammon, xhash * ph, bearoffcontext * pbc, const int fCompress, FILE * pfOutput, FILE * pfTmp)
{
#if !defined(G_DISABLE_ASSERT)
int iBest;
#endif
int iMode, j, anRoll[2], aProb[64] = { 0 };
unsigned int i;
TanBoard anBoard, anBoardTemp;
movelist ml;
int k;
unsigned int us;
unsigned int usBest;
unsigned short int *pusj;
unsigned short int ausj[64];
unsigned short int ausBest[32];
unsigned int usGammonBest;
unsigned short int ausGammonBest[32];
unsigned int nBack;
/* get board for given position */
PositionFromBearoff(anBoard[1], nId, nPoints, 15);
/* initialise probabilities */
for (i = 0; i < 64; i++)
aOutProb[i] = 0;
/* all chequers off is easy :-) */
if (!nId) {
aOutProb[0] = 0xFFFF;
aOutProb[32] = 0xFFFF;
return;
}
/* initialise the remainder of the board */
for (i = nPoints; i < 25; i++)
anBoard[1][i] = 0;
for (i = 0; i < 25; i++)
anBoard[0][i] = 0;
nBack = 0;
for (i = 0, k = 0; i < nPoints; ++i) {
if (anBoard[1][i])
nBack = i;
k += anBoard[1][i];
}
/* look for position in existing bearoff file */
if (pbc && nBack < pbc->nPoints) {
unsigned int nPosID = PositionBearoff(anBoard[1],
pbc->nPoints, pbc->nChequers);
BearoffDist(pbc, nPosID, NULL, NULL, NULL, aOutProb, aOutProb + 32);
return;
}
if (k < 15)
aProb[32] = 0xFFFF * 36;
for (anRoll[0] = 1; anRoll[0] <= 6; anRoll[0]++)
for (anRoll[1] = 1; anRoll[1] <= anRoll[0]; anRoll[1]++) {
GenerateMoves(&ml, (ConstTanBoard) anBoard, anRoll[0], anRoll[1], FALSE);
usBest = 0xFFFFFFFF;
#if !defined(G_DISABLE_ASSERT)
iBest = -1;
#endif
usGammonBest = 0xFFFFFFFF;
for (i = 0; i < ml.cMoves; i++) {
PositionFromKey(anBoardTemp, &ml.amMoves[i].key);
j = PositionBearoff(anBoardTemp[1], nPoints, 15);
g_assert(j >= 0);
g_assert(j < nId);
if (!j) {
memset(pusj = ausj, 0, fGammon ? 128 : 64);
pusj[0] = 0xFFFF;
pusj[32] = 0xFFFF;
} else if (!(pusj = XhashLookup(ph, j))) {
/* look up in file generated so far */
pusj = ausj;
OSLookup(j, nPoints, pusj, fGammon, fCompress, pfOutput, pfTmp);
XhashAdd(ph, j, pusj, fGammon ? 128 : 64);
}
/* find best move to win */
if ((us = RollsOS(pusj)) < usBest) {
#if !defined(G_DISABLE_ASSERT)
iBest = j;
#endif
usBest = us;
memcpy(ausBest, pusj, 64);
}
/* find best move to save gammon */
if (fGammon && ((us = RollsOS(pusj + 32)) < usGammonBest)) {
usGammonBest = us;
memcpy(ausGammonBest, pusj + 32, 64);
}
}
g_assert(iBest >= 0);
/* g_assert( iGammonBest >= 0 ); */
if (anRoll[0] == anRoll[1]) {
for (i = 0; i < 31; i++) {
aProb[i + 1] += ausBest[i];
if (k == 15 && fGammon)
aProb[32 + i + 1] += ausGammonBest[i];
}
} else {
for (i = 0; i < 31; i++) {
aProb[i + 1] += 2 * ausBest[i];
if (k == 15 && fGammon)
aProb[32 + i + 1] += 2 * ausGammonBest[i];
}
}
}
for (i = 0, j = 0, iMode = 0; i < 32; i++) {
j += (aOutProb[i] = (unsigned short) ((aProb[i] + 18) / 36));
if (aOutProb[i] > aOutProb[iMode])
iMode = i;
}
aOutProb[iMode] -= (unsigned short int) (j - 0xFFFF);
/* gammon probs */
if (fGammon) {
for (i = 0, j = 0, iMode = 0; i < 32; i++) {
j += (aOutProb[32 + i] = (unsigned short) ((aProb[32 + i] + 18) / 36));
if (aOutProb[32 + i] > aOutProb[32 + iMode])
iMode = i;
}
aOutProb[32 + iMode] -= (unsigned short int) (j - 0xFFFF);
}
}
static void
WriteOS(const unsigned short int aus[32], const int fCompress, FILE * output)
{
unsigned int iIdx, nNonZero;
unsigned int j;
if (fCompress)
CalcIndex(aus, &iIdx, &nNonZero);
else {
iIdx = 0;
nNonZero = 32;
}
for (j = iIdx; j < iIdx + nNonZero; j++) {
putc(aus[j] & 0xFF, output);
putc(aus[j] >> 8, output);
}
}
static void
WriteIndex(unsigned int *pnpos, const unsigned short int aus[64], const int fGammon, FILE * output)
{
unsigned int iIdx, nNonZero;
/* write offset */
putc(*pnpos & 0xFF, output);
putc((*pnpos >> 8) & 0xFF, output);
putc((*pnpos >> 16) & 0xFF, output);
putc((*pnpos >> 24) & 0xFF, output);
/* write index and number of non-zero elements */
CalcIndex(aus, &iIdx, &nNonZero);
putc(nNonZero & 0xFF, output);
putc(iIdx & 0xFF, output);
*pnpos += nNonZero;
/* gammon probs: write index and number of non-zero elements */
if (fGammon) {
CalcIndex(aus + 32, &iIdx, &nNonZero);
putc(nNonZero & 0xFF, output);
putc(iIdx & 0xFF, output);
*pnpos += nNonZero;
}
}
static void
WriteFloat(const float r, FILE * output)
{
int j;
const unsigned char *pc;
pc = (const unsigned char *) &r;
for (j = 0; j < 4; ++j)
putc(*(pc++), output);
}
/*
* Generate one sided bearoff database
*
* ! fCompress:
* write database directly to stdout
*
* fCompress:
* write index directly to stdout
* and write actual db to tmp file
* the tmp file is concatenated to the index when
* the generation is done.
*
*/
static int
generate_os(const int nOS, const int fHeader,
const int fCompress, const int fGammon, const int nHashSize, bearoffcontext * pbc, FILE * output)
{
int i;
int n;
unsigned short int aus[64];
xhash h;
FILE *pfTmp = NULL;
unsigned int npos;
char *tmpfile = NULL;
int fTTY = isatty(STDERR_FILENO);
/* initialise xhash */
if (XhashCreate(&h, nHashSize / (fGammon ? 128 : 64))) {
g_printerr(_("Error creating xhash with %d elements\n"), nHashSize / (fGammon ? 128 : 64));
exit(2);
}
XhashStatus(&h);
/* write header */
if (fHeader) {
char sz[41];
sprintf(sz, "gnubg-OS-%02d-15-%1d-%1d-0xxxxxxxxxxxxxxxxxxx\n", nOS, fGammon, fCompress);
fputs(sz, output);
}
if (fCompress) {
pfTmp = GetTemporaryFile(NULL, &tmpfile);
if (pfTmp == NULL) {
g_printerr(_("Error creating temporary file\n"));
exit(2);
}
}
/* loop trough bearoff positions */
n = Combination(nOS + 15, nOS);
npos = 0;
for (i = 0; i < n; ++i) {
if (i)
BearOff(i, nOS, aus, fGammon, &h, pbc, fCompress, output, pfTmp);
else {
memset(aus, 0, 128);
aus[0] = 0xFFFF;
aus[32] = 0xFFFF;
}
if (!(i % 100) && fTTY)
g_printerr("%d/%d\r", i, n);
WriteOS(aus, fCompress, fCompress ? pfTmp : output);
if (fGammon)
WriteOS(aus + 32, fCompress, fCompress ? pfTmp : output);
XhashAdd(&h, i, aus, fGammon ? 128 : 64);
if (fCompress)
WriteIndex(&npos, aus, fGammon, output);
}
if (fCompress) {
char ac[256];
size_t u;
/* write contents of pfTmp to output */
if (fseek(pfTmp, 0L, SEEK_SET) < 0) {
g_printerr(_("Error rewinding '%s'\n"), tmpfile);
exit(3);
}
while (!feof(pfTmp) && (u = fread(ac, 1, sizeof(ac), pfTmp))) {
if (fwrite(ac, 1, u, output) != u) {
g_printerr(_("Error writing to '%s'\n"), tmpfile);
exit(3);
}
}
if (ferror(pfTmp))
exit(3);
fclose(pfTmp);
g_unlink(tmpfile);
g_free(tmpfile);
}
putc('\n', stderr);
XhashStatus(&h);
XhashDestroy(&h);
return 0;
}
static void
NDBearoff(const int iPos, const unsigned int nPoints, float ar[4], xhash * ph, bearoffcontext * pbc)
{
int d0, d1;
movelist ml;
TanBoard anBoard, anBoardTemp;
int j, k;
unsigned int i;
#if !defined(G_DISABLE_ASSERT)
int iBest;
int iGammonBest;
#endif
float rBest;
float rMean;
float rVarSum, rGammonVarSum;
float *prj;
float arj[4] = { 0.0, 0.0, 0.0, 0.0 };
float arBest[4] = { 0.0, 0.0, 0.0, 0.0 };
float arGammonBest[4] = { 0.0, 0.0, 0.0, 0.0 };
float rGammonBest;
for (i = 0; i < 4; ++i)
ar[i] = 0.0f;
if (!iPos)
return;
PositionFromBearoff(anBoard[1], iPos, nPoints, 15);
for (i = nPoints; i < 25; i++)
anBoard[1][i] = 0;
/*
* look for position in existing bearoff file
*/
if (pbc) {
int ii;
for (ii = 24; ii >= 0 && !anBoard[1][ii]; --ii);
if (ii < (int) pbc->nPoints) {
unsigned int nPosID = PositionBearoff(anBoard[1],
pbc->nPoints, pbc->nChequers);
BearoffDist(pbc, nPosID, NULL, NULL, ar, NULL, NULL);
return;
}
}
memset(anBoard[0], 0, 25 * sizeof(int));
for (i = 0, k = 0; i < nPoints; ++i)
k += anBoard[1][i];
/* loop over rolls */
rVarSum = rGammonVarSum = 0.0f;
for (d0 = 1; d0 <= 6; ++d0)
for (d1 = 1; d1 <= d0; ++d1) {
GenerateMoves(&ml, (ConstTanBoard) anBoard, d0, d1, FALSE);
rBest = 1e10;
rGammonBest = 1e10;
#if !defined(G_DISABLE_ASSERT)
iBest = -1;
iGammonBest = -1;
#endif
for (i = 0; i < ml.cMoves; ++i) {
PositionFromKey(anBoardTemp, &ml.amMoves[i].key);
j = PositionBearoff(anBoardTemp[1], nPoints, 15);
if (!(prj = XhashLookup(ph, j))) {
prj = arj;
NDBearoff(j, nPoints, prj, ph, pbc);
XhashAdd(ph, j, prj, 16);
}
/* find best move to win */
if (prj[0] < rBest) {
#if !defined(G_DISABLE_ASSERT)
iBest = j;
#endif
rBest = prj[0];
memcpy(arBest, prj, 4 * sizeof(float));
}
/* find best move to save gammon */
if (prj[2] < rGammonBest) {
#if !defined(G_DISABLE_ASSERT)
iGammonBest = j;
#endif
rGammonBest = prj[2];
memcpy(arGammonBest, prj, 4 * sizeof(float));
}
}
g_assert(iBest >= 0);
g_assert(iGammonBest >= 0);
rMean = 1.0f + arBest[0];
ar[0] += (d0 == d1) ? rMean : 2.0f * rMean;
rMean = arBest[1] * arBest[1] + rMean * rMean;
rVarSum += (d0 == d1) ? rMean : 2.0f * rMean;
if (k == 15) {
rMean = 1.0f + arGammonBest[2];
ar[2] += (d0 == d1) ? rMean : 2.0f * rMean;
rMean = arGammonBest[3] * arGammonBest[3] + rMean * rMean;
rGammonVarSum += (d0 == d1) ? rMean : 2.0f * rMean;
}
}
ar[0] /= 36.0f;
ar[1] = sqrtf(rVarSum / 36.0f - ar[0] * ar[0]);
ar[2] /= 36.0f;
ar[3] = sqrtf(rGammonVarSum / 36.0f - ar[2] * ar[2]);
}
static void
generate_nd(const int nPoints, const int nHashSize, const int fHeader, bearoffcontext * pbc, FILE * outfile)
{
int n = Combination(nPoints + 15, nPoints);
int i, j;
float ar[4];
int fTTY = isatty(STDERR_FILENO);
xhash h;
/* initialise xhash */
if (XhashCreate(&h, (int) (nHashSize / (4 * sizeof(float))))) {
g_printerr(_("Error creating cache\n"));
return;
}
XhashStatus(&h);
if (fHeader) {
char sz[41];
sprintf(sz, "gnubg-OS-%02d-15-1-0-1xxxxxxxxxxxxxxxxxxx\n", nPoints);
fputs(sz, outfile);
}
for (i = 0; i < n; ++i) {
if (i)
NDBearoff(i, nPoints, ar, &h, pbc);
else
ar[0] = ar[1] = ar[2] = ar[3] = 0.0f;
for (j = 0; j < 4; ++j)
WriteFloat(ar[j], outfile);
XhashAdd(&h, i, ar, 16);
if (!(i % 100) && fTTY)
g_printerr("%d/%d\r", i, n);
}
putc('\n', stderr);
XhashStatus(&h);
XhashDestroy(&h);
}
static short int
CubeEquity(const short int siND, const short int siDT, const short int siDP)
{
if (2 * siDT >= siND && siDP >= siND) {
/* it's a double */
if (2 * siDT >= siDP)
/* double, pass */
return siDP;
else
/* double, take */
return (short int) (2 * siDT);
} else
/* no double */
return siND;
}
static int
CalcPosition(const int i, const int j, const int n)
{
int k;
if (i + j < n)
k = (i + j) * (i + j + 1) / 2 + j;
else
k = n * n - CalcPosition(n - 1 - i, n - 1 - j, n - 1) - 1;
#if 0
if (n == 6) {
g_printerr("%2d ", k);
if (j == n - 1)
g_printerr("\n");
}
#endif
return k;
}
static void
TSLookup(const int nUs, const int nThem,
const int UNUSED(nTSP), const int UNUSED(nTSC),
short int arEquity[4], const int n, const int fCubeful, FILE * pfTmp)
{
int iPos = CalcPosition(nUs, nThem, n);
unsigned char ac[8];
int i;
/* seek to position */
if (fseek(pfTmp, iPos * (fCubeful ? 8 : 2), SEEK_SET) < 0) {
perror("temporary file");
exit(-1);
}
if (fread(ac, 1, 8, pfTmp) < 8) {
if (errno)
perror("temporary file");
else
g_printerr(_("Error reading temporary file\n"));
exit(-1);
}
/* re-position at EOF */
if (fseek(pfTmp, 0L, SEEK_END) < 0) {
perror("temporary file");
exit(-1);
}
for (i = 0; i < (fCubeful ? 4 : 1); ++i)
arEquity[i] = (unsigned short) ((ac[2 * i] | ac[2 * i + 1] << 8) - 0x8000);
++cLookup;
}
/*
* Calculate exact equity for position.
*
* We store the equity in two bytes:
* 0x0000 meaning equity=-1 and 0xFFFF meaning equity=+1.
*
*/
static void
BearOff2(int nUs, int nThem,
const int nTSP, const int nTSC,
short int asiEquity[4], const int n, const int fCubeful, xhash * ph, bearoffcontext * pbc, FILE * pfTmp)
{
int j, anRoll[2];
unsigned int i;
TanBoard anBoard, anBoardTemp;
movelist ml;
#if !defined(G_DISABLE_ASSERT)
int aiBest[4];
#endif
int asiBest[4];
int aiTotal[4];
short int k;
short int *psij;
short int asij[4];
const short int EQUITY_P1 = 0x7FFF;
const short int EQUITY_M1 = ~EQUITY_P1;
if (!nUs) {
/* we have won */
asiEquity[0] = asiEquity[1] = asiEquity[2] = asiEquity[3] = EQUITY_P1;
return;
}
if (!nThem) {
/* we have lost */
asiEquity[0] = asiEquity[1] = asiEquity[2] = asiEquity[3] = EQUITY_M1;
return;
}
PositionFromBearoff(anBoard[0], nThem, nTSP, nTSC);
PositionFromBearoff(anBoard[1], nUs, nTSP, nTSC);
for (i = nTSP; i < 25; i++)
anBoard[1][i] = anBoard[0][i] = 0;
/* look for position in bearoff file */
if (pbc && isBearoff(pbc, (ConstTanBoard) anBoard)) {
unsigned short int nUsL = (unsigned short) PositionBearoff(anBoard[1], pbc->nPoints, pbc->nChequers);
unsigned short int nThemL = (unsigned short) PositionBearoff(anBoard[0], pbc->nPoints, pbc->nChequers);
int nL = Combination(pbc->nPoints + pbc->nChequers, pbc->nPoints);
unsigned int iPos = nUsL * nL + nThemL;
unsigned short int aus[4];
BearoffCubeful(pbc, iPos, NULL, aus);
for (i = 0; i < 4; ++i)
asiEquity[i] = (short int) (aus[i] - 0x8000);
return;
}
aiTotal[0] = aiTotal[1] = aiTotal[2] = aiTotal[3] = 0;
for (anRoll[0] = 1; anRoll[0] <= 6; anRoll[0]++)
for (anRoll[1] = 1; anRoll[1] <= anRoll[0]; anRoll[1]++) {
GenerateMoves(&ml, (ConstTanBoard) anBoard, anRoll[0], anRoll[1], FALSE);
asiBest[0] = asiBest[1] = asiBest[2] = asiBest[3] = -0xFFFF;
#if !defined(G_DISABLE_ASSERT)
aiBest[0] = aiBest[1] = aiBest[2] = aiBest[3] = -1;
#endif
for (i = 0; i < ml.cMoves; i++) {
PositionFromKey(anBoardTemp, &ml.amMoves[i].key);
j = PositionBearoff(anBoardTemp[1], nTSP, nTSC);
g_assert(j >= 0);
g_assert(j < nUs);
if (!nThem) {
asij[0] = asij[1] = asij[2] = asij[3] = EQUITY_P1;
} else if (!j) {
asij[0] = asij[1] = asij[2] = asij[3] = EQUITY_M1;
}
if (!(psij = XhashLookup(ph, n * nThem + j))) {
/* lookup in file */
psij = asij;
TSLookup(nThem, j, nTSP, nTSC, psij, n, fCubeful, pfTmp);
XhashAdd(ph, n * nThem + j, psij, fCubeful ? 8 : 2);
}
/* cubeless */
if (psij[0] < -asiBest[0]) {
#if !defined(G_DISABLE_ASSERT)
aiBest[0] = j;
#endif
asiBest[0] = ~psij[0];
}
if (fCubeful) {
/* I own cube:
* from opponent's view he doesn't own cube */
if (psij[3] < -asiBest[1]) {
#if !defined(G_DISABLE_ASSERT)
aiBest[1] = j;
#endif
asiBest[1] = ~psij[3];
}
/* Centered cube (so centered for opponent too) */
k = CubeEquity(psij[2], psij[3], EQUITY_P1);
if (~k > asiBest[2]) {
#if !defined(G_DISABLE_ASSERT)
aiBest[2] = j;
#endif
asiBest[2] = ~k;
}
/* Opponent owns cube:
* from opponent's view he owns cube */
k = CubeEquity(psij[1], psij[3], EQUITY_P1);
if (~k > asiBest[3]) {
#if !defined(G_DISABLE_ASSERT)
aiBest[3] = j;
#endif
asiBest[3] = ~k;
}
}
}
g_assert(aiBest[0] >= 0);
g_assert(!fCubeful || aiBest[1] >= 0);
g_assert(!fCubeful || aiBest[2] >= 0);
g_assert(!fCubeful || aiBest[3] >= 0);
if (anRoll[0] == anRoll[1])
for (k = 0; k < (fCubeful ? 4 : 1); ++k)
aiTotal[k] += asiBest[k];
else
for (k = 0; k < (fCubeful ? 4 : 1); ++k)
aiTotal[k] += 2 * asiBest[k];
}
/* final equities */
for (k = 0; k < (fCubeful ? 4 : 1); ++k)
asiEquity[k] = (short) (aiTotal[k] / 36);
}
static void
WriteEquity(FILE * pf, const short int si)
{
unsigned short int us = (unsigned short int) (si + 0x8000);
putc(us & 0xFF, pf);
putc((us >> 8) & 0xFF, pf);
}
static void
generate_ts(const int nTSP, const int nTSC,
const int fHeader, const int fCubeful, const int nHashSize, bearoffcontext * pbc, FILE * output)
{
int i, j, k;
int iPos;
int n;
short int asiEquity[4];
xhash h;
FILE *pfTmp;
unsigned char ac[8];
char *tmpfile;
int fTTY = isatty(STDERR_FILENO);
pfTmp = GetTemporaryFile(NULL, &tmpfile);
if (pfTmp == NULL) {
g_printerr(_("Error creating temporary file\n"));
exit(2);
}
/* initialise xhash */
if (XhashCreate(&h, nHashSize / (fCubeful ? 8 : 2))) {
g_printerr(_("Error creating xhash with %d elements\n"), nHashSize / (fCubeful ? 8 : 2));
exit(2);
}
XhashStatus(&h);
/* write header information */
if (fHeader) {
char sz[41];
sprintf(sz, "gnubg-TS-%02d-%02d-%1dxxxxxxxxxxxxxxxxxxxxxxx\n", nTSP, nTSC, fCubeful);
fputs(sz, output);
}
/* generate bearoff database */
n = Combination(nTSP + nTSC, nTSC);
iPos = 0;
/* positions above diagonal */
for (i = 0; i < n; i++) {
for (j = 0; j <= i; j++, ++iPos) {
BearOff2(i - j, j, nTSP, nTSC, asiEquity, n, fCubeful, &h, pbc, pfTmp);
for (k = 0; k < (fCubeful ? 4 : 1); ++k)
WriteEquity(pfTmp, asiEquity[k]);
XhashAdd(&h, (i - j) * n + j, asiEquity, fCubeful ? 8 : 2);
}
if (fTTY)
g_printerr("%d/%d\r", iPos, n * n);
}
/* positions below diagonal */
for (i = 0; i < n; i++) {
for (j = i + 1; j < n; j++, ++iPos) {
BearOff2(i + n - j, j, nTSP, nTSC, asiEquity, n, fCubeful, &h, pbc, pfTmp);
for (k = 0; k < (fCubeful ? 4 : 1); ++k)
WriteEquity(pfTmp, asiEquity[k]);
XhashAdd(&h, (i + n - j) * n + j, asiEquity, fCubeful ? 8 : 2);
}
if (fTTY)
g_printerr("%d/%d\r", iPos, n * n);
}
putc('\n', stderr);
XhashStatus(&h);
XhashDestroy(&h);
/* sort file from ordering:
*
* 136 123
* 258 to 456
* 479 789
*
*/
for (i = 0; i < n; ++i) {
for (j = 0; j < n; ++j) {
unsigned int count = fCubeful ? 8 : 2;
k = CalcPosition(i, j, n);
fseek(pfTmp, count * k, SEEK_SET);
if (fread(ac, 1, count, pfTmp) != count || fwrite(ac, 1, count, output) != count) {
g_printerr(_("failed to read from or write to database file\n"));
exit(3);
}
}
}
fclose(pfTmp);
g_unlink(tmpfile);
g_free(tmpfile);
}
extern int
main(int argc, char **argv)
{
/* static storage for options to keep picky compiler happy */
static int nOS = 0;
static int fHeader = TRUE;
static int fCompress = TRUE;
static int fGammon = TRUE;
static int nHashSize = 100000000;
static int fCubeful = TRUE;
static char *szOldBearoff = NULL;
static int fND = FALSE;
static char *szOutput = NULL;
static char *szTwoSided = NULL;
bearoffcontext *pbc = NULL;
FILE *outfile;
double r;
int nTSP = 0, nTSC = 0;
GOptionEntry ao[] = {
{"two-sided", 't', 0, G_OPTION_ARG_STRING, &szTwoSided,
N_("Number of points (P) and number of chequers (C) for two-sided database"), "PxC"},
{"one-sided", 'o', 0, G_OPTION_ARG_INT, &nOS,
N_("Number of points (P) for one-sided database"), "P"},
{"xhash-size", 's', 0, G_OPTION_ARG_INT, &nHashSize,
N_("Use cache of size N bytes"), "N"},
{"old-bearoff", 'O', 0, G_OPTION_ARG_STRING, &szOldBearoff,
N_("Reuse already generated bearoff database \"filename\""), "filename"},
{"no-header", 'H', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &fHeader,
N_("Do not write header"), NULL},
{"no-cubeful", 'C', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &fCubeful,
N_("Do not calculate cubeful equities for two-sided databases"), NULL},
{"no-compress", 'c', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &fCompress,
N_("Do not use compression scheme for one-sided databases"), NULL},
{"no-gammon", 'g', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &fGammon,
N_("Do not include gammon distribution for one-sided databases"), NULL},
{"normal-dist", 'n', 0, G_OPTION_ARG_NONE, &fND,
N_("Approximate one-sided bearoff database with normal distributions"), NULL},
{"outfile", 'f', 0, G_OPTION_ARG_STRING, &szOutput,
N_("Required output filename"), "filename"},
{NULL, 0, 0, (GOptionArg) 0, NULL, NULL, NULL}
};
GError *error = NULL;
GOptionContext *context;
/* i18n */
MT_InitThreads();
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
g_set_printerr_handler(print_utf8_to_locale);
context = g_option_context_new(NULL);
g_option_context_add_main_entries(context, ao, PACKAGE);
g_option_context_parse(context, &argc, &argv, &error);
g_option_context_free(context);
if (error) {
g_printerr("%s\n", error->message);
exit(EXIT_FAILURE);
}
if (szTwoSided)
sscanf(szTwoSided, "%2dx%2d", &nTSP, &nTSC);
if (!szOutput) {
g_printerr(_("Required argument -f missing\n"));
exit(EXIT_FAILURE);
}
if (!(outfile = g_fopen(szOutput, "w+b"))) {
perror(szOutput);
return EXIT_FAILURE;
}
/* one sided database */
if (nOS) {
if (nOS > 13) {
g_printerr(_("Size of one-sided bearoff database should be at most 13 points\n"));
exit(2);
}
g_printerr("%-37s\n", _("One-sided database"));
g_printerr("%-37s: %12d\n", _("Number of points"), nOS);
g_printerr("%-37s: %12d\n", _("Number of chequers"), 15);
g_printerr("%-37s: %12u\n", _("Number of positions"), Combination(nOS + 15, nOS));
g_printerr("%-37s: %12s\n", _("Approximate by normal distribution"), fND ? _("yes") : _("no"));
g_printerr("%-37s: %12s\n", _("Include gammon distributions"), fGammon ? _("yes") : _("no"));
g_printerr("%-37s: %12s\n", _("Use compression scheme"), fCompress ? _("yes") : _("no"));
g_printerr("%-37s: %12s\n", _("Write header"), fHeader ? _("yes") : _("no"));
g_printerr("%-37s: %12d\n", _("Size of cache"), nHashSize);
g_printerr("%-37s: %12s %s\n", _("Reuse old bearoff database"), szOldBearoff ? _("yes") : _("no"),
szOldBearoff ? szOldBearoff : "");
if (fND) {
r = Combination(nOS + 15, nOS) * 16.0;
g_printerr("%-37s: %.0f (%.1f MB)\n", _("Size of database"), r, r / 1048576.0);
} else {
r = (float) Combination(nOS + 15, nOS) * (fGammon ? 128.0f : 64.0f);
g_printerr("%-37s: %.0f (%.1f MB)\n", _("Size of database (uncompressed)"), r, r / 1048576.0);
if (fCompress) {
r = (float) Combination(nOS + 15, nOS) * (fGammon ? 32.0f : 16.0f);
g_printerr("%-37s: %.0f (%.1f MB)\n", _("Estimated size of compressed db"), r, r / 1048576.0);
}
}
if (szOldBearoff && !(pbc = BearoffInit(szOldBearoff, BO_NONE, NULL))) {
g_printerr(_("Error initialising old bearoff database!\n"));
exit(2);
}
/* bearoff database must be of the same kind as the request one-sided
* database */
if (pbc && (pbc->bt != BEAROFF_ONESIDED || pbc->fND != fND || pbc->fGammon < fGammon)) {
g_printerr(_("The old database is not of the same kind as the" " requested database\n"));
exit(2);
}
if (fND) {
generate_nd(nOS, nHashSize, fHeader, pbc, outfile);
} else {
generate_os(nOS, fHeader, fCompress, fGammon, nHashSize, pbc, outfile);
}
BearoffClose(pbc);
g_printerr(_("Number of re-reads while generating: %ld\n"), cLookup);
}
/*
* Two-sided database
*/
if (nTSC && nTSP) {
int n = Combination(nTSP + nTSC, nTSC);
if (nTSC > 11) {
g_printerr(_("Size of two-sided bearoff database must be at most 11 chequers\n"));
exit(2);
}
r = n;
r = r * r * (fCubeful ? 8.0 : 2.0);
g_printerr("%-37s\n", _("Two-sided database:\n"));
g_printerr("%-37s: %12d\n", _("Number of points"), nTSP);
g_printerr("%-37s: %12d\n", _("Number of chequers"), nTSC);
g_printerr("%-37s: %12s\n", _("Calculate equities"),
fCubeful ? _("cubeless and cubeful") : _("cubeless only"));
g_printerr("%-37s: %12s\n", _("Write header"), fHeader ? _("yes") : _("no"));
g_printerr("%-37s: %12d\n", _("Number of one-sided positions"), n);
g_printerr("%-37s: %12d\n", _("Total number of positions"), n * n);
g_printerr("%-37s: %.0f %s (%.1f MB)\n", _("Size of resulting file"), r, _("bytes"), r / 1048576.0);
g_printerr("%-37s: %12d\n", _("Size of xhash"), nHashSize);
g_printerr("%-37s: %12s %s\n", _("Reuse old bearoff database"), szOldBearoff ? _("yes") : _("no"),
szOldBearoff ? szOldBearoff : "");
/* initialise old bearoff database */
if (szOldBearoff && !(pbc = BearoffInit(szOldBearoff, BO_NONE, NULL))) {
g_printerr(_("Error initialising old bearoff database!\n"));
exit(2);
}
/* bearoff database must be of the same kind as the request one-sided
* database */
if (pbc && (pbc->bt != BEAROFF_TWOSIDED || pbc->fCubeful != fCubeful)) {
g_printerr(_("The old database is not of the same kind as the" " requested database\n"));
exit(2);
}
generate_ts(nTSP, nTSC, fHeader, fCubeful, nHashSize, pbc, outfile);
/* close old bearoff database */
BearoffClose(pbc);
g_printerr(_("Number of re-reads while generating: %ld\n"), cLookup);
}
fclose(outfile);
return 0;
}