mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-22 04:50:46 +02:00
Finished core deck builder. Still need to do cleanup, implement cards that are not singleton, and pricing
This commit is contained in:
parent
8478bc2534
commit
b7ee6ea57d
5 changed files with 1074 additions and 26 deletions
File diff suppressed because it is too large
Load diff
|
@ -157,7 +157,7 @@ DEFAULT_MAX_CARD_PRICE: Final[float] = 20.0 # Default maximum price per card
|
||||||
# Deck composition defaults
|
# Deck composition defaults
|
||||||
DEFAULT_RAMP_COUNT: Final[int] = 8 # Default number of ramp pieces
|
DEFAULT_RAMP_COUNT: Final[int] = 8 # Default number of ramp pieces
|
||||||
DEFAULT_LAND_COUNT: Final[int] = 35 # Default total land count
|
DEFAULT_LAND_COUNT: Final[int] = 35 # Default total land count
|
||||||
DEFAULT_BASIC_LAND_COUNT: Final[int] = 10 # Default minimum basic lands
|
DEFAULT_BASIC_LAND_COUNT: Final[int] = 15 # Default minimum basic lands
|
||||||
DEFAULT_NON_BASIC_LAND_SLOTS: Final[int] = 10 # Default number of non-basic land slots to reserve
|
DEFAULT_NON_BASIC_LAND_SLOTS: Final[int] = 10 # Default number of non-basic land slots to reserve
|
||||||
DEFAULT_BASICS_PER_COLOR: Final[int] = 5 # Default number of basic lands to add per color
|
DEFAULT_BASICS_PER_COLOR: Final[int] = 5 # Default number of basic lands to add per color
|
||||||
|
|
||||||
|
@ -364,7 +364,7 @@ LAND_REMOVAL_MAX_ATTEMPTS: Final[int] = 3
|
||||||
PROTECTED_LANDS: Final[List[str]] = BASIC_LANDS + [land['name'] for land in KINDRED_STAPLE_LANDS]
|
PROTECTED_LANDS: Final[List[str]] = BASIC_LANDS + [land['name'] for land in KINDRED_STAPLE_LANDS]
|
||||||
|
|
||||||
# Other defaults
|
# Other defaults
|
||||||
DEFAULT_CREATURE_COUNT: Final[int] = 30 # Default number of creatures
|
DEFAULT_CREATURE_COUNT: Final[int] = 25 # Default number of creatures
|
||||||
DEFAULT_REMOVAL_COUNT: Final[int] = 10 # Default number of spot removal spells
|
DEFAULT_REMOVAL_COUNT: Final[int] = 10 # Default number of spot removal spells
|
||||||
DEFAULT_WIPES_COUNT: Final[int] = 2 # Default number of board wipes
|
DEFAULT_WIPES_COUNT: Final[int] = 2 # Default number of board wipes
|
||||||
|
|
||||||
|
@ -375,7 +375,7 @@ DEFAULT_PROTECTION_COUNT: Final[int] = 8 # Default number of protection spells
|
||||||
DECK_COMPOSITION_PROMPTS: Final[Dict[str, str]] = {
|
DECK_COMPOSITION_PROMPTS: Final[Dict[str, str]] = {
|
||||||
'ramp': 'Enter desired number of ramp pieces (default: 8):',
|
'ramp': 'Enter desired number of ramp pieces (default: 8):',
|
||||||
'lands': 'Enter desired number of total lands (default: 35):',
|
'lands': 'Enter desired number of total lands (default: 35):',
|
||||||
'basic_lands': 'Enter minimum number of basic lands (default: 20):',
|
'basic_lands': 'Enter minimum number of basic lands (default: 15):',
|
||||||
'creatures': 'Enter desired number of creatures (default: 25):',
|
'creatures': 'Enter desired number of creatures (default: 25):',
|
||||||
'removal': 'Enter desired number of spot removal spells (default: 10):',
|
'removal': 'Enter desired number of spot removal spells (default: 10):',
|
||||||
'wipes': 'Enter desired number of board wipes (default: 2):',
|
'wipes': 'Enter desired number of board wipes (default: 2):',
|
||||||
|
|
|
@ -385,8 +385,11 @@ def select_color_balance_removal(builder, deficit_colors: set[str], overages: di
|
||||||
3. Mono-color non-flex land not producing deficit colors
|
3. Mono-color non-flex land not producing deficit colors
|
||||||
"""
|
"""
|
||||||
matrix_current = builder._compute_color_source_matrix()
|
matrix_current = builder._compute_color_source_matrix()
|
||||||
# Flex first
|
land_names = set(matrix_current.keys()) # ensure we only ever remove lands
|
||||||
|
# Flex lands first
|
||||||
for name, entry in builder.card_library.items():
|
for name, entry in builder.card_library.items():
|
||||||
|
if name not in land_names:
|
||||||
|
continue
|
||||||
if entry.get('Role') == 'flex':
|
if entry.get('Role') == 'flex':
|
||||||
colors = matrix_current.get(name, {})
|
colors = matrix_current.get(name, {})
|
||||||
if not any(colors.get(c, 0) for c in deficit_colors):
|
if not any(colors.get(c, 0) for c in deficit_colors):
|
||||||
|
@ -396,10 +399,12 @@ def select_color_balance_removal(builder, deficit_colors: set[str], overages: di
|
||||||
color_remove = max(overages.items(), key=lambda x: x[1])[0]
|
color_remove = max(overages.items(), key=lambda x: x[1])[0]
|
||||||
basic_map = {'W': 'Plains', 'U': 'Island', 'B': 'Swamp', 'R': 'Mountain', 'G': 'Forest'}
|
basic_map = {'W': 'Plains', 'U': 'Island', 'B': 'Swamp', 'R': 'Mountain', 'G': 'Forest'}
|
||||||
candidate = basic_map.get(color_remove)
|
candidate = basic_map.get(color_remove)
|
||||||
if candidate and candidate in builder.card_library:
|
if candidate and candidate in builder.card_library and candidate in land_names:
|
||||||
return candidate
|
return candidate
|
||||||
# Mono-color non-flex
|
# Mono-color non-flex lands
|
||||||
for name, entry in builder.card_library.items():
|
for name, entry in builder.card_library.items():
|
||||||
|
if name not in land_names:
|
||||||
|
continue
|
||||||
if entry.get('Role') == 'flex':
|
if entry.get('Role') == 'flex':
|
||||||
continue
|
continue
|
||||||
colors = matrix_current.get(name, {})
|
colors = matrix_current.get(name, {})
|
||||||
|
|
|
@ -106,7 +106,7 @@ def run_menu() -> NoReturn:
|
||||||
case 'Tag CSV Files':
|
case 'Tag CSV Files':
|
||||||
tagger.run_tagging()
|
tagger.run_tagging()
|
||||||
case 'Build a Deck':
|
case 'Build a Deck':
|
||||||
builder.determine_commander()
|
builder.build_deck_full()
|
||||||
case 'Quit':
|
case 'Quit':
|
||||||
logger.info("Exiting application")
|
logger.info("Exiting application")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
from deck_builder.builder import DeckBuilder
|
from deck_builder.builder import DeckBuilder
|
||||||
|
|
||||||
"""Non-interactive harness.
|
"""Non-interactive harness.
|
||||||
|
@ -13,19 +17,32 @@ Indices correspond to the numbered tag list presented during interaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
command_name: str = "Finneas, Ace Archer",
|
command_name: str = "Rocco, Street Chef",
|
||||||
add_creatures: bool = True,
|
add_creatures: bool = True,
|
||||||
|
add_non_creature_spells: bool = True,
|
||||||
|
# Fine-grained toggles (used only if add_non_creature_spells is False)
|
||||||
|
add_ramp: bool = True,
|
||||||
|
add_removal: bool = True,
|
||||||
|
add_wipes: bool = True,
|
||||||
|
add_card_advantage: bool = True,
|
||||||
|
add_protection: bool = True,
|
||||||
use_multi_theme: bool = True,
|
use_multi_theme: bool = True,
|
||||||
primary_choice: int = 11,
|
primary_choice: int = 9,
|
||||||
secondary_choice: int | None = None,
|
secondary_choice: Optional[int] = 1,
|
||||||
tertiary_choice: int | None = None,
|
tertiary_choice: Optional[int] = 11,
|
||||||
add_lands: bool = True,
|
add_lands: bool = True,
|
||||||
fetch_count: int | None = 3,
|
fetch_count: Optional[int] = 3,
|
||||||
dual_count: int | None = None,
|
dual_count: Optional[int] = None,
|
||||||
triple_count: int | None = None,
|
triple_count: Optional[int] = None,
|
||||||
utility_count: int | None = None,
|
utility_count: Optional[int] = None,
|
||||||
):
|
) -> DeckBuilder:
|
||||||
scripted_inputs: list[str] = []
|
"""Run a scripted non-interactive deck build and return the DeckBuilder instance.
|
||||||
|
|
||||||
|
Integer parameters (primary_choice, secondary_choice, tertiary_choice) correspond to the
|
||||||
|
numeric indices shown during interactive tag selection. Pass None to omit secondary/tertiary.
|
||||||
|
Optional counts (fetch_count, dual_count, triple_count, utility_count) constrain land steps.
|
||||||
|
"""
|
||||||
|
scripted_inputs: List[str] = []
|
||||||
# Commander query & selection
|
# Commander query & selection
|
||||||
scripted_inputs.append(command_name) # initial query
|
scripted_inputs.append(command_name) # initial query
|
||||||
scripted_inputs.append("1") # choose first search match to inspect
|
scripted_inputs.append("1") # choose first search match to inspect
|
||||||
|
@ -73,16 +90,36 @@ def run(
|
||||||
if hasattr(builder, 'run_land_step6'):
|
if hasattr(builder, 'run_land_step6'):
|
||||||
builder.run_land_step6(requested_count=triple_count)
|
builder.run_land_step6(requested_count=triple_count)
|
||||||
if hasattr(builder, 'run_land_step7'):
|
if hasattr(builder, 'run_land_step7'):
|
||||||
|
|
||||||
builder.run_land_step7(requested_count=utility_count)
|
builder.run_land_step7(requested_count=utility_count)
|
||||||
if hasattr(builder, 'run_land_step8'):
|
if hasattr(builder, 'run_land_step8'):
|
||||||
builder.run_land_step8()
|
builder.run_land_step8()
|
||||||
|
|
||||||
if add_creatures:
|
if add_creatures:
|
||||||
builder.add_creatures()
|
builder.add_creatures()
|
||||||
|
# Non-creature spell categories (ramp / removal / wipes / draw / protection)
|
||||||
|
if add_non_creature_spells and hasattr(builder, 'add_non_creature_spells'):
|
||||||
|
builder.add_non_creature_spells()
|
||||||
|
else:
|
||||||
|
# Allow selective invocation if orchestrator not desired
|
||||||
|
if add_ramp and hasattr(builder, 'add_ramp'):
|
||||||
|
builder.add_ramp()
|
||||||
|
if add_removal and hasattr(builder, 'add_removal'):
|
||||||
|
builder.add_removal()
|
||||||
|
if add_wipes and hasattr(builder, 'add_board_wipes'):
|
||||||
|
builder.add_board_wipes()
|
||||||
|
if add_card_advantage and hasattr(builder, 'add_card_advantage'):
|
||||||
|
builder.add_card_advantage()
|
||||||
|
if add_protection and hasattr(builder, 'add_protection'):
|
||||||
|
builder.add_protection()
|
||||||
|
|
||||||
|
|
||||||
builder.print_card_library()
|
# Suppress verbose library print in non-interactive run since CSV export is produced.
|
||||||
|
# builder.print_card_library()
|
||||||
builder.post_spell_land_adjust()
|
builder.post_spell_land_adjust()
|
||||||
|
# Export decklist CSV (commander first word + date)
|
||||||
|
if hasattr(builder, 'export_decklist_csv'):
|
||||||
|
builder.export_decklist_csv()
|
||||||
return builder
|
return builder
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue