Skip to content

Commit

Permalink
Update BPdice
Browse files Browse the repository at this point in the history
  • Loading branch information
DoroWolf authored Mar 24, 2024
1 parent 61b4934 commit 8f64c42
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 235 deletions.
12 changes: 2 additions & 10 deletions modules/dice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@

@dice.command('<dices> [<dc>] {{dice.help}}')
async def _(msg: Bot.MessageSession, dices: str, dc = None):

if '#' in dices:
times = dices.partition('#')[0]
dices = dices.partition('#')[2]
else:
times = '1'
if not times.isdigit():
await msg.finish(msg.locale.t('dice.message.N.invalid') + times)
await msg.finish(await process_expression(msg, dices, int(times), dc))
await msg.finish(await process_expression(msg, dices, dc))


@dice.regex(r"[扔投掷擲丢]([0-9]*)?[个個]([0-9]*面)?骰子?([0-9]*次)?", desc="{dice.help.regex.desc}")
Expand All @@ -24,7 +16,7 @@ async def _(msg: Bot.MessageSession):
default_type = msg.data.options.get('dice_default_face') if msg.data.options.get('dice_default_face') else '6'
dice_type = groups[1][:-1] if groups[1] else default_type
roll_time = groups[2][:-1] if groups[2] else '1'
await msg.finish(await process_expression(msg, f'{groups[0]}D{dice_type}', int(roll_time), None))
await msg.finish(await process_expression(msg, f'{roll_time}#{groups[0]}D{dice_type}', None))


@dice.command('set <face> {{dice.help.set}}', required_admin=True)
Expand Down
253 changes: 31 additions & 222 deletions modules/dice/dice.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import math
import re
import secrets

import numpy as np
from simpleeval import SimpleEval, FunctionNotDefined, NameNotDefined

from config import Config
from core.exceptions import ConfigValueError
from core.logger import Logger
from .type import Dice, FudgeDice, DiceSyntaxError, DiceValueError

# 配置常量
MAX_DICE_COUNT = Config('dice_limit', 100) # 一次摇动最多的骰子数量
Expand Down Expand Up @@ -40,205 +39,7 @@
se.functions.update(math_funcs)


# 异常类定义
class DiceSyntaxError(Exception):
"""骰子语法错误"""

def __init__(self, msg, message):
self.message = message


class DiceValueError(Exception):
"""骰子参数值错误"""

def __init__(self, msg, message, value=None):
if value:
self.message = msg.locale.t("dice.message.error.value", value=value) + message
else:
self.message = message


# 类定义
class DiceItemBase(object):
"""骰子项的基类"""

def __init__(self, dice_code: str):
self.code = dice_code
self.result = None
self.detail = ''

def GetResult(self):
return self.result

def GetDetail(self):
return self.detail

def Roll(self, msg, use_markdown: bool = False):
pass


class Dice(DiceItemBase):
"""骰子项"""

def __init__(self, msg, dice_code: str):

dice_code = dice_code.replace(' ', '')
super().__init__(dice_code)
args = self.GetArgs(msg)
self.count = args[0]
self.type = args[1]
self.adv = args[2]
if self.count <= 0 or self.count > MAX_DICE_COUNT:
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.n.out_of_range", max=MAX_DICE_COUNT),
self.count)
if self.type <= 0:
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.n.less_2"),
self.type)
if self.type == 1:
raise DiceValueError(msg, msg.locale.t("dice.message.error.value.n.d1"))
if abs(self.adv) > self.count:
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.k.out_of_range"),
self.adv)

def GetArgs(self, msg):
dice_code = self.code.upper() # 便于识别
dice_code = dice_code.replace("D%", "D100") # 百分骰别名
dice_count = '1' # 骰子数量
advantage = '0' # 保留的骰子量
if re.search(r'[^0-9DKQ\%]', dice_code):
raise DiceSyntaxError(msg, msg.locale.t("dice.message.error.invalid"))
temp = dice_code.split('D')
if len(temp[0]):
dice_count = temp[0]
else:
dice_count = '1'
dice_type = temp[1]
if 'K' in temp[1]:
midstrs = temp[1].partition('K')
dice_type = midstrs[0]
advantage = midstrs[2]
elif 'Q' in temp[1]:
midstrs = temp[1].partition('Q')
dice_type = midstrs[0]
advantage = f'-{midstrs[2]}'
if not len(advantage.removeprefix('-')):
advantage += '1' # K/Q后没有值默认为1
# 语法合法检定
if not dice_count.isdigit():
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.m.invalid"),
dice_count)
if not dice_type.isdigit():
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.n.invalid"),
dice_type)
if not (advantage.isdigit() or (advantage[0] == '-' and advantage[1:].isdigit())):
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.k.invalid"),
advantage)
return (int(dice_count), int(dice_type), int(advantage))

def Roll(self, msg, use_markdown=False):
output = ''
result = 0
dice_results = []
adv = self.adv
output += self.code + '='
# 生成随机序列
for i in range(self.count):
dice_results.append(secrets.randbelow(int(self.type)) + 1)
if adv != 0:
new_results = []
indexes = np.array(dice_results).argsort()
indexes = indexes[-adv:] if adv > 0 else indexes[:-adv]
output += '['
output_buffer = ''
for i in range(self.count):
if use_markdown:
if i in indexes:
new_results.append(dice_results[i])
output_buffer += f"*{str(dice_results[i])}*"
else:
output_buffer += f"{str(dice_results[i])}"
else:
output_buffer += str(dice_results[i])
if i in indexes:
new_results.append(dice_results[i])
output_buffer += '*'
if i < self.count - 1:
output_buffer += ', '
if self.count >= MAX_OUTPUT_CNT:
output_buffer = msg.locale.t("dice.message.output.too_long", length=self.count)
output += output_buffer + ']='
dice_results = new_results
# 公用加法
length = len(dice_results)
if length > 1:
output += '['
if length > MAX_OUTPUT_CNT: # 显示数据含100
output += msg.locale.t("dice.message.output.too_long", length=length)
for i in range(length):
result += dice_results[i]
if length <= MAX_OUTPUT_CNT: # 显示数据含100
output += str(dice_results[i])
if i < length - 1:
output += '+'
output += ']='
else:
result = dice_results[0]
if len(output) > MAX_OUTPUT_LEN:
output = msg.locale.t("dice.message.too_long")
self.detail = output + f"{result}"
self.result = result


class FudgeDice(DiceItemBase):
"""命运骰子项"""

def __init__(self, msg, dice_code: str):
super().__init__(dice_code)
self.count = 4 # 默认投掷次数为4

# 兼容旧格式
if dice_code.upper().endswith('DF'):
dice_code = dice_code[:-2] + 'F'

if len(dice_code) > 1:
try:
self.count = int(dice_code[:-1])
except ValueError:
raise DiceSyntaxError(msg, msg.locale.t("dice.message.error.invalid"))
if self.count <= 0 or self.count > MAX_DICE_COUNT:
raise DiceValueError(msg,
msg.locale.t("dice.message.error.value.n.out_of_range", max=MAX_DICE_COUNT),
self.count)

def Roll(self, msg, use_markdown=False):
output = ''
result = 0
output += self.code + '='

dice_results = ['-', '-', '0', '0', '+', '+']
selected_results = [secrets.choice(dice_results) for _ in range(self.count)]
output += '[' + ', '.join(selected_results) + ']'

for res in selected_results:
if res == '-':
result -= 1
elif res == '+':
result += 1

self.detail = output + f"={result}"
self.result = result

def GetArgs(self, msg):
return self.count, 6, 0


async def process_expression(msg, expr: str, times: int, dc, use_markdown = False):
async def process_expression(msg, expr: str, dc, use_markdown = False):
if not all([MAX_DICE_COUNT > 0, MAX_ROLL_TIMES > 0, MAX_OUTPUT_CNT > 0,
MAX_OUTPUT_LEN > 0, MAX_DETAIL_CNT > 0, MAX_ITEM_COUNT > 0]):
raise ConfigValueError(msg.locale.t("error.config.invalid"))
Expand All @@ -248,7 +49,7 @@ async def process_expression(msg, expr: str, times: int, dc, use_markdown = Fals
expr = expr.replace('*', '\*')
expr = expr.replace('\\*', '\*')

dice_list, count, err = parse_dice_expression(msg, expr)
dice_list, count, times, err = parse_dice_expression(msg, expr)
if err:
return err
output = generate_dice_message(msg, expr, dice_list, count, times, dc, use_markdown)
Expand All @@ -265,43 +66,50 @@ def parse_dice_expression(msg, dices):
errmsg = None

# 切分骰子表达式
if '#' in dices:
times = dices.partition('#')[0]
dices = dices.partition('#')[2]
else:
times = '1'
if not times.isdigit():
return None, None, None, DiceValueError(msg, msg.locale.t('dice.message.N.invalid'), times).message

dice_expr_list = re.split('|'.join(patterns), dices, flags=re.I)
dice_expr_list = [item for item in dice_expr_list if item] # 清除空白元素
for item in range(len(dice_expr_list)):
if dice_expr_list[item][-1].upper() == 'D' and msg.data.options.get('dice_default_face'):
dice_expr_list[item] += str(msg.data.options.get('dice_default_face'))
Logger.debug(dice_expr_list)

for item in dice_expr_list:
for i, item in enumerate(dice_expr_list):
for pattern in patterns:
match = re.match(pattern, item, flags=re.I)
if match:
dice_expr_list[i] = item.upper()
dice_item_list.append(item)
break
if len(dice_item_list) > MAX_ITEM_COUNT:
return None, None, DiceValueError(msg, msg.locale.t('dice.message.error.value.too_long')).message
return None, None, None, DiceValueError(msg, msg.locale.t('dice.message.error.value.too_long')).message

dice_count = 0
i = 0
# 初始化骰子序列
for item in dice_expr_list:
for j, item in enumerate(dice_expr_list):
try:
i += 1
if 'F' in item or 'f' in item:
dice_count += 1
dice_expr_list[i-1] = FudgeDice(msg, item)
dice_expr_list[j] = FudgeDice(msg, item)
elif 'D' in item or 'd' in item:
dice_count += 1
dice_expr_list[i-1] = Dice(msg, item)
dice_expr_list[j] = Dice(msg, item)
elif item.isdigit():
dice_count += 1
else:
continue
except (DiceSyntaxError, DiceValueError) as ex:
errmsg = msg.locale.t('dice.message.error.prompt', i=dice_count) + ex.message
if errmsg:
return None, None, DiceValueError(msg, msg.locale.t('dice.message.error') + '\n' + errmsg).message
return dice_expr_list, dice_count, None
return None, None, None, DiceValueError(msg, msg.locale.t('dice.message.error') + '\n' + errmsg).message
return dice_expr_list, dice_count, int(times), None


# 在数字与数字之间加上乘号
Expand Down Expand Up @@ -333,21 +141,19 @@ def generate_dice_message(msg, expr, dice_expr_list, dice_count, times, dc, use_
msg.locale.t("dice.message.error.value.N.out_of_range", max=MAX_ROLL_TIMES),
times).message
# 开始投掷并生成消息
for i in range(times):
j = 0
for _ in range(times):
dice_detail_list = dice_expr_list.copy()
dice_res_list = dice_expr_list.copy()
output_line = ''
for item in dice_detail_list:
j += 1
for i, item in enumerate(dice_detail_list):
if isinstance(item, (Dice, FudgeDice)):
item.Roll(msg, use_markdown)
res = item.GetResult()
if times * dice_count < MAX_DETAIL_CNT:
dice_detail_list[j-1] = f'({item.GetDetail()})'
dice_detail_list[i] = f'({item.GetDetail()})'
else:
dice_detail_list[j-1] = f'({str(res)})' if res < 0 else str(res) # 负数加括号
dice_res_list[j-1] = f'({str(res)})' if res < 0 else str(res)
dice_detail_list[i] = f'({str(res)})' if res < 0 else str(res) # 负数加括号
dice_res_list[i] = f'({str(res)})' if res < 0 else str(res)
else:
continue
dice_detail_list = insert_multiply(dice_detail_list, use_markdown)
Expand All @@ -356,10 +162,13 @@ def generate_dice_message(msg, expr, dice_expr_list, dice_count, times, dc, use_
Logger.debug(dice_detail_list)
Logger.debug(dice_res_list)
try:
dice_res = ''.join(dice_res_list)
dice_res = dice_res.replace('\*', '*')
Logger.debug(dice_res)
result = int(se.eval(dice_res))
if dice_res_list:
dice_res = ''.join(dice_res_list)
dice_res = dice_res.replace('\*', '*')
Logger.debug(dice_res)
result = int(se.eval(dice_res))
else:
raise SyntaxError
except (FunctionNotDefined, NameNotDefined, SyntaxError):
return DiceSyntaxError(msg, msg.locale.t('dice.message.error.syntax')).message
except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion modules/dice/locales/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dice.message.error.value.n.invalid": "Invalid sides number of dice.",
"dice.message.error.value.n.less_2": "The sides number of dice cannot be less than 2.",
"dice.message.error.value.N.out_of_range": "The number of dice roll cannot be less than 1 or greater than ${max}.",
"dice.message.error.value.n.out_of_range": "The number of dice cannot be less than 1 or greater than ${max}.",
"dice.message.error.value.m.out_of_range": "The number of dice cannot be less than 1 or greater than ${max}.",
"dice.message.error.value.too_long": "The number of items in the dice expression exceeds the limit.",
"dice.message.error.value.y.invalid": "Invalid adjustment value.",
"dice.message.error.value.y.out_of_range": "The adjustment value cannot be less than ${min} or greater than ${max}.",
Expand Down
2 changes: 1 addition & 1 deletion modules/dice/locales/zh_cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dice.message.error.value.n.invalid": "无效的骰子面数。",
"dice.message.error.value.n.less_2": "骰子面数不得小于 2。",
"dice.message.error.value.N.out_of_range": "投骰次数不得小于 1 或大于 ${max}。",
"dice.message.error.value.n.out_of_range": "骰子数量不得小于 1 或大于 ${max}。",
"dice.message.error.value.m.out_of_range": "骰子数量不得小于 1 或大于 ${max}。",
"dice.message.error.value.too_long": "骰子表达式项数超过限制。",
"dice.message.error.value.y.invalid": "无效的调节值项。",
"dice.message.error.value.y.out_of_range": "调节值不得小于 ${min} 或大于 ${max}。",
Expand Down
2 changes: 1 addition & 1 deletion modules/dice/locales/zh_tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dice.message.error.value.n.d1": "1 面的骰子?",
"dice.message.error.value.n.invalid": "無效的骰子面數。",
"dice.message.error.value.n.less_2": "骰子面數不得小於 2。",
"dice.message.error.value.n.out_of_range": "骰子數量不得小於 1 或大於 ${max}。",
"dice.message.error.value.m.out_of_range": "骰子數量不得小於 1 或大於 ${max}。",
"dice.message.error.value.N.out_of_range": "投骰次數不得小於 1 或大於 ${max}。",
"dice.message.error.value.too_long": "骰子運算式項數超過限制。",
"dice.message.error.value.y.invalid": "無效的調整值項。",
Expand Down
Loading

0 comments on commit 8f64c42

Please sign in to comment.