Probability in Char. Gen.

This was done in Visual Basic 6.0 using a single form with a button and a textbox. It took about 5-10 minutes to run.

The calculation of 31.12104 assumed point buy for stats below 8 scale linearly. I can calculate for other assumptions if folks are interested.

Code:
Private Sub Command1_Click()
 
Dim V As Single
Dim P As Single
Dim A As Single
Dim Value As Single
Dim Probability As Single
Dim Strength As Integer
Dim Dexterity As Integer
Dim Intelligence As Integer
Dim Wisdom As Integer
Dim Charisma As Integer
Dim SampleText As String
 
V = 0
P = 0
 
' We will cycle through each possible stat array that could be created
For Strength = 3 To 18
For Dexterity = 3 To 18
For Constitution = 3 To 18
For Intelligence = 3 To 18
For Wisdom = 3 To 18
For Charisma = 3 To 18
 
' If the stat array is a "worthless character", then skip it
' Deal with no stats 14+ first
If Strength > 13 Or Dexterity > 13 Or Consitution > 13 _
Or Intelligence > 13 Or Wisdom > 13 Or Chrarisma > 13 Then
 
' Deal with total mods of 0 or below next
	If Int((Strength - 10) / 2) + Int((Dexterity - 10) / 2)_
	+ Int((Constitution - 10) / 2) + Int((Intelligence - 10) / 2)_
	+ Int((Wisdom - 10) / 2) + Int((Charisma - 10) / 2) > 0 Then
 
' If the stat array is not a worthless character, calculate the 
! point buy value (PBV) and the probability of that character occurring
	Value = PBV(Strength) + PBV(Dexterity) + PBV(Constitution)_
	+ PBV(Intelligence) + PBV(Wisdom) + PBV(Charisma)
 
	Probability = prob(Strength) * prob(Dexterity) * prob(Constitution)_
	* prob(Intelligence) * prob(Wisdom) * prob(Charisma)
 
' Add the weighted point buy value to a running total
	V = V + Value * Probability
 
' Track the total probability (P) of getting a "non-worthless" character
' for normalization below
	P = P + Probability
 
	End If
End If
 
Next
Next
Next
Next
Next
Next
 
' We know the probability (1-P) of rolling a worthless character,
' and we calculated the average point buy value (V) that results
' from a single roll of 4d6dl. Since you keep rolling until you get a
' non-worthless character, the percentage chance (1-P) of a 
' worthless ' character gets distributed across the possible 
' non-worthless characters. Thus, we simply need to normalize 
' the result.
A = V / P
 
' And display it
Text1.Text = "Final Result:" + Str(A)
 
End Sub
Function PBV(x)
' This assumes that point buy for stats below 8 scales linearly
If x < 15 Then PBV = x - 8
If x = 15 Then PBV = 8
If x = 16 Then PBV = 10
If x = 17 Then PBV = 13
If x = 18 Then PBV = 16
End Function
 
Function prob(x)
'These percentages taken from dcollins 
' ([url="http://superdan.net.home.comcast.net/dndmisc/4d6curve.html"]http://superdan.net.home.comcast.net/dndmisc/4d6curve.html[/url])
If x = 3 Then prob = 0.0008
If x = 4 Then prob = 0.0031
If x = 5 Then prob = 0.0077
If x = 6 Then prob = 0.0162
If x = 7 Then prob = 0.0293
If x = 8 Then prob = 0.0478
If x = 9 Then prob = 0.0702
If x = 10 Then prob = 0.0941
If x = 11 Then prob = 0.1142
If x = 12 Then prob = 0.1289
If x = 13 Then prob = 0.1327
If x = 14 Then prob = 0.1235
If x = 15 Then prob = 0.1011
If x = 16 Then prob = 0.0725
If x = 17 Then prob = 0.0417
If x = 18 Then prob = 0.0162
End Function
 

log in or register to remove this ad

