We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
.dice breaks if you try to roll a die with no sides
Repro:

```
(8:55:43 PM) creftos: .roll 1d0
(8:55:43 PM) Willie: ValueError: empty range for randrange() (1,1, 0) (file "/usr/lib64/python2.7/random.py", line 217, in randrange)
```

Logs:

```
    Traceback (most recent call last):
      File "/home/bdc/workspace/willie/willie/bot.py", line 741, in call
        exit_code = func(willie, trigger)
      File "/home/bdc/workspace/willie/willie/modules/dice.py", line 180, in roll
        dice = list(map(_roll_dice, dice_expressions))
      File "/home/bdc/workspace/willie/willie/modules/dice.py", line 140, in _roll_dice
        dice = DicePouch(dice_num, dice_type, 0)
      File "/home/bdc/workspace/willie/willie/modules/dice.py", line 34, in __init__
        self.roll_dice()
      File "/home/bdc/workspace/willie/willie/modules/dice.py", line 41, in roll_dice
        number = random.randint(1, self.type)
      File "/usr/lib64/python2.7/random.py", line 241, in randint
        return self.randrange(a, b+1)
      File "/usr/lib64/python2.7/random.py", line 217, in randrange
        raise ValueError, "empty range for randrange() (%d,%d, %d)" % (istart, istop, width)
    ValueError: empty range for randrange() (1,1, 0)
```

Proposed Solution:
Needs a catch to make sure user can't do this.

Additional Information:
Willie v. 4.5.1
OS: Fedora release 20 (Heisenbug)
Kernel Version: 3.16.2-201.fc20.x86_64


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `willie/modules/dice.py`
Content:
```
1 # coding=utf8
2 """
3 dice.py - Dice Module
4 Copyright 2010-2013, Dimitri "Tyrope" Molenaars, TyRope.nl
5 Copyright 2013, Ari Koivula, <ari@koivu.la>
6 Licensed under the Eiffel Forum License 2.
7 
8 http://willie.dftba.net/
9 """
10 from __future__ import unicode_literals
11 import random
12 import re
13 
14 import willie.module
15 from willie.tools import eval_equation
16 
17 
18 class DicePouch:
19     def __init__(self, num_of_die, type_of_die, addition):
20         """Initialize dice pouch and roll the dice.
21 
22         Args:
23             num_of_die: number of dice in the pouch.
24             type_of_die: how many faces the dice have.
25             addition: how much is added to the result of the dice.
26         """
27         self.num = num_of_die
28         self.type = type_of_die
29         self.addition = addition
30 
31         self.dice = {}
32         self.dropped = {}
33 
34         self.roll_dice()
35 
36     def roll_dice(self):
37         """Roll all the dice in the pouch."""
38         self.dice = {}
39         self.dropped = {}
40         for __ in range(self.num):
41             number = random.randint(1, self.type)
42             count = self.dice.setdefault(number, 0)
43             self.dice[number] = count + 1
44 
45     def drop_lowest(self, n):
46         """Drop n lowest dice from the result.
47 
48         Args:
49             n: the number of dice to drop.
50         """
51         for i, count in self.dice.items():
52             count = self.dice[i]
53             if n == 0:
54                 break
55             elif n < count:
56                 self.dice[i] = count - n
57                 self.dropped[i] = n
58                 break
59             else:
60                 self.dice[i] = 0
61                 self.dropped[i] = count
62                 n = n - count
63 
64         for i, count in self.dropped.items():
65             if self.dice[i] == 0:
66                 del self.dice[i]
67 
68     def get_simple_string(self):
69         """Return the values of the dice like (2+2+2[+1+1])+1."""
70         dice = self.dice.items()
71         faces = ("+".join([str(face)] * times) for face, times in dice)
72         dice_str = "+".join(faces)
73 
74         dropped_str = ""
75         if self.dropped:
76             dropped = self.dropped.items()
77             dfaces = ("+".join([str(face)] * times) for face, times in dropped)
78             dropped_str = "[+%s]" % ("+".join(dfaces),)
79 
80         plus_str = ""
81         if self.addition:
82             plus_str = "{:+d}".format(self.addition)
83 
84         return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
85 
86     def get_compressed_string(self):
87         """Return the values of the dice like (3x2[+2x1])+1."""
88         dice = self.dice.items()
89         faces = ("%dx%d" % (times, face) for face, times in dice)
90         dice_str = "+".join(faces)
91 
92         dropped_str = ""
93         if self.dropped:
94             dropped = self.dropped.items()
95             dfaces = ("%dx%d" % (times, face) for face, times in dropped)
96             dropped_str = "[+%s]" % ("+".join(dfaces),)
97 
98         plus_str = ""
99         if self.addition:
100             plus_str = "{:+d}".format(self.addition)
101 
102         return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
103 
104     def get_sum(self):
105         """Get the sum of non-dropped dice and the addition."""
106         result = self.addition
107         for face, times in self.dice.items():
108             result += face * times
109         return result
110 
111     def get_number_of_faces(self):
112         """Returns sum of different faces for dropped and not dropped dice
113 
114         This can be used to estimate, whether the result can be shown in
115         compressed form in a reasonable amount of space.
116         """
117         return len(self.dice) + len(self.dropped)
118 
119 
120 def _roll_dice(dice_expression):
121     result = re.search(
122         r"""
123         (?P<dice_num>\d*)
124         d
125         (?P<dice_type>\d+)
126         (v(?P<drop_lowest>\d+))?
127         $""",
128         dice_expression,
129         re.IGNORECASE | re.VERBOSE)
130 
131     dice_num = int(result.group('dice_num') or 1)
132     dice_type = int(result.group('dice_type'))
133 
134     # Upper limit for dice should be at most a million. Creating a dict with
135     # more than a million elements already takes a noticeable amount of time
136     # on a fast computer and ~55kB of memory.
137     if dice_num > 1000:
138         return None
139 
140     dice = DicePouch(dice_num, dice_type, 0)
141 
142     if result.group('drop_lowest'):
143         drop = int(result.group('drop_lowest'))
144         dice.drop_lowest(drop)
145 
146     return dice
147 
148 
149 @willie.module.commands("roll")
150 @willie.module.commands("dice")
151 @willie.module.commands("d")
152 @willie.module.priority("medium")
153 @willie.module.example(".roll 3d1+1", 'You roll 3d1+1: (1+1+1)+1 = 4')
154 @willie.module.example(".roll 3d1v2+1", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')
155 @willie.module.example(".roll 2d4", 'You roll 2d4: \(\d\+\d\) = \d', re=True)
156 @willie.module.example(".roll 100d1", '[^:]*: \(100x1\) = 100', re=True)
157 @willie.module.example(".roll 1001d1", 'I only have 1000 dice. =(')
158 @willie.module.example(".roll 1d1 + 1d1", 'You roll 1d1 + 1d1: (1) + (1) = 2')
159 @willie.module.example(".roll 1d1+1d1", 'You roll 1d1+1d1: (1)+(1) = 2')
160 def roll(bot, trigger):
161     """.dice XdY[vZ][+N], rolls dice and reports the result.
162 
163     X is the number of dice. Y is the number of faces in the dice. Z is the
164     number of lowest dice to be dropped from the result. N is the constant to
165     be applied to the end result.
166     """
167     # This regexp is only allowed to have one captured group, because having
168     # more would alter the output of re.findall.
169     dice_regexp = r"\d*d\d+(?:v\d+)?"
170 
171     # Get a list of all dice expressions, evaluate them and then replace the
172     # expressions in the original string with the results. Replacing is done
173     # using string formatting, so %-characters must be escaped.
174     arg_str = trigger.group(2)
175     dice_expressions = re.findall(dice_regexp, arg_str)
176     arg_str = arg_str.replace("%", "%%")
177     arg_str = re.sub(dice_regexp, "%s", arg_str)
178     dice = list(map(_roll_dice, dice_expressions))
179     if None in dice:
180         bot.reply("I only have 1000 dice. =(")
181         return
182 
183     def _get_eval_str(dice):
184         return "(%d)" % (dice.get_sum(),)
185 
186     def _get_pretty_str(dice):
187         if dice.num <= 10:
188             return dice.get_simple_string()
189         elif dice.get_number_of_faces() <= 10:
190             return dice.get_compressed_string()
191         else:
192             return "(...)"
193 
194     eval_str = arg_str % (tuple(map(_get_eval_str, dice)))
195     pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))
196 
197     # Showing the actual error will hopefully give a better hint of what is
198     # wrong with the syntax than a generic error message.
199     try:
200         result = eval_equation(eval_str)
201     except Exception as e:
202         bot.reply("SyntaxError, eval(%s), %s" % (eval_str, e))
203         return
204 
205     bot.reply("You roll %s: %s = %d" % (
206         trigger.group(2), pretty_str, result))
207 
208 
209 @willie.module.commands("choice")
210 @willie.module.commands("ch")
211 @willie.module.commands("choose")
212 @willie.module.priority("medium")
213 def choose(bot, trigger):
214     """
215     .choice option1|option2|option3 - Makes a difficult choice easy.
216     """
217     if not trigger.group(2):
218         return bot.reply('I\'d choose an option, but you didn\'t give me any.')
219     choices = re.split('[\|\\\\\/]', trigger.group(2))
220     pick = random.choice(choices)
221     return bot.reply('Your options: %s. My choice: %s' % (', '.join(choices), pick))
222 
223 
224 if __name__ == "__main__":
225     from willie.test_tools import run_example_tests
226     run_example_tests(__file__)
227
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/willie/modules/dice.py b/willie/modules/dice.py
--- a/willie/modules/dice.py
+++ b/willie/modules/dice.py
@@ -117,7 +117,7 @@
         return len(self.dice) + len(self.dropped)
 
 
-def _roll_dice(dice_expression):
+def _roll_dice(bot, dice_expression):
     result = re.search(
         r"""
         (?P<dice_num>\d*)
@@ -131,11 +131,17 @@
     dice_num = int(result.group('dice_num') or 1)
     dice_type = int(result.group('dice_type'))
 
+    # Dice can't have zero or a negative number of sides.
+    if dice_type <= 0:
+        bot.reply("I don't have any dice with %d sides. =(" % dice_type)
+        return None # Signal there was a problem
+
     # Upper limit for dice should be at most a million. Creating a dict with
     # more than a million elements already takes a noticeable amount of time
     # on a fast computer and ~55kB of memory.
     if dice_num > 1000:
-        return None
+        bot.reply('I only have 1000 dice. =(')
+        return None # Signal there was a problem
 
     dice = DicePouch(dice_num, dice_type, 0)
 
@@ -171,13 +177,18 @@
     # Get a list of all dice expressions, evaluate them and then replace the
     # expressions in the original string with the results. Replacing is done
     # using string formatting, so %-characters must be escaped.
+    if not trigger.group(2):
+        return bot.reply("No dice to roll.")
     arg_str = trigger.group(2)
     dice_expressions = re.findall(dice_regexp, arg_str)
     arg_str = arg_str.replace("%", "%%")
     arg_str = re.sub(dice_regexp, "%s", arg_str)
-    dice = list(map(_roll_dice, dice_expressions))
+
+    f = lambda dice_expr: _roll_dice  (bot, dice_expr)
+    dice = list(map(f, dice_expressions))
+
     if None in dice:
-        bot.reply("I only have 1000 dice. =(")
+        # Stop computing roll if there was a problem rolling dice.
         return
 
     def _get_eval_str(dice):

{"golden_diff": "diff --git a/willie/modules/dice.py b/willie/modules/dice.py\n--- a/willie/modules/dice.py\n+++ b/willie/modules/dice.py\n@@ -117,7 +117,7 @@\n         return len(self.dice) + len(self.dropped)\n \n \n-def _roll_dice(dice_expression):\n+def _roll_dice(bot, dice_expression):\n     result = re.search(\n         r\"\"\"\n         (?P<dice_num>\\d*)\n@@ -131,11 +131,17 @@\n     dice_num = int(result.group('dice_num') or 1)\n     dice_type = int(result.group('dice_type'))\n \n+    # Dice can't have zero or a negative number of sides.\n+    if dice_type <= 0:\n+        bot.reply(\"I don't have any dice with %d sides. =(\" % dice_type)\n+        return None # Signal there was a problem\n+\n     # Upper limit for dice should be at most a million. Creating a dict with\n     # more than a million elements already takes a noticeable amount of time\n     # on a fast computer and ~55kB of memory.\n     if dice_num > 1000:\n-        return None\n+        bot.reply('I only have 1000 dice. =(')\n+        return None # Signal there was a problem\n \n     dice = DicePouch(dice_num, dice_type, 0)\n \n@@ -171,13 +177,18 @@\n     # Get a list of all dice expressions, evaluate them and then replace the\n     # expressions in the original string with the results. Replacing is done\n     # using string formatting, so %-characters must be escaped.\n+    if not trigger.group(2):\n+        return bot.reply(\"No dice to roll.\")\n     arg_str = trigger.group(2)\n     dice_expressions = re.findall(dice_regexp, arg_str)\n     arg_str = arg_str.replace(\"%\", \"%%\")\n     arg_str = re.sub(dice_regexp, \"%s\", arg_str)\n-    dice = list(map(_roll_dice, dice_expressions))\n+\n+    f = lambda dice_expr: _roll_dice  (bot, dice_expr)\n+    dice = list(map(f, dice_expressions))\n+\n     if None in dice:\n-        bot.reply(\"I only have 1000 dice. =(\")\n+        # Stop computing roll if there was a problem rolling dice.\n         return\n \n     def _get_eval_str(dice):\n", "issue": ".dice breaks if you try to roll a die with no sides\nRepro:\n\n```\n(8:55:43 PM) creftos: .roll 1d0\n(8:55:43 PM) Willie: ValueError: empty range for randrange() (1,1, 0) (file \"/usr/lib64/python2.7/random.py\", line 217, in randrange)\n```\n\nLogs:\n\n```\n    Traceback (most recent call last):\n      File \"/home/bdc/workspace/willie/willie/bot.py\", line 741, in call\n        exit_code = func(willie, trigger)\n      File \"/home/bdc/workspace/willie/willie/modules/dice.py\", line 180, in roll\n        dice = list(map(_roll_dice, dice_expressions))\n      File \"/home/bdc/workspace/willie/willie/modules/dice.py\", line 140, in _roll_dice\n        dice = DicePouch(dice_num, dice_type, 0)\n      File \"/home/bdc/workspace/willie/willie/modules/dice.py\", line 34, in __init__\n        self.roll_dice()\n      File \"/home/bdc/workspace/willie/willie/modules/dice.py\", line 41, in roll_dice\n        number = random.randint(1, self.type)\n      File \"/usr/lib64/python2.7/random.py\", line 241, in randint\n        return self.randrange(a, b+1)\n      File \"/usr/lib64/python2.7/random.py\", line 217, in randrange\n        raise ValueError, \"empty range for randrange() (%d,%d, %d)\" % (istart, istop, width)\n    ValueError: empty range for randrange() (1,1, 0)\n```\n\nProposed Solution:\nNeeds a catch to make sure user can't do this.\n\nAdditional Information:\nWillie v. 4.5.1\nOS: Fedora release 20 (Heisenbug)\nKernel Version: 3.16.2-201.fc20.x86_64\n\n", "before_files": [{"content": "# coding=utf8\n\"\"\"\ndice.py - Dice Module\nCopyright 2010-2013, Dimitri \"Tyrope\" Molenaars, TyRope.nl\nCopyright 2013, Ari Koivula, <ari@koivu.la>\nLicensed under the Eiffel Forum License 2.\n\nhttp://willie.dftba.net/\n\"\"\"\nfrom __future__ import unicode_literals\nimport random\nimport re\n\nimport willie.module\nfrom willie.tools import eval_equation\n\n\nclass DicePouch:\n    def __init__(self, num_of_die, type_of_die, addition):\n        \"\"\"Initialize dice pouch and roll the dice.\n\n        Args:\n            num_of_die: number of dice in the pouch.\n            type_of_die: how many faces the dice have.\n            addition: how much is added to the result of the dice.\n        \"\"\"\n        self.num = num_of_die\n        self.type = type_of_die\n        self.addition = addition\n\n        self.dice = {}\n        self.dropped = {}\n\n        self.roll_dice()\n\n    def roll_dice(self):\n        \"\"\"Roll all the dice in the pouch.\"\"\"\n        self.dice = {}\n        self.dropped = {}\n        for __ in range(self.num):\n            number = random.randint(1, self.type)\n            count = self.dice.setdefault(number, 0)\n            self.dice[number] = count + 1\n\n    def drop_lowest(self, n):\n        \"\"\"Drop n lowest dice from the result.\n\n        Args:\n            n: the number of dice to drop.\n        \"\"\"\n        for i, count in self.dice.items():\n            count = self.dice[i]\n            if n == 0:\n                break\n            elif n < count:\n                self.dice[i] = count - n\n                self.dropped[i] = n\n                break\n            else:\n                self.dice[i] = 0\n                self.dropped[i] = count\n                n = n - count\n\n        for i, count in self.dropped.items():\n            if self.dice[i] == 0:\n                del self.dice[i]\n\n    def get_simple_string(self):\n        \"\"\"Return the values of the dice like (2+2+2[+1+1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"+\".join([str(face)] * times) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"+\".join([str(face)] * times) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_compressed_string(self):\n        \"\"\"Return the values of the dice like (3x2[+2x1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"%dx%d\" % (times, face) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"%dx%d\" % (times, face) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_sum(self):\n        \"\"\"Get the sum of non-dropped dice and the addition.\"\"\"\n        result = self.addition\n        for face, times in self.dice.items():\n            result += face * times\n        return result\n\n    def get_number_of_faces(self):\n        \"\"\"Returns sum of different faces for dropped and not dropped dice\n\n        This can be used to estimate, whether the result can be shown in\n        compressed form in a reasonable amount of space.\n        \"\"\"\n        return len(self.dice) + len(self.dropped)\n\n\ndef _roll_dice(dice_expression):\n    result = re.search(\n        r\"\"\"\n        (?P<dice_num>\\d*)\n        d\n        (?P<dice_type>\\d+)\n        (v(?P<drop_lowest>\\d+))?\n        $\"\"\",\n        dice_expression,\n        re.IGNORECASE | re.VERBOSE)\n\n    dice_num = int(result.group('dice_num') or 1)\n    dice_type = int(result.group('dice_type'))\n\n    # Upper limit for dice should be at most a million. Creating a dict with\n    # more than a million elements already takes a noticeable amount of time\n    # on a fast computer and ~55kB of memory.\n    if dice_num > 1000:\n        return None\n\n    dice = DicePouch(dice_num, dice_type, 0)\n\n    if result.group('drop_lowest'):\n        drop = int(result.group('drop_lowest'))\n        dice.drop_lowest(drop)\n\n    return dice\n\n\n@willie.module.commands(\"roll\")\n@willie.module.commands(\"dice\")\n@willie.module.commands(\"d\")\n@willie.module.priority(\"medium\")\n@willie.module.example(\".roll 3d1+1\", 'You roll 3d1+1: (1+1+1)+1 = 4')\n@willie.module.example(\".roll 3d1v2+1\", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')\n@willie.module.example(\".roll 2d4\", 'You roll 2d4: \\(\\d\\+\\d\\) = \\d', re=True)\n@willie.module.example(\".roll 100d1\", '[^:]*: \\(100x1\\) = 100', re=True)\n@willie.module.example(\".roll 1001d1\", 'I only have 1000 dice. =(')\n@willie.module.example(\".roll 1d1 + 1d1\", 'You roll 1d1 + 1d1: (1) + (1) = 2')\n@willie.module.example(\".roll 1d1+1d1\", 'You roll 1d1+1d1: (1)+(1) = 2')\ndef roll(bot, trigger):\n    \"\"\".dice XdY[vZ][+N], rolls dice and reports the result.\n\n    X is the number of dice. Y is the number of faces in the dice. Z is the\n    number of lowest dice to be dropped from the result. N is the constant to\n    be applied to the end result.\n    \"\"\"\n    # This regexp is only allowed to have one captured group, because having\n    # more would alter the output of re.findall.\n    dice_regexp = r\"\\d*d\\d+(?:v\\d+)?\"\n\n    # Get a list of all dice expressions, evaluate them and then replace the\n    # expressions in the original string with the results. Replacing is done\n    # using string formatting, so %-characters must be escaped.\n    arg_str = trigger.group(2)\n    dice_expressions = re.findall(dice_regexp, arg_str)\n    arg_str = arg_str.replace(\"%\", \"%%\")\n    arg_str = re.sub(dice_regexp, \"%s\", arg_str)\n    dice = list(map(_roll_dice, dice_expressions))\n    if None in dice:\n        bot.reply(\"I only have 1000 dice. =(\")\n        return\n\n    def _get_eval_str(dice):\n        return \"(%d)\" % (dice.get_sum(),)\n\n    def _get_pretty_str(dice):\n        if dice.num <= 10:\n            return dice.get_simple_string()\n        elif dice.get_number_of_faces() <= 10:\n            return dice.get_compressed_string()\n        else:\n            return \"(...)\"\n\n    eval_str = arg_str % (tuple(map(_get_eval_str, dice)))\n    pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))\n\n    # Showing the actual error will hopefully give a better hint of what is\n    # wrong with the syntax than a generic error message.\n    try:\n        result = eval_equation(eval_str)\n    except Exception as e:\n        bot.reply(\"SyntaxError, eval(%s), %s\" % (eval_str, e))\n        return\n\n    bot.reply(\"You roll %s: %s = %d\" % (\n        trigger.group(2), pretty_str, result))\n\n\n@willie.module.commands(\"choice\")\n@willie.module.commands(\"ch\")\n@willie.module.commands(\"choose\")\n@willie.module.priority(\"medium\")\ndef choose(bot, trigger):\n    \"\"\"\n    .choice option1|option2|option3 - Makes a difficult choice easy.\n    \"\"\"\n    if not trigger.group(2):\n        return bot.reply('I\\'d choose an option, but you didn\\'t give me any.')\n    choices = re.split('[\\|\\\\\\\\\\/]', trigger.group(2))\n    pick = random.choice(choices)\n    return bot.reply('Your options: %s. My choice: %s' % (', '.join(choices), pick))\n\n\nif __name__ == \"__main__\":\n    from willie.test_tools import run_example_tests\n    run_example_tests(__file__)\n", "path": "willie/modules/dice.py"}], "after_files": [{"content": "# coding=utf8\n\"\"\"\ndice.py - Dice Module\nCopyright 2010-2013, Dimitri \"Tyrope\" Molenaars, TyRope.nl\nCopyright 2013, Ari Koivula, <ari@koivu.la>\nLicensed under the Eiffel Forum License 2.\n\nhttp://willie.dftba.net/\n\"\"\"\nfrom __future__ import unicode_literals\nimport random\nimport re\n\nimport willie.module\nfrom willie.tools import eval_equation\n\n\nclass DicePouch:\n    def __init__(self, num_of_die, type_of_die, addition):\n        \"\"\"Initialize dice pouch and roll the dice.\n\n        Args:\n            num_of_die: number of dice in the pouch.\n            type_of_die: how many faces the dice have.\n            addition: how much is added to the result of the dice.\n        \"\"\"\n        self.num = num_of_die\n        self.type = type_of_die\n        self.addition = addition\n\n        self.dice = {}\n        self.dropped = {}\n\n        self.roll_dice()\n\n    def roll_dice(self):\n        \"\"\"Roll all the dice in the pouch.\"\"\"\n        self.dice = {}\n        self.dropped = {}\n        for __ in range(self.num):\n            number = random.randint(1, self.type)\n            count = self.dice.setdefault(number, 0)\n            self.dice[number] = count + 1\n\n    def drop_lowest(self, n):\n        \"\"\"Drop n lowest dice from the result.\n\n        Args:\n            n: the number of dice to drop.\n        \"\"\"\n        for i, count in self.dice.items():\n            count = self.dice[i]\n            if n == 0:\n                break\n            elif n < count:\n                self.dice[i] = count - n\n                self.dropped[i] = n\n                break\n            else:\n                self.dice[i] = 0\n                self.dropped[i] = count\n                n = n - count\n\n        for i, count in self.dropped.items():\n            if self.dice[i] == 0:\n                del self.dice[i]\n\n    def get_simple_string(self):\n        \"\"\"Return the values of the dice like (2+2+2[+1+1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"+\".join([str(face)] * times) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"+\".join([str(face)] * times) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_compressed_string(self):\n        \"\"\"Return the values of the dice like (3x2[+2x1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"%dx%d\" % (times, face) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"%dx%d\" % (times, face) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_sum(self):\n        \"\"\"Get the sum of non-dropped dice and the addition.\"\"\"\n        result = self.addition\n        for face, times in self.dice.items():\n            result += face * times\n        return result\n\n    def get_number_of_faces(self):\n        \"\"\"Returns sum of different faces for dropped and not dropped dice\n\n        This can be used to estimate, whether the result can be shown in\n        compressed form in a reasonable amount of space.\n        \"\"\"\n        return len(self.dice) + len(self.dropped)\n\n\ndef _roll_dice(bot, dice_expression):\n    result = re.search(\n        r\"\"\"\n        (?P<dice_num>\\d*)\n        d\n        (?P<dice_type>\\d+)\n        (v(?P<drop_lowest>\\d+))?\n        $\"\"\",\n        dice_expression,\n        re.IGNORECASE | re.VERBOSE)\n\n    dice_num = int(result.group('dice_num') or 1)\n    dice_type = int(result.group('dice_type'))\n\n    # Dice can't have zero or a negative number of sides.\n    if dice_type <= 0:\n        bot.reply(\"I don't have any dice with %d sides. =(\" % dice_type)\n        return None # Signal there was a problem\n\n    # Upper limit for dice should be at most a million. Creating a dict with\n    # more than a million elements already takes a noticeable amount of time\n    # on a fast computer and ~55kB of memory.\n    if dice_num > 1000:\n        bot.reply('I only have 1000 dice. =(')\n        return None # Signal there was a problem\n\n    dice = DicePouch(dice_num, dice_type, 0)\n\n    if result.group('drop_lowest'):\n        drop = int(result.group('drop_lowest'))\n        dice.drop_lowest(drop)\n\n    return dice\n\n\n@willie.module.commands(\"roll\")\n@willie.module.commands(\"dice\")\n@willie.module.commands(\"d\")\n@willie.module.priority(\"medium\")\n@willie.module.example(\".roll 3d1+1\", 'You roll 3d1+1: (1+1+1)+1 = 4')\n@willie.module.example(\".roll 3d1v2+1\", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')\n@willie.module.example(\".roll 2d4\", 'You roll 2d4: \\(\\d\\+\\d\\) = \\d', re=True)\n@willie.module.example(\".roll 100d1\", '[^:]*: \\(100x1\\) = 100', re=True)\n@willie.module.example(\".roll 1001d1\", 'I only have 1000 dice. =(')\n@willie.module.example(\".roll 1d1 + 1d1\", 'You roll 1d1 + 1d1: (1) + (1) = 2')\n@willie.module.example(\".roll 1d1+1d1\", 'You roll 1d1+1d1: (1)+(1) = 2')\ndef roll(bot, trigger):\n    \"\"\".dice XdY[vZ][+N], rolls dice and reports the result.\n\n    X is the number of dice. Y is the number of faces in the dice. Z is the\n    number of lowest dice to be dropped from the result. N is the constant to\n    be applied to the end result.\n    \"\"\"\n    # This regexp is only allowed to have one captured group, because having\n    # more would alter the output of re.findall.\n    dice_regexp = r\"\\d*d\\d+(?:v\\d+)?\"\n\n    # Get a list of all dice expressions, evaluate them and then replace the\n    # expressions in the original string with the results. Replacing is done\n    # using string formatting, so %-characters must be escaped.\n    if not trigger.group(2):\n        return bot.reply(\"No dice to roll.\")\n    arg_str = trigger.group(2)\n    dice_expressions = re.findall(dice_regexp, arg_str)\n    arg_str = arg_str.replace(\"%\", \"%%\")\n    arg_str = re.sub(dice_regexp, \"%s\", arg_str)\n\n    f = lambda dice_expr: _roll_dice  (bot, dice_expr)\n    dice = list(map(f, dice_expressions))\n\n    if None in dice:\n        # Stop computing roll if there was a problem rolling dice.\n        return\n\n    def _get_eval_str(dice):\n        return \"(%d)\" % (dice.get_sum(),)\n\n    def _get_pretty_str(dice):\n        if dice.num <= 10:\n            return dice.get_simple_string()\n        elif dice.get_number_of_faces() <= 10:\n            return dice.get_compressed_string()\n        else:\n            return \"(...)\"\n\n    eval_str = arg_str % (tuple(map(_get_eval_str, dice)))\n    pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))\n\n    # Showing the actual error will hopefully give a better hint of what is\n    # wrong with the syntax than a generic error message.\n    try:\n        result = eval_equation(eval_str)\n    except Exception as e:\n        bot.reply(\"SyntaxError, eval(%s), %s\" % (eval_str, e))\n        return\n\n    bot.reply(\"You roll %s: %s = %d\" % (\n        trigger.group(2), pretty_str, result))\n\n\n@willie.module.commands(\"choice\")\n@willie.module.commands(\"ch\")\n@willie.module.commands(\"choose\")\n@willie.module.priority(\"medium\")\ndef choose(bot, trigger):\n    \"\"\"\n    .choice option1|option2|option3 - Makes a difficult choice easy.\n    \"\"\"\n    if not trigger.group(2):\n        return bot.reply('I\\'d choose an option, but you didn\\'t give me any.')\n    choices = re.split('[\\|\\\\\\\\\\/]', trigger.group(2))\n    pick = random.choice(choices)\n    return bot.reply('Your options: %s. My choice: %s' % (', '.join(choices), pick))\n\n\nif __name__ == \"__main__\":\n    from willie.test_tools import run_example_tests\n    run_example_tests(__file__)\n", "path": "willie/modules/dice.py"}]}