Skip to content

Commit e4f0a81

Browse files
committed
feat(rules): support '+' sum operator in condition parsing
Allow expressions like 'CL + DD >= 1' in enemy_rules conditions. Multiple ship types joined by '+' are summed before comparison. Bump version to 2.1.4.
1 parent a732533 commit e4f0a81

4 files changed

Lines changed: 57 additions & 6 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.3'
3+
__version__ = '2.1.4'

autowsgr/combat/rules.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ def __post_init__(self) -> None:
136136

137137
def evaluate(self, context: dict[str, int | float]) -> bool:
138138
"""在给定上下文中评估此条件。"""
139-
actual = context.get(self.field, 0)
139+
if '+' in self.field:
140+
parts = [p.strip() for p in self.field.split('+')]
141+
actual = sum(context.get(p, 0) for p in parts)
142+
else:
143+
actual = context.get(self.field, 0)
140144
return _OPERATORS[self.op](actual, self.value)
141145

142146

@@ -275,8 +279,10 @@ def evaluate_formation(self, enemy_formation: str) -> RuleAction:
275279
# 旧格式解析
276280
# ═══════════════════════════════════════════════════════════════════════════════
277281

278-
# 匹配 "BB >= 2" 形式的条件片段
279-
_CONDITION_PIECE_RE = re.compile(r'([A-Z]{2,4})\s*(>=|<=|>|<|==|!=)\s*(\d+(?:\.\d+)?)')
282+
# 匹配 "BB >= 2" 或 "CL + DD >= 1" 形式的条件片段
283+
_CONDITION_PIECE_RE = re.compile(
284+
r'([A-Z]{2,4}(?:\s*\+\s*[A-Z]{2,4})*)\s*(>=|<=|>|<|==|!=)\s*(\d+(?:\.\d+)?)'
285+
)
280286

281287

282288
def _parse_legacy_condition(condition_str: str) -> list[Condition]:
@@ -287,15 +293,19 @@ def _parse_legacy_condition(condition_str: str) -> list[Condition]:
287293
288294
用 ``and`` 连接的条件被拆分为独立的 ``Condition`` (AND 语义)。
289295
不支持 ``or`` — 用多条规则替代。
296+
297+
支持 ``+`` 运算符将多个舰种求和,如 ``CL + DD >= 1``。
290298
"""
291299
matches = _CONDITION_PIECE_RE.findall(condition_str)
292300
if not matches:
293301
raise ValueError(f"无法解析规则条件: '{condition_str}'")
294302

295303
conditions: list[Condition] = []
296-
for field_name, op, value_str in matches:
304+
for field_expr, op, value_str in matches:
297305
value = float(value_str) if '.' in value_str else int(value_str)
298-
conditions.append(Condition(field=field_name, op=op, value=value))
306+
# 规范化字段表达式:去除空格,如 'CL + DD' -> 'CL+DD'
307+
field_key = '+'.join(p.strip() for p in field_expr.split('+'))
308+
conditions.append(Condition(field=field_key, op=op, value=value))
299309
return conditions
300310

301311

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
equipments
2+
nd

testing/combat/test_combat.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,51 @@ def test_complex(self):
227227
assert conditions[1].field == 'DD'
228228
assert conditions[1].op == '<='
229229

230+
def test_sum_expression(self):
231+
conditions = _parse_legacy_condition('(CL + DD >= 1)')
232+
assert len(conditions) == 1
233+
assert conditions[0].field == 'CL+DD'
234+
assert conditions[0].op == '>='
235+
assert conditions[0].value == 1
236+
237+
def test_sum_expression_triple(self):
238+
conditions = _parse_legacy_condition('(CL + DD + CA >= 3)')
239+
assert len(conditions) == 1
240+
assert conditions[0].field == 'CL+DD+CA'
241+
assert conditions[0].op == '>='
242+
assert conditions[0].value == 3
243+
244+
def test_sum_compound_and(self):
245+
conditions = _parse_legacy_condition('(CL + DD >= 1) and (BB >= 2)')
246+
assert len(conditions) == 2
247+
assert conditions[0].field == 'CL+DD'
248+
assert conditions[1].field == 'BB'
249+
230250
def test_invalid_raises(self):
231251
with pytest.raises(ValueError, match='无法解析'):
232252
_parse_legacy_condition('hello world')
233253

234254

255+
class TestConditionSumEvaluation:
256+
"""Condition '+' sum evaluation tests."""
257+
258+
def test_sum_basic(self):
259+
c = Condition(field='CL+DD', op='>=', value=2)
260+
assert c.evaluate({'CL': 1, 'DD': 1})
261+
assert c.evaluate({'CL': 2, 'DD': 0})
262+
assert not c.evaluate({'CL': 0, 'DD': 1})
263+
264+
def test_sum_missing_fields(self):
265+
c = Condition(field='CL+DD', op='>=', value=1)
266+
assert c.evaluate({'CL': 1})
267+
assert not c.evaluate({'BB': 5})
268+
269+
def test_sum_legacy_roundtrip(self):
270+
engine = RuleEngine.from_legacy_rules([['(CL + DD >= 2) and (BB > 0)', 'retreat']])
271+
assert engine.evaluate({'CL': 1, 'DD': 1, 'BB': 1}).result == RuleResult.RETREAT
272+
assert engine.evaluate({'CL': 0, 'DD': 0, 'BB': 3}).result == RuleResult.NO_ACTION
273+
274+
235275
# ═══════════════════════════════════════════════════════════════════════════════
236276
# history.py 测试
237277
# ═══════════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)