mirror of
https://github.com/283375/arcaea-offline.git
synced 2025-07-01 04:06:27 +00:00
refactor!: calculate -> calculators
This commit is contained in:
@ -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,
|
||||
)
|
@ -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")
|
@ -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)
|
@ -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)
|
2
src/arcaea_offline/calculators/__init__.py
Normal file
2
src/arcaea_offline/calculators/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import world
|
||||
from .play_result import PlayResultCalculators, calculate_constants_from_play_rating
|
105
src/arcaea_offline/calculators/play_result.py
Normal file
105
src/arcaea_offline/calculators/play_result.py
Normal file
@ -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),
|
||||
}
|
11
src/arcaea_offline/calculators/world/__init__.py
Normal file
11
src/arcaea_offline/calculators/world/__init__.py
Normal file
@ -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,
|
||||
)
|
50
src/arcaea_offline/calculators/world/_common.py
Normal file
50
src/arcaea_offline/calculators/world/_common.py
Normal file
@ -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")
|
45
src/arcaea_offline/calculators/world/legacy.py
Normal file
45
src/arcaea_offline/calculators/world/legacy.py
Normal file
@ -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
|
57
src/arcaea_offline/calculators/world/main.py
Normal file
57
src/arcaea_offline/calculators/world/main.py
Normal file
@ -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)
|
||||
)
|
16
src/arcaea_offline/calculators/world/partners.py
Normal file
16
src/arcaea_offline/calculators/world/partners.py
Normal file
@ -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")
|
@ -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")
|
42
tests/calculators/test_play_result.py
Normal file
42
tests/calculators/test_play_result.py
Normal file
@ -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)
|
74
tests/calculators/test_world.py
Normal file
74
tests/calculators/test_world.py
Normal file
@ -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")
|
Reference in New Issue
Block a user