Since there was a decent amount of consensus on that number, I went through and reviewed my code again -- found one more issue. I was only allowing a reroll if the max score was <= 13 and the sum of modifiers was <= 0, instead of if either one is true. After fixing that, I get a number consistent with the others in that thread -- 30.4769. Note that this is an analytical/deterministic result, not stochastic.Asmor said:A bunch of people, myself included, tackled this a long time ago.
http://www.enworld.org/showthread.php?t=128481
Short, possibly biased, answer: The average rolled character is just under 30.5 points. This is assuming that a 7 is -1, a 6 -2, etc.
There was a bit of variation, but after checking both an "exact" method and the "monte carlo" method of rolling up a million characters and averaging them, both answers came out close enough that I'm pretty confident in the 30.5.
With that final correction, here's a review of the average point buy costs for various dice rolling methods:
Average Characters (3d6, reroll if max < 12 or modifiers < -2): 19.5109 (26.3% chance of reroll)
Standard rolling (4d6, drop lowest, reroll if max < 14 or modifiers < +1): 30.4769 (12.9% chance of reroll)
Standard + reroll ones (4d6, drop lowest, reroll 1s, reroll if max < 14 or modifiers < +1): 37.7441 (2.4% chance of reroll)
High-Powered Characters (5d6, drop lowest 2, reroll if max < 15 or modifiers < +2): 39.1323 (6.6% chance of reroll)
Also, I cleaned up and consolidated the code, and I added comments. So it's available for double-checking, or if anyone wants to use it as a basis for other calculations. The program is in C++, and it runs in about 1.3 seconds on a 2GHz machine whe compiled using gcc -O2. There's not much code involved, but it's bulked up to about double its raw size due to comments.
This is for the straight 4d6-drop-lowest version, but it's easy to change with the #defines at the top (rerolling 1s is done by using 5-sided dice with a die bonus of 1):
Code:
#include <math.h>
#include <memory.h>
#include <stdio.h>
#include <algorithm>
#define NUM_DICE 4
#define NUM_SIDES 6
#define DIE_BONUS 0
#define REROLL_MAX_SCORE_THRESHOLD 14
#define REROLL_MODIFIER_SUM_THRESHOLD 1
// calculate the probability of each score and fill it into _score_prob
void calculate_score_probs(double* _score_prob)
{
memset(_score_prob, 0, sizeof(double)*19);
// iterate through all possible sets of dice rolls
int num_possibilities = (int)pow(NUM_SIDES, NUM_DICE);
for(int i=1; i<num_possibilities; i++) {
int die[NUM_DICE];
memset(die, 0, sizeof(int)*NUM_DICE);
// turn this index into individual die roll numbers
int i2 = i;
for(int j=0; j<NUM_DICE; j++) {
die[j] = i2 % NUM_SIDES + 1 + DIE_BONUS;
i2 /= NUM_SIDES;
}
// sort the dice so that the highest rolls are first in the array
std::sort(die, die+NUM_DICE);
std::reverse(die, die+NUM_DICE);
// increment the probability for the score that's the sum of the highest 3 dice
_score_prob[die[0]+die[1]+die[2]] += 1.0/num_possibilities;
}
}
int main()
{
// score_prob is the probability of rolling each score. The score is the index into
// the array, so the first 3 entries will be empty.
double score_prob[19];
calculate_score_probs(score_prob);
// score_cost is an array of the point costs for each score, indexed the same way
int score_cost[19] = {0, 0, 0, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16};
// prob_of_cost is the probability of a set of scores having a specific total point
// cost (ignoring score sets that can be rerolled). The point cost + 24 is the index
// into the array (total possible range of -24 to 96).
double prob_of_cost[121];
memset(prob_of_cost, 0, sizeof(double)*121);
// total prob is the probability of rolling a set of scores that can't be rerolled.
double total_prob = 0;
// iterate through all possible sets of scores. There are 16 possible scores (3-18),
// so the total number of sets of scores is 16^6 = 16777216.
for(int i=0; i<16777216; i++)
{
bool max_score_over_threshold = false;
int modifier_sum = 0;
double prob = 1;
int cost = 0;
// turn the index into a set of individual scores
int i2 = i;
for(int j=0; j < 6; j++) {
int score = i2 % 16 + 3;
i2 /= 16;
// for each score, record if it's over 13 or and its effect on the sum of
// the modifiers, so we can decide if this score set can be rerolled.
if(score >= REROLL_MAX_SCORE_THRESHOLD)
max_score_over_threshold = true;
modifier_sum += score/2 - 5;
// after each roll, multiply the probability of this score set by the
// probability of rolling the most recent score. By the end of the set,
// we'll have the overall probability of the entire score set.
prob *= score_prob[score];
// likewise, add the point cost of the current score into the total for
// this set.
cost += score_cost[score];
}
// only count this score set if it can't be rerolled
if(max_score_over_threshold && modifier_sum >= REROLL_MODIFIER_SUM_THRESHOLD) {
prob_of_cost[cost+24] += prob;
total_prob += prob;
}
}
// add up the cost of each score set, weighted by the probability of that score
// set occurring.
double total_cost = 0;
for(int i=0; i<=121; i++)
total_cost += (i-24) * prob_of_cost[i];
printf("score sets that can be rerolled: %.4f%\n", (1 - total_prob)*100);
// the overall EV is the (weighted) average cost of non-rerollable score sets
// divided by the chance of rolling a non-rerollable score set in the first place.
printf("average point cost after rerolls: %.4f\n", total_cost/total_prob);
return 0;
}