Before anything else, here is some code that I refuse to work with in any remotely professional capacity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
import inspect import sys class TailCall(BaseException): def __init__(self, function, args, kwargs, exits): self.function = function self.args = args self.kwargs = kwargs self.exits = exits def process_exits(exits, exc_type=None, exc_value=None, traceback=None): for exit_group in reversed(exits): for exit in exit_group: if exit(exc_type, exc_value, traceback): exc_type = exc_value = traceback = None return exc_type is not None class tail_call: __thing = None def __init__(self, thing): self._thing = thing @property def _thing(self): return self.__thing @_thing.setter def _thing(self, value): self.__thing = value self.__doc__ = value.__doc__ def __get__(self, instance, owner): return tail_call( inspect.getattr_static(self._thing, "__get__")(self._thing, instance, owner) ) def __call__(self, *args, **kwargs): frame = sys._getframe() function = self._thing if ( frame.f_back and frame.f_back.f_back and frame.f_back.f_back.f_code == frame.f_code ): raise TailCall(function, args, kwargs, ) exits =  raised = False try: while True: try: return function(*args, **kwargs) except TailCall as tc: function = tc.function args = tc.args kwargs = tc.kwargs exits.append(tc.exits) except: raised = True if process_exits(exits, *sys.exc_info()): raise finally: if not raised: process_exits(exits) def call(self, *args, **kwargs): return self._thing(*args, **kwargs) def __enter__(self): return self._thing.__enter__() def __exit__(self, exc_type, exc_value, traceback): exit_func = self._thing.__exit__ if exc_type is not TailCall: return exit_func(exc_type, exc_value, traceback) exc_value.exits.append(exit_func)
Anyway, I'm going to try to go into a little more detail on the dice-rolling stuff. It might be useful to people rolling their own.
First off, one of the most important things to me in writing code to simulate die rolls, is making sure the rolls are handled transparently. While it's possible to consider simple rolls in terms of binomial distributions, turning rolls for a tabletop game into a black box doesn't really serve any purpose. My feeling on this is, we want to have as easy of a time as possible checking the logic; ideally, the roller should surface all of the information required to replicate the results by hand, given the random inputs used.
My next priority was presenting this information in a way that makes sense. To this end, I've got an object that effectively allows the construction of two parallel narratives. The first details what was rolled, and for what purpose. The second distills and interprets the results into high-level descriptions. Let's use the current maneuver roller:
>>> rolls.maneuver(10) # Don't know if you can do this in normal play. Rolling a maneuver die. Rolled 3. Rolling a maneuver die. Rolled 3. Rolling a maneuver die. Rolled 2. Rolling a maneuver die. Rolled 2. Rolling a maneuver die. Rolled 5. Rolling a maneuver die. Rolled 6. Rolling a maneuver die. Rolled 6. Rolling a maneuver die. Rolled 5. Rolling a maneuver die. Rolled 3. Rolling a maneuver die. Rolled 6. -------------------------------------------------------------------------------- Strike dice: 5. Charge dice: 3. Failed dice: 2. Earned an Awesome Token.
There are several aspects to this:
- The initial call
- The die roll reports.
- A bunch of dashes to separate the areas.
- The high-level report, which turns (in this case) twenty lines of rolling into 4 (usually 3 for lower numbers of dice) lines of results.
I'm going to try to wrap things up this week, but I'm right now thinking about writing an interface using the cmd module to simplify things. The big thing I'd add over the existing functionality is a top-level command for rolling arbitrary-sized dice, for how Mythic wants you to choose randomly out of lists sometimes.
I'll try to get that put together, and then show off the code later. I promise it's less horrifying than the stuff up top.
(The joke in the summary is that the monospaced text is going way off to the right because it is long and cannot wrap.)