Source code for thekraf.diceutils

"""
thekraf.diceutils
=================

Utilities for use with dice
"""
import collections
import logging
import random
import string
from itertools import combinations
import thekraf.config as cfg

logr = logging.getLogger('thk')


[docs]def norm_dice(arg_index=0): """Normalize any dice representations as true dice Args: arg_index (int): Index of the target arg in the parameter list. Usually this would be the first arg, but it would be the second if used on a class or instance method. Returns: Configured decorator """ def decorator(func): def wrapped(*args, **kwargs): mod_args = list(args) # If dice was passed in as a kwarg if 'dice' in kwargs: kwargs['dice'] = DiceUtils.create_dice(kwargs['dice']) # Modify the indicated argument elif len(mod_args) > arg_index: mod_args[arg_index] = DiceUtils.create_dice(mod_args[arg_index]) return func(*mod_args, **kwargs) return wrapped return decorator
[docs]class DiceUtils(object): """Utilities for use with/on dice""" VALS = cfg.GAME_DICE_VALS @staticmethod
[docs] def roll(n): random.seed() return tuple(random.choice(DiceUtils.VALS) for _ in range(n))
@staticmethod
[docs] def concat(dice, other): """Concatenate dice Args: dice (tuple[int]): Starting dice other (tuple[int]): Dice to add on Returns: tuple[int]: Concatenated dice """ return sum((dice, other), ())
@staticmethod
[docs] def diff(minuend, subtrahend): """Return dice in minuend less the dice in the subtrahend Args: minuend (tuple[int]): Starting dice subtrahend (tuple[int]): The dice to remove from `minuend` Returns: tuple[int]: The difference """ minuend = list(minuend) # Note you can't use a comprehension because you might remove too much # diff = [item for item in minuend if item not in subtrahend] # Won't work --^ for item in subtrahend: del minuend[minuend.index(item)] return tuple(minuend)
@staticmethod
[docs] def is_dice_repr(obj): """True if `obj` appears to be a valid dice representation Args: obj (object): Object to test Returns: bool: """ # If string if isinstance(obj, str): return True # If sequence of strings or ints elif isinstance(obj, collections.Sequence): if not obj or isinstance(obj[0], (int, str)): return True return False
@staticmethod
[docs] def is_dice_inst(obj): """True if `obj` is an instance of dice Args: obj (object): Object to test Returns: bool: """ if isinstance(obj, tuple): if not obj or isinstance(obj[0], int): return True return False
@classmethod
[docs] def create_dice(cls, dice_repr): """Create dice values from various representations Representation can be: * string * '123456' * '1 2 3 4 5 6' * '1,2,3,4,5,6' * sequence of strings * ['1', '1', '2', '2'] * sequence of int (basically passthrough) * (3, 4, 5) Args: dice_repr (str): String to parse Returns: tuple[int]: Corresponding values Raises: TypeError: If `dice_repr` can't be converted to dice ValueError: If dice values are not in :attr:`.VALS` """ # No conversion if already dice if DiceUtils.is_dice_inst(dice_repr): dice = tuple(dice_repr) # Otherwise convert repr to dice elif DiceUtils.is_dice_repr(dice_repr): dice_repr = ''.join(map(str, dice_repr)) dice = tuple(int(c) for c in dice_repr if c in string.digits) else: raise TypeError('Cannot create dice from {}'.format(repr(dice_repr))) if not all(die in cls.VALS for die in dice): raise ValueError('Values {} -> {} must be in {}'.format( repr(dice_repr), repr(dice), cls.VALS, )) return dice
@staticmethod
[docs] def as_string(dice): """Convert dice to string representation Dice are sorted before conversion so this also the key for the caches Args: dice (tuple[int]): Dice to convert Returns: str: Corresponding string """ assert DiceUtils.is_dice_inst(dice) return ''.join(map(str, sorted(dice)))
@staticmethod @norm_dice()
[docs] def indices_of(dice, subset): """Return the indices of the subset in dice This requires more that a simple comprehension because items may be repeated. A repeated item will search for its index after the index of the previously found item. Args: dice (tuple[int]): Dice to search subset (tuple[int]): Dice for which to search Returns: tuple[int]: The indices """ subset = DiceUtils.create_dice(subset) indices = [] begins = {} for die in subset: i = dice.index(die, begins.get(die, 0)) indices.append(i) # If this item is repeated, it will start searching after the # current index begins[die] = i + 1 return tuple(indices)
# class Action(object): # # def __init__(self, dice, scoring_dice, score=None): # self.dice = dice # self.scoring_dice = scoring_dice # if score is None: # self.score = calc_score(self.dice) # else: # self.score = score # # ACTION_DICT = {} # # # def possible_actions(unsorted_dice): # dice = sorted(unsorted_dice) # key = ''.join(str(d) for d in dice) # logr.debug('key: "{}"'.format(key)) # if key in ACTION_DICT: # logr.debug('key already in dict') # return ACTION_DICT[key] # # actions = [] # for count in range(1, len(dice) + 1)[::-1]: # for combo in combinations(dice, count): # action = Action(dice, combo) # if action.score: # actions.append(action)