# AnyDice function to use a die as high/low flip and critical

I've been developing a homebrew system that utilizes 3d12 and I've been wanting to get the probabilities plotted so I could, for one, see how viable such a system would work and, two, be able to work out how skills and attributes would work. The purpose of 3d12 is purely thematic, as almost everything revolves around treating "3" as a sacred number in-game.

I'm still learning to utilize AnyDice. I had borrowed a function I found online that was designed, originally, to roll 2d6 and then a coin flip, the result of that flip being for either taking the highest or lowest result.

I tried to emulate it, but for my purposes, I'm trying to create a function that will roll 3d12, but one of the d12 is a decider die similar to the coin. Should the decider die be odd, you take the lowest of the other two d12. Evens, you take the highest.

A caveat to the problem is how I handle criticals. If the two main dice were equal (doubles), then they are totaled instead of choosing highest or lowest. And should the decider die also match (triples), this counts as a "critical" and the third die also gets added.

The following is the frankenfunction I tried to pull together from what I found online and through documentation, but I'm clearly an idiot because it used to actually resolve; I've now somehow managed to make it so this produces a syntax error:

function: well of DICE:d flip COIN:n
{
if (COIN = 1, 3, 5, 7, 9, 11)
{
result: [lowest 1 of DICE]
}
result: [highest 1 of DICE]
}

loop N over {2..5}
{
output [well of Nd12 flip d12] named "[N]d12"
}

output [well of 2d12 flip d12] named "2d12 biased"

• Doesn't this produce the exact same results as: roll a d12, keep that, roll another if it matches, add it and roll a third, adding that if it also matches"? – user3294068 Dec 14 '16 at 20:54
• I'm voting to close this question as off-topic because the question isn't about P&P RPGs and is only tangentially related, in that Anydice is used in some cases to calculate dice odds for P&P RPGs. This question requires no expertise in table-top role-playing games to answer. The question is about Anydice syntax, and not about dice rolling odds or their impact on an RPG system. – LegendaryDude Dec 14 '16 at 21:08
• @LegendaryDude The practical observation is that we do have many of these questions, so it follows that at least at the moment, they are de facto on topic unless made de jure off topic. – SevenSidedDie Dec 14 '16 at 22:10
• @LegendaryDude This is probably a matter for meta to handle. We do, as SSD points out, nevertheless handle a lot of anydice questions because of its relevance to RPGs. – doppelgreener Dec 15 '16 at 0:57
• @LegendaryDude Another point is that we are the experts on using AnyDice to analyse RPG dice mechanic designs, like this one. SO would not likely result in anything useful, since the required expertise is domain-specific rather than general programming. (SO currently has a single post that only mentions AnyDice only in passing.) – SevenSidedDie Dec 15 '16 at 1:24

The syntax error comes in that AnyDice doesn't (like C-family languages) wrap if conditions in parens. In fact, its surface syntax is misleading; it shares very little with those languages. Also, to compare against a list, you have to turn it into a sequence with {}.

You were going to need a wrapper function as well, to reify the individual rolls. (Unlike most languages, AnyDice can easily have variables within a function that can only be handled usefully by passing to another function.) Here's how I did it with tweaks suggested by Ilmari Karonen.

function: H:n or L:n per COIN:n {
if COIN = (COIN/2)*2 {
R: H
}
else {
R: L
}
if L = H {
\ Doubles means keep both \
R: L + H
if L = COIN {
\ Triples adds the decider die as well \
R: R + COIN
}
}
result: R
}

function: well of DICE:s flip COIN:n
{
result: [1@DICE or 2@DICE per COIN]
}

output [well of 2d12 flip d12] named "2d12 biased"
output [well of 2d12 flip 1] named "2d12 always lowest"
output [well of 2d12 flip 2] named "2d12 always highest"


The graphed curves are, of course, rather peculiar-looking, but I think it's correct. There are dips and corresponding bumps where the doubles shift occurrences up.

• I think you have a bug in your second function; it should take its first argument as a sequence, not as a die, e.g. function: well of DICE:s flip COIN:n { result: [1@DICE or 2@DICE per COIN] }. In your current code, the two instances of DICE inside the function are treated as independent rolls, so that it's even quite possible that [highest 1 of DICE] < [lowest 1 of DICE]. With that bug fixed, your results agree with mine. – Ilmari Karonen Dec 17 '16 at 0:39
• @IlmariKaronen: Making the change makes the d1 test case fail badly by not exhibiting any bias at all. So I don't think that works. (I suspect you're right that the rolls are treated independently, though, so I'll work on fixing that some other way.) – user17995 Dec 17 '16 at 0:54
• Strange; it seems to work for me. – Ilmari Karonen Dec 17 '16 at 1:09
• @IlmariKaronen: Ah, the accessing trick makes the difference. Thanks. – user17995 Dec 17 '16 at 1:17
• Yeah, it's really counterintuitive that [highest 1 of SEQ] yields the sum of the sequence SEQ in AnyDice. Technically, it works exactly how the documentation says it does (the highest NUMBER of DICE function expects a die, so the sequence will be summed and converted into a one-sided die before the function even sees it), but that doesn't make it any less weird. – Ilmari Karonen Dec 17 '16 at 1:25

Here's a simpler way to implement this mechanic in AnyDice:

function: combine A:n and B:n using C:n {
if A = B & B = C { result: A+B+C }
if A = B { result: A+B }
if C = 2*(C/2) { result: [highest of A and B] }
else { result: [lowest of A and B] }
}

output [combine d12 and d12 using d12]


The code should be fairly self-explanatory, although it does rely on two slightly obscure (but quite important) details about how AnyDice functions work:

1. assigning anything to result ends the function immediately, and
2. if a function expecting one or more numbers as parameters (as marked by the :n after the parameter name) is called with dice as those parameters, the function will be evaluated for every possible combination of the dice, and the results will be weighted (resulting in a new, biased die) according to the probability of each combination.

So that's the general trick to evaluating the results of a dice roll in unusual ways in AnyDice: just pass dice into a function that expects a number (or a sequence), and figure out the result of the roll inside the function.

(The C = 2*(C/2) might need a bit of explanation, too. AnyDice doesn't have a remainder / modulo operator like % in C, but it does have an integer division operator / that rounds down the result. So an easy way to check whether a number is even is to divided it by 2, rounding down, and then multiply it by 2 again and check if the result equals the original number.)

Anyway, if you look at the graph of the results, you'll notice that the resulting distribution is very close to a simple d12 roll, expect that there's a small chance of the result being doubled, and an even smaller chance of it being tripled.

In fact, it turns out that the following formula generates the exact same distribution:

output d12 * d{3, 2:11, 1:(11*12)}


The d{3, 2:11, 1:(11*12)} thing is a biased die that rolls a three with probability 1/144, a two with probability 11/144, and a one with probability 132/144. Those are exactly the probabilities of rolling a "triple critical", a "normal critical" and a non-critical roll in your system.

Thus, the "highest if even, lowest if odd" mechanic effectively makes no difference; only the criticals distinguish your system from a straight 1d12 roll. In fact, if you remove the first two if statements from the combine A:n and B:n using C:n function I gave above, you'll see that the results will be identical to a simple 1d12.