3
\$\begingroup\$

I am very, very new to writing code and working on designing a RPG. As part of that, I am trying to do a simulation of three weighted dice rolls, ranging from 1-12 where I set the limit of how much the total of all three rolls can be. Meaning, I want to limit the total that any set of three rolls can give me to 12.

I am interested in the actual results of the dice and not just their sum so 3/3/3 is different from 4/4/1.

If the sum of the dice would be above 12 then the die making it go above 12 would be rerolled until this is not the case.

As an example:

  • If you rolled 5/5 on the first two dice, the third die would be rolled until it was either a 1 or 2.
  • If you rolled 1/1 on the first two dice, the third die would be rolled until it was a number 1-10.

I have the dice weighted according to the percentages I need. I just have no idea how to do the language to limit the total.

Here's what I have thus far:

W: {
 1:12,
 2:14,
 3:18,
 4:18,
 5:17,
 6:8,
 7:8,
 8:1,
 9:1,
 10:1,
 11:1,
 12:1
}
output dW

Any help would be GREATLY appreciated!

\$\endgroup\$
3
  • 2
    \$\begingroup\$ Also, Welcome to RPG.SE! Take the tour if you haven't already and see the help center or ask us here in the comments (use @ to ping someone) if you need more guidance. Good Luck and Happy Gaming! \$\endgroup\$
    – Someone_Evil
    May 25 '20 at 16:07
  • \$\begingroup\$ Comments attempting to clarify the question were getting a bit lengthy so I went ahead and moved them to chat. \$\endgroup\$
    – Rubiksmoose
    May 25 '20 at 17:51
  • \$\begingroup\$ I'm a little confused, you are using normal die here, correct? If so, what happens when the first two roll 6. Once you roll 6 on the first is the second limited to 5? \$\endgroup\$ May 28 '20 at 5:19
8
\$\begingroup\$

Don't use dice for this

Your probability distribution is unnecessarily complicated. There's no way that you are going to be able to make its function clear via dice for tabletop play-- you are going to need a computer or a deck of cards or something. Your base distribution looks like it would be most simply implemented by doing something like roll 2d3 and if both are 3 then you get a 4+d4 instead unless you roll a 4 then you get a 7+d5. That takes a really long time for something that could basically just be roll 2d3, 3s explode. If the level of precision you are going for is important, you should already know the probability distribution because the only reason to use such a screwed up, complicated, lengthy, and difficult to implement random number generation system is precisely because it best implements some desired set of probability curves.

Since you don't know the probability distribution, you must not have come up with this system for such a purpose. Use a simpler generation system for your game. If you want it to cap at 12 and follow a similar curve, use 2d3 where each 3 can explode exactly once. That looks like this. Or, if the minimum of 3 is also important, roll 3d2 with each exploding maximum of once instead.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the suggestions! \$\endgroup\$
    – Adair
    May 25 '20 at 17:11
3
\$\begingroup\$

What you might be looking for is the empty die trick for rejecting "impossible" outcomes (i.e. results that you'll always reroll until they don't happen).

For example, here's how to model a mechanic where you roll 3d12 and reroll them all if the result is over 12:

function: test ROLL:s if total is MAX:n or less {
  SUM: 0 + ROLL
  if SUM > MAX { result: d{} } \ ignore rolls that sum to more than MAX! \

  \ now do something with ROLL (and/or SUM) here... \
  result: SUM
}

output [test 3d12 if total is 12 or less]

The magic happens on the line if SUM > MAX { result: d{} }; if this condition is true, the function stops and returns the "empty die" d{}, which AnyDice will completely ignore when collecting the results. The end result is exactly as if you were to reroll any rolls that match the condition until they no longer do.

In the rest of the function, you can then calculate whatever result you want based on the input ROLL and return it. Note that the return value needs to be a number (or a die); if we tried to return a sequence (such as ROLL itself), AnyDice would just automatically sum it. In the example program above I just return the sum, but one possible alternative (if you wanted to examine the values of the individual dice) would be to encode the sequence ROLL as a base-10 or base-100 number, e.g. like this:

  result: 10000 * 1@ROLL + 100 * 2@ROLL + 3@ROLL

(Here's a more generic helper function to do this if you want one.)

I used an normal d12 for the example above, but obviously you could also use a custom biased die if you wanted. And you could also implement something like your "roll one die at a time and reroll last if over 12" mechanic, it would just be more complicated and/or tedious.

Basically, you'd need to have a series of function calling each other or a single function calling itself recursively to model the step-by-step rolling, something like this:

function: test BASE:n plus ROLL:n plus N:n times DIE:d max MAX:n {
  if BASE + ROLL + N > MAX { result: d{} }
  if N = 0 { result: BASE + ROLL }
  result: [test BASE + ROLL plus DIE plus N-1 times DIE max MAX] 
}

output [test 0 plus d12 plus 2 times d12 max 12]

Note that, in this example, I'm rerolling the most recent die if the roll plus the base total so far plus the number of remaining dice to roll exceeds the maximum. That's because we know that the remaining dice will always roll at least 1 each anyway. So, for example, if we rolled an 11 on the first d12 out of three when the maximum was 12, we'd reroll it since we'd know that the following two rolls would have to each increase the total by at least 1, making it at least 11 + 1 + 1 = 13.

\$\endgroup\$
2
\$\begingroup\$

You may need to add a user-defined function.

function: minimum of X:n and Y:n {
  if X > Y {
    result: Y
  } else {
    result: X
  }
}

W: {
 1:12,
 2:14,
 3:18,
 4:18,
 5:17,
 6:8,
 7:8,
 8:1,
 9:1,
 10:1,
 11:1,
 12:1
}

output [minimum of 3dW and 12]

AnyDice link: https://anydice.com/program/1bd45

\$\endgroup\$
6
  • 3
    \$\begingroup\$ Is there a reason the build in lowest of N and M wouldn't work? \$\endgroup\$
    – Someone_Evil
    May 25 '20 at 16:26
  • \$\begingroup\$ That seems to give values way above 12. I want it to total to no more than 12. So, if 12 got rolled in the first slot, the other two would be zeros. Or if 9 and 1 got rolled first and second, there would only be the options of 0, 1 or 2 for the third roll. Does that make sense? Thanks for trying to help me. It is much appreciated. \$\endgroup\$
    – Adair
    May 25 '20 at 16:31
  • \$\begingroup\$ @Someone_Evil I believe that method works perfectly well such as shown here \$\endgroup\$
    – Medix2
    May 25 '20 at 16:38
  • 1
    \$\begingroup\$ @Someone_Evil I couldn't find it when I Ctrl+F'd on the documentation page. Normally, this function is called "min" elsewhere, so I overlooked it. Good catch. \$\endgroup\$
    – Axoren
    May 25 '20 at 16:57
  • 2
    \$\begingroup\$ @Adair Do you want to extract the 3rd roll? Or do you want the total? Because this anydice code does not give values above 12 because they've been reduced to 12 if they are bigger. \$\endgroup\$
    – Axoren
    May 25 '20 at 16:59
2
\$\begingroup\$

Don't use anydice for this

Once you get into certain types of rolls, especially ones where you might (theoretically) have infinite re-rolls and/or where what to do with a roll depends very on other rolls, Anydice isn't up for the job. Monte-Carlo simulations are going to be the most broadly applicable tool to solve the distributions of strange rolls like this. It's not too complicated either, all you really need to do is convert your algorithm into code. The basic steps:

  1. Create a function which can do one roll for you.

  2. Run that function \$N\$ times, saving the results.

  3. Analyse that result as though it is experimental data.

In this case I did it in python (code below), with 100 000 rolls and plotted the frequency of each result for each roll:

plot of dice distribution

Here series 1 is the distribution of the first die which is just distribution of the weighted dice, series 2 is the second die which is mostly the same, and series 3 is the third die which is rerolled often and has quite a skewed distribution.

If you wanted to, you could modify the below distribution to measure how many times you would have to reroll etc.

import _random
import math

#Generate random integers
rnd = _random.Random()
def getrandint(m):
    return math.ceil(rnd.random()*m)

w = [1]*12+[2]*14+[3]*18+[4]*18+[5]*17+[6]*8+[7]*8+[8]*1+[9]*1+[10]*1+[11]*1+[12]*1

def rollW(): #Does a roll of our weighted die
    return w[getrandint(len(w))-1]

def AdairRoll(): #Each function generates a set of rolls for us
    roll = [rollW(), rollW(), rollW()] #an array for our rolls, with inital rolls

    if roll[0] == 12: #If the first roll is 12, the next has to be 0
        roll[1] = 0

    while roll[0]+roll[1] > 12: #Otherwise, keep rolling the second until the sum of the first two is 12 or less
        roll[1] = rollW()
    
    if roll[0]+roll[1] == 12: #If the first two are =12, third must be 0
        roll[2] = 0

    while roll[0]+roll[1]+roll[2] > 12: #otherwise, keep rolling the third until sum is 12 or less
        roll[2] = rollW()

    return roll

f = open('Adair/output.txt', 'a+')
for i in range(100000): #Roll the algorith N times, and write the results to file
    roll = AdairRoll()
    f.write('{}\t{}\t{}\n'.format(*roll))
f.close()
\$\endgroup\$
1
\$\begingroup\$

Yes

Like this.

The first shows the total of the dice as a number from 2 to 12. The second breaks out each die to give a result of XYZ where X is the highest die, Y is the middle and Z is the lowest - the range of the lowest die depends on the values of the other two.

function: weighted A:s total{
  result: 1*1@A + 1*2@A + 1*[highest of 0 and [lowest of 3@A and 12-1@A-2@A]]
}

output [weighted 3d6 total] named "Total Results"

function: weighted A:s individual {
  result: 100*1@A + 10*2@A + 1*[highest of 0 and [lowest of 3@A and 12-1@A-2@A]]
}

output [weighted 3d6 individual] named "Sequence Results"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.