From aa47cc139c2535b40ee4d40b96e9485ead54d359 Mon Sep 17 00:00:00 2001 From: Natalie Fearnley Date: Mon, 20 May 2024 22:06:40 -0400 Subject: [PATCH] cleaned up stats.py --- sizebot/cogs/help.py | 4 +- sizebot/lib/fakeplayer.py | 4 +- sizebot/lib/macrovision.py | 1 - sizebot/lib/proportions.py | 16 ++--- sizebot/lib/statproxy.py | 7 ++- sizebot/lib/stats.py | 122 ++++++++++++++++--------------------- sizebot/lib/utils.py | 21 +++++-- 7 files changed, 81 insertions(+), 94 deletions(-) diff --git a/sizebot/cogs/help.py b/sizebot/cogs/help.py index c3cb8fd6..a4191ff5 100644 --- a/sizebot/cogs/help.py +++ b/sizebot/cogs/help.py @@ -16,7 +16,7 @@ from sizebot.lib import checks, userdb, utils from sizebot.lib.constants import colors, emojis from sizebot.lib.menu import Menu -from sizebot.lib.stats import statmap +from sizebot.lib.stats import all_stats from sizebot.lib.types import BotContext from sizebot.lib.units import SV, WV @@ -41,7 +41,7 @@ alpha_warning = f"{emojis.warning} **This command is in ALPHA.** It may break, be borked, change randomly, be removed randomly, or be deprecated at any time. Proceed with caution." accuracy_warning = f"{emojis.warning} **This command may not be entirely accurate.** It makes assumptions and guesses that have a decent amount of wiggle room, even because the information isn't known or because the calculations are meant to be for fiction only. Don't take these results at face value!" -stats_string = utils.sentence_join([f"`{v}`" for v in statmap.keys()]) +stats_string = utils.sentence_join([f"`{s.key}`" for s in all_stats]) async def post_report(report_type: str, message: discord.Message, report_text: str): diff --git a/sizebot/lib/fakeplayer.py b/sizebot/lib/fakeplayer.py index 9bdbc693..28f7da13 100644 --- a/sizebot/lib/fakeplayer.py +++ b/sizebot/lib/fakeplayer.py @@ -13,7 +13,7 @@ from sizebot.lib.types import BotContext from sizebot.lib.units import SV, WV from sizebot.lib.utils import parse_scale -from sizebot.lib.stats import HOUR, statmap +from sizebot.lib.stats import HOUR, get_mapped_stat T = TypeVar("T") @@ -69,7 +69,7 @@ def parse_keyvalue(kv_str: str) -> tuple[str, Any]: key, val_str = m.groups() # Special exception for shoesize where we _actually_ set footlength if key != "shoesize": - key = statmap.get(key, None) + key = get_mapped_stat(key) if key not in fakestats: raise InvalidStat(key) stat = fakestats[key] diff --git a/sizebot/lib/macrovision.py b/sizebot/lib/macrovision.py index e2ffebf4..0ce209b3 100644 --- a/sizebot/lib/macrovision.py +++ b/sizebot/lib/macrovision.py @@ -11,7 +11,6 @@ import sizebot.data from sizebot.conf import conf -from sizebot.lib import errors from sizebot.lib.digidecimal import Decimal from sizebot.lib.stats import StatBox from sizebot.lib.units import SV diff --git a/sizebot/lib/proportions.py b/sizebot/lib/proportions.py index d7bd3037..80282b1a 100644 --- a/sizebot/lib/proportions.py +++ b/sizebot/lib/proportions.py @@ -11,7 +11,7 @@ from sizebot.lib.types import EmbedField, EmbedToSend, StrToSend from sizebot.lib.units import SV, TV, WV from sizebot.lib.userdb import User -from sizebot.lib.stats import Stat, calc_view_angle, statmap, StatBox +from sizebot.lib.stats import Stat, calc_view_angle, get_mapped_stat, StatBox logger = logging.getLogger("sizebot") @@ -40,7 +40,7 @@ def get_speedcompare(userdata1: User, userdata2: User, requesterID: int) -> Embe # stat with stat.key=key def get_speedcompare_stat(userdata1: User, userdata2: User, key: str) -> EmbedToSend | None: small, big, _, _, _ = _get_compare_statboxes(userdata1, userdata2) - mapped_key = _get_mapped_stat(key) + mapped_key = get_mapped_stat(key) if mapped_key is None: return None stat = big[mapped_key] @@ -161,7 +161,7 @@ def get_compare_bytag(userdata1: User, userdata2: User, tag: str, requesterID: i # stat with stat.key=key def get_compare_stat(userdata1: User, userdata2: User, key: str) -> StrToSend | None: small, big, small_viewby_big, big_viewby_small, _ = _get_compare_statboxes(userdata1, userdata2) - mapped_key = _get_mapped_stat(key) + mapped_key = get_mapped_stat(key) if mapped_key is None: return None small_stat = small_viewby_big[mapped_key] @@ -210,7 +210,7 @@ def get_stats_bytag(userdata: User, tag: str, requesterID: int) -> EmbedToSend: # stat with stat.key=key def get_stat(userdata: User, key: str) -> StrToSend | None: - mapped_key = _get_mapped_stat(key) + mapped_key = get_mapped_stat(key) if mapped_key is None: return None stats = StatBox.load(userdata.stats).scale(userdata.scale) @@ -325,14 +325,6 @@ def _get_compare_color(requesterID: int, small: StatBox, big: StatBox) -> str: return colors.purple -def _get_mapped_stat(key: str) -> str | None: - try: - mapped_key = statmap[key] - except KeyError: - return None - return mapped_key - - def _get_compare_statboxes(userdata1: User, userdata2: User) -> tuple[StatBox, StatBox, StatBox, StatBox, Decimal]: stats1 = StatBox.load(userdata1.stats).scale(userdata1.scale) stats2 = StatBox.load(userdata2.stats).scale(userdata2.scale) diff --git a/sizebot/lib/statproxy.py b/sizebot/lib/statproxy.py index 27c8849a..331a1b32 100644 --- a/sizebot/lib/statproxy.py +++ b/sizebot/lib/statproxy.py @@ -3,7 +3,7 @@ import logging from sizebot.lib.errors import InvalidStat, InvalidStatTag -from sizebot.lib.stats import statmap, taglist +from sizebot.lib.stats import get_mapped_stat, taglist from sizebot.lib.types import BotContext re_full_string = r"\$(\w+=[^;$]+;?)+" @@ -35,9 +35,10 @@ def parse(cls, s: str) -> StatProxy: raise InvalidStatTag(s) return StatProxy(s, True) else: - if s not in statmap.keys(): + mapped = get_mapped_stat(s) + if mapped is None: raise InvalidStat(s) - return StatProxy(statmap[s], False) + return StatProxy(mapped, False) @classmethod async def convert(cls, ctx: BotContext, argument: str) -> StatProxy: diff --git a/sizebot/lib/stats.py b/sizebot/lib/stats.py index daf6569b..e52b80c3 100644 --- a/sizebot/lib/stats.py +++ b/sizebot/lib/stats.py @@ -12,6 +12,7 @@ from sizebot.lib.units import SV, TV, WV, AV from sizebot.lib.shoesize import to_shoe_size from sizebot.lib.surface import can_walk_on_water +from sizebot.lib.utils import AliasMapper AVERAGE_HEIGHT = SV("1.754") # meters AVERAGE_WEIGHT = WV("66760") # grams @@ -61,7 +62,7 @@ class PlayerStats(TypedDict): macrovision_view: str | None -def format_scale(scale: Decimal) -> str: +def _format_scale(scale: Decimal) -> str: reversescale = 1 / scale if reversescale > 10: @@ -77,7 +78,7 @@ def format_scale(scale: Decimal) -> str: ValueDict = dict[str, Any] -def wrap_str(f: str | Callable[[StatBox], str]) -> Callable[[StatBox], str]: +def _wrap_str(f: str | Callable[[StatBox], str]) -> Callable[[StatBox], str]: if isinstance(f, str): def wrapped(sb: StatBox) -> str: return f.format(**sb.values) @@ -86,7 +87,7 @@ def wrapped(sb: StatBox) -> str: return f -def wrap_bool(f: bool | Callable[[StatBox], bool]) -> Callable[[StatBox], bool]: +def _wrap_bool(f: bool | Callable[[StatBox], bool]) -> Callable[[StatBox], bool]: if isinstance(f, bool): def wrapped(sb: StatBox) -> bool: return f @@ -95,7 +96,7 @@ def wrapped(sb: StatBox) -> bool: return f -def grouped_z(z: int | None) -> tuple[int, int]: +def _grouped_z(z: int | None) -> tuple[int, int]: if z is None: return (1, 0) elif z < 0: @@ -104,10 +105,7 @@ def grouped_z(z: int | None) -> tuple[int, int]: return (0, z) -T = TypeVar("T") - - -def wrap_type(f: Callable[[Any], T] | None) -> Callable[[Any], T | None]: +def _wrap_type[T](f: Callable[[Any], T] | None) -> Callable[[Any], T | None]: if f is None: def default_func(v: Any) -> Any: return v @@ -141,16 +139,16 @@ def __init__(self, if userkey is not None and power is None: raise Exception(f'StatDef("{key}") with userkey must have power set') self.key = key - self.get_title = wrap_str(title) - self.get_body = wrap_str(body) - self.get_string = wrap_str(string) - self.get_is_shown = wrap_bool(is_shown) + self.get_title = _wrap_str(title) + self.get_body = _wrap_str(body) + self.get_string = _wrap_str(string) + self.get_is_shown = _wrap_bool(is_shown) self.userkey = userkey self.get_value = value self.requires = requires or [] self.power = power - self.type = wrap_type(type) - self.orderkey = grouped_z(z) + self.type = _wrap_type(type) + self.orderkey = _grouped_z(z) self.tags = tags or [] self.inline = inline self.aliases = aliases or [] @@ -272,7 +270,7 @@ def __str__(self) -> str: OUT = TypeVar('OUT') -def process_queue(source: list[IN], _process: Callable[[IN], None | OUT]) -> list[OUT]: +def _process_queue(source: list[IN], _process: Callable[[IN], None | OUT]) -> list[OUT]: queued: list[Any] = source.copy() processing: list[Any] = [] processed: list[Any] = [] @@ -316,7 +314,7 @@ def _process(sdef: StatDef) -> Stat | None: if result is not None: values[result.key] = result.value return result - stats = process_queue(all_stats, _process) + stats = _process_queue(all_stats, _process) sb.set_stats(stats) return sb @@ -352,7 +350,7 @@ def _process(s: Stat) -> Stat | None: if result is not None: values[result.key] = result.value return result - stats = process_queue(self.stats, _process) + stats = _process_queue(self.stats, _process) sb.set_stats(stats) return sb @@ -364,15 +362,6 @@ def __getitem__(self, k: str) -> Stat: return self.stats_by_key[k] -def bool_to_icon(value: bool) -> str: - CHK_Y = "✅" - CHK_N = "❎" - if value: - return CHK_Y - else: - return CHK_N - - all_stats = [ StatDef("id", title="ID", @@ -401,24 +390,24 @@ def bool_to_icon(value: bool) -> str: aliases=["nick", "name"]), StatDef("scale", title="Scale", - string=lambda s: format_scale(s['scale'].value), - body=lambda s: format_scale(s['scale'].value), + string=lambda s: _format_scale(s['scale'].value), + body=lambda s: _format_scale(s['scale'].value), is_shown=False, type=Decimal, power=1, value=lambda v: 1), StatDef("viewscale", title="Viewscale", - string=lambda s: format_scale(s['viewscale'].value), - body=lambda s: format_scale(s['viewscale'].value), + string=lambda s: _format_scale(s['viewscale'].value), + body=lambda s: _format_scale(s['viewscale'].value), is_shown=False, requires=["scale"], type=Decimal, value=lambda v: 1 / v["scale"]), StatDef("squarescale", title="Square Scale", - string=lambda s: format_scale(s['squarescale'].value), - body=lambda s: format_scale(s['squarescale'].value), + string=lambda s: _format_scale(s['squarescale'].value), + body=lambda s: _format_scale(s['squarescale'].value), is_shown=False, requires=["scale"], power=2, @@ -426,8 +415,8 @@ def bool_to_icon(value: bool) -> str: value=lambda v: v["scale"] ** 2), StatDef("cubescale", title="Cube Scale", - string=lambda s: format_scale(s['cubescale'].value), - body=lambda s: format_scale(s['cubescale'].value), + string=lambda s: _format_scale(s['cubescale'].value), + body=lambda s: _format_scale(s['cubescale'].value), is_shown=False, requires=["scale"], power=3, @@ -790,7 +779,7 @@ def bool_to_icon(value: bool) -> str: is_shown=False, requires=["height"], type=SV, - value=lambda v: calcHorizon(v["height"]), + value=lambda v: _calc_horizon(v["height"]), aliases=["horizon"]), StatDef("dragcoefficient", title="Drag Coefficient", @@ -812,15 +801,15 @@ def bool_to_icon(value: bool) -> str: StatDef("fallproof", title="Fallproof", string=lambda s: f"""{s['nickname'].value} {'is' if s['fallproof'].value else "isn't"} fallproof.""", - body=lambda s: bool_to_icon(s['fallproof'].value), + body=lambda s: _bool_to_icon(s['fallproof'].value), is_shown=False, requires=["terminalvelocity"], type=bool, value=lambda v: v["terminalvelocity"] < FALL_LIMIT), StatDef("fallprooficon", title="Fallproof Icon", - string=lambda s: bool_to_icon(s['fallproof']), - body=lambda s: bool_to_icon(s['fallproof'].value), + string=lambda s: _bool_to_icon(s['fallproof']), + body=lambda s: _bool_to_icon(s['fallproof'].value), is_shown=False, requires=["fallproof"], type=str, @@ -919,7 +908,7 @@ def bool_to_icon(value: bool) -> str: is_shown=lambda s: s["scale"].value < 1, requires=["height"], type=str, - value=lambda v: calcVisibility(v["height"]), + value=lambda v: _calc_visibility(v["height"]), aliases=["visible", "see"]), StatDef("eyeheight", title="Eye Height", @@ -1059,7 +1048,7 @@ def bool_to_icon(value: bool) -> str: StatDef("pawtoggle", title="Paw Toggle", string="{nickname}'s paw toggle is {pawtoggle}.", - body=lambda s: bool_to_icon(s['pawtoggle'].value), + body=lambda s: _bool_to_icon(s['pawtoggle'].value), is_shown=False, type=bool, power=0, @@ -1067,7 +1056,7 @@ def bool_to_icon(value: bool) -> str: StatDef("furtoggle", title="Fur Toggle", string="{nickname}'s fur toggle is {furtoggle}.", - body=lambda s: bool_to_icon(s['furtoggle'].value), + body=lambda s: _bool_to_icon(s['furtoggle'].value), is_shown=False, type=bool, power=0, @@ -1089,7 +1078,7 @@ def bool_to_icon(value: bool) -> str: StatDef("walkonwater", title="Can Walk On Water", string=lambda s: f"""{s['nickname'].value} {'can' if s['walkonwater'].value else "can't"} walk on water.""", - body=lambda s: bool_to_icon(s['walkonwater'].value), + body=lambda s: _bool_to_icon(s['walkonwater'].value), requires=["weight", "footlength", "footwidth"], type=bool, is_shown=False, @@ -1133,33 +1122,23 @@ def bool_to_icon(value: bool) -> str: ] -def generate_statmap() -> dict[str, str]: - statmap = {} - # Load all keys +def _generate_taglist() -> list[str]: + tags = set() for stat in all_stats: - if stat.key in statmap: - raise Exception(f"Duplicate key: {stat.key}") - statmap[stat.key] = stat.key - # Load all aliases - for stat in all_stats: - for alias in stat.aliases: - if alias in statmap: - raise Exception(f"Duplicate alias: {alias}") - statmap[alias] = stat.key - return statmap + tags.update(stat.tags) + return sorted(tags) -def generate_taglist() -> list[str]: - tags = [] - for stat in all_stats: - if stat.tags: - tags.extend(stat.tags) - tags = list(set(tags)) - return tags +_statmap = AliasMapper({s.key: s.aliases for s in all_stats}) +taglist = _generate_taglist() -statmap = generate_statmap() -taglist = generate_taglist() +def get_mapped_stat(key: str) -> str | None: + try: + mapped_key = _statmap[key] + except KeyError: + return None + return mapped_key def calc_view_angle(viewer: SV, viewee: SV) -> Decimal: @@ -1175,14 +1154,12 @@ def calc_view_angle(viewer: SV, viewee: SV) -> Decimal: return viewangle -# TODO: CamelCase -def calcHorizon(height: SV) -> SV: +def _calc_horizon(height: SV) -> SV: EARTH_RADIUS = 6378137 return SV(math.sqrt((EARTH_RADIUS + height) ** 2 - EARTH_RADIUS ** 2)) -# TODO: CamelCase -def calcVisibility(height: SV) -> str: +def _calc_visibility(height: SV) -> str: if height < SV(0.000001): visibility = "magic" elif height < SV(0.00005): @@ -1192,3 +1169,12 @@ def calcVisibility(height: SV) -> str: else: visibility = "only the naked eye" return visibility + + +def _bool_to_icon(value: bool) -> str: + CHK_Y = "✅" + CHK_N = "❎" + if value: + return CHK_Y + else: + return CHK_N diff --git a/sizebot/lib/utils.py b/sizebot/lib/utils.py index 664fcc97..bd9bf0ad 100644 --- a/sizebot/lib/utils.py +++ b/sizebot/lib/utils.py @@ -267,8 +267,8 @@ def round_fraction(number: Decimal, denominator: int) -> Decimal: class AliasMapper(Generic[T]): - def __init__(self, aliases: AliasList[T]): - self._map = map_aliases(aliases) + def __init__(self, aliases: AliasList[T], *, unique: bool = True): + self._map = map_aliases(aliases, unique=unique) def __getitem__(self, key: str) -> T: return self._map[key.lower()] @@ -277,10 +277,19 @@ def __contains__(self, key: str) -> bool: return key.lower() in self._map -def map_aliases[T: str](alias_dict: AliasList[T]) -> AliasMap[T]: +def map_aliases[T: str](alias_dict: AliasList[T], *, unique: bool = True) -> AliasMap[T]: aliasmap: AliasMap[T] = {} + for key, aliases in alias_dict: - aliasmap[key.lower()] = key.lower() + k = key.lower() + if unique and k in aliasmap: + raise ValueError(f"Duplicate key: {k}") + aliasmap[k] = k + for alias in aliases: - aliasmap[alias.lower()] = key.lower() - return aliasmap \ No newline at end of file + a = alias.lower() + if unique and a in aliasmap: + raise ValueError(f"Duplicate key: {a}") + aliasmap[a] = k + + return aliasmap