Menu
News
All News
Dungeons & Dragons
Level Up: Advanced 5th Edition
Pathfinder
Starfinder
Warhammer
2d20 System
Year Zero Engine
Industry News
Reviews
Dragon Reflections
White Dwarf Reflections
Columns
Weekly Digests
Weekly News Digest
Freebies, Sales & Bundles
RPG Print News
RPG Crowdfunding News
Game Content
ENterplanetary DimENsions
Mythological Figures
Opinion
Worlds of Design
Peregrine's Nest
RPG Evolution
Other Columns
From the Freelancing Frontline
Monster ENcyclopedia
WotC/TSR Alumni Look Back
4 Hours w/RSD (Ryan Dancey)
The Road to 3E (Jonathan Tweet)
Greenwood's Realms (Ed Greenwood)
Drawmij's TSR (Jim Ward)
Community
Forums & Topics
Forum List
Latest Posts
Forum list
*Dungeons & Dragons
Level Up: Advanced 5th Edition
D&D Older Editions, OSR, & D&D Variants
*TTRPGs General
*Pathfinder & Starfinder
EN Publishing
*Geek Talk & Media
Search forums
Chat/Discord
Resources
Wiki
Pages
Latest activity
Media
New media
New comments
Search media
Downloads
Latest reviews
Search resources
EN Publishing
Store
EN5ider
Adventures in ZEITGEIST
Awfully Cheerful Engine
What's OLD is NEW
Judge Dredd & The Worlds Of 2000AD
War of the Burning Sky
Level Up: Advanced 5E
Events & Releases
Upcoming Events
Private Events
Featured Events
Socials!
EN Publishing
Twitter
BlueSky
Facebook
Instagram
EN World
BlueSky
YouTube
Facebook
Twitter
Twitch
Podcast
Features
Top 5 RPGs Compiled Charts 2004-Present
Adventure Game Industry Market Research Summary (RPGs) V1.0
Ryan Dancey: Acquiring TSR
Q&A With Gary Gygax
D&D Rules FAQs
TSR, WotC, & Paizo: A Comparative History
D&D Pronunciation Guide
Million Dollar TTRPG Kickstarters
Tabletop RPG Podcast Hall of Fame
Eric Noah's Unofficial D&D 3rd Edition News
D&D in the Mainstream
D&D & RPG History
About Morrus
Log in
Register
What's new
Search
Search
Search titles only
By:
Forums & Topics
Forum List
Latest Posts
Forum list
*Dungeons & Dragons
Level Up: Advanced 5th Edition
D&D Older Editions, OSR, & D&D Variants
*TTRPGs General
*Pathfinder & Starfinder
EN Publishing
*Geek Talk & Media
Search forums
Chat/Discord
Menu
Log in
Register
Install the app
Install
Upgrade your account to a Community Supporter account and remove most of the site ads.
Community
General Tabletop Discussion
*Pathfinder & Starfinder
math on initiative
JavaScript is disabled. For a better experience, please enable JavaScript in your browser before proceeding.
You are using an out of date browser. It may not display this or other websites correctly.
You should upgrade or use an
alternative browser
.
Reply to thread
Message
<blockquote data-quote="komi" data-source="post: 4392759" data-attributes="member: 60878"><p>WARNING: I'm just geeking out with math here. It won't really help you build a better character or win more fights. But if this sort of thing interests you, keep reading.</p><p></p><p>I wrote a program that calculates the statistics for rolling initiative at the start of combat. It started over at <a href="http://forums.gleemax.com/showpost.php?p=16398005&postcount=106" target="_blank">Gleemax</a> with people trying to figure out if Improved Init or Danger Sense was better. This was solved by looking at 1-on-1 statistics. But then someone asked what happens when you expand the problem to N people, how does the problem change. I thought this was an interesting math problem, so I found a solution (with help from people at xkcd) and wrote a program to calculate the odds. I worked hard on it and didn't want it to die with one post over there, so I thought I'd share here as well. <img src="https://cdn.jsdelivr.net/joypixels/assets/8.0/png/unicode/64/1f642.png" class="smilie smilie--emoji" loading="lazy" width="64" height="64" alt=":)" title="Smile :)" data-smilie="1"data-shortname=":)" /></p><p></p><p>The problem is simply: After an initiative challenge among N creatures, what's the odds of Creature 1 going 1st, 2nd, ... last? I made the problem general enough where each creature can have any bonus value and can roll a d20 (or really a dR, where R is any integer) with arbitrary statistics. For example, you can work out the odds for rolling twice and keeping the best, rolling 2d10, rolling a cheat die where the '1' is replaced with a second '20', always rolling max, or anything else. In my python code there's a section that looks like this:</p><p>[code] ( 8, 'rolltwice'),</p><p> ( 1, 'fair'),</p><p> ( 0, 'fair'),</p><p> ( 2, 'fair'),</p><p> ( 3, 'fair'),</p><p> ( 4, 'fair'),[/code]</p><p>Each line is a creature. The first number is its init bonus, the second column is the die statistics. 'fair' means a normal d20 with a uniform distribution. 'rolltwice' means roll two d20s and keep the higher number.</p><p></p><p>After running the code, you get a result like this:</p><p>[code] # | B |Statistics| 1 2 3 4 5 6 | Avg</p><p>---+---+----------+ ------ ------ ------ ------ ------ ------ +-----</p><p> 1 | 8 |rolltwice | 70.5% 14.7% 8.6% 4.4% 1.6% 0.3% | 1.53</p><p> 2 | 1 |fair | 3.1% 12.9% 17.8% 19.2% 21.5% 25.5% | 4.20</p><p> 3 | 0 |fair | 1.8% 9.4% 15.9% 18.6% 20.2% 34.2% | 4.49</p><p> 4 | 2 |fair | 5.1% 16.9% 19.1% 19.5% 21.0% 18.4% | 3.90</p><p> 5 | 3 |fair | 7.9% 21.0% 19.6% 19.4% 19.2% 12.9% | 3.60</p><p> 6 | 4 |fair | 11.7% 25.1% 19.0% 18.9% 16.6% 8.7% | 3.30[/code]</p><p></p><p>Each row is a creature. B is its bonus. The second column are the die statistics.. 1-6 going across are the chances for that creature to achieve that position in initiative. Avg is the average ranking. For example if Creature 1 took note of what place in initiative order he got for every fight, then averaged them, he'd get a number close to 1.53.</p><p></p><p>The previous example modeled Creature 1 with Danger Sense. Here's the same example where he has Improved Init instead.</p><p>[code] # | B |Statistics| 1 2 3 4 5 6 | Avg</p><p>---+---+----------+ ------ ------ ------ ------ ------ ------ +-----</p><p> 1 |12 |fair | 67.5% 15.7% 10.3% 4.9% 1.4% 0.2% | 1.58</p><p> 2 | 1 |fair | 3.5% 12.9% 17.5% 19.0% 21.5% 25.5% | 4.19</p><p> 3 | 0 |fair | 2.1% 9.4% 15.6% 18.5% 20.2% 34.2% | 4.48</p><p> 4 | 2 |fair | 5.7% 16.7% 18.7% 19.4% 21.0% 18.5% | 3.89</p><p> 5 | 3 |fair | 8.7% 20.7% 19.2% 19.3% 19.2% 12.9% | 3.59</p><p> 6 | 4 |fair | 12.5% 24.6% 18.7% 18.9% 16.6% 8.7% | 3.29[/code]</p><p></p><p>Here's a last example for fun. It compares the same Creature 1 from above in a variety of situations. I ran the program for each case and stripped out just Creature 1 and combined them here. This compares how he does with the various feats, and I also threw in my extra dice statistics (cheat, reroll, and 2d10, rolls natural 20, rolls natural 1).</p><p>[code] # | B |Statistics| 1 2 3 4 5 6 | Avg</p><p>---+---+----------+ ------ ------ ------ ------ ------ ------ +-----</p><p> 1 | 8 |fair | 47.8% 17.6% 15.3% 11.7% 6.1% 1.5% | 2.15 Normal</p><p> 1 |10 |fair | 57.7% 17.0% 13.3% 8.2% 3.2% 0.6% | 1.84 QD</p><p> 1 |12 |fair | 67.5% 15.7% 10.3% 4.9% 1.4% 0.2% | 1.58 II</p><p> 1 | 8 |rolltwice | 70.5% 14.7% 8.6% 4.4% 1.6% 0.3% | 1.53 DS</p><p> 1 |10 |rolltwice | 79.9% 11.2% 5.7% 2.4% 0.7% 0.1% | 1.33 DS+QD</p><p> 1 |12 |rolltwice | 87.4% 7.9% 3.3% 1.1% 0.2% 0.0% | 1.19 DS+II</p><p> 1 | 8 |cheat | 52.8% 17.4% 14.4% 10.0% 4.5% 0.9% | 1.99</p><p> 1 | 8 |reroll | 66.7% 14.7% 8.8% 6.0% 3.0% 0.8% | 1.66</p><p> 1 | 8 |2d10 | 49.4% 24.6% 15.3% 7.6% 2.6% 0.4% | 1.91</p><p> 1 | 8 |max | 100.0% 0.0% 0.0% 0.0% 0.0% 0.0% | 1.00</p><p> 1 | 8 |min | 0.5% 4.7% 18.1% 34.1% 31.4% 11.3% | 4.25[/code]</p><p></p><p>I wish I could provide some cool graphs that gave a comprehensive look at this problem with insight on what decisions to make in game, but the problem is basically open-ended because you can have any number of participants. And even if we fix that, each new creature adds another dimension to the result space, so it's impractical to graph. So I just have to leave you with examples and my code itself. Feel free to play with it. I hope someone else does. <img src="https://cdn.jsdelivr.net/joypixels/assets/8.0/png/unicode/64/1f642.png" class="smilie smilie--emoji" loading="lazy" width="64" height="64" alt=":)" title="Smile :)" data-smilie="1"data-shortname=":)" /> But if not, I still enjoyed working on the problem.</p><p></p><p>If you're curious about the math behind this, go to the <a href="http://forums.xkcd.com/viewtopic.php?f=17&t=24889&start=0&st=0&sk=t&sd=a" target="_blank">xkcd thread</a> I started. Yakk over there had a very clever way of setting up the math for this problem.</p><p></p><p>Here's my python code to run it. I tried to comment it as best as I could. Basically you just need to edit the creatureTable as I mentioned above. Change the bonus to model II and QD. Change the die statistics to model DS. This should handle about 20 creatures. I wrote this code to only be from one creature's perspective. To give lines for all the other creatures, I had to run it N times. So it runs N times longer than it would otherwise. You can improve this by only running for Creature 1 (change the comment on a for loop in the code as noted in the comments). It will give exactly the same statistics but only for creature 1. In this way you can have dozens of creatures if you want. I ran 60 in about a minute.</p><p></p><p>[sblock=init4e.py][code]</p><p>#!/bin/env python</p><p></p><p># Die used in initiave contest. 20 is normal, but any value will</p><p># generate valid statistics.</p><p>die = 20</p><p></p><p># Dice statistics. Each entry has a distribution for generating a</p><p># 1-20 (or 1-die).</p><p>prob = {</p><p> 'fair' : [1.0/die]*die,</p><p> 'rolltwice': [(2.0*d-1)/die/die for d in range(1, die+1)],</p><p> 'cheat' : [0]+[1.0/die]*(die-2)+[2.0/die],</p><p> 'reroll' : [1.0/2/die]*(die/2) + [3.0/2/die]*(die/2),</p><p> '2d10' : [v*4.0/die/die for v in range(die/2+1)+range(die/2-1, 0, -1)],</p><p> 'max' : [0.0]*(die-1)+[1.0],</p><p> 'min' : [1.0]+[0.0]*(die-1),</p><p> }</p><p></p><p>## This code validates the dice statistics above. It's commented</p><p>## here because these are all valid. If you want to add your own,</p><p>## you can uncomment this to check the results. Each distribution</p><p>## must satisfy three conditions:</p><p>## 1) 20 entries (or equal to value of die)</p><p>## 2) must sum to 1</p><p>## 3) must all be positive</p><p>#</p><p>#for k, v in prob.items():</p><p># tot = abs(sum(v)-1) < 0.000001</p><p># cnt = len(v) == die</p><p># pos = len([p for p in v if p < 0]) == 0</p><p># if not (tot and cnt and pos):</p><p># print "WARNING: Statistics '%s' are invalid!" % k</p><p></p><p>######################################################################</p><p>def main(args):</p><p> </p><p> ####################################################################</p><p> # Each line of the creature table represents an initiave roll. It</p><p> # has the following format:</p><p> # (Bonus, 'Stats')</p><p> #</p><p> # Bonus is that creature's initiative bonus and 'Stats' are the</p><p> # die statistics. Here are the valid 'Stats' values</p><p> #</p><p> # 'fair' = normal d20 roll</p><p> # 'rolltwice' = roll twice, keep better result</p><p> # 'cheat' = cheat die where '1' is replaced with second '20'</p><p> # 'reroll' = reroll but must keep second. Assumes reroll if</p><p> # less than average</p><p> # '2d10' = 2d10 instead of 1d20</p><p> # 'max' = always roll maximum</p><p> # 'min' = always roll minimum</p><p> ####################################################################</p><p> creatureTable = [</p><p> ( 8, 'fair'),</p><p> ( 1, 'fair'),</p><p> ( 1, 'fair'),</p><p> ( 2, 'fair'),</p><p> ( 2, 'fair'),</p><p> ( 3, 'fair'),</p><p> ]</p><p> ####################################################################</p><p></p><p> # Each run through calcRanking below only gives results based on</p><p> # creatureTable[0]. At first I was only going to deal with this</p><p> # problem, but then I realized that I can rotate through the</p><p> # creatures starting with a new one each time. This way I can</p><p> # calculate complete statistics. Unfortunately this gives an O(n^2)</p><p> # running time and becomes unwieldy after about 20 creatures. You</p><p> # can run many many more by not looping through all creatures. To</p><p> # do this, change the commented for loop line below.</p><p></p><p> placeList = [] # stores the odds for each ranking.</p><p> mplaceList = [] # stores the average ranking.</p><p> for k in range(len(creatureTable)):</p><p> #for k in [0]:</p><p> # Run calcRanking on the list N times, each time appending the</p><p> # results on the end of placeList.</p><p> placeList.append(calcRanking(creatureTable[k:]+creatureTable[:k]))</p><p> mplaceList.append(sum([n*p for n, p in enumerate(placeList[-1])])+1)</p><p></p><p> # Generate a report.</p><p> print</p><p> if die != 20:</p><p> print "Rolling d%d's for initiative!" % die</p><p> print</p><p> print ' # | B |Statistics|',</p><p> for n in range(len(creatureTable)):</p><p> print ' %-2d ' % (n+1),</p><p> print '| Avg'</p><p></p><p> print '---+---+----------+',</p><p> for n in range(len(creatureTable)):</p><p> print '------',</p><p> print '+-----'</p><p></p><p> for cn, ((b, p), place, mplace) in enumerate(zip(creatureTable,</p><p> placeList,</p><p> mplaceList)):</p><p> print '%2d' % (cn+1),</p><p> print '|%2d' % b,</p><p> print '|%-10s|' % p,</p><p> for p in place:</p><p> print '%5.1f%%' % (p*100),</p><p> print '| %4.2f' % mplace</p><p> print</p><p></p><p> ## Generate the table for one-on-one statistics.</p><p> #creatureTable = [</p><p> # [ 0, 'fair'],</p><p> # [ 0, 'fair'],</p><p> # ]</p><p> #res = []</p><p> #for d in range(-die, die+1):</p><p> # creatureTable[0][0] = d</p><p> # res.append([d]+calcRanking(creatureTable))</p><p> #</p><p> #for d, p, q in res:</p><p> # print d, p</p><p></p><p></p><p></p><p>######################################################################</p><p></p><p></p><p>######################################################################</p><p>def calcRanking(C):</p><p> """</p><p> Calculates the statistics of creature C[0] ending in 1st through Nth</p><p> place. All statistics are based on C[0], so you don't find out</p><p> anything about the other creatures.</p><p> """</p><p></p><p> # Creature C[0] is the main creature. Pull it out of the list.</p><p> B0, pkey = C[0]</p><p> pR = prob[pkey]</p><p></p><p> res = []</p><p></p><p> # The strategy is assume creature C[0] rolls a value r. For each r,</p><p> # calculate the odds of each place. Then at the end, weight each</p><p> # result by the odds of C[0] rolling r.</p><p> for r in range(die):</p><p></p><p> # I'm not going to go into too much detail on the math.</p><p> # Basically, I work out a distribution pair-wise for C[0] and each</p><p> # other creature and then add them together (done through</p><p> # convolution). To properly deal with multi-way ties, I need to</p><p> # keep track of win, lose, and tie conditions. This makes it 2D</p><p> # convolution. The tie cases are broken up later.</p><p> mat = [[1]];</p><p></p><p> for B, pkey in C[1:]:</p><p> p = prob[pkey]</p><p> dB = B - B0</p><p> extra = r-dB;</p><p> P0 = sum(p[:max(min(extra,die),0)])</p><p> Pt = 0</p><p> P1 = sum(p[max(extra+1,0):]);</p><p></p><p> if 0 <= extra <= die-1:</p><p> if dB > 0:</p><p> P1 += p[extra]</p><p> elif dB == 0:</p><p> Pt = p[extra]</p><p> else:</p><p> P0 += p[extra]</p><p></p><p> A = [ [P0, P1], [Pt, 0] ]</p><p> mat = conv2(mat, A)</p><p></p><p> pr = mat[0][:]</p><p> ind = len(mat)-1</p><p> for n in range(ind):</p><p> z = conv(mat[n+1][:-n-1], [1.0/(n+2)]*(n+2))</p><p> for i in range(len(pr)):</p><p> pr[i] += z[i]</p><p></p><p> res.append([v*pR[r] for v in pr])</p><p></p><p> return [sum(li) for li in zip(*res)]</p><p></p><p>######################################################################</p><p></p><p></p><p>######################################################################</p><p>def conv(x, y):</p><p> """</p><p> 1D convolution. The resultant vector is of length len(x)+len(y)-1.</p><p> """</p><p> P, Q = len(x), len(y)</p><p> N = P + Q - 1</p><p> z = []</p><p> for k in range(N):</p><p> t = 0</p><p> lower = max(0, k-(Q-1))</p><p> upper = min(P-1, k)</p><p> for i in range(lower, upper+1):</p><p> t = t + x[i]*y[k-i]</p><p> z.append(t)</p><p> return z</p><p></p><p>######################################################################</p><p></p><p></p><p>######################################################################</p><p>def conv2(A, B):</p><p> """</p><p> 2D convolution. A and B are matrices stored as lists of lists. The</p><p> resultant matrix has a number of rows of nrows(A)+nrows(B)-1, and</p><p> number of columns of ncols(A)+ncols(B)-1.</p><p> """</p><p> ar = len(A)</p><p> ac = len(A[0])</p><p></p><p> br = len(B)</p><p> bc = len(B[0])</p><p></p><p> cr = ar + br - 1</p><p> cc = ac + bc - 1</p><p></p><p> C = []</p><p> for k in range(cr):</p><p> C.append([])</p><p> for j in range(cc):</p><p> lrows = max(0, k-(br-1))</p><p> urows = min(ar-1, k)</p><p> lcols = max(0, j-(bc-1))</p><p> ucols = min(ac-1, j)</p><p> t = 0</p><p> for kk in range(lrows, urows+1):</p><p> for jj in range(lcols, ucols+1):</p><p> t += A[kk][jj]*B[k-kk][j-jj]</p><p> C[-1].append(t)</p><p> return C</p><p></p><p>######################################################################</p><p></p><p></p><p># Start. Pass arguments into the main function at the top.</p><p>if __name__ == '__main__':</p><p> import sys</p><p> main(sys.argv[1:])</p><p>[/code][/sblock]</p></blockquote><p></p>
[QUOTE="komi, post: 4392759, member: 60878"] WARNING: I'm just geeking out with math here. It won't really help you build a better character or win more fights. But if this sort of thing interests you, keep reading. I wrote a program that calculates the statistics for rolling initiative at the start of combat. It started over at [URL="http://forums.gleemax.com/showpost.php?p=16398005&postcount=106"]Gleemax[/URL] with people trying to figure out if Improved Init or Danger Sense was better. This was solved by looking at 1-on-1 statistics. But then someone asked what happens when you expand the problem to N people, how does the problem change. I thought this was an interesting math problem, so I found a solution (with help from people at xkcd) and wrote a program to calculate the odds. I worked hard on it and didn't want it to die with one post over there, so I thought I'd share here as well. :) The problem is simply: After an initiative challenge among N creatures, what's the odds of Creature 1 going 1st, 2nd, ... last? I made the problem general enough where each creature can have any bonus value and can roll a d20 (or really a dR, where R is any integer) with arbitrary statistics. For example, you can work out the odds for rolling twice and keeping the best, rolling 2d10, rolling a cheat die where the '1' is replaced with a second '20', always rolling max, or anything else. In my python code there's a section that looks like this: [code] ( 8, 'rolltwice'), ( 1, 'fair'), ( 0, 'fair'), ( 2, 'fair'), ( 3, 'fair'), ( 4, 'fair'),[/code] Each line is a creature. The first number is its init bonus, the second column is the die statistics. 'fair' means a normal d20 with a uniform distribution. 'rolltwice' means roll two d20s and keep the higher number. After running the code, you get a result like this: [code] # | B |Statistics| 1 2 3 4 5 6 | Avg ---+---+----------+ ------ ------ ------ ------ ------ ------ +----- 1 | 8 |rolltwice | 70.5% 14.7% 8.6% 4.4% 1.6% 0.3% | 1.53 2 | 1 |fair | 3.1% 12.9% 17.8% 19.2% 21.5% 25.5% | 4.20 3 | 0 |fair | 1.8% 9.4% 15.9% 18.6% 20.2% 34.2% | 4.49 4 | 2 |fair | 5.1% 16.9% 19.1% 19.5% 21.0% 18.4% | 3.90 5 | 3 |fair | 7.9% 21.0% 19.6% 19.4% 19.2% 12.9% | 3.60 6 | 4 |fair | 11.7% 25.1% 19.0% 18.9% 16.6% 8.7% | 3.30[/code] Each row is a creature. B is its bonus. The second column are the die statistics.. 1-6 going across are the chances for that creature to achieve that position in initiative. Avg is the average ranking. For example if Creature 1 took note of what place in initiative order he got for every fight, then averaged them, he'd get a number close to 1.53. The previous example modeled Creature 1 with Danger Sense. Here's the same example where he has Improved Init instead. [code] # | B |Statistics| 1 2 3 4 5 6 | Avg ---+---+----------+ ------ ------ ------ ------ ------ ------ +----- 1 |12 |fair | 67.5% 15.7% 10.3% 4.9% 1.4% 0.2% | 1.58 2 | 1 |fair | 3.5% 12.9% 17.5% 19.0% 21.5% 25.5% | 4.19 3 | 0 |fair | 2.1% 9.4% 15.6% 18.5% 20.2% 34.2% | 4.48 4 | 2 |fair | 5.7% 16.7% 18.7% 19.4% 21.0% 18.5% | 3.89 5 | 3 |fair | 8.7% 20.7% 19.2% 19.3% 19.2% 12.9% | 3.59 6 | 4 |fair | 12.5% 24.6% 18.7% 18.9% 16.6% 8.7% | 3.29[/code] Here's a last example for fun. It compares the same Creature 1 from above in a variety of situations. I ran the program for each case and stripped out just Creature 1 and combined them here. This compares how he does with the various feats, and I also threw in my extra dice statistics (cheat, reroll, and 2d10, rolls natural 20, rolls natural 1). [code] # | B |Statistics| 1 2 3 4 5 6 | Avg ---+---+----------+ ------ ------ ------ ------ ------ ------ +----- 1 | 8 |fair | 47.8% 17.6% 15.3% 11.7% 6.1% 1.5% | 2.15 Normal 1 |10 |fair | 57.7% 17.0% 13.3% 8.2% 3.2% 0.6% | 1.84 QD 1 |12 |fair | 67.5% 15.7% 10.3% 4.9% 1.4% 0.2% | 1.58 II 1 | 8 |rolltwice | 70.5% 14.7% 8.6% 4.4% 1.6% 0.3% | 1.53 DS 1 |10 |rolltwice | 79.9% 11.2% 5.7% 2.4% 0.7% 0.1% | 1.33 DS+QD 1 |12 |rolltwice | 87.4% 7.9% 3.3% 1.1% 0.2% 0.0% | 1.19 DS+II 1 | 8 |cheat | 52.8% 17.4% 14.4% 10.0% 4.5% 0.9% | 1.99 1 | 8 |reroll | 66.7% 14.7% 8.8% 6.0% 3.0% 0.8% | 1.66 1 | 8 |2d10 | 49.4% 24.6% 15.3% 7.6% 2.6% 0.4% | 1.91 1 | 8 |max | 100.0% 0.0% 0.0% 0.0% 0.0% 0.0% | 1.00 1 | 8 |min | 0.5% 4.7% 18.1% 34.1% 31.4% 11.3% | 4.25[/code] I wish I could provide some cool graphs that gave a comprehensive look at this problem with insight on what decisions to make in game, but the problem is basically open-ended because you can have any number of participants. And even if we fix that, each new creature adds another dimension to the result space, so it's impractical to graph. So I just have to leave you with examples and my code itself. Feel free to play with it. I hope someone else does. :) But if not, I still enjoyed working on the problem. If you're curious about the math behind this, go to the [URL="http://forums.xkcd.com/viewtopic.php?f=17&t=24889&start=0&st=0&sk=t&sd=a"]xkcd thread[/URL] I started. Yakk over there had a very clever way of setting up the math for this problem. Here's my python code to run it. I tried to comment it as best as I could. Basically you just need to edit the creatureTable as I mentioned above. Change the bonus to model II and QD. Change the die statistics to model DS. This should handle about 20 creatures. I wrote this code to only be from one creature's perspective. To give lines for all the other creatures, I had to run it N times. So it runs N times longer than it would otherwise. You can improve this by only running for Creature 1 (change the comment on a for loop in the code as noted in the comments). It will give exactly the same statistics but only for creature 1. In this way you can have dozens of creatures if you want. I ran 60 in about a minute. [sblock=init4e.py][code] #!/bin/env python # Die used in initiave contest. 20 is normal, but any value will # generate valid statistics. die = 20 # Dice statistics. Each entry has a distribution for generating a # 1-20 (or 1-die). prob = { 'fair' : [1.0/die]*die, 'rolltwice': [(2.0*d-1)/die/die for d in range(1, die+1)], 'cheat' : [0]+[1.0/die]*(die-2)+[2.0/die], 'reroll' : [1.0/2/die]*(die/2) + [3.0/2/die]*(die/2), '2d10' : [v*4.0/die/die for v in range(die/2+1)+range(die/2-1, 0, -1)], 'max' : [0.0]*(die-1)+[1.0], 'min' : [1.0]+[0.0]*(die-1), } ## This code validates the dice statistics above. It's commented ## here because these are all valid. If you want to add your own, ## you can uncomment this to check the results. Each distribution ## must satisfy three conditions: ## 1) 20 entries (or equal to value of die) ## 2) must sum to 1 ## 3) must all be positive # #for k, v in prob.items(): # tot = abs(sum(v)-1) < 0.000001 # cnt = len(v) == die # pos = len([p for p in v if p < 0]) == 0 # if not (tot and cnt and pos): # print "WARNING: Statistics '%s' are invalid!" % k ###################################################################### def main(args): #################################################################### # Each line of the creature table represents an initiave roll. It # has the following format: # (Bonus, 'Stats') # # Bonus is that creature's initiative bonus and 'Stats' are the # die statistics. Here are the valid 'Stats' values # # 'fair' = normal d20 roll # 'rolltwice' = roll twice, keep better result # 'cheat' = cheat die where '1' is replaced with second '20' # 'reroll' = reroll but must keep second. Assumes reroll if # less than average # '2d10' = 2d10 instead of 1d20 # 'max' = always roll maximum # 'min' = always roll minimum #################################################################### creatureTable = [ ( 8, 'fair'), ( 1, 'fair'), ( 1, 'fair'), ( 2, 'fair'), ( 2, 'fair'), ( 3, 'fair'), ] #################################################################### # Each run through calcRanking below only gives results based on # creatureTable[0]. At first I was only going to deal with this # problem, but then I realized that I can rotate through the # creatures starting with a new one each time. This way I can # calculate complete statistics. Unfortunately this gives an O(n^2) # running time and becomes unwieldy after about 20 creatures. You # can run many many more by not looping through all creatures. To # do this, change the commented for loop line below. placeList = [] # stores the odds for each ranking. mplaceList = [] # stores the average ranking. for k in range(len(creatureTable)): #for k in [0]: # Run calcRanking on the list N times, each time appending the # results on the end of placeList. placeList.append(calcRanking(creatureTable[k:]+creatureTable[:k])) mplaceList.append(sum([n*p for n, p in enumerate(placeList[-1])])+1) # Generate a report. print if die != 20: print "Rolling d%d's for initiative!" % die print print ' # | B |Statistics|', for n in range(len(creatureTable)): print ' %-2d ' % (n+1), print '| Avg' print '---+---+----------+', for n in range(len(creatureTable)): print '------', print '+-----' for cn, ((b, p), place, mplace) in enumerate(zip(creatureTable, placeList, mplaceList)): print '%2d' % (cn+1), print '|%2d' % b, print '|%-10s|' % p, for p in place: print '%5.1f%%' % (p*100), print '| %4.2f' % mplace print ## Generate the table for one-on-one statistics. #creatureTable = [ # [ 0, 'fair'], # [ 0, 'fair'], # ] #res = [] #for d in range(-die, die+1): # creatureTable[0][0] = d # res.append([d]+calcRanking(creatureTable)) # #for d, p, q in res: # print d, p ###################################################################### ###################################################################### def calcRanking(C): """ Calculates the statistics of creature C[0] ending in 1st through Nth place. All statistics are based on C[0], so you don't find out anything about the other creatures. """ # Creature C[0] is the main creature. Pull it out of the list. B0, pkey = C[0] pR = prob[pkey] res = [] # The strategy is assume creature C[0] rolls a value r. For each r, # calculate the odds of each place. Then at the end, weight each # result by the odds of C[0] rolling r. for r in range(die): # I'm not going to go into too much detail on the math. # Basically, I work out a distribution pair-wise for C[0] and each # other creature and then add them together (done through # convolution). To properly deal with multi-way ties, I need to # keep track of win, lose, and tie conditions. This makes it 2D # convolution. The tie cases are broken up later. mat = [[1]]; for B, pkey in C[1:]: p = prob[pkey] dB = B - B0 extra = r-dB; P0 = sum(p[:max(min(extra,die),0)]) Pt = 0 P1 = sum(p[max(extra+1,0):]); if 0 <= extra <= die-1: if dB > 0: P1 += p[extra] elif dB == 0: Pt = p[extra] else: P0 += p[extra] A = [ [P0, P1], [Pt, 0] ] mat = conv2(mat, A) pr = mat[0][:] ind = len(mat)-1 for n in range(ind): z = conv(mat[n+1][:-n-1], [1.0/(n+2)]*(n+2)) for i in range(len(pr)): pr[i] += z[i] res.append([v*pR[r] for v in pr]) return [sum(li) for li in zip(*res)] ###################################################################### ###################################################################### def conv(x, y): """ 1D convolution. The resultant vector is of length len(x)+len(y)-1. """ P, Q = len(x), len(y) N = P + Q - 1 z = [] for k in range(N): t = 0 lower = max(0, k-(Q-1)) upper = min(P-1, k) for i in range(lower, upper+1): t = t + x[i]*y[k-i] z.append(t) return z ###################################################################### ###################################################################### def conv2(A, B): """ 2D convolution. A and B are matrices stored as lists of lists. The resultant matrix has a number of rows of nrows(A)+nrows(B)-1, and number of columns of ncols(A)+ncols(B)-1. """ ar = len(A) ac = len(A[0]) br = len(B) bc = len(B[0]) cr = ar + br - 1 cc = ac + bc - 1 C = [] for k in range(cr): C.append([]) for j in range(cc): lrows = max(0, k-(br-1)) urows = min(ar-1, k) lcols = max(0, j-(bc-1)) ucols = min(ac-1, j) t = 0 for kk in range(lrows, urows+1): for jj in range(lcols, ucols+1): t += A[kk][jj]*B[k-kk][j-jj] C[-1].append(t) return C ###################################################################### # Start. Pass arguments into the main function at the top. if __name__ == '__main__': import sys main(sys.argv[1:]) [/code][/sblock] [/QUOTE]
Insert quotes…
Verification
Post reply
Community
General Tabletop Discussion
*Pathfinder & Starfinder
math on initiative
Top