It's somewhat simpler to write a program that estimates the average, if you've forgotten the probability and stats math (which I have -- I knew this stuff in college). Write a routine that rolls a character, determines whether or not it's hopeless, and then either re-rolls (if it's hopeless), or returns the point buy value (if it's not). Then wrap it in a loop that sums the point buy values. Divide by the number of iterations in the loop. If you generate ten thousand or so characters this way, you'll get a very good estimate (unless you've got a bad random number generator).
 

RedShirtNo5 said:
This was done in Visual Basic 6.0 using a single form with a button and a textbox. It took about 5-10 minutes to run.

The calculation of 31.12104 assumed point buy for stats below 8 scale linearly. I can calculate for other assumptions if folks are interested.

Interesting. I wrote a program to calculate the answer using Charwoman Gene's specs (that is, using only integer arithmetic) and I have to admit I came to a very different answer (off by about 3). I wonder if I made a mistake in my coding, since I doubt roundoff from your method is nearly that extreme.

This program is written in C# and runs in just a few seconds. It takes 16^6 loops in the main section to finish. Instead of calculating point buy as it goes, it stores the chance of getting each

Code:
int[] ways = new int[19];
long[] stat = new long[19];
int a,b,s,d,c,i,w,ch;
String o = "";
long total = 0, totalPoints1 = 0, totalPoints2 = 0;
for (i = 3; i < 19; i++)
	stat[i] = 0;

for (i = 3; i < 19; i++)
	ways[i] = 0;
for (a = 1; a < 7; a++)
	for (b = 1; b < 7; b++)
		for (c = 1; c < 7; c++)
			for (d = 1; d < 7; d++)
				ways[a+b+c+d-Math.Min(a,Math.Min(b,Math.Min(c,d)))]++;

for (s = 3; s < 19; s++)
	for (d = 3; d < 19; d++)
		for (c = 3; c < 19; c++)
			for (i = 3; i < 19; i++)
				for (w = 3; w < 19; w++)
					for (ch = 3; ch < 19; ch++) 
					{
						if (s < 14 && d < 14 && c < 14 && i < 14 && w < 14 && ch < 14)
							break;
						if (m(s) + m(d) + m(c) + m(i) + m(w) + m(ch) <= 0)
							break;
						stat[s] += ways[s];
						stat[d] += ways[d];
						stat[c] += ways[c];
						stat[i] += ways[i];
						stat[w] += ways[w];
						stat[ch] += ways[ch];
					}
for (i = 3; i < 19; i++) 
{
	o += "Ways to get a " + i + ": " + stat[i] + "\n";
	total += stat[i];
	totalPoints1 += pb1(i) * stat[i];
	totalPoints2 += pb2(i) * stat[i];
}

o += "\nAverage point buy: \n";
o += "0 method:       " + totalPoints1 + "/" + total + " = " + (6.0 * totalPoints1 / total) + "\n";
o += "1-for-1 method: " + totalPoints2 + "/" + total + " = " + (6.0 * totalPoints2 / total) + "\n";
return o;

The program's output:

Code:
Ways to get a 3: 856665
Ways to get a 4: 3426660
Ways to get a 5: 11088250
Ways to get a 6: 23285325
Ways to get a 7: 54198070
Ways to get a 8: 88428430
Ways to get a 9: 164239075
Ways to get a 10: 220188650
Ways to get a 11: 267114100
Ways to get a 12: 372160335
Ways to get a 13: 383302860
Ways to get a 14: 432541600
Ways to get a 15: 354143435
Ways to get a 16: 299238190
Ways to get a 17: 171902790
Ways to get a 18: 76717725

Average point buy: 
0 method:       16542089730/2922832160 = 33.9576591972356
1-for-1 method: 16694113165/2922832160 = 34.2697334321106
 

My Java program got:

[sblock]
Code:
/////////////////////////////////////////////////////

class SCException extends Exception{
	SCException(){ super();}
	SCException(String desc){ super(desc);}
	}

/////////////////////////////////////////////////////

class AbilityScores{
	public static int mod(int score){
		switch (score){
			case 3:		return -4;
			case 4:		;
			case 5:		return -3;
			case 6:		;
			case 7:		return -2;
			case 8:		;
			case 9:		return -1;
			case 10:	;
			case 11:	return 0;
			case 12:	;
			case 13:	return 1;
			case 14:	;
			case 15:	return 2;
			case 16:	;
			case 17:	return 3;
			case 18:	return 4;
			default: return -150;
		}

	}
	public static int pbv(int score){
		switch (score){
			case 3:		return -5;
			case 4:		return -4;
			case 5:		return -3;
			case 6:		return -2;
			case 7:		return -1;
			case 8:		return 0;
			case 9:		return 1;
			case 10:	return 2;
			case 11:	return 3;
			case 12:	return 4;
			case 13:	return 5;
			case 14:	return 6;
			case 15:	return 8;
			case 16:	return 10;
			case 17:	return 13;
			case 18:	return 16;
			default: return -150;
		}
	}
	public static double prob(int score){
		switch (score){
			case 3:		return 1.0/1296.0;
			case 4:		return 4.0/1296.0;
			case 5:		return 10.0/1296.0;
			case 6:		return 21.0/1296.0;
			case 7:		return 38.0/1296.0;
			case 8:		return 62.0/1296.0;
			case 9:		return 91.0/1296.0;
			case 10:	return 122.0/1296.0;
			case 11:	return 148.0/1296.0;
			case 12:	return 167.0/1296.0;
			case 13:	return 172.0/1296.0;
			case 14:	return 160.0/1296.0;
			case 15:	return 131.0/1296.0;
			case 16:	return 94.0/1296.0;
			case 17:	return 54.0/1296.0;
			case 18:	return 21.0/1296.0;
			default: return -150.0;
		}
	}
	/*public static long prob(int score){
		switch (score){
			case 3:		return 1;
			case 4:		return 4;
			case 5:		return 10;
			case 6:		return 21;
			case 7:		return 38;
			case 8:		return 62;
			case 9:		return 91;
			case 10:	return 122;
			case 11:	return 148;
			case 12:	return 167;
			case 13:	return 172;
			case 14:	return 160;
			case 15:	return 131;
			case 16:	return 94;
			case 17:	return 54;
			case 18:	return 21;
			default: return -150;
		}
	}*/
	
	private class AbScInc{
		public int[] scores;
	
		AbScInc(){
			scores = new int[6];
			int i;
			for (i=0; i<6; i++){
				scores[i]=3;
			}
		}
		
		public boolean inc() throws SCException{
			// false means all 18's already
			int i;
			int tmp;
		
			for (i=0; i<6; i++){
				tmp = scores[i];
				if ((tmp < 3) || (tmp > 18)) throw new SCException("Bad Data");
				if (tmp < 18){
					scores[i] = tmp + 1;
					return true;
				}
				else{
					scores[i] = 3;
				}
			}
			return false;
		}	
	}
		
	private AbScInc ab;
		
	AbilityScores(){
		ab = new AbScInc();
	}
	
	public boolean next() throws SCException{
		// false means all 18's already
		return ab.inc();
	}
	public int getStr(){return ab.scores[0];}
	public int getDex(){return ab.scores[1];}
	public int getCon(){return ab.scores[2];}
	public int getInt(){return ab.scores[3];}
	public int getWis(){return ab.scores[4];}
	public int getCha(){return ab.scores[5];}		
}

/////////////////////////////////////////////////////

public class StatCalcDouble{
	public static void main(String[] a){
		AbilityScores stats = new AbilityScores();
		long accepted = 0;
		double totalModPBV = 0.0;
		double totalModScore = 0.0;
		double totalProb = 0.0;
		double currentProb = 0.0;
		//long totalProb = 0;
		//long currentProb = 0;
		boolean notDone = true;
		boolean worthless;
		
		do {
			int str = stats.getStr();
			int dex = stats.getDex();
			int con = stats.getCon();
			int intl = stats.getInt();
			int wis = stats.getWis();
			int cha = stats.getCha();
			int strMod = AbilityScores.mod(str);
			int dexMod = AbilityScores.mod(dex);
			int conMod = AbilityScores.mod(con);
			int intMod = AbilityScores.mod(intl);
			int wisMod = AbilityScores.mod(wis);
			int chaMod = AbilityScores.mod(cha);
			double strProb = AbilityScores.prob(str);
			double dexProb = AbilityScores.prob(dex);
			double conProb = AbilityScores.prob(con);
			double intProb = AbilityScores.prob(intl);
			double wisProb = AbilityScores.prob(wis);
			double chaProb = AbilityScores.prob(cha);
			/*long strProb = AbilityScores.prob(str);
			long dexProb = AbilityScores.prob(dex);
			long conProb = AbilityScores.prob(con);
			long intProb = AbilityScores.prob(intl);
			long wisProb = AbilityScores.prob(wis);
			long chaProb = AbilityScores.prob(cha);*/
			int strPbv = AbilityScores.pbv(str);
			int dexPbv = AbilityScores.pbv(dex);
			int conPbv = AbilityScores.pbv(con);
			int intPbv = AbilityScores.pbv(intl);
			int wisPbv = AbilityScores.pbv(wis);
			int chaPbv = AbilityScores.pbv(cha);

			if ((strMod + dexMod + conMod + intMod + wisMod + chaMod) < 0)
				worthless = true;
			else{
				if ((str<14) && (dex<14) && (con<14) && (intl<14) && (wis<14) && (cha<14))
					worthless = true;
				else
					worthless = false;
			}
			//////////////////////////
			// if (wis>3) break;
			//////////////////////////
			currentProb = (	strProb
						*	dexProb
						*	conProb
						*	intProb
						*	wisProb
						*	chaProb);
										
			System.out.println(	"Str: " + str
								+ " / Dex: " + dex
								+ " / Con: " + con
								+ " / Int: " + intl
								+ " / Wis: " + wis
								+ " / Cha: " + cha);
			
			if (!worthless){
				accepted++;

				totalModPBV += (strPbv * currentProb);
				totalModPBV += (dexPbv * currentProb);
				totalModPBV += (conPbv * currentProb);
				totalModPBV += (intPbv * currentProb);
				totalModPBV += (wisPbv * currentProb);
				totalModPBV += (chaPbv * currentProb);
				totalModScore += (str * currentProb);
				totalModScore += (dex * currentProb);
				totalModScore += (con * currentProb);
				totalModScore += (intl * currentProb);
				totalModScore += (wis * currentProb);
				totalModScore += (cha * currentProb);
				totalProb += currentProb;
			}
			else{
				System.out.println("^^^^WORTHLESS^^^^");
			}
			
			try{
				notDone = stats.next();
			}
			catch (SCException e){
				System.out.println("Bad Data");
			}
		}
		while (notDone);
		double avgPBV = totalModPBV / totalProb;
		double avgScore = (totalModScore / totalProb) / 6;
		System.out.println("Average Score for 4d6dl: " + avgScore);
		System.out.println("Average Point Buy Value for 4d6dl: " + avgPBV);
	}
}
[/sblock]

Average Score for 4d6dl: 12.44796477197748
Average Point Buy Value for 4d6dl: 30.0556696948599

The difference is probably due to my using double precision floats and long ints.

So, if you allow for the extra value of choosing your own scores, 28 pb is pretty fair.
 

I have no background in this whatsoever, so forgive me if this is of absolutely no help.

If you think of the set of all possible 4d6 drop the lowest characters as defining a multivariate "character space", can you create an algorithm that can eliminate parts of that space (worthless characters) from consideration for subsequent calculations? For instance, any character with three scores of 3 is "worthless," regardless of the other scores, so, you could just ignore all characters with three 3s and have that many fewer calculations to make.

The only reason I'm suggesting this is that it sounds like a problem that biologists run into when trying to analyze evolutionary relationships. You have a matrix of organisms and scores for various traits, and you can create "trees" that are networks implying certain changes in traits, or steps. The tree with the fewest steps is considered to be best, so you can use a computer to find the tree with the fewest steps. That's easy for three organisms, for which there are only three possible combinations, but, as you increase the number of organisms in the analysis, the number of possible trees increases by a sort of factorial progression. Thus, looking at every possible tree becomes impractical in terms of time. One solution is an algorithm called "branch-and-bound", which basically finds the shortest tree by a process analogous to what I described above. I forget the details of how it works at the moment, but someone familiar with graph theory may know these things; apparently similar algorithms are used for telecommunications networks.

--Axe
 

I got a significantly different answer than you guys... I got the average point buy value to be 25.95220907629133

Here's my program:

Code:
package com.asmor.dnd4d6dlcalc2;

public class bleh {

public static int getMod(int stat) {
    return ((stat/2)-5);
}

public static void main(String[] args) {
int stats[]={0, 0, 0, 0, 0, 0, 0}, mods[]={0, 0, 0, 0, 0, 0, 0}, i, toss, totalMod, pbv;
int PBValues[]={0, 0, 0, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16};
int baseWeights[]={0, 0, 0, 1, 4, 10, 21, 38, 62, 91, 122, 148, 167, 172, 160, 131, 94, 54, 21};
double weights[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, prob, totalValue=0, totalProb=0;

for (i=3; i<19; i++) {
    weights[i]=(double) baseWeights[i]/1296;
}

for (stats[0]=3; stats[0]<19; stats[0]++) {
for (stats[1]=3; stats[1]<19; stats[1]++) {
for (stats[2]=3; stats[2]<19; stats[2]++) {
for (stats[3]=3; stats[3]<19; stats[3]++) {
for (stats[4]=3; stats[4]<19; stats[4]++) {
for (stats[5]=3; stats[5]<19; stats[5]++) {

toss=1;
totalMod=0;
for (i=0; i<6; i++) {
    if (stats[i]>13) {
        toss=0;
    }
    mods[i]=getMod(stats[i]);
    totalMod+=mods[i];
}

if (totalMod<=0) {
    toss=1;
}

if (toss==0) {
    pbv=0;
    prob=1;
    for (i=0; i<5; i++) {
        pbv+=PBValues[stats[i]];
        prob*=weights[stats[i]];
    }
    totalValue+=(pbv*prob);
    totalProb+=(prob);
}

}}}}}}

System.out.print(totalValue/totalProb+"\n");
}
}

I added some checking to make sure it was doing the correct numer of iterations and see how many worthless characters there were. How do these numbers compare to you guys?

Total iterations: 16777216
Characters tossed: 8986646
Characters kept:7790570
Total of tossed and kept: 16777216
 
Last edited:

Asmor said:
I got a significantly different answer than you guys... I got the average point buy value to be 25.95220907629133

Here's my program:

Code:
package com.asmor.dnd4d6dlcalc2;

public class bleh {

public static int getMod(int stat) {
    return ((stat/2)-5);
}

public static void main(String[] args) {
int stats[]={0, 0, 0, 0, 0, 0, 0}, mods[]={0, 0, 0, 0, 0, 0, 0}, i, toss, totalMod, pbv;
int PBValues[]={0, 0, 0, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16};
int baseWeights[]={0, 0, 0, 1, 4, 10, 21, 38, 62, 91, 122, 148, 167, 172, 160, 131, 94, 54, 21};
double weights[]={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, prob, totalValue=0, totalProb=0;

for (i=3; i<19; i++) {
    weights[i]=(double) baseWeights[i]/1296;
}

for (stats[0]=3; stats[0]<19; stats[0]++) {
for (stats[1]=3; stats[1]<19; stats[1]++) {
for (stats[2]=3; stats[2]<19; stats[2]++) {
for (stats[3]=3; stats[3]<19; stats[3]++) {
for (stats[4]=3; stats[4]<19; stats[4]++) {
for (stats[5]=3; stats[5]<19; stats[5]++) {

toss=1;
totalMod=0;
for (i=0; i<6; i++) {
    if (stats[i]>13) {
        toss=0;
    }
    mods[i]=getMod(stats[i]);
    totalMod+=mods[i];
}

if (totalMod<=0) {
    toss=1;
}

if (toss==0) {
    pbv=0;
    prob=1;
    for (i=0; i<5; i++) {
        pbv+=PBValues[stats[i]];
        prob*=weights[stats[i]];
    }
    totalValue+=(pbv*prob);
    totalProb+=(prob);
}

}}}}}}

System.out.print(totalValue/totalProb+"\n");
}
}

I added some checking to make sure it was doing the correct numer of iterations and see how many worthless characters there were. How do these numbers compare to you guys?

Total iterations: 16777216
Characters tossed: 8986646
Characters kept:7790570
Total of tossed and kept: 16777216
Just from anecdotal evidence, that can't be right because it is not significantly more than the you would get if you didn't throw out hopelesses and you didn't have to pay extra points for high stats and low stats gave extra points (i.e. 18 would be 10 PB and 3 would be -5 PB). The number using these rules is 25.4676, so you must have made a mistake, I guess.
 

Am I doing the math right?

Code:
if (toss==0) {
    pbv=0;
    prob=1;
    for (i=0; i<5; i++) {
        pbv+=PBValues[stats[i]];
        prob*=weights[stats[i]];
    }
    totalValue+=(pbv*prob);
    totalProb+=(prob);
}

Notes: Toss equals 0 only if the character's total modifier is greater than 0 and it has at least one stat greater than 13. So basically this code is run for every character you'd keep.

PBValues[] is a 19-element array with indexes 3-18 containing the pointbuy value for the appropriate score.

weights[] is basically the same thing, except it has the probability of that score being rolled (i.e. total ways it could be rolled divided by 1296).

pbv is the sum of the point buy values for all stats for the current character

prob is the product of the probability of all stats for the current character

Each character's pbv is added to totalValue.

Each character's prob is added to totalProb.

So am I screwing up the math somewhere?
 

Asmor said:
Am I doing the math right?

Code:
if (toss==0) {
    pbv=0;
    prob=1;
    for (i=0; i<5; i++) {
        pbv+=PBValues[stats[i]];
        prob*=weights[stats[i]];
    }
    totalValue+=(pbv*prob);
    totalProb+=(prob);
}

So am I screwing up the math somewhere?

You made one of the three classic blunders...
this wasn't as bad as starting a land war in asia, but... (BTW, that is a good thing to avoid in Risk.)

Charisma isn't that much of a dump stat...
Code:
  for (i=0; i<[B]5[/B]; i++) {
        pbv+=PBValues[stats[i]];
        prob*=weights[stats[i]];
    }

Array Indexing off-by-one bug. <=5 OR < 6

Much simpler looking and nicer code than mine, btw.
 

Gah!

See, this is why it's good to have other people look at your code... I dunno if it's just me, but I always miss simple stuff like that and assume I have a flaw in the logic somewhere. LOL!

Even more annoying since I did that exact same loop correctly a bunch of other times... ugh.

Now my output is 30.463321972692526, which is much better, I think.
 

Remove ads

Top