rollcake bot
This commit is contained in:
		
							parent
							
								
									02586f1d34
								
							
						
					
					
						commit
						86e4c583b4
					
				
					 1 changed files with 170 additions and 0 deletions
				
			
		
							
								
								
									
										170
									
								
								zulip_bots/zulip_bots/bots/rollcake/rollcake.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								zulip_bots/zulip_bots/bots/rollcake/rollcake.py
									
										
									
									
									
										Normal 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 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue