FilteredRangeRNG.h

#ifndef FILTERED_RANGE_RNG_H
#define FILTERED_RANGE_RNG_H

// Sometimes a randomized sequence does not "feel" random to a human. While this isn't
// an indication of an error, sometimes we want to generate values that intuitively
// "feel" random, even if they are actually less-random as a result of this filtering.
// This concept is adapted from Chapter 3 of Steve Rabin, Jay Goldblatt, and Fernando 
// Silva's book: "Game AI Pro"

double filteredRangeRNG(double low, double high);

#endif

FilteredRangeRNG.cpp


// Helper function: Return a random double in range [low, high)
double randRange(double low, double high) { return 1.0 * rand() / RAND_MAX * (high - low) + low; }

// We need to keep track of the most recent rolls
double r[5] = { 0.0, 0.0, 0.0, 0.0, 0.0};
int rc = 0;

// Return a value in range [low, high)
double filteredRangeRNG(double low, double high) {

	// Begin by rolling a candidate
	double candidate = randRange(low, high);

	// If it's the first roll, accept it
	if (rc == 0) { 
		r[rc++] = candidate;
		return candidate;
	}

	// Update the array of the most recent rolls
	for (int i = 0; i < rc; i++) { r[i] = r[i + 1]; }
	if (rc > 4) { rc = 4; }

	// Re-roll if any of the following rules are broken:
	// Rule 1: Re-roll if last two consecutive numbers differ by less than 2 percent.
	// Rule 2: Re-roll if last three consecutive numbers differ by less than 1 percent.
	// Rule 3: Re-roll if last five consecutive numbers increase
	// Rule 4: Re-roll if last five consecutive numbers decrease
	// Rule 5: Re-roll if last five consecutive numbers are all in the top half of the possible range
	// Rule 6: Re-roll if last five consecutive numbers are all in the bottom half of the possible range
	double h = low + high / 2;
	while (
		((rc >= 1) && (abs(r[rc-1] - candidate) <= 0.02*(high - low))) || 
		((rc >= 2) && (abs(r[rc-2] - candidate) <= 0.01*(high - low))) ||
		((rc >= 4 && r[4] > r[3] && r[3] > r[2] && r[2] > r[1] && r[1] > r[0])) ||
		((rc >= 4 && r[4] < r[3] && r[3] < r[2] < r[2] < r[1] && r[1] < r[0])) ||
		((rc >= 4) && r[4] > h && r[3] > h && r[2] > h && r[1] > h && r[0] > h) ||
		((rc >= 4) && r[4] < h && r[3] < h && r[2] < h && r[1] < h && r[0] < h)
		) { candidate = randRange(low, high); }

	// Log and return the valid roll
	r[rc] = candidate;
	return candidate;
}