🎲 Better !roll command
This commit is contained in:
@ -88,7 +88,7 @@ async def parseCommands(message,content):
|
|||||||
await message.channel.send(roll_dice(message.author.display_name, content.replace("roll","")))
|
await message.channel.send(roll_dice(message.author.display_name, content.replace("roll","")))
|
||||||
except:
|
except:
|
||||||
logThis("Something fucked up (error code 400)",str(message.channel))
|
logThis("Something fucked up (error code 400)",str(message.channel))
|
||||||
await message.channel.send("Something fucked up (error code 400)")
|
await message.channel.send("Not a valid command (error code 400)")
|
||||||
|
|
||||||
# Looks up a spell with the spellFunc function from funcs/lookup/lookuppy
|
# Looks up a spell with the spellFunc function from funcs/lookup/lookuppy
|
||||||
elif content.startswith("spell "):
|
elif content.startswith("spell "):
|
||||||
|
@ -7,4 +7,4 @@ from .trivia import triviaCountPoints, triviaStart, triviaAnswer
|
|||||||
from .blackjack import blackjackShuffle, blackjackStart, blackjackPlayerDrawHand, blackjackContinue, blackjackFinish, blackjackHit, blackjackStand, blackjackDouble, blackjackSplit
|
from .blackjack import blackjackShuffle, blackjackStart, blackjackPlayerDrawHand, blackjackContinue, blackjackFinish, blackjackHit, blackjackStand, blackjackDouble, blackjackSplit
|
||||||
from .fourInARow import parseFourInARow, fourInARowAI
|
from .fourInARow import parseFourInARow, fourInARowAI
|
||||||
from .hex import parseHex, hexAI
|
from .hex import parseHex, hexAI
|
||||||
from .monopoly import parseMonopoly, monopo;lyContinue
|
from .monopoly import parseMonopoly, monopolyContinue
|
@ -1,4 +1,4 @@
|
|||||||
"""I stole this."""
|
"""Rolling dice."""
|
||||||
|
|
||||||
__all__ = ["roll_dice"]
|
__all__ = ["roll_dice"]
|
||||||
|
|
||||||
|
@ -1,560 +1,20 @@
|
|||||||
import random
|
import d20
|
||||||
import re
|
|
||||||
import traceback
|
|
||||||
from heapq import nlargest, nsmallest
|
|
||||||
from math import floor
|
|
||||||
from re import IGNORECASE
|
|
||||||
|
|
||||||
import numexpr
|
class MyStringifier(d20.MarkdownStringifier):
|
||||||
|
|
||||||
from . import errors
|
def _str_expression(self, node):
|
||||||
#from funcs import logThis
|
|
||||||
|
|
||||||
VALID_OPERATORS = 'k|rr|ro|mi|ma|ra|e|p'
|
if node.comment == None:
|
||||||
VALID_OPERATORS_ARRAY = VALID_OPERATORS.split('|')
|
resultText = "Result"
|
||||||
VALID_OPERATORS_2 = re.compile('|'.join(["({})".format(i) for i in VALID_OPERATORS_ARRAY]))
|
|
||||||
DICE_PATTERN = re.compile(
|
|
||||||
r'^\s*(?:(?:(\d*d\d+)(?:(?:' + VALID_OPERATORS + r')(?:[lh<>]?\d+))*|(\d+)|([-+*/().=])?)\s*(\[.*\])?)(.*?)\s*$',
|
|
||||||
IGNORECASE)
|
|
||||||
|
|
||||||
|
|
||||||
def roll_dice(author : str, rollStr : str = "1d20"):
|
|
||||||
if rollStr == '0/0': # easter eggs
|
|
||||||
return("What do you expect me to do, destroy the universe?")
|
|
||||||
|
|
||||||
adv = 0
|
|
||||||
if re.search('(^|\s+)(adv|dis)(\s+|$)', rollStr) is not None:
|
|
||||||
adv = 1 if re.search('(^|\s+)adv(\s+|$)', rollStr) is not None else -1
|
|
||||||
rollStr = re.sub('(adv|dis)(\s+|$)', '', rollStr)
|
|
||||||
res = roll(rollStr, adv=adv)
|
|
||||||
out = res.result
|
|
||||||
outStr = author + ' :game_die:\n' + out
|
|
||||||
if len(outStr) > 1999:
|
|
||||||
outputs = author + ' :game_die:\n[Output truncated due to length]\n**Result:** ' + str(res.plain)
|
|
||||||
else:
|
|
||||||
outputs = outStr
|
|
||||||
return(outputs)
|
|
||||||
|
|
||||||
def list_get(index, default, l):
|
|
||||||
try:
|
|
||||||
a = l[index]
|
|
||||||
except IndexError:
|
|
||||||
a = default
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
def roll(rollStr, adv: int = 0, rollFor='', inline=False, double=False, show_blurbs=True, **kwargs):
|
|
||||||
roller = Roll()
|
|
||||||
result = roller.roll(rollStr, adv, rollFor, inline, double, show_blurbs, **kwargs)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def get_roll_comment(rollStr):
|
|
||||||
"""Returns: A two-tuple (dice without comment, comment)"""
|
|
||||||
try:
|
|
||||||
comment = ''
|
|
||||||
no_comment = ''
|
|
||||||
dice_set = re.split('([-+*/().=])', rollStr)
|
|
||||||
dice_set = [d for d in dice_set if not d in (None, '')]
|
|
||||||
#logThis("Found dice set: " + str(dice_set))
|
|
||||||
for index, dice in enumerate(dice_set):
|
|
||||||
match = DICE_PATTERN.match(dice)
|
|
||||||
#logThis("Found dice group: " + str(match.groups()))
|
|
||||||
no_comment += dice.replace(match.group(5), '')
|
|
||||||
if match.group(5):
|
|
||||||
comment = match.group(5) + ''.join(dice_set[index + 1:])
|
|
||||||
break
|
|
||||||
|
|
||||||
return no_comment, comment
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return rollStr, ''
|
|
||||||
|
|
||||||
|
|
||||||
class Roll(object):
|
|
||||||
def __init__(self, parts=None):
|
|
||||||
if parts ==None:
|
|
||||||
parts = []
|
|
||||||
self.parts = parts
|
|
||||||
|
|
||||||
def get_crit(self):
|
|
||||||
"""Returns: 0 for no crit, 1 for 20, 2 for 1."""
|
|
||||||
try:
|
|
||||||
crit = next(p.get_crit() for p in self.parts if isinstance(p, SingleDiceGroup))
|
|
||||||
except StopIteration:
|
|
||||||
crit = 0
|
|
||||||
return crit
|
|
||||||
|
|
||||||
def get_total(self):
|
|
||||||
"""Returns: int"""
|
|
||||||
return numexpr.evaluate(''.join(p.get_eval() for p in self.parts if not isinstance(p, Comment)))
|
|
||||||
|
|
||||||
# # Dice Roller
|
|
||||||
def roll(self, rollStr, adv: int = 0, rollFor='', inline=False, double=False, show_blurbs=True, **kwargs):
|
|
||||||
try:
|
|
||||||
if '**' in rollStr:
|
|
||||||
raise errors.InvalidArgument("Exponents are currently disabled.")
|
|
||||||
self.parts = []
|
|
||||||
# split roll string into XdYoptsSel [comment] or Op
|
|
||||||
# set remainder to comment
|
|
||||||
# parse each, returning a SingleDiceResult
|
|
||||||
dice_set = re.split('([-+*/().=])', rollStr)
|
|
||||||
dice_set = [d for d in dice_set if not d in (None, '')]
|
|
||||||
#logThis("Found dice set: " + str(dice_set))
|
|
||||||
for index, dice in enumerate(dice_set):
|
|
||||||
match = DICE_PATTERN.match(dice)
|
|
||||||
#logThis("Found dice group: " + str(match.groups()))
|
|
||||||
# check if it's dice
|
|
||||||
if match.group(1):
|
|
||||||
roll = self.roll_one(dice.replace(match.group(5), ''), adv)
|
|
||||||
self.parts.append(roll)
|
|
||||||
# or a constant
|
|
||||||
elif match.group(2):
|
|
||||||
self.parts.append(Constant(value=int(match.group(2)), annotation=match.group(4)))
|
|
||||||
# or an operator
|
|
||||||
elif not match.group(5):
|
|
||||||
self.parts.append(Operator(op=match.group(3), annotation=match.group(4)))
|
|
||||||
|
|
||||||
if match.group(5):
|
|
||||||
self.parts.append(Comment(match.group(5) + ''.join(dice_set[index + 1:])))
|
|
||||||
break
|
|
||||||
|
|
||||||
# calculate total
|
|
||||||
crit = self.get_crit()
|
|
||||||
try:
|
|
||||||
total = self.get_total()
|
|
||||||
except SyntaxError:
|
|
||||||
raise errors.InvalidArgument("No dice found to roll.")
|
|
||||||
rolled = ' '.join(str(res) for res in self.parts if not isinstance(res, Comment))
|
|
||||||
if rollFor =='':
|
|
||||||
rollFor = ''.join(str(c) for c in self.parts if isinstance(c, Comment))
|
|
||||||
# return final solution
|
|
||||||
if not inline:
|
|
||||||
# Builds end result while showing rolls
|
|
||||||
reply = ' '.join(
|
|
||||||
str(res) for res in self.parts if not isinstance(res, Comment)) + '\n**Total:** ' + str(
|
|
||||||
floor(total))
|
|
||||||
skeletonReply = reply
|
|
||||||
rollFor = rollFor if rollFor != '' else 'Result'
|
|
||||||
reply = '**{}:** '.format(rollFor) + reply
|
|
||||||
if show_blurbs:
|
|
||||||
if adv == 1:
|
|
||||||
reply += '\n**Rolled with Advantage**'
|
|
||||||
elif adv == -1:
|
|
||||||
reply += '\n**Rolled with Disadvantage**'
|
|
||||||
if crit == 1:
|
|
||||||
critStr = "\n_**Critical Hit!**_ "
|
|
||||||
reply += critStr
|
|
||||||
elif crit == 2:
|
|
||||||
critStr = "\n_**Critical Fail!**_ "
|
|
||||||
reply += critStr
|
|
||||||
else:
|
|
||||||
# Builds end result while showing rolls
|
|
||||||
reply = ' '.join(str(res) for res in self.parts if not isinstance(res, Comment)) + ' = `' + str(
|
|
||||||
floor(total)) + '`'
|
|
||||||
skeletonReply = reply
|
|
||||||
rollFor = rollFor if rollFor != '' else 'Result'
|
|
||||||
reply = '**{}:** '.format(rollFor) + reply
|
|
||||||
if show_blurbs:
|
|
||||||
if adv == 1:
|
|
||||||
reply += '\n**Rolled with Advantage**'
|
|
||||||
elif adv == -1:
|
|
||||||
reply += '\n**Rolled with Disadvantage**'
|
|
||||||
if crit == 1:
|
|
||||||
critStr = "\n_**Critical Hit!**_ "
|
|
||||||
reply += critStr
|
|
||||||
elif crit == 2:
|
|
||||||
critStr = "\n_**Critical Fail!**_ "
|
|
||||||
reply += critStr
|
|
||||||
reply = re.sub(' +', ' ', reply)
|
|
||||||
skeletonReply = re.sub(' +', ' ', str(skeletonReply))
|
|
||||||
return DiceResult(result=int(floor(total)), verbose_result=reply, crit=crit, rolled=rolled,
|
|
||||||
skeleton=skeletonReply, raw_dice=self)
|
|
||||||
except Exception as ex:
|
|
||||||
if not isinstance(ex, (SyntaxError, KeyError, errors.AvraeException)):
|
|
||||||
#logThis('Error in roll() caused by roll {}:'.format(rollStr))
|
|
||||||
traceback.print_exc()
|
|
||||||
return DiceResult(verbose_result="Invalid input: {}".format(ex))
|
|
||||||
|
|
||||||
def roll_one(self, dice, adv: int = 0):
|
|
||||||
result = SingleDiceGroup()
|
|
||||||
result.rolled = []
|
|
||||||
# splits dice and comments
|
|
||||||
split = re.match(r'^([^\[\]]*?)\s*(\[.*\])?\s*$', dice)
|
|
||||||
dice = split.group(1).strip()
|
|
||||||
annotation = split.group(2)
|
|
||||||
result.annotation = annotation if annotation != None else ''
|
|
||||||
# Recognizes dice
|
|
||||||
obj = re.findall('\d+', dice)
|
|
||||||
obj = [int(x) for x in obj]
|
|
||||||
numArgs = len(obj)
|
|
||||||
|
|
||||||
ops = []
|
|
||||||
if numArgs == 1:
|
|
||||||
if not dice.startswith('d'):
|
|
||||||
raise errors.InvalidArgument('Please pass in the value of the dice.')
|
|
||||||
numDice = 1
|
|
||||||
diceVal = obj[0]
|
|
||||||
if adv != 0 and diceVal == 20:
|
|
||||||
numDice = 2
|
|
||||||
ops = ['k', 'h1'] if adv ==1 else ['k', 'l1']
|
|
||||||
elif numArgs == 2:
|
|
||||||
numDice = obj[0]
|
|
||||||
diceVal = obj[-1]
|
|
||||||
if adv != 0 and diceVal == 20:
|
|
||||||
ops = ['k', 'h' + str(numDice)] if adv ==1 else ['k', 'l' + str(numDice)]
|
|
||||||
numDice = numDice * 2
|
|
||||||
else: # split into xdy and operators
|
|
||||||
numDice = obj[0]
|
|
||||||
diceVal = obj[1]
|
|
||||||
dice = re.split('(\d+d\d+)', dice)[-1]
|
|
||||||
ops = VALID_OPERATORS_2.split(dice)
|
|
||||||
ops = [a for a in ops if a != None]
|
|
||||||
|
|
||||||
# dice repair/modification
|
|
||||||
if numDice > 300 or diceVal < 1:
|
|
||||||
raise errors.InvalidArgument('Too many dice rolled.')
|
|
||||||
|
|
||||||
result.max_value = diceVal
|
|
||||||
result.num_dice = numDice
|
|
||||||
result.operators = ops
|
|
||||||
|
|
||||||
for _ in range(numDice):
|
|
||||||
try:
|
|
||||||
tempdice = SingleDice()
|
|
||||||
tempdice.value = random.randint(1, diceVal)
|
|
||||||
tempdice.rolls = [tempdice.value]
|
|
||||||
tempdice.max_value = diceVal
|
|
||||||
tempdice.kept = True
|
|
||||||
result.rolled.append(tempdice)
|
|
||||||
except:
|
|
||||||
result.rolled.append(SingleDice())
|
|
||||||
|
|
||||||
if ops != None:
|
|
||||||
|
|
||||||
rerollList = []
|
|
||||||
reroll_once = []
|
|
||||||
keep = None
|
|
||||||
to_explode = []
|
|
||||||
to_reroll_add = []
|
|
||||||
|
|
||||||
valid_operators = VALID_OPERATORS_ARRAY
|
|
||||||
last_operator = None
|
|
||||||
for index, op in enumerate(ops):
|
|
||||||
if last_operator != None and op in valid_operators and not op == last_operator:
|
|
||||||
result.reroll(reroll_once, 1)
|
|
||||||
reroll_once = []
|
|
||||||
result.reroll(rerollList, greedy=True)
|
|
||||||
rerollList = []
|
|
||||||
result.keep(keep)
|
|
||||||
keep = None
|
|
||||||
result.reroll(to_reroll_add, 1, keep_rerolled=True, unique=True)
|
|
||||||
to_reroll_add = []
|
|
||||||
result.reroll(to_explode, greedy=True, keep_rerolled=True)
|
|
||||||
to_explode = []
|
|
||||||
if op == 'rr':
|
|
||||||
rerollList += parse_selectors([list_get(index + 1, 0, ops)], result, greedy=True)
|
|
||||||
if op == 'k':
|
|
||||||
keep = [] if keep ==None else keep
|
|
||||||
keep += parse_selectors([list_get(index + 1, 0, ops)], result)
|
|
||||||
if op == 'p':
|
|
||||||
keep = [] if keep ==None else keep
|
|
||||||
keep += parse_selectors([list_get(index + 1, 0, ops)], result, inverse=True)
|
|
||||||
if op == 'ro':
|
|
||||||
reroll_once += parse_selectors([list_get(index + 1, 0, ops)], result)
|
|
||||||
if op == 'mi':
|
|
||||||
_min = list_get(index + 1, 0, ops)
|
|
||||||
for r in result.rolled:
|
|
||||||
if r.value < int(_min):
|
|
||||||
r.update(int(_min))
|
|
||||||
if op == 'ma':
|
|
||||||
_max = list_get(index + 1, 0, ops)
|
|
||||||
for r in result.rolled:
|
|
||||||
if r.value > int(_max):
|
|
||||||
r.update(int(_max))
|
|
||||||
if op == 'ra':
|
|
||||||
to_reroll_add += parse_selectors([list_get(index + 1, 0, ops)], result)
|
|
||||||
if op == 'e':
|
|
||||||
to_explode += parse_selectors([list_get(index + 1, 0, ops)], result, greedy=True)
|
|
||||||
if op in valid_operators:
|
|
||||||
last_operator = op
|
|
||||||
result.reroll(reroll_once, 1)
|
|
||||||
result.reroll(rerollList, greedy=True)
|
|
||||||
result.keep(keep)
|
|
||||||
result.reroll(to_reroll_add, 1, keep_rerolled=True, unique=True)
|
|
||||||
result.reroll(to_explode, greedy=True, keep_rerolled=True)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Part:
|
|
||||||
"""Class to hold one part of the roll string."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SingleDiceGroup(Part):
|
|
||||||
def __init__(self, num_dice: int = 0, max_value: int = 0, rolled=None, annotation: str = "", result: str = "",
|
|
||||||
operators=None):
|
|
||||||
if operators ==None:
|
|
||||||
operators = []
|
|
||||||
if rolled ==None:
|
|
||||||
rolled = []
|
|
||||||
self.num_dice = num_dice
|
|
||||||
self.max_value = max_value
|
|
||||||
self.rolled = rolled # list of SingleDice
|
|
||||||
self.annotation = annotation
|
|
||||||
self.result = result
|
|
||||||
self.operators = operators
|
|
||||||
|
|
||||||
def keep(self, rolls_to_keep):
|
|
||||||
if rolls_to_keep ==None: return
|
|
||||||
for _roll in self.rolled:
|
|
||||||
if not _roll.value in rolls_to_keep:
|
|
||||||
_roll.kept = False
|
|
||||||
elif _roll.kept:
|
|
||||||
rolls_to_keep.remove(_roll.value)
|
|
||||||
|
|
||||||
def reroll(self, rerollList, max_iterations=1000, greedy=False, keep_rerolled=False, unique=False):
|
|
||||||
if not rerollList: return # don't reroll nothing - minor optimization
|
|
||||||
if unique:
|
|
||||||
rerollList = list(set(rerollList)) # remove duplicates
|
|
||||||
if len(rerollList) > 100:
|
|
||||||
raise OverflowError("Too many dice to reroll (max 100)")
|
|
||||||
last_index = 0
|
|
||||||
count = 0
|
|
||||||
should_continue = True
|
|
||||||
while should_continue: # let's only iterate 250 times for sanity
|
|
||||||
should_continue = False
|
|
||||||
if any(d.value in set(rerollList) for d in self.rolled[last_index:] if d.kept and not d.exploded):
|
|
||||||
should_continue = True
|
|
||||||
to_extend = []
|
|
||||||
for r in self.rolled[last_index:]: # no need to recheck everything
|
|
||||||
count += 1
|
|
||||||
if count > max_iterations:
|
|
||||||
should_continue = False
|
|
||||||
if r.value in rerollList and r.kept and not r.exploded:
|
|
||||||
try:
|
|
||||||
tempdice = SingleDice()
|
|
||||||
tempdice.value = random.randint(1, self.max_value)
|
|
||||||
tempdice.rolls = [tempdice.value]
|
|
||||||
tempdice.max_value = self.max_value
|
|
||||||
tempdice.kept = True
|
|
||||||
to_extend.append(tempdice)
|
|
||||||
if not keep_rerolled:
|
|
||||||
r.drop()
|
|
||||||
else:
|
|
||||||
r.explode()
|
|
||||||
except:
|
|
||||||
to_extend.append(SingleDice())
|
|
||||||
if not keep_rerolled:
|
|
||||||
r.drop()
|
|
||||||
else:
|
|
||||||
r.explode()
|
|
||||||
if not greedy:
|
|
||||||
rerollList.remove(r.value)
|
|
||||||
last_index = len(self.rolled)
|
|
||||||
self.rolled.extend(to_extend)
|
|
||||||
|
|
||||||
def get_total(self):
|
|
||||||
"""Returns:
|
|
||||||
int - The total value of the dice."""
|
|
||||||
return sum(r.value for r in self.rolled if r.kept)
|
|
||||||
|
|
||||||
def get_eval(self):
|
|
||||||
return str(self.get_total())
|
|
||||||
|
|
||||||
def get_num_kept(self):
|
|
||||||
return sum(1 for r in self.rolled if r.kept)
|
|
||||||
|
|
||||||
def get_crit(self):
|
|
||||||
"""Returns:
|
|
||||||
int - 0 for no crit, 1 for crit, 2 for crit fail."""
|
|
||||||
if self.get_num_kept() == 1 and self.max_value == 20:
|
|
||||||
if self.get_total() == 20:
|
|
||||||
return 1
|
|
||||||
elif self.get_total() == 1:
|
|
||||||
return 2
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{0.num_dice}d{0.max_value}{1} ({2}) {0.annotation}".format(
|
|
||||||
self, ''.join(self.operators), ', '.join(str(r) for r in self.rolled))
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {'type': 'dice', 'dice': [d.to_dict() for d in self.rolled], 'annotation': self.annotation,
|
|
||||||
'value': self.get_total(), 'is_crit': self.get_crit(), 'num_kept': self.get_num_kept(),
|
|
||||||
'text': str(self), 'num_dice': self.num_dice, 'dice_size': self.max_value, 'operators': self.operators}
|
|
||||||
|
|
||||||
|
|
||||||
class SingleDice:
|
|
||||||
def __init__(self, value: int = 0, max_value: int = 0, kept: bool = True, exploded: bool = False):
|
|
||||||
self.value = value
|
|
||||||
self.max_value = max_value
|
|
||||||
self.kept = kept
|
|
||||||
self.rolls = [value] # list of ints (for X -> Y -> Z)
|
|
||||||
self.exploded = exploded
|
|
||||||
|
|
||||||
def drop(self):
|
|
||||||
self.kept = False
|
|
||||||
|
|
||||||
def explode(self):
|
|
||||||
self.exploded = True
|
|
||||||
|
|
||||||
def update(self, new_value):
|
|
||||||
self.value = new_value
|
|
||||||
self.rolls.append(new_value)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
formatted_rolls = [str(r) for r in self.rolls]
|
|
||||||
if int(formatted_rolls[-1]) == self.max_value or int(formatted_rolls[-1]) == 1:
|
|
||||||
formatted_rolls[-1] = '**' + formatted_rolls[-1] + '**'
|
|
||||||
if self.exploded:
|
|
||||||
formatted_rolls[-1] = '__' + formatted_rolls[-1] + '__'
|
|
||||||
if self.kept:
|
|
||||||
return ' -> '.join(formatted_rolls)
|
|
||||||
else:
|
else:
|
||||||
return '~~' + ' -> '.join(formatted_rolls) + '~~'
|
resultText = node.comment.capitalize()
|
||||||
|
|
||||||
def __repr__(self):
|
return f"**{resultText}**: {self._stringify(node.roll)}\n**Total**: {int(node.total)}"
|
||||||
return "<SingleDice object: value={0.value}, max_value={0.max_value}, kept={0.kept}, rolls={0.rolls}>".format(
|
|
||||||
self)
|
|
||||||
|
|
||||||
def to_dict(self):
|
def roll_dice(user,rollString = "1d20"):
|
||||||
return {'type': 'single_dice', 'value': self.value, 'size': self.max_value, 'is_kept': self.kept,
|
while len(rollString) > 1 and rollString[0] == " ":
|
||||||
'rolls': self.rolls, 'exploded': self.exploded}
|
rollString = rollString[1:]
|
||||||
|
return user+" :game_die:\n"+str(d20.roll(rollString, allow_comments=True,stringifier=MyStringifier()))
|
||||||
|
|
||||||
|
|
||||||
class Constant(Part):
|
|
||||||
def __init__(self, value: int = 0, annotation: str = ""):
|
|
||||||
self.value = value
|
|
||||||
self.annotation = annotation if annotation != None else ''
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{0.value} {0.annotation}".format(self)
|
|
||||||
|
|
||||||
def get_eval(self):
|
|
||||||
return str(self.value)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {'type': 'constant', 'value': self.value, 'annotation': self.annotation}
|
|
||||||
|
|
||||||
|
|
||||||
class Operator(Part):
|
|
||||||
def __init__(self, op: str = "+", annotation: str = ""):
|
|
||||||
self.op = op if op != None else ''
|
|
||||||
self.annotation = annotation if annotation != None else ''
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{0.op} {0.annotation}".format(self)
|
|
||||||
|
|
||||||
def get_eval(self):
|
|
||||||
return self.op
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {'type': 'operator', 'value': self.op, 'annotation': self.annotation}
|
|
||||||
|
|
||||||
|
|
||||||
class Comment(Part):
|
|
||||||
def __init__(self, comment: str = ""):
|
|
||||||
self.comment = comment
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.comment.strip()
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {'type': 'comment', 'value': self.comment}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_selectors(opts, res, greedy=False, inverse=False):
|
|
||||||
"""Returns a list of ints."""
|
|
||||||
for o in range(len(opts)):
|
|
||||||
if opts[o][0] =='h':
|
|
||||||
opts[o] = nlargest(int(opts[o].split('h')[1]), (d.value for d in res.rolled if d.kept))
|
|
||||||
elif opts[o][0] =='l':
|
|
||||||
opts[o] = nsmallest(int(opts[o].split('l')[1]), (d.value for d in res.rolled if d.kept))
|
|
||||||
elif opts[o][0] =='>':
|
|
||||||
if greedy:
|
|
||||||
opts[o] = list(range(int(opts[o].split('>')[1]) + 1, res.max_value + 1))
|
|
||||||
else:
|
|
||||||
opts[o] = [d.value for d in res.rolled if d.value > int(opts[o].split('>')[1])]
|
|
||||||
elif opts[o][0] =='<':
|
|
||||||
if greedy:
|
|
||||||
opts[o] = list(range(1, int(opts[o].split('<')[1])))
|
|
||||||
else:
|
|
||||||
opts[o] = [d.value for d in res.rolled if d.value < int(opts[o].split('<')[1])]
|
|
||||||
out = []
|
|
||||||
for o in opts:
|
|
||||||
if isinstance(o, list):
|
|
||||||
out.extend(int(l) for l in o)
|
|
||||||
elif not greedy:
|
|
||||||
out.extend(int(o) for a in res.rolled if a.value ==int(o) and a.kept)
|
|
||||||
else:
|
|
||||||
out.append(int(o))
|
|
||||||
|
|
||||||
if not inverse:
|
|
||||||
return out
|
|
||||||
|
|
||||||
inverse_out = []
|
|
||||||
for rolled in res.rolled:
|
|
||||||
if rolled.kept and rolled.value in out:
|
|
||||||
out.remove(rolled.value)
|
|
||||||
elif rolled.kept:
|
|
||||||
inverse_out.append(rolled.value)
|
|
||||||
return inverse_out
|
|
||||||
|
|
||||||
|
|
||||||
class DiceResult:
|
|
||||||
"""Class to hold the output of a dice roll."""
|
|
||||||
|
|
||||||
def __init__(self, result: int = 0, verbose_result: str = '', crit: int = 0, rolled: str = '', skeleton: str = '',
|
|
||||||
raw_dice: Roll = None):
|
|
||||||
self.plain = result
|
|
||||||
self.total = result
|
|
||||||
self.result = verbose_result
|
|
||||||
self.crit = crit
|
|
||||||
self.rolled = rolled
|
|
||||||
self.skeleton = skeleton if skeleton != '' else verbose_result
|
|
||||||
self.raw_dice = raw_dice # Roll
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.result
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<DiceResult object: total={}>'.format(self.total)
|
|
||||||
|
|
||||||
def consolidated(self):
|
|
||||||
"""Gets the most simplified version of the roll string."""
|
|
||||||
if self.raw_dice ==None:
|
|
||||||
return "0"
|
|
||||||
parts = [] # list of (part, annotation)
|
|
||||||
last_part = ""
|
|
||||||
for p in self.raw_dice.parts:
|
|
||||||
if isinstance(p, SingleDiceGroup):
|
|
||||||
last_part += str(p.get_total())
|
|
||||||
else:
|
|
||||||
last_part += str(p)
|
|
||||||
if not isinstance(p, Comment) and p.annotation:
|
|
||||||
parts.append((last_part, p.annotation))
|
|
||||||
last_part = ""
|
|
||||||
if last_part:
|
|
||||||
parts.append((last_part, ""))
|
|
||||||
|
|
||||||
to_roll = ""
|
|
||||||
last_annotation = ""
|
|
||||||
out = ""
|
|
||||||
for numbers, annotation in parts:
|
|
||||||
if annotation and annotation != last_annotation and to_roll:
|
|
||||||
out += f"{roll(to_roll).total:+} {last_annotation}"
|
|
||||||
to_roll = ""
|
|
||||||
if annotation:
|
|
||||||
last_annotation = annotation
|
|
||||||
to_roll += numbers
|
|
||||||
if to_roll:
|
|
||||||
out += f"{roll(to_roll).total:+} {last_annotation}"
|
|
||||||
out = out.strip('+ ')
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
while True:
|
|
||||||
print(roll(input().strip()))
|
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
class AvraeException(Exception):
|
|
||||||
|
|
||||||
"""A base exception class."""
|
|
||||||
|
|
||||||
def __init__(self, msg):
|
|
||||||
|
|
||||||
"""A."""
|
|
||||||
super().__init__(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class NoCharacter(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when a user has no active character."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
"""A."""
|
|
||||||
super().__init__("You have no character active.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoActiveBrew(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when a user has no active homebrew of a certain type."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("You have no homebrew of this type active.")
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalImportError(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when something fails to import."""
|
|
||||||
|
|
||||||
def __init__(self, msg):
|
|
||||||
super().__init__(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidArgument(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when an argument is invalid."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EvaluationError(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when a cvar evaluation causes an error."""
|
|
||||||
|
|
||||||
def __init__(self, original):
|
|
||||||
super().__init__(f"Error evaluating expression: {original}")
|
|
||||||
self.original = original
|
|
||||||
|
|
||||||
|
|
||||||
class FunctionRequiresCharacter(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when a function that requires a character is called without one."""
|
|
||||||
|
|
||||||
def __init__(self, msg=None):
|
|
||||||
super().__init__(msg or "This alias requires an active character.")
|
|
||||||
|
|
||||||
|
|
||||||
class OutdatedSheet(AvraeException):
|
|
||||||
|
|
||||||
"""Raised when a feature is used that requires an updated sheet."""
|
|
||||||
|
|
||||||
def __init__(self, msg=None):
|
|
||||||
super().__init__(msg or "This command requires an updated character sheet. Try running `!update`.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoSpellDC(AvraeException):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("No spell save DC found.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoSpellAB(AvraeException):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("No spell attack bonus found.")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidSaveType(AvraeException):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Invalid save type.")
|
|
||||||
|
|
||||||
|
|
||||||
class ConsumableException(AvraeException):
|
|
||||||
|
|
||||||
"""A base exception for consumable exceptions to stem from."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ConsumableNotFound(ConsumableException):
|
|
||||||
|
|
||||||
"""Raised when a consumable is not found."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("The requested counter does not exist.")
|
|
||||||
|
|
||||||
|
|
||||||
class CounterOutOfBounds(ConsumableException):
|
|
||||||
|
|
||||||
"""Raised when a counter is set to a value out of bounds."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("The new value is out of bounds.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoReset(ConsumableException):
|
|
||||||
|
|
||||||
"""Raised when a consumable without a reset is reset."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("The counter does not have a reset value.")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidSpellLevel(ConsumableException):
|
|
||||||
|
|
||||||
"""Raised when a spell level is invalid."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("The spell level is invalid.")
|
|
||||||
|
|
||||||
|
|
||||||
class SelectionException(AvraeException):
|
|
||||||
|
|
||||||
"""A base exception for message awaiting exceptions to stem from."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoSelectionElements(SelectionException):
|
|
||||||
|
|
||||||
"""Raised when get_selection() is called with no choices."""
|
|
||||||
|
|
||||||
def __init__(self, msg=None):
|
|
||||||
super().__init__(msg or "There are no choices to select from.")
|
|
||||||
|
|
||||||
|
|
||||||
class SelectionCancelled(SelectionException):
|
|
||||||
|
|
||||||
"""Raised when get_selection() is cancelled or times out."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Selection timed out or was cancelled.")
|
|
||||||
|
|
||||||
|
|
||||||
class CombatException(AvraeException):
|
|
||||||
|
|
||||||
"""A base exception for combat-related exceptions to stem from."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CombatNotFound(CombatException):
|
|
||||||
|
|
||||||
"""Raised when a channel is not in combat."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("This channel is not in combat.")
|
|
||||||
|
|
||||||
|
|
||||||
class RequiresContext(CombatException):
|
|
||||||
|
|
||||||
"""Raised when a combat is committed without context."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Combat not contextualized.")
|
|
||||||
|
|
||||||
|
|
||||||
class ChannelInCombat(CombatException):
|
|
||||||
|
|
||||||
"""Raised when a combat is started with an already active combat."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Channel already in combat.")
|
|
||||||
|
|
||||||
|
|
||||||
class CombatChannelNotFound(CombatException):
|
|
||||||
|
|
||||||
"""Raised when a combat's channel is not in the channel list."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Combat channel does not exist.")
|
|
||||||
|
|
||||||
|
|
||||||
class NoCombatants(CombatException):
|
|
||||||
|
|
||||||
"""Raised when a combat tries to advance turn with no combatants."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("There are no combatants.")
|
|
Reference in New Issue
Block a user