Rolling Dice

Monday, July 21, 2008

When you don't have a set of dice lying around, or when 20d6+2d4 just takes too long to add up:

#!/usr/bin/env python
import logging
import random
import re
import sys

ROLL = re.compile(r'(\d*)d(\d+)')

def roll(dice):
  """Randomly generate the result of a dice roll.
  
  >>> 1 <= roll('d6') <= 6
  True
  >>> 4 <= roll('4d6') <= 24
  True
  >>> 2 <= roll('1d10+1d4') <= 14
  True
  >>> 2+1-1 <= roll('2d8+1d6-1') <= 16+6-1
  True
  """
  logging.debug("roll(%r)", dice)
  def _roll(m):
    x = m.group(1) and int(m.group(1)) or 1
    y = int(m.group(2))
    return '%d' % (x * random.randint(1, y))
  postroll = ROLL.sub(_roll, dice)
  logging.debug("roll(%r) -> %r", dice, postroll)
  
  value = eval(postroll, {}, {})
  logging.debug("eval(%r) -> %r", postroll, value)
  return value

def argz():
  """Define the arguments to this script."""
  from optparse import OptionParser
  parser = OptionParser()
  parser.set_defaults(log_level=logging.WARNING)
  
  parser.add_option('-d', '--debug',
    action='store_const', const=logging.DEBUG, dest='log_level')
  parser.add_option('-v', '--verbose',
    action='store_const', const=logging.INFO, dest='log_level')
  parser.add_option('-q', '--quiet',
    action='store_const', const=logging.ERROR, dest='log_level')
  
  parser.add_option('--test',
    action='store_true', dest='test', default=False,
    help="Run the unit tests.")
  
  parser.add_option('--stat',
    action='store_true', dest='stat', default=False,
    help="Roll 4d6, drop the lowest (for ability scores).")
  
  (options, args) = parser.parse_args()
  
  logging.basicConfig(level=options.log_level)
  
  if options.stat:
    dice = [roll('d6') for x in xrange(4)]
    logging.info("rolled %r for ability", dice)
    dice = sorted(dice)[1:]
    logging.debug("kept %r", dice)
    stat = sum(dice)
    print "%d (%+01d)" % (stat, (stat - 10) / 2)
    sys.exit(0)

  if options.test or len(args) == 0:
    import doctest
    doctest.testmod()
    sys.exit(0)
  
  return (options, args)

if __name__ == '__main__':
  (options, args) = argz()
  for dice in args:
    print roll(dice)

You can even roll stats!

$ for stat in STR DEX CON WIS INT CHA EXT
> do ./roll.py --stat
> done | sort -n | tail -n6
13 (+1)
14 (+2)
15 (+2)
16 (+3)
17 (+3)
17 (+3)


blog comments powered by Disqus