diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/examples/ndice/infini.toml | 9 | ||||
| -rw-r--r-- | tests/examples/ndice/src/dicer.py | 360 | ||||
| -rw-r--r-- | tests/examples/ndice/src/ndice.py | 156 | ||||
| -rw-r--r-- | tests/examples/ndice/tests.py | 17 |
4 files changed, 542 insertions, 0 deletions
diff --git a/tests/examples/ndice/infini.toml b/tests/examples/ndice/infini.toml new file mode 100644 index 00000000..5db718a3 --- /dev/null +++ b/tests/examples/ndice/infini.toml @@ -0,0 +1,9 @@ +[infini] +name = "ndice" +version = "0.1.0" +description = "基础掷骰指令规则包" +license = "Apache-2.0" + +[requirements] + +[dependencies] diff --git a/tests/examples/ndice/src/dicer.py b/tests/examples/ndice/src/dicer.py new file mode 100644 index 00000000..74bb3714 --- /dev/null +++ b/tests/examples/ndice/src/dicer.py @@ -0,0 +1,360 @@ +from multilogging import multilogger +from typing import List + +import abc +import re +import random + + +ZERO = 0 +EMPTY_STRING = "" +EMPTY_LIST = [] + +logger = multilogger(name="DicerGirl", payload="Dicer") + + +class BaseDice: + def __init__(self, roll_string: str = EMPTY_STRING) -> None: + self.roll_string = roll_string + self.db = EMPTY_STRING + self.outcome = ZERO + self.display = EMPTY_LIST + + def __repr__(self) -> str: + return self.db.upper() + + @abc.abstractmethod + def parse(self) -> "BaseDice": + raise NotImplementedError + + @abc.abstractmethod + def roll(self) -> int: + """对骰子进行投掷并给出结果""" + raise NotImplementedError + + +class DigitDice(BaseDice): + """数字骰""" + + def __init__(self, roll_string: str = EMPTY_STRING) -> None: + super().__init__(roll_string=roll_string) + if not roll_string.isdigit(): + raise ValueError + + self.parse() + + def parse(self) -> "DigitDice": + self.a = int(self.roll_string) + self.b = 1 + self.db = f"{self.a}" + return self + + def roll(self) -> int: + self.outcome = self.a + self.display = [self.a] + return self.outcome + + +class Dice(BaseDice): + """多面骰""" + + def __init__(self, roll_string: str = "", explode: bool = False) -> None: + super().__init__(roll_string=roll_string) + self.dices = EMPTY_LIST + self.great = False + self.explode = explode + self.parse() + + def parse(self) -> "Dice": + self.dices = [] + split = re.split(r"[dD]", self.roll_string) + + if split[0]: + self.a = int(split[0]) + else: + self.a = 1 + + if split[1]: + self.b = int(split[1]) + else: + self.b = 100 + + self.db = f"{self.a}D{self.b}" + self.dices += [f"D{self.b}"] * self.a + return self + + def roll(self) -> int: + self.results = [] + self.display = [] + + for _ in range(self.a): + result = random.randint(1, self.b) + + if result == 1 and self.explode: + result -= 1 + + if self.explode and self.b == 8: + self.dices.append("D10") + result2 = random.randint(1, 10) + if result2 == 1: + result -= 1 + result += result2 + if result2 == 10: + self.dices.append("D12") + result3 = random.randint(1, 12) + if result3 == 1: + result -= 1 + result += result3 + if result3 == 12: + self.dices.append("D20") + result4 = random.randint(1, 20) + if result4 == 1: + result -= 1 + result += result4 + if result4 == 20: + self.great = True + + self.results.append(result) + self.display.append(result) + + self.outcome = sum(self.results) + return self.outcome + + +class AwardDice(BaseDice): + """奖励骰""" + + def __init__(self, roll_string: str = "") -> None: + super().__init__(roll_string=roll_string) + self.parse() + + def parse(self) -> "AwardDice": + split = re.split(r"[bB]", self.roll_string) + + if split[0]: + self.a = int(split[0]) + else: + self.a = 1 + + self.b = int(split[1]) + self.db = f"{self.a}B{self.b}" + return self + + def roll(self) -> int: + self.results = [] + self.display = [] + + for _ in range(self.a): + ten = [] + for _ in range(self.b): + outcome = Dice("1d10").roll() + outcome = outcome if outcome != 10 else 0 + ten.append(outcome) + + result = Dice("1d100").roll() + ten.append(result // 10) + minten = min(ten) + ten.remove(result // 10) + outcome = minten * 10 + (result % 10) + self.results.append(outcome) + self.display.append([result, ten]) + + self.outcome = sum(self.results) + return self.outcome + + +class PunishDice(BaseDice): + """惩罚骰""" + + def __init__(self, roll_string: str = "") -> None: + super().__init__(roll_string=roll_string) + self.parse() + + def parse(self) -> "PunishDice": + split = re.split(r"[pP]", self.roll_string) + + if split[0]: + self.a = int(split[0]) + else: + self.a = 1 + + self.b = int(split[1]) + self.db = f"{self.a}P{self.b}" + return self + + def roll(self) -> int: + self.results = [] + self.display = [] + + for _ in range(self.a): + ten = [] + for _ in range(self.b): + outcome = Dice("1d10").roll() + outcome = outcome if outcome != 10 else 0 + ten.append(outcome) + + result = Dice("1d100").roll() + ten.append(result // 10) + maxten = max(ten) + ten.remove(result // 10) + outcome = maxten * 10 + (result % 10) + self.results.append(outcome) + self.display.append([result, ten]) + + self.outcome = sum(self.results) + return self.outcome + + +class Dicer: + """掷骰类 + 参数: + roll_string: 标准掷骰表达式 + explode: 是否启用爆炸骰 + 示例: + ```python + dice = Dice("1d10") + dice.roll() + print(dice.outcome) # 输出`1d10`投掷结果 + ``` + """ + + def __init__(self, roll_string: str = EMPTY_STRING, explode: bool = False) -> None: + self.roll_string: str = roll_string + self.explode: bool = explode + self.calc_list: List[str | Dice | DigitDice | AwardDice | PunishDice] = [] + self.results: List[int] = [] + self.display: List[int | List[int]] = [] + self.outcome: int = ZERO + self.great: bool = False + self.dices: List[str] = [] + + def parse(self, roll_string: str = EMPTY_STRING, explode: bool = False): + self.roll_string = roll_string if roll_string else self.roll_string + self.calc_list = [] + self.db = EMPTY_STRING + matches: List[str] = re.findall(r"\d*[a-zA-Z]\w*|\d+|[-+*/]", self.roll_string) + + for match in matches: + if match in ("+", "-", "*", "/", "(", ")"): + self.calc_list.append(match) + self.db += match + elif re.match(r"\d*[dD]\d*", match): + self.calc_list.append(Dice(match, explode=explode)) + self.db += match.upper() + elif re.match(r"\d*[bB]\d+", match): + self.calc_list.append(AwardDice(match)) + self.db += match.upper() + elif re.match(r"\d*[pP]\d+", match): + self.calc_list.append(PunishDice(match)) + self.db += match.upper() + elif re.match(r"\d+", match): + self.calc_list.append(DigitDice(match)) + self.db += match.upper() + else: + raise ValueError(f"骰 {match} 不符合规范.") + + if not matches: + self.calc_list.append(Dice("1d100")) + self.db = "1D100" + + return self + + def roll(self): + self.parse(roll_string=self.roll_string, explode=self.explode) + self.dices = [] + self.display = [] + for index, calc in enumerate(self.calc_list): + if calc in ("+", "-", "*", "/", "(", ")"): + continue + + outcome = calc.roll() + self.calc_list[index] = outcome + self.results.append(outcome) + self.display += calc.display + + if isinstance(calc, Dice) and self.explode: + if calc.great: + self.great = True + + self.dices += calc.dices + + self.outcome = eval("".join(map(str, self.calc_list))) + return self + + def description(self): + def count_integers(lst: list) -> int: + count = 0 + for item in lst: + if isinstance(item, int): + count += 1 + elif isinstance(item, list): + count += count_integers(item) + return count + + results = self.display + len_display = count_integers(self.display) + len_results = count_integers(self.results) + + if len_display <= 10: + results = self.display + elif len_results <= 10: + results = self.results + else: + results = [...] + + return f"{self.db}={results}={self.outcome}" + + def get_results(self): + return self.results + + def detail_expr(self): + return str(self.results) + + def calc(self): + return self.outcome + + def __repr__(self): + return self.db + + +if __name__ == "__main__": + # text = "-10/d2/1d10+2d2-22/2+3p2+2b10-p4/b2/d2" + # dice = Dicer(text) + # print(dice.calc_list) + # dice.roll() + # print(dice.calc_list) + # print(dice.results) + # print(dice.outcome) + roll_strings = { + "1": 1, + "10": 10, + "100": 100, + "-1": -1, + "-10": -10, + "-100": -100, + "1d1": 1, + "10d1": 10, + "100d1": 100, + "10d1+10d1": 20, + "10d1-10d1": 0, + "10d1+10d1+10d1": 30, + "10d1-10d1+10d1": 10, + "10d1-10d1-10d1": -10, + } + for roll_string in roll_strings.keys(): + try: + dice = Dicer().parse(roll_string).roll().roll() + if dice.outcome != roll_strings[roll_string]: + print(dice.description()) + raise ValueError( + f"对于 {roll_string} dice.toal={dice.outcome} 但期待 {roll_strings[roll_string]}" + ) + except ValueError as error: + logger.exception(error) + + try: + roll_string = "d" + dice = Dicer(roll_string).roll() + print(dice.description()) + except ValueError as error: + logger.exception(error) diff --git a/tests/examples/ndice/src/ndice.py b/tests/examples/ndice/src/ndice.py new file mode 100644 index 00000000..a0b88f5e --- /dev/null +++ b/tests/examples/ndice/src/ndice.py @@ -0,0 +1,156 @@ +# Initialized `__init__.py` generated by ipm. +# Documents at https://ipm.hydroroll.team/ + +from infini.register import Register +from infini.router import Startswith +from infini.input import Input +from infini.output import Output +from .dicer import Dicer + +import re + +register = Register() +register.regist_textevent( + "ndice.roll", + "[{{ username }}]掷骰: " + "{% if descs|length == 1 %}" + "{{ descs[0] }}" + "{% else %}" + "{{ db }}=[...]=" + "{% for outcome in outcomes %}" + "{{ outcome }}" + "{% if not loop.last %}, {% endif %}" + "{% endfor %}" + "{% endif %}", +) +register.regist_textevent( + "ndice.error.bad_roll_string", "[{{ username }}]掷骰时出现异常, 疑似掷骰表达式错误." +) +register.regist_textevent("ndice.error.too_much_round", "[{{ username }}]给入的掷骰轮数超出预期.") +register.regist_textevent( + "ndice.error.unknown", + "未知错误: {{ error }}, 可能是掷骰语法异常.\nBUG提交: https://gitee.com/unvisitor/issues", +) +register.regist_textevent("ndice.error.bad_round", "多轮检定的轮数应当是整型数.") +register.regist_textevent("ndice.error.too_much_round", "多轮检定的轮数超出预期.") + + +def translate_punctuation(string: str) -> str: + punctuation_mapping = { + ",": ",", + "。": ".", + "!": "!", + "?": "?", + ";": ";", + ":": ":", + "“": '"', + "”": '"', + "‘": "'", + "’": "'", + "(": "(", + ")": ")", + "【": "[", + "】": "]", + "《": "<", + "》": ">", + } + for ch_punct, en_punct in punctuation_mapping.items(): + string = string.replace(ch_punct, en_punct) + return string + + +def format_str(message: str, begin=None, lower=True) -> str: + regex = r"[<\[](.*?)[\]>]" + message = str(message).lower() if lower else str(message) + msg = translate_punctuation( + re.sub("\s+", " ", re.sub(regex, "", message)).strip(" ") + ) + if msg.startswith("/"): + msg = "." + msg[1:] + + if begin: + if isinstance(begin, str): + begin = [ + begin, + ] + elif isinstance(begin, tuple): + begin = list(begin) + + begin.sort(reverse=True) + for b in begin: + msg = msg.replace(b, "").lstrip(" ") + + return msg + + +def roll(_: Input, args: str, name: str = None) -> str: + time = 1 + if "#" in args: + args = args.split("#") + + try: + time = int(args[0].strip()) + if time > 20: + return Output( + "text", "ndice.error.too_much_round", variables={"username": name} + ) + except ValueError: + return Output("text", "ndice.error.bad_round", variables={"username": name}) + + if len(args) == 1: + args = "1d100" + else: + args = args[1] + else: + args = args.strip() + + args = args.split() + if len(args) > 1: + reason = args[1] + else: + reason = None + + roll_string = args[0] + descs = [] + outcomes = [] + + try: + dice = Dicer(roll_string).roll() + descs.append(dice.description()) + outcomes.append(dice.outcome) + + for _ in range(time - 1): + dice.roll() + descs.append(dice.description()) + outcomes.append(dice.outcome) + except ValueError: + return Output( + "text", + "ndice.error.bad_roll_string", + status=1, + variables={"username": name}, + ) + + return Output( + "text", + "ndice.roll", + variables={ + "username": name, + "descs": descs, + "outcomes": outcomes, + "db": dice.db, + }, + ) + + +@register.handler(router=Startswith(".r"), priority=0) +def roll_handler(input: Input): + args = format_str(input.get_plain_text(), begin=(".r", ".roll")) + name = input.variables.get("nickname") or "苏向夜" + if not args: + return roll(input, "1d100", name=name) + + try: + return roll(input, args, name=name) + except Exception as error: + return Output("text", "ndice.error.unknown", variables={"error": str(error)}) diff --git a/tests/examples/ndice/tests.py b/tests/examples/ndice/tests.py new file mode 100644 index 00000000..c2e9b9d3 --- /dev/null +++ b/tests/examples/ndice/tests.py @@ -0,0 +1,17 @@ +from infini.loader import Loader +from infini.input import Input +from pathlib import Path +from ipm import api +from ipm.models.ipk import InfiniProject + +ipk = InfiniProject() +api.build(".") +api.install(f"dist/{ipk.default_name}", force=True) + +loader = Loader() +loader.load("ndice") +loader.close() + +core = loader.into_core() +for output in core.input(Input(".r20#d6")): + print(output) |
