AnyDice error: Boolean values can only be numbers

anydice

I'm trying to write code that helps calculate the probability of success.
Negative luck adds cursed dice to the bonus dice pool, which in the case of a crit burns additional dice from the skill level.
If the bones are completely burned and there is still one -1, then the skill check failed.
Anydice issues:

calculation error
Boolean values can only be numbers, but you provided "d{?}".
Depending on what you want, you might need to create a function.

complaining about logic checks

CHARACTERISTIC_BONUS: 2
SKILL_LEVEL: 4
LUCK_LEVEL: 1
CORRUPTED_LEVEL: -1
SKILL_DICE: d{0,0,0,1}
CORRUPTED_DICE: d{-1,0,0,-1}
ROLL_LEVEL: 6


function: sand {
 if CORRUPTED_LEVEL< 0 
 {
  ADDITIONAL_DICE_POOL: SKILL_LEVELdSKILL_DICE + LUCK_LEVELdCORRUPTED_DICE
 }
 else 
 {
  ADDITIONAL_DICE_POOL: SKILL_LEVELdSKILL_DICE
 }
 result: ADDITIONAL_DICE_POOL
}


function: additionalcheck 
{
 if [sand] < 0{
  result: 1
 }
 else{
  result: 0
 }
}

function: skillcheck {
 RL_TEMP: 0
if [additionalcheck] < 0 {
 RL_TEMP: 0
}
else{
  RL_TEMP: 0
  ROLL_RESULT: 1d12 + CHARACTERISTIC_BONUS
  if ROLL_RESULT > ROLL_LEVEL {
   
   RL_TEMP: 1
  }
}
 result: RL_TEMP
}

output [skillcheck]

There are unnecessary elements in the code, but I will be able to fix them only when I understand the principle of anydice

Best Answer

The error message means what it says — you can't do something like:

if d2 = 1 { \ do something \ } else { \ do something else \ }

because AnyDice would have to somehow execute both paths, since d2 can be either 1 or 2, and combine the resulting arbitrary program states into some kind of weird "quantum superposition". AnyDice is pretty clever, but it's not that clever.

In your program, [sand] returns a die. When you do if [sand], AnyDice would somehow have to execute one block of code when [sand] is non-zero (i.e. true) and another when it is zero (i.e. false) — but, since it's a die, AnyDice cannot tell which it is. Thus the error.

However, for simple cases (such as choosing the value of a variable based on a die roll) there's a work-around. All you need is a helper function like this:

function: if CONDITION:n then A else B {
  if CONDITION { result: A } else { result: B }
}

FOO: [if d2 = 1 then 42d6 else 23 + 17d8]

Wait, so why does it work inside the helper function, then?!?

The answer is the little :n marker at the end of the parameter name CONDITION in the function definition. That :n tells AnyDice to do whatever it needs to do in order to convert that parameter into a number before running the function, no matter what the actual value given in the function call is:

  • If the given value is already a number, AnyDice just passes it to the function unchanged.
  • If the given value is a sequence, AnyDice will sum it and pass the sum to the function.
  • If the given value is a die, AnyDice will call the function for every possible (summed) value of the die and collect the results of all those function calls into a single custom die, weighted according to their probabilities!

So, basically, in the last case AnyDice does manage to create the "quantum superposition"* of all possible results of running the function for different rolls of the given die!

But it can only do that for functions because AnyDice functions are deliberately designed so that they cannot have externally visible side effects: no matter what happens inside the function, the only observable thing that comes out of the function is a single number (or die). (For example, if you try to change the value of an external variable inside a function, it will revert back to its original value when the function ends.) And AnyDice knows how to represent superpositions of different possible numbers as discrete probability distributions (a.k.a. "dice").


*) It's actually just a plain old non-quantum statistical superposition — no complex numbers are involved. But the general principle is in fact the same.


Anyway, the main limitation of the helper function above is that it only works for choosing between two different expressions (like 42d6 and 23 + 17d8 in the example above). An actual if statement can do more complex things, since it can choose between two completely different block of program code, which could even include things like different output statements.

For example, in your sample program you have the following code (which I've re-indented for clarity):

if [additionalcheck] < 0 {
  RL_TEMP: 0
} else {
  RL_TEMP: 0
  ROLL_RESULT: 1d12 + CHARACTERISTIC_BONUS
  if ROLL_RESULT > ROLL_LEVEL {
    RL_TEMP: 1
  }
}

Obviously, you can't just directly convert that into the [if CONDITION then A else B] form. But we can, in fact, rearrange it so that it can be converted. The trick is to move everything except the final assignment to RL_TEMP outside the if blocks, like this:

ROLL_RESULT: 1d12 + CHARACTERISTIC_BONUS
if [additionalcheck] < 0 {
  RL_TEMP: 0
} else {
  if ROLL_RESULT > ROLL_LEVEL {
    RL_TEMP: 1
  } else {
    RL_TEMP: 0
  }
}

and then convert that to something like this:

ROLL_RESULT: 1d12 + CHARACTERISTIC_BONUS
RL_TEMP: [if [additionalcheck] < 0 then 0 else [if ROLL_RESULT > ROLL_LEVEL then 1 else 0]]

In this particular case, we can in fact simplify that further. First, we can combine the two nested conditions using the & operator:

ROLL_RESULT: 1d12 + CHARACTERISTIC_BONUS
RL_TEMP: [if [additionalcheck] >= 0 & ROLL_RESULT > ROLL_LEVEL then 1 else 0]

It also turns out that [if CONDITION then 1 else 0] is actually redundant, since AnyDice already represents "true" and "false" as the numbers 1 and 0. So all we really need is:

ROLL_RESULT: 1d12 + CHARACTERISTIC_BONUS
RL_TEMP: [additionalcheck] >= 0 & ROLL_RESULT > ROLL_LEVEL

In general, if CONDITION could take values other than 0 and 1, we'd also need to force any non-zero values to 1. In that case probably the simplest expression equivalent to [if CONDITION then 1 else 0] would be CONDITION != 0. (Technically, if CONDITION could be a sequence, we'd actually need to use something like CONDITION + 0 != 0 or !!CONDITION.) But the AnyDice logical operators & and | are already guaranteed to always yield 0 or 1 (or dice with 0 and 1 as the only possible values) — as are comparison operators like > when applied to numbers — so we don't need to do that here.