# What is incorrect with my Anydice logic for 'Weapons of the Gods' roll mechanics?

While scouting Actual Play write-ups, I have encountered a reference for an interesting dice-rolling mechanic.

For a test, players roll a pool (3-7) of d10 (but 0..9 not 1..10). The value coming out the most (and the highest one among these) becomes the result's ones digit. The number of dice showing that value: it's tens digit. So (1, 3, 5, 5, 5, 7, 7) results in 35 (three fives).

(can't find a RAW quote, my own interpretation of someone else's own interpretation)

I instantly wanted to see how the results distribution curve would look like. My Anydice code for that:

DICE: {0..9}

function: weapon ROLL:s {
NUM: [maximum of ROLL]

SUM: 10 + NUM

loop I over DICE{
B: [count I in ROLL]
I_ROLL: 10*B + I
if B > 0 & I_ROLL >= SUM {
SUM: I_ROLL
}
}
result: SUM
}

N: 4
output [weapon NdDICE] named "Weapon of the Gods [N]D10"


My problem is: the distribution shows different probability percentages for results that I thought should come out the same. For example, for N = 4 (rolling 4d10), it shows 41 (four ones) will come out 0.57% of the time, while 45(four fives) - 0.05%.

What is wrong with my Anydice code, or, conversely, with my understanding of the mechanic (is there a reason for ones to be more statistically probable than fives)?

• Could you explain what NUM: [maximum of ROLL] is used for? Commented Jan 28, 2016 at 15:03
• That's a basic point for comparison. If a roll has no repeating values in it at all - (1,2,3,4) gives a result of 14(one four), (2,4,6,7) - a result of 17(one seven). Later loop sees if there are any combinations better than that in the roll, but if it doesn't find those - it uses a base point. I could have actually used a zero for that, probably.
– Nox
Commented Jan 28, 2016 at 15:08
• Using 0 for sum actually gives a vastly different distribution, possibly the distribution you want. It might be related to the fact that [maximum of ROLL] returns values 0..36. You can check that for yourself. Commented Jan 28, 2016 at 15:22
• Damn. I may have used a function summing the roll, not looking for the biggest value.
– Nox
Commented Jan 28, 2016 at 15:25
• Yes, looking at the AnyDice documentation shows this to be the case. I will quote the relevant documentation in my answer. Commented Jan 28, 2016 at 15:29

You are using this code to set up a base value:

NUM: [maximum of ROLL]

SUM: 10 + NUM


Your intent is to get the highest number rolled on any d10, however the maximum function expects dice, and you have provided a sequence. According to the documentation:

If a number is provided, then it will be converted to a die that can roll only that number. If a sequence is provided, then the sequence will be summed and treated the same as a number.

I believe that initializing SUM to 0 should suffice, as your loop should always find something greater than 0 anyway.

• Now that I look at it again, The function expects a sequence but is being passed dice. I'm not sure it's correct, I think it would give the same output every time because it's always getting all possible values of 4d10. But the roller seems to contradict me. Commented Jan 28, 2016 at 16:10
• Well, the distribution looks right, so I assume the function is just being called once for all possible rolls. Commented Jan 28, 2016 at 16:53
• Yes, your answer is precisely correct. Just for comparison, here's an alternative way to get the same results. This behavior of passing a die to a function expecting a sequence is documented (under "Functions" in the online documentation) and is very useful. Unfortunately, as you correctly note, the built-in maximum of DIE function expects a die instead, and the behavior of passing a sequence to a function expecting a die is much less useful. Using NUM: 1@ROLL instead of NUM: [maximum of ROLL] would also fix the OP's code. Commented Feb 19, 2018 at 22:15
• And yes, I realize that this is a two year old answer, but I just happened to stumble across it while searching and noticed that it lacked confirmation and upvotes. So here, have an upvote. :) Commented Feb 19, 2018 at 22:16
• @AlanDeSmet: Done. Although, for the sake of fairness, I also optimized the OP's code to be approximately as fast as mine, since it only took minor tweaks. There's nothing fundamentally wrong with their algorithm, and indeed it could well be faster than mine (although in practice the difference does not seem to be significant). Commented Jan 22, 2022 at 19:20

Here's another solution that runs a bit faster:

function: most common number in ROLL:s {
MAX: 1@ROLL
loop SIDE over ROLL {
if (SIDE = ROLL) > (MAX = ROLL) {
MAX: SIDE
}
}
result: MAX
}

function: count and max in ROLL:s {
MAX: [most common number in ROLL]
COUNT: MAX = ROLL
result: 10*COUNT + MAX
}

loop N over {3..7} {
output [count and max in Nd{0..9}] named "[N] dice"
}


One difference between my solution and your original code is that, instead of calculating 10 × (number of dice that rolled X) + X for each rolled number X and choosing the highest one as your original code tries to do (and, with Mystagogue's correction, indeed does), my code instead first finds the most common number rolled (implicitly favoring higher numbers in case of a tie, since AnyDice sorts rolled arrays in descending numerical order) and then separately counts how many times that number has been rolled to calculate the result.

However, that doesn't actually make any significant difference in performance. Instead, the main reasons why your code is slower than mine seem to be that:

1. You're using [count I in ROLL] instead of I = ROLL to count the number of occurrences. While both do the same thing, calling a function is noticeably slower than using the comparison operator directly.

2. You're looping over all the 10 sides of the die, whereas my code only loops over the actual rolled array of numbers. While my code can end up looping over the same number more than once if the rolled numbers aren't all different, it's still faster as long as there are less than 10 dice in the roll!

Both of those things are easy to change, and with those changes (plus the minor optimization of removing the redundant B > 0 condition), your code in fact runs basically just as fast as mine: both can handle N = 8, but will time out for N = 9. (The unoptimized version of your code only goes up to N = 6.)