mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-22 04:50:46 +02:00
115 lines
4.9 KiB
Python
115 lines
4.9 KiB
Python
from __future__ import annotations
|
|
from typing import Dict, Optional
|
|
from .. import builder_constants as bc
|
|
|
|
"""Phase 2 (part 1): Basic land addition logic (Land Step 1).
|
|
|
|
Extracted from the monolithic `builder.py` to begin modularizing land building.
|
|
|
|
Responsibilities provided by this mixin:
|
|
- add_basic_lands(): core allocation & addition of basic (or snow) lands.
|
|
- run_land_step1(): public wrapper invoked by the deck build orchestrator.
|
|
|
|
Expected attributes / methods on the host DeckBuilder:
|
|
- color_identity, selected_tags, commander_tags, ideal_counts
|
|
- determine_color_identity(), setup_dataframes(), add_card()
|
|
- output_func for user messaging
|
|
- bc (builder_constants) imported in builder module; we import locally here.
|
|
"""
|
|
|
|
# (Imports moved to top for lint compliance)
|
|
|
|
|
|
class LandBasicsMixin:
|
|
def add_basic_lands(self): # type: ignore[override]
|
|
"""Add basic (or snow basic) lands based on color identity.
|
|
|
|
Logic:
|
|
- Determine target basics = ceil(1.3 * ideal_basic_min) (rounded) but capped by total land target
|
|
- Evenly distribute among colored identity letters (W,U,B,R,G)
|
|
- If commander/selected tags include 'Snow' (case-insensitive) use snow basics mapping
|
|
- Colorless commander: use Wastes for the entire basic allocation
|
|
"""
|
|
# Ensure color identity determined
|
|
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 basics until color identity resolved: {e}")
|
|
return
|
|
|
|
# Ensure ideal counts (for min basics & total lands)
|
|
basic_min: Optional[int] = None
|
|
land_total: Optional[int] = None
|
|
if hasattr(self, 'ideal_counts') and getattr(self, 'ideal_counts'):
|
|
basic_min = self.ideal_counts.get('basic_lands') # type: ignore[attr-defined]
|
|
land_total = self.ideal_counts.get('lands') # type: ignore[attr-defined]
|
|
if basic_min is None:
|
|
basic_min = getattr(bc, 'DEFAULT_BASIC_LAND_COUNT', 20)
|
|
if land_total is None:
|
|
land_total = getattr(bc, 'DEFAULT_LAND_COUNT', 35)
|
|
|
|
# Target basics = 1.3 * minimum (rounded) but not exceeding total lands
|
|
target_basics = int(round(1.3 * basic_min))
|
|
if target_basics > land_total:
|
|
target_basics = land_total
|
|
if target_basics <= 0:
|
|
self.output_func("Target basic land count is zero; skipping basics.")
|
|
return
|
|
|
|
colors = [c for c in getattr(self, 'color_identity', []) if c in ['W', 'U', 'B', 'R', 'G']]
|
|
if not colors: # colorless special case -> Wastes only
|
|
colors = []
|
|
|
|
# Determine if snow preferred
|
|
selected_tags = getattr(self, 'selected_tags', []) or []
|
|
commander_tags = getattr(self, 'commander_tags', []) or []
|
|
tag_pool = selected_tags + commander_tags
|
|
use_snow = any('snow' in str(t).lower() for t in tag_pool)
|
|
snow_map = getattr(bc, 'SNOW_BASIC_LAND_MAPPING', {})
|
|
basic_map = getattr(bc, 'COLOR_TO_BASIC_LAND', {})
|
|
|
|
allocation: Dict[str, int] = {}
|
|
if not colors: # colorless
|
|
allocation_name = snow_map.get('C', 'Wastes') if use_snow else 'Wastes'
|
|
allocation[allocation_name] = target_basics
|
|
else:
|
|
n = len(colors)
|
|
base = target_basics // n
|
|
rem = target_basics % n
|
|
for idx, c in enumerate(sorted(colors)): # sorted for deterministic distribution
|
|
count = base + (1 if idx < rem else 0)
|
|
land_name = snow_map.get(c) if use_snow else basic_map.get(c)
|
|
if not land_name:
|
|
continue
|
|
allocation[land_name] = allocation.get(land_name, 0) + count
|
|
|
|
# Add to library
|
|
for land_name, count in allocation.items():
|
|
for _ in range(count):
|
|
# Role metadata: basics (or snow basics)
|
|
self.add_card(
|
|
land_name,
|
|
card_type='Land',
|
|
role='basic',
|
|
sub_role='snow-basic' if use_snow else 'basic',
|
|
added_by='lands_step1',
|
|
trigger_tag='Snow' if use_snow else None
|
|
)
|
|
|
|
# Summary output
|
|
self.output_func("\nBasic Lands Added:")
|
|
width = max((len(n) for n in allocation.keys()), default=0)
|
|
for name, cnt in allocation.items():
|
|
self.output_func(f" {name.ljust(width)} : {cnt}")
|
|
self.output_func(f" Total Basics : {sum(allocation.values())} (Target {target_basics}, Min {basic_min})")
|
|
|
|
def run_land_step1(self): # type: ignore[override]
|
|
"""Public wrapper to execute land building step 1 (basics)."""
|
|
self.add_basic_lands()
|
|
|
|
|
|
__all__ = [
|
|
'LandBasicsMixin'
|
|
]
|