olinox 8 年之前
父節點
當前提交
39f6a97d32
共有 2 個文件被更改,包括 173 次插入144 次删除
  1. 5 0
      .gitignore
  2. 168 144
      dice.py

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+*.pyc
+.project
+.pydevproject
+.coverage
+htmlcov/*

+ 168 - 144
dice.py

@@ -1,144 +1,168 @@
-'''
-
-'''
-import random
-import re
-
-_ALLOWED = {'abs': abs}
-
-def roll_d(pattern):
-    """ Parse, roll and evaluate the pattern.
-
-    'pattern' can be any mathematics expression and include dice notations ('xDx').
-    Eg: '1d4+2', '10d6+2-1d20', '3*(1d6+2)'...Etc
-
-    Returns tuple of score (integer) and detailed result (string)
-    """
-    # Parse the pattern
-    parsed = _parse(pattern)
-    raw = _forcejoin(parsed)
-    return _secured_eval(raw), raw
-
-def roll(pattern):
-    """ Similar to roll_d(), but only return the numeric results (integer) """
-    return roll_d(pattern)[0]
-
-def _secured_eval(raw):
-    """ securely evaluate the incoming raw string """
-    return eval(raw, {"__builtins__":None}, _ALLOWED)
-
-def _forcejoin(obj_list):
-    """ force-join the objects of the list by forcing string conversion of its items """
-    return "".join(map(str, obj_list))
-
-def _parse(pattern):
-    """ split the members and symbols of the pattern and roll them when it is possible """
-    return [_roll_if_u_can(m) for m in _split_members(pattern)]
-
-def _normalize(pattern):
-    """ normalize the incoming string to a lower string without spaces"""
-    return str(pattern).replace(" ", "").lower()
-
-def _split_members(pattern):
-    """ split a string by blocks of numeric / symbols / dice notations
-    eg: '1d6+2' becomes ['1d6', '+', '2']
-    """
-    return re.findall(r"[\w']+|[\W']+", _normalize(pattern))
-
-def _roll_if_u_can(member):
-    """ try to interpret member as a dice notation and roll it.
-    If it can not, member is returned as it was."""
-    try:
-        return Dice.parse(member).roll()
-    except ValueError:
-        return member
-
-class Dice():
-    """ Dice(sides, amount=1)
-    Set of dice. Use roll() to get a score.
-    """
-    def __init__(self, sides, amount=1):
-        self._sides = 1
-        self._amount = 0
-
-        self.sides = sides
-        self.amount = amount
-
-    @property
-    def sides(self):
-        return self._sides
-
-    @sides.setter
-    def sides(self, sides):
-        try:
-            if not int(sides) >= 1:
-                raise ValueError()
-        except (TypeError, ValueError):
-            raise TypeError("Invalid value for sides (given: '{}')".format(sides))
-        self._sides = sides
-
-    @property
-    def amount(self):
-        return self._amount
-
-    @amount.setter
-    def amount(self, amount):
-        try:
-            if not int(amount) >= 0:
-                raise ValueError()
-        except (TypeError, ValueError):
-            raise ValueError("Invalid value for amount (given: '{}')".format(amount))
-        self._amount = amount
-
-    def roll(self):
-        """ Role the dice and return a Score object """
-        return Score([random.randint(1, self._sides) for _ in range(self._amount)])
-
-    @classmethod
-    def parse(cls, pattern):
-        """ parse a pattern of the form 'xdx', where x are positive integers """
-        # normalize
-        pattern = str(pattern).replace(" ", "").lower()
-        # parse
-        amount, faces = (int(x) for x in pattern.split("d"))
-        # instanciate
-        return Dice(faces, amount)
-
-class Score(int):
-    """ Score is a subclass of integer.
-    Then you can manipulate it as you would do with an integer.
-
-    It also provides an access to the detail with the property 'results'.
-    'results' is the list of the scores obtained by each dice.
-
-    Score class can also be used as an iterable, to walk trough the individual scores.
-
-    eg:
-        >>> s = Score([1,2,3])
-        >>> print(s)
-        6
-        >>> s + 1
-        7
-        >>> list(s)
-        [1,2,3]
-
-    """
-    def __new__(cls, results):
-        score = super(Score, cls).__new__(cls, sum(results))
-        score._results = results
-        return score
-
-    @property
-    def results(self):
-        return self._results
-
-    def __repr__(self):
-        return "<<{} ({})>>".format(int(self), self.results)
-
-    def __contains__(self, value):
-        return self.results.__contains__(value)
-
-    def __iter__(self):
-        return self.results.__iter__()
-
-# print(roll("2*(2d1+1d1-1d1-3)//2"))
+'''
+    pydice is a lightweight python library for managing rolls of dice.
+
+    License: GNU
+
+@author: olinox14, 2017
+'''
+import random
+import re
+
+__VERSION__ = 0.1
+
+def compile(pattern_string):  # @ReservedAssignment
+    p = Pattern(pattern_string)
+    p.compile()
+    return p
+
+def roll(pattern_string):
+    return Pattern(pattern_string).roll()
+
+def rolldice(faces, amount=1):
+    return Dice(faces, amount).roll()
+
+_ALLOWED = {'abs': abs}
+
+def _secured_eval(raw):
+    """ securely evaluate the incoming raw string by avoiding the use of any non-allowed function """
+    return eval(raw, {"__builtins__":None}, _ALLOWED)
+
+class Dice():
+    """
+    Dice(sides, amount=1):
+    Set of dice.
+
+    Use roll() to get a Score() object.
+    """
+    def __init__(self, sides, amount=1):
+        self._sides = 1
+        self._amount = 0
+
+        self.sides = sides
+        self.amount = amount
+
+    @property
+    def sides(self):
+        return self._sides
+
+    @sides.setter
+    def sides(self, sides):
+        try:
+            if not int(sides) >= 1:
+                raise ValueError()
+        except (TypeError, ValueError):
+            raise TypeError("Invalid value for sides (given: '{}')".format(sides))
+        self._sides = sides
+
+    @property
+    def amount(self):
+        return self._amount
+
+    @amount.setter
+    def amount(self, amount):
+        try:
+            if not int(amount) >= 0:
+                raise ValueError()
+        except (TypeError, ValueError):
+            raise ValueError("Invalid value for amount (given: '{}')".format(amount))
+        self._amount = amount
+
+    def __repr__(self):
+        return "<Dice; sides={}; amount={}>".format(self.sides, self.amount)
+
+    def roll(self):
+        """ Role the dice and return a Score object """
+        return Score([random.randint(1, self._sides) for _ in range(self._amount)])
+
+    @classmethod
+    def parse(cls, pattern):
+        """ parse a pattern of the form 'xdx', where x are positive integers """
+        # normalize
+        pattern = str(pattern).replace(" ", "").lower()
+        # parse
+        amount, faces = (int(x) for x in pattern.split("d"))
+        # instanciate
+        return Dice(faces, amount)
+
+class Score(int):
+    """ Score is a subclass of integer.
+    Then you can manipulate it as you would do with an integer.
+
+    It also provides an access to the detail with the property 'results'.
+    'results' is the list of the scores obtained by each dice.
+
+    Score class can also be used as an iterable, to walk trough the individual scores.
+
+    eg:
+        >>> s = Score([1,2,3])
+        >>> print(s)
+        6
+        >>> s + 1
+        7
+        >>> list(s)
+        [1,2,3]
+
+    """
+    def __new__(cls, detail):
+        score = super(Score, cls).__new__(cls, sum(detail))
+        score._detail = detail
+        return score
+
+    @property
+    def detail(self):
+        return self._detail
+
+    def __repr__(self):
+        return "<Score; score={}; detail={}>".format(self.detail, int(self))
+
+    def __contains__(self, value):
+        return self.detail.__contains__(value)
+
+    def __iter__(self):
+        return self.detail.__iter__()
+
+
+class Pattern():
+    def __init__(self, instr):
+        self.instr = Pattern._normalize(instr)
+        self.dices = {}
+        self.format_string = ""
+
+    @staticmethod
+    def _normalize(instr):
+        """ normalize the incoming string to a lower string without spaces"""
+        return str(instr).replace(" ", "").lower()
+
+    def compile(self):
+
+        def _submatch(match):
+            dice = Dice.parse(match.group(0))
+            name = "d{}".format(len(self.dices) + 1)
+            self.dices[name] = dice
+            return "{{{}}}".format(name)
+
+        self.format_string = re.sub('\d+d\d+', _submatch, self.instr)
+
+    def roll(self):
+        if not self.format_string:
+            self.compile()
+        scores = {name: dice.roll() for name, dice in self.dices.items()}
+        return PatternScore(self.format_string, scores)
+
+class PatternScore(int):
+    def __new__(cls, eval_string, scores):
+        ps = super(PatternScore, cls).__new__(cls, _secured_eval(eval_string.format(**scores)))
+
+        ps._eval_string = eval_string
+        ps._scores = scores
+
+        return ps
+
+    def format(self):
+        return self._eval_string.format(**{name: str(list(score)) for name, score in self._scores.items()})
+
+    def score(self, i):
+        return self.scores[i]
+
+    def scores(self):
+        return self._scores