Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Need a macro to generate valid ability score sets within specific parameters

Hello, this is my first time posting, sorry if my title isn't descriptive enough.

I'm trying to write a macro to generate ability scores for Shadowdark, which includes a clause that tells the program to reroll the whole set if there is no result greater than 14. So far I have this macro written:


&{template:default} {{name=Ability Scores}} [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]] {{1=STR: $[[0]]}} {{2=DEX: $[[1]]}} {{3=CON: $[[2]]}} {{4=INT: $[[3]]}} {{5=WIS: $[[4]]}} {{6=CHA: $[[5]]}}


Right now this generates and correctly assigns the values, and I know there is a reroll rule for specific die values (reroll 1's, 2's, etc.), but in this instance, I want to have the macro look at the whole list of roll results to determine if it is a valid set, and then generate a new set if the current one isn't valid. Is there a command or argument structure I can incorperate to achieve this?

March 28 (1 week ago)
Gauss
Forum Champion

Unless there is some arcane trickery (probably from RainbowEncoder or Tuo) you will probably need an API Script to do that. Probably Scriptcards

With that said, you can do it manually pretty quickly. 

&{template:default} {{name=Ability Scores}} [[{[[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]]}>14]] {{1=STR: $[[0]]}} {{2=DEX: $[[1]]}} {{3=CON: $[[2]]}} {{4=INT: $[[3]]}} {{5=WIS: $[[4]]}} {{6=CHA: $[[5]]}} {{if $[[6]] is a 0=[Push me to reroll](`#macroname)}}


Note: "macroname" is the macro name you have assigned. 
If you are using an Ability macro instead use ~ in place of `#. 


You can't modify a roll by the product of said roll, so just honest rerolling is out of the picture. However, we're not quite out of options:

&{template:default} {{name=Ability Scores [ ](#" hidden null=)
}} {{[0](#" hidden)=
}} {{[1](#" hidden)=[No valid sets found, try again?](`#macroname)
}} {{[[[{ [[{[[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]]}>14]],1}kl1]]](#" hidden)=$[[0]] STR

$[[1]] DEX

$[[2]] CON

$[[3]] INT

$[[4]] WIS

$[[5]] CHA
}} {{[[[{ [[{[[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]]}>14]],1}kl1]]](#" hidden)=$[[8]] STR

$[[9]] DEX

$[[10]] CON

$[[11]] INT

$[[12]] WIS

$[[13]] CHA
}} {{[[[{ [[{[[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]]}>14]],1}kl1]]](#" hidden)=$[[16]] STR

$[[17]] DEX

$[[18]] CON

$[[19]] INT

$[[20]] WIS

$[[21]] CHA
}} {{[[[{ [[{[[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]]}>14]],1}kl1]]](#" hidden)=$[[24]] STR

$[[25]] DEX

$[[26]] CON

$[[27]] INT

$[[28]] WIS

$[[29]] CHA
}} {{[[[{ [[{[[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]], [[3d6]]}>14]],1}kl1]]](#" hidden)=$[[32]] STR

$[[33]] DEX

$[[34]] CON

$[[35]] INT

$[[36]] WIS

$[[37]] CHA
}} {{[0](#" hidden)= [ ](#" hidden null=)
}}

This macro will roll 5 sets of ability scores at once, and if any are valid, it'll show one of them (the last one rolled). If none are valid, it'll only show a link to try again. Same deal as with Gauss's proposal, change #macroname to fit whatever name you give the macro, or make it an ability. Since it has so many rolls in it, it'll come out a little slow, but since this probably isn't something you'll be constantly rolling, that shouldn't be too much of an issue.

March 28 (1 week ago)
GiGs
Pro
Sheet Author
API Scripter

I was going to suggest the same method Gauss used. Tuo's method is clever and works perfectly, but is too slow for me.


March 28 (1 week ago)

Edited March 29 (1 week ago)

With some statistical trickery it can be done without manual rerolls

&{template:default} {{1=STR: $[[26]]}} {{2=DEX: $[[27]]}} {{3=CON: $[[28]]}} {{4=INT: $[[29]]}} {{5=WIS: $[[30]]}} {{6=CHA: $[[31]]}} {{name=

$[[$[[1]]]]
[[[ [[1 + {1, 1 +216**5, 1 +216**5 +181*216**4, 1 +216**5 +181*216**4 +181**2*216**3, 1 +216**5 +181*216**4 +181**2*216**3 +181**3*216**2, 1 +216**5 +181*216**4 +181**2*216**3 +181**3*216**2 +181**4*216}<[[ d31 +31*(d61-1 + 61*(d397-1 + 397*(d661-1 + 661*(d3823-1)))) ]] ]] ]]]

}} [[0 {{6=CHA: $[&lsqb;21&rsqb;]}} {{5=WIS: $[&lsqb;31&rsqb;]}} [[0 {{5=WIS: $[&lsqb;22&rsqb;]}} {{4=INT: $[&lsqb;31&rsqb;]}} [[0 {{4=INT: $[&lsqb;23&rsqb;]}} {{3=CON: $[&lsqb;31&rsqb;]}} [[0 {{3=CON: $[&lsqb;24&rsqb;]}} {{2=DEX: $[&lsqb;31&rsqb;]}} [[0 {{2=DEX: $[&lsqb;25&rsqb;]}} {{1=STR: $[&lsqb;31&rsqb;]}} [[0]] ]] ]] ]] ]] ]] {{name=

$[[8]] [[[ [[ [[ [[ [[ [[ [[0]] ]] ]] ]] ]] ]] ]]]

[[0]] [[0]] [[0]] [[0]] [[0]] [[0]]
[[2+{1,2,5,11,21,36,57,82,109,136,161}<[[1d181]] ]]
[[2+{1,2,5,11,21,36,57,82,109,136,161}<[[1d181]] ]]
[[2+{1,2,5,11,21,36,57,82,109,136,161}<[[1d181]] ]]
[[2+{1,2,5,11,21,36,57,82,109,136,161}<[[1d181]] ]]
[[2+{1,2,5,11,21,36,57,82,109,136,161}<[[1d181]] ]]
[[13+{1,16,26,32,35}<[[1d35]] ]]
[[3d6]] [[3d6]] [[3d6]] [[3d6]] [[3d6]]

}} {{name=Ability Scores}}


It functions by calculating which stat is the first to roll greater than 13. Then using some deep template manipulation causes the earlier stats to be rolls not greater than 13, the selected roll to be greater than 13, and any remaining rolls are standard 3d6's.


Fun fact you cannot roll a die greater than 10 million sides, so I had to improvise since I needed a 21.9 billion 1.9 trillion sided die

March 28 (1 week ago)
Gauss
Forum Champion

As I said arcane trickery via RainbowEncoder :D

March 29 (1 week ago)

Edited March 29 (1 week ago)

That is an absolutely terrifying macro, RainbowEncoder - but it got me thinking - are we overthinking this? We only really need to reroll a single ability score to be over 14, assign that to a random ability, and then roll 3d6 for all others:

&{template:default} {{name=Ability Scores
}} {{[1](#" hidden)=[[3d6]] [ ](#" hidden null=)
}} {{[7](#)STR=
}} {{[2](#" hidden)=[[3d6]] [ ](#" hidden null=)
}} {{[8](#)DEX=
}} {{[3](#" hidden)=[[3d6]] [ ](#" hidden null=)
}} {{[9](#)CON=
}} {{[4](#" hidden)=[[3d6]] [ ](#" hidden null=)
}} {{[10](#)INT=
}} {{[5](#" hidden)=[[3d6]] [ ](#" hidden null=)
}} {{[11](#)WIS=
}} {{[6](#" hidden)=[[3d6]] [ ](#" hidden null=)
}} {{[12](#)CHA=
}} {{[[[1d6]]](#" hidden)=[[14+{197,207,213,216}<[[1d216r<181]] ]] [ ](#" hidden null=)
}}

This uses row collapsing to detach the ability score labels from the rolls, and replaces a random score with the reconstructed 3d6 that will be rerolled until it's 14 or above.
EDIT: Put the rolls and the labels closer together.


Tuo said:

That is an absolutely terrifying macro, RainbowEncoder - but it got me thinking - are we overthinking this? We only really need to reroll a single ability score to be over 14, assign that to a random ability, and then roll 3d6 for all others

It wouldn't be statistically accurate, it would inflate the average value of the stats rolled

March 29 (1 week ago)

Edited March 29 (1 week ago)


RainbowEncoder said:


Tuo said:

That is an absolutely terrifying macro, RainbowEncoder - but it got me thinking - are we overthinking this? We only really need to reroll a single ability score to be over 14, assign that to a random ability, and then roll 3d6 for all others

It wouldn't be statistically accurate, it would inflate the average value of the stats rolled

Would it? I see no difference between a fresh set of 3d6 rolls containing a 3d6 roll 14 or above, and a set of 3d6 rolls where a random roll is replaced by a 3d6 roll that is rerolled to be 14 or above. After all, the initial problem was to reroll the whole set if it doesn't contain 14 or above - in other words, any set that contains a validly generated 14 or above in a random position, and 5 other rolls of 3d6, is valid. 5 of the scores are 3d6 rolls with no bias, and one is known to be 14 or above - this is the set that you get with my macro, and if you keep rerolling sets until one contains any score 14 or above.


Tuo said:

Would it? I see no difference between a fresh set of 3d6 rolls containing a 3d6 roll 14 or above, and a set of 3d6 rolls where a random roll is replaced by a 3d6 roll that is rerolled to be 14 or above. After all, the initial problem was to reroll the whole set if it doesn't contain 14 or above - in other words, any set that contains a validly generated 14 or above in a random position, and 5 other rolls of 3d6, is valid.

Thinking about it a bit more it generally messes the probability curve up.

Looking at some of the possibilities:

  • An initial invalid set: Replacing a single roll with a high roll isn't the same as a reroll since a reroll could roll multiple high rolls. This would lower average value
  • An initial valid set, replacing a high roll: This wouldn't change the outcome
  • An initial valid set, replacing a low roll: This would increase the average value of the set
March 29 (1 week ago)

Edited March 29 (1 week ago)


RainbowEncoder said:


Tuo said:

Would it? I see no difference between a fresh set of 3d6 rolls containing a 3d6 roll 14 or above, and a set of 3d6 rolls where a random roll is replaced by a 3d6 roll that is rerolled to be 14 or above. After all, the initial problem was to reroll the whole set if it doesn't contain 14 or above - in other words, any set that contains a validly generated 14 or above in a random position, and 5 other rolls of 3d6, is valid.

Thinking about it a bit more it generally messes the probability curve up.

Looking at some of the possibilities:

  • An initial invalid set: Replacing a single roll with a high roll isn't the same as a reroll since a reroll could roll multiple high rolls. This would lower average value
  • An initial valid set, replacing a high roll: This wouldn't change the outcome
  • An initial valid set, replacing a low roll: This would increase the average value of the set

I think you're getting stuck on the replacement part -  it's not 6 rolls + one guaranteed high, it's one guaranteed high + 5 rolls. It skips any possibility of an invalid set, instead generating a set that is, for all intents and purposes, the same as a valid set generated by rerolling the whole set. It doesn't matter what the replaced roll was, we're ignorant of it, it might as well not exist. The conditions for a valid set are that it contains at least a single high roll - which means one random ability must be high, and the rest are unknown and unaffected. These conditions are met - the required high roll has no bearing on the probabilities of the other 5 rolls, as if they were rerolled too, it wouldn't change them being straight 3d6 rolls in a valid set. We don't need to know any previous set to call a set valid, as all the necessary information is contained in the set itself.

EDIT: in terms of real dice, what I'm doing is rolling 3d6 until the result is at least 14, rolling 1d6 to place it in one of the 6 ability scores, and then rolling 3d6 for all the rest - which is functionally identical to rolling sets of 6 3d6 until you have a set that contains a 14 or above, just slightly different order of doing it.

March 29 (1 week ago)

Edited March 29 (1 week ago)


Tuo said:

I think you're getting stuck on the replacement part -  it's not 6 rolls + one guaranteed high, it's one guaranteed high + 5 rolls. It skips any possibility of an invalid set, instead generating a set that is, for all intents and purposes, the same as a valid set generated by rerolling the whole set. It doesn't matter what the replaced roll was, we're ignorant of it, it might as well not exist. The conditions for a valid set are that it contains at least a single high roll - which means one random ability must be high, and the rest are unknown and unaffected. These conditions are met - the required high roll has no bearing on the probabilities of the other 5 rolls, as if they were rerolled too, it wouldn't change them being straight 3d6 rolls in a valid set. We don't need to know any previous set to call a set valid, as all the necessary information is contained in the set itself.

EDIT: in terms of real dice, what I'm doing is rolling 3d6 until the result is at least 14, rolling 1d6 to place it in one of the 6 ability scores, and then rolling 3d6 for all the rest - which is functionally identical to rolling sets of 6 3d6 until you have a set that contains a 14 or above, just slightly different order of doing it.

The other rolls aren't truly independant; if any of the 5 normal rolls were high you wouldn't need to force a high roll for the 6th to make it valid. And when one of those normal rolls is high the 6th could be low in a natural roll. By forcing it to be high an already valid set can have it's value pushed up.

I've mocked up a natural vs force valid set on anydice so you can see the probablities.

https://anydice.com/program/3c3f9

Edit: I should say this shows the number of high rolls in a valid set

Yeah, now I see it, in effect what I was doing was rolling 3d6 for every ability score, and then randomly picking one to reroll if it wasn't high - regardless of the rest of the set. Which in hindsight obviously improves the average over natural valid sets. I may have gotten out of bed to write my macro, I'll have to take another look when I'm better rested - perhaps I can figure something more sound later.

Thanks guys, I'll give these a try and see which might work best for me. I've never played around with API before, so hopefully I can figure it out without much issue. 


I never thought that generating an ability score set that can guarantee at least one 14 would be so complicated to code. It makes me wonder how the Avrae discord bot handles it.


Jacob B. said:

I never thought that generating an ability score set that can guarantee at least one 14 would be so complicated to code. It makes me wonder how the Avrae discord bot handles it.


Wait a minute... Rerolling the set if there is no result greater than 14 is NOT the same as ensuring at least one 14+. Reroll if not greater than 14 implies at least one 15+. But at least one 14+ implies you reroll if no result is greater than 13. So which is it? My macro works on your original statement of reroll if none greater than 14, ie at least one 15+.

March 29 (1 week ago)
keithcurtis
Forum Champion
Marketplace Creator
API Scripter

Fates preserve us if you ever decide to turn your talents to evil, RainbowEncoder.

Well I couldn't come up with a smart way to do this like RainbowEncoder did, but I did figure out a very stupid way to do this:

[PRESS ME!](!/&NewLine;&amp;{template:default} {{name=Ability Scores}} &lbrack;&lbrack;1/&lbrack;&lbrack;{&lbrack;&lbrack;3d6&rbrack;&rbrack;,&lbrack;&lbrack;3d6&rbrack;&rbrack;,&lbrack;&lbrack;3d6&rbrack;&rbrack;,&lbrack;&lbrack;3d6&rbrack;&rbrack;,&lbrack;&lbrack;3d6&rbrack;&rbrack;,&lbrack;&lbrack;3d6&rbrack;&rbrack;}>14&rbrack;&rbrack; &rbrack;&rbrack; {{STR=$&lbrack;&lbrack;0&rbrack;&rbrack;}}{{DEX=$&lbrack;&lbrack;1&rbrack;&rbrack;}}{{CON=$&lbrack;&lbrack;2&rbrack;&rbrack;}}{{INT=$&lbrack;&lbrack;3&rbrack;&rbrack;}}{{WIS=$&lbrack;&lbrack;4&rbrack;&rbrack;}}{{CHA=$&lbrack;&lbrack;5&rbrack;&rbrack;}})

This macro will produce a button that will only produce valid sets of ability scores. It does this by not doing anything if the set would not be valid. Told you it was stupid.

March 29 (1 week ago)

Edited March 29 (1 week ago)


RainbowEncoder said:


Jacob B. said:

I never thought that generating an ability score set that can guarantee at least one 14 would be so complicated to code. It makes me wonder how the Avrae discord bot handles it.


Wait a minute... Rerolling the set if there is no result greater than 14 is NOT the same as ensuring at least one 14+. Reroll if not greater than 14 implies at least one 15+. But at least one 14+ implies you reroll if no result is greater than 13. So which is it? My macro works on your original statement of reroll if none greater than 14, ie at least one 15+.


Oh, I think I did mess up explaining it. The objective is to have at least one score that is 14+, not 15+. Is that going to be difficult to modify in your original code?


In my defense I think I originally posted this at like 11pm After trying to find a solution all evening.

Jacob B. said:
Oh, I think I did mess up explaining it. The objective is to have at least one score that is 14+, not 15+. Is that going to be difficult to modify in your original code?

Nah, just had to put in the new numbers. I've now edited the macro to be correctly 14+.

March 29 (1 week ago)

Edited March 31 (1 week ago)
timmaugh
Pro
API Scripter


keithcurtis said:

Fates preserve us if you ever decide to turn your talents to evil, RainbowEncoder.


Seconded.

That said, if I can jump in with a script solution just as an alternative:

!{&global ([Roll1] [[3d6]]) ([Roll2] [[3d6]]) ([Roll3] [[3d6]]) ([Roll4] [[3d6]]) ([Roll5] [[3d6]]) ([Roll6] [[3d6]]) }{\&if Roll1 < 14 && Roll2 < 14 && Roll3 < 14 && Roll4 < 14 && Roll5 < 14 && Roll6 < 14}%\(CharacterName.StatGen){\&else}{\&template:default}{{name=StatGen Results}} {{Roll 1=Roll1}} {{Roll 2 = Roll2}} {{Roll 3 = Roll3}} {{Roll 4 = Roll4}} {{Roll 5 = Roll5}} {{Roll 6 = Roll6}} {\&end}{&simple}

Save that as a character ability named "StatGen", and replace the "CharacterName" bit with the name of your character (alternately, this could be done with a macro. If that's what you need, I can easily modify).

This works by rolling 6 sets of 3d6, and defining them to be global variables of known identity (Roll1, Roll2, etc.). Then we test the values to be greater than 13. If none of them pass that test, we call the same ability over again... which will redefine a new set of rolls to be our known identities and test them again. Only when at least one of them passes the test does the ELSE case happen, which uses the last set of rolls in a template to display them.


If you wanted to know how many cycles this had to roll to get a "valid" result, that can be worked in, too. It would just require a little more setup.

REQUIRED SCRIPTS

Metascript Toolbox


Edit: to provide image of the result.

March 30 (1 week ago)
David M.
Pro
API Scripter

Another script solution that (1) might be a little easier to infer the logic and (2) see all of the roll sets (if more than one required). Requires the Scriptcards api script. I didn't bother sorting the rolls because it would be less intuitive to understand how the script works due to additional funky syntax.

!script {{
  --#title|Shadowdark Stat Generation
  --#noMinMaxHighlight|1

  --:Start|
  --:INITIALIZE VARIABLES|
  --&rolls|
  --=maxRoll|0

  --:INCREMENT SET NUMBER|
  --=setNum|[$setNum]+1

  --:ROLL 3d6 SIX TIMES WITHIN A LOOP| roll dice, update maxRoll, append roll results to string variable called rolls
  --%i|1;6;1
      --=roll|3d6
      --?[$roll] -gt [$maxRoll]|=maxRoll;[$roll]
      --&rolls|[&rolls][$roll]
  --%|

  --:DISPLAY ROLLS|
  --+Set[$setNum]|[&rolls]

 --:IF ALL ROLLS UNDER 14 GOTO START|
  --?[$maxRoll.Raw] -lt 14|Start
}}

Example output: