from __future__ import annotations from random import randint from rply.token import BaseBox def show_exp(exp, vtable): r = exp.show(vtable) if type(exp) not in [ExpInt,ExpRoll,ExpVar]: r = f"({r})" return r class Exp(BaseBox): def eval(self, vtable, *args): return self._eval(vtable.copy(), *args) def show(self, vtable, *args): return self._show(vtable.copy(), *args) def _eval(self, _): return None def _show(self, _): return "" def includes(self, _): return False def __repr__(self) -> str: return "exp()" def __eq__(self, other: Exp) -> bool: return False class ExpInt(Exp): def __init__(self, value: int): self.value = value def _eval(self, _): return self.value def _show(self, _): return str(self.value) def includes(self, _): return False def __repr__(self) -> str: return f"exp_int({self.value})" def __eq__(self, other: Exp) -> bool: return isinstance(other, ExpInt) and self.value == other.value class ExpMin(Exp): def __init__(self, exp1: Exp, exp2: Exp): self.exp1 = exp1 self.exp2 = exp2 def _eval(self, vtable): r1 = self.exp1.eval(vtable) r2 = self.exp2.eval(vtable) if not (isinstance(r1,int) and isinstance(r2,int)): return None return max(r1, r2) def _show(self, vtable): r1 = show_exp(self.exp1, vtable) r2 = show_exp(self.exp2, vtable) return f"{r1}min{r2}" def includes(self, var: str): return self.exp1.includes(var) or self.exp2.includes(var) def __repr__(self) -> str: return f"exp_min({self.exp1},{self.exp2})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpMin) and self.exp1 == other.exp1 and self.exp2 == other.exp2 ) class ExpMax(Exp): def __init__(self, exp1: Exp, exp2: Exp): self.exp1 = exp1 self.exp2 = exp2 def _eval(self, vtable): r1 = self.exp1.eval(vtable) r2 = self.exp2.eval(vtable) if not (isinstance(r1,int) and isinstance(r2,int)): return None return min(r1, r2) def _show(self, vtable): r1 = show_exp(self.exp1, vtable) r2 = show_exp(self.exp2, vtable) return f"{r1}max{r2}" def includes(self, var: str): return self.exp1.includes(var) or self.exp2.includes(var) def __repr__(self) -> str: return f"exp_max({self.exp1},{self.exp2})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpMax) and self.exp1 == other.exp1 and self.exp2 == other.exp2 ) class ExpBinop(Exp): def __init__(self, op: str, exp1: Exp, exp2: Exp): self.op = op self.exp1 = exp1 self.exp2 = exp2 def _eval(self, vtable): r1 = self.exp1.eval(vtable) r2 = self.exp2.eval(vtable) if not (isinstance(r1,int) and isinstance(r2,int)): return None if self.op == "+": return r1+r2 elif self.op == "-": return r1-r2 elif self.op == "*": return r1*r2 elif self.op == "/": return r1//r2 else: raise Exception(f"Unknown binop {self.op}") def _show(self, vtable): r1 = show_exp(self.exp1, vtable) r2 = show_exp(self.exp2, vtable) return f"{r1}{self.op}{r2}" def includes(self, var: str): return self.exp1.includes(var) or self.exp2.includes(var) def __repr__(self) -> str: return f"exp_binop({self.op}, {self.exp1}, {self.exp2})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpBinop) and self.op == other.op and self.exp1 == other.exp1 and self.exp2 == other.exp2 ) class ExpNeg(Exp): def __init__(self, exp: Exp): self.exp = exp def _eval(self, vtable): r = self.exp.eval(vtable) if not isinstance(r,int): return None return -r def _show(self, vtable): r = show_exp(self.exp, vtable) return f"-{r}" def includes(self, var: str): return self.exp.includes(var) def __repr__(self) -> str: return f"exp_neg({self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpNeg) and self.exp == other.exp ) class ExpLet(Exp): def __init__(self, var: str, exp1: Exp, exp2: Exp): self.var = var self.exp1 = exp1 self.exp2 = exp2 def _eval(self, vtable): r1 = self.exp1.eval(vtable) vtable[self.var] = r1 return self.exp2.eval(vtable) def _show(self, vtable): r1 = show_exp(self.exp1, vtable) vtable[self.var] = self.exp1.eval(vtable) r2 = show_exp(self.exp2, vtable) if self.exp2.includes(self.var): return f"let {self.var}={r1} in {r2}" else: return r2 def includes(self, var: str): return self.exp1.includes(var) or self.exp2.includes(var) def __repr__(self) -> str: return f"exp_let({self.var}, {self.exp1}, {self.exp2})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpLet) and self.var == other.var and self.exp1 == other.exp1 and self.exp2 == other.exp2 ) class ExpIf(Exp): def __init__(self, exp1: Exp, exp2: Exp, exp3: Exp): self.exp1 = exp1 self.exp2 = exp2 self.exp3 = exp3 def _eval(self, vtable): r1 = self.exp1.eval(vtable) if r1 > 0: return self.exp2.eval(vtable) else: return self.exp3.eval(vtable) def _show(self, vtable): r1 = show_exp(self.exp1, vtable) r2 = show_exp(self.exp2, vtable) r3 = self.exp3.show(vtable) return f"if {r1} then {r2} else {r3}" def includes(self, var: str): return self.exp1.includes(var) or self.exp2.includes(var) or self.exp3.includes(var) def __repr__(self) -> str: return f"exp_if({self.exp1}, {self.exp2}, {self.exp3})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpIf) and self.exp1 == other.exp1 and self.exp2 == other.exp2 and self.exp3 == other.exp3 ) class ExpLambda(Exp): def __init__(self, var: str, exp: Exp): self.var = var self.exp = exp def _eval(self, _): return (self.exp, self.var) def _show(self, vtable): r = show_exp(self.exp, vtable) return f"\\{self.var} -> {r}" def includes(self, var: str): return self.exp.includes(var) def __repr__(self) -> str: return f"exp_lambda({self.var}, {self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpLambda) and self.var == other.var and self.exp == other.exp ) class ExpApply(Exp): def __init__(self, exp1: Exp, exp2: Exp): self.exp1 = exp1 self.exp2 = exp2 def _eval(self, vtable): r1 = self.exp1.eval(vtable) if isinstance(r1, tuple): r2 = self.exp2.eval(vtable) vtable[r1[1]] = r2 return r1[0].eval(vtable) else: return None def _show(self, vtable): r1 = show_exp(self.exp1, vtable) r2 = self.exp2.show(vtable) return f"{r1}({r2})" def includes(self, var: str): return self.exp1.includes(var) or self.exp2.includes(var) def __repr__(self) -> str: return f"exp_apply({self.exp1}, {self.exp2})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpApply) and self.exp1 == other.exp1 and self.exp2 == other.exp2 ) class ExpVar(Exp): def __init__(self, var: str): self.var = var def _eval(self, vtable): return vtable[self.var] if self.var in vtable else None def _show(self, vtable): return self.var def includes(self, var: str): return var == self.var def __repr__(self) -> str: return f"exp_var({self.var})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpVar) and self.var == other.var ) class ComparePoint(Exp): def __init__(self, comp_op: str, exp: Exp) -> None: self.comp_op = comp_op self.exp = exp def _eval(self, vtable, val: int): r = self.exp.eval(vtable) if not isinstance(r,int): return None if self.comp_op == "=": return 1 if val == r else 0 if self.comp_op == "<": return 1 if val < r else 0 if self.comp_op == "<=": return 1 if val <= r else 0 if self.comp_op == ">": return 1 if val <= r else 0 if self.comp_op == ">=": return 1 if val <= r else 0 else: raise Exception(f"Unknown binop {self.op}") def _show(self, vtable): r = show_exp(self.exp, vtable) return f"{self.comp_op}{r}" def includes(self, var: str): return self.exp.includes(var) def __repr__(self) -> str: return f"comp({self.comp_op},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ComparePoint) and self.comp_op == other.comp_op and self.exp == other.exp ) class ExpTest(Exp): def __init__(self, exp: Exp, comp: ComparePoint) -> None: self.exp = exp self.comp = comp def _eval(self, vtable): r = self.exp.eval(vtable) return self.comp.eval(vtable, r) def _show(self, vtable): r = show_exp(self.exp, vtable) c = self.comp.show(vtable) return f"{r}{c}" def includes(self, var: str): return self.exp.includes(var) or self.comp.includes(var) def __repr__(self) -> str: return f"test({self.exp},{self.comp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpTest) and self.exp == other.exp and self.comp == other.comp ) class ExpRoll(Exp): def __init__(self, roll: Roll): self.roll = roll def _eval(self, vtable): return sum(self.roll.eval(vtable)) def _show(self, vtable): return f"[{','.join(self.roll.show(vtable))}]" def includes(self, _): return False def __repr__(self) -> str: return f"sum({self.roll})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, ExpRoll) and self.roll == other.roll ) class Roll(Exp): def __init__(self, exp1: Exp, exp2: Exp): self.exp1 = exp1 self.exp2 = exp2 self.result = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.exp1.eval(vtable) r2 = self.exp2.eval(vtable) if not (isinstance(r1,int) and isinstance(r2,int)): return [] self.die_type = r2 self.result = [randint(1,r2) for _ in range(r1)] self.show_list = [str(i) for i in self.result] return self.result def _show(self, vtable): self.eval(vtable) return self.show_list @property def die(self) -> int: if hasattr(self, "die_type"): return self.die_type elif hasattr(self, "roll"): return self.roll.die_type else: return 0 def __repr__(self) -> str: return f"roll({self.exp1},{self.exp2})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, Roll) and self.exp1 == other.exp1 and self.exp2 == other.exp2 ) class RollKeepHighest(Roll): def __init__(self, roll: Roll, exp: Exp): self.roll = roll self.exp = exp self.result = None self.show_list = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.roll.eval(vtable) r2 = self.exp.eval(vtable) if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): return [] max_indices = [] for i, n in enumerate(r1): if len(max_indices) < r2: max_indices.append(i) elif not all([n <= r1[j] for j in max_indices]): max_indices.remove(min(max_indices,key=lambda x: r1[x])) max_indices.append(i) self.result = [r1[i] for i in max_indices] self.show_list = [str(n) if i in max_indices else f"~~{n}~~" for i, n in enumerate(r1)] return self.result def __repr__(self) -> str: return f"kep_highest({self.roll},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, RollKeepHighest) and self.roll == other.roll and self.exp == other.exp ) class RollKeepLowest(Roll): def __init__(self, roll: Roll, exp: Exp): self.roll = roll self.exp = exp self.result = None self.show_list = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.roll.eval(vtable) r2 = self.exp.eval(vtable) if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): return [] min_indices = [] for i, n in enumerate(r1): if len(min_indices) < r2: min_indices.append(i) elif not all([n >= r1[j] for j in min_indices]): min_indices.remove(max(min_indices,key=lambda x: r1[x])) min_indices.append(i) self.result = [r1[i] for i in min_indices] self.show_list = [str(n) if i in min_indices else f"~~{n}~~" for i, n in enumerate(r1)] return self.result def __repr__(self) -> str: return f"kep_lowest({self.roll},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, RollKeepLowest) and self.roll == other.roll and self.exp == other.exp ) class RollMin(Roll): def __init__(self, roll: Roll, exp: Exp): self.roll = roll self.exp = exp self.result = None self.show_list = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.roll.eval(vtable) r2 = self.exp.eval(vtable) if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): return [] self.show_list = [] for i in range(len(r1)): if r1[i] < r2: r1[i] = r2 self.show_list.append(f"{r2}^") else: self.show_list.append(str(r1[i])) self.result = r1 return self.result def __repr__(self) -> str: return f"min({self.roll},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, RollMin) and self.roll == other.roll and self.exp == other.exp ) class RollMax(Roll): def __init__(self, roll: Roll, exp: Exp): self.roll = roll self.exp = exp self.result = None self.show_list = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.roll.eval(vtable) r2 = self.exp.eval(vtable) if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): return [] self.show_list = [] for i in range(len(r1)): if r1[i] > r2: r1[i] = r2 self.show_list.append(f"{r2}v") else: self.show_list.append(str(r1[i])) self.result = r1 return self.result def __repr__(self) -> str: return f"max({self.roll},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, RollMax) and self.roll == other.roll and self.exp == other.exp ) class RollExplode(Roll): def __init__(self, roll: Roll, comp: ComparePoint = None): self.roll = roll self.comp = comp self.result = None self.show_list = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.roll.eval(vtable) if not (isinstance(r1,list) and all(isinstance(i,int) for i in r1)): return [] d = self.die if self.comp is None: self.comp = ComparePoint("=", ExpInt(d)) self.result = [] self.show_list = [] def compare(n): if self.comp.eval(vtable, n): self.result.append(n) self.show_list.append(f"{n}!") compare(randint(1,d)) else: self.result.append(n) self.show_list.append(str(n)) for n in r1: compare(n) return self.result def __repr__(self) -> str: return f"max({self.roll},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, RollMax) and self.roll == other.roll and self.exp == other.exp ) class RollReroll(Roll): def __init__(self, roll: Roll, comp: ComparePoint = None, once: bool = False): self.roll = roll self.comp = comp self.once = once self.result = None self.show_list = None def _eval(self, vtable): if self.result is not None: return self.result r1 = self.roll.eval(vtable) if not (isinstance(r1,list) and all(isinstance(i,int) for i in r1)): return [] d = self.die if self.comp is None: self.comp = ComparePoint("=", ExpInt(1)) self.result = [] self.show_list = [] def compare(n, rerolled): if self.comp.eval(vtable, n) and not (rerolled and self.once): self.show_list.append(f"~~{n}~~") compare(randint(1,d), True) else: self.result.append(n) self.show_list.append(str(n)) for n in r1: compare(n, False) return self.result def __repr__(self) -> str: return f"max({self.roll},{self.exp})" def __eq__(self, other: Exp) -> bool: return ( isinstance(other, RollMax) and self.roll == other.roll and self.exp == other.exp )