mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-16 23:50:12 +01:00
feature: add non-basic land types to tagging mechanics
This commit is contained in:
parent
fbb85645e6
commit
4a8d71b16b
6 changed files with 402 additions and 177 deletions
|
|
@ -10,9 +10,10 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Summary
|
### Summary
|
||||||
- Fixed issues with custom themes in the web UI.
|
- 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
|
### Added
|
||||||
- _No unreleased changes yet._
|
- Non-basic land type tagging (i.e. Caves, Deserts, Gates, etc...) in the tagging module.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- _No unreleased changes yet._
|
- _No unreleased changes yet._
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
# MTG Python Deckbuilder ${VERSION}
|
# MTG Python Deckbuilder ${VERSION}
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
### Summary
|
### 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
|
### Added
|
||||||
- _No unreleased changes yet._
|
- Non-basic land type tagging (i.e. Caves, Deserts, Gates, etc...) in the tagging module.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- _No unreleased changes yet._
|
- _No unreleased changes yet._
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- _No unreleased changes yet._
|
- Custom theme fuzzy matching now accepts selection.
|
||||||
|
- Custom themes may now be removed from the list.
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,14 @@ class LandMiscUtilityMixin:
|
||||||
return
|
return
|
||||||
basics = self._basic_land_names()
|
basics = self._basic_land_names()
|
||||||
already = set(self.card_library.keys())
|
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)
|
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
|
effective_n = 999999 if use_full else top_n
|
||||||
top_candidates = bu.select_top_land_candidates(df, already, basics, effective_n)
|
top_candidates = bu.select_top_land_candidates(df, already, basics, effective_n)
|
||||||
# Dynamic EDHREC keep percent
|
# Dynamic EDHREC keep percent
|
||||||
|
|
|
||||||
|
|
@ -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]
|
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).
|
# 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:
|
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])
|
ce_len = len(curated_list) + len([s for s in enforced_list if s not in curated_list])
|
||||||
if ce_len < synergy_cap:
|
if ce_len < synergy_cap:
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,8 @@ def _tag_mechanical_themes(df: pd.DataFrame, color: str) -> None:
|
||||||
print('\n====================\n')
|
print('\n====================\n')
|
||||||
tag_for_bending(df, color)
|
tag_for_bending(df, color)
|
||||||
print('\n====================\n')
|
print('\n====================\n')
|
||||||
|
tag_for_land_types(df, color)
|
||||||
|
print('\n====================\n')
|
||||||
tag_for_web_slinging(df, color)
|
tag_for_web_slinging(df, color)
|
||||||
print('\n====================\n')
|
print('\n====================\n')
|
||||||
tag_for_tokens(df, color)
|
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)}')
|
logger.error(f'Error tagging Web-Slinging keywords: {str(e)}')
|
||||||
raise
|
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
|
## Big Mana
|
||||||
def create_big_mana_cost_mask(df: pd.DataFrame) -> pd.Series:
|
def create_big_mana_cost_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards with high mana costs or X costs.
|
"""Create a boolean mask for cards with high mana costs or X costs.
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue