Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Thursday, March 11, 2010

AC Bonus vs Save, Death Match!

For various reasons I'm really wanting AC to be only armor and not a bunch of other stuff e.g. dex bonus, magic bonus, spell buff. I'm also trying to eliminate as many modifiers during game play as possible.

Using Target 20 with only positive AC (10-0). No Dex modifiers. I was also gonna ditch +x items until I read this magical +x bonus dice mechanic. Which is a great method for handling +x protective items. (well not so sure about picking two numbers and rolling multiple d20's rather than one number and d10's or picking one number per bonus and rolling single d10). Might even use it for shield bonus. Although, keeping "magic" as a separate/special mechanic has appeal.


It So Awesome

It makes +x magic less mundane. It also has the same effect of "Saving throws must be requested by players"(lost link). It puts more of players fate in to their hands. Like The Mule said it provides dramatic tension and excitement at the table over rolling dice.
"... take aspects of the game that normally get resolved off-screen beforehand and instead make them happen at the table as the spotlighted consequence of a dramatic event."

"... even if a PC’s magical protection will stop a blow most of the time, I want to make the players sweat in the interval between when I announce the hit and when their magic save comes through for them!"
And it speeds combat resolution.
"... dozens of men-at-arms in the combat it’s much easier if I can just roll a handful of dice and count all the 17s or above, knowing that such rolls always have a chance of hitting any target."

Lies, and Damn Lies

I was real curious about the statistical analysis. So, I broke out my Python dice code and ran (a lot) of tests. First, some results I'm not gonna bother showing you:
  • picking one number and rolling d10 was close enough to picking two numbers and rolling d20 that I'm only using former.
  • picking one number and rolling d20 caused even greater variation than d10s so ditched that idea.
So, pick one number, roll d10's equal to bonus, if number comes up then no hit (a save). Bonus from 1 to 3, to hit from 1 to 9, even ACs 10 to 2. Random results (rather than calculated) so 100,000 swings were made per row.

d20 + (AC-bonus) + tohit >= 20 is a hit
d20 + AC + tohit >= 20 and failed save(s) is a hit

100000 iterations of +1 tohit vs +1 to AC or 1d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 9 54.92% | AC 10 53.94% | 0.98% |
AC 7 45.07% | AC 8 44.94% | 0.13% |
AC 5 34.93% | AC 6 35.96% | -1.03% |
AC 3 24.85% | AC 4 26.88% | -2.03% |
AC 1 15.02% | AC 2 18.06% | -3.04% |

100000 iterations of +1 tohit vs +2 to AC or 2d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 8 50.27% | AC 10 48.79% | 1.48% |
AC 6 40.05% | AC 8 40.63% | -0.58% |
AC 4 30.09% | AC 6 32.64% | -2.55% |
AC 2 20.12% | AC 4 24.42% | -4.30% |
AC 0 9.97% | AC 2 16.18% | -6.22% |

100000 iterations of +1 tohit vs +3 to AC or 3d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 7 44.84% | AC 10 43.78% | 1.06% |
AC 5 35.04% | AC 8 36.30% | -1.26% |
AC 3 25.00% | AC 6 29.23% | -4.23% |
AC 1 15.03% | AC 4 21.96% | -6.93% |
AC -1 5.00% | AC 2 14.53% | -9.52% |


100000 iterations of +2 tohit vs +1 to AC or 1d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 9 60.14% | AC 10 58.63% | 1.51% |
AC 7 49.67% | AC 8 49.26% | 0.42% |
AC 5 40.20% | AC 6 40.66% | -0.46% |
AC 3 29.76% | AC 4 31.36% | -1.60% |
AC 1 19.88% | AC 2 22.25% | -2.37% |

100000 iterations of +2 tohit vs +2 to AC or 2d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 8 54.93% | AC 10 52.52% | 2.41% |
AC 6 45.04% | AC 8 44.53% | 0.51% |
AC 4 35.14% | AC 6 36.41% | -1.26% |
AC 2 24.88% | AC 4 28.32% | -3.44% |
AC 0 15.02% | AC 2 20.10% | -5.08% |

100000 iterations of +2 tohit vs +3 to AC or 3d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 7 49.80% | AC 10 47.04% | 2.76% |
AC 5 40.08% | AC 8 40.14% | -0.05% |
AC 3 29.80% | AC 6 32.88% | -3.07% |
AC 1 20.01% | AC 4 25.37% | -5.36% |
AC -1 9.88% | AC 2 17.97% | -8.09% |


100000 iterations of +3 tohit vs +1 to AC or 1d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 9 64.88% | AC 10 62.93% | 1.95% |
AC 7 54.92% | AC 8 53.98% | 0.94% |
AC 5 45.21% | AC 6 45.25% | -0.04% |
AC 3 34.89% | AC 4 35.91% | -1.02% |
AC 1 24.94% | AC 2 27.00% | -2.06% |

100000 iterations of +3 tohit vs +2 to AC or 2d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 8 60.03% | AC 10 56.57% | 3.46% |
AC 6 50.16% | AC 8 48.76% | 1.40% |
AC 4 40.01% | AC 6 40.51% | -0.50% |
AC 2 29.96% | AC 4 32.17% | -2.21% |
AC 0 19.80% | AC 2 24.21% | -4.41% |

100000 iterations of +3 tohit vs +3 to AC or 3d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 7 55.19% | AC 10 51.02% | 4.17% |
AC 5 44.88% | AC 8 43.71% | 1.17% |
AC 3 35.09% | AC 6 36.39% | -1.30% |
AC 1 25.08% | AC 4 29.24% | -4.16% |
AC -1 14.93% | AC 2 21.82% | -6.89% |


100000 iterations of +4 tohit vs +1 to AC or 1d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 9 69.88% | AC 10 67.56% | 2.32% |
AC 7 59.88% | AC 8 58.40% | 1.48% |
AC 5 49.88% | AC 6 49.41% | 0.46% |
AC 3 39.81% | AC 4 40.42% | -0.61% |
AC 1 29.93% | AC 2 31.30% | -1.36% |

100000 iterations of +4 tohit vs +2 to AC or 2d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 8 65.08% | AC 10 60.70% | 4.39% |
AC 6 55.06% | AC 8 52.74% | 2.33% |
AC 4 44.89% | AC 6 44.57% | 0.32% |
AC 2 34.79% | AC 4 36.24% | -1.44% |
AC 0 24.91% | AC 2 28.47% | -3.55% |

100000 iterations of +4 tohit vs +3 to AC or 3d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 7 59.88% | AC 10 54.75% | 5.12% |
AC 5 49.85% | AC 8 47.28% | 2.58% |
AC 3 39.95% | AC 6 39.99% | -0.04% |
AC 1 30.00% | AC 4 33.13% | -3.12% |
AC -1 20.10% | AC 2 25.47% | -5.37% |


100000 iterations of +5 tohit vs +1 to AC or 1d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 9 75.06% | AC 10 72.03% | 3.03% |
AC 7 65.09% | AC 8 63.13% | 1.96% |
AC 5 55.04% | AC 6 54.02% | 1.02% |
AC 3 45.02% | AC 4 45.05% | -0.03% |
AC 1 35.12% | AC 2 36.18% | -1.06% |

100000 iterations of +5 tohit vs +2 to AC or 2d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 8 70.03% | AC 10 64.62% | 5.41% |
AC 6 60.14% | AC 8 56.71% | 3.43% |
AC 4 50.14% | AC 6 48.62% | 1.52% |
AC 2 40.18% | AC 4 40.70% | -0.52% |
AC 0 29.88% | AC 2 32.31% | -2.43% |

100000 iterations of +5 tohit vs +3 to AC or 3d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 7 65.13% | AC 10 58.43% | 6.70% |
AC 5 54.95% | AC 8 51.22% | 3.74% |
AC 3 45.00% | AC 6 43.71% | 1.29% |
AC 1 34.64% | AC 4 36.24% | -1.60% |
AC -1 24.99% | AC 2 29.20% | -4.21% |


I think you get the idea. Here's the last one, +9 tohit.


100000 iterations of +9 tohit vs +1 to AC or 1d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 9 94.98% | AC 10 90.01% | 4.97% |
AC 7 84.99% | AC 8 81.21% | 3.79% |
AC 5 75.22% | AC 6 72.21% | 3.00% |
AC 3 65.39% | AC 4 63.32% | 2.07% |
AC 1 55.01% | AC 2 53.99% | 1.02% |

100000 iterations of +9 tohit vs +2 to AC or 2d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 8 90.07% | AC 10 80.90% | 9.18% |
AC 6 79.95% | AC 8 72.97% | 6.99% |
AC 4 69.93% | AC 6 65.15% | 4.78% |
AC 2 59.94% | AC 4 56.60% | 3.34% |
AC 0 50.31% | AC 2 48.89% | 1.41% |

100000 iterations of +9 tohit vs +3 to AC or 3d10 save rolls.
hit w/ bonus | hit w/ saves | diff |
AC 7 85.17% | AC 10 72.73% | 12.44% |
AC 5 74.89% | AC 8 65.72% | 9.17% |
AC 3 64.87% | AC 6 58.25% | 6.62% |
AC 1 54.97% | AC 4 51.16% | 3.81% |
AC -1 45.11% | AC 2 43.69% | 1.42% |

Conclusion
  • The difference between normal AC modification and save mechanic vary greatly depending on mix of AC, tohit, bonus.
  • The difference between normal AC modification and save mechanic regularly very large 5-10%
  • Save mechanic starts out mostly worse than AC modification. This gradually changes as tohit bonus increases. At +9 save is always better than AC modification.
  • Save mechanic compresses to hit percentage range i.e. AC10 is less likely and AC2 more likely to be hit.
  • The worse your AC is, the more beneficial saves are. Means bonus items are more valuable to low AC types.
  • The better tohit bonus opponent has, the more beneficial saves are. Means bonus items are less helpful vs weak opponents and very helpful against high lvl threats.
I don't think it bothers me much that this mechanic produces quite different results than modifying AC.

This save mechanic fits very well my ideal for shields. Providing large bonus to light armor and less and less as armor improves. Not mucking up AC, would blend nicely with shields shall be shattered, no changing ac if shield is / is not used. But then I thought of all the freakin dice rolls. Even if used just for +x protective magics I gotta think 1/2 the party is gonna have those and need to make extra dice rolls during combat.

I've become less sold. I really need to try it out in play.

BTW this mechanic is "reversable" for use with +x weapons. If miss with one use similar save mechanic to see if magic makes it a hit anyways. Very powerful for those who can't hit worth a damn. Less useful to martial masters. That sits well with my sensibilities.

Wednesday, October 28, 2009

Hackmaster / Exploding Dice Analysis


Back when reviewing Hackmaster I wondered about the penetration (exploding) dice. I worried that a d4 would be better than d6 as the d4 was more likely to roll it's max value and explode than the d6. Being a programmer dude I wrote code and ran some tests.

The results of "throwing" each type of die 100,000 times.
die   avg    max
d4p 3.01 24
d6p 3.99 37
d8p 5.00 49
d12p 7.00 55
As you can see, exploding only increases the avg by about .5 and max values ramp up nicely. If you roll 100,000 times you can get some impressive max rolls. But, super high explosions are rare. Here are the details on d4, d6, and d12 (chosen cause of this) rolled "only" 12,000 times each. Results are value rolled, number of times it was rolled, % chance of rolling that value, % chance of rolling that value or higher.
d4p count chance cumulative
1 2954 24.62% 100.00%
2 3060 25.50% 75.38%
3 2991 24.93% 49.88%
4 756 6.30% 24.96%
5 796 6.63% 18.66%
6 740 6.17% 12.02%
7 177 1.47% 5.86%
8 184 1.53% 4.38%
9 181 1.51% 2.85%
10 43 0.36% 1.34%
11 43 0.36% 0.98%
12 42 0.35% 0.62%
13 11 0.09% 0.27%
14 3 0.03% 0.18%
15 10 0.08% 0.16%
16 5 0.04% 0.07%
17 not rolled
18 3 0.03% 0.03%
19 1 0.01% 0.01%
20 not rolled
21 1 0.01% -0.00%

d6p count chance cumulative
1 1951 16.26% 100.00%
2 2064 17.20% 83.74%
3 1922 16.02% 66.54%
4 1993 16.61% 50.53%
5 1992 16.60% 33.92%
6 334 2.78% 17.32%
7 347 2.89% 14.53%
8 331 2.76% 11.64%
9 370 3.08% 8.88%
10 322 2.68% 5.80%
11 60 0.50% 3.12%
12 68 0.57% 2.62%
13 43 0.36% 2.05%
14 75 0.62% 1.69%
15 64 0.53% 1.07%
16 7 0.06% 0.53%
17 11 0.09% 0.48%
18 9 0.07% 0.38%
19 12 0.10% 0.31%
20 16 0.13% 0.21%
21 not rolled
22 not rolled
23 4 0.03% 0.08%
24 2 0.02% 0.04%
25 1 0.01% 0.03%
26 not rolled
27 not rolled
28 2 0.02% 0.02%
29 1 0.01% 0.00%

d12p count chance cumulative
1 968 8.07% 100.00%
2 978 8.15% 91.93%
3 991 8.26% 83.78%
4 1004 8.37% 75.53%
5 987 8.22% 67.16%
6 996 8.30% 58.93%
7 980 8.17% 50.63%
8 982 8.18% 42.47%
9 1006 8.38% 34.28%
10 1025 8.54% 25.90%
11 1057 8.81% 17.36%
12 87 0.73% 8.55%
13 78 0.65% 7.83%
14 74 0.62% 7.18%
15 80 0.67% 6.56%
16 88 0.73% 5.89%
17 99 0.83% 5.16%
18 82 0.68% 4.33%
19 93 0.78% 3.65%
20 95 0.79% 2.87%
21 83 0.69% 2.08%
22 84 0.70% 1.39%
23 6 0.05% 0.69%
24 8 0.07% 0.64%
25 7 0.06% 0.57%
26 5 0.04% 0.52%
27 10 0.08% 0.47%
28 7 0.06% 0.39%
29 8 0.07% 0.33%
30 5 0.04% 0.27%
31 8 0.07% 0.22%
32 7 0.06% 0.16%
33 5 0.04% 0.10%
34 not rolled
35 3 0.03% 0.06%
36 not rolled
37 2 0.02% 0.03%
38 not rolled
39 not rolled
40 not rolled
41 not rolled
42 not rolled
43 not rolled
44 1 0.01% 0.02%
45 not rolled
46 not rolled
47 1 0.01% 0.01%
48 not rolled
49 1 0.01% -0.00%
5% chance for 17+ damage is a little high (2d12 avg=13). Seems not too out of whack for "critical hit" system. Still might change 2-hand damage to d10p.

In my Post on Weapon Damage a comment was made that exploding dice combined with my house rule, pure fighters get to roll damage twice and take the higher result, is too much of a damage escalation. An unmentioned tweak is fighters get to take the higher of the initial non-exploded roll, they don't roll exploded dice twice. Mostly cause I don't want to deal with insane amount of dice rolling / tracking.

So, modifying my program... Here are 2d10 and 2d12 rolled 12000 times, highest initial roll taken, and then exploded as appropriate.
d10p count chance cumulative
1 131 1.09% 100.00%
2 351 2.93% 98.91%
3 565 4.71% 95.98%
4 867 7.22% 91.28%
5 1082 9.02% 84.05%
6 1278 10.65% 75.03%
7 1600 13.33% 64.38%
8 1763 14.69% 51.05%
9 2072 17.27% 36.36%
10 232 1.93% 19.09%
11 241 2.01% 17.16%
12 233 1.94% 15.15%
13 218 1.82% 13.21%
14 213 1.77% 11.39%
15 243 2.02% 9.62%
16 226 1.88% 7.59%
17 207 1.73% 5.71%
18 234 1.95% 3.98%
19 26 0.22% 2.03%
20 23 0.19% 1.82%
21 24 0.20% 1.63%
22 13 0.11% 1.43%
23 28 0.23% 1.32%
24 27 0.22% 1.08%
25 33 0.27% 0.86%
26 19 0.16% 0.58%
27 23 0.19% 0.43%
28 5 0.04% 0.23%
29 2 0.02% 0.19%
30 1 0.01% 0.18%
31 6 0.05% 0.17%
32 2 0.02% 0.12%
33 3 0.03% 0.10%
34 2 0.02% 0.08%
35 1 0.01% 0.06%
36 7 0.06% 0.05%

d12p count chance cumulative
1 83 0.69% 100.00%
2 227 1.89% 99.31%
3 416 3.47% 97.42%
4 573 4.78% 93.95%
5 767 6.39% 89.17%
6 885 7.38% 82.78%
7 1067 8.89% 75.41%
8 1291 10.76% 66.52%
9 1366 11.38% 55.76%
10 1590 13.25% 44.38%
11 1813 15.11% 31.12%
12 148 1.23% 16.02%
13 149 1.24% 14.78%
14 182 1.52% 13.54%
15 163 1.36% 12.02%
16 141 1.18% 10.67%
17 159 1.32% 9.49%
18 153 1.27% 8.17%
19 181 1.51% 6.89%
20 154 1.28% 5.38%
21 148 1.23% 4.10%
22 182 1.52% 2.87%
23 13 0.11% 1.35%
24 13 0.11% 1.24%
25 20 0.17% 1.13%
26 14 0.12% 0.97%
27 11 0.09% 0.85%
28 19 0.16% 0.76%
29 7 0.06% 0.60%
30 10 0.08% 0.54%
31 10 0.08% 0.46%
32 18 0.15% 0.37%
33 19 0.16% 0.22%
34 1 0.01% 0.07%
35 1 0.01% 0.06%
36 1 0.01% 0.05%
37 not rolled
38 not rolled
39 not rolled
40 2 0.02% 0.04%
41 1 0.01% 0.02%
42 2 0.02% 0.02%
43 1 0.01% -0.00%
Chance of exploding is 2x normal what I'd expected. Did not realize "rolling twice taking best" would produce such high results. 55% chance of 9 or higher on d12.


For completeness here's d6p as rolled by fighter.
d6p  count chance cumulative
1 328 2.73% 100.00%
2 1016 8.47% 97.27%
3 1727 14.39% 88.80%
4 2322 19.35% 74.41%
5 2937 24.47% 55.06%
6 620 5.17% 30.58%
7 589 4.91% 25.42%
8 580 4.83% 20.51%
9 632 5.27% 15.68%
10 612 5.10% 10.41%
11 104 0.87% 5.31%
12 101 0.84% 4.44%
13 104 0.87% 3.60%
14 107 0.89% 2.73%
15 102 0.85% 1.84%
16 18 0.15% 0.99%
17 21 0.18% 0.84%
18 18 0.15% 0.67%
19 23 0.19% 0.52%
20 22 0.18% 0.33%
21 1 0.01% 0.14%
22 3 0.03% 0.13%
23 2 0.02% 0.11%
24 2 0.02% 0.09%
25 5 0.04% 0.08%
26 not rolled
27 not rolled
28 not rolled
29 1 0.01% 0.03%
30 1 0.01% 0.03%
31 1 0.01% 0.02%
32 not rolled
33 not rolled
34 not rolled
35 2 0.02% 0.01%

Sunday, December 28, 2008

DMG Based Dungeon Generator

In my current 3.5 campaign the players will be shortly chasing after the Keys of Time, which are spread all over various alternate dimensions. Since I totally got bit with the old-school bug I've decided one of the keys will be found in an old-style Dungeon Crawl Dimension. The characters will start in a 10'x10' dark room(hope they have someway to make light). In the corner will be a 10' pole, a lantern, bundle of torches and a backpack filled with 3 flasks of oil, 10 spikes, a hammer, 50' rope, flint&steel, 1 wk iron rations, and a potion of healing. In each wall will be a wooden door, stuck of course. From there I planned to use the random dungeon generation tables from the DMG. But they turned out to be too unwieldy for use during game. So, I whipped up this simple program to do all the rolls and table lookups for me.

After doing a couple of test runs, I'm a little disappointed with appendix A. Not enough stuff (monsters/treatures/traps), too many freaking wide passages. Too many levels up/down (for my purposes).

For v2 I'll change passages widths to 50% 5', 30% 10', 20% other. Make Traps/Treasures/Monsters more common. Add in room types and dungeon dressings from the other appendixes. Maybe simplified tables to generate magic items, wandering monsters.

There's probably a million of these, this one is mine. Python source dmg_dungeon_generator.py

Python is keen, by far my favorite programming language. It's a bit like modern rules lite RPG's, there's very little syntax and "rules" to it and what there is has a consistency. Makes it easier to tinker with. For instance the ability to redefine how objects are converted to strings, __str__, lets me define random tables that "roll" themselves when printed. Like so
Door = table_d20(
  ( 6, "Door left leading to a", Beyond_Door, ),
  (12, "Door right leading to a", Beyond_Door, ),
  (20, "Door ahead leading to a", Beyond_Door, ),
  )
Beyond_Door = table_d20(
  ( 4, Passage_Width, "parallel passage extending 30' in both directions. Or 10'x10'.", ),
  ( 8, Passage_Width, "passage straight ahead.", ),
  ( 9, Passage_Width, "passage ahead/behind 45deg.", ),
  (10, Passage_Width, "passage behind/ahead 45deg.", ),
  (18, Room, Contents, ),
  (20, Chamber, Contents, ),
  )
The simple python statement print Door will randomly roll a d20, say 11, look up matching row in Door table print "Door right leading to a ". Then roll another d20 this time on Beyond_Door table, which leads to other tables and text and so on.

Those are interpreted (along with a few helper classes/functions) by this relatively short class. Not stupendous but I like how simple/clean/flexible the table definitions are.
class Table(object):
  """Object that when evaluated into string will return random result from table"""
  def __init__(self, dice, *table):
      """@param dice: callable that returns something comparable to x.
      @param *table: list of tuples (x,foo1,foo2,fooN) where x is comparable to return value of dice and foo? are evaluatable into strings.
      """
      self.dice = dice
      self.table = table
  def __str__(self):
      """@return: foo1,foo2,fooN stringified and space separated, from row in table where x >= return value of dice.
      """
      return " ".join(str(s) for s in self.get_result())
  def get_result(self):
      roll = self.dice()
      for row in self.table:
          if row[0] >= roll:
              return row[1:]
      return self.table[-1][1:]

def table_d20(*table):
  return Table(lambda: random.randint(1, 20), *table)
The table_d20 is a helper function to return a Table object preset to use d20 for its dice rolls. It takes a list of arguments, *table, which in our case are the rows of the table. It passes those along with a lambda function that returns a number between 1 and 20 (our d20 die). We need to wrap the random.randint in a lambda so it's callable, since we want a new random number every time we "roll" on the table.

The Table classes __init__ is nothing special. And the __str__ function just applies the str builtin to each item in the list that self.get_result() returns. It then compiles them all into one string, added a space between each one. If you did now know __str__ gets called whenever the object is converted to a string. e.g str(obj) is basically obj.__str__()

The get_result has all the action. It "rolls the dice" then iterates over the table rows looking at the first item until it finds a match. It then returns all but the first of that row's items. There's a fail safe, if no matching row is found it returns items from the last row.

All Time Most Popular Posts