# Syntax for a unique dice system on Anydice?

I've been studying and toying with a lot of odd dice systems for a while now, and Anydice has been a great help in understanding the probability effects of different systems. However, I've run into one that, while fun in practice, I have not been able to model in Anydice to better understand its curves.

An example output could be described as

Highest 2 of 2d6 and 3d8

however Anydice doesn't recognize this syntax. I found a similar question from about a year ago with some great answers, but that person was only trying to find the highest single result, while I need the highest two results of an irregular dice pool.

The system is a combination of roll and keep with step dice, with step dice for attributes and number of dice rolled, always keeping 2, for skills. Attributes step up unevenly, so the pool is either made up only from d6, a mixture of d6 and d8, or only d8. In the case of a mixed pool, each type of dice takes up half of it, rounding in favor of the larger die if your pool is odd.

I can figure out how to loop the variables myself though once I can find a way to arrange said variables.

• is a "half d6/d8" meant to be 'labeled' as "1,2,3,1,2,3,4", "1,2,3,5,6,7,8" or "4,5,6,5,6,7,8" ? – Trish Dec 8 '16 at 11:12
• It means half the pool is d6 and the other half is d8, sorry for the confusion. So if the pool size is two, you are rolling 1d6 and 1d8. If it is 3, you roll 1d6 and 2d8 (because you round in favor of the larger die). And if it is 4 you roll 2d6 and 2d8. – Narrator Dec 8 '16 at 17:39
• ahhh, clarified the question based on this. – Trish Dec 8 '16 at 17:48
• Is there a reason why you wish to stick with Anydice? It would be easy to code this up in Python with natural language behavior (which would allow you to be arbitrary about what dice you were rolling). – tillmas Dec 8 '16 at 18:03

This is actually fairly simple to implement in AnyDice:

function: highest N:n of A:s and B:s {
result: {1..N} @ [sort {A, B}]
}

function: lowest N:n of A:s and B:s {
LEN: #A + #B
result: {LEN-N+1 .. LEN} @ [sort {A, B}]
}


These functions assume that you haven't changed the sorting order, which is highest-to-lowest by default. If you reverse it, the "highest" function will start returning the lowest rolls, and vice versa.

You can also easily modify these functions to handle more than two types of dice, as in:

function: highest N:n of A:s and B:s and C:s {
result: {1..N} @ [sort {A, B, C}]
}

function: lowest N:n of A:s and B:s and C:s {
LEN: #A + #B + #C
result: {LEN-N+1 .. LEN} @ [sort {A, B, C}]
}


Now for the bad news: these functions can get very slow for large dice pools, because they enumerate all the possible rolls by brute force. The built-in "highest/lowest N of DICE" functions are optimized using clever math to run very quickly even for large numbers of dice, but these aren't. If you try to compute something like [highest 2 of 10d6 and 10d8] using these functions, you'll almost certainly get a time-out error.

Also, it's worth noting that, just like the built-ins, these functions return a single biased die describing the sum of the highest / lowest N values. Thus, for example, [highest 1 of [highest 2 of 1d6 and 1d8]] is not the same as [highest 1 of 1d6 and 1d8]! As far as I know, actually combining e.g. a d6 and a d8 into a single dice pool in AnyDice is simply not possible; you can have biased dice representing any probability distribution you want, and you can have pools of several identical dice, but you can't have different types of dice in the same pool.

