# How do I implement this specialized roll-and-keep mechanic in AnyDice?

I need help with an anydice function.

I'm looking for a modified Roll and Keep type of mechanic, where if you have more dice that roll the max result than you are keeping, each discarded max result adds +1 to the total.

Example: if you were rolling 6d10 and keeping two (equivalent to HIGHEST 2 of 6d10 in anydice), and you rolled 10, 10, 10, 8, 3, and 1, your total would be 21--that's 20 for the two 10s you kept, and +1 for the 10 you couldn't keep.

Example 2: if you were rolling 5d6 drop 2 (equivalent to HIGHEST 3 of 5d6 in anydice), and rolled 6, 6, 6, 6, and 6, your result would be 20.

Maybe I don't know how to word what I am looking for correctly, but I have not been able to find any answers to this question when searching around online.

• Would non-max repeating rolls count? For example, 5d6 keep 3 rolls all 4’s; would it add to 14? (3x4+2) Aug 16, 2021 at 5:01
• No, it would just be 12. Aug 17, 2021 at 0:20

Here's an implementation of Ben Barden's algorithm to compute the distributions you want.

function: N:n of SIZE:n keep K:n extras add {
result: [helper NdSIZE SIZE K]
}

function: helper ROLL:s SIZE:n K:n {
COUNT: [count SIZE in ROLL]
if COUNT > K { result: K*SIZE - K + COUNT }
result: {1..K}@ROLL
}


The output is somewhat underwhelming, since your bonus points for having lots of extra maximum values on your dice rarely happens. You need very large die pools relative to the number of dice you're keeping for the rule to have any noticeable effect. Here's some code that computes the distributions for keeping three dice from varying pools of d6s. The effect of the special rule only really matters for pools that are many times larger than the number of dice you're keeping.

D: 6
K: 3

loop N over {K+1..K+8} {
output [N of D keep K extras add] named "[N]d[D] keep [K] extras add +1"
}
loop N over {K+1..K+8} {
output {1..K}@NdD named "[N]d[D] keep [K]"
}


Your special rule only effects the small part of the graph over on the right, where the circle-marked lines diverge from the square marked lines out beyond 18 (the normal maximum for 3d6). For values less than 18 (which is 95% or more of the distribution for most of the pool sizes), the two kinds of lines are exactly coincident.

• Nice answer, +1. We seem to have interpreted the OP's description slightly differently: I assumed that e.g. rolling (5, 5, 5, 5) on 4d6 keep 2 would yield 5+5+2 = 12, since the highest result rolled is 5 and there are two extra fives not kept, whereas your code just yields 5+5 = 10 since the highest result that could be rolled is 6, and there are none of those. Without clarification from the OP, I can't really tell which one of us is right. Aug 16, 2021 at 9:39
• Yeah, the fact that we had different interpretations is why I finished off my answer and posted it even after seeing your excellent one. Aug 16, 2021 at 9:42
• Actually, on a second look, I believe my original interpretation was based on a misreading of the question. (Basically, I misread "than" as "that".) I've revised my answer to match your interpretation. Aug 16, 2021 at 14:05
• A variation anydice.com/program/23ccc based on "bonus equal to number of max rolls minus number of keep, min 0" to reduce branching. Also, the observation that complex mechanics that don't come up in practice are not really worth having is a good one.
– Yakk
Aug 16, 2021 at 19:48
• Thanks guys, I have a lot of reading here and messing around with new things to learn, but I think this looks like what I'm looking for. Aug 17, 2021 at 0:34

As usual, the answer is to write a function. Something like this, for example:

function: ROLL:s keep N:n plus one for each extra MAX:n {
SUM: {1..N}@ROLL
COUNT: ROLL = MAX
result: SUM + [highest of 0 and COUNT - N]
}

output [highest 2 of 6d10] named "normal"
output [6d10 keep 2 plus one for each extra 10] named "with bonus"


Of course, you could also shorten that to just:

function: ROLL:s keep N:n plus one for each extra MAX:n {
result: {1..N}@ROLL + [highest of 0 and (ROLL = MAX) - N]
}


but I figured that a few extra named variables wouldn't hurt readability.

Note: I've revised my code to produce the same results as Blckknght's solution, since my original interpretation (of giving +1 for every discarded die matching highest number actually rolled, i.e. basically omitting the MAX parameter and setting MAX: 1@ROLL instead) seems to have been based on a misreading of the question. (For anyone who's curious, my original code can be found in the edit history of this answer.) I encourage everyone to upvote Blckknght's excellent answer.

In any case, if the logic I've implemented above is not exactly what you want, you should hopefully be able to edit the code fairly easily to match your actual mechanic. Note that, inside the function, ROLL is just a fixed sequence of numbers (sorted in descending order), so you can use any sequence manipulation syntax in AnyDice on it.

• Why do you think it is a misreading?
– Yakk
Aug 16, 2021 at 19:37
• @Yakk: Basically, I misread "if you have more dice that roll the max result than you are keeping" as "if you have more dice that roll the max result that you are keeping", making me think that the OP was referring to the maximum result rolled (and kept) rather than to the maximum possible result. (I suppose it's possible that that's in fact what they meant, but with the misreading corrected, it seems far less plausible.) Aug 16, 2021 at 20:24
• Yes, thanks. I think there was a miscommunication in how I was trying to describe it. The bonus comes not from rolling doubles or sets or what have you, but when you have to throw away "perfect" dice because of a keep limitation. If you're rolling 4d6k3 for your D&D chars stats, throwing away that 4th 6 (it happens) feels bad. Starting with a 19, to sort of limit break or crit, and go above the 18 max would be the type of mechanic I am talking about. It should only come up when you've rolled the maximum possible result (18 on 4d6k3 or 20 on 8d10k2), and even then it should happen rarely. Aug 17, 2021 at 0:36

You should be able to handle this with a conditional block, because your dicerolling mechanic really splits into two distinct options. The psuedocode would look something like the following

Roll 5d6

if (# of 6s) > = 3 {
output 15 + (# of 6s)
}
else {
output sum of top 3 dice
}
$$$$

• Or, Roll N; Return Sum Top K of Roll + max(0, # max rolls-K)?
– Yakk
Aug 16, 2021 at 19:40
• This answer makes perfect sense to me in plain english, and I think (I'm no mathemagician) gives me exactly what I want. I don't understand the other answers by reading them (I have to plug it in, mess around with, and learn those functions now), but if those answers are an anydice interpretation of this here answer, than thank you all, and my question is answered. Aug 17, 2021 at 0:43

The trick may be in exploiting Anydice's type casting in functions. When you cast a variable DICE as sequences (DICE:s), the function gets evaluated for every possible combination of DICE

https://anydice.com/program/23cf1

function: roll DICE:s keeping N:n {

result: {1..N}@DICE + [highest of 0 and [count [maximum of 1@DICE] in DICE] - N]
}

output [roll 8d6 keeping 5] named "8d6 keeping 5"
output [roll 5d10 keeping 3] named "5d10 keeping 3"


• FYI, [maximum of NUMBER] does nothing, it just returns NUMBER. You can also rewrite [count NUMBER in SEQUENCE] as just NUMBER = SEQUENCE`, which I believe is slightly faster (although it may not matter in practice). With those changes, you code is basically the same as what I had in the first version of my answer. Alas, it doesn't seem to match the OP's intent, which (as clarified in comments) is to only add the bonus for discarded dice that rolled the highest possible result. Aug 17, 2021 at 7:55
• That's a leftover from before I did typecasting. You are right. Aug 17, 2021 at 8:02