aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author苏向夜 <fu050409@163.com>2024-01-27 22:28:09 +0800
committer苏向夜 <fu050409@163.com>2024-01-27 22:28:09 +0800
commit63d3232e9765553e9e5418542d890977fc14621b (patch)
treec872494a383e444710ae6740423d6da6278f60e4
parent1d9a500b727e8879cdaeb9c378ac717001c56ccc (diff)
downloadinfini-63d3232e9765553e9e5418542d890977fc14621b.tar.gz
infini-63d3232e9765553e9e5418542d890977fc14621b.zip
:sparkles: test(ci): add ndice package as example tests
-rw-r--r--tests/examples/ndice/infini.toml9
-rw-r--r--tests/examples/ndice/src/dicer.py360
-rw-r--r--tests/examples/ndice/src/ndice.py156
-rw-r--r--tests/examples/ndice/tests.py17
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)