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
180
code/deck_builder/phases/phase2_lands_misc.py
Normal file
180
code/deck_builder/phases/phase2_lands_misc.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from .. import builder_constants as bc
|
||||
from .. import builder_utils as bu
|
||||
|
||||
|
||||
class LandMiscUtilityMixin:
|
||||
"""Mixin for Land Building Step 7: Misc / Utility Lands.
|
||||
|
||||
Provides:
|
||||
- add_misc_utility_lands
|
||||
- run_land_step7
|
||||
- tag-driven suggestion queue helpers (_build_tag_driven_land_suggestions, _apply_land_suggestions_if_room)
|
||||
|
||||
Extracted verbatim (with light path adjustments) from original monolithic builder.
|
||||
"""
|
||||
|
||||
def add_misc_utility_lands(self, requested_count: Optional[int] = None): # type: ignore[override]
|
||||
if not getattr(self, 'files_to_load', None):
|
||||
try:
|
||||
self.determine_color_identity()
|
||||
self.setup_dataframes()
|
||||
except Exception as e:
|
||||
self.output_func(f"Cannot add misc utility lands until color identity resolved: {e}")
|
||||
return
|
||||
df = getattr(self, '_combined_cards_df', None)
|
||||
if df is None or df.empty:
|
||||
self.output_func("Misc Lands: No card pool loaded.")
|
||||
return
|
||||
|
||||
land_target = getattr(self, 'ideal_counts', {}).get('lands', getattr(bc, 'DEFAULT_LAND_COUNT', 35)) if getattr(self, 'ideal_counts', None) else getattr(bc, 'DEFAULT_LAND_COUNT', 35)
|
||||
current = self._current_land_count()
|
||||
remaining_capacity = max(0, land_target - current)
|
||||
if remaining_capacity <= 0:
|
||||
remaining_capacity = 0
|
||||
|
||||
min_basic_cfg = getattr(bc, 'DEFAULT_BASIC_LAND_COUNT', 20)
|
||||
if hasattr(self, 'ideal_counts') and self.ideal_counts:
|
||||
min_basic_cfg = self.ideal_counts.get('basic_lands', min_basic_cfg)
|
||||
basic_floor = self._basic_floor(min_basic_cfg)
|
||||
|
||||
if requested_count is not None:
|
||||
desired = max(0, int(requested_count))
|
||||
else:
|
||||
desired = max(0, land_target - current)
|
||||
if desired == 0:
|
||||
self.output_func("Misc Lands: No remaining land capacity; skipping.")
|
||||
return
|
||||
|
||||
basics = self._basic_land_names()
|
||||
already = set(self.card_library.keys())
|
||||
top_n = getattr(bc, 'MISC_LAND_TOP_POOL_SIZE', 30)
|
||||
top_candidates = bu.select_top_land_candidates(df, already, basics, top_n)
|
||||
if not top_candidates:
|
||||
self.output_func("Misc Lands: No remaining candidate lands.")
|
||||
return
|
||||
|
||||
weighted_pool: List[tuple[str,int]] = []
|
||||
base_weight_fix = getattr(bc, 'MISC_LAND_COLOR_FIX_PRIORITY_WEIGHT', 2)
|
||||
fetch_names = set()
|
||||
for seq in getattr(bc, 'COLOR_TO_FETCH_LANDS', {}).values():
|
||||
for nm in seq:
|
||||
fetch_names.add(nm)
|
||||
for nm in getattr(bc, 'GENERIC_FETCH_LANDS', []):
|
||||
fetch_names.add(nm)
|
||||
existing_fetch_count = bu.count_existing_fetches(self.card_library)
|
||||
fetch_cap = getattr(bc, 'FETCH_LAND_MAX_CAP', 99)
|
||||
remaining_fetch_slots = max(0, fetch_cap - existing_fetch_count)
|
||||
|
||||
for edh_val, name, tline, text_lower in top_candidates:
|
||||
w = 1
|
||||
if bu.is_color_fixing_land(tline, text_lower):
|
||||
w *= base_weight_fix
|
||||
if name in fetch_names and remaining_fetch_slots <= 0:
|
||||
continue
|
||||
weighted_pool.append((name, w))
|
||||
|
||||
if self._current_land_count() >= land_target and desired > 0:
|
||||
slots_needed = desired
|
||||
freed = 0
|
||||
while freed < slots_needed and self._count_basic_lands() > basic_floor:
|
||||
target_basic = self._choose_basic_to_trim()
|
||||
if not target_basic or not self._decrement_card(target_basic):
|
||||
break
|
||||
freed += 1
|
||||
if freed == 0 and self._current_land_count() >= land_target:
|
||||
self.output_func("Misc Lands: Cannot free capacity; skipping.")
|
||||
return
|
||||
|
||||
remaining_capacity = max(0, land_target - self._current_land_count())
|
||||
desired = min(desired, remaining_capacity, len(weighted_pool))
|
||||
if desired <= 0:
|
||||
self.output_func("Misc Lands: No capacity after trimming; skipping.")
|
||||
return
|
||||
|
||||
rng = getattr(self, 'rng', None)
|
||||
chosen = bu.weighted_sample_without_replacement(weighted_pool, desired, rng=rng)
|
||||
|
||||
added: List[str] = []
|
||||
for nm in chosen:
|
||||
if self._current_land_count() >= land_target:
|
||||
break
|
||||
# Misc utility lands baseline role
|
||||
self.add_card(nm, card_type='Land', role='utility', sub_role='misc', added_by='lands_step7')
|
||||
added.append(nm)
|
||||
|
||||
self.output_func("\nMisc Utility Lands Added (Step 7):")
|
||||
if not added:
|
||||
self.output_func(" (None added)")
|
||||
else:
|
||||
width = max(len(n) for n in added)
|
||||
for n in added:
|
||||
note = ''
|
||||
row = next((r for r in top_candidates if r[1] == n), None)
|
||||
if row:
|
||||
for edh_val, name2, tline2, text_lower2 in top_candidates:
|
||||
if name2 == n and bu.is_color_fixing_land(tline2, text_lower2):
|
||||
note = '(fixing)'
|
||||
break
|
||||
self.output_func(f" {n.ljust(width)} : 1 {note}")
|
||||
self.output_func(f" Land Count Now : {self._current_land_count()} / {land_target}")
|
||||
|
||||
def run_land_step7(self, requested_count: Optional[int] = None): # type: ignore[override]
|
||||
self.add_misc_utility_lands(requested_count=requested_count)
|
||||
self._enforce_land_cap(step_label="Utility (Step 7)")
|
||||
self._build_tag_driven_land_suggestions()
|
||||
self._apply_land_suggestions_if_room()
|
||||
|
||||
# ---- Tag-driven suggestion helpers (used after Step 7) ----
|
||||
def _build_tag_driven_land_suggestions(self): # type: ignore[override]
|
||||
suggestions = bu.build_tag_driven_suggestions(self)
|
||||
if suggestions:
|
||||
self.suggested_lands_queue.extend(suggestions)
|
||||
|
||||
def _apply_land_suggestions_if_room(self): # type: ignore[override]
|
||||
if not self.suggested_lands_queue:
|
||||
return
|
||||
land_target = getattr(self, 'ideal_counts', {}).get('lands', getattr(bc, 'DEFAULT_LAND_COUNT', 35)) if getattr(self, 'ideal_counts', None) else getattr(bc, 'DEFAULT_LAND_COUNT', 35)
|
||||
applied: List[Dict] = []
|
||||
remaining: List[Dict] = []
|
||||
min_basic_cfg = getattr(bc, 'DEFAULT_BASIC_LAND_COUNT', 20)
|
||||
if hasattr(self, 'ideal_counts') and self.ideal_counts:
|
||||
min_basic_cfg = self.ideal_counts.get('basic_lands', min_basic_cfg)
|
||||
basic_floor = self._basic_floor(min_basic_cfg)
|
||||
for sug in self.suggested_lands_queue:
|
||||
name = sug['name']
|
||||
if name in self.card_library:
|
||||
continue
|
||||
if not sug['condition'](self):
|
||||
remaining.append(sug)
|
||||
continue
|
||||
if self._current_land_count() >= land_target:
|
||||
if sug.get('defer_if_full'):
|
||||
if self._count_basic_lands() > basic_floor:
|
||||
target_basic = self._choose_basic_to_trim()
|
||||
if not target_basic or not self._decrement_card(target_basic):
|
||||
remaining.append(sug)
|
||||
continue
|
||||
else:
|
||||
remaining.append(sug)
|
||||
continue
|
||||
# Tag suggestion additions (flex if marked)
|
||||
self.add_card(
|
||||
name,
|
||||
card_type='Land',
|
||||
role=('flex' if sug.get('flex') else 'utility'),
|
||||
sub_role='tag-suggested',
|
||||
added_by='tag_suggestion',
|
||||
trigger_tag=sug.get('reason')
|
||||
)
|
||||
applied.append(sug)
|
||||
self.suggested_lands_queue = remaining
|
||||
if applied:
|
||||
self.output_func("\nTag-Driven Utility Lands Added:")
|
||||
width = max(len(s['name']) for s in applied)
|
||||
for s in applied:
|
||||
role = ' (flex)' if s.get('flex') else ''
|
||||
self.output_func(f" {s['name'].ljust(width)} : 1 {s['reason']}{role}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue