feature updates

This commit is contained in:
2026-02-02 16:28:53 -05:00
parent bf9aa3fa23
commit 44c06530ac
83 changed files with 282641 additions and 11251 deletions

View File

@@ -0,0 +1,332 @@
#!/usr/bin/env python3
"""
Tests for the conditional ability parsing in ability_processor.py
Run with: python -m pytest tests/test_ability_processor_conditionals.py -v
Or directly: python tests/test_ability_processor_conditionals.py
"""
import sys
import os
import unittest
# Add tools directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'tools'))
from ability_processor import (
parse_condition,
parse_conditional_ability,
parse_chained_effect,
parse_scaling_effect,
parse_effects,
)
class TestParseCondition(unittest.TestCase):
"""Tests for parse_condition function"""
def test_control_card_basic(self):
"""Should parse 'control [Card Name]' condition"""
condition = parse_condition("you control Cloud")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "CONTROL_CARD")
self.assertEqual(condition["card_name"], "Cloud")
def test_control_card_with_prefix(self):
"""Should parse 'control a Card Name X' condition"""
condition = parse_condition("you control a Card Name Tidus")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "CONTROL_CARD")
def test_control_count(self):
"""Should parse 'control X or more Forwards' condition"""
condition = parse_condition("you control 3 or more Forwards")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "CONTROL_COUNT")
self.assertEqual(condition["comparison"], "GTE")
self.assertEqual(condition["value"], 3)
self.assertEqual(condition["card_type"], "FORWARD")
def test_control_count_backups(self):
"""Should parse 'control X or more Backups' condition"""
condition = parse_condition("you control 5 or more Backups")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "CONTROL_COUNT")
self.assertEqual(condition["value"], 5)
self.assertEqual(condition["card_type"], "BACKUP")
def test_damage_received(self):
"""Should parse 'have received X or more damage' condition"""
condition = parse_condition("you have received 5 or more damage")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "DAMAGE_RECEIVED")
self.assertEqual(condition["comparison"], "GTE")
self.assertEqual(condition["value"], 5)
def test_damage_received_points_of(self):
"""Should parse 'X points of damage or more' condition"""
condition = parse_condition("you have received 6 points of damage or more")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "DAMAGE_RECEIVED")
self.assertEqual(condition["value"], 6)
def test_break_zone_count(self):
"""Should parse break zone count condition"""
condition = parse_condition("you have 5 or more Ifrit in your break zone")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "BREAK_ZONE_COUNT")
self.assertEqual(condition["comparison"], "GTE")
self.assertEqual(condition["value"], 5)
def test_forward_state_dull(self):
"""Should parse 'this forward is dull' condition"""
condition = parse_condition("this forward is dull")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "FORWARD_STATE")
self.assertEqual(condition["state"], "DULL")
self.assertTrue(condition["check_self"])
def test_forward_state_active(self):
"""Should parse 'this forward is active' condition"""
condition = parse_condition("this forward is active")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "FORWARD_STATE")
self.assertEqual(condition["state"], "ACTIVE")
def test_cost_comparison_less(self):
"""Should parse 'of cost X or less' condition"""
condition = parse_condition("of cost 3 or less")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "COST_COMPARISON")
self.assertEqual(condition["comparison"], "LTE")
self.assertEqual(condition["value"], 3)
def test_cost_comparison_more(self):
"""Should parse 'of cost X or more' condition"""
condition = parse_condition("of cost 4 or more")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "COST_COMPARISON")
self.assertEqual(condition["comparison"], "GTE")
self.assertEqual(condition["value"], 4)
def test_opponent_no_forwards(self):
"""Should parse 'opponent doesn't control any forwards' condition"""
condition = parse_condition("your opponent doesn't control any Forwards")
self.assertIsNotNone(condition)
self.assertEqual(condition["type"], "CONTROL_COUNT")
self.assertEqual(condition["comparison"], "EQ")
self.assertEqual(condition["value"], 0)
self.assertEqual(condition["owner"], "OPPONENT")
def test_unknown_condition(self):
"""Should return None for unparseable conditions"""
condition = parse_condition("something random and complex")
self.assertIsNone(condition)
class TestParseConditionalAbility(unittest.TestCase):
"""Tests for parse_conditional_ability function"""
def test_if_control_deal_damage(self):
"""Should parse 'If you control X, deal Y damage' pattern"""
text = "If you control Cloud, deal 5000 damage to target Forward."
result = parse_conditional_ability(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "CONDITIONAL")
self.assertIn("condition", result)
self.assertIn("then_effects", result)
self.assertTrue(len(result["then_effects"]) > 0)
def test_if_damage_received_gain_brave(self):
"""Should parse damage received condition with ability grant"""
text = "If you have received 5 or more damage, this Forward gains Brave."
result = parse_conditional_ability(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "CONDITIONAL")
# Even if condition is UNKNOWN, the structure should be correct
self.assertIn("condition", result)
self.assertIn("then_effects", result)
def test_not_if_you_do(self):
"""Should NOT parse 'If you do so' as conditional (that's chained)"""
text = "If you do so, draw 2 cards."
result = parse_conditional_ability(text)
self.assertIsNone(result)
def test_non_conditional_text(self):
"""Should return None for non-conditional text"""
text = "Deal 5000 damage to target Forward."
result = parse_conditional_ability(text)
self.assertIsNone(result)
class TestParseChainedEffect(unittest.TestCase):
"""Tests for parse_chained_effect function"""
def test_discard_if_you_do_damage(self):
"""Should parse 'Discard 1 card. If you do so, deal damage' pattern"""
text = "Discard 1 card from your hand. If you do so, deal 7000 damage to target Forward."
result = parse_chained_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "CHAINED_EFFECT")
self.assertEqual(result["chain_condition"], "IF_YOU_DO")
self.assertIn("primary_effect", result)
self.assertIn("chain_effects", result)
self.assertEqual(result["primary_effect"]["type"], "DISCARD")
def test_when_you_do(self):
"""Should parse 'When you do so' variant"""
text = "Dull 1 Backup. When you do so, draw 1 card."
result = parse_chained_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "CHAINED_EFFECT")
def test_no_chain_pattern(self):
"""Should return None for text without chain pattern"""
text = "Deal 5000 damage. Draw 1 card."
result = parse_chained_effect(text)
self.assertIsNone(result)
class TestParseScalingEffect(unittest.TestCase):
"""Tests for parse_scaling_effect function"""
def test_damage_for_each_damage_received(self):
"""Should parse 'Deal X damage for each damage received' pattern"""
text = "Deal it 1000 damage for each point of damage you have received."
result = parse_scaling_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "SCALING_EFFECT")
self.assertEqual(result["scale_by"], "DAMAGE_RECEIVED")
self.assertEqual(result["multiplier"], 1000)
self.assertEqual(result["base_effect"]["type"], "DAMAGE")
def test_power_for_each_forward(self):
"""Should parse '+X power for each Forward you control' pattern"""
text = "This Forward gains +1000 power for each Forward you control until the end of the turn."
result = parse_scaling_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "SCALING_EFFECT")
self.assertEqual(result["scale_by"], "FORWARDS_CONTROLLED")
self.assertEqual(result["multiplier"], 1000)
self.assertEqual(result["base_effect"]["type"], "POWER_MOD")
def test_draw_for_each_forward(self):
"""Should parse 'Draw X cards for each Forward' pattern"""
text = "Draw 1 card for each Forward your opponent controls."
result = parse_scaling_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "SCALING_EFFECT")
self.assertEqual(result["multiplier"], 1)
self.assertEqual(result["base_effect"]["type"], "DRAW")
def test_no_scaling_pattern(self):
"""Should return None for text without scaling pattern"""
text = "Deal 5000 damage to target Forward."
result = parse_scaling_effect(text)
self.assertIsNone(result)
class TestParseEffectsIntegration(unittest.TestCase):
"""Integration tests for parse_effects with conditionals"""
def test_chained_effect_detected(self):
"""parse_effects should return CHAINED_EFFECT for 'if you do' text"""
text = "Discard 1 card from your hand. If you do, draw 2 cards."
effects = parse_effects(text)
self.assertEqual(len(effects), 1)
self.assertEqual(effects[0]["type"], "CHAINED_EFFECT")
def test_scaling_effect_detected(self):
"""parse_effects should return SCALING_EFFECT for 'for each' text"""
text = "Deal it 1000 damage for each point of damage you have received."
effects = parse_effects(text)
self.assertEqual(len(effects), 1)
self.assertEqual(effects[0]["type"], "SCALING_EFFECT")
def test_conditional_effect_detected(self):
"""parse_effects should return CONDITIONAL for 'If X, Y' text"""
text = "If you control Cloud, deal 5000 damage to target Forward."
effects = parse_effects(text)
self.assertEqual(len(effects), 1)
self.assertEqual(effects[0]["type"], "CONDITIONAL")
def test_normal_effect_not_conditional(self):
"""parse_effects should NOT return CONDITIONAL for normal effects"""
text = "Draw 2 cards."
effects = parse_effects(text)
self.assertEqual(len(effects), 1)
self.assertEqual(effects[0]["type"], "DRAW")
class TestRealCardExamples(unittest.TestCase):
"""Tests using real card text examples"""
def test_warrior_damage_scaling(self):
"""Test Warrior card with damage scaling"""
text = "Deal it 1000 damage for each point of damage you have received."
result = parse_scaling_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "SCALING_EFFECT")
self.assertEqual(result["scale_by"], "DAMAGE_RECEIVED")
self.assertEqual(result["multiplier"], 1000)
def test_zidane_chained_effect(self):
"""Test Zidane-style chained effect"""
text = "Discard 1 card from your hand. If you do so, choose 1 Forward. Deal it 7000 damage."
result = parse_chained_effect(text)
self.assertIsNotNone(result)
self.assertEqual(result["type"], "CHAINED_EFFECT")
self.assertEqual(result["primary_effect"]["type"], "DISCARD")
self.assertTrue(len(result["chain_effects"]) > 0)
if __name__ == "__main__":
unittest.main(verbosity=2)