Skip to main content
deleted 8 characters in body
Source Link
HighDiceRoller
  • 7k
  • 1
  • 21
  • 43

A more feasible way to handle this stochasticallyrandom-sized dice pool is to first determine how many defense dice (including explosions) fell into each of three categories:

A more feasible way to handle this stochastically-sized dice pool is to first determine how many defense dice (including explosions) fell into each of three categories:

A more feasible way to handle this random-sized dice pool is to first determine how many defense dice (including explosions) fell into each of three categories:

added 63 characters in body
Source Link
HighDiceRoller
  • 7k
  • 1
  • 21
  • 43

Unfortunately, AnyDice times out beyond around 4 dice on each side. Here's the same strategy in my own Icepool Python probability package:, which can compute larger pools in a reasonable amount of time.

Unfortunately, AnyDice times out beyond around 4 dice on each side. Here's the same strategy in my own Icepool Python probability package:

Unfortunately, AnyDice times out beyond around 4 dice on each side. Here's the same strategy in my own Icepool Python probability package, which can compute larger pools in a reasonable amount of time.

deleted 4 characters in body
Source Link
HighDiceRoller
  • 7k
  • 1
  • 21
  • 43

"Prerolling""Pre-rolling" large numbers of dice in their original order is too costly

Unfortunately, there's no easy way in AnyDice to get RR in thetheir original order they would be rolled. Even if you could, it would make the calculation much more expensive. For 20d6, this would increase the number of possibilities for RR

  • Those that rolled a 6.
  • Those that rolled 2-5 ("mids"). We don't immediately determine exactly what number these dice rolled, only how many dice rolled within this range. These mids could come from either the initial level or the post-explosion levels.
  • Those that rolled a 1. (Ones1s can only occur post-explosion, since all initial-level 1s are rerolled. If the initial level were the same as the exploding levels, we wouldn't need to go through this whole binning process.)

Note that we choose to split between 2-5s and 1s instead of splitting between initial-level 2-5s and exploding-level 1-5s. This is because a pool of flat 1s doesn't expand into multiple possibilities. Minimizing the number of non-trivial pools is important for efficiency; the "pre-rolling" problem above can be seen as the difference between one pool of 20 dice and twenty pools of 1 die each.

def compute_attack(att_countatk_count, def_count):
    att_poolatk_pool = d(6).pool(att_countatk_count)
    def_pool = (def_count @ def_die).map_to_pool(make_def_pool)
    return att_poolatk_pool.sort_pair('>', def_pool, extra='keep').highest(1).sum()
  • att_poolatk_pool is just a pool of d6s.
  • Since Vectors add component wise, we can roll our desired number of def_die and add them together to get the total numbers of 1s, 2-5s, and 6s across them. This is what def_count @ def_die does. Note that depth effectively applies to each individual defense die.
  • Then we use map_to_pool and make_def_pool to convert this binning into a Pool.
  • sort_pair effectively sorts both pools, pairs them up 1:1 in descending order like RISK, keeps the elements from att_poolatk_pool that beat their corresponding defense die, and keeps the single highest. (If all attacker dice were eliminated, nothing gets summed and the result is 0.) Internally, this line uses a dynamic programming algorithm instead of actually enumerating all possible sorted rolls of the two pools, but the details are a much longer story.

You can try this in your browser here.You can try this in your browser here.

"Prerolling" large numbers of dice in their original order is too costly

Unfortunately, there's no easy way in AnyDice to get RR in the order they would be rolled. Even if you could, it would make the calculation much more expensive. For 20d6, this would increase the number of possibilities for RR

  • Those that rolled a 6.
  • Those that rolled 2-5 ("mids"). We don't immediately determine exactly what number these dice rolled, only how many rolled within this range.
  • Those that rolled a 1. (Ones can only occur post-explosion, since all initial-level 1s are rerolled. If the initial level were the same as the exploding levels, we wouldn't need to go through this whole binning process.)
def compute_attack(att_count, def_count):
    att_pool = d(6).pool(att_count)
    def_pool = (def_count @ def_die).map_to_pool(make_def_pool)
    return att_pool.sort_pair('>', def_pool, extra='keep').highest(1).sum()
  • att_pool is just a pool of d6s.
  • Since Vectors add component wise, we can roll our desired number of def_die and add them together to get the total numbers of 1s, 2-5s, and 6s across them. This is what def_count @ def_die does. Note that depth effectively applies to each individual defense die.
  • Then we use map_to_pool and make_def_pool to convert this binning into a Pool.
  • sort_pair effectively sorts both pools, pairs them up 1:1 in descending order like RISK, keeps the elements from att_pool that beat their corresponding defense die, and keeps the single highest. (If all attacker dice were eliminated, nothing gets summed and the result is 0.) Internally, this line uses a dynamic programming algorithm instead of actually enumerating all possible sorted rolls of the two pools, but the details are a much longer story.

You can try this in your browser here.

"Pre-rolling" large numbers of dice in their original order is too costly

Unfortunately, there's no easy way in AnyDice to get RR in their original order. Even if you could, it would make the calculation much more expensive. For 20d6, this would increase the number of possibilities for RR

  • Those that rolled a 6.
  • Those that rolled 2-5 ("mids"). We don't immediately determine exactly what number these dice rolled, only how many dice rolled within this range. These mids could come from either the initial level or the post-explosion levels.
  • Those that rolled a 1. 1s can only occur post-explosion, since all initial-level 1s are rerolled. If the initial level were the same as the exploding levels, we wouldn't need to go through this whole binning process.

Note that we choose to split between 2-5s and 1s instead of splitting between initial-level 2-5s and exploding-level 1-5s. This is because a pool of flat 1s doesn't expand into multiple possibilities. Minimizing the number of non-trivial pools is important for efficiency; the "pre-rolling" problem above can be seen as the difference between one pool of 20 dice and twenty pools of 1 die each.

def compute_attack(atk_count, def_count):
    atk_pool = d(6).pool(atk_count)
    def_pool = (def_count @ def_die).map_to_pool(make_def_pool)
    return atk_pool.sort_pair('>', def_pool, extra='keep').highest(1).sum()
  • atk_pool is just a pool of d6s.
  • Since Vectors add component wise, we can roll our desired number of def_die and add them together to get the total numbers of 1s, 2-5s, and 6s across them. This is what def_count @ def_die does. Note that depth effectively applies to each individual defense die.
  • Then we use map_to_pool and make_def_pool to convert this binning into a Pool.
  • sort_pair effectively sorts both pools, pairs them up 1:1 in descending order like RISK, keeps the elements from atk_pool that beat their corresponding defense die, and keeps the single highest. (If all attacker dice were eliminated, nothing gets summed and the result is 0.) Internally, this line uses a dynamic programming algorithm instead of actually enumerating all possible sorted rolls of the two pools, but the details are a much longer story.

You can try this in your browser here.

deleted 4 characters in body
Source Link
HighDiceRoller
  • 7k
  • 1
  • 21
  • 43
Loading
deleted 2 characters in body
Source Link
HighDiceRoller
  • 7k
  • 1
  • 21
  • 43
Loading
Source Link
HighDiceRoller
  • 7k
  • 1
  • 21
  • 43
Loading