Computer Chess Club Archives


Search

Terms

Messages

Subject: Re: Nalimov's EGTBs (long post with code)

Author: Heiner Marxen

Date: 06:05:01 10/13/01

Go up one level in this thread


On October 13, 2001 at 07:52:57, José Carlos wrote:

>  Recently I posted a message asking for some kind of explanation on
>implementing Nalimov's EGTB's in chess programs.
>  I was suggested to read probe.txt from Hyatt's ftp and some chess programs'
>sources.
>  I'm starting to believe that I'm too stupid, because most programmers include
>EGTB's with no problem, and I'm having a hard time trying to understand how to
>do it. If that is the case, just tell me "you're too stupid to do the job" and
>I'll give up :)
>  Otherwise, if anyone has written some step-by-step guide, I'd even pay for it.
>Eugene told me he has no time now to help me, and probe.txt is too vague for me
>(not explicit enough). Also, I've seen no program that works the way probe.txt
>suggests, which confuses me more. And all those programs are bitboard-based,
>which makes it more difficult for me to uderstand what they're doing (I'm 0x88).
>  So, pleaseeeeeeeeeeeeeeeeee, help me.
>
>  José C.

No, you are not stupid, and no, not everybody else has just no problems.
I had some problems and had to investigate some time to really understand,
what is going on.  I'll tell you what I remember:

After comparing the crafty version of the access code (egtb.cpp) with the
version from Eugene (tbindex.cpp), I decided to adapt the crafty version.
It appeared to be a bit cleaner.  My notes say:

Copied from Crafty 18.10 on Jul-18-2001

- Renamed "egtb.cpp" --> "tbindex.cpp"
- Checked into RCS
- deleted the first 51 lines in "tbindex.cpp", which are added for Crafty
  (they correspond to our "egtbdefs.h").
- added RCS comment at top
- (new) line 3193ff: make TB_CRC_CHECK a variable, again.

- tbdecode.h: lines 79-82: deleted terminating CNTL-M

To avoid some gcc-warnings:
- commenting out 2 unused vars
- commenting out unused function: `int TbtProbeTable(int, color, INDEX)'
- augmenting the TB-macro to make the sizes (unsigned long).


Since I want the rest of my program still to be ANSI-C (as opposed to C++),
I tried to seperate the C++ part as unchanged as possible from the rest.
I also have done several tries to download and proceed (crafty 17.07 & 18.10),
and finally did a careful compare of all the snippets I had.
I copy my notes below, and hope not to confuse you:


Date:           Sat Feb 19 16:33:26 MET 2000
Author:         Heiner Marxen
Subject:        Chess Endgame Tables by Eugene Nalimov
Reference:      EGTB/probe.txt & EGTB/tbindex.cpp
$Id: egtb.hm,v 1.2 2001/08/17 01:38:14 heiner Exp $
$Source: /home/heiner/ca/chest/RCS/egtb.hm,v $


Additional comments by Heiner:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The value returned by the probing function "L_TbtProbeTable()"
returns an "int".  Its meaning is defined by the manifests
whose names start with "L_bev_":

L_bev_broken            (32767  max val 16 bit signed)
    No value could be obtained.  Either the table is not
    there, or the index does not code a legal position.

L_bev_draw              (0)
    The side to move can neither win, nor is it lost.

L_bev_mi1               (32766  max non-broken)
    Side to move can mate in one move/ply.
    Further mate in N reduce this value by one for each
    move (2 plies), e.g. 32765 == mate in 2

L_bev_mimin             (1)
    The code for the deepest codable mate.
    Mate in 32766.

L_bev_li0               (-32766)
    The side to move is already mate.  You are lost now.
    Mated in 0 moves.
    For mated in N moves this value is increased by one
    for each further move (2 plies), e.g. -32765 codes
    "mated in 1", i.e. whatever you do, a mate in at most
    1 move will follow.

L_bev_limax             (-1)
    The code for the deepest codable loss.
    Mated in 32765 (mate in 32765 will follow).

FFS: how does this relate to ce-values from EPD?


The variable informing about a possible e.p. (sqEnP) points to
the potential target position of an e.p. capture (the empty square
which the other pawn just skipped over).  This is the same
convention as in EPD, but differs from the coding of CHEST,
which notes the square of the pawn that can be captured.
Chest e.p. target lines:        4 & 5
EGTB  e.p. target lines:        3 & 6


Conditions to be checked:
- whether our codes for piece types (Figure) plus one yield the EGTB coding,
  which uses 1=pawn ... 6=king
- whether our Pos64 coding corresponds exactly to the EGTB coding,
  which uses 0=a1, 1=b1 ... 63=h8.
  It is sufficient to verify the code for b1:  MK_POS64(1,0) == 1


Later, I would like to also have table bases for "stalemate"  and "helpmate",
not just for "mate".  In that case we would have to distinguish two sets
of files & params.  Ouch.

===============================================================================
Sources which I have seen for the EGTB probing code for Nalimov tables,
and how they do compare.

Crafty 17.7
    tbdecode.h          05-Jan-2000      25587
    egtb.cpp            21-Jan-2000     159410

Crafty 18.10
    tbdecode.h          07-Jun-2001      25670
        Comparing to crafty 17.7:
        - C18.10 makes var cbEGTBCompBytes usable from ANSI-C.
          This is not relevant while using this from the C++ source, only.
    egtb.cpp            11-Apr-2001     159464
        Comparing to crafty 17.7:
        - C18.10 defines T41_INCLUDE and T33_INCLUDE (in initial section)
        - C18.10 has more cases to define "lock_t"
        - C18.10 has more "%ld" corrections
        - C18.10 has a corrected entry for krrkrn (!)
    Summary: this is slighly better and correcter than Crafty 17.7

tbgen.zip (downloaded   06-Aug-2001      51718  from crafty ftp)  ca/TB/Gen
    tbindex.cpp         13-Dec-1998     139395
    "tbdecode" is missing.  Appears to be really outdated.

EGTB    (from tbgen.zip 20-Jan-2000      79851  from crafty ftp)
    tbdecode.c          18-Feb-2000      25585
        Comparing to crafty 17.7:
        - C177 has 1 var-init more: line 562: s=0.  Is not really needed.
    tbindex.cpp         18-Feb-2000     143406
        Comparing to crafty 17.7:
        - C177 has 49 leading lines
        - C177 has i59
        - C177 has EXCLUDE5
        - C177 has typos in comments corrected
        - C177 has lots of %ld in format strings
        - C177 has code for T33_INCLUDE (class T33)
        - C177 includes "tbdecode" as .h instead of as .c
        - C177 uses "lockDecode" instead of "LockDecode" (portability fix)
        - C177 #defs TB_CRC_CHECK 0 (was a variable)
        - C177++:       #define       PFNCALCINDEX_DECLARED
        - C177 has 2 more bools in TB macro (and descriptors): fSym, f16bit
        - C177 has extended code for fSym
        - C177 has 3990c3693: bugfix: if (0 == cb)      is correct
          (confirmed by e-mail from Eugene via Dann)
        - C177 has wrapped the main probing function, has new TbtProbeTable()
    Summary: Crafty17.7 is much better

EGTB17.07       (modified based on Crafty 17.7)
    This is, what I have done, based on Crafty 17.7:
    tbdecode.h          -                25587  (unchanged)
    tbindex.cpp         05-Mar-2000     158357  deleted first 49 lines (by Bob)
                                                replaced line 3188 by 2996
                                                TB_CRC_CHECK variable
-------------------------------------------------------------------------------
Organization:
    The EGTB* directories are alternate sources.
    Chest itself does use copies one directory level up, i.e. where all
    the other sources are.  This is kind of an "install" step, but also
    a version organization.

Roadmap:
    - We start with the version from Crafty 18.10, since it appears to be
      the most recent and correct one.
    - We delete the initial part of "tbindex", added for crafty.
    - We call it "tbindex.cpp" (unlike crafty), since it is intended
      to be as unchanged as possible from future versions.
    - We construct an embedder "egtbembed.cpp", which provides what
      crafty inserted at the start, and then inserts Eugenes probe code.
      The provisions are located in "egtbdefs.h", which includes
      "bastyp.h", both of which must be usable for C and C++.
    - "egtbembed.h" is the export interface of "egtbembed.cpp",
      but written in ANSI-C, usable for the caller.
      FFS: fopen-failure and wrapping
    - We write "egtbif.c", our own way to interface table bases.

Modules:
    egtbembed.cpp                               C/C++
        - using         egtbdefs.h              C/C++
        - including       bastyp.h              C/C++
        - including         basdef.h            C/C++
        - including         lcltyp.h            C/C++
        + including     tbindex.cpp             C++
        + including       tbdecode.h            C/C++
    egtbembed.h                                 C       export interface

    egtbif.c & egtbif.h                         C
        Implements Heiner's interface to EGTBs.
        Maps to whatever is below.

**** end of cite ****

Up to now not much happened:  tbdecode.h is unchanged, and egtb.cpp is
renamed tbindex.cpp, with crafty special code at the top deleted and some
minor changes in the rest.

Now I had to do the embedding.  I just copy from my commented sources:

/*
 * CHEST, chess analyst.  For Copyright notice read file "COPYRIGHT".
 *
 * $Source: /home/heiner/ca/chest/RCS/egtbdefs.h,v $
 * $Id: egtbdefs.h,v 1.2 2001/08/17 01:37:55 heiner Exp $
 *
 *      WARNING:        We want to use this header file for ANSI-C and C++.
 *
 *      Here we prepare the usage of "tbindex.cpp",
 *      as explained in "EGTB/probe.txt".
 *
 *      We decide some configuration manifests,
 *      and provide some demanded typedefs and macros.
 *      Part of this is also used (needed) in the ANSI-C part
 *      accessing the C++ code, and therefore this file is
 *      part of the export interface "egtbembed.h".
 */

#ifndef CHEST_egtbdefs_h_INCLUDED
#define CHEST_egtbdefs_h_INCLUDED

/*---------------------------------------------------------------------------*/
/*
 * We have to declare some integral types and need to think about
 * the amount of bits that fit into them.
 * Therefore we would like to include "bastyp.h", or at least "lcltyp.h",
 * in order to access types with a guaranteed width.
 * FFS: We hope "bastyp.h" is completely C++ compatible.
 */

#include "bastyp.h"                     /* types with known bit width */

/*
 * The type INDEX must be declared here.
 * 32 bit (unsigned) values shall fit into it,
 * which is enough for 5-piece tables.
 * This type is used to compute the byte index into the EGTB-files.
 * FFS: for 6-piece tables we need a wider type, but also an
 *      OS which offers large enough files (size > 4GB).
 */
typedef uint32          INDEX;          /* shall hold 32 bit */

/*
 * The type square has to be declared here.
 * It may be any integer type, preferably a fast one.
 * Crafty even uses an unsigned one.  An unsigned type may have
 * the advantage that index calculations are done easier, since
 * they logically are unsigned.
 * Also, the way we code our square arrays for the probing function,
 * we want the maximum number of equal pieces to fit into such a value
 * simultaneously.  With 5-piece tables we need 3*6 = 18 bit,
 * and with 6-piece tables we need 3*4 = 24 bit.
 */
typedef uint32          square;         /* shall hold 24 bit */

/*---------------------------------------------------------------------------*/
/*
 * Some configuration manifests, which we decide.
 * Except for XX they are just defined, and carry no value.
 *      NEW             use Nalimov tables, not the old SJE tables
 *      XX              square value for sqEnP when e.p. is not possible.
 *                      must be outside 0..63
 *      T41_INCLUDE     we want 4+1 tables (5 pieces, rarely useful)
 *      T33_INCLUDE     we want 3+3 tables (6 pieces, useful but large)
 *                      (Crafty uses this together with 32 bit INDEX type)
 *      DEBUG           more code, for testing
 *                      Do not define empty (tbdecode soen't like that)
 *                      undef: no debugging in tbindex or tbdecode
 *                      0    : no debugging in            tbdecode
 *                      1    :    debugging in both, but tbdecode
 *                                redefines "assert" (that's not clean)
 * FFS: these should be changable from the Makefile.
 */

#define NEW             /* we want these Nalimov tables */
#define T41_INCLUDE     /* we want them (except memory is low) */
#define T33_INCLUDE     /* we want them (except memory is low) */
#define DEBUG   0       /* FFS: PROD_LEV? */
#define XX      127     /* arbitrary value > 63 (crafty uses 127) */

/*---------------------------------------------------------------------------*/
/*
 * We have to hand two arrays to the table base probing function,
 * containing the locations of the pieces.  But we are free
 * to organize them as we like, since we here have to provide
 * access macros, which are used by the probing code included below.
 * Here is their functional interface, demanded by the probing code:
 *       SqFindKing   (square *)
 *                              return location of the king,
 *       SqFindFirst  (square *, piece),
 *       SqFindSecond (square *, piece),
 *       SqFindThird  (square *, piece)
 *                              find location of the first/second/third
 *                              piece of the given color,
 *       SqFindOne (square *, piece)
 *                              (hopefully) more efficient variant of
 *                              SqFindFirst(); it's called when there is
 *                              exactly one piece of the given color
 *                              (plus the king, of course) on the board.
 * The "piece" above corresponds to the "x_piece*" constants in the
 * probing code.  The documentation fixes them: 1=pawn ... 6=king
 *
 * We use a coding trick that makes building up the location array
 * more easy:  we code all locations of one piece type into one (!)
 * square value, using 6 bits for each.  See comment above.
 * Note that the XX value does not occur here, it only occurs as special
 * argument to the function "PfnIndCalcFun()".
 *
 * We could map the EGTB piece codes down to 0.
 * But that would slow down the probe code a bit,
 * and so we burden the caller with mapping his codes to 1=P .. 6=K
 * NB:  Although just now the "x_pieceKing" is not defined,
 *      it will be defined by the time the macro below is used,
 *      either from "tbindex.cpp" or from "egtbembed.h"
 */

#define SqFindFirst(sqp, pi)    ( (sqp)[ pi ]        & 077)
#define SqFindSecond(sqp, pi)   (((sqp)[ pi ] >>  6) & 077)
#define SqFindThird(sqp, pi)    (((sqp)[ pi ] >> 12) & 077)
#if 0           /* works only for non-trick coding */
# define SqFindOne(sqp, pi)     ( (sqp)[ pi ])
#else           /* needed for trick coding */
# define SqFindOne(sqp, pi)     SqFindFirst(sqp, pi)
#endif
#define SqFindKing(sqp)         SqFindOne(sqp, x_pieceKing)

/*---------------------------------------------------------------------------*/
/*
 * Thread safety... is not needed for CHEST.  It is strictly sequential.
 */

/*
 * The following manifests can be defined to avoid the default:
 * TB_CB_CACHE_SIZE     # of bytes in one TB cache chunk;  Changing it will
 *                      render downloaded compressed TBs useless.
 * TB_DIRECTORY_SIZE    (default 32) #(LRU chains for one TB)
 */

#endif  /* ndef CHEST_egtbdefs_h_INCLUDED */




/*
 * CHEST, chess analyst.  For Copyright notice read file "COPYRIGHT".
 *
 * $Source: /home/heiner/ca/chest/RCS/egtbembed.h,v $
 * $Id: egtbembed.h,v 1.2 2001/08/17 01:37:20 heiner Exp $
 *
 *      This is the ANSI-C export interface of "egtbembed.cpp"
 *      (which in turn includes "tbindex.cpp").
 */

#ifndef CHEST_egtbembed_h_INCLUDED
#define CHEST_egtbembed_h_INCLUDED

/*
 * Part of the definitions & types provided for the probe code
 * is also needed here by the caller/user of the probe code.
 * It is written such that it can be used in both, ANSI-C and C++.
 */
#include "egtbdefs.h"


/*---------------------------------------------------------------------------*/
/*
 * This section is is (mostly) copied from "EGTB/probe.txt",
 * and edited for readability.
 * The defines for "color" and "piece" are the same as in "tbindex.cpp".
 */

#if defined (_MSC_VER)
# define TB_FASTCALL    __fastcall
#else
# define TB_FASTCALL
#endif

typedef int             color;
#define x_colorWhite    0
#define x_colorBlack    1
#define x_colorNeutral  2

typedef int             piece;
#define x_pieceNone     0
#define x_piecePawn     1
#define x_pieceKnight   2
#define x_pieceBishop   3
#define x_pieceRook     4
#define x_pieceQueen    5
#define x_pieceKing     6

#define L_pageL 65536
#define L_tbbe_ssL      ((L_pageL - 4) / 2)
#define L_bev_broken    (L_tbbe_ssL + 1)  /* illegal or busted */
#define L_bev_mi1       L_tbbe_ssL        /* mate in 1 move */
#define L_bev_mimin     1                 /* mate in 32766 moves */
#define L_bev_draw      0                 /* draw */
#define L_bev_limax     (-1)              /* mated in 32765 moves */
#define L_bev_li0       (-L_tbbe_ssL)     /* mated in 0 moves */

typedef INDEX (TB_FASTCALL * PfnCalcIndex)
                (square*, square*, square, int fInverse);

/*
 * IDescFindFromCounters (int *) - that function returns TB #. It takes as an
 * input the 10-element integer array - it's zeroth element is number of white
 * pawns on the board, first - number of white knights, ..., nineth - number of
* black queens. It returns zero if no such TB registered, TB #, if TB found,
 * or negative TB # if you asked for TB with 'wrong' colors, e.g. KPKRP.
 * WARNING: that function will return non-zero even if only one of the pair of
 * the corresponding TBs registered (e.g. KRK.WTM, but not KRK.BTM). You can use
* FRegistered() function for more precise information - see example.
 * Also, you can write your own function that will return TB #, if you prefer
 * to do so. Just look at the IDescFindFromCounters source.
 */
extern int              IDescFindFromCounters (int*);

/*
 * FRegisteredFun(iTb, side) - function that returns true if corresponding TB
 * was found during initializing.
 */
extern int              FRegisteredFun (int, color);

/*
 * PfnIndCalc(iTb, side) - function that returns function that calculate index
 * of the position in the TB.
 */
extern PfnCalcIndex     PfnIndCalcFun (int, color);

/*
 * L_TbtProbeTable (int iTb, color side, INDEX index) - probe TB and return
 * either result or 'L_bev_broken' if some error happened. If specified TB was
 * loaded to memory, function will return result immediately. Otherwise it will
 * try to find needed value in TB cache; in the worst case it have to read chunk
* of a file from disk.
 */
extern int TB_FASTCALL  L_TbtProbeTable (int, color, INDEX);

#if 0           /* unused FFS */
/*
 * The following two macros are defined in C++ and are not accessible
 * here, in ANSI-C.  We here let them call the functions.
 */
# define FRegistered    FRegisteredFun
# define PfnIndCalc     PfnIndCalcFun
#endif


/*
 * IInitializeTb (char *path) - initialize data structures, parse specified
 * directories and register all tablebases that are there. Path consists of
 * several directory names seperated by commas, semicolons, or (at non-Windows
 * systems) by colons. Directories are searched in the specified order.
 * Function returns # of pieces in the "largest" tablebase it found - e.g. 5 if
 * it found KRPKR, 4 if it found KBNK, 3 if it found KRK, 0 if no tablebases
 * found. You can call this function several times if you want to change
 * directories. In that case it will "forget" previously found tablebases.
 * Please note that in it will not "forget" tablebases that were read or mapped
 * to memory.
 */
extern int      IInitializeTb (char* path);

extern int      TB_CRC_CHECK;           /* used   by IInitializeTb() */
extern int      cbEGTBCompBytes;        /* filled by IInitializeTb() */

/*
 * FTbSetCacheSize (void *buffer, ULONG size) - set buffer for TB caching.
 * Returns false if buffer is too small. You can call this routine as many times
* as you want. Pass 0 as size to turn caching (and probing of all non-loaded-
 * to-memory TBs) off. You can call that function as many times as you want.
 * [NB: no automatic/default caching]
 * [ The memory passed is still owned by the caller.  EGTB stops to use
 *   it when this function is called with another set of values. ]
 */
extern int      FTbSetCacheSize (void* buffer, unsigned long size);

/*
 * VTbCloseFiles (void) - close all open TB files; for performance reasons, code
* tries to keep opened as much TB files as possible. On some systems that can
 * cause problems when chess program tries to open file for its own purposes.
 * So, if fopen() in your code returns NULL, you can call that function and then
* try again.
 */
extern void     VTbCloseFiles (void);

/*---------------------------------------------------------------------------*/

#endif  /* ndef CHEST_egtbembed_h_INCLUDED */



/*
 * CHEST, chess analyst.  For Copyright notice read file "COPYRIGHT".
 *
 * $Source: /home/heiner/ca/chest/RCS/egtbembed.cpp,v $
 * $Id: egtbembed.cpp,v 1.2 2001/08/17 01:37:33 heiner Exp $
 *
 *      This file is written in C++, not in ANSI-C.
 *      It embeds the endgame table base probe code from
 *      Eugene Nalimov, which is written in C++, and needs
 *      some parameterization and an interface.
 *
 *      First, we decide some configuration manifests,
 *      and provide some demanded information.
 *      Then, we include the table base probing code (!).
 *      After that we check some assumptions for sanity,
 *      and may add some support code which must be located
 *      within the C++ world as there is no export interface to C.
 *
 *      The resulting export interface ("egtbif.h") is NOT included here,
 *      since it is in C, and not in C++.
 */

#include "egtbdefs.h"                   /* also used for "egtbif.h" */

/*===========================================================================*/

#include "tbindex.cpp"          /* !! */

/*===========================================================================*/
/*
 * Sanity check for assumptions made elsewhere ("egtbif.h").
 */

#if                x_piecePawn   != 1
# include "/// >>> x_piecePawn   != 1 <<< ///"
#endif
#if                x_pieceKnight != 2
# include "/// >>> x_pieceKnight != 2 <<< ///"
#endif
#if                x_pieceBishop != 3
# include "/// >>> x_pieceBishop != 3 <<< ///"
#endif
#if                x_pieceRook   != 4
# include "/// >>> x_pieceRook   != 4 <<< ///"
#endif
#if                x_pieceQueen  != 5
# include "/// >>> x_pieceQueen  != 5 <<< ///"
#endif
#if                x_pieceKing   != 6
# include "/// >>> x_pieceKing   != 6 <<< ///"
#endif



Now, compiling "egtbembed.cpp" yields a version of Eugenes code adapted
to the decisions I have made for Chest (in egtbdefs.h).

So I still have to write the interface between his code and mine.
I have looked carefully at the crafty code, and have come up with
something similar.  I show the relevant part of it, init & probe:


    Eximpl Bool
eg_init(void)
{
    Bool        ok;

    ok = TRUE;
    if( ! egtb__inited ) {
        egtb__inited = 1;               /* we are comitted */
        eg_stat_clear();
        if( ! egtb__conf_ok() ) {
            printf("%% EGTB config error, disabled\n");
            egtb__maxcnt = 0;
            return FALSE;
        }
        /*
         * Before we can hand him any cache memory, we MUST initialize
         * him, since otherwise he does not know how to use it, yet.
         */
#if 0                                   /* CF FFS */
        TB_CRC_CHECK = 0;               /* disable CRC checking */
#else
        TB_CRC_CHECK = 1;               /*  enable CRC checking */
#endif
        egtb__maxcnt = IInitializeTb(egtb__path);
        if( egtb__maxcnt ) {
            printf("%% EGTB found tables for max %d pieces\n", egtb__maxcnt);
        }
        if( cbEGTBCompBytes ) {
            egtb__allo2 = cbEGTBCompBytes;
            printf("%% EGTB uses %.1fK memory internally\n",
                    (double)cbEGTBCompBytes / 1024.);
        }
        if( egtb__maxcnt > 0 ) {        /* some tables found */
            if( ! egtb__want ) {
                egtb__want = EGTB_DFT_SIZE;
            }
            if( egtb__want ) {
                if( ! egtb_try_allo(egtb__want) ) {     /* failed... */
                    if( egtb__want > EGTB_DFT_SIZE ) {  /* try smaller dft */
                        (void) egtb_try_allo(EGTB_DFT_SIZE);
                    }
                }
            }
            if( egtb__cache ) {         /* got some memory */
                ok = FTbSetCacheSize(egtb__cache, egtb__allo);
            }else {
                ok = FALSE;             /* got no memory */
            }
            if( ! ok ) {
                /*
                 * Somehow, we did not succeed.
                 * Hence, he will not probe at all: forget about it!
                 * FFS: use EGTB even without a cache?  Currently not.
                 */
                egtb_free();
                egtb__maxcnt = 0;       /* we will not ask anymore */
                printf("%% EGTB failed to setup cache: disabled\n");
            }
        }
    }
    return ok;
}



/*===========================================================================*/
/* Probing section.
 */

/*
 * EG_map_color         maps our Chest colour code to EGTB code.
 * EG_opp_color         switches EGTB code.
 * EG_map_fig           maps our figure codes to EGTB code.
 */

#if  defined(white) && defined(x_colorWhite)
# if defined(black) && defined(x_colorBlack)
#  if (white ^ x_colorWhite) == (black ^ x_colorBlack)
#   define EG_map_color(c)      ((c) ^ (white ^ x_colorWhite))
#  endif
# endif
#endif
#ifndef  EG_map_color
# define EG_map_color(c)        (((c) == white) ? x_colorWhite : x_colorBlack)
#endif
#define EG_opp_color(c)         ((c) ^ (x_colorWhite ^ x_colorBlack))


/*
 * We expect an offset between our codes and his codes.
 */
#define EG_off_fig              (x_piecePawn - bauer)
#define EG_map_fig(f)           (EG_off_fig + (f))              /* intended */

#if 1   /* constructed by Util/EG_map_fig.sh */
# if  x_piecePawn               != (EG_off_fig + bauer)
#  undef EG_map_fig
# endif
# if  x_pieceKnight             != (EG_off_fig + springer)
#  undef EG_map_fig
# endif
# if  x_pieceBishop             != (EG_off_fig + laeufer)
#  undef EG_map_fig
# endif
# if  x_pieceRook               != (EG_off_fig + turm)
#  undef EG_map_fig
# endif
# if  x_pieceQueen              != (EG_off_fig + dame)
#  undef EG_map_fig
# endif
# if  x_pieceKing               != (EG_off_fig + koenig)
#  undef EG_map_fig
# endif
# if  x_piecePawn       >= 8
#  include "/// >>> EGTB piece code too large <<< ///"
# endif
# if  x_pieceKnight     >= 8
#  include "/// >>> EGTB piece code too large <<< ///"
# endif
# if  x_pieceBishop     >= 8
#  include "/// >>> EGTB piece code too large <<< ///"
# endif
# if  x_pieceRook       >= 8
#  include "/// >>> EGTB piece code too large <<< ///"
# endif
# if  x_pieceQueen      >= 8
#  include "/// >>> EGTB piece code too large <<< ///"
# endif
# if  x_pieceKing       >= 8
#  include "/// >>> EGTB piece code too large <<< ///"
# endif
#endif

#ifndef EG_map_fig
# include "/// >>> not yet adapted piece code mapping <<< ///"
#endif


    Eximpl EgQual
eg_probe( register const Board* bp )
{
    int                 cntarr[10];
    int                 iTb;
    color               side;
    int                 fInvert;
    register rColour    c;              /* our colour */
    register rPosition  pos;
    register int        ifig;
    register int        minifig;
    register square*    p;
    square              sqarr[2][8];    /* [c][x_piece] positions */
    square              sqep;           /* e.p. target or XX */
    square*             psqW;
    square*             psqB;
    INDEX               ind;
    int                 v;
    EgQual              q;

    scinc(egsc.prob_called);
    XDB(1, printf("> eg_probe "); put_fen_eng(bp); )
    if( ! egtb__inited ) {
        /*
         * The init function will set us a real maxcnt: re-check!
         */
        if( ! eg_init()
         || ! EGA_CAN_CNTS(bp->b_cur_piece[white], bp->b_cur_piece[black]) ) {
            XDB(1, printf("< eg_probe dunno\n"); )
            scinc(egsc.prob_broken);
            return EGQ_DUNNO;           /* table missing */
        }
    }
    /*
     * First, we have to characterize the wanted table,
     * by filling a counter array with the number of white/black
     * pieces of the five different kinds.  The ten indexes
     * have hardwired meaning: wP, wN, wB, wR, wQ; bP, bN, bB, bR, bQ.
     * Unfortunately, that is one array and not two, and its type is int.
     * We have to copy around.
     */
#define EG_fill__5cnt(arr,off, bp,c)                            \
                ( ((arr)[(off)+0] = (bp)->b_fig_cnt[c][bauer   ]), \
                  ((arr)[(off)+1] = (bp)->b_fig_cnt[c][springer]), \
                  ((arr)[(off)+2] = (bp)->b_fig_cnt[c][laeufer ]), \
                  ((arr)[(off)+3] = (bp)->b_fig_cnt[c][turm    ]), \
                  ((arr)[(off)+4] = (bp)->b_fig_cnt[c][dame    ])  )

    EG_fill__5cnt(cntarr,0, bp,white);
    EG_fill__5cnt(cntarr,5, bp,black);
    XDB(2,  int i;
            printf("= eg_probe cntarr=");
            for( i=0 ; i<10 ; ++i ) printf("%d,", (int) cntarr[i]);
            printf("\n");
    )

    iTb = IDescFindFromCounters(cntarr);
    XDB(1, printf("= eg_probe iTb=%d\n", iTb); )
    if( iTb == 0 ) {
        if( (bp->b_cur_piece[white] + bp->b_cur_piece[black]) == 2 ) {
            scinc(egsc.prob_draw);
            XDB(1,  printf("< eg_probe draw by kk\n"); )
            return EGQ_DRAW;
        }
        XDB(1, printf("< eg_probe dunno\n"); )
        scinc(egsc.prob_nopair);
        return EGQ_DUNNO;               /* table missing */
    }
    /*
     * At least one of the pair wtm/btm is registered.
     * If the returned "iTb" is negative, a table is registered,
     * but it has the sides the other way round.  We must than
     * pass the white pieces as black, and the black pieces as white,
     * and adjust the side to move, accordingly.
     * The coordinate flipping up/down will he do, as we pass him
     * the "fInvert", also.
     */
    side = EG_map_color(bp->b_tomove);
    if( iTb > 0 ) {             /* directly: our white --> x_colorWhite */
        if( ! FRegisteredFun(iTb, side) ) {
            XDB(1, printf("< eg_probe notab\n"); )
            scinc(egsc.prob_notab);
            return EGQ_DUNNO;           /* table missing */
        }
        fInvert = FALSE;
        psqW    = sqarr[white];         /* filled below */
        psqB    = sqarr[black];         /* filled below */
    }else {                     /* inverted: our white --> x_colorBlack */
        side    = EG_opp_color(side);
        iTb     = - iTb;
        if( ! FRegisteredFun(iTb, side) ) {
            XDB(1, printf("< eg_probe notab\n"); )
            scinc(egsc.prob_notab);
            return EGQ_DUNNO;           /* table missing */
        }
        fInvert = TRUE;
        psqW    = sqarr[black];         /* filled below */
        psqB    = sqarr[white];         /* filled below */
    }
    XDB(2,  printf("= eg_probe fInvert=%d\n", (int)fInvert); )
    /*
     * Ok, the table is there, and we know whether to invert w/b.
     * Independantly of that decision, we now fill two arrays
     * with the locations of the pieces.  These are passed to the
     * EGTB-probe function, but that code will access them exclusively
     * via the SqFind-Macros, which WE designed in "egtbdefs.h".
     * We need not even try to initialize anything, here, but rather
     * shift out the old values.  The access macros correctly
     * mask out the wanted values.
     */
    XDB(3,  for( c=0 ; c<2 ; ++c ) {
                    int i;
                for( i=0 ; i<8 ; ++i ) sqarr[c][i] = 0;
            }
    )
    for( c=0 ; c<2 ; ++c ) {
        minifig = COLOUR_IDX(c);
        ifig    = minifig + bp->b_max_piece[c] - 1;     /* max (inclusive) */
#if KING_IDX == 0       /* FFS: kings out of colour loop */
        minifig += 1;
        pos = bp->b_piece[ K_IDX(c) ];
        sqarr[c][ EG_map_fig(koenig) ] = POS_POS64(pos);
#endif
        for( ; ifig >= minifig ; --ifig ) {
            pos = bp->b_piece[ifig];
            if( no_pos(pos) )
                continue;               /* skip empty slot */
            p = &( sqarr[c][ EG_map_fig(bp->b_f[pos].f_f) ] );
            *p = (*p << 6) | bp->b_f[pos].f_pos64 /*FFS: POS_POS64*/;
        }
    }
    XDB(2,  printf("= eg_probe sqarr:\n");
            for( c=0 ; c<2 ; ++c ) {
                    int i;
                    int j;
                for( i=1 ; i<=6 ; ++i ) {
                    printf(" [%d][%d] = %11lo",
                            c, i, (long)sqarr[c][i]);
                    j = ((i==6) ? 1 : cntarr[(i-1) + 5*c]);
                    if( j<0 || j>11 ) j = 11;
                    if( j ) {
                        printf(" = %11.*lo",
                            2*j,
                            (long)(sqarr[c][i] & (((square)1 << (6*j)) - 1))
                        );
                    }
                    printf("\n");
                }
            }
    )
    /*
     * Additional info about e.p. must be exported to EGTB, also...
     */
    pos = bp->b_ep;
    if( no_pos(pos) ) {
        sqep = XX;
    }else {
        scinc(egsc.prob_ep);
        pos += bau_mov[bp->b_tomove];   /* map passive --> active target */
        sqep = MK_POS64(COL(pos), LIN(pos));
    }
    XDB(2,  printf("= eg_probe sqep=%lo\n", (long)sqep); )
    scinc(egsc.prob_done);
    /*
     * OK, everything together.
     * Go compute the index, and probe the table...
     */
    ind = PfnIndCalcFun(iTb, side) (psqW, psqB, sqep, fInvert);
    XDB(2,  printf("= eg_probe ind=%lu\n", (long)ind); )
    v = L_TbtProbeTable(iTb, side, ind);
    XDB(1,  printf("= eg_probe v=%d\n", (int)v); )
    /*
     * Now, we must translate back that value into a value,
     * which we understand...
     */
    if( v == L_bev_broken ) {
        scinc(egsc.prob_broken);
        q = EGQ_DUNNO;
    }else if( v == L_bev_draw ) {
        scinc(egsc.prob_draw);
        q = EGQ_DRAW;
    }else if( v > 0 ) {                 /* wins */
        /*
         * Deeper wins have smaller values.
         */
        scinc(egsc.prob_wins);
        q = EGQ_WINS_Q(1 + (L_bev_mi1 - v));
    }else {                             /* lost */
        /*
         * Deeper losses have larger values (less negative).
         */
        scinc(egsc.prob_loss);
        q = EGQ_LOST_Q(v - L_bev_li0);
    }
    XDB(1,  printf("< eg_probe q=%d\n", (int)q); )
    return q;
}



Ok, that's it.  It appears to work for me, although I have not yet performed
any extensive test set.  Does anyone have an EPD collection for testing
an EGTB implementation (with only 3 & 4 piece tables installed) ?
I'd like to get it :-)


All of the above will be part of the next release of Chest.  Version 3.19
did not yet have it.

Phew.  My longest post, so far.  I hope it helps.

Cheers,
Heiner



This page took 0 seconds to execute

Last modified: Thu, 15 Apr 21 08:11:13 -0700

Current Computer Chess Club Forums at Talkchess. This site by Sean Mintz.