Skip to content

Commit

Permalink
Add functionality to specify a source range
Browse files Browse the repository at this point in the history
Now a range can be specified to pick random numbers from:
`!roll 1d{0,9}`, `!roll 2d{3,7}`, `!roll 3d{-5,-1}`.
  • Loading branch information
eandersons committed Mar 10, 2023
1 parent 30952b9 commit 99871c8
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 21 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# dice

A [maubot](https://github.com/maubot/maubot) that rolls dice. Has built-in calculator.

## Usage
The base command is `!roll`. To roll dice, pass `XdY` as an argument, where `X`
is the number of dice (optional) and `Y` is the number of sides in each dice.

The base command is `!roll`.

To roll a dice, pass `XdY` as an argument, where `X` is the number of dice
(optional) and `Y` is the number of sides in each dice. `Y` can be passed as a
specific range as well (for example: `{0,9}`, `{-5,-1}`).

Most Python math and bitwise operators and basic `math` module functions are
also supported, which means you can roll different kinds of dice and combine
the results however you like.
61 changes: 43 additions & 18 deletions dice.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Match, Union, Any, Type
from typing import Match, Optional, Tuple, Union, Any, Type
import operator
import random
import math
Expand All @@ -24,7 +24,10 @@
from maubot import Plugin, MessageEvent
from maubot.handlers import command

pattern_regex = re.compile("([0-9]{0,9})[dD]([0-9]{1,9})")
pattern_regex = re.compile(
r"(\d{0,9})d((?:\d|(\{(-?\d), *(-?\d)\})){1,9})",
re.IGNORECASE
)

_OP_MAP = {
ast.Add: operator.add,
Expand Down Expand Up @@ -206,37 +209,58 @@ async def roll(self, evt: MessageEvent, pattern: str) -> None:

individual_rolls = [] if self.show_rolls else None

def randomize(number: int, size: int) -> int:
if size < 0 or number < 0:
def randomize(number: int, size: Union[int, Tuple[int, int]]) -> int:
size_is_int: bool = isinstance(size, int)
choices_range: Tuple[int, int] = (
size if isinstance(size, tuple) else (1, size)
)
if not choices_range[0] <= choices_range[1]:
raise ValueError(
"The range's first element must not be greater than the second"
)
largest: int = choices_range[1]
if size_is_int and largest < 0 or number < 0:
raise ValueError("randomize() only accepts non-negative values")
if size == 0 or number == 0:
if size_is_int and largest == 0 or number == 0:
return 0
elif size == 1:
elif size_is_int and largest == 1:
return number
_result = 0
if number < self.gauss_limit:
individual = [] if self.show_rolls and number < self.show_rolls_limit else None
for i in range(number):
roll = random.randint(1, size)
for _ in range(number):
roll = random.randint(*choices_range)
if individual is not None:
individual.append(roll)
_result += roll
if individual:
individual_rolls.append((number, size, individual))
if individual and individual_rolls is not None:
individual_rolls.append(
(
number,
largest
if size_is_int
else "{" + ", ".join(str(i) for i in choices_range) + "}",
individual
)
)
else:
mean = number * (size + 1) / 2
variance = number * (size ** 2 - 1) / 12
while _result < number or _result > number * size:
mean = number * (largest + 1) / 2
variance = number * (largest ** 2 - 1) / 12
while _result < number or _result > number * largest:
_result = int(random.gauss(mean, math.sqrt(variance)))
return _result

def replacer(match: Match) -> str:
number = int(match.group(1) or "1")
size = int(match.group(2))
number: int = int(match.group(1) or "1")
range: Optional[str] = match.group(3)
size: Union[int, Tuple[int, int]] = (
int(match.group(2))
if not range
else (int(match.group(4)), int(match.group(5)))
)
return str(randomize(number, size))

pattern = pattern_regex.sub(replacer, pattern)
try:
pattern = pattern_regex.sub(replacer, pattern)
result = Calc.evaluate(pattern)
if self.round_decimals >= 0:
result = round(result, self.round_decimals)
Expand All @@ -263,7 +287,8 @@ async def help(self, evt: MessageEvent) -> None:
"The base command is `!roll`. \\\n"
"To roll a dice, pass `XdY` as an argument, where `X` is the "
"number of dices (optional) and `Y` is the number of sides on "
"each dice. \\\n"
"each dice. `Y` can be passed as a specific range as well "
"(for example: `{0,9}`, `{-5,-1}`). \\\n"
"Most Python math and bitwise operators and basic `math` module "
"functions are also supported, which means you can roll different "
"kinds of dice and combine the results however you like."
Expand Down
2 changes: 1 addition & 1 deletion maubot.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
maubot: 0.1.0
id: xyz.maubot.dice
version: 1.1.0
version: 1.2.0
license: AGPL-3.0-or-later
modules:
- dice
Expand Down

0 comments on commit 99871c8

Please sign in to comment.