Skip to content

Commit d8db89e

Browse files
committed
fix: loot_rec 171->17
1 parent c5ef4e0 commit d8db89e

6 files changed

Lines changed: 131 additions & 111 deletions

File tree

autowsgr/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""AutoWSGR — 战舰少女R 自动化框架 (v2)"""
22

3-
__version__ = '2.1.6'
3+
__version__ = '2.1.6.post1'

autowsgr/context/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## 维护的内容
2+
3+
原则为尽力维护正确的全局状态,不保证绝对准确。
4+
5+
- 全局舰船状态(以舰船名为唯一标识)
6+
- 等级
7+
- 血量状态
8+
- 已获取战利品数量、已获取舰船数量
9+
- 剩余决战磁盘数
10+
- 今日战役次数
11+
- 今日演习状态
12+
- 日常任务状态
13+
- 周常任务状态
14+
- 浴场状态
15+
16+
17+
## 实时性保证
18+
19+
20+
21+
## 维护职责
22+
23+
由 ops 负责维护

autowsgr/ui/map/data.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,13 +420,15 @@ def parse_map_title(text: str) -> MapIdentity | None:
420420
LOOT_COUNT_CROP: tuple[float, float, float, float] = (0.804, 0.025, 0.863, 0.065)
421421
"""战利品获取数量 OCR 裁切区域 (x1, y1, x2, y2)。
422422
423-
对应出征面板右上角的胖次获取计数,格式如 ``X/50``。
423+
对应出征面板右上角的胖次获取计数, 格式如 ``X/50``.
424+
仅解析 ``/`` 前的数字部分, 分母固定为 50.
424425
"""
425426

426427
SHIP_COUNT_CROP: tuple[float, float, float, float] = (0.904, 0.025, 0.975, 0.064)
427428
"""舰船获取数量 OCR 裁切区域 (x1, y1, x2, y2)。
428429
429-
对应出征面板右上角的舰船获取计数,格式如 ``X/500``。
430+
对应出征面板右上角的舰船获取计数, 格式如 ``X/500``.
431+
仅解析 ``/`` 前的数字部分, 分母固定为 500.
430432
"""
431433

432434
# ═══════════════════════════════════════════════════════════════════════════════

autowsgr/ui/map/panels/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from autowsgr.ui.map.panels.decisive import DecisivePanelMixin
55
from autowsgr.ui.map.panels.exercise import ExercisePanelMixin
66
from autowsgr.ui.map.panels.expedition import ExpeditionPanelMixin
7-
from autowsgr.ui.map.panels.sortie import LootShipCount, SortiePanelMixin
7+
from autowsgr.ui.map.panels.sortie import (
8+
LootShipCount,
9+
SortiePanelMixin,
10+
recognize_loot_count,
11+
recognize_ship_count,
12+
)
813

914

1015
__all__ = [
@@ -14,4 +19,6 @@
1419
'ExpeditionPanelMixin',
1520
'LootShipCount',
1621
'SortiePanelMixin',
22+
'recognize_loot_count',
23+
'recognize_ship_count',
1724
]

autowsgr/ui/map/panels/campaign.py

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import re
65
import time
76
from dataclasses import dataclass
87

@@ -14,10 +13,14 @@
1413
CLICK_DIFFICULTY,
1514
DIFFICULTY_EASY_COLOR,
1615
DIFFICULTY_HARD_COLOR,
17-
LOOT_COUNT_CROP,
18-
SHIP_COUNT_CROP,
1916
MapPanel,
2017
)
18+
from autowsgr.ui.map.panels.sortie import (
19+
LOOT_MAX,
20+
SHIP_MAX,
21+
recognize_loot_count,
22+
recognize_ship_count,
23+
)
2124
from autowsgr.ui.utils import wait_for_page
2225
from autowsgr.vision import PixelChecker
2326

@@ -129,11 +132,11 @@ class AcquisitionCounts:
129132

130133
ship_count: int | None = None
131134
"""今日已获取舰船数量。"""
132-
ship_max: int | None = None
135+
ship_max: int = SHIP_MAX
133136
"""今日舰船获取上限。"""
134137
loot_count: int | None = None
135138
"""今日已获取战利品数量。"""
136-
loot_max: int | None = None
139+
loot_max: int = LOOT_MAX
137140
"""今日战利品获取上限。"""
138141

139142
def _recognize_acquisition_counts(
@@ -142,8 +145,6 @@ def _recognize_acquisition_counts(
142145
) -> AcquisitionCounts:
143146
"""从截图中 OCR 识别今日舰船与战利品获取数量。
144147
145-
读取出征面板右上角的 ``X/500`` (舰船) 和 ``X/50`` (战利品) 文本。
146-
147148
Parameters
148149
----------
149150
screen:
@@ -152,34 +153,16 @@ def _recognize_acquisition_counts(
152153
Returns
153154
-------
154155
AcquisitionCounts
155-
识别到的数量信息无法识别的字段为 ``None``。
156+
识别到的数量信息, 无法识别的字段为 ``None``。
156157
"""
157158
result = self.AcquisitionCounts()
158159
ocr = self._ocr
159160
if ocr is None:
160161
_log.warning('[UI] 未提供 OCR 引擎,无法识别获取数量')
161162
return result
162163

163-
# ── 战利品 ──
164-
loot_img = PixelChecker.crop(screen, *LOOT_COUNT_CROP)
165-
loot_text = ocr.recognize_single(loot_img, allowlist='0123456789/').text.strip()
166-
parsed = self._parse_fraction(loot_text)
167-
if parsed is not None:
168-
result.loot_count, result.loot_max = parsed
169-
_log.info('[UI] 今日战利品: {}/{}', result.loot_count, result.loot_max)
170-
else:
171-
_log.warning("[UI] 战利品数量识别失败: '{}'", loot_text)
172-
173-
# ── 舰船 ──
174-
ship_img = PixelChecker.crop(screen, *SHIP_COUNT_CROP)
175-
ship_text = ocr.recognize_single(ship_img, allowlist='0123456789/').text.strip()
176-
parsed = self._parse_fraction(ship_text)
177-
if parsed is not None:
178-
result.ship_count, result.ship_max = parsed
179-
_log.info('[UI] 今日舰船: {}/{}', result.ship_count, result.ship_max)
180-
else:
181-
_log.warning("[UI] 舰船数量识别失败: '{}'", ship_text)
182-
164+
result.loot_count = recognize_loot_count(screen, ocr)
165+
result.ship_count = recognize_ship_count(screen, ocr)
183166
return result
184167

185168
def get_acquisition_counts(self) -> AcquisitionCounts:
@@ -196,26 +179,3 @@ def get_acquisition_counts(self) -> AcquisitionCounts:
196179
time.sleep(0.5)
197180
screen = self._ctrl.screenshot()
198181
return self._recognize_acquisition_counts(screen)
199-
200-
@staticmethod
201-
def _parse_fraction(text: str) -> tuple[int, int] | None:
202-
"""解析 ``"X/Y"`` 格式文本为 ``(X, Y)``。
203-
204-
容错处理: OCR 可能把 ``/`` 识别为 ``1`` 或其他字符,
205-
对常见格式做特殊处理。
206-
"""
207-
# 标准格式: "12/500"
208-
m = re.match(r'(\d+)\s*/\s*(\d+)', text)
209-
if m:
210-
return int(m.group(1)), int(m.group(2))
211-
212-
# OCR 把 "/" 识别为 "1" 等情况: "121500" → 尝试按已知上限拆分
213-
digits = re.sub(r'\D', '', text)
214-
if digits:
215-
# 尝试常见上限: 50 (战利品) 和 500 (舰船)
216-
for max_val_str in ('500', '50'):
217-
if digits.endswith(max_val_str):
218-
current = digits[: -len(max_val_str)]
219-
if current.isdigit():
220-
return int(current), int(max_val_str)
221-
return None

autowsgr/ui/map/panels/sortie.py

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import re
65
import time
76
from dataclasses import dataclass
87
from typing import TYPE_CHECKING
@@ -32,9 +31,17 @@
3231
if TYPE_CHECKING:
3332
import numpy as np
3433

34+
from autowsgr.vision import EasyOCREngine
35+
3536

3637
_log = get_logger('ui')
3738

39+
LOOT_MAX = 50
40+
"""战利品 (胖次) 上限, 固定值。"""
41+
42+
SHIP_MAX = 500
43+
"""舰船上限, 固定值。"""
44+
3845

3946
# ── 数据类 ──
4047

@@ -46,49 +53,95 @@ class LootShipCount:
4653
Attributes
4754
----------
4855
loot:
49-
战利品 (胖次) 已获取数量识别失败时为 ``None``。
56+
战利品 (胖次) 已获取数量, 识别失败时为 ``None``。
5057
loot_max:
51-
战利品上限 (通常 50),识别失败时为 ``None``
58+
战利品上限, 固定 50。
5259
ship:
53-
舰船已获取数量识别失败时为 ``None``。
60+
舰船已获取数量, 识别失败时为 ``None``。
5461
ship_max:
55-
舰船上限 (通常 500),识别失败时为 ``None``
62+
舰船上限, 固定 500。
5663
"""
5764

5865
loot: int | None = None
59-
loot_max: int | None = None
66+
loot_max: int = LOOT_MAX
6067
ship: int | None = None
61-
ship_max: int | None = None
68+
ship_max: int = SHIP_MAX
6269

6370

64-
# ── 内部工具 ──
71+
# ── 独立识别函数 ──
6572

66-
_FRACTION_RE = re.compile(r'(\d+)\s*[/|]\s*(\d+)')
67-
"""匹配 "X/Y" 格式的正则 (兼容 OCR 把 / 识别为 | 的情况)。"""
73+
_OCR_ALLOWLIST = '0123456789/|'
74+
"""OCR 字符白名单。包含 ``/`` 和 ``|`` 使 OCR 正确识别斜线而非误读为 ``1``。"""
6875

69-
_KNOWN_DENOMS = (500, 50)
70-
"""已知分母值, 用于 OCR 将 ``/`` 误识为 ``1`` 时的回退解析 (长的优先匹配)。"""
7176

77+
def _parse_numerator(text: str, max_val: int) -> int:
78+
"""从 ``"X/Y"`` 格式的 OCR 文本中提取分子 (``/`` 前的数字)。
7279
73-
def _parse_fraction(text: str) -> tuple[int, int] | None:
74-
"""解析 ``"123/500"`` 格式文本, 返回 ``(numerator, denominator)``。"""
75-
m = _FRACTION_RE.search(text)
76-
if m:
77-
return int(m.group(1)), int(m.group(2))
78-
79-
# 回退: OCR 有时将 '/' 误识为 '1', 导致纯数字串如 "17150" (实为 "17/50")。
80-
# 尝试去掉已知分母前的多余 '1' 来还原。
80+
- 优先按 ``/`` 或 ``|`` 分割取第一段。
81+
- 回退: 若无分隔符, 按已知分母剥离末尾后缀。
82+
"""
83+
# 优先: 按 "/" 或 "|" 分割
84+
for sep in ('/', '|'):
85+
if sep in text:
86+
left = text.split(sep, 1)[0]
87+
digits = ''.join(c for c in left if c.isdigit())
88+
if digits:
89+
return int(digits)
90+
raise ValueError(f'分子部分无数字: "{text}"')
91+
92+
# 回退: OCR 偶尔把 "/" 识别为 "1", 导致纯数字串如 "17150"。
93+
# 已知分母为 max_val, 则后缀为 "1" + str(max_val)。
8194
digits = ''.join(c for c in text if c.isdigit())
82-
if digits:
83-
for denom in _KNOWN_DENOMS:
84-
suffix = '1' + str(denom)
85-
if digits.endswith(suffix) and len(digits) > len(suffix):
86-
numerator = int(digits[: -len(suffix)])
87-
if numerator >= 0 and numerator <= denom:
88-
return numerator, denom
95+
if not digits:
96+
raise ValueError(f'文本中无数字: "{text}"')
97+
suffix = '1' + str(max_val)
98+
if digits.endswith(suffix) and len(digits) > len(suffix):
99+
return int(digits[: -len(suffix)])
100+
# 无 "1" 前缀: 可能分母直接拼接
101+
denom_str = str(max_val)
102+
if digits.endswith(denom_str) and len(digits) > len(denom_str):
103+
return int(digits[: -len(denom_str)])
89104
return None
90105

91106

107+
def recognize_loot_count(screen: np.ndarray, ocr: EasyOCREngine) -> int | None:
108+
"""识别出征面板战利品 (胖次) 已获取数量。
109+
110+
OCR ``X/50`` 区域并提取 ``/`` 前的数字, 上限固定为 50。
111+
"""
112+
img = PixelChecker.crop(screen, *LOOT_COUNT_CROP)
113+
text = ocr.recognize_single(img, allowlist=_OCR_ALLOWLIST).text.strip()
114+
if not text:
115+
_log.warning('[UI] 战利品数量 OCR 无结果')
116+
return None
117+
count = _parse_numerator(text, LOOT_MAX)
118+
if count > 50 and str(count).endswith('1'):
119+
count = int(str(count)[:-1]) # 可能 OCR 把 "/50" 识别成 "150"
120+
if count is not None:
121+
_log.info('[UI] 战利品数量: {}/{}', count, LOOT_MAX)
122+
else:
123+
_log.warning("[UI] 战利品数量 OCR 解析失败: '{}'", text)
124+
return count
125+
126+
127+
def recognize_ship_count(screen: np.ndarray, ocr: EasyOCREngine) -> int | None:
128+
"""识别出征面板舰船已获取数量。
129+
130+
OCR ``X/500`` 区域并提取 ``/`` 前的数字, 上限固定为 500。
131+
"""
132+
img = PixelChecker.crop(screen, *SHIP_COUNT_CROP)
133+
text = ocr.recognize_single(img, allowlist=_OCR_ALLOWLIST).text.strip()
134+
if not text:
135+
_log.warning('[UI] 舰船数量 OCR 无结果')
136+
return None
137+
count = _parse_numerator(text, SHIP_MAX)
138+
if count is not None:
139+
_log.info('[UI] 舰船数量: {}/{}', count, SHIP_MAX)
140+
else:
141+
_log.warning("[UI] 舰船数量 OCR 解析失败: '{}'", text)
142+
return count
143+
144+
92145
class SortiePanelMixin(BaseMapPage):
93146
"""Mixin: 出征面板操作 — 选择章节 / 地图节点 / 进入出征准备。"""
94147

@@ -206,7 +259,7 @@ def get_loot_and_ship_count(
206259
) -> LootShipCount:
207260
"""读取出征面板右上角的已获取舰船/战利品数量。
208261
209-
通过 OCR 识别 ``X/Y`` 格式的数字。需要先处于出征面板。
262+
通过 OCR 识别数字。需要先处于出征面板。
210263
211264
Parameters
212265
----------
@@ -218,35 +271,10 @@ def get_loot_and_ship_count(
218271
if screen is None:
219272
screen = self._ctrl.screenshot()
220273

221-
loot = loot_max = ship = ship_max = None
222-
223-
# -- 战利品 (胖次) --
224-
loot_img = PixelChecker.crop(screen, *LOOT_COUNT_CROP)
225-
loot_text = self._ocr.recognize_single(loot_img, allowlist='0123456789/|').text.strip()
226-
if loot_text:
227-
parsed = _parse_fraction(loot_text)
228-
if parsed:
229-
loot, loot_max = parsed
230-
_log.info('[UI] 战利品数量: {}/{}', loot, loot_max)
231-
else:
232-
_log.warning("[UI] 战利品数量 OCR 解析失败: '{}'", loot_text)
233-
else:
234-
_log.warning('[UI] 战利品数量 OCR 无结果')
235-
236-
# -- 舰船 --
237-
ship_img = PixelChecker.crop(screen, *SHIP_COUNT_CROP)
238-
ship_text = self._ocr.recognize_single(ship_img, allowlist='0123456789/|').text.strip()
239-
if ship_text:
240-
parsed = _parse_fraction(ship_text)
241-
if parsed:
242-
ship, ship_max = parsed
243-
_log.info('[UI] 舰船数量: {}/{}', ship, ship_max)
244-
else:
245-
_log.warning("[UI] 舰船数量 OCR 解析失败: '{}'", ship_text)
246-
else:
247-
_log.warning('[UI] 舰船数量 OCR 无结果')
274+
loot = recognize_loot_count(screen, self._ocr)
275+
ship = recognize_ship_count(screen, self._ocr)
248276

249-
return LootShipCount(loot=loot, loot_max=loot_max, ship=ship, ship_max=ship_max)
277+
return LootShipCount(loot=loot, ship=ship)
250278

251279
# ═══════════════════════════════════════════════════════════════════════
252280
# 进入出征

0 commit comments

Comments
 (0)