Source Code

Forum Archive : Source Code

 
race4.c

From:   Marc Ringuette
Address:   mnr@CS.CMU.EDU
Date:   10 December 1993
Subject:   Re: Analysis of Non-Contact Endgames (Long)
Forum:   rec.games.backgammon
Google:   CHt2AH.D53.1@cs.cmu.edu

Here are two programs which may interest you, by my friend David
Applegate.  The first, "race4.c", computes the expected rolls to
bear off for a single player.  The second, "race2.c", is even cooler:
it computes the exact winning probability and correct cube action
for a given position for both players given no contact.  Both programs
exhaustively compute answers which are guaranteed correct.

These are computationally intensive tasks, but the programs are speedy
and use a hashtable to cache results for later.  Of course, race2.c,
because it considers both sides of the board, can't handle as many checkers
on the board. Try them on small positions and then work up to ones with
more checkers.  Both are in vanilla C for Unix.

Enjoy!

-- Marc Ringuette (mnr@cs.cmu.edu)


/* This program computes the expected number of rolls a player needs to bear
 * off.  Its usage is:
 *
 * race4 [-i hash_in] [-o hash_out] [-r roll] position
 *
 * where position is a sequence of numbers specifying the number of men on
 * point 1, 2, 3, ..., and roll is either one or two numbers between 1 and 6.
 *
 * race4 reads in an initial hash table, performs its evaluations, and then
 * writes out the new hash table.  hash_in and hash_out both default to
 * race4.hash.  It then prints out the expected number of rolls to bear off.
 * In addition, if a roll is specified, it prints the expected number of
 * rolls after each way of playing that roll, and then repeats the best play.
 *
 * An example:
 *
 * $ race4 -r 64 3 2 3 3 2 1 0 1
 * initial hash stats: entries 10616 overwrites 0
 * Eval <0 | 3 2 3 3 2 1 | 0 1 >...
 * .....................
 * Expected number of rolls = 7.926747; sigma = 0.961766
 * 8/6 4/4 = 6.842434 0.821341
 * 8/6 5/4 = 7.227027 0.803111
 * 8/6 6/4 = 7.230616 0.806788
 * 6/6 8/4 = 6.831183 0.905598
 * Best: 6/6 8/4 = 6.831183 0.905598
 * hash stats: queries: 17425595 hits 17302863 entries 19808 overwrites 113540
 *
 * This means that the position:
 *
 *   X   X X
 *   X X X X X
 *   X X X X X X   X
 *   - - - - - - - -
 * has an expected 7.93 rolls to bear off, with a standard deviation of .96,
 * and that the correct way to play a 6 4 is to move from the 8 to the 4,
 * and bear off the 6 point, resulting in 6.83 expected rolls remaining to
 * bear off.
 *
 * The position is limited to points 1 through 17.
 */

#include <stdio.h>
#include <sys/file.h>

#define NPIECES (15)
#define NSLOTS (32-15+1)
#define INNERSIZE (6)

#define BIGNMOVES (1e10)

typedef struct position {
    int maxloc;
    unsigned char npieces[NSLOTS];
} position;

int debug;
int depth = 0;
int quiet = 0;
char *hashin = "race4.hash";
char *hashout = "race4.hash";
int roll1 = 0, roll2 = 0;

#define DEPTH_INDENT ("                    "+20-depth)

double eval();
double pos_eval();
double best_move_val();
double best_val_double();
double best_val_notdouble();
double sqrt();

int hash_queries = 0;
int hash_hits = 0;
int hash_entries = 0;
int hash_overwrites = 0;

main (ac, av)
int ac;
char **av;
{
    position p;
    double val, variance;

    initialize();

    parseargs (ac, av, &p);

    if (hashin) {
        read_hash(hashin);
    }

    printf ("Eval <");
    print_pos (&p);
    printf (">...\n");

    val = eval(&p, &variance);
    printf ("\nExpected number of rolls = %f; sigma = %f\n",val, sqrt(variance));

    if (roll1) {
        list_moves (&p, roll1, roll2);
    }

    printf ("hash stats: queries: %d hits %d entries %d overwrites %d\n",
     hash_queries, hash_hits, hash_entries, hash_overwrites);

    if (hashout) {
     write_hash(hashout);
    } return 0;
}

/* eval
 *    Evaluate position. First look in hash table to see if it is there.
 *    Otherwise do the work.
 */
double eval (p, pvariance)
position *p;
double *pvariance;
{
    double val, var;
    unsigned int pos;

    hash_compress (p, &pos);
    if (!hashcheck (pos, &val, &var)) {
        if (debug) {
            printf ("%s<", DEPTH_INDENT);
            print_pos (p);
            printf (">\n");
        }
        depth++;
        val = pos_eval (p, &var);
        depth--;
        if (debug) {
            printf ("%s==> %f %f\n", DEPTH_INDENT, val, var);
        } hashstore (pos, val, var);
    } else {
        if (debug) {
            printf ("%s<", DEPTH_INDENT);
            print_pos (p);
            printf ("> ==> %f %f", val, var);
        }
    }
    *pvariance = var;
    return val;
}

/* pos_eval
 *        For position p, calculate its value and variance recursively.
 *        (For all 36 rolls, find the value and variance resulting from
 *        the roll, and combine them.)
 */
double pos_eval(p, pvariance)
position *p;
double *pvariance;
{
    double sum, mean, varsum, variance;
    int d1, d2;

    if (p->npieces[0] == NPIECES) {
        *pvariance = 0.0;
        return (double) 0.0;
    }
    sum = varsum = 0.0;
    for (d1 = 6; d1 >= 1; d1--) {
        sum += (mean = 1.0 + best_move_val (p, d1, d1, &variance));
        varsum += variance + mean * mean;
        if (depth == 1 && !quiet) {
            putchar ('.'); fflush (stdout);
        }
        for (d2 = d1+1; d2 <= 6; d2++) {
            sum += 2 * (mean = 1.0 + best_move_val (p, d1, d2, &variance));
            varsum += 2 * (variance + mean * mean);
            if (depth == 1 && !quiet) {
                putchar ('.'); fflush (stdout);
            }
        }
    }
    mean = sum/36.0;
    *pvariance = varsum / 36.0 - mean * mean;
    return mean;
}


/* best_move_val
 *        For position p and roll (d1,d2), d1 != d2, find the best move that
 *        can be made and its variance.
 */
double best_move_val (p, d1, d2, pvariance)
position *p;
int d1, d2;
double *pvariance;
{
    double v1, v2, variance1, variance2;

    if (d1 == d2) {
        return best_val_double(p, d1, pvariance);
    } else {
        v1 = best_val_notdouble(p, d1, d2, &variance1);
        v2 = best_val_notdouble(p, d2, d1, &variance2);
        if (v1 < v2)
        {   *pvariance = variance1;
            return v1;
        }
        else
        {   *pvariance = variance2;
            return v2;
        } /* return v1 < v2 ? v1 : v2; */
    }
}

/* best_val_double
 *        For position p and double-roll (d,d), find the best move that
 *        can be made and its variance.
 */
double best_val_double (p, d, pvariance)
position *p;
int d;
double *pvariance;
{
    int from1, from2, from3, from4;
    int to1, to2, to3, to4;
    double minmoves = BIGNMOVES;
    double val, variance;
    int maxloc = p->maxloc;

    *pvariance = 0.0;
    for (from1=0; from1<=maxloc; from1++) {
        if (domove (p, from1, d, &to1)) {
            for (from2=0; from2<=from1; from2++) {
                if (domove (p, from2, d, &to2)) {
                    for (from3=0; from3<=from2; from3++) {
                        if (domove (p, from3, d, &to3)) {
                            for (from4=0; from4<=from3; from4++) {
                                if (domove (p, from4, d, &to4)) {
                                    val = eval (p, &variance);
                                    if (val < minmoves) {
                                        minmoves = val;
                                        *pvariance = variance;
                                    }
                                    unmove (p, from4, to4);
                                }
                            }
                            unmove (p, from3, to3);
                        }
                    }
                    unmove (p, from2, to2);
                }
            }
            unmove (p, from1, to1);
        }
    } return minmoves;
}


/* best_val_notdouble
 *        For position p and roll (d1,d2), find the best move that
 *        can be made and its variance.
 */
double best_val_notdouble (p, d1, d2, pvariance)
position *p;
int d1, d2;
double *pvariance;
{
    int from1, from2;
    int to1, to2;
    double minmoves = BIGNMOVES;
    double val, variance;
    int maxloc = p->maxloc;

    *pvariance = 0.0;
    for (from1=0; from1<=maxloc; from1++) {
        if (domove (p, from1, d1, &to1)) {
            for (from2=0; from2<=from1; from2++) {
                if (domove (p, from2, d2, &to2)) {
                    val = eval (p, &variance);
                    if (val < minmoves) {
                        minmoves = val;
                        *pvariance = variance;
                    }
                    unmove (p, from2, to2);
                }
            }
            unmove (p, from1, to1);
        }
    } return minmoves;
}

domove (p, from, die, to)
position *p;
int from;
int die;
int *to;
{
    if (p->npieces[from] == 0) return 0;
    if (from < die && p->maxloc > from) return 0;
    if (from == die && p->maxloc > INNERSIZE) return 0;

    *to = from - die;
    if (*to < 0) *to = 0;
    p->npieces[from]--;
    p->npieces[*to]++;
    if (from == p->maxloc && p->npieces[from] == 0) {
        set_maxloc(p);
    }

    return 1;
}

unmove (p, from, to)
position *p;
int from;
int to;
{
    p->npieces[from]++;
    p->npieces[to]--;
    if (from > p->maxloc) p->maxloc = from;
}

list_moves (p, die1, die2)
position *p;
int die1, die2;
{
    if (die2 == 0) {
        list_moves_single (p, die1);
    } else if (die1 == die2) { list_moves_double (p, die1);
    } else { list_moves_notdouble (p, die1, die2);
    }
}

list_moves_double (p, d)
position *p;
int d;
{
    int from1, from2, from3, from4;
    int to1, to2, to3, to4;
    int maxloc = p -> maxloc;
    int best1, best2, best3, best4;
    double bestval = 1e10, val, var, bestvar;

    for (from1 = 0; from1 <= maxloc; from1++) {
        if (domove (p, from1, d, &to1)) {
            for (from2 = 0; from2 <= from1; from2++) {
                if (domove (p, from2, d, &to2)) {
                    for (from3 = 0; from3 <= from2; from3++) {
                        if (domove (p, from3, d, &to3)) {
                            for (from4 = 0; from4 <= from3; from4++) {
                                if (domove (p, from4, d, &to4)) {
                                    val = eval (p, &var);
                                    if (val < bestval) {
                                        best1 = from1;
                                        best2 = from2;
                                        best3 = from3;
                                        best4 = from4;
                                        bestval = val;
                                        bestvar = var;
                                    }
                                    printf ("%d/%d %d/%d %d/%d %d/%d = %f %f\n",
                                            from1, d, from2, d,
                                            from3, d, from4, d,
                                            val, sqrt(var));
                                    unmove (p, from4, to4);
                                }
                            }
                            unmove (p, from3, to3);
                        }
                    }
                    unmove (p, from2, to2);
                }
            }
            unmove (p, from1, to1);
        }
    }
    printf ("Best: %d/%d %d/%d %d/%d %d/%d = %f %f\n",
            best1, d, best2, d, best3, d, best4, d, bestval, sqrt(bestvar));
}

list_moves_notdouble (p, d1, d2)
position *p;
int d1, d2;
{
    int from1, from2;
    int to1, to2;
    int maxloc = p -> maxloc;
    int best1, best2;
    double bestval = 1e10, val, var, bestvar;

    for (from1 = 0; from1 <= maxloc; from1++) {
        if (domove (p, from1, d1, &to1)) {
            for (from2 = 0; from2 <= from1; from2++) {
                if (domove (p, from2, d2, &to2)) {
                    val = eval (p, &var);
                    if (val < bestval) {
                        best1 = from1;
                        best2 = from2;
                        bestval = val;
                        bestvar = var;
                    }
                    printf ("%d/%d %d/%d = %f %f\n", from1, d1, from2, d2,
                                val, sqrt(var));
                    unmove (p, from2, to2);
                }
            }
            unmove (p, from1, to1);
        }
    }
    for (from2 = 0; from2 <= maxloc; from2++) {
        if (domove (p, from2, d2, &to2)) {
            for (from1 = 0; from1 <= from2; from1++) {
                if (domove (p, from1, d1, &to1)) {
                    val = eval (p, &var);
                    if (val < bestval) {
                        best1 = from1;
                        best2 = from2;
                        bestval = val;
                        bestvar = var;
                    }
                    printf ("%d/%d %d/%d = %f %f\n", from1, d1, from2, d2,
                                val, sqrt(var));
                    unmove (p, from1, to1);
                }
            }
            unmove (p, from2, to2);
        }
    } printf ("Best: %d/%d %d/%d = %f %f\n", best1, d1, best2, d2, bestval, sqrt(bestvar));
}

list_moves_single (p, d)
position *p;
int d;
{
    int from1;
    int to1;
    int maxloc = p -> maxloc;
    int best1;
    double bestval = 1e10, val, var, bestvar;

    for (from1 = 0; from1 <= maxloc; from1++) {
        if (domove (p, from1, d, &to1)) {
            val = eval (p, &var);
            if (val < bestval) {
                best1 = from1;
                bestval = val;
                bestvar = var;
            }
            printf ("%d/%d = %f %f\n", from1, d, val, sqrt(var));
            unmove (p, from1, to1);
        }
    } printf ("Best: %d/%d = %f %f\n", best1, d, bestval, sqrt(bestvar));
}

set_maxloc (p)
position *p;
{
    int i;

    for (i=NSLOTS-1; i >= 0 && p->npieces[i] == 0; --i);
    p->maxloc = i;
}

print_pos(p)
position *p;
{
    int i;

    for (i=0; i<=p->maxloc; i++) {
        printf ("%d ",p->npieces[i]);
        if (i % 6 == 0) {
            printf ("| ");
        }
    }
}

#define HASHSIZE 500009
#define HASHLOC(pos) ((3121 * (pos)) % HASHSIZE)

struct hashent {
    unsigned int pos;
    float value, variance;
} hashtable[HASHSIZE];

hash_compress (p, pos)
position *p;
unsigned int *pos;
{
    int i;
    unsigned char *s, *send;
    unsigned int v1 = 0;

    i = 0;
    for (s = p->npieces, send = s + p->maxloc; s <= send; s++) {
        i += *s;
        v1 |= (1 << i);
        i++;
    }

    *pos = v1;
}

hashcheck (pos, val, var)
unsigned int pos;
double *val, *var;
{
    int loc;

    hash_queries++;

    loc = HASHLOC(pos);

    if (hashtable[loc].pos == pos) {
        *val = hashtable[loc].value;
        *var = hashtable[loc].variance;
        hash_hits++;
        return 1;
    }

    return 0;
}

hashstore (pos, val, var)
unsigned int pos;
double val, var
;
{
    int loc;

    loc = HASHLOC(pos);

    if (hashtable[loc].pos) {
        hash_overwrites++;
    } else { hash_entries++;
    }

    hashtable[loc].pos = pos;
    hashtable[loc].value = val;
    hashtable[loc].variance = var;
}

