You need to use a function with a sequence (or number) parameter in AnyDice when…
…you want to use the result of a die roll in an if condition.
AnyDice doesn't let you use a dice roll in the condition part of an if statement. For example, the following function will trigger an error if called with a die roll:
function: clamp NUMBER between MIN and MAX {
if NUMBER < MIN { result: MIN }
if NUMBER > MAX { result: MAX }
result: NUMBER
}
output [clamp 2d6 between 4 and 10] \ <-- this triggers a calculation error
If you try to run the code above, all AnyDice will output is a red error message box saying:
calculation error
Boolean values can only be numbers, but you provided "d{?}".
Depending on what you want, you might need to create a function.
To make this code work as expected, you need to tell AnyDice that all these parameters should be converted to numbers when the function is called (and, if necessary, the function should be called multiple times with all the possible values the parameters may have), like this:
function: clamp NUMBER:n between MIN:n and MAX:n {
if NUMBER < MIN { result: MIN }
if NUMBER > MAX { result: MAX }
result: NUMBER
}
output [clamp 2d6 between 4 and 10] \ <-- this works!
Of course you can sometimes work around this problem by using combinations of AnyDice built-in functions and/or operators, such as:
function: clamp NUMBER between MIN and MAX {
result: [highest of MIN and [lowest of MAX and NUMBER]]
}
…but this is really just doing the same thing "under the hood". In particular, if you wanted to reimplement the built-in functions [highest of NUMBER and NUMBER] and [lowest of NUMBER and NUMBER] yourself, you'd have to tag their parameters with :n to make them work like the AnyDice built-ins work.
…you want to "freeze" the roll so it has a fixed, definite result.
In AnyDice, each dice roll is treated as independent. For example, the following code does not always output zero, but rather outputs the difference between two separate 2d6 rolls:
output 2d6 - 2d6 named "not always zero"
What might be less obvious is that this is also true for dice-valued variables; every time the variable is evaluated, AnyDice treats it as a separate roll:
DICE: 2d6
output DICE - DICE named "still not always zero"
And the same even also true of function parameter that are of the "die" type:
function: diff DICE { result: DICE - DICE }
output [diff 2d6] named "also not always zero"
However, variables and function parameters with numeric or sequence values do have definite values. If you tell AnyDice that your function parameters should be numbers (or sequences), AnyDice will convert any dice rolls to the required type, possibly calling the function multiple times for each possible result of rolling the dice:
function: diff DICE:n { result: DICE - DICE }
output [diff 2d6] named "this is always zero"
…you want to inspect or loop over the specific numbers rolled.
There's only a limited number of (useful) things that AnyDice will let you do with a multi-dice pool such as 3d6 without passing it to a function:
- You can sum it into a single weighted die. This is what AnyDice automatically does when you output such a value or use it in an arithmetic expression or in a comparison.
- You can extract the distribution of the N-th highest die (or even the sum of the M-th highest, N-th highest, etc.) in the pool with the
@ operator.
- You can get the number of dice in the pool with the
# operator (which is occasionally useful, but not very often).
Meanwhile, things you cannot do include:
- Counting the number of times a specific result or results was rolled (although there's a built-in function for that).
- Comparing e.g. the highest and the second-highest result in the roll. (Using e.g.
1@DICE - 2@DICE does not work for that, since as noted above, the two evaluations of DICE are treated as separate rolls!)
- Looping over the numbers rolled. (If you try to do e.g.
loop N over 2d6 { ... }, you'll just get a calculation error saying "A variable must loop over a sequence, but you provided "2d6".")
However, if you pass the dice into a (custom or built-in) function that is defined to take a sequence parameter, AnyDice will call the function once for each possible sequence of results the dice can roll (sorted in descending order by default) and collect the results returned by the function into a custom die weighted by their probability.
And with sequences, you can do a lot more than with dice, including all the things listed above!
For example, let's say you wanted to model a mechanic where the player rolls a number of 6-sided dice and counts the number of sixes plus the number of pairs of dice that sum to six. How would you do that in AnyDice?
Well, the only way is to write a function that takes a sequence parameter, e.g. like this:
function: count ROLL:s {
result: (ROLL = 6)
+ [lowest of (ROLL = 5) and (ROLL = 1)]
+ [lowest of (ROLL = 4) and (ROLL = 2)]
+ (ROLL = 3) / 2
}
output [count 3d6] named "sixes and pairs summing to six"
Or what about a mechanic where you roll two or more dice and your result is the difference between the lowest and the highest number rolled? Like this:
function: delta ROLL:s {
result: 1@ROLL - (#ROLL)@ROLL
}
loop N over {2..6} {
output [delta Nd6] named "[N]d6 highest - lowest"
}
Or how about one where you roll two pools of dice and count the number of matching pairs between the two pools (using each die in only one pair). You can do that like this:
function: match ROLL_A:s and ROLL_B:s {
COUNT: 0
DISTINCT: {d ROLL_A} \ <-- a quick way to remove duplicates from a sequence! \
loop X over DISTINCT {
COUNT: COUNT + [lowest of (ROLL_A = X) and (ROLL_B = X)]
}
result: COUNT
}
output [match 3d6 and 4d6]
None of these mechanics can be modelled in AnyDice, except by using a function that takes (one or more) sequence parameters and passing dice rolls into them, because they all involve either inspecting the results of the roll in ways that AnyDice only supports for sequences, or using the result of a single multiple times in the code (without having it be interpreted as multiple independent rolls), or both.