[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Rolldice 1.1



Stevie Strickland wrote:
> Because not all Debian GNU/Linux systems have /dev/urandom (at least
> I, personally, had to mknod c 1 9 /dev/urandom myself), and I want
> this running on any basic system... eventually, when I had more time
> (like this weekend), I was going to add a command line option to allow
> usage of this device, as well as /dev/random...  which would knock out
> the sleep delay (yay!), but introduce the delay of the kernel gathering
> entropy (oh, well, can't win all the time, I guess ;)...

Well, here's a modified version, with a good RNG (a variant of George
Marsaglia's highly-regarded KISS generator) stuck in for the cases
where /dev/urandom isn't available.  It's seeded with gettimeofday(),
getpid() and getppid().

It also prints out the numbers it's summing to get the final result.
For example, roll %-%-3d6+12 might print 
%-%-3d6+12 = (98+90)-(01-62)-4-2-1+12 = 254

d100 are printed out with "%02d" format; other numbers are printed with
%d format.  I realize that "00" is conventional for 100 on d%, but I'm
printing it as 100 to minimuze confusion.

The sum is printed without spaces to make it easy to extract the result
in a shell script with "cut -d' ' -f5".
-- 
	-Colin

/*
 * roll.c - FRP die-rolling utility.
 * This uses Linux /dev/random for the highest-quality possible random
 * numbers.  It avoids using any more entropy than necessary (e.g. rolling
 * 3d6 has 216 possibilities and requires 1 byte of entropy in the common
 * case), and goes to pains to make sure that the results it produces
 * are completely free of bias, so the chance of rolling 1 on d6 is
 * precisely 1/6.
 * 
 * It also has a (fairly good) emergency backup pseudo-random number
 * generator if /dev/urandom isn't available for some reason.
 * 
 * Output is of the form "3d6 = 1+2+3 = 6", so the sum can be picked
 * out with "cut -d' ' -f5".
 */
#include <stdio.h>
#include <assert.h>
#include <unistd.h>	/* For read(), gettimeofday() */
#include <fcntl.h>	/* For open(), O_RDONLY */
#include <stdlib.h>	/* For exit() */
#include <sys/time.h>	/* For struct timeval */
#include <limits.h>	/* For Uxxx_MAX */

#if ULONG_MAX == 0xffffffff
typedef unsigned long word32;
#elif UINT_MAX == 0xffffffff
typedef unsigned word32;
#elif USHRT_MAX == 0xffffffff
typedef unsigned short word32;
#elif UCHAR_MAX == 0xffffffff
typedef unsigned char word32;
#else
#error No 32-bit type available!
#endif


struct random_state {
	int randfd;	/* /dev/urandom.  If >= 0, the rest is ignored. */
	word32 w, x, y, z;
};

/*
 * Emergency backup RNG, returns a byte.
 * Based on George Marsaglia's KISS generator, except that the two
 * MWC components are added together rather than concatenated, since
 * this only returns a byte.  It also uses the high half of the CONG
 * generator and returns the high half of the 16-bit sum.
 * is returned.
 * 
 * The period of w is 18000*2^15-1 = 589823999, a prime.
 * The period of x is 2^32 = 4294967296
 * The period of y is 2^32-1 = 4294967295
 * The period of z is 36969*2^15-1 = 1211400191, a prime
 * Thus, the total period is 13180436693658741103741078002865274880,
 * 1.318e37, a bit over 2^123.
 */
static unsigned
random_kiss(struct random_state *r)
{
	/* cycle the generators */
	r->w = 18000*(r->w & 65535) + (r->w >> 16);	/* Mult with carry */
	r->x = 69069*r->x + 1234567;	/* Linear congruential */
	r->y ^= r->y << 17;		/* Shift register generator */
	r->y ^= r->y >> 13;
	r->y ^= r->y << 5;
	r->z = 36969*(r->z & 65535) + (r->z >> 16);	/* Mult with carry */
	
	/* Join them all together to return. */
	return ((((r->w - (r->x>>16)) ^ r->y) + r->z) >> 8) & 255;
}

/*
 * Bob Jenkins' mixing function for 32-bit words.
 * Note that this is reversible, and maps zero
 * to zero, so it maps non-zero to non-zero.
 */
#define mix(a,b,c) \
{ \
	a -= b; a -= c; a ^= (c>>13); \
	b -= c; b -= a; b ^= (a<<8); \
	c -= a; c -= b; c ^= (b>>13); \
	a -= b; a -= c; a ^= (c>>12);  \
	b -= c; b -= a; b ^= (a<<16); \
	c -= a; c -= b; c ^= (b>>5); \
	a -= b; a -= c; a ^= (c>>3);  \
	b -= c; b -= a; b ^= (a<<10); \
	c -= a; c -= b; c ^= (b>>15); \
}
/* Start up the random state */
static void
random_init(struct random_state *r)
{
	word32 w, x, y, z;
	struct timeval tv;

	r->randfd = open("/dev/urandom", O_RDONLY);

	/* Emergency backup seeding, in case /dev/urandom doesn't work. */
	
	/* Backup seed, good enough for government work. */
	gettimeofday(&tv, (struct timezone *)0);
	w = tv.tv_sec;
	x = tv.tv_usec;
	z = ((word32)getppid() << 16) + (word32)getpid();
	mix(w, x, z);	/* Shuffle the seed values non-destructively */
	
	/*
	 * Now ensure that seed constraints are met.
	 * w should be between 1 and 589823999.
	 * x can be anything between 0 and 2^32-1.
	 * y should be between 1 and 2^32-1.
	 * z should be between 1 and 1211400191.
	 */
	r->w = w % 589823999 + 1;
	r->x = x;
	r->z = z % 1211400191 + 1;
	/*
	 * Finally, we initialize y.  Since the range desired for y,
	 * 2^32-1, exactly divides the range available from the
	 * triple-width number wxz, 2^96-1, the remainder modulo 2^32-1
	 * will be uniformly distributed.  Fortunately, due to the special
	 * form of the modulus, this computation is easy.
	 * Since tv_sec never returns 0, and mix() preserves
	 * non-zeroness, the full value wxz here is never 0, so the
	 * result computed here is never 0.
	 */
	y = w+x;
	y += y<x;	/* End-around carry */
	y += z;
	y += y<z;	/* End-around carry */
	r->y = y;
}

/* Return a random byte 0..255 */
static unsigned
random_byte(struct random_state *r)
{
	unsigned char c = 0;

	if (r->randfd >= 0 && read(r->randfd, &c, 1) != 1) {
		perror("Unable to read from /dev/urandom");
		r->randfd = -1;
	}
	/* Add backup generator in case /dev/urandom is missing or broken */
	return c ^ random_kiss(r);
}

/*
 * This ingenious function returns perfectly uniformly distributed
 * random numbers between 0 and n-1 using a minimum of input from
 * /dev/urandom.  The truly macho should try to understand it without
 * the comments.
 * 
 * At all times, x is a random number uniformly distributed between 0
 * and lim-1.  We increase lim by a factor of 256 by appending a byte
 * onto x (x = (x<<8) + random_byte) whenever necessary.
 * 
 * The value of x%n has at least floor(lim/n) ways of generating each
 * possible value from 0..n-1, but it has an additional way
 * (ceiling(lim/n)) of generating values less than lim%n.  (Consider
 * lim = 4 or 5 and n = 3.)
 * 
 * To produce perfectly uniform numbers, if x < lim%n, we add another
 * byte rather than returning x%n.  Becasue we only do this if x < lim%n,
 * taking the branch means that lim %= n.
 * 
 * Once we have x >= lim%n, then y = x%n is the desired random number.
 * x/n is "unused" and can be saved for next time, with a lim of lim/n.
 * There is a small correction that has to be added to deal with the fact
 * that x/n can be == lim, which is illegal.
 */
static unsigned 
rand_mod(struct random_state *r, unsigned n)
{
        static word32 x = 0;
        static word32 lim = 1;
        unsigned y;

	assert(n <= 65536);	/* Larger risks arithmetic overflow */
	assert(n > 0);

        while (x < lim % n) {
                lim %= n;
                x = (x<<8) + random_byte(r);
                lim <<= 8;
        }

        y = x % n;
        x = (x / n) - (y < lim%n);
        lim /= n;
        return y;
}

#if __GNUC__ >= 2
static void fatal() __attribute__ ((noreturn));
#endif

/* Flush stdout, then print erro message and quit. */
static void
fatal(char const *s1, char const *s2)
{
	putchar('\n');
	fflush(stdout);

	fputs(s1, stderr);
	fputs(": ", stderr);
	fputs(s2, stderr);
	putc('\n', stderr);
	exit(1);
}

/*
 * Parse a die-rolling string and return the total.  This does not
 * bother checking for overflow, although limiting the numbers to
 * 65536 prevents most situations.  It also prints the individual
 * parts of the sum.  d100 is printed in %02ld format by convention.
 * (However, 100 is printed as "100" not "00", unless someone feels
 * that is really important.)
 * 
 * This bombs via fatal() on a parsing error.
 */
long
roll_string(char const *s, struct random_state *r)
{
	unsigned n, d;	/* Roll n dice of size d */
	char const *p = s;
	long total = 0;	/* Value of entire string */
	long part;	/* Value of current component */
	long x;
	int sign = 0;	/* 1 for - */
	int sign_required = 0;	/* Is a leading + optional? */

	/* Simple hand-rolled parsing loop */
	do {
		/* Parse one component at a time */
		sign = 0;
		/* Optional leading sign */
		if (*p == '+') {
			p++;
		} else if (*p == '-') {
			sign = 1;
			p++;
		} else if (sign_required) {
			/* Without this test, d6d8 would mean d6+d8 */
			fatal(s, "I don't understand that.");
		}
		sign_required |= sign;

		if (*p == 'd') {
			n = 1;	/* Number of dice defaults to 1 if omitted. */
		} else if (*p == '%') {
			/* Special RoleMaster "critical" roll rules */
			if (sign_required)
				putchar("+-"[sign]);
			part = rand_mod(r, 100)+1;
			if (part <= 5) {
				printf("(%02ld", part);
				do {
					part -= x = rand_mod(r, 100)+1;
					printf("-%02ld", x);
				} while (x >= 96);
				putchar(')');
			} else if (part >= 96) {
				printf("(%02ld", part);
				do {
					part += x = rand_mod(r, 100)+1;
					printf("+%02ld", x);
				} while (x >= 96);
				putchar(')');
			} else {
				printf("%02ld", part);
			}
			p++;
			goto got_component;
		} else if (*p >= '1' && *p <= '9') {
			n = 0;
			do {
				n = n*10 + (*p++-'0');
				if (n > 65536)
					fatal(s, "I can't count that high.");
			} while (*p >= '0'&& *p <= '9');
		} else {
			fatal(s, "I don't understand that.");
		}
		/* Now the "d" part */
		if (*p != 'd') {
			d = 1;
		} else if (*++p == '%') {
			d = 100;
			p++;
		} else {
			/* Note that the grammar here disallows d0 */
			if (*p < '1' || *p > '9')
				fatal(s, "I don't have that kind of dice.");
			d = 0;
			do {
				d = d*10 + (*p++-'0');
				if (d > 65536)
					fatal(s, "I don't have dice that big.");
			} while (*p >= '0'&& *p <= '9');
		}

		/* Now add NdD to the total */
		if (d > 1) {
			part = 0;
			while (n--) {
				if (sign_required)
					putchar("+-"[sign]);
				x = rand_mod(r, d) + 1;
				part += x;
				if (d == 100)
					printf("%02ld", x);
				else
					printf("%ld", x);
				sign_required = 1;
			}
		} else {
			part = n;
			if (sign_required)
				putchar("+-"[sign]);
			printf("%u", n);
		}
got_component:
		if (sign)
			total -= part;
		else
			total += part;
		sign_required = 1;
	} while (*p);
	return total;
}

/* The main argument-processing loop. */
int
main(int argc, char **argv)
{
	int i;
	struct random_state r;

	random_init(&r);

	if (argc <= 1) {
		printf("Usage %s roll_spec [...]\n"
"roll_spec is a standrd FRP diece specification, of the form 3d6, d8+12,\n"
"5+d100, etc.  To be precise, it's:\n\t"
"nzdigit ::= '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'\n\t"
"digit ::= '0' | nzdigit\n\t"
"number ::= nzdigit | number digit\n\t"
"die_spec = 'd' number | 'd' '%%'\n\t"
"component ::= number | die_spec | number die_spec | '%%'\n\t"
"sign ::= '+' | '-'\n\t"
"roll_spec ::= component | sign component | roll_spec sign component\n"
"dN neans an N-sided die, with values from 1..N.  2dN = dN+dN is the sum of\n"
"two N-sided dice.  A bare N is just a constant, so 3d6-3 is from 0..15.\n"
"d%% is an alias for d100.  The magic component '%%' stands for d100 with the\n"
"extra RoleMaster \"critical\" rules, where if you roll 96..100, you roll\n"
"again and add the result, repeating until you roll <= 95.  If you roll\n"
"1..5, you likewise roll again and subtract the result, again repeating until\n"
"you roll <= 95.\n",
			argv[0]);
		exit(1);
	}
	for (i = 1; i < argc; i++) {
		printf("%s = ", argv[i]);
		printf(" = %ld\n", roll_string(argv[i], &r));
	}
	
	/* randfd is closed automatically */
	return 0;
}


Reply to: