def create_response(message: Message) -> Optional[Response]: regex_result = parse(message.content) if regex_result: try: result, rolls = calc(regex_result.group("calc") or "0") response = " {comment}\n{results}\nErgebnis: **{FP}**".format( comment=regex_result.group("comment").strip(), results=(" ").join([ f"{len(roll_list)}d{sides}: [{' + '.join([str(roll) for roll in roll_list])}]" for sides, roll_list in rolls ]), FP=int(result), ) for sides, roll_list in rolls: save_check( message.author, "DiceRoll", [int(roll) for roll in roll_list], sides, ) return Response(message.channel.send, message.author.mention + response) except: return None return None
def test_calc(self): self.assertEqual(calc("1"), (1, [])) self.assertEqual(calc("1+1"), (2, [])) self.assertEqual(calc("1-1"), (0, [])) self.assertEqual(calc("-1"), (-1, [])) self.assertEqual(calc("(2 + 1 * 2) / 2"), (2, [])) self.assertEqual(calc("+ ((4 + -1 * 2) / 2) * (1+-3/4) "), (0.25, []))
class SkillCheck(GenericCheck): matcher = re.compile( r""" ^!?\ ? # Optional exclamation mark (?P<force>f(?:orce)?\ )? # Check if force (?P<attributes>(?:[0-9]+,?\ ?){3})\ ? # A non-zero amount of numbers divided by comma or space (?:@\ ?(?P<SR>[0-9]+))\ ? # An @ followed by a number (?P<modifier>(\ *[\+\-]\ *[0-9]+)*) # A modifier (?P<modifierFP>(\ *[\+\-]\ *[0-9]+FP)*) # FP modifier (\ (?P<comment>.*?))?$ # Anything else is lazy-matched as a comment """, re.VERBOSE | re.I, ) transform = { **GenericCheck.transform, "SR": int, "modifierFP": lambda x: int(calc(x.replace("FP", "") or "0")[0]), "force": lambda x: x is not None, } _response = " {comment}\n```py\nEEW: {EAV}\nWürfel: {rolls}\nFW {SR_mod}{diffs} = {SP} FP\n{result}\n```" _routine = " {comment}\n```py\nRoutineprobe: {SP} FP = QS {QL}\n```" @property def diffs(self) -> List[int]: return [ min([eav - roll, 0]) for eav, roll in zip(self.data["EAV"], self.data["rolls"]) ] @property def skill_points(self) -> int: return self.data["SR"] + sum(self.diffs) + self.data["modifierFP"] @property def routine(self) -> bool: attr_ge_13 = all([attr >= 13 for attr in self.data["attributes"]]) sr_ge_10 = self.data["SR"] >= 10 + 3 * -self.data["modifier"] return attr_ge_13 and sr_ge_10 def force(self) -> None: self._force = True def ql(self, skill_points: int) -> int: return min([max([skill_points - 1, 0]) // 3 + 1, 6]) def __str__(self) -> str: if (self.routine and not getattr(self, "_force", False) and not self.data["force"]): self._force = False sp = self.data["SR"] - self.data["SR"] // 2 # Rounds up return self._routine.format(**self.data, SP=sp, QL=self.ql(sp)) else: self.data["diffs"] = "".join("{:>4}".format(d or "") for d in self.diffs) self.data["SP"] = self.skill_points if self.data["modifierFP"]: mod = f"{self.data['modifierFP']:+d}" self.data["SR_mod"] = f"{self.data['SR']:<2}{mod:<3}" else: self.data["SR_mod"] = f"{self.data['SR']:<5}" return super().__str__() def _get_result(self) -> str: if self.skill_points < 0 and self.data["rolls"].critical_success: return "Kritischer Erfolg! - Automatisch bestanden" if self.skill_points < 0 and self.data["rolls"].botch: return "Patzer!" if self.skill_points < 0: return "Nicht bestanden" if self.skill_points >= 0 and self.data["rolls"].botch: return "Patzer! - Automatisch nicht bestanden" if self.skill_points >= 0 and self.data["rolls"].critical_success: return "Kritischer Erfolg! (QS {})".format( self.ql(self.skill_points)) else: return "Bestanden mit QS {}".format(self.ql(self.skill_points))
def test_with_dice(self, mock_randint: MagicMock): mock_randint.return_value = 1 self.assertEqual(calc("1d6"), (1, [(6, [1])])) self.assertEqual(calc("14+1d6"), (15, [(6, [1])])) self.assertEqual(calc("1d6+14"), (15, [(6, [1])])) self.assertEqual(calc("5+1w6+2"), (8, [(6, [1])])) self.assertEqual(calc("3w20 + 1 - 4"), (0, [(20, [1, 1, 1])])) self.assertEqual(calc("1337d1337")[0], 1337) self.assertEqual(calc("4W20"), (4, [(20, [1, 1, 1, 1])])) self.assertEqual(calc("w3"), (1, [(3, [1])])) self.assertEqual(calc("-w6 "), (-1, [(6, [1])])) self.assertEqual( calc("1w6+2W6+3d6+4D6"), ( 10, [ (6, [1]), (6, [1, 1]), (6, [1, 1, 1]), (6, [1, 1, 1, 1]), ], ), ) mock_randint.return_value = 2 self.assertEqual( calc("(3*2w6-4)d(1W6+2)"), (16, [(6, [2, 2]), (6, [2]), (4, [2, 2, 2, 2, 2, 2, 2, 2])]), )
class GenericCheck: matcher = re.compile( r""" ^!?\ ? # Optional exclamation mark (?P<attributes>(?:[0-9]+,?\ ?)+)\ ? # A non-zero amount of numbers divided by comma or space (?P<modifier>(\ *[\+\-]\ *[0-9]+)*) # A modifier (\ (?P<comment>.*?))?$ # Anything else is lazy-matched as a comment """, re.VERBOSE | re.I, ) transform: dict[str, Any] = { "attributes": lambda x: Attributes( [int(attr) for attr in re.split(r"[, ]+", x.strip(", "))]), "modifier": lambda x: int(calc(x or "0")[0]), "comment": lambda x: (x or "").strip(), } _response = " {comment}\n```py\nEEW: {EAV}\nWürfel:{rolls}\n{result}\n```" _impossible = " {comment}\n```py\nEEW:{EAV}\nProbe nicht möglich\n```" def impossible(self) -> bool: return any(int(eav) <= 0 for eav in self.data["EAV"]) def recalculate(self) -> None: self.data["EAV"] = Attributes( [attr + self.data["modifier"] for attr in self.data["attributes"]]) self.data["rolls"] = CheckRolls(len(self.data["attributes"])) def __init__(self, author: Member, message: str): parsed = self.matcher.search(message) self.author = author if parsed: self.data = {} for name, value in parsed.groupdict().items(): self.data[name] = self.transform[name](value) self.recalculate() else: raise ValueError def __str__(self) -> str: if self.impossible(): return self._impossible.format(**self.data) save_check(self.author, self.__class__.__name__, self.data["rolls"], 20) return self._response.format( **self.data, result=self._get_result(), ) def _get_result(self) -> str: rolls: type[CheckRolls] = self.data["rolls"] if rolls.critical_success: return "Kritischer Erfolg!" if rolls.botch: return "Patzer!" if all(roll <= eav for roll, eav in zip(rolls, self.data["EAV"])): return "Bestanden" else: return "Nicht bestanden"