On the plus side, unlike the built-ins, these functions work "as expected" also for sequence inputs: for example, [highest 2 of {1,2} and {3,4}] returns 7 = 3+4 as expected, not 10 = 1+2+3+4 like the corresponding built-in [highest 2 of {1,2,3,4}] would(!). (This happens because the built-ins are defined to take a die as their input, not a sequence, and so AnyDice will auto-convert a sequence to a die by summing its values before the function even sees it. I kind of consider that a bug in AnyDice, even if it's technically working exactly as documented.)

Based on Tim's and Trish's initial answers, I've constructed this monstrosity, which, like most of my programs, makes heavy use of AnyDice parameter casting/reification to handle the tedious process of constructing and using dice with functions:

function: X:n upper D:n {
Y: X / 2
result: YdD
}
\Dissimilar sizes\
function: X:n smaller D:n {
Y: X / 2
Z: X - Y
E: D - 2
result: ZdE
}
\Same size, other half\
function: X:n lower D:n {
Y: X / 2
Z: X - Y
result: ZdD
}
function: highest N:n of B:s and L:s {
J: 1
K: 1
SUM: 0
loop I over {1..N} {
BC: J@B
LC: K@L
if BC >= LC {
SUM: SUM + BC
J: J + 1
}
else {
SUM: SUM + LC
K: K + 1
}
}
result: SUM
}

\Skill is in integral die sizes, smaller better, which are split
around odd sizes to make pairs of dissimilar even-sided groups \
function: highest N:n of P:n skill S:n {
if S / 2 = 0 {
SFINAL: S
B: [P lower SFINAL]
}
else {
SFINAL: S + 1
B: [P smaller SFINAL]
}
A: [P upper SFINAL]
result: [highest N of A and B]
}
output [2 upper 8] + [2 smaller 8] named "1d6 + 1d8, basically"
output [highest 1 of [2 upper 8] and [2 smaller 8]] named "highest 1 of 1d6 and 1d8"
output [highest 1 of 2 skill 7] named "highest 1, pool 2, skill 7"
output [highest 1 of 2d8] named "highest 1 of 2d8"
output [highest 2 of [3 upper 8] and [3 smaller 8]] named "highest 2 of 1d6 and 2d8"
output [highest 2 of 3 skill 7] named "highest 2, pool 3, skill 7"
output [highest 2 of 3 skill 8] named "highest 2, pool 3, skill 8"
output [highest 2 of 5 skill 7] named "highest 2, pool 5, skill 7"


The output statements give some idea of its use, along with test cases to show various rolls this code does and doesn't match. For general use you should use [highest N of P skill S]. Pass the number you want to keep, the die pool size (total, of both sizes together), and the skill. "Skill" means the average die size of your pool, which can be an odd number and will in that case be sorted out to have half the pool (rounded up) using the next bigger even-sided die, and the remainder using the next smaller even-sided die. So a skill of 9 becomes a pool that's roughly half d10s and the rest d8s. (No attempt is made to prevent you from rolling crazy sizes, e.g. 2d14 + 3d16, but the math would check out if you wanted to roll those weird dice anyway.)

Checking out the results, means grow with increasing pool and kept sizes, which is expected; bounds are kept to the actual available dice (so [highest 2 of 3 skill 7] ends up using the d8 and one of the d6s most of the time, but has no second d8 around), but otherwise are as large as the kept number of rolled dice of the larger die size. And visually, the peaks tend to exhibit the "bunching" characteristic of best N arrangements, along with the skewing from dissimilar die pools. So I believe this is all correct.

• it can be made faster: anydice.com/program/a06a :P See rpg.stackexchange.com/a/91475/30306 – Trish Dec 8 '16 at 10:37
• Impressive, I've never seen such a large anydice function lol. Testing the results it gives for [highest 2 of 3 skill 6] vs the vanilla function [highest 2 of 3d6], the probabilities come out different. Not by a lot though, and by intuition the probabilities do seem pretty close to what I would expect. I'll toy with it some and see if I can figure out the discrepancy. Thanks. – Narrator Dec 8 '16 at 17:56
• I'm pretty sure there's something wrong with your solution. In particular, your example plot for "highest 2 of 1d6 and 2d8" gives a 0% chance of rolling 15 or 16, which is surely not right. (In any case, I obviously prefer my own solution. :) – Ilmari Karonen Dec 17 '16 at 19:14

.

# Number chances

After fiddling around with anydice for several hours and going through a fully false result, I finally got the following code. It tells how often each number occurs on the die combination:

DONE:d6
NONE:2
DTWO:d8
NTWO:3

function: run{
result: (NONE d (DONE =NUMBER)+ NTWO d (DTWO=NUMBER))
}

loop NUMBER over {1..[maximum of DTWO]}{
output [run] named "[NUMBER]s on [NONE][DONE]+[NTWO][DTWO]"
}


# Counting out

However, that is not yet counted out and pretty user unfriendly. So more fiddling resulted in this:

DONE:d6
NONE:2
DTWO:d8
NTWO:3

function: run{
COUNTER:0
loop RUNS over {1..AMMOUNT}{
COUNTER: COUNTER+ (NONE d (DONE =NUMBER)+ NTWO d (DTWO=NUMBER))=RUNS
}
result: COUNTER
}

loop AMMOUNT over {1..2}{
loop NUMBER over {1..[maximum of DTWO]}{
output [run] named "[AMMOUNT] times [NUMBER] on [NONE][DONE]+[NTWO][DTWO]"
}}


This at least tells us how many of those cases are 1 or 2 of those. We don't have an interest in cases where 3 or more of a given result pop up, as those extras will have to be ignored. Still, that only gives us the plain "once or twice" and "twice the same" probabilities, not the probabilities for the highest two of the set.

Now, working that out is clearly not done yet..

# Sequencing

But here sequences come in handy: Anydice can make sequences, so we can make a sequence of NdX and MdY, that is a single string holding all those numbers. It handles almost like a die, but isn't one.

DONE:d6
NONE:2
DTWO:d8
NTWO:3

output {DONE:NONE,DTWO:NTWO} named "1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8"


# Getting Sequenced Solutions

This sequence can't be used as a die as it is, we must turn it into one. To do this, we pretend it is an N-sided super-die (where N length of the sequence) which is labeled in the same manner as the sequence, so in our example 5x1, 5x2, ..., 5x6, 3x7, 3x8. The easy part is, we just need to add a "d" in front of the sequence and got a die.

To get the final result of highest two out of one roll set, we just need to roll a number of these imaginary super-dices equal to the total number of real dice we plunged into the super-dice involved.

DONE:d6
NONE:2
DTWO:d8
NTWO:3

output [highest 2 of (NONE+NTWO)d{DONE:NONE,DTWO:NTWO}]


For the test case NONE:0, with any NTWO, it does hold the correct result, so here we are, closing in on the Home Stretch! This still has some errors: for NONE:1 NTOW:1 the code allows 15 and 16, which shouldn't be. That case is actually the highest of 1d6 and 1d8, always taking both. We can't have 2d6 and 1d8 as of the premise. For 2d6 we fall back to the simple case of keeping both, so we need a to check some cases separately to fix the results.

# Case analysis

Case differentiation for NTWO=0, 1 and more does fix the faulty results in the low regions:

DONE:d6
NONE:2
DTWO:d8
NTWO:0

function: run{
if (NTWO>1){result: [highest 2 of (NONE+NTWO)d{DONE:NONE,DTWO:NTWO}]}
if (NTWO=1){result: [highest 1 of d6]+[highest 1 of d8]}
if (NTWO=0){result: [highest 2 of 2d6]}
}

output [run]


# Final Words

The solution gives at best a very good approximation but, as Ilmari pointed out:

[Y]ou're effectively defining a hybrid die that behaves like a d6 with probability N1/(N1+N2) and like a d8 with probability N2/(N1+N2), and then making N1+N2 independent rolls of that die. Thus, while the dice pool you're rolling will have an average of N1 d6's and N2 d8's, you also end up counting cases where the composition of the pool differs from the average

• Looks like yours and mine differ, and I don't have time right now to work out which one is wrong. – user17995 Dec 8 '16 at 11:03
• While the probabilities look pretty convincing, there are some incorrect results. For example, if you have a dice pool of 2, and rolling half d6s and half d8s, meaning your physical pool is 1d6 1d8. Your program would roll 2d{d6,d8} which means results requiring 2d8s are possible. Further, that calculation gives a different probability for the highest result (16) and the minimum result (2), which shouldn't happen when the pool size is the same as the keep size (since both results can only be reached by exactly one combination of numbers). – Narrator Dec 8 '16 at 17:52
• @TuggyNE Revised with case analysis. Now the difference between mine and TuggyNE's down to 0.2 percent points in the mixed pool of 3, and at worst 5 percent points on a pool of 5. – Trish Dec 8 '16 at 18:18
• @Narrator Revised, see above. – Trish Dec 8 '16 at 18:18
• This appears to be accurate. Thank you very much! Closing the question. – Narrator Dec 8 '16 at 20:57

EDIT: After reading Trish's comment about how the die needs to be rolled as often as there are dice in the pool, I wondered if the following would actually work:

output [highest 2 of 5d{d6, d6, d8, d8, d8}]


It looks like this might be the simplest way to do it? I get a nice bell curve with this third try.

• it should be the highest 2 of the whole dice pool... so the highest 1 of A and the highest one of A without that one... – Trish Dec 8 '16 at 6:01
• Ah I see. I think there's a way to do that, give me some time to figure it out and edit my answer. – Tim Dec 8 '16 at 6:04
• For some reason, I think this only gives the single highest die, rather than the highest N. Getting closer, though. – user17995 Dec 8 '16 at 6:58
• that... still doesn't work out. I mean, it results in a flat 1 to 8, not the expected 2 to 16 – Trish Dec 8 '16 at 6:59
• I feel like something's missing from the updated answer as well. When doing "output [highest 1 of 2d{d20, d12}]", it has different odds for each d20-only value. Unless either my probability's off or my understanding of the question's off, I'd think each value above 12 would have the same odds when rolling 1d20 & 1d12 and taking the higher of the two. – CTWind Dec 8 '16 at 18:35