Merge pull request #35 from mwisnowski/features/non-basic-land-tagging

feature: add non-basic land types to tagging mechanics
This commit is contained in:
mwisnowski 2025-10-13 15:47:13 -07:00 committed by GitHub
commit 575b85b254
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 402 additions and 177 deletions

View file

@ -10,9 +10,10 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
## [Unreleased]
### Summary
- Fixed issues with custom themes in the web UI.
- Added non-basic land type tagging (i.e. Caves, Deserts, Gates, etc...) in the tagging module.
### Added
- _No unreleased changes yet._
- Non-basic land type tagging (i.e. Caves, Deserts, Gates, etc...) in the tagging module.
### Changed
- _No unreleased changes yet._

View file

@ -1,14 +1,15 @@
# MTG Python Deckbuilder ${VERSION}
## [Unreleased]
### Summary
- _No unreleased changes yet._
- Fixed issues with custom themes in the web UI.
- Added non-basic land type tagging (i.e. Caves, Deserts, Gates, etc...) in the tagging module.
### Added
- _No unreleased changes yet._
- Non-basic land type tagging (i.e. Caves, Deserts, Gates, etc...) in the tagging module.
### Changed
- _No unreleased changes yet._
### Fixed
- _No unreleased changes yet._
- Custom theme fuzzy matching now accepts selection.
- Custom themes may now be removed from the list.

View file

@ -44,8 +44,14 @@ class LandMiscUtilityMixin:
return
basics = self._basic_land_names()
already = set(self.card_library.keys())
top_n = getattr(bc, 'MISC_LAND_TOP_POOL_SIZE', 30)
top_n = getattr(bc, 'MISC_LAND_TOP_POOL_SIZE', 60)
use_full = getattr(bc, 'MISC_LAND_USE_FULL_POOL', False)
if not use_full:
rng = getattr(self, 'rng', None)
pool_multiplier = rng.uniform(1.2, 2.0) if rng else 1.5
top_n = int(top_n * pool_multiplier)
if getattr(self, 'show_diagnostics', False):
self.output_func(f"[Diagnostics] Misc Step pool size multiplier: {pool_multiplier:.2f}x (base={getattr(bc, 'MISC_LAND_TOP_POOL_SIZE', 60)} → effective={top_n})")
effective_n = 999999 if use_full else top_n
top_candidates = bu.select_top_land_candidates(df, already, basics, effective_n)
# Dynamic EDHREC keep percent

View file

@ -730,6 +730,18 @@ def build_catalog(limit: int, verbose: bool) -> Dict[str, Any]:
merged = [s for s in merged if s not in special_noise]
# If theme is one of the special ones, keep the other if present (no action needed beyond above filter logic).
# Land type theme filtering: Gates/Caves/Spheres are land types, not artifact/token mechanics.
# Rationale: These themes tag specific land cards, creating spurious correlations with artifact/token
# themes when those cards happen to also produce artifacts/tokens (e.g., Tireless Tracker in Gates decks).
# Filter out artifact/token synergies that don't make thematic sense for land-type-matters strategies.
land_type_themes = {"Gates Matter"}
incompatible_with_land_types = {
"Investigate", "Clue Token", "Detective Kindred"
}
if theme in land_type_themes:
merged = [s for s in merged if s not in incompatible_with_land_types]
# For non-land-type themes, don't filter (they can legitimately synergize with these)
if synergy_cap > 0 and len(merged) > synergy_cap:
ce_len = len(curated_list) + len([s for s in enforced_list if s not in curated_list])
if ce_len < synergy_cap:

View file

@ -417,6 +417,8 @@ def _tag_mechanical_themes(df: pd.DataFrame, color: str) -> None:
print('\n====================\n')
tag_for_bending(df, color)
print('\n====================\n')
tag_for_land_types(df, color)
print('\n====================\n')
tag_for_web_slinging(df, color)
print('\n====================\n')
tag_for_tokens(df, color)
@ -4239,6 +4241,55 @@ def tag_for_web_slinging(df: pd.DataFrame, color: str) -> None:
logger.error(f'Error tagging Web-Slinging keywords: {str(e)}')
raise
### Tag for land types
def tag_for_land_types(df: pd.DataFrame, color: str) -> None:
"""Tag card for specific non-basic land types.
Looks for 'Cave', 'Desert', 'Gate', 'Lair', 'Locus', 'Sphere', 'Urza's' in rules text and applies tags accordingly.
"""
try:
cave_mask = (
(tag_utils.create_text_mask(df, 'Cave') & ~tag_utils.create_text_mask(df, 'scavenge')) |
tag_utils.create_type_mask(df, 'Cave')
)
desert_mask = (
tag_utils.create_text_mask(df, 'Desert') |
tag_utils.create_type_mask(df, 'Desert')
)
gate_mask = (
(
tag_utils.create_text_mask(df, 'Gate') &
~tag_utils.create_text_mask(df, 'Agate') &
~tag_utils.create_text_mask(df, 'Legate') &
~tag_utils.create_text_mask(df, 'Throw widethe Gates') &
~tag_utils.create_text_mask(df, 'Eternity Gate') &
~tag_utils.create_text_mask(df, 'Investigates')
) |
tag_utils.create_text_mask(df, 'Gate card') |
tag_utils.create_type_mask(df, 'Gate')
)
lair_mask = (tag_utils.create_type_mask(df, 'Lair'))
locus_mask = (tag_utils.create_type_mask(df, 'Locus'))
sphere_mask = (
(tag_utils.create_text_mask(df, 'Sphere') & ~tag_utils.create_text_mask(df, 'Detention Sphere')) |
tag_utils.create_type_mask(df, 'Sphere'))
urzas_mask = (tag_utils.create_type_mask(df, "Urza's"))
rules = [
{'mask': cave_mask, 'tags': ['Caves Matter', 'Lands Matter']},
{'mask': desert_mask, 'tags': ['Deserts Matter', 'Lands Matter']},
{'mask': gate_mask, 'tags': ['Gates Matter', 'Lands Matter']},
{'mask': lair_mask, 'tags': ['Lairs Matter', 'Lands Matter']},
{'mask': locus_mask, 'tags': ['Locus Matter', 'Lands Matter']},
{'mask': sphere_mask, 'tags': ['Spheres Matter', 'Lands Matter']},
{'mask': urzas_mask, 'tags': ["Urza's Lands Matter", 'Lands Matter']},
]
tag_utils.tag_with_rules_and_logging(df, rules, 'non-basic land types', color=color, logger=logger)
except Exception as e:
logger.error(f'Error tagging non-basic land types: {str(e)}')
raise
## Big Mana
def create_big_mana_cost_mask(df: pd.DataFrame) -> pd.Series:
"""Create a boolean mask for cards with high mana costs or X costs.

File diff suppressed because it is too large Load diff