Have highest die be rerolled if a 1 is rolled in AnyDice

anydice

The system uses a dice pool of d4s of up to 3 dice (3d4 is the maximum number of dice you can roll). If at least a 4 comes up in the pool, that's a success. If the highest number is a 3, it's a partial success. If the highest is lower than 3 it's a failure.

Output [count {3..4} in 3d4]

I was able to calculate that with ease, but here's the part I am stuck with: If you get any number of 1s in your pool, the highest die is rerolled if it's a 3 or a 4. The second result is kept. Examples:

  • You roll 2d4 and get 4, 1. Since you got a 1, you reroll the 4 and keep the second result. We get a 3, which means the result is a partial success instead of a success.

  • You roll 3d4 and get 3, 1, 1. Since there's a 1, you reroll the 3 and get 4. Lucky you, you got a success instead of a partial.

How can I code this in AnyDice to accurately calculate a player's chance of success, partial success, or failure?

Best Answer

You need a function, but no loops :)

As Someone_Evil correctly notes, the way to do any non-trivial inspection and manipulation of the results of a roll in AnyDice is to pass that roll into a function expecting a sequence (i.e. a parameter tagged with :s). When you do that, what AnyDice does is it calls the function for every possible (sorted) outcome of the roll and collects the results, weighted by their probability, into a new custom die.

So what should your function look like? Something like this (modulo a misreading of the question; see correction below), for example:

function: test ROLL:s {
  if !(ROLL = 1) { result: 1@ROLL }
  result: [highest of d4 and 2@ROLL]
}

output [test 3d4]

OK, let's unpack that a bit.

First of all, ROLL = 1 compares a number (1) with a sequence (ROLL), returning the number of values in the sequence that match. Normally an if statement in AnyDice treats 0 as false and anything non-zero as true, but the ! operator negates that, turning 0 to 1 and anything else to 0. So if !(ROLL = 1) executes the following block only if ROLL contains no ones.

Inside the block, we just return the first element of the sequence ROLL. We know the sequence is sorted in descending order (because that's what AnyDice does when you convert a die to a sequence in a function call), so the first element is the highest. Note that AnyDice will stop running the function as soon as it sees a result: statement, so the second line of code in the function will not be run in this case.

If ROLL includes some ones, however, the !(ROLL = 1) expression will be false and the block after it will not run, so AnyDice instead continues on to the second line. In this case, we return the highest of d4 and 2@ROLL, which is the second-highest value originally rolled.

Why those? Well, imagine what you'd do when rolling the dice by hand. You'd first roll them all, and then observe that you indeed rolled a 1. Now you can safely set aside all but the highest two rolled dice, since those can never be the final result. Then you reroll the higher of the remaining two dice, and take the highest of the two after that.

And that's what the code does.

(BTW, you might be wondering what happens if you call the function above with 1d4, so that ROLL has only one element. In fact, it still works, though only by a bit of a coincidence. What happens in that case is that 2@ROLL evaluates to 0, since that what AnyDice gives you if you ask for an element past the end of a sequence. And since d4 is always higher than 0, the function just ends up rerolling the single die if it's originally a 1.)


Correction: When writing the answer above, I completely missed the latter part of the "the highest die is rerolled if it's a 3 or a 4" rule, so the code above doesn't implement it. We can fix it easily, though:

function: test ROLL:s {
  if !(ROLL = 1 & 1@ROLL = {3,4}) { result: 1@ROLL }
  result: [highest of d4 and 2@ROLL]
}

This version runs the { result: 1@ROLL } block unless the roll contains any ones and the first (i.e. highest) number in the roll is 3 or 4.

(1@ROLL = {3,4} is a fairly literal translation of the "it's a 3 or a 4" rule. Of course we could've euivalently written it e.g. as 1@ROLL >= 3 or even — since having any number in the roll ≥ 3 is equivalent to the highest number being ≥ 3 — as ROLL >= 3. We can't write it as ROLL = {3,4}, though; that's a sequence-to-sequence comparison, and those work differently.)