5
\$\begingroup\$

I'm trying to model my RPG system in Anydice. The rules are:

Roll 3d20. A die that rolls under your stat is a success. If you have advantage, you may reroll a failed die. If you have disadvantage, you must reroll a success, if there were any. Harder tasks require more successes.

I've been working on this for an hour and can't figure out how to make it work. I tried modeling this literally in the code, but it came back with up to 60 successes instead of 3, so I clearly did something wrong. link to program

ABILITY: 10

function: set element I:n in SEQ:s to N:n {
  NEW: {}
  loop J over {1 .. #SEQ} {
    if I = J { NEW: {NEW, N} }
    else { NEW: {NEW, J@SEQ} }
  }
  result: NEW
}

function: reroll greatest of ROLL:s as REROLL:n if greater than TARGET:n {
  GREATEST: 1@ROLL       \ the GREATEST die is sorted first \
  if GREATEST < TARGET {
    result: ROLL
  } else {
    result: [set element 1 in ROLL to REROLL]
  }
}

output [reroll greatest of 3d20 as 1d20 if greater than ABILITY]

After working on it for some time, I think I should be able to model advantage as 4d20 take the three highest, and disadvantage as 4d20 take the three lowest. But since the highest function sums the dice sequences, I don't know how to tell Anydice to do this.

\$\endgroup\$
5
\$\begingroup\$

I was able to solve this with help from Someone_Evil's comment.

have the function work out the number of successes, which we can handily do by comparing our ROLL sequence to our TARGET number.

I didn't know AnyDice could do this, so now I have a working program.

function: reroll greatest of ROLL:s as REROLL:n if greater than TARGET:n {
  SUCCESSES: ROLL <= TARGET
  if SUCCESSES < #ROLL {
    result: SUCCESSES + (REROLL <= TARGET)
  }
  else {
   result: SUCCESSES
  }
}

function: reroll least of ROLL:s as REROLL:n if less than TARGET:n {
  SUCCESSES: ROLL <= TARGET
  if SUCCESSES > 0 {
    result: SUCCESSES - 1 + (REROLL <= TARGET)
  }
  else {
   result: 0
  }
}


loop N over {8, 10, 12, 14, 16, 18} {
  output [reroll greatest of 3d20 as 1d20 if greater than N] named "Advantage [N]"
  \output [count {1..N} in 3d20] named "Normal [N]"\
  \output [reroll least of 3d20 as 1d20 if less than N] named "Disadvantage [N]"\
}
\$\endgroup\$
4
\$\begingroup\$

Your advantage and disadvantage mechanics are actually equivalent to the following ones:

  • Advantage: Roll 4d20 and take the lowest 3 rolls. Any roll under your stat is a success.
  • Disadvantage: Roll 4d20 and take the highest 3 rolls. Any roll under your stat is a success.

or even to the following ones:

  • Advantage: Roll 4d20 and count any roll under your stat as a success. Any number of successes greater than 3 counts as 3.
  • Disadvantage: Roll 4d20 and count any roll under your stat as a success, then subtract one success. Any number of successes less than 0 counts as 0.

The nice thing about these alternative formulations is that they're significantly easier and more efficient to implement in AnyDice, while still giving the same results:

loop N over {8, 10, 12, 14, 16, 18} {
  X: d20 <= N
  output [lowest of 3 and 4dX] named "Advantage [N]"
  output 3dX named "Normal [N]"
  output [highest of 0 and 4dX-1] named "Disadvantage [N]"
}

One generally useful performance trick I'm using here is defining a custom die X that rolls 1 with the probability of a d20 roll being a success (i.e. <= N) and 0 otherwise. You can think of it as a relabeled d20, with the successful sides labeled as 1 and the unsuccessful sides relabeled as 0.

The reason this helps with performance is that, when you then tell AnyDice to consider a roll of a pool of these dice, AnyDice doesn't have to separately examine each possible combination of e.g. 4d20, from (1, 1, 1, 1), (1, 1, 1, 2), (1, 1, 1, 3) and so on all the way to (20, 20, 20, 20). Rather, it just needs to examine the possible numbers of successes and failures, of which there are only five (0 to 4).


Ps. The same trick can also be used to optimize your original program:

function: reroll one failure in ROLL:s as REROLL:n {
  if 0 = ROLL {
    result: ROLL + REROLL
  } else {
    result: ROLL
  }
}

function: reroll one success in ROLL:s as REROLL:n {
  if 1 = ROLL {
    result: ROLL - 1 + REROLL
  } else {
    result: ROLL
  }
}


loop N over {8, 10, 12, 14, 16, 18} {
  X: d20 <= N
  output [reroll one failure in 3dX as 1dX] named "Advantage [N]"
  output 3dX named "Normal [N]"
  output [reroll one success in 3dX as 1dX] named "Disadvantage [N]"
}

(Note that comparing a number and a sequence in AnyDice checks whether any number in the sequence satisfies the comparison, so e.g. if 0 = ROLL means "if ROLL contains any 0".)

\$\endgroup\$
0
\$\begingroup\$

The issue you're running into here is that AnyDice loves summing numbers, especially when outputing. The output you're getting is the sum of the dice, not the number of successes (hence going up to 60). The easiest way around this, is to have the function work out the number of successes, which we can handily do by comparing our ROLL sequence to our TARGET number. Also handily, to not have to worry about actually shuffling around the sequence to get the reroll, we can simply add the new roll and subtract a 1 (to compensate for the roll that should be removed). We also know that if we don't reroll, we have no successes. The resulting function then becomes:

function: reroll greatest of ROLL:s as REROLL:n if greater than TARGET:n {
   if 1@ROLL >= TARGET {
      result: (ROLL >= TARGET) + (REROLL >= TARGET) -1
   } else {
      result: 0
   }
}

Which we can test over a range of targets (you'll want to limit the number in that range so it doesn't time out):

loop N over {10,12,14,16,18,20} {
  output [reroll greatest of 3d20 as 1d20 if greater than N] named "[N]"
}

And see the transposed graph to see how the probability of a given number of sucesses vary with the target.

enter image description here

\$\endgroup\$
2
  • \$\begingroup\$ This doesn't quite solve my problem. It's a roll-under system, so higher TARGETs should have more likely successes. Also, I'm not sure what your solution is meant to be rerolling, but "if we don't reroll, we have no successes", isn't right. There may be 0 to 3 successes, and if there's < 3 successes (for 3d20) then we reroll one of the failures. It looks like your solution rerolls the greatest die if it failed, but doesn't account for the first die succeeding while others fail, which should be rerolled. Your comment did help me make my own solution though, so thank you. \$\endgroup\$ Jul 5 at 12:52
  • \$\begingroup\$ @decon_structed Oh, I think I both missed the roll under aspect and assumed the issue you were facing was with modeling the disadvantage roll, but am I right that the code is for the advantage roll? \$\endgroup\$
    – Someone_Evil
    Jul 5 at 13:03

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.