mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-16 15:40:12 +01:00
With assistance from Github CoPilot, massively overhauled the builder functionality, splitting it into smaller modules to provide a better step-by-step focus and drastically reduce the overall size of the core builder module
This commit is contained in:
parent
ff1912f979
commit
760c36d75d
17 changed files with 3044 additions and 2602 deletions
115
code/deck_builder/phases/phase2_lands_basics.py
Normal file
115
code/deck_builder/phases/phase2_lands_basics.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
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'
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue