From f359322b6cbc2bf1950ef41a5a294a335b22a416 Mon Sep 17 00:00:00 2001 From: 283375 Date: Thu, 4 Apr 2024 18:10:53 +0800 Subject: [PATCH] refactor!: calculate -> calculators --- src/arcaea_offline/calculate/__init__.py | 8 - src/arcaea_offline/calculate/b30.py | 24 --- src/arcaea_offline/calculate/score.py | 66 ------- src/arcaea_offline/calculate/world_step.py | 174 ------------------ src/arcaea_offline/calculators/__init__.py | 2 + src/arcaea_offline/calculators/play_result.py | 105 +++++++++++ .../calculators/world/__init__.py | 11 ++ .../calculators/world/_common.py | 50 +++++ .../calculators/world/legacy.py | 45 +++++ src/arcaea_offline/calculators/world/main.py | 57 ++++++ .../calculators/world/partners.py | 16 ++ tests/calculate/test_world_step.py | 22 --- tests/calculators/test_play_result.py | 42 +++++ tests/calculators/test_world.py | 74 ++++++++ 14 files changed, 402 insertions(+), 294 deletions(-) delete mode 100644 src/arcaea_offline/calculate/__init__.py delete mode 100644 src/arcaea_offline/calculate/b30.py delete mode 100644 src/arcaea_offline/calculate/score.py delete mode 100644 src/arcaea_offline/calculate/world_step.py create mode 100644 src/arcaea_offline/calculators/__init__.py create mode 100644 src/arcaea_offline/calculators/play_result.py create mode 100644 src/arcaea_offline/calculators/world/__init__.py create mode 100644 src/arcaea_offline/calculators/world/_common.py create mode 100644 src/arcaea_offline/calculators/world/legacy.py create mode 100644 src/arcaea_offline/calculators/world/main.py create mode 100644 src/arcaea_offline/calculators/world/partners.py delete mode 100644 tests/calculate/test_world_step.py create mode 100644 tests/calculators/test_play_result.py create mode 100644 tests/calculators/test_world.py diff --git a/src/arcaea_offline/calculate/__init__.py b/src/arcaea_offline/calculate/__init__.py deleted file mode 100644 index 5213cd2..0000000 --- a/src/arcaea_offline/calculate/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .b30 import calculate_b30, get_b30_calculated_list -from .score import ( - calculate_constants_from_play_rating, - calculate_play_rating, - calculate_score_modifier, - calculate_score_range, - calculate_shiny_pure, -) diff --git a/src/arcaea_offline/calculate/b30.py b/src/arcaea_offline/calculate/b30.py deleted file mode 100644 index 3d640db..0000000 --- a/src/arcaea_offline/calculate/b30.py +++ /dev/null @@ -1,24 +0,0 @@ -from decimal import Decimal -from typing import Dict, List - -from ..models.scores import ScoreCalculated - - -def get_b30_calculated_list( - calculated_list: List[ScoreCalculated], -) -> List[ScoreCalculated]: - best_scores: Dict[str, ScoreCalculated] = {} - for calculated in calculated_list: - key = f"{calculated.song_id}_{calculated.rating_class}" - stored = best_scores.get(key) - if stored and stored.score < calculated.score or not stored: - best_scores[key] = calculated - ret_list = list(best_scores.values()) - ret_list = sorted(ret_list, key=lambda c: c.potential, reverse=True)[:30] - return ret_list - - -def calculate_b30(calculated_list: List[ScoreCalculated]) -> Decimal: - ptt_list = [Decimal(c.potential) for c in get_b30_calculated_list(calculated_list)] - sum_ptt_list = sum(ptt_list) - return (sum_ptt_list / len(ptt_list)) if sum_ptt_list else Decimal("0.0") diff --git a/src/arcaea_offline/calculate/score.py b/src/arcaea_offline/calculate/score.py deleted file mode 100644 index f9c4764..0000000 --- a/src/arcaea_offline/calculate/score.py +++ /dev/null @@ -1,66 +0,0 @@ -from dataclasses import dataclass -from decimal import Decimal -from math import floor -from typing import Tuple, Union - - -def calculate_score_range(notes: int, pure: int, far: int): - single_note_score = 10000000 / Decimal(notes) - - actual_score = floor( - single_note_score * pure + single_note_score * Decimal(0.5) * far - ) - return (actual_score, actual_score + pure) - - -def calculate_score_modifier(score: int) -> Decimal: - if score >= 10000000: - return Decimal(2) - if score >= 9800000: - return Decimal(1) + (Decimal(score - 9800000) / 200000) - return Decimal(score - 9500000) / 300000 - - -def calculate_play_rating(constant: int, score: int) -> Decimal: - score_modifier = calculate_score_modifier(score) - return max(Decimal(0), Decimal(constant) / 10 + score_modifier) - - -def calculate_shiny_pure(notes: int, score: int, pure: int, far: int) -> int: - single_note_score = 10000000 / Decimal(notes) - actual_score = single_note_score * pure + single_note_score * Decimal(0.5) * far - return score - floor(actual_score) - - -@dataclass -class ConstantsFromPlayRatingResult: - # pylint: disable=invalid-name - EXPlus: Tuple[Decimal, Decimal] - EX: Tuple[Decimal, Decimal] - AA: Tuple[Decimal, Decimal] - A: Tuple[Decimal, Decimal] - B: Tuple[Decimal, Decimal] - C: Tuple[Decimal, Decimal] - - -def calculate_constants_from_play_rating(play_rating: Union[Decimal, str, float, int]): - # pylint: disable=no-value-for-parameter - - play_rating = Decimal(play_rating) - - ranges = [] - for upper_score, lower_score in [ - (10000000, 9900000), - (9899999, 9800000), - (9799999, 9500000), - (9499999, 9200000), - (9199999, 8900000), - (8899999, 8600000), - ]: - upper_score_modifier = calculate_score_modifier(upper_score) - lower_score_modifier = calculate_score_modifier(lower_score) - ranges.append( - (play_rating - upper_score_modifier, play_rating - lower_score_modifier) - ) - - return ConstantsFromPlayRatingResult(*ranges) diff --git a/src/arcaea_offline/calculate/world_step.py b/src/arcaea_offline/calculate/world_step.py deleted file mode 100644 index eb32db2..0000000 --- a/src/arcaea_offline/calculate/world_step.py +++ /dev/null @@ -1,174 +0,0 @@ -from decimal import Decimal -from typing import Literal, Optional, Union - - -class PlayResult: - def __init__( - self, - *, - play_rating: Union[Decimal, str, float, int], - partner_step: Union[Decimal, str, float, int], - ): - self.__play_rating = play_rating - self.__partner_step = partner_step - - @property - def play_rating(self): - return Decimal(self.__play_rating) - - @property - def partner_step(self): - return Decimal(self.__partner_step) - - -class PartnerBonus: - def __init__( - self, - *, - step_bonus: Union[Decimal, str, float, int] = Decimal("0.0"), - final_multiplier: Union[Decimal, str, float, int] = Decimal("1.0"), - ): - self.__step_bonus = step_bonus - self.__final_multiplier = final_multiplier - - @property - def step_bonus(self): - return Decimal(self.__step_bonus) - - @property - def final_multiplier(self): - return Decimal(self.__final_multiplier) - - -AwakenedIlithPartnerBonus = PartnerBonus(step_bonus="6.0") -AwakenedEtoPartnerBonus = PartnerBonus(step_bonus="7.0") -AwakenedLunaPartnerBonus = PartnerBonus(step_bonus="7.0") - - -class AwakenedAyuPartnerBonus(PartnerBonus): - def __init__(self, step_bonus: Union[Decimal, str, float, int]): - super().__init__(step_bonus=step_bonus) - - -AmaneBelowExPartnerBonus = PartnerBonus(final_multiplier="0.5") - - -class MithraTerceraPartnerBonus(PartnerBonus): - def __init__(self, step_bonus: int): - super().__init__(step_bonus=step_bonus) - - -MayaPartnerBonus = PartnerBonus(final_multiplier="2.0") - - -class StepBooster: - def final_value(self) -> Decimal: - raise NotImplementedError() - - -class LegacyMapStepBooster(StepBooster): - def __init__( - self, - stamina: Literal[2, 4, 6], - fragments: Literal[100, 250, 500, None], - ): - self.stamina = stamina - self.fragments = fragments - - @property - def stamina(self): - return self.__stamina - - @stamina.setter - def stamina(self, value: Literal[2, 4, 6]): - if value not in [2, 4, 6]: - raise ValueError("stamina can only be one of [2, 4, 6]") - self.__stamina = value - - @property - def fragments(self): - return self.__fragments - - @fragments.setter - def fragments(self, value: Literal[100, 250, 500, None]): - if value not in [100, 250, 500, None]: - raise ValueError("fragments can only be one of [100, 250, 500, None]") - self.__fragments = value - - def final_value(self) -> Decimal: - stamina_multiplier = Decimal(self.stamina) - fragments_multiplier = Decimal(1) - if self.fragments == 100: - fragments_multiplier = Decimal("1.1") - elif self.fragments == 250: - fragments_multiplier = Decimal("1.25") - elif self.fragments == 500: - fragments_multiplier = Decimal("1.5") - return stamina_multiplier * fragments_multiplier - - -class MemoriesStepBooster(StepBooster): - def final_value(self) -> Decimal: - return Decimal("4.0") - - -def calculate_step_original( - play_result: PlayResult, - *, - partner_bonus: Optional[PartnerBonus] = None, - step_booster: Optional[StepBooster] = None, -) -> Decimal: - ptt = play_result.play_rating - step = play_result.partner_step - if partner_bonus: - partner_bonus_step = partner_bonus.step_bonus - partner_bonus_multiplier = partner_bonus.final_multiplier - else: - partner_bonus_step = Decimal("0") - partner_bonus_multiplier = Decimal("1.0") - - result = (Decimal("2.45") * ptt.sqrt() + Decimal("2.5")) * (step / 50) - result += partner_bonus_step - result *= partner_bonus_multiplier - if step_booster: - result *= step_booster.final_value() - - return result - - -def calculate_step( - play_result: PlayResult, - *, - partner_bonus: Optional[PartnerBonus] = None, - step_booster: Optional[StepBooster] = None, -) -> Decimal: - result_original = calculate_step_original( - play_result, partner_bonus=partner_bonus, step_booster=step_booster - ) - - return round(result_original, 1) - - -def calculate_play_rating_from_step( - step: Union[Decimal, str, int, float], - partner_step_value: Union[Decimal, str, int, float], - *, - partner_bonus: Optional[PartnerBonus] = None, - step_booster: Optional[StepBooster] = None, -): - step = Decimal(step) - partner_step_value = Decimal(partner_step_value) - - # get original play result - if partner_bonus and partner_bonus.final_multiplier: - step /= partner_bonus.final_multiplier - if step_booster: - step /= step_booster.final_value() - - if partner_bonus and partner_bonus.step_bonus: - step -= partner_bonus.step_bonus - - play_rating_sqrt = (Decimal(50) * step - Decimal("2.5") * partner_step_value) / ( - Decimal("2.45") * partner_step_value - ) - return play_rating_sqrt**2 if play_rating_sqrt >= 0 else -(play_rating_sqrt**2) diff --git a/src/arcaea_offline/calculators/__init__.py b/src/arcaea_offline/calculators/__init__.py new file mode 100644 index 0000000..65a2c0a --- /dev/null +++ b/src/arcaea_offline/calculators/__init__.py @@ -0,0 +1,2 @@ +from . import world +from .play_result import PlayResultCalculators, calculate_constants_from_play_rating diff --git a/src/arcaea_offline/calculators/play_result.py b/src/arcaea_offline/calculators/play_result.py new file mode 100644 index 0000000..6e6ffdc --- /dev/null +++ b/src/arcaea_offline/calculators/play_result.py @@ -0,0 +1,105 @@ +from decimal import Decimal +from math import floor +from typing import Tuple, TypedDict, Union + +from arcaea_offline.constants.play_result import ScoreLowerLimits + + +class PlayResultCalculators: + @staticmethod + def score_possible_range(notes: int, pure: int, far: int) -> tuple[int, int]: + """ + Returns the possible range of score based on the given values. + + The first integer of returned tuple is the lower limit of the score, + and the second integer is the upper limit. + + For example, ... + """ + single_note_score = 10000000 / Decimal(notes) + + actual_score = floor( + single_note_score * pure + single_note_score * Decimal(0.5) * far + ) + return (actual_score, actual_score + pure) + + @staticmethod + def shiny_pure(notes: int, score: int, pure: int, far: int) -> int: + single_note_score = 10000000 / Decimal(notes) + actual_score = single_note_score * pure + single_note_score * Decimal(0.5) * far + return score - floor(actual_score) + + @staticmethod + def score_modifier(score: int) -> Decimal: + """ + Returns the score modifier of the given score + + https://arcaea.fandom.com/wiki/Potential#Score_Modifier + + :param score: The score of the play result, e.g. 9900000 + :return: The modifier of the given score, e.g. Decimal("1.5") + """ + if not isinstance(score, int): + raise TypeError("score must be an integer") + if score < 0: + raise ValueError("score cannot be negative") + + if score >= 10000000: + return Decimal(2) + if score >= 9800000: + return Decimal(1) + (Decimal(score - 9800000) / 200000) + return Decimal(score - 9500000) / 300000 + + @classmethod + def play_rating(cls, score: int, constant: int) -> Decimal: + """ + Returns the play rating of the given score + + https://arcaea.fandom.com/wiki/Potential#Play_Rating + + :param constant: The (constant * 10) of the played chart, e.g. 120 for Testify[BYD] + :param score: The score of the play result, e.g. 10002221 + :return: The play rating of the given values, e.g. Decimal("14.0") + """ + if not isinstance(score, int): + raise TypeError("score must be an integer") + if not isinstance(constant, int): + raise TypeError("constant must be an integer") + if score < 0: + raise ValueError("score cannot be negative") + if constant < 0: + raise ValueError("constant cannot be negative") + + score_modifier = cls.score_modifier(score) + return max(Decimal(0), Decimal(constant) / 10 + score_modifier) + + class ConstantsFromPlayRatingResult(TypedDict): + EX_PLUS: Tuple[Decimal, Decimal] + EX: Tuple[Decimal, Decimal] + AA: Tuple[Decimal, Decimal] + A: Tuple[Decimal, Decimal] + B: Tuple[Decimal, Decimal] + C: Tuple[Decimal, Decimal] + + @classmethod + def constants_from_play_rating( + cls, play_rating: Union[Decimal, str, float, int] + ) -> ConstantsFromPlayRatingResult: + play_rating = Decimal(play_rating) + + def _result(score_upper: int, score_lower: int) -> Tuple[Decimal, Decimal]: + upper_score_modifier = cls.score_modifier(score_upper) + lower_score_modifier = cls.score_modifier(score_lower) + return ( + play_rating - upper_score_modifier, + play_rating - lower_score_modifier, + ) + + return { + "EX_PLUS": _result(10000000, ScoreLowerLimits.EX_PLUS), + "EX": _result(ScoreLowerLimits.EX_PLUS - 1, ScoreLowerLimits.EX), + "AA": _result(ScoreLowerLimits.EX - 1, ScoreLowerLimits.AA), + "A": _result(ScoreLowerLimits.AA - 1, ScoreLowerLimits.A), + "B": _result(ScoreLowerLimits.A - 1, ScoreLowerLimits.B), + "C": _result(ScoreLowerLimits.B - 1, ScoreLowerLimits.C), + } diff --git a/src/arcaea_offline/calculators/world/__init__.py b/src/arcaea_offline/calculators/world/__init__.py new file mode 100644 index 0000000..6f8e5ec --- /dev/null +++ b/src/arcaea_offline/calculators/world/__init__.py @@ -0,0 +1,11 @@ +from ._common import MemoriesStepBooster, PartnerBonus, WorldPlayResult +from .legacy import LegacyMapStepBooster +from .main import WorldMainMapCalculators +from .partners import ( + AmaneBelowExPartnerBonus, + AwakenedEtoPartnerBonus, + AwakenedIlithPartnerBonus, + AwakenedLunaPartnerBonus, + MayaPartnerBonus, + MithraTerceraPartnerBonus, +) diff --git a/src/arcaea_offline/calculators/world/_common.py b/src/arcaea_offline/calculators/world/_common.py new file mode 100644 index 0000000..6c682cd --- /dev/null +++ b/src/arcaea_offline/calculators/world/_common.py @@ -0,0 +1,50 @@ +from decimal import Decimal +from typing import Union + + +class WorldPlayResult: + def __init__( + self, + *, + play_rating: Union[Decimal, str, float, int], + partner_step: Union[Decimal, str, float, int], + ): + self.__play_rating = play_rating + self.__partner_step = partner_step + + @property + def play_rating(self): + return Decimal(self.__play_rating) + + @property + def partner_step(self): + return Decimal(self.__partner_step) + + +class PartnerBonus: + def __init__( + self, + *, + step_bonus: Union[Decimal, str, float, int] = Decimal("0.0"), + final_multiplier: Union[Decimal, str, float, int] = Decimal("1.0"), + ): + self.__step_bonus = step_bonus + self.__final_multiplier = final_multiplier + + @property + def step_bonus(self): + return Decimal(self.__step_bonus) + + @property + def final_multiplier(self): + return Decimal(self.__final_multiplier) + + +class StepBooster: + def final_value(self) -> Decimal: + raise NotImplementedError() + + +class MemoriesStepBooster(StepBooster): + def final_value(self) -> Decimal: + return Decimal("4.0") diff --git a/src/arcaea_offline/calculators/world/legacy.py b/src/arcaea_offline/calculators/world/legacy.py new file mode 100644 index 0000000..e6a0d79 --- /dev/null +++ b/src/arcaea_offline/calculators/world/legacy.py @@ -0,0 +1,45 @@ +from decimal import Decimal +from typing import Literal + +from ._common import StepBooster + + +class LegacyMapStepBooster(StepBooster): + def __init__( + self, + stamina: Literal[2, 4, 6], + fragments: Literal[100, 250, 500, None], + ): + self.stamina = stamina + self.fragments = fragments + + @property + def stamina(self): + return self.__stamina + + @stamina.setter + def stamina(self, value: Literal[2, 4, 6]): + if value not in [2, 4, 6]: + raise ValueError("stamina can only be one of [2, 4, 6]") + self.__stamina = value + + @property + def fragments(self): + return self.__fragments + + @fragments.setter + def fragments(self, value: Literal[100, 250, 500, None]): + if value not in [100, 250, 500, None]: + raise ValueError("fragments can only be one of [100, 250, 500, None]") + self.__fragments = value + + def final_value(self) -> Decimal: + stamina_multiplier = Decimal(self.stamina) + fragments_multiplier = Decimal(1) + if self.fragments == 100: + fragments_multiplier = Decimal("1.1") + elif self.fragments == 250: + fragments_multiplier = Decimal("1.25") + elif self.fragments == 500: + fragments_multiplier = Decimal("1.5") + return stamina_multiplier * fragments_multiplier diff --git a/src/arcaea_offline/calculators/world/main.py b/src/arcaea_offline/calculators/world/main.py new file mode 100644 index 0000000..af2f0f2 --- /dev/null +++ b/src/arcaea_offline/calculators/world/main.py @@ -0,0 +1,57 @@ +from decimal import Decimal +from typing import Optional, Union + +from ._common import PartnerBonus, StepBooster, WorldPlayResult + + +class WorldMainMapCalculators: + @staticmethod + def step( + play_result: WorldPlayResult, + *, + partner_bonus: Optional[PartnerBonus] = None, + step_booster: Optional[StepBooster] = None, + ) -> Decimal: + ptt = play_result.play_rating + step = play_result.partner_step + if partner_bonus: + partner_bonus_step = partner_bonus.step_bonus + partner_bonus_multiplier = partner_bonus.final_multiplier + else: + partner_bonus_step = Decimal("0") + partner_bonus_multiplier = Decimal("1.0") + + result = (Decimal("2.45") * ptt.sqrt() + Decimal("2.5")) * (step / 50) + result += partner_bonus_step + result *= partner_bonus_multiplier + if step_booster: + result *= step_booster.final_value() + + return result + + @staticmethod + def play_rating_from_step( + step: Union[Decimal, str, int, float], + partner_step_value: Union[Decimal, str, int, float], + *, + partner_bonus: Optional[PartnerBonus] = None, + step_booster: Optional[StepBooster] = None, + ): + step = Decimal(step) + partner_step_value = Decimal(partner_step_value) + + # get original play result + if partner_bonus and partner_bonus.final_multiplier: + step /= partner_bonus.final_multiplier + if step_booster: + step /= step_booster.final_value() + + if partner_bonus and partner_bonus.step_bonus: + step -= partner_bonus.step_bonus + + play_rating_sqrt = ( + Decimal(50) * step - Decimal("2.5") * partner_step_value + ) / (Decimal("2.45") * partner_step_value) + return ( + play_rating_sqrt**2 if play_rating_sqrt >= 0 else -(play_rating_sqrt**2) + ) diff --git a/src/arcaea_offline/calculators/world/partners.py b/src/arcaea_offline/calculators/world/partners.py new file mode 100644 index 0000000..017f023 --- /dev/null +++ b/src/arcaea_offline/calculators/world/partners.py @@ -0,0 +1,16 @@ +from ._common import PartnerBonus + +AwakenedIlithPartnerBonus = PartnerBonus(step_bonus="6.0") +AwakenedEtoPartnerBonus = PartnerBonus(step_bonus="7.0") +AwakenedLunaPartnerBonus = PartnerBonus(step_bonus="7.0") + + +AmaneBelowExPartnerBonus = PartnerBonus(final_multiplier="0.5") + + +class MithraTerceraPartnerBonus(PartnerBonus): + def __init__(self, step_bonus: int): + super().__init__(step_bonus=step_bonus) + + +MayaPartnerBonus = PartnerBonus(final_multiplier="2.0") diff --git a/tests/calculate/test_world_step.py b/tests/calculate/test_world_step.py deleted file mode 100644 index 714e9a0..0000000 --- a/tests/calculate/test_world_step.py +++ /dev/null @@ -1,22 +0,0 @@ -from decimal import Decimal - -from arcaea_offline.calculate.world_step import ( - AwakenedAyuPartnerBonus, - LegacyMapStepBooster, - PlayResult, - calculate_step_original, -) - - -def test_world_step(): - # the result was copied from https://arcaea.fandom.com/wiki/World_Mode_Mechanics#Calculation - # CC BY-SA 3.0 - - booster = LegacyMapStepBooster(6, 250) - partner_bonus = AwakenedAyuPartnerBonus("+3.6") - play_result = PlayResult(play_rating=Decimal("11.299"), partner_step=92) - result = calculate_step_original( - play_result, partner_bonus=partner_bonus, step_booster=booster - ) - - assert result.quantize(Decimal("0.000")) == Decimal("175.149") diff --git a/tests/calculators/test_play_result.py b/tests/calculators/test_play_result.py new file mode 100644 index 0000000..d099594 --- /dev/null +++ b/tests/calculators/test_play_result.py @@ -0,0 +1,42 @@ +from decimal import Decimal + +import pytest + +from arcaea_offline.calculators.play_result import PlayResultCalculators + + +class TestPlayResultCalculators: + def test_score_modifier(self): + # Results from https://arcaea.fandom.com/wiki/Potential#Score_Modifier + + assert PlayResultCalculators.score_modifier(10000000) == Decimal("2.0") + assert PlayResultCalculators.score_modifier(9900000) == Decimal("1.5") + assert PlayResultCalculators.score_modifier(9800000) == Decimal("1.0") + assert PlayResultCalculators.score_modifier(9500000) == Decimal("0.0") + assert PlayResultCalculators.score_modifier(9200000) == Decimal("-1.0") + assert PlayResultCalculators.score_modifier(8900000) == Decimal("-2.0") + assert PlayResultCalculators.score_modifier(8600000) == Decimal("-3.0") + + assert PlayResultCalculators.score_modifier(0).quantize( + Decimal("-0.00") + ) == Decimal("-31.67") + + pytest.raises(ValueError, PlayResultCalculators.score_modifier, -1) + + pytest.raises(TypeError, PlayResultCalculators.score_modifier, "9800000") + pytest.raises(TypeError, PlayResultCalculators.score_modifier, None) + pytest.raises(TypeError, PlayResultCalculators.score_modifier, []) + + def test_play_rating(self): + assert PlayResultCalculators.play_rating(10002221, 120) == Decimal("14.0") + + assert PlayResultCalculators.play_rating(5500000, 120) == Decimal("0.0") + + pytest.raises(TypeError, PlayResultCalculators.play_rating, "10002221", 120) + pytest.raises(TypeError, PlayResultCalculators.play_rating, 10002221, "120") + pytest.raises(TypeError, PlayResultCalculators.play_rating, "10002221", "120") + + pytest.raises(TypeError, PlayResultCalculators.play_rating, 10002221, None) + + pytest.raises(ValueError, PlayResultCalculators.play_rating, -1, 120) + pytest.raises(ValueError, PlayResultCalculators.play_rating, 10002221, -1) diff --git a/tests/calculators/test_world.py b/tests/calculators/test_world.py new file mode 100644 index 0000000..f3c73e1 --- /dev/null +++ b/tests/calculators/test_world.py @@ -0,0 +1,74 @@ +from decimal import ROUND_FLOOR, Decimal + +from arcaea_offline.calculators.play_result import PlayResultCalculators +from arcaea_offline.calculators.world import ( + LegacyMapStepBooster, + PartnerBonus, + WorldMainMapCalculators, + WorldPlayResult, +) + + +class TestWorldMainMapCalculators: + def test_step_fandom(self): + # Final result from https://arcaea.fandom.com/wiki/World_Mode_Mechanics#Calculation + # CC BY-SA 3.0 + + booster = LegacyMapStepBooster(6, 250) + partner_bonus = PartnerBonus(step_bonus="+3.6") + play_result = WorldPlayResult(play_rating=Decimal("11.299"), partner_step=92) + result = WorldMainMapCalculators.step( + play_result, partner_bonus=partner_bonus, step_booster=booster + ) + + assert result.quantize(Decimal("0.000")) == Decimal("175.149") + + def test_step(self): + # Results from actual play results, Arcaea v5.5.8c + + def _quantize(decimal: Decimal) -> Decimal: + return decimal.quantize(Decimal("0.0"), rounding=ROUND_FLOOR) + + # goldenslaughter FTR [9.7], 9906968 + # 10.7 > 34.2 < 160 + assert _quantize( + WorldMainMapCalculators.step( + WorldPlayResult( + play_rating=PlayResultCalculators.play_rating(9906968, 97), + partner_step=160, + ) + ) + ) == Decimal("34.2") + + # Luna Rossa FTR [9.7], 9984569 + # 10.8 > 34.7 < 160 + assert _quantize( + WorldMainMapCalculators.step( + WorldPlayResult( + play_rating=PlayResultCalculators.play_rating(9984569, 97), + partner_step=160, + ) + ) + ) == Decimal("34.7") + + # ultradiaxon-N3 FTR [10.5], 9349575 + # 10.2 > 32.7 < 160 + assert _quantize( + WorldMainMapCalculators.step( + WorldPlayResult( + play_rating=PlayResultCalculators.play_rating(9349575, 105), + partner_step=160, + ) + ) + ) == Decimal("32.7") + + # san skia FTR [8.3], 10001036 + # 10.3 > 64.2 < 310 + assert _quantize( + WorldMainMapCalculators.step( + WorldPlayResult( + play_rating=PlayResultCalculators.play_rating(10001036, 83), + partner_step=310, + ) + ) + ) == Decimal("64.2")