Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions autowsgr/ui/battle/fleet_change/_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING

from autowsgr.combat.fleet import ShipSelector
from autowsgr.infra.logger import get_logger
from autowsgr.ui.battle.constants import CLICK_BACK
from autowsgr.vision.ocr_rules import get_user_ship_name_aliases

from ._planning import FleetPlanningMixin

Expand Down Expand Up @@ -39,6 +40,15 @@ class _ShipSelection:
class FleetSelectionMixin(FleetPlanningMixin):
"""提供船池页面的进入、退出、选择和移除操作。"""

def _search_options(self, option: ShipSelector) -> tuple[ShipSelector, ...]:
"""按固定顺序生成自定义舰名和标准舰名搜索规则。"""
if not self._use_search or option.search_name is not None:
return (option,)

aliases = get_user_ship_name_aliases(option.name)
search_names = aliases if option.name in aliases else (*aliases, option.name)
return tuple(replace(option, search_name=name) for name in search_names)

def _open_choose_page(self, slot: int) -> ChooseShipPage:
"""打开指定物理槽位的选船页面。"""
from autowsgr.ui.choose_ship_page import ChooseShipPage
Expand Down Expand Up @@ -76,14 +86,16 @@ def _try_select_option(
if self._ctx.ocr is None:
raise RuntimeError('智能换船需要 OCR 引擎')

choose_page = self._open_choose_page(slot)
selected = choose_page.change_single_ship(
option,
use_search=self._use_search,
)
if selected is None:
for search_option in self._search_options(option):
choose_page = self._open_choose_page(slot)
selected = choose_page.change_single_ship(
search_option,
use_search=self._use_search,
)
if selected is not None:
return _ShipSelection(name=selected, option=option)
self._cancel_choose_page()
return _ShipSelection(name=selected, option=option)
return _ShipSelection(name=None, option=option)

# 打开指定槽位的选船页面,完成单艘舰船的选择或移除。
def _change_single_ship(
Expand Down
24 changes: 23 additions & 1 deletion autowsgr/vision/ocr_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
from autowsgr.constants import (
expand_ship_name_candidates as expand_group_candidates,
)
from autowsgr.constants import get_ship_name_group_id, set_ship_name_aliases
from autowsgr.constants import (
get_ship_name_group_id,
normalize_ship_name,
set_ship_name_aliases,
)
from autowsgr.infra.logger import get_logger
from autowsgr.types import ShipType

Expand Down Expand Up @@ -211,6 +215,24 @@ def set_user_ship_name_aliases(aliases: Mapping[str, str]) -> int:
return len(loaded)


def get_user_ship_name_aliases(ship_name: str) -> tuple[str, ...]:
"""返回标准舰名对应的全部游戏内自定义名,结果不依赖配置顺序。"""
name = ship_name.strip()
if not name:
return ()
if name in _USER_SHIP_NAME_ALIASES:
return (name,)

identity = normalize_ship_name(name)
return tuple(
sorted(
alias
for alias, standard_name in _USER_SHIP_NAME_ALIASES.items()
if normalize_ship_name(standard_name) == identity
)
)


def expand_ship_name_candidates(candidates: list[str]) -> list[str]:
"""将当前舰名候选扩展为同组全部名称。"""
return expand_group_candidates(candidates)
Expand Down
87 changes: 87 additions & 0 deletions testing/ui/battle_preparation/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,93 @@ def test_decisive_uses_new_flow_when_enabled(self):
new_change.assert_called_once_with(None, exact_fleet_rules(['A']))


class TestFleetSelection:
@pytest.mark.parametrize(
'aliases',
[
{'别名甲': '85工程', '别名乙': '85工程'},
{'别名乙': '85工程', '别名甲': '85工程'},
],
)
def test_try_select_option_retries_all_aliases_in_stable_order(
self,
aliases: dict[str, str],
):
page = BattlePreparationPage(
_make_ctx(MagicMock(spec=AndroidController), MagicMock()),
)
set_user_ship_name_aliases(aliases)
option = ShipSelector(
name='85工程',
ship_types=(ShipType.CV,),
min_level=100,
)
first_page = MagicMock()
first_page.change_single_ship.return_value = None
second_page = MagicMock()
second_page.change_single_ship.return_value = '85工程'

with (
patch.object(
page,
'_open_choose_page',
side_effect=[first_page, second_page],
) as open_page,
patch.object(page, '_cancel_choose_page') as cancel_page,
):
selected = page._try_select_option(2, option)

attempted = [
first_page.change_single_ship.call_args.args[0],
second_page.change_single_ship.call_args.args[0],
]
assert selected == _ShipSelection(name='85工程', option=option)
assert [item.search_name for item in attempted] == sorted(aliases)
assert all(item.ship_types == (ShipType.CV,) for item in attempted)
assert all(item.min_level == 100 for item in attempted)
assert open_page.call_args_list == [call(2), call(2)]
cancel_page.assert_called_once_with()

def test_try_select_option_falls_back_to_standard_name(self):
page = BattlePreparationPage(
_make_ctx(MagicMock(spec=AndroidController), MagicMock()),
)
aliases = {'别名甲': '85工程', '别名乙': '85工程'}
set_user_ship_name_aliases(aliases)
option = ShipSelector(name='85工程')
choose_pages = [MagicMock(), MagicMock(), MagicMock()]
for choose_page in choose_pages[:-1]:
choose_page.change_single_ship.return_value = None
choose_pages[-1].change_single_ship.return_value = '85工程'

with (
patch.object(
page,
'_open_choose_page',
side_effect=choose_pages,
),
patch.object(page, '_cancel_choose_page') as cancel_page,
):
selected = page._try_select_option(0, option)

attempted = [
choose_page.change_single_ship.call_args.args[0].search_name
for choose_page in choose_pages
]
assert selected == _ShipSelection(name='85工程', option=option)
assert attempted == [*sorted(aliases), '85工程']
assert cancel_page.call_count == 2

def test_explicit_search_name_is_not_expanded(self):
page = BattlePreparationPage(
_make_ctx(MagicMock(spec=AndroidController), MagicMock()),
)
set_user_ship_name_aliases({'别名甲': '85工程', '别名乙': '85工程'})
option = ShipSelector(name='85工程', search_name='别名乙')

assert page._search_options(option) == (option,)


# ─────────────────────────────────────────────
# 智能换船
# ─────────────────────────────────────────────
Expand Down
5 changes: 4 additions & 1 deletion testing/ui/test_choose_ship_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ def test_custom_name_rejected_above_confidence(self):
def test_bidirectional_prefix_ambiguity_is_rejected(self):
assert not ChooseShipPage._matches_ship_name('安东尼奥', '安东尼')

def test_user_alias_is_used_for_search_and_matching(self):
def test_user_alias_is_used_for_matching(self):
set_user_ship_name_aliases({'契卡洛夫': '85工程'})

assert ChooseShipPage._normalize_search_keyword('契卡洛夫') == '契卡洛夫'
assert ChooseShipPage._matches_ship_name('契卡洛夫', '85工程')
assert ChooseShipPage._matches_ship_name('85工程', '契卡洛夫')

def test_standard_name_is_used_when_no_user_alias_exists(self):
assert ChooseShipPage._normalize_search_keyword(' 岛风 ') == '岛风'


class TestShipTypeProbeRoutes:
@staticmethod
Expand Down
18 changes: 18 additions & 0 deletions testing/vision/test_ocr.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
FastOCRProfile,
get_easyocr_params,
get_fastocr_params,
get_user_ship_name_aliases,
normalize_level_digits,
set_user_ship_name_aliases,
set_user_ship_name_corrections,
Expand Down Expand Up @@ -569,6 +570,23 @@ def test_user_ship_name_aliases_map_display_names_to_standard_names(self):
assert _fuzzy_match(apply_ship_patches('U-47·狼群'), SHIPNAMES) == 'U-47'
assert _fuzzy_match(apply_ship_patches('巴尔的摩:英魂'), SHIPNAMES) == '巴尔的摩'

@pytest.mark.parametrize(
'aliases',
[
{'别名甲': '85工程', '别名乙': '85工程'},
{'别名乙': '85工程', '别名甲': '85工程'},
],
)
def test_reverse_alias_lookup_returns_all_aliases_in_stable_order(
self,
aliases: dict[str, str],
):
set_user_ship_name_aliases(aliases)

expected = tuple(sorted(aliases))
assert get_user_ship_name_aliases('85工程') == expected
assert get_user_ship_name_aliases(expected[0]) == (expected[0],)

def test_user_ship_name_is_added_to_the_same_ship_group(self):
set_user_ship_name_aliases({'契卡洛夫': '85工程'})

Expand Down
Loading