mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-22 04:50:46 +02:00
151 lines
7 KiB
Python
151 lines
7 KiB
Python
![]() |
from __future__ import annotations
|
||
|
from typing import List, Dict
|
||
|
from .. import builder_constants as bc
|
||
|
|
||
|
"""Phase 2 (part 2): Staple nonbasic lands (Land Step 2).
|
||
|
|
||
|
Extracted logic for adding generic staple lands (excluding kindred / tribal, fetches, etc.).
|
||
|
|
||
|
Provided by LandStaplesMixin:
|
||
|
- _current_land_count(): counts land cards currently in library.
|
||
|
- add_staple_lands(): core staple inclusion logic with capacity management.
|
||
|
- run_land_step2(): public wrapper invoked by orchestrator.
|
||
|
|
||
|
Expected host DeckBuilder attributes / methods:
|
||
|
- card_library (dict), output_func
|
||
|
- files_to_load, determine_color_identity(), setup_dataframes()
|
||
|
- ideal_counts (dict) possibly present
|
||
|
- commander_tags, selected_tags, commander_row
|
||
|
- helper methods: _basic_floor, _count_basic_lands, _choose_basic_to_trim, _decrement_card, _enforce_land_cap
|
||
|
- builder_constants imported as bc in host package (we import locally for clarity)
|
||
|
"""
|
||
|
|
||
|
# (Imports moved to top for lint compliance)
|
||
|
|
||
|
|
||
|
class LandStaplesMixin:
|
||
|
# ---------------------------
|
||
|
# Land Building Step 2: Staple Nonbasic Lands (NO Kindred yet)
|
||
|
# ---------------------------
|
||
|
def _current_land_count(self) -> int: # type: ignore[override]
|
||
|
"""Return total number of land cards currently in the library (counts duplicates)."""
|
||
|
total = 0
|
||
|
for name, entry in self.card_library.items(): # type: ignore[attr-defined]
|
||
|
ctype = entry.get('Card Type', '')
|
||
|
if ctype and 'land' in ctype.lower():
|
||
|
total += entry.get('Count', 1)
|
||
|
continue
|
||
|
df = getattr(self, '_combined_cards_df', None)
|
||
|
if df is not None and 'name' in getattr(df, 'columns', []):
|
||
|
try:
|
||
|
row = df[df['name'] == name]
|
||
|
if not row.empty:
|
||
|
type_field = str(row.iloc[0].get('type', '')).lower()
|
||
|
if 'land' in type_field:
|
||
|
total += entry.get('Count', 1)
|
||
|
except Exception:
|
||
|
continue
|
||
|
return total
|
||
|
|
||
|
def add_staple_lands(self): # type: ignore[override]
|
||
|
"""Add generic staple lands defined in STAPLE_LAND_CONDITIONS (excluding kindred lands).
|
||
|
|
||
|
Respects total land target (ideal_counts['lands']). Skips additions once target reached.
|
||
|
Conditions may use commander tags (all available, not just selected), color identity, and commander power.
|
||
|
"""
|
||
|
if not getattr(self, 'files_to_load', []):
|
||
|
try:
|
||
|
self.determine_color_identity()
|
||
|
self.setup_dataframes()
|
||
|
except Exception as e: # pragma: no cover - defensive
|
||
|
self.output_func(f"Cannot add staple lands until color identity resolved: {e}")
|
||
|
return
|
||
|
land_target = None
|
||
|
if hasattr(self, 'ideal_counts') and getattr(self, 'ideal_counts'):
|
||
|
land_target = self.ideal_counts.get('lands') # type: ignore[attr-defined]
|
||
|
if land_target is None:
|
||
|
land_target = getattr(bc, 'DEFAULT_LAND_COUNT', 35)
|
||
|
min_basic_cfg = getattr(bc, 'DEFAULT_BASIC_LAND_COUNT', 20)
|
||
|
if hasattr(self, 'ideal_counts') and getattr(self, 'ideal_counts'):
|
||
|
min_basic_cfg = self.ideal_counts.get('basic_lands', min_basic_cfg) # type: ignore[attr-defined]
|
||
|
basic_floor = self._basic_floor(min_basic_cfg) # type: ignore[attr-defined]
|
||
|
|
||
|
def ensure_capacity() -> bool:
|
||
|
if self._current_land_count() < land_target: # type: ignore[attr-defined]
|
||
|
return True
|
||
|
if self._count_basic_lands() <= basic_floor: # type: ignore[attr-defined]
|
||
|
return False
|
||
|
target_basic = self._choose_basic_to_trim() # type: ignore[attr-defined]
|
||
|
if not target_basic:
|
||
|
return False
|
||
|
if not self._decrement_card(target_basic): # type: ignore[attr-defined]
|
||
|
return False
|
||
|
return self._current_land_count() < land_target # type: ignore[attr-defined]
|
||
|
|
||
|
commander_tags_all = set(getattr(self, 'commander_tags', []) or []) | set(getattr(self, 'selected_tags', []) or [])
|
||
|
colors = getattr(self, 'color_identity', []) or []
|
||
|
commander_power = 0
|
||
|
try:
|
||
|
row = getattr(self, 'commander_row', None)
|
||
|
if row is not None:
|
||
|
raw_power = row.get('power')
|
||
|
if isinstance(raw_power, (int, float)):
|
||
|
commander_power = int(raw_power)
|
||
|
elif isinstance(raw_power, str) and raw_power.isdigit():
|
||
|
commander_power = int(raw_power)
|
||
|
except Exception:
|
||
|
commander_power = 0
|
||
|
|
||
|
added: List[str] = []
|
||
|
reasons: Dict[str, str] = {}
|
||
|
for land_name, cond in getattr(bc, 'STAPLE_LAND_CONDITIONS', {}).items():
|
||
|
if not ensure_capacity():
|
||
|
self.output_func("Staple Lands: Cannot free capacity without violating basic floor; stopping additions.")
|
||
|
break
|
||
|
if land_name in self.card_library: # type: ignore[attr-defined]
|
||
|
continue
|
||
|
try:
|
||
|
include = cond(list(commander_tags_all), colors, commander_power)
|
||
|
except Exception:
|
||
|
include = False
|
||
|
if include:
|
||
|
self.add_card(
|
||
|
land_name,
|
||
|
card_type='Land',
|
||
|
role='staple',
|
||
|
sub_role='generic-staple',
|
||
|
added_by='lands_step2'
|
||
|
) # type: ignore[attr-defined]
|
||
|
added.append(land_name)
|
||
|
if land_name == 'Command Tower':
|
||
|
reasons[land_name] = f"multi-color ({len(colors)} colors)"
|
||
|
elif land_name == 'Exotic Orchard':
|
||
|
reasons[land_name] = f"multi-color ({len(colors)} colors)"
|
||
|
elif land_name == 'War Room':
|
||
|
reasons[land_name] = f"<=2 colors ({len(colors)})"
|
||
|
elif land_name == 'Reliquary Tower':
|
||
|
reasons[land_name] = 'always include'
|
||
|
elif land_name == 'Ash Barrens':
|
||
|
reasons[land_name] = 'no Landfall tag'
|
||
|
elif land_name == "Rogue's Passage":
|
||
|
reasons[land_name] = f"commander power {commander_power} >=5"
|
||
|
self.output_func("\nStaple Lands Added (Step 2):")
|
||
|
if not added:
|
||
|
self.output_func(" (None added)")
|
||
|
else:
|
||
|
width = max(len(n) for n in added)
|
||
|
for n in added:
|
||
|
reason = reasons.get(n, '')
|
||
|
self.output_func(f" {n.ljust(width)} : 1 {('(' + reason + ')') if reason else ''}")
|
||
|
self.output_func(f" Land Count Now : {self._current_land_count()} / {land_target}") # type: ignore[attr-defined]
|
||
|
|
||
|
def run_land_step2(self): # type: ignore[override]
|
||
|
"""Public wrapper for adding generic staple nonbasic lands (excluding kindred)."""
|
||
|
self.add_staple_lands()
|
||
|
self._enforce_land_cap(step_label="Staples (Step 2)") # type: ignore[attr-defined]
|
||
|
|
||
|
|
||
|
__all__ = [
|
||
|
'LandStaplesMixin'
|
||
|
]
|