Archived
1
0
Fork 0
This repository has been archived on 2024-04-26. You can view files and clone it, but cannot push or open issues or pull requests.
akari-bot/modules/dice/dice.py
2023-04-30 11:30:59 +08:00

226 lines
8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
import secrets
import numpy as np
MAX_DICE_COUNT = 100 # 一次摇动最多的骰子数量
MAX_ROLL_TIMES = 10 # 一次命令最多的摇动次数
MAX_MOD_NUMBER = 10000 # 骰子最大加权值
MIN_MOD_NUMBER = -10000 # 骰子最小加权值
MAX_OUTPUT_NUM = 50 # 输出的最多数据量
MAX_OUTPUT_LEN = 200 # 输出的最大长度
MAX_DETAIL_CNT = 5 # n次摇动的骰子的总量超过该值时将不再显示详细信息
MAX_ITEM_COUNT = 10 # 骰子多项式最多的项数
class DiceSyntaxError(Exception):
"""骰子语法错误"""
def __init__(self, message):
self.message = f"语法错误: {message}"
class DiceValueError(Exception):
"""骰子参数值错误"""
def __init__(self, message, value=None):
if value is not None:
self.message = f"参数错误: 输入为{value}{message} "
else:
self.message = f"参数错误: {message} "
class DiceItemBase(object):
"""骰子项的基类"""
def __init__(self, diceCode: str, postive: bool):
self.postive = postive
self.code = diceCode
self.result = None
self.detail = ''
def GetResult(self, abs=True):
if abs:
return self.result
else:
return self.result if self.postive else -self.result
def GetDetail(self):
return self.detail
def Roll(self):
pass
class DiceMod(DiceItemBase):
"""调节值项"""
def __init__(self, diceCode: str, postive: bool):
super().__init__(diceCode, postive)
if not diceCode.isdigit():
raise DiceValueError(f'无效的调节值项', '+' if self.postive else '-' + diceCode)
else:
self.result = int(diceCode)
if self.result > MAX_MOD_NUMBER or self.result < MIN_MOD_NUMBER:
raise DiceValueError(f'调节值不得小于 {MIN_MOD_NUMBER} 或大于 {MIN_MOD_NUMBER}', self.result)
def GetDetail(self):
return self.result
class Dice(DiceItemBase):
"""骰子项"""
def __init__(self, diceCode: str, postive: bool):
diceCode = diceCode.replace(' ', '')
super().__init__(diceCode, postive)
args = self.GetArgs()
self.count = args[0]
self.type = args[1]
self.adv = args[2]
if self.count <= 0 or self.count > MAX_DICE_COUNT:
raise DiceValueError(f'骰子数量不得小于 1 或大于 {MAX_DICE_COUNT}', self.count)
if self.type <= 0:
raise DiceValueError(f'骰子面数不得小于 2', self.count)
if self.type == 1:
raise DiceValueError(f'1 ... 1 面的骰子?')
if abs(self.adv) > self.count:
raise DiceValueError(f'优劣势骰数大于总骰子数', self.adv)
def GetArgs(self):
diceCode = self.code.upper() # 便于识别
diceCount = '1' # 骰子数量
advantage = '0' # 保留的骰子量
if re.search(r'[^0-9DKL]', diceCode):
raise DiceSyntaxError('骰子语句中存在无法识别的字符')
if 'D' not in diceCode:
raise DiceSyntaxError('骰子语句缺失字符 D')
temp = diceCode.split('D')
if len(temp[0]):
diceCount = temp[0]
else:
diceCount = '1'
midstrs = temp[1].partition('K')
diceType = midstrs[0]
if 'K' in midstrs[1]:
advantage = midstrs[2].replace('L', '-')
if not len(advantage.removeprefix('-')):
advantage += '1' # K/KL后没有值默认为1
# 语法合法检定
if not diceCount.isdigit():
raise DiceValueError(f'无效的骰子数量', diceCount)
if not diceType.isdigit():
raise DiceValueError(f'无效的骰子面数', diceType)
if not (advantage.isdigit() or (advantage[0] == '-' and advantage[1:].isdigit())):
raise DiceValueError(f'无效的优劣势', advantage)
return (int(diceCount), int(diceType), int(advantage))
def Roll(self):
output = ''
result = 0
diceResults = []
adv = self.adv
output += self.code + ' = '
# 生成随机序列
for i in range(self.count):
diceResults.append(secrets.randbelow(int(self.type)) + 1)
if adv != 0:
newResults = []
indexs = np.array(diceResults).argsort()
indexs = indexs[-adv:] if adv > 0 else indexs[:-adv]
output += '( '
outputBuffer = ''
for i in range(self.count):
outputBuffer += str(diceResults[i])
if i in indexs:
newResults.append(diceResults[i])
outputBuffer += '*'
if i < self.count - 1:
outputBuffer += ','
if self.count >= MAX_OUTPUT_NUM:
outputBuffer = f"数量过大,已省略 {self.count} 个数据"
output += outputBuffer + ' ) = '
diceResults = newResults
# 公用加法
length = len(diceResults)
if (length > 1):
output += '[ '
if length > MAX_OUTPUT_NUM: # 显示数据含100
output += f'数量过大,已省略 {length} 个数据'
for i in range(length):
result += diceResults[i]
if length <= MAX_OUTPUT_NUM: # 显示数据含100
output += str(diceResults[i])
if i < length - 1:
output += '+'
output += ' ] = '
else:
result = diceResults[0]
if len(output) > MAX_OUTPUT_LEN:
output = '输出过长...'
self.detail = output + f" {result} "
self.result = result
async def GenerateMessage(dices: str, times: int, dc: int):
if re.search(r'[^0-9+\-DKL]', dices.upper()):
return DiceSyntaxError('骰子语句中存在无法识别的字符').message
if times > MAX_ROLL_TIMES or times < 1:
return DiceValueError(f'投骰次数不得小于 1 或 大于 {MAX_ROLL_TIMES}', times).message
diceCodeList = re.compile(r'[+-]?[^+-]+').findall(dices)
diceList = []
haveErr = False
output = ""
diceCount = 0
i = 0
if len(diceCodeList) > MAX_ITEM_COUNT:
return DiceValueError('骰子多项式项数超过限制', len(diceCodeList)).message
# 初始化骰子序列
for item in diceCodeList:
i += 1
isAdd = True
if item[0] == '-':
isAdd = False
item = item[1:]
if item[0] == '+':
item = item[1:]
try:
if 'D' in item or 'd' in item:
d = Dice(item, isAdd)
diceList.append(d)
diceCount += d.count
elif item.isdigit():
diceList.append(DiceMod(item, isAdd))
except (DiceSyntaxError, DiceValueError) as ex:
output += f'\n{i}项发生{ex.message}'
haveErr = True
if haveErr:
return '解析骰子多项式时存在以下错误:' + output
successNum = 0
failNum = 0
output = '你掷得的结果是:'
# 开始摇动并输出
for i in range(times):
outputLine = ''
result = 0
for dice in diceList:
dice.Roll()
outputLine += '+' if dice.postive else '-'
if isinstance(dice, Dice) and times * diceCount < MAX_DETAIL_CNT:
outputLine += f'( {dice.GetDetail()})'
else:
outputLine += str(dice.GetResult())
result += dice.GetResult(False)
outputLine = outputLine.removeprefix('+') # 移除多项式首个+
outputLine += ' = ' + str(result)
if dc != 0:
if result > dc:
outputLine += ',判定成功!'
successNum += 1
else:
outputLine += ',判定失败!'
failNum += 1
output += f'\n{dices} = {outputLine}'
if dc != 0 and times > 1:
output += '\n▷ 判定成功数量:' + str(successNum) + ' 判定失败数量:' + str(failNum)
return output