rollcake bot

This commit is contained in:
xenofem 2023-03-24 04:17:07 -04:00
parent 02586f1d34
commit 86e4c583b4

View file

@ -0,0 +1,170 @@
import random
import re
USAGE = '''@ me to roll RPG dice. Examples:
@**Rollcake** 2d6+2
@**Rollcake** pbta -1 adv
*(Powered by the Apocalypse, -1 penalty, roll with advantage: 3d6, add highest 2, subtract 1)*
@**Rollcake** fitd 2
*(Forged in the Dark, action rating 2: 2d6, take highest)*
@**Rollcake** sbr 3 danger
*(Sparked by the Resistance, dice pool 3, dangerous: 3d10, remove highest 2, take highest remaining)*
@**Rollcake** void 3
*(Voidheart Symphony city roll, stress gauge 3: 2d6, check if either/both are above 3)*
'''
PBTA_RE = re.compile(r'(pbta|apoc(alypse)?|void(heart)? castle)\s+(?P<mod>[-+]?[0-9])(\s+(?P<adv>adv|dis))?')
FITD_RE = re.compile(r'(fitd|forged)\s+(?P<rating>[0-9])\b')
SBR_RE = re.compile(r'(sbr|res(istance)?)\s+(?P<pool>[0-9])(\s+(?P<risk>risk|dang))?')
VOID_RE = re.compile(r'(void(heart)?( city)?)\s+(?P<stress>[0-6])(\s+(?P<adv>adv|dis))?')
DICE_RE = re.compile(r'\b(?P<count>[0-9]*)d(?P<sides>[0-9]+)\s*(?P<mod>[-+][0-9]+)?')
def roll(count, sides):
return [random.randint(1, sides) for i in range(count)]
def handle_roll(content):
content = content.lower()
dice = DICE_RE.search(content)
if dice:
count = int(dice.group('count') or '1')
sides = int(dice.group('sides'))
mod = int(dice.group('mod') or '0')
results = roll(count, sides)
total = sum(results) + mod
return 'Total: **{}**\n(max {}, min {})\nRolls: {}'.format(
total,
max(results),
min(results),
', '.join(str(n) for n in results),
)
pbta = PBTA_RE.search(content)
if pbta:
mod = int(pbta.group('mod') or '0')
adv = pbta.group('adv')
if adv:
results = roll(3, 6)
idx = results.index(min(results) if adv == 'adv' else max(results))
else:
results = roll(2, 6)
idx = -1
total = sum(n for (i, n) in enumerate(results) if i != idx) + mod
if total >= 10:
outcome = 'Strong hit'
elif total >= 7:
outcome = 'Weak hit'
else:
outcome = 'Miss'
return '**{}**\n**{}**\nRolls: {}'.format(
outcome,
total,
', '.join(
('~~{}~~' if i == idx else '{}').format(n)
for (i, n) in enumerate(results)
)
)
void = VOID_RE.search(content)
if void:
stress = int(void.group('stress'))
adv = void.group('adv')
if adv:
results = roll(3, 6)
idx = results.index(min(results) if adv == 'adv' else max(results))
else:
results = roll(2, 6)
idx = -1
count = sum(1 for (i, n) in enumerate(results) if i != idx and n > stress)
return '**{}**\nRolls: {}'.format(
['Miss', 'Weak hit', 'Strong hit'][count],
', '.join(
('~~{}~~' if i == idx else '{}').format(n)
for (i, n) in enumerate(results)
)
)
fitd = FITD_RE.search(content)
if fitd:
rating = int(fitd.group('rating'))
if rating == 0:
results = roll(2, 6)
idx = results.index(max(results))
value = min(results)
crit = False
else:
results = roll(rating, 6)
idx = -1
value = max(results)
crit = results.count(6) >= 2
if crit:
outcome = 'Crit'
elif value == 6:
outcome = 'Full success'
elif value >= 4:
outcome = 'Mixed success'
else:
outcome = 'Failure'
return '**{}**\n**{}**\nRolls: {}'.format(
outcome,
"6, 6" if crit else value,
', '.join(
('~~{}~~' if i == idx else '{}').format(n)
for (i, n) in enumerate(results)
)
)
sbr = SBR_RE.search(content)
if sbr:
pool = int(sbr.group('pool'))
risk = sbr.group('risk')
remove = 2 if risk == 'dang' else (1 if risk == 'risk' else 0)
if remove >= pool:
result = roll(1, 10)[0]
return '**{}**\n{}'.format(
'Success at a cost' if result == 10 else 'Failure',
result,
)
results = roll(pool, 10)
indices = list(range(pool))
indices.sort(key=lambda i: results[i], reverse=True)
indices = indices[:remove]
best = max(n for (i, n) in enumerate(results) if i not in indices)
if best == 10:
outcome = 'Critical success'
elif best >= 8:
outcome = 'Success'
elif best >= 6:
outcome = 'Success at a cost'
elif best >= 2:
outcome = 'Failure'
else:
outcome = 'Critical failure'
return '**{}**\n**{}**\nRolls: {}'.format(
outcome,
best,
', '.join(
('~~{}~~' if i in indices else '{}').format(n)
for (i, n) in enumerate(results)
)
)
return USAGE
class RollcakeHandler(object):
'''
A Zulip bot to roll RPG dice.
'''
def usage(self):
return USAGE
def handle_message(self, message, bot_handler):
bot_handler.send_reply(message, handle_roll(message['content']))
handler_class = RollcakeHandler