5
\$\begingroup\$

I am looking for help with creating an AnyDice program that can calculate the possibilities of the following mechanic.

Players roll a number of d6, then proceed to sum all dice of a single set of matching dice.

Thus...

... a roll of 3, 3, 5, 5, 6 would generate a score of 10 (5 + 5).

... a roll of 1, 1, 2, 2, 4 would generate a score of 4 (2 + 2).

If no matching set is rolled, the highest die is regarded a set.

Thus...

... a roll of 1, 2, 3, 4, 5 would generate a score of 5 (5).

Preferably, the program should output [mechanic] >= [mechanic].

Thank you.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ What if a single die is higher than any matched set? Like, if I rolled 1, 1, 1, 2, 3, 6, could I take the 6, or am I stuck with the (1 + 1 + 1) = 3? \$\endgroup\$ Commented Jun 6, 2015 at 22:33
  • \$\begingroup\$ @DuckTapeAL: you are never forced to use a set if a single die would produce a better result. \$\endgroup\$ Commented Jun 11, 2015 at 14:52
  • 1
    \$\begingroup\$ @RavenStag You seem to have made two accounts. You should merge them by following the instructions here. \$\endgroup\$ Commented Jun 11, 2015 at 22:54

5 Answers 5

4
\$\begingroup\$

Edited using Ilmari Karonen advice.

You only need to make N be the number of rolled dies.

Use the link or copy the code yourself.

function: sum all I in ROLL:s{ result: I*( ROLL=I ) } function: myroll ROLL:s{ A: [sum all 1 in ROLL] loop I over {2..6}{ B: [sum all I in ROLL] A: [highest of A and B] } result: A } output [myroll 5d6] 
\$\endgroup\$
0
2
\$\begingroup\$

Here's a simpler AnyDice solution, originally based on Albert Masclans' code, but almost entirely rewritten:

function: highest sum of equal dice in ROLL:s { MAX: 0 loop I over ROLL { SUM: I * (ROLL = I) MAX: [highest of SUM and MAX] } result: MAX } output [highest sum of equal dice in 5d6] 

You can change the 5d6 on the last line to anything you want. Large die pools, however, will likely time out, because the code works by naïvely iterating over every possible roll (that's what happens in AnyDice when you pass a die to a function expecting a sequence), and that quickly becomes way too slow.


For somewhat larger dice pools, here's a Python program that does the same calculation:

# ORE-like dice odds calculator for https://rpg.stackexchange.com/q/63120 import argparse parser = argparse.ArgumentParser(description='Distribution of the maximum sum of identical dice in NdD.') parser.add_argument('N', type=int, help='number of dice to roll') parser.add_argument('D', type=int, default=6, nargs='?', help='number of sides per die') args = parser.parse_args() # generate all possible sorted NdD rolls and their probabilities # see http://en.wikipedia.org/wiki/Multinomial_distribution for the math factorial = [1.0] def dice_pool(n, d): for i in xrange(len(factorial), n+1): factorial.append(factorial[i-1] * i) nom = factorial[n] / float(d)**n for roll, den in _dice_pool(n, d): yield roll, nom / den def _dice_pool(n, d): if d > 1: for i in xrange(0, n+1): pair = (d, i) for roll, den in _dice_pool(n-i, d-1): yield roll + (pair,), den * factorial[i] else: yield ((d, n),), factorial[n] # the actual calculation and output code starts here dist = {} for roll, prob in dice_pool(args.N, args.D): total = max(num * count for num, count in roll) if total not in dist: dist[total] = 0.0 dist[total] += prob max_prob = max(dist.values()) print "Maximum sum of identical dice in %dd%d:" % (args.N, args.D) for total, prob in dist.iteritems(): print "%4d %7.4f%% %s" % (total, 100 * prob, "#" * int(60 * prob / max_prob + 0.5)) 

The dice_pool(n, d) function that makes up most of the code is actually a general-purpose tool for calculating probabilities involving unordered pools of dice. It's a generator that successively returns each possible roll (in the form ((1, n1), (2, n2), (3, n3), ...), where n1, n2, n3, etc. are the number of dice that rolled 1, 2, 3, etc. respectively) and the probability of obtaining that roll. (Yes, the yielded value is a tuple containing a tuple of tuples. It's perfectly natural to use.)

For speed, I've chosen to use floats for the probability calculations, despite the possibility of small roundoff errors. It would be possible to obtain exact results by using integer / rational math everywhere, but the gain would likely be minimal, since there should be no real opportunities for catastrophic cancellation here. There are also a few other optimizations, like precalculating the factorials of numbers from 0 to N. With the optimizations, calculating the probabilities for 20d6 takes about 0.8 seconds on my old laptop, while 50d6 takes about 47 seconds.

\$\endgroup\$
1
\$\begingroup\$

I can't do it in AnyDice, but it's not that hard to code in Python...

In Python

# OREishOdds.py # ©2015 William F. Hostman. CC by attribution # res is output results array - a place to store our output. res = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] # num is our running total of iterations num = 0 # work is the array we do the math work upon. work = [0,0,0,0] # we now tell it what the die looks like... diedef = [1,2,3,4,5,6] # dice: for die in [array of faces] for a in diedef: for b in diedef: for c in diedef: for d in diedef: # work work[0] = a work[1] = b work[2] = c work[3] = d # insert additional dice above work.sort() # counting score = 0 for z in diedef: tempscore = 0 for y in work: if y == z: tempscore += y if tempscore > score: score = tempscore res[score] +=1 num += 1 # end of work # output temp = 0 for score in res: temp += 1 print temp, score, (1000*score/num) 

Extending this

res needs to have at least as many 0 values as 1+(sides * number).

Each die gets a separate value, but you could shortcut this by defining a die as a variable and replacing the arrays for individual die for statements with that defined array.

The work block needs to be indented together. Python is whitespace dependent.

To add a die: add another for line with another letter, add another 0 to the array work, add another work[ ] = line with the next sequential number, and the new die's variable.

You cannot shortcut and simply load work directly in the for statements, because the sort routine would screw up the accounting.

The output is in result value, number of occurrences, and permille result (percentage * 10). If you want more places, increase the multiplier in the print line.


Tweaking the dice values

If you want to play around with some funky results, for example, replacing rolls of 1 with 0 (so 1's count as nothing), just replace the 1's in the arrays in the for lines. So the arrays would be [0,2,3,4,5,6].

You switch to d8's by using [1,2,3,4,5,6,7,8].


It's a bit uglier to code for ORE's 2-d results (width and height), but since you weren't asking about that... aw, heck, I'll discuss that, too.

And that's because any true ORE roll has the question of whether one's prioritizing height (value of number counted) or width (number of replications in the roll).

If one prioritizes one or the other, it's easy to grid out. Otherwise, one has to tick the various array locations possible on a 2d grid. The sum of the entries would thus be more than the count of iterations.


This isn't particularly fast code. (Python's not particularly fast, and this isn't optimized code.) But its good enough provided you keep it under 8 dice or so, and those dice are all under d12's.


Above code ©2015 William F. Hostman. Permission to duplicate and use for any legal purpose granted, including inclusion in texts, provided attribution is given. Code took 10 minutes to write. Took as long or longer to comment it.

\$\endgroup\$
1
\$\begingroup\$

Not quite sure why mrlemon's answer got downvoted, as I had the same inclination. In my case, though, I wanted to use some of the newer items found in the Python3 standard library to make that code quite a bit simpler:

# OREishOdds.py # ©2015 William F. Hostman. CC by attribution # Python3 version (c)2017 Tim Keating from itertools import product d6 = list(range(1, 7)) def compute_scores(num, die): score_counts = {} all_possible_outcomes = product(*([die] * num)) for roll in all_possible_outcomes: roll_score = score(roll) score_counts.update({roll_score: score_counts.get(roll_score, 0)+1}) return score_counts def score(roll): scores = {} for die in roll: scores[die] = scores.get(die, 0) + die return max(scores.values()) def all_odds(num, die): total_num_outcomes = len(die) ** num total_values = len(die) * num score_counts = compute_scores(num, die) for n in range(1, total_values + 1): score = score_counts.get(n, 0) print('{:<4}{:>6}{:>10.2%}'.format(n, score, score / total_num_outcomes)) if __name__ == '__main__': all_odds(5, d6) 
\$\endgroup\$
0
\$\begingroup\$

After reading aramis' very long answer (no offense), I couldn't help but golf the python down into a neat function. This one is using statistics for simplicity, rather than actually calculate the probabilities, but it works quite well as an estimate.

def orestats(X,Y=6,N=1000): """ X: Number of dice rolled Y: Number of Sides per Die N: Number of repetitions for statistics """ def ore(X,Y): # Roll XdY and return the ORE result as a list from random import randint roll = [randint(1,Y) for _ in range(X)] return [i*roll.count(i) for i in range(1,Y+1)] # Calculate the results N times and deconvolve the list. result = [x for y in [ore(X,Y) for _ in range(N)] for x in y] # Count all occurences of every possible result totals = [(i,result.count(i)/N*100) for i in range(1,X*Y+1)] # Print into a neat pseudo-AnyDice format (result, probability, bar graph) print('\n'.join('{:>3}: {:>4.1f} {}'.format(x[0],x[1],'█'*int(x[1])) for x in totals)) # Example call for 10d12, executed 10000 times orestats(10,12,10000) 

(Took me longer than 10 minutes, too.)

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.