151 lines
5.0 KiB
Python
151 lines
5.0 KiB
Python
import pytest
|
|
import math
|
|
from unittest.mock import MagicMock
|
|
from src.classes.battle import (
|
|
get_base_strength,
|
|
_combat_strength_vs,
|
|
_strength_diff,
|
|
calc_win_rate,
|
|
_REALM_BASE_STRENGTH,
|
|
_STAGE_BONUS_STRENGTH,
|
|
_SUPPRESSION_POINTS
|
|
)
|
|
from src.classes.cultivation import Realm, Stage
|
|
from src.classes.technique import TechniqueAttribute
|
|
|
|
# Helper to create a mock avatar
|
|
def create_mock_avatar(level, realm=None, stage=None, effects=None, technique_attr=None):
|
|
avatar = MagicMock()
|
|
|
|
# Setup cultivation progress
|
|
cp = MagicMock()
|
|
cp.level = level
|
|
|
|
# Setup Realm Enum Mock or Real Enum
|
|
if realm:
|
|
cp.realm = realm
|
|
else:
|
|
# Fallback to Qi Refinement if not specified
|
|
cp.realm = Realm.Qi_Refinement
|
|
|
|
if stage:
|
|
cp.stage = stage
|
|
else:
|
|
# Fallback to Early Stage
|
|
cp.stage = Stage.Early_Stage
|
|
|
|
avatar.cultivation_progress = cp
|
|
|
|
# Setup effects
|
|
avatar.effects = effects or {}
|
|
|
|
# Setup technique
|
|
if technique_attr:
|
|
tech = MagicMock()
|
|
tech.attribute = technique_attr
|
|
avatar.technique = tech
|
|
else:
|
|
avatar.technique = None
|
|
|
|
return avatar
|
|
|
|
class TestBattleStrength:
|
|
def test_base_strength_qi_early_min(self):
|
|
# 练气前期 1级
|
|
# Base: 10, Stage: 0
|
|
avatar = create_mock_avatar(1, Realm.Qi_Refinement, Stage.Early_Stage)
|
|
strength = get_base_strength(avatar)
|
|
expected = 10.0 + 0.0
|
|
assert strength == expected
|
|
|
|
def test_base_strength_qi_late_max(self):
|
|
# 练气后期 30级
|
|
# Base: 10, Stage: 5
|
|
avatar = create_mock_avatar(30, Realm.Qi_Refinement, Stage.Late_Stage)
|
|
strength = get_base_strength(avatar)
|
|
expected = 10.0 + 5.0
|
|
assert strength == pytest.approx(expected)
|
|
|
|
def test_base_strength_foundation_early_min(self):
|
|
# 筑基前期 31级
|
|
# Base: 20, Stage: 0
|
|
avatar = create_mock_avatar(31, Realm.Foundation_Establishment, Stage.Early_Stage)
|
|
strength = get_base_strength(avatar)
|
|
expected = 20.0 + 0.0
|
|
assert strength == expected
|
|
|
|
def test_base_strength_nascent_middle(self):
|
|
# 元婴中期 105级
|
|
# Base: 40, Stage: 2.5
|
|
avatar = create_mock_avatar(105, Realm.Nascent_Soul, Stage.Middle_Stage)
|
|
strength = get_base_strength(avatar)
|
|
expected = 40.0 + 2.5
|
|
assert strength == pytest.approx(expected)
|
|
|
|
def test_extra_effects(self):
|
|
# Test extra strength points from effects
|
|
avatar = create_mock_avatar(1, Realm.Qi_Refinement, Stage.Early_Stage, effects={"extra_battle_strength_points": 5.0})
|
|
strength = get_base_strength(avatar)
|
|
assert strength == 15.0
|
|
|
|
class TestCombatMechanics:
|
|
def test_realm_gap_win_rate(self):
|
|
# 筑基前期 vs 练气巅峰
|
|
# 筑基前期: 20.0
|
|
# 练气巅峰: 15.0 (10 + 5)
|
|
# Diff: 5.0
|
|
p1 = create_mock_avatar(31, Realm.Foundation_Establishment, Stage.Early_Stage)
|
|
p2 = create_mock_avatar(30, Realm.Qi_Refinement, Stage.Late_Stage)
|
|
|
|
# Win rate check
|
|
# p = 1 / (1 + exp(-0.15 * 5.0)) = 1 / (1 + exp(-0.75)) = 1 / (1 + 0.472) = 1 / 1.472 = 0.679
|
|
rate = calc_win_rate(p1, p2)
|
|
assert rate > 0.67
|
|
assert rate < 0.69
|
|
|
|
def test_massive_gap_win_rate(self):
|
|
# 元婴 vs 练气
|
|
# 元婴: 40+
|
|
# 练气: 10+
|
|
# Diff > 20 -> should be close to max win rate
|
|
p1 = create_mock_avatar(91, Realm.Nascent_Soul, Stage.Early_Stage)
|
|
p2 = create_mock_avatar(1, Realm.Qi_Refinement, Stage.Early_Stage)
|
|
|
|
rate = calc_win_rate(p1, p2)
|
|
# With cap at 0.99, but actually calculation might be slightly below 0.99 if diff isn't huge enough
|
|
# Diff = 30, p = 1/(1+exp(-4.5)) = 0.989
|
|
assert rate > 0.98
|
|
|
|
def test_technique_suppression(self):
|
|
# Test attribute suppression bonus (Metal > Wood)
|
|
# GOLD suppresses WOOD
|
|
p1 = create_mock_avatar(10, Realm.Qi_Refinement, Stage.Early_Stage, technique_attr=TechniqueAttribute.GOLD)
|
|
p2 = create_mock_avatar(10, Realm.Qi_Refinement, Stage.Early_Stage, technique_attr=TechniqueAttribute.WOOD)
|
|
|
|
# Base strengths are equal (same level/realm/stage)
|
|
# P1 attacks P2: Gold vs Wood -> Bonus
|
|
s1 = _combat_strength_vs(p2, p1)
|
|
|
|
# P2 attacks P1: Wood vs Gold -> No Bonus
|
|
s2 = _combat_strength_vs(p1, p2)
|
|
|
|
base = get_base_strength(p1)
|
|
|
|
assert s1 == base + _SUPPRESSION_POINTS
|
|
assert s2 == base
|
|
|
|
diff = s1 - s2
|
|
assert diff == _SUPPRESSION_POINTS
|
|
|
|
def test_intra_stage_diff(self):
|
|
# Test same stage same strength
|
|
# Level 1 vs Level 10 (Early Stage)
|
|
# Diff = 0
|
|
p1 = create_mock_avatar(10, Realm.Qi_Refinement, Stage.Early_Stage)
|
|
p2 = create_mock_avatar(1, Realm.Qi_Refinement, Stage.Early_Stage)
|
|
|
|
diff = _strength_diff(p1, p2)
|
|
expected_diff = 0.0
|
|
assert diff == pytest.approx(expected_diff)
|
|
|