read_hash (s)
char *s;
{
    int d = open (s, O_RDONLY, 0);
    int loc;
    struct hashent new;

    if (d < 0) {
        return;
    }
    for (;;) {
        if (read (d, (char *) &new, sizeof (new)) <= 0) {
            break;
        }

        loc = HASHLOC (new.pos);

        if (hashtable[loc].pos) {
            hash_overwrites++;
        } else {  hash_entries++;
        } hashtable[loc] = new;
    }
    close (d);

    printf ("initial hash stats: entries %d overwrites %d\n",
            hash_entries, hash_overwrites);
}

write_hash (s)
char *s;
{
    int d = open (s, O_WRONLY|O_CREAT|O_TRUNC, 0666);
    int i;

    if (d < 0) {
        perror (s);
        exit (1);
    }

    for (i=0; i<HASHSIZE; i++) {
        if (hashtable[i].pos) {
            write (d, (char *) &hashtable[i], sizeof (struct hashent));
        }
    } close (d);
}

initialize()
{
}

parseargs (ac, av, p)
int ac;
char **av;
position *p;
{
    int c;
    extern int optind;
    extern char *optarg;
    int i;
    int sum;

    while ((c = getopt (ac, av, "dqi:o:r:?")) != EOF) switch (c) {
        case 'd': debug++; break;
        case 'q': quiet++; break;
        case 'i': hashin = optarg; break;
        case 'o': hashout = optarg; break;
        case 'r': roll1 = optarg[0] - '0';
                  if (optarg[1]) roll2 = optarg[1] - '0';
                  break;
        case '?':
        default:  usage (av[0]); exit (1);
    }

    for (i = 0; i < NSLOTS; i++) {
        p -> npieces[i] = 0;
    }

    i = 1;
    sum = 0;
    while (optind < ac && i < NSLOTS && sum < NPIECES) {
        p -> npieces[i] = atoi (av[optind]);
        sum += p -> npieces[i];
        i++;
        optind++;
    }
    p -> npieces[0] = NPIECES - sum;

    set_maxloc (p);
}

usage (s)
char *s;
{
    fprintf (stderr, "Usage: %s [-d] [-q] [-r roll] [-i hashin] [-o hashout] position\n",s);
}
 
Did you find the information in this article useful?          

Do you have any comments you'd like to add?     

 

Source Code

Benchmark player "pubeval.c"  (Gerry Tesauro, Feb 1993) 
GamesGrid .CBG viewer in flex  (Gary Wong, Sept 1997) 
Match equity calculator  (Claes Thornberg, Sept 1996)  [Recommended reading]
Move generator  (Gary Wong, Oct 1998)  [Long message]
Rfibs and sfibs recorder and replayer  (Jan Spitzkowsky, Aug 1994) 
race2.c  (Marc Ringuette, Dec 1993)  [Long message]
race4.c  (Marc Ringuette, Dec 1993)  [Long message]

[GammOnLine forum]  From GammOnLine       [Long message]  Long message       [Recommended reading]  Recommended reading       [Recent addition]  Recent addition
 

  Book Suggestions
Books
Cheating
Chouettes
Computer Dice
Cube Handling
Cube Handling in Races
Equipment
Etiquette
Extreme Gammon
Fun and frustration
GNU Backgammon
History
Jellyfish
Learning
Luck versus Skill
Magazines & E-zines
Match Archives
Match Equities
Match Play
Match Play at 2-away/2-away
Miscellaneous
Opening Rolls
Pip Counting
Play Sites
Probability and Statistics
Programming
Propositions
Puzzles
Ratings
Rollouts
Rules
Rulings
Snowie
Software
Source Code
Strategy--Backgames
Strategy--Bearing Off
Strategy--Checker play
Terminology
Theory
Tournaments
Uncategorized
Variations

 

Return to:  Backgammon Galore : Forum Archive Main Page