瀏覽代碼

Include the 'Rx(...)' notation

olinox 8 年之前
父節點
當前提交
18cfba129d
共有 2 個文件被更改,包括 27 次插入12 次删除
  1. 10 4
      test.py
  2. 17 8
      xdice.py

+ 10 - 4
test.py

@@ -54,6 +54,7 @@ class Test(unittest.TestCase):
         xdice.roll("3dlh")
         xdice.roll("1d%")
         xdice.roll("d%")
+        xdice.roll("1+R3(1d6+1)")
 
         # test invalid expressions
         self.assertRaises(ValueError, xdice.roll, "")
@@ -63,6 +64,7 @@ class Test(unittest.TestCase):
         self.assertRaises(ValueError, xdice.roll, "1d6l2")
         self.assertRaises(ValueError, xdice.roll, "1d6h2")
         self.assertRaises(ValueError, xdice.roll, "1d6lh")
+        self.assertRaises(SyntaxError, xdice.roll, "1+R3(1d6+1")
 
     def test_dice_object(self):
 
@@ -114,16 +116,20 @@ class Test(unittest.TestCase):
 
     def test_pattern_object(self):
 
-        p = xdice.Pattern("6d1+6")
-
         self.assertEqual(xdice._normalize("1 D 6"), "1d6")
 
+        p = xdice.Pattern("6d1+6")
         p.compile()
         self.assertEqual(p.format_string, "{0}+6")
         self.assertEqual(p.dices, [xdice.Dice(1, 6)])
-
         self.assertEqual(p.roll(), 12)
 
+        p = xdice.Pattern("R2(6d1+6)")
+        p.compile()
+        self.assertEqual(p.format_string, "({0}+6+{1}+6)")
+        self.assertEqual(p.dices, [xdice.Dice(1, 6), xdice.Dice(1, 6)])
+        self.assertEqual(p.roll(), 24)
+
     def test_patternscore_objet(self):
         ps = xdice.PatternScore("{0}+6", [xdice.Score([1, 1, 1, 1, 1, 1])])
 
@@ -131,7 +137,7 @@ class Test(unittest.TestCase):
         self.assertEqual(ps.score(0), 6)
         self.assertEqual(ps.scores(), [6])
         self.assertEqual(ps.format(), "[1, 1, 1, 1, 1, 1]+6")
-        self.assertEqual(ps.format(verbose=True), " (scores:[1, 1, 1, 1, 1, 1]) +6")
+        self.assertEqual(ps.format(verbose=True), "(scores:[1, 1, 1, 1, 1, 1])+6")
 
     def test_compile(self):
         p1 = xdice.compile("6d1+6")

+ 17 - 8
xdice.py

@@ -10,7 +10,6 @@ import re
 
 __VERSION__ = 1.1
 
-# TODO: (?) 'Rx(...)' notation: roll x times the pattern in the parenthesis => eg: R3(1d4+3)
 # TODO: (?) Dice pools, 6-sided variations, 10-sided variations,
 # Open-ended variations (https://en.wikipedia.org/wiki/Dice_notation)
 
@@ -51,11 +50,6 @@ def _assert_int_ge_to(value, threshold=0, msg=""):
     except (TypeError, ValueError):
         raise ValueError(msg)
 
-def _split_list(lst, left, right):
-    """ divides a list in 3 sections: [:left], [left:right], [right:]
-    return a tuple of lists"""
-    return lst[:left], lst[left:right], lst[right:]
-
 def _pop_lowest(lst):
     """ pop the lowest value from the list
     return the popped value"""
@@ -242,7 +236,7 @@ class Score(int):
             return basestr
         else:
             droppedstr = ", dropped:{}".format(self.dropped) if verbose and self.dropped else ""
-            return " {}(scores:{}{}) ".format(self._name, basestr, droppedstr)
+            return "{}(scores:{}{})".format(self._name, basestr, droppedstr)
 
     def __contains__(self, value):
         """ Does score contains the given result """
@@ -265,6 +259,8 @@ class Score(int):
 
 class Pattern():
     """ A dice-notation pattern """
+    RE_REPEAT = re.compile(r"(?:r(\d*)\((.*)\))")
+
     def __init__(self, instr):
         """ Instantiate a Pattern object. """
         if not instr:
@@ -291,7 +287,8 @@ class Pattern():
             self.dices.append(dice)
             return "{{{}}}".format(index)
 
-        self.format_string = Dice.DICE_RE.sub(_submatch, self.instr)
+        expandedstr = Pattern.parse_repeat(self.instr)
+        self.format_string = Dice.DICE_RE.sub(_submatch, expandedstr)
 
     def roll(self):
         """
@@ -303,6 +300,18 @@ class Pattern():
         scores = [dice.roll() for dice in self.dices]
         return PatternScore(self.format_string, scores)
 
+    @classmethod
+    def parse_repeat(cls, pattern):
+        """ parse a pattern to replace the rX(expr) patterns by (expr + ... + expr) [X times] """
+        return cls.RE_REPEAT.sub(cls._sub_repeat, pattern)
+
+    @classmethod
+    def _sub_repeat(cls, match):
+        """ internal """
+        repeat, expr = match.groups()
+        return "({})".format("+".join([expr for _ in range(int(repeat))]))
+
+
 class PatternScore(int):
     """
     PatternScore is a subclass of integer, you can then manipulate it as you would do with an integer.