mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-17 08:00:13 +01:00
Added logic to tagger and deck_builder for "hidden themes" related to multiple-copy cards, such as Hare Apparent or Dragon's Approach
This commit is contained in:
parent
c74d1ff03a
commit
0dfe53bb32
3 changed files with 561 additions and 304 deletions
690
deck_builder.py
690
deck_builder.py
|
|
@ -46,10 +46,21 @@ Land spread will ideally be handled based on pips and some adjustment
|
|||
is planned based on mana curve and ramp added.
|
||||
"""
|
||||
|
||||
def new_line():
|
||||
print('\n')
|
||||
def new_line(num_lines: int = 1) -> None:
|
||||
"""Print specified number of newlines for formatting output.
|
||||
|
||||
Args:
|
||||
num_lines (int): Number of newlines to print. Defaults to 1.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if num_lines < 0:
|
||||
raise ValueError("Number of lines cannot be negative")
|
||||
print('\n' * num_lines)
|
||||
|
||||
class DeckBuilder:
|
||||
|
||||
def __init__(self):
|
||||
self.card_library = pd.DataFrame()
|
||||
self.card_library['Card Name'] = pd.Series(dtype='str')
|
||||
|
|
@ -62,15 +73,35 @@ class DeckBuilder:
|
|||
self.set_max_card_price = False
|
||||
self.card_prices = {} if use_scrython else None
|
||||
|
||||
def validate_text(self, result):
|
||||
def pause_with_message(self, message="Press Enter to continue..."):
|
||||
"""Helper function to pause execution with a message."""
|
||||
print(f"\n{message}")
|
||||
input()
|
||||
|
||||
def validate_text(self, result: str) -> bool:
|
||||
"""Validate text input is not empty.
|
||||
|
||||
Args:
|
||||
result (str): Text input to validate
|
||||
|
||||
Returns:
|
||||
bool: True if text is not empty after stripping whitespace
|
||||
"""
|
||||
return bool(result and result.strip())
|
||||
|
||||
def validate_number(self, result):
|
||||
def validate_number(self, result: str) -> float | None:
|
||||
"""Validate and convert string input to float.
|
||||
|
||||
Args:
|
||||
result (str): Number input to validate
|
||||
|
||||
Returns:
|
||||
float | None: Converted float value or None if invalid
|
||||
"""
|
||||
try:
|
||||
return float(result)
|
||||
except ValueError:
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
def validate_confirm(self, result):
|
||||
return bool(result)
|
||||
|
||||
|
|
@ -144,7 +175,7 @@ class DeckBuilder:
|
|||
logging.error(f"Invalid price format for '{card_name}': {card_price}")
|
||||
return 0.0
|
||||
return 0.0
|
||||
except scrython.foundation.ScryfallError as e:
|
||||
except (scrython.foundation.ScryfallError, scrython.foundation.ScryfallRequestError) as e:
|
||||
logging.error(f"Scryfall API error for '{card_name}': {e}")
|
||||
return 0.0
|
||||
except TimeoutError:
|
||||
|
|
@ -187,8 +218,9 @@ class DeckBuilder:
|
|||
fuzzy_card_choices.append('Neither')
|
||||
print(fuzzy_card_choices)
|
||||
fuzzy_card_choice = self.questionnaire('Choice', choices_list=fuzzy_card_choices)
|
||||
if fuzzy_card_choice != 'Neither':
|
||||
if isinstance(fuzzy_card_choice, tuple):
|
||||
fuzzy_card_choice = fuzzy_card_choice[0]
|
||||
if fuzzy_card_choice != 'Neither':
|
||||
print(fuzzy_card_choice)
|
||||
fuzzy_chosen = True
|
||||
|
||||
|
|
@ -209,12 +241,12 @@ class DeckBuilder:
|
|||
self.commander_info = df_dict
|
||||
self.commander = self.commander_df.at[0, 'name']
|
||||
self.price_check(self.commander)
|
||||
logging.info(f"Commander selected: {self.commander}")
|
||||
break
|
||||
#print(self.commander)
|
||||
else:
|
||||
commander_chosen = False
|
||||
|
||||
|
||||
# Send commander info to setup commander, including extracting info on colors, color identity,
|
||||
# creature types, and other information, like keywords, abilities, etc...
|
||||
self.commander_setup()
|
||||
|
|
@ -240,9 +272,15 @@ class DeckBuilder:
|
|||
self.commander_mana_value = int(df.at[0, 'manaValue'])
|
||||
|
||||
# Set color identity
|
||||
try:
|
||||
self.color_identity = df.at[0, 'colorIdentity']
|
||||
if pd.isna(self.color_identity):
|
||||
self.color_identity = 'COLORLESS'
|
||||
self.color_identity_full = ''
|
||||
self.determine_color_identity()
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to set color identity: {e}")
|
||||
raise ValueError("Could not determine color identity") from e
|
||||
|
||||
# Set creature colors
|
||||
if pd.notna(df.at[0, 'colors']) and df.at[0, 'colors'].strip():
|
||||
|
|
@ -259,15 +297,7 @@ class DeckBuilder:
|
|||
self.commander_tags = list(df.at[0, 'themeTags'])
|
||||
|
||||
self.determine_themes()
|
||||
self.themes = [self.primary_theme]
|
||||
if not self.secondary_theme:
|
||||
pass
|
||||
else:
|
||||
self.themes.append(self.secondary_theme)
|
||||
if not self.tertiary_theme:
|
||||
pass
|
||||
else:
|
||||
self.themes.append(self.tertiary_theme)
|
||||
|
||||
|
||||
self.commander_dict = {
|
||||
'Commander Name': self.commander,
|
||||
|
|
@ -307,69 +337,17 @@ class DeckBuilder:
|
|||
print(f'Enchantment cards: {self.enchantment_cards}')
|
||||
print(f'Land cards cards: {self.land_cards}')
|
||||
print(f'Number of cards in Library: {len(self.card_library)}')
|
||||
self.get_cmc()
|
||||
self.count_pips()
|
||||
self.concatenate_duplicates()
|
||||
self.organize_library()
|
||||
self.sort_library()
|
||||
self.get_cmc()
|
||||
self.commander_to_top()
|
||||
self.card_library.to_csv(f'{csv_directory}/test_deck_done.csv', index=False)
|
||||
self.full_df.to_csv(f'{csv_directory}/test_all_after_done.csv', index=False)
|
||||
|
||||
def determine_color_identity(self):
|
||||
# Determine the color identity for later
|
||||
color_dict = [
|
||||
{'color_identity': 'COLORLESS',
|
||||
'color_identity_full': 'Colorless',
|
||||
'color_identity_options': ['Colorless'],
|
||||
'files_to_load': ['colorless']},
|
||||
{'color_identity': 'B',
|
||||
'color_identity_full': 'Black',
|
||||
'color_identity_options': ['Black'],
|
||||
'files_to_load': ['colorless', 'black']},
|
||||
{'color_identity': 'G',
|
||||
'color_identity_full': 'Green',
|
||||
'color_identity_options': ['Green'],
|
||||
'files_to_load': ['colorless', 'green']},
|
||||
{'color_identity': 'R',
|
||||
'color_identity_full': 'Red',
|
||||
'color_identity_options': ['Red'],
|
||||
'files_to_load': ['colorless', 'red']},
|
||||
{'color_identity': 'U',
|
||||
'color_identity_full': 'Blue',
|
||||
'color_identity_options': ['Blue'],
|
||||
'files_to_load': ['colorless', 'blue']},
|
||||
{'color_identity': 'W',
|
||||
'color_identity_full': 'White',
|
||||
'files_to_load': ['colorless', 'white']},
|
||||
{'color_identity': 'B, G',
|
||||
'color_identity_full': 'Golgari: Black/Green',
|
||||
'files_to_load': ['colorless', 'black', 'green', 'golgari']},
|
||||
{'color_identity': 'B, R',
|
||||
'color_identity_full': 'Rakdos: Black/Red',
|
||||
'files_to_load': ['colorless', 'black', 'red', 'rakdos']},
|
||||
{'color_identity': 'B, U',
|
||||
'color_identity_full': 'Dimir: Black/Blue',
|
||||
'files_to_load': ['colorless', 'black', 'blue', 'dimir']},
|
||||
{'color_identity': 'B, W',
|
||||
'color_identity_full': 'Orzhov: Black/White',
|
||||
'files_to_load': ['colorless', 'black', 'white', 'orzhov']},
|
||||
{'color_identity': 'G, R',
|
||||
'color_identity_full': 'Gruul: Green/Red',
|
||||
'files_to_load': ['colorless', 'green', 'red', 'gruul']},
|
||||
{'color_identity': 'G, U',
|
||||
'color_identity_full': 'Gruul: Green/Blue',
|
||||
'files_to_load': ['colorless', 'green', 'blue', 'simic']},
|
||||
{'color_identity': 'G, W',
|
||||
'color_identity_full': 'Selesnya: Green/White',
|
||||
'files_to_load': ['colorless', 'green', 'white', 'selesnya']},
|
||||
{'color_identity': 'G, R',
|
||||
'color_identity_full': 'Gruul: Black/White',
|
||||
'files_to_load': ['colorless', 'green', 'red', 'gruul']},
|
||||
{'color_identity': 'U, R',
|
||||
'color_identity_full': 'Izzet Blue/Red',
|
||||
'color_identity_options': ['U', 'R', 'U, R'],
|
||||
'files_to_load': ['colorless', 'blue', 'red', 'azorius']}
|
||||
]
|
||||
# Mono color
|
||||
if self.color_identity == 'COLORLESS':
|
||||
self.color_identity_full = 'Colorless'
|
||||
|
|
@ -603,13 +581,19 @@ class DeckBuilder:
|
|||
'This will be the "focus" of the deck, in a kindred deck this will typically be a creature type for example.')
|
||||
choice = self.questionnaire('Choice', choices_list=themes)
|
||||
self.primary_theme = choice
|
||||
self.primary_weight = 1.0
|
||||
|
||||
weights_default = {
|
||||
'primary': 1.0,
|
||||
'secondary': 0.0,
|
||||
'tertiary': 0.0,
|
||||
'hidden': 0.0
|
||||
}
|
||||
weights = weights_default
|
||||
themes.remove(choice)
|
||||
themes.append('Stop Here')
|
||||
|
||||
secondary_theme_chosen = False
|
||||
tertiary_theme_chosen = False
|
||||
self.hidden_theme = False
|
||||
|
||||
while not secondary_theme_chosen:
|
||||
# Secondary theme
|
||||
|
|
@ -631,25 +615,20 @@ class DeckBuilder:
|
|||
pass
|
||||
|
||||
else:
|
||||
weights = weights_default # primary = 1.0, secondary = 0.0, tertiary = 0.0
|
||||
self.secondary_theme = choice
|
||||
themes.remove(choice)
|
||||
secondary_theme_chosen = True
|
||||
# Set weights for primary/secondary themes
|
||||
if 'Kindred' in self.primary_theme:
|
||||
weights = {
|
||||
'primary': 0.75,
|
||||
'secondary': 0.25
|
||||
}
|
||||
if 'Kindred' in self.primary_theme and 'Kindred' not in self.secondary_theme:
|
||||
weights['primary'] -= 0.25 # 0.75
|
||||
weights['secondary'] += 0.25 # 0.25
|
||||
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme:
|
||||
weights = {
|
||||
'primary': 0.55,
|
||||
'secondary': 0.45
|
||||
}
|
||||
weights['primary'] -= 0.45 # 0.55
|
||||
weights['secondary'] += 0.45 # 0.45
|
||||
else:
|
||||
weights = {
|
||||
'primary': 0.6,
|
||||
'secondary': 0.4
|
||||
}
|
||||
weights['primary'] -= 0.4 # 0.6
|
||||
weights['secondary'] += 0.4 # 0.4
|
||||
self.primary_weight = weights['primary']
|
||||
self.secondary_weight = weights['secondary']
|
||||
break
|
||||
|
|
@ -672,37 +651,115 @@ class DeckBuilder:
|
|||
pass
|
||||
|
||||
else:
|
||||
weights = weights_default # primary = 1.0, secondary = 0.0, tertiary = 0.0
|
||||
self.tertiary_theme = choice
|
||||
tertiary_theme_chosen = True
|
||||
|
||||
if 'Kindred' in self.primary_theme:
|
||||
weights = {
|
||||
'primary': 0.7,
|
||||
'secondary': 0.2,
|
||||
'tertiary': 0.1
|
||||
}
|
||||
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme:
|
||||
weights = {
|
||||
'primary': 0.55,
|
||||
'secondary': 0.35,
|
||||
'tertiary': 0.1
|
||||
}
|
||||
# Set weights for themes:
|
||||
if 'Kindred' in self.primary_theme and 'Kindred' not in self.secondary_theme and 'Kindred' not in self.tertiary_theme:
|
||||
weights['primary'] -= 0.3 # 0.7
|
||||
weights['secondary'] += 0.2 # 0.2
|
||||
weights['tertiary'] += 0.1 # 0.1
|
||||
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme and 'Kindred' not in self.tertiary_theme:
|
||||
weights['primary'] -= 0.45 # 0.55
|
||||
weights['secondary'] += 0.35 # 0.35
|
||||
weights['tertiary'] += 0.1 # 0.1
|
||||
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme and 'Kindred' in self.tertiary_theme:
|
||||
weights = {
|
||||
'primary': 0.5,
|
||||
'secondary': 0.3,
|
||||
'tertiary': 0.2
|
||||
}
|
||||
weights['primary'] -= 0.5 # 0.5
|
||||
weights['secondary'] += 0.3 # 0.3
|
||||
weights['tertiary'] += 0.2 # 0.2
|
||||
else:
|
||||
weights = {
|
||||
'primary': 0.4,
|
||||
'secondary': 0.3,
|
||||
'tertiary': 0.3
|
||||
}
|
||||
weights['primary'] -= 0.6 # 0.4
|
||||
weights['secondary'] += 0.3 # 0.3
|
||||
weights['tertiary'] += 0.3 # 0.3
|
||||
self.primary_weight = weights['primary']
|
||||
self.secondary_weight = weights['secondary']
|
||||
self.tertiary_weight = weights['tertiary']
|
||||
break
|
||||
|
||||
self.themes = [self.primary_theme]
|
||||
if not self.secondary_theme:
|
||||
pass
|
||||
else:
|
||||
self.themes.append(self.secondary_theme)
|
||||
if not self.tertiary_theme:
|
||||
pass
|
||||
else:
|
||||
self.themes.append(self.tertiary_theme)
|
||||
|
||||
"""
|
||||
Setting 'Hidden' themes for multiple-copy cards, such as 'Hare Apparent' or 'Shadowborn Apostle'.
|
||||
These are themes that will be prompted for under specific conditions, such as a matching Kindred theme or a matching color combination and Spellslinger theme for example.
|
||||
Typically a hidden theme won't come up, but if it does, it will take priority with theme weights to ensure a decent number of the specialty cards are added.
|
||||
"""
|
||||
# Setting hidden theme for Kindred-specific themes
|
||||
hidden_themes = ['Advisor Kindred', 'Demon Kindred', 'Dwarf Kindred', 'Rabbit Kindred', 'Rat Kindred', 'Wraith Kindred']
|
||||
theme_cards = ['Persistent Petitioners', 'Shadowborn Apostle', 'Seven Dwarves', 'Hare Apparent', ['Rat Colony', 'Relentless Rats'], 'Nazgûl']
|
||||
color = ['Blue', 'Black', 'Red', 'White', 'Black', 'Black']
|
||||
for i in range(min(len(hidden_themes), len(theme_cards), len(color))):
|
||||
if (hidden_themes[i] in self.themes
|
||||
and hidden_themes[i] != 'Rat Kindred'
|
||||
and color[i] in self.colors):
|
||||
print(f'Looks like you\'re making a {hidden_themes[i]} deck, would you like it to be a {theme_cards[i]} deck?')
|
||||
choice = self.questionnaire('Confirm', False)
|
||||
if choice:
|
||||
self.hidden_theme = theme_cards[i]
|
||||
self.themes.append(self.hidden_theme)
|
||||
weights['primary'] -= weights['primary'] / 2 # 0.3
|
||||
weights['secondary'] += weights['secondary'] / 2 # 0.2
|
||||
weights['tertiary'] += weights['tertiary'] / 2 # 0.1
|
||||
weights['hidden'] = 1.0 - weights['primary'] - weights['secondary'] - weights['tertiary']
|
||||
self.primary_weight = weights['primary']
|
||||
self.secondary_weight = weights['secondary']
|
||||
self.tertiary_weight = weights['tertiary']
|
||||
self.hidden_weight = weights['hidden']
|
||||
else:
|
||||
continue
|
||||
|
||||
elif (hidden_themes[i] in self.themes
|
||||
and hidden_themes[i] == 'Rat Kindred'
|
||||
and color[i] in self.colors):
|
||||
print(f'Looks like you\'re making a {hidden_themes[i]} deck, would you like it to be a {theme_cards[i][0]} or {theme_cards[i][1]} deck?')
|
||||
choice = self.questionnaire('Confirm', False)
|
||||
if choice:
|
||||
print('Which one?')
|
||||
choice = self.questionnaire('Choice', choices_list=theme_cards[i])
|
||||
if choice:
|
||||
self.hidden_theme = choice
|
||||
self.themes.append(self.hidden_theme)
|
||||
weights['primary'] -= weights['primary'] / 2 # 0.3
|
||||
weights['secondary'] += weights['secondary'] / 2 # 0.2
|
||||
weights['tertiary'] += weights['tertiary'] / 2 # 0.1
|
||||
weights['hidden'] = 1.0 - weights['primary'] - weights['secondary'] - weights['tertiary']
|
||||
self.primary_weight = weights['primary']
|
||||
self.secondary_weight = weights['secondary']
|
||||
self.tertiary_weight = weights['tertiary']
|
||||
self.hidden_weight = weights['hidden']
|
||||
else:
|
||||
continue
|
||||
|
||||
# Setting the hidden theme for non-Kindred themes
|
||||
hidden_themes = ['Little Fellas', 'Mill', 'Spellslinger', 'Spellslinger']
|
||||
theme_cards = ['Hare Apparent', 'Persistent Petitions', 'Dragon\'s Approach', 'Slime Against Humanity']
|
||||
color = ['White', 'Blue', 'Red', 'Green']
|
||||
for i in range(min(len(hidden_themes), len(theme_cards), len(color))):
|
||||
if (hidden_themes[i] in self.themes
|
||||
and color[i] in self.colors):
|
||||
print(f'Looks like you\'re making a {hidden_themes[i]} deck, would you like it to be a {theme_cards[i]} deck?')
|
||||
choice = self.questionnaire('Confirm', False)
|
||||
if choice:
|
||||
self.hidden_theme = theme_cards[i]
|
||||
self.themes.append(self.hidden_theme)
|
||||
weights['primary'] -= weights['primary'] / 2 # 0.3
|
||||
weights['secondary'] += weights['secondary'] / 2 # 0.2
|
||||
weights['tertiary'] += weights['tertiary'] / 2 # 0.1
|
||||
weights['hidden'] = 1.0 - weights['primary'] - weights['secondary'] - weights['tertiary']
|
||||
self.primary_weight = weights['primary']
|
||||
self.secondary_weight = weights['secondary']
|
||||
self.tertiary_weight = weights['tertiary']
|
||||
self.hidden_weight = weights['hidden']
|
||||
else:
|
||||
continue
|
||||
break
|
||||
|
||||
def determine_ideals(self):
|
||||
|
|
@ -781,7 +838,7 @@ class DeckBuilder:
|
|||
# Determine spot/targetted removal
|
||||
print('How many spot removal pieces would you like to include?\n'
|
||||
'A good starting point is about 8-12 pieces of spot removal.\n'
|
||||
'Counterspells can be consisdered proactive removal and protection.\n'
|
||||
'Counterspells can be considered proactive removal and protection.\n'
|
||||
'If you\'re going spellslinger, more would be a good idea as you might have less cretaures.\n'
|
||||
'Default: 10')
|
||||
answer = self.questionnaire('Number', 10)
|
||||
|
|
@ -823,14 +880,21 @@ class DeckBuilder:
|
|||
print(f'Free slots that aren\'t part of the ideals: {self.free_slots}')
|
||||
print('Keep in mind that many of the ideals can also cover multiple roles, but this will give a baseline POV.')
|
||||
|
||||
def add_card(self, card: str, card_type: str, mana_cost: str, mana_value: int, is_commander=False) -> None:
|
||||
def add_card(self, card: str, card_type: str, mana_cost: str, mana_value: int, is_commander: bool = False) -> None:
|
||||
"""Add a card to the deck library with price checking if enabled.
|
||||
|
||||
Args:
|
||||
card (str): Name of the card
|
||||
card_type (str): Type of the card
|
||||
mana_cost (str): Mana cost of the card
|
||||
mana_value (int): Converted mana cost
|
||||
card (str): Name of the card to add
|
||||
card_type (str): Type of the card (e.g., 'Creature', 'Instant')
|
||||
mana_cost (str): Mana cost string representation
|
||||
mana_value (int): Converted mana cost/mana value
|
||||
is_commander (bool, optional): Whether this card is the commander. Defaults to False.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
ValueError: If card price exceeds maximum allowed price when price checking is enabled
|
||||
"""
|
||||
multiple_copies = basic_lands + multiple_copy_cards
|
||||
|
||||
|
|
@ -863,18 +927,8 @@ class DeckBuilder:
|
|||
logging.debug(f"Added {card} to deck library")
|
||||
|
||||
def organize_library(self):
|
||||
# Initialize counters dictionary
|
||||
card_counters = {
|
||||
'Artifact': 0,
|
||||
'Battle': 0,
|
||||
'Creature': 0,
|
||||
'Enchantment': 0,
|
||||
'Instant': 0,
|
||||
'Kindred': 0,
|
||||
'Land': 0,
|
||||
'Planeswalker': 0,
|
||||
'Sorcery': 0
|
||||
}
|
||||
# Initialize counters dictionary dynamically from card_types
|
||||
card_counters = {card_type: 0 for card_type in card_types}
|
||||
|
||||
# Count cards by type
|
||||
for card_type in card_types:
|
||||
|
|
@ -887,7 +941,7 @@ class DeckBuilder:
|
|||
self.creature_cards = card_counters['Creature']
|
||||
self.enchantment_cards = card_counters['Enchantment']
|
||||
self.instant_cards = card_counters['Instant']
|
||||
self.kindred_cards = card_counters['Kindred']
|
||||
self.theme_cards = card_counters['Kindred']
|
||||
self.land_cards = card_counters['Land']
|
||||
self.planeswalker_cards = card_counters['Planeswalker']
|
||||
self.sorcery_cards = card_counters['Sorcery']
|
||||
|
|
@ -903,41 +957,82 @@ class DeckBuilder:
|
|||
self.card_library.loc[index, 'Sort Order'] = card_type
|
||||
|
||||
custom_order = ['Planeswalker', 'Battle', 'Creature', 'Instant', 'Sorcery', 'Artifact', 'Enchantment', 'Land']
|
||||
self.card_library['Sort Order'] = pd.Categorical(self.card_library['Sort Order'], categories=custom_order, ordered=True)
|
||||
self.card_library = self.card_library.sort_values(by=['Sort Order', 'Card Name'], ascending=[True, True])
|
||||
#self.card_library = self.card_library.reset_index(drop=True)
|
||||
self.card_library = self.card_library.drop(columns=['Sort Order'])
|
||||
self.card_library['Sort Order'] = pd.Categorical(
|
||||
self.card_library['Sort Order'],
|
||||
categories=custom_order,
|
||||
ordered=True
|
||||
)
|
||||
self.card_library = (self.card_library
|
||||
.sort_values(by=['Sort Order', 'Card Name'], ascending=[True, True])
|
||||
.drop(columns=['Sort Order'])
|
||||
.reset_index(drop=True)
|
||||
)
|
||||
|
||||
def commander_to_top(self):
|
||||
target_index = self.card_library[self.card_library['Commander']].index.to_list()
|
||||
row_to_move = self.card_library.loc[target_index]
|
||||
row_to_move.loc[1.5] = ['-'] * len(row_to_move.columns)
|
||||
row_to_move = row_to_move.sort_index().reset_index(drop=True)
|
||||
self.card_library = self.card_library.drop(target_index)
|
||||
self.card_library = pd.concat([row_to_move, self.card_library], ignore_index = False)
|
||||
self.card_library = self.card_library.reset_index(drop=True)
|
||||
"""Move commander card to the top of the library."""
|
||||
try:
|
||||
# Extract commander row
|
||||
commander_row = self.card_library[self.card_library['Commander']].copy()
|
||||
if commander_row.empty:
|
||||
logging.warning("No commander found in library")
|
||||
return
|
||||
|
||||
# Remove commander from main library
|
||||
self.card_library = self.card_library[~self.card_library['Commander']]
|
||||
|
||||
# Concatenate with commander at top
|
||||
self.card_library = pd.concat([commander_row, self.card_library], ignore_index=True)
|
||||
self.card_library = self.card_library.drop(columns=['Commander'])
|
||||
|
||||
logging.info(f"Successfully moved commander '{commander_row['Card Name'].iloc[0]}' to top")
|
||||
except Exception as e:
|
||||
logging.error(f"Error moving commander to top: {e}")
|
||||
|
||||
def concatenate_duplicates(self):
|
||||
"""Handle duplicate cards in the library while maintaining data integrity."""
|
||||
duplicate_lists = basic_lands + multiple_copy_cards
|
||||
|
||||
# Create a count column for duplicates
|
||||
self.card_library['Card Count'] = 1
|
||||
|
||||
for duplicate in duplicate_lists:
|
||||
duplicate_search = self.card_library[self.card_library['Card Name'] == duplicate]
|
||||
num_duplicates = len(duplicate_search)
|
||||
if num_duplicates > 0:
|
||||
print(f'Found {num_duplicates} copies of {duplicate}')
|
||||
print(f'Dropping {num_duplicates -1} duplicate copies of {duplicate}')
|
||||
print(f'Setting remaining {duplicate} to be called "{duplicate} x {num_duplicates}"')
|
||||
self.card_library.loc[self.card_library['Card Name'] == duplicate, 'Card Name'] = f'{duplicate} x {num_duplicates}'
|
||||
mask = self.card_library['Card Name'] == duplicate
|
||||
count = mask.sum()
|
||||
|
||||
self.card_library = self.card_library.drop_duplicates(subset='Card Name', keep='first')
|
||||
if count > 0:
|
||||
logging.info(f'Found {count} copies of {duplicate}')
|
||||
|
||||
# Keep first occurrence with updated count
|
||||
first_idx = mask.idxmax()
|
||||
self.card_library.loc[first_idx, 'Card Count'] = count
|
||||
|
||||
# Drop other occurrences
|
||||
self.card_library = self.card_library.drop(
|
||||
self.card_library[mask & (self.card_library.index != first_idx)].index
|
||||
)
|
||||
|
||||
# Update card names with counts where applicable
|
||||
mask = self.card_library['Card Count'] > 1
|
||||
self.card_library.loc[mask, 'Card Name'] = (
|
||||
self.card_library.loc[mask, 'Card Name'] +
|
||||
' x ' +
|
||||
self.card_library.loc[mask, 'Card Count'].astype(str)
|
||||
)
|
||||
|
||||
# Clean up
|
||||
self.card_library = self.card_library.drop(columns=['Card Count'])
|
||||
self.card_library = self.card_library.reset_index(drop=True)
|
||||
def drop_card(self, dataframe: pd.DataFrame, index: int) -> None:
|
||||
"""Safely drop a card from the dataframe by index.
|
||||
|
||||
def drop_card(self, dataframe, index):
|
||||
Args:
|
||||
dataframe: DataFrame to modify
|
||||
index: Index to drop
|
||||
"""
|
||||
try:
|
||||
dataframe.drop(index, inplace=True)
|
||||
except KeyError:
|
||||
pass # Index already dropped or does not exist
|
||||
|
||||
logging.warning(f"Attempted to drop non-existent index {index}")
|
||||
def add_lands(self):
|
||||
"""
|
||||
Begin the process to add lands, the number will depend on ideal land count, ramp,
|
||||
|
|
@ -977,10 +1072,7 @@ class DeckBuilder:
|
|||
self.remove_basic()
|
||||
self.organize_library()
|
||||
|
||||
#if self.card_library < self.ideal_land_count:
|
||||
# pass
|
||||
print(f'Total lands: {self.land_cards}')
|
||||
#print(self.total_basics)
|
||||
|
||||
def add_basics(self):
|
||||
base_basics = self.ideal_land_count - 10 # Reserve 10 slots for non-basic lands
|
||||
|
|
@ -1013,14 +1105,14 @@ class DeckBuilder:
|
|||
basic = color_to_basic.get(color)
|
||||
if basic:
|
||||
for _ in range(basics_per_color):
|
||||
self.add_card(basic, 'Basic Land', '', 0)
|
||||
self.add_card(basic, 'Basic Land', None, 0)
|
||||
|
||||
# Distribute remaining basics based on color requirements
|
||||
if remaining_basics > 0:
|
||||
for color in self.colors[:remaining_basics]:
|
||||
basic = color_to_basic.get(color)
|
||||
if basic:
|
||||
self.add_card(basic, 'Basic Land', '', 0)
|
||||
self.add_card(basic, 'Basic Land', None, 0)
|
||||
|
||||
lands_to_remove = []
|
||||
for key in color_to_basic:
|
||||
|
|
@ -1051,11 +1143,10 @@ class DeckBuilder:
|
|||
|
||||
for card in self.staples:
|
||||
if card not in self.card_library:
|
||||
self.add_card(card, 'Land', '', 0)
|
||||
self.add_card(card, 'Land', None, 0)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
lands_to_remove = self.staples
|
||||
self.land_df = self.land_df[~self.land_df['name'].isin(lands_to_remove)]
|
||||
self.land_df.to_csv(f'{csv_directory}/test_lands.csv', index=False)
|
||||
|
|
@ -1066,17 +1157,22 @@ class DeckBuilder:
|
|||
'For most decks you\'ll likely be good with 3 or 4, just enough to thin the deck and help ensure the color availability.\n'
|
||||
'If you\'re doing Landfall, more fetches would be recommended just to get as many Landfall triggers per turn.')
|
||||
answer = self.questionnaire('Number', 2)
|
||||
|
||||
MAX_ATTEMPTS = 50 # Maximum attempts to prevent infinite loops
|
||||
attempt_count = 0
|
||||
desired_fetches = int(answer)
|
||||
chosen_fetches = []
|
||||
|
||||
generic_fetches = ['Evolving Wilds', 'Terramorphic Expanse', 'Shire Terrace', 'Escape Tunnel', 'Promising Vein','Myriad Landscape', 'Fabled Passage', 'Terminal Moraine']
|
||||
fetches = generic_fetches
|
||||
lands_to_remove = generic_fetches
|
||||
generic_fetches = [
|
||||
'Evolving Wilds', 'Terramorphic Expanse', 'Shire Terrace',
|
||||
'Escape Tunnel', 'Promising Vein', 'Myriad Landscape',
|
||||
'Fabled Passage', 'Terminal Moraine'
|
||||
]
|
||||
fetches = generic_fetches.copy()
|
||||
lands_to_remove = generic_fetches.copy()
|
||||
|
||||
# Adding in expensive fetches
|
||||
if (use_scrython and self.set_max_card_price):
|
||||
if self.price_check('Prismatic Vista') <= self.max_card_price * (random.randint(100, 110) / 100):
|
||||
if self.price_check('Prismatic Vista') <= self.max_card_price * 1.1:
|
||||
lands_to_remove.append('Prismatic Vista')
|
||||
fetches.append('Prismatic Vista')
|
||||
else:
|
||||
|
|
@ -1107,28 +1203,34 @@ class DeckBuilder:
|
|||
if fetch not in lands_to_remove:
|
||||
lands_to_remove.extend(fetch)
|
||||
|
||||
fetches_chosen = False
|
||||
# Randomly choose fetches up to the desired number
|
||||
while not fetches_chosen:
|
||||
while len(chosen_fetches) < desired_fetches + 3:
|
||||
while len(chosen_fetches) < desired_fetches + 3 and attempt_count < MAX_ATTEMPTS:
|
||||
if not fetches: # If we run out of fetches to choose from
|
||||
break
|
||||
|
||||
fetch_choice = random.choice(fetches)
|
||||
if use_scrython and self.set_max_card_price:
|
||||
if self.price_check(fetch_choice) <= self.max_card_price * (random.randint(100, 110) / 100):
|
||||
if self.price_check(fetch_choice) <= self.max_card_price * 1.1:
|
||||
chosen_fetches.append(fetch_choice)
|
||||
fetches.remove(fetch_choice)
|
||||
else:
|
||||
chosen_fetches.append(fetch_choice)
|
||||
fetches.remove(fetch_choice)
|
||||
|
||||
attempt_count += 1
|
||||
|
||||
# Select final fetches to add
|
||||
fetches_to_add = []
|
||||
while len(fetches_to_add) < desired_fetches:
|
||||
card = random.choice(fetches)
|
||||
if card not in fetches_to_add:
|
||||
fetches_to_add.append(card)
|
||||
fetches_chosen = True
|
||||
available_fetches = chosen_fetches[:desired_fetches]
|
||||
for fetch in available_fetches:
|
||||
if fetch not in fetches_to_add:
|
||||
fetches_to_add.append(fetch)
|
||||
|
||||
if attempt_count >= MAX_ATTEMPTS:
|
||||
logging.warning(f"Reached maximum attempts ({MAX_ATTEMPTS}) while selecting fetch lands")
|
||||
|
||||
for card in fetches_to_add:
|
||||
self.add_card(card, 'Land', '',0)
|
||||
self.add_card(card, 'Land', None, 0)
|
||||
|
||||
self.land_df = self.land_df[~self.land_df['name'].isin(lands_to_remove)]
|
||||
self.land_df.to_csv(f'{csv_directory}/test_lands.csv', index=False)
|
||||
|
|
@ -1136,22 +1238,20 @@ class DeckBuilder:
|
|||
def add_kindred_lands(self):
|
||||
print('Adding lands that care about the commander having a Kindred theme.')
|
||||
print('Adding general Kindred lands.')
|
||||
|
||||
def create_land(name: str, land_type: str) -> dict:
|
||||
"""Helper function to create land card dictionaries"""
|
||||
return {
|
||||
'name': name,
|
||||
'type': land_type,
|
||||
'manaCost': None,
|
||||
'manaValue': 0
|
||||
}
|
||||
|
||||
kindred_lands = [
|
||||
{'name': 'Path of Ancestry',
|
||||
'type': 'Land',
|
||||
'manaCost': '',
|
||||
'manaValue': 0
|
||||
},
|
||||
{'name': 'Three Tree City',
|
||||
'type': 'Legendary Land',
|
||||
'manaCost': '',
|
||||
'manaValue': 0
|
||||
},
|
||||
{'name': 'Cavern of Souls',
|
||||
'type': 'Land',
|
||||
'manaCost': '',
|
||||
'manaValue': 0
|
||||
},
|
||||
create_land('Path of Ancestry', 'Land'),
|
||||
create_land('Three Tree City', 'Legendary Land'),
|
||||
create_land('Cavern of Souls', 'Land')
|
||||
]
|
||||
|
||||
for theme in self.themes:
|
||||
|
|
@ -1333,26 +1433,31 @@ class DeckBuilder:
|
|||
print(f'Added {len(cards_to_add)} land cards.')
|
||||
|
||||
def check_basics(self):
|
||||
basic_lands = ['Plains', 'Island', 'Swamp', 'Forest', 'Mountain']
|
||||
self.total_basics = 0
|
||||
self.total_basics += len(self.card_library[self.card_library['Card Name'].isin(basic_lands)])
|
||||
print(f'Number of basic lands: {self.total_basics}')
|
||||
"""Check and display counts of each basic land type."""
|
||||
basic_lands = {
|
||||
'Plains': 0,
|
||||
'Island': 0,
|
||||
'Swamp': 0,
|
||||
'Mountain': 0,
|
||||
'Forest': 0,
|
||||
'Snow-Covered Plains': 0,
|
||||
'Snow-Covered Island': 0,
|
||||
'Snow-Covered Swamp': 0,
|
||||
'Snow-Covered Mountain': 0,
|
||||
'Snow-Covered Forest': 0
|
||||
}
|
||||
|
||||
def concatenate_basics(self):
|
||||
basic_lands = ['Plains', 'Island', 'Swamp', 'Forest', 'Mountain']
|
||||
self.total_basics = 0
|
||||
self.total_basics += len(self.card_library[self.card_library['Card Name'].isin(basic_lands)])
|
||||
for basic_land in basic_lands:
|
||||
basic_count = len(self.card_library[self.card_library['Card Name'] == basic_land])
|
||||
if basic_count > 0:
|
||||
# Keep first occurrence and update its name to show count
|
||||
mask = self.card_library['Card Name'] == basic_land
|
||||
first_occurrence = mask.idxmax()
|
||||
self.card_library.loc[first_occurrence, 'Card Name'] = f'{basic_land} x {basic_count}'
|
||||
# Drop other occurrences
|
||||
indices_to_drop = self.card_library[mask].index[1:]
|
||||
self.card_library.drop(indices_to_drop, inplace=True)
|
||||
self.card_library.reset_index(drop=True, inplace=True)
|
||||
for land in basic_lands:
|
||||
count = len(self.card_library[self.card_library['Card Name'] == land])
|
||||
basic_lands[land] = count
|
||||
self.total_basics += count
|
||||
|
||||
print("\nBasic Land Counts:")
|
||||
for land, count in basic_lands.items():
|
||||
if count > 0:
|
||||
print(f"{land}: {count}")
|
||||
print(f"Total basic lands: {self.total_basics}\n")
|
||||
|
||||
def remove_basic(self):
|
||||
"""
|
||||
|
|
@ -1400,44 +1505,110 @@ class DeckBuilder:
|
|||
|
||||
except (IndexError, KeyError) as e:
|
||||
logging.error(f"Error removing {basic_land}: {e}")
|
||||
# Try next most numerous basic if available
|
||||
# Iterative approach instead of recursion
|
||||
while basic_counts:
|
||||
basic_counts.pop(basic_land, None)
|
||||
if basic_counts:
|
||||
basic_land = max(basic_counts.items(), key=lambda x: x[1])[0]
|
||||
self.remove_basic() # Recursive call with remaining basics
|
||||
else:
|
||||
if not basic_counts:
|
||||
logging.error("Failed to remove any basic land")
|
||||
break
|
||||
|
||||
basic_land = max(basic_counts.items(), key=lambda x: x[1])[0]
|
||||
try:
|
||||
condition = self.card_library['Card Name'] == basic_land
|
||||
index_to_drop = self.card_library[condition].index[0]
|
||||
self.card_library = self.card_library.drop(index_to_drop)
|
||||
self.card_library = self.card_library.reset_index(drop=True)
|
||||
logging.info(f'{basic_land} removed successfully')
|
||||
self.check_basics()
|
||||
break
|
||||
except (IndexError, KeyError):
|
||||
continue
|
||||
else:
|
||||
print(f'Not enough basic lands to keep the minimum of {self.min_basics}.')
|
||||
self.remove_land()
|
||||
|
||||
def remove_land(self):
|
||||
"""Remove a random non-basic, non-staple land from the deck."""
|
||||
print('Removing a random nonbasic land.')
|
||||
basic_lands = ['Plains', 'Island', 'Swamp', 'Mountain', 'Forest',
|
||||
|
||||
# Define basic lands including snow-covered variants
|
||||
basic_lands = [
|
||||
'Plains', 'Island', 'Swamp', 'Mountain', 'Forest',
|
||||
'Snow-Covered Plains', 'Snow-Covered Island', 'Snow-Covered Swamp',
|
||||
'Snow-Covered Mountain', 'Snow-Covered Forest']
|
||||
library_filter = self.card_library[self.card_library['Card Type'].str.contains('Land')].copy()
|
||||
library_filter = library_filter[~library_filter['Card Name'].isin((basic_lands + self.staples))]
|
||||
card = np.random.choice(library_filter.index, 1, replace=False)
|
||||
print(library_filter.loc[card, 'Card Name'].to_string(index=False))
|
||||
self.card_library.drop(card, inplace=True)
|
||||
'Snow-Covered Mountain', 'Snow-Covered Forest'
|
||||
]
|
||||
|
||||
try:
|
||||
# Filter for non-basic, non-staple lands
|
||||
library_filter = self.card_library[
|
||||
(self.card_library['Card Type'].str.contains('Land')) &
|
||||
(~self.card_library['Card Name'].isin(basic_lands + self.staples))
|
||||
].copy()
|
||||
|
||||
if len(library_filter) == 0:
|
||||
print("No suitable non-basic lands found to remove.")
|
||||
return
|
||||
|
||||
# Select random land to remove
|
||||
card_index = np.random.choice(library_filter.index)
|
||||
card_name = self.card_library.loc[card_index, 'Card Name']
|
||||
|
||||
print(f"Removing {card_name}")
|
||||
self.card_library.drop(card_index, inplace=True)
|
||||
self.card_library.reset_index(drop=True, inplace=True)
|
||||
print("Card removed.")
|
||||
print("Card removed successfully.")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error removing land: {e}")
|
||||
print("Failed to remove land card.")
|
||||
|
||||
def count_pips(self):
|
||||
print('Checking the number of color pips in each color.')
|
||||
mana_cost_list = self.card_library['Mana Cost'].tolist()
|
||||
print(mana_cost_list)
|
||||
"""Count and display the number of colored mana symbols in casting costs."""
|
||||
print('Analyzing color pip distribution...')
|
||||
|
||||
pip_counts = {
|
||||
'W': 0, 'U': 0, 'B': 0, 'R': 0, 'G': 0
|
||||
}
|
||||
|
||||
#keyboard.wait('space')
|
||||
for cost in self.card_library['Mana Cost'].dropna():
|
||||
for color in pip_counts:
|
||||
pip_counts[color] += cost.count(color)
|
||||
|
||||
total_pips = sum(pip_counts.values())
|
||||
if total_pips == 0:
|
||||
print("No colored mana symbols found in casting costs.")
|
||||
return
|
||||
|
||||
print("\nColor Pip Distribution:")
|
||||
for color, count in pip_counts.items():
|
||||
if count > 0:
|
||||
percentage = (count / total_pips) * 100
|
||||
print(f"{color}: {count} pips ({percentage:.1f}%)")
|
||||
print(f"Total colored pips: {total_pips}\n")
|
||||
|
||||
def get_cmc(self):
|
||||
print('Getting the combined mana value of non-land cards.')
|
||||
non_land = self.card_library[~self.card_library['Card Type'].str.contains('Land')].copy()
|
||||
"""Calculate average converted mana cost of non-land cards."""
|
||||
logging.info('Calculating average mana value of non-land cards.')
|
||||
|
||||
try:
|
||||
# Filter non-land cards
|
||||
non_land = self.card_library[
|
||||
~self.card_library['Card Type'].str.contains('Land')
|
||||
].copy()
|
||||
|
||||
if non_land.empty:
|
||||
logging.warning("No non-land cards found")
|
||||
self.cmc = 0.0
|
||||
else:
|
||||
total_cmc = non_land['Mana Value'].sum()
|
||||
self.cmc = round((total_cmc / len(non_land)), 2)
|
||||
self.cmc = round(total_cmc / len(non_land), 2)
|
||||
|
||||
self.commander_dict.update({'CMC': float(self.cmc)})
|
||||
logging.info(f"Average CMC: {self.cmc}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error calculating CMC: {e}")
|
||||
self.cmc = 0.0
|
||||
|
||||
def weight_by_theme(self, tag, ideal=1, weight=1):
|
||||
# First grab the first 50/30/20 cards that match each theme
|
||||
|
|
@ -1457,15 +1628,15 @@ class DeckBuilder:
|
|||
tag_df = tag_df.head(pool_size)
|
||||
|
||||
# Convert to list of card dictionaries
|
||||
card_pool = []
|
||||
for _, row in tag_df.iterrows():
|
||||
card = {
|
||||
card_pool = [
|
||||
{
|
||||
'name': row['name'],
|
||||
'type': row['type'],
|
||||
'manaCost': row['manaCost'],
|
||||
'manaValue': row['manaValue']
|
||||
}
|
||||
card_pool.append(card)
|
||||
for _, row in tag_df.iterrows()
|
||||
]
|
||||
|
||||
# Randomly select cards up to ideal value
|
||||
cards_to_add = []
|
||||
|
|
@ -1531,15 +1702,15 @@ class DeckBuilder:
|
|||
tag_df = tag_df.head(pool_size)
|
||||
|
||||
# Convert to list of card dictionaries
|
||||
card_pool = []
|
||||
for _, row in tag_df.iterrows():
|
||||
card = {
|
||||
card_pool = [
|
||||
{
|
||||
'name': row['name'],
|
||||
'type': row['type'],
|
||||
'manaCost': row['manaCost'],
|
||||
'manaValue': row['manaValue']
|
||||
}
|
||||
card_pool.append(card)
|
||||
for _, row in tag_df.iterrows()
|
||||
]
|
||||
|
||||
# Randomly select cards up to ideal value
|
||||
cards_to_add = []
|
||||
|
|
@ -1592,6 +1763,10 @@ class DeckBuilder:
|
|||
print(f'Adding creatures to deck based on the ideal creature count of {self.ideal_creature_count}...')
|
||||
|
||||
try:
|
||||
if self.hidden_theme:
|
||||
print(f'Processing primary theme: {self.hidden_theme}')
|
||||
self.weight_by_theme(self.hidden_theme, self.ideal_creature_count, self.hidden_weight)
|
||||
|
||||
print(f'Processing primary theme: {self.primary_theme}')
|
||||
self.weight_by_theme(self.primary_theme, self.ideal_creature_count, self.primary_weight)
|
||||
|
||||
|
|
@ -1626,15 +1801,52 @@ class DeckBuilder:
|
|||
self.add_by_tags('Unconditional Draw', math.ceil(self.ideal_card_advantage * 0.8))
|
||||
|
||||
def fill_out_deck(self):
|
||||
"""Fill out the deck to 100 cards with theme-appropriate cards."""
|
||||
print('Filling out the Library to 100 with cards fitting the themes.')
|
||||
while len(self.card_library) < 100:
|
||||
if self.tertiary_theme:
|
||||
self.add_by_tags(self.tertiary_theme, math.ceil(self.tertiary_weight * 3))
|
||||
if self.secondary_theme:
|
||||
self.add_by_tags(self.secondary_theme, math.ceil(self.secondary_weight))
|
||||
self.add_by_tags(self.primary_theme, math.ceil(self.primary_weight / 5))
|
||||
|
||||
cards_needed = 100 - len(self.card_library)
|
||||
if cards_needed <= 0:
|
||||
return
|
||||
|
||||
logging.info(f"Need to add {cards_needed} more cards")
|
||||
MAX_ATTEMPTS = max(20, cards_needed * 2) # Scale attempts with cards needed
|
||||
attempts = 0
|
||||
|
||||
while len(self.card_library) < 100 and attempts < MAX_ATTEMPTS:
|
||||
initial_count = len(self.card_library)
|
||||
remaining = 100 - len(self.card_library)
|
||||
|
||||
# Adjust weights based on remaining cards needed
|
||||
weight_multiplier = remaining / cards_needed
|
||||
|
||||
if self.tertiary_theme:
|
||||
self.add_by_tags(self.tertiary_theme,
|
||||
math.ceil(self.tertiary_weight * 3 * weight_multiplier))
|
||||
if self.secondary_theme:
|
||||
self.add_by_tags(self.secondary_theme,
|
||||
math.ceil(self.secondary_weight * weight_multiplier))
|
||||
self.add_by_tags(self.primary_theme,
|
||||
math.ceil(self.primary_weight * weight_multiplier))
|
||||
|
||||
if len(self.card_library) == initial_count:
|
||||
attempts += 1
|
||||
if attempts % 5 == 0: # Log progress every 5 failed attempts
|
||||
logging.warning(f"Made {attempts} attempts, still need {100 - len(self.card_library)} cards")
|
||||
|
||||
final_count = len(self.card_library)
|
||||
if final_count < 100:
|
||||
logging.warning(f"Could not reach 100 cards after {attempts} attempts. Current count: {final_count}")
|
||||
print(f"\nWARNING: Deck is incomplete with {final_count} cards. Manual additions may be needed.")
|
||||
else:
|
||||
logging.info(f"Successfully filled deck to {final_count} cards in {attempts} attempts")
|
||||
|
||||
def main():
|
||||
"""Main entry point for deck builder application."""
|
||||
build_deck = DeckBuilder()
|
||||
build_deck.determine_commander()
|
||||
pprint.pprint(build_deck.commander_dict, sort_dicts=False)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
#pprint.pprint(build_deck.card_library['Card Name'], sort_dicts = False)
|
||||
|
|
@ -114,8 +114,8 @@ enchantment_tokens = ['Cursed Role', 'Monster Role', 'Royal Role', 'Sorcerer Rol
|
|||
'Virtuous Role', 'Wicked Role', 'Young Hero Role', 'Shard']
|
||||
|
||||
multiple_copy_cards = ['Dragon\'s Approach', 'Hare Apparent', 'Nazgûl', 'Persistent Petitioners',
|
||||
'Rat Colony','Relentless Rars', 'Seven Dwarves', 'Shadowborn Apostle',
|
||||
'Slime Against Humanity', 'Templar Knights']
|
||||
'Rat Colony', 'Relentless Rats', 'Seven Dwarves', 'Shadowborn Apostle',
|
||||
'Slime Against Humanity', 'Templar Knight']
|
||||
|
||||
non_creature_types = ['Legendary', 'Creature', 'Enchantment', 'Artifact',
|
||||
'Battle', 'Sorcery', 'Instant', 'Land', '-', '—',
|
||||
|
|
|
|||
47
tagger.py
47
tagger.py
|
|
@ -5,7 +5,7 @@ import pandas as pd # type: ignore
|
|||
|
||||
import settings
|
||||
|
||||
from settings import artifact_tokens, csv_directory, colors, counter_types, enchantment_tokens, num_to_search, triggers
|
||||
from settings import artifact_tokens, csv_directory, colors, counter_types, enchantment_tokens, multiple_copy_cards, num_to_search, triggers
|
||||
from setup import regenerate_csv_by_color
|
||||
from utility import pluralize, sort_list
|
||||
|
||||
|
|
@ -3176,10 +3176,14 @@ def tag_for_themes(df, color):
|
|||
print('==========\n')
|
||||
search_for_legends(df, color)
|
||||
print('==========\n')
|
||||
tag_for_little_guys(df, color)
|
||||
print('==========\n')
|
||||
tag_for_mill(df, color)
|
||||
print('==========\n')
|
||||
tag_for_monarch(df, color)
|
||||
print('==========\n')
|
||||
tag_for_multiple_copies(df, color)
|
||||
print('==========\n')
|
||||
tag_for_planeswalkers(df, color)
|
||||
print('==========\n')
|
||||
tag_for_reanimate(df, color)
|
||||
|
|
@ -3703,6 +3707,32 @@ def search_for_legends(df, color):
|
|||
|
||||
print(f'"Legends Matter" and "Historics Matter" cards in {color}_cards.csv have been tagged.\n')
|
||||
|
||||
## Little Fellas
|
||||
def tag_for_little_guys(df, color):
|
||||
print(f'Tagging cards in {color}_cards.csv that are or care about low-power (2 or less) creatures.')
|
||||
for index, row in df.iterrows():
|
||||
theme_tags = row['themeTags']
|
||||
if pd.notna(row['power']):
|
||||
if '*' in row['power']:
|
||||
continue
|
||||
if (int(row['power']) <= 2):
|
||||
tag_type = ['Little Fellas']
|
||||
for tag in tag_type:
|
||||
if tag not in theme_tags:
|
||||
theme_tags.extend([tag])
|
||||
df.at[index, 'themeTags'] = theme_tags
|
||||
|
||||
if pd.notna(row['text']):
|
||||
if ('power 2 or less' in row['text'].lower()
|
||||
):
|
||||
tag_type = ['Little Fellas']
|
||||
for tag in tag_type:
|
||||
if tag not in theme_tags:
|
||||
theme_tags.extend([tag])
|
||||
df.at[index, 'themeTags'] = theme_tags
|
||||
|
||||
print(f'Low-power (2 or less) creature cards in {color}_cards.csv have been tagged.\n')
|
||||
|
||||
## Mill
|
||||
def tag_for_mill(df, color):
|
||||
print(f'Tagging cards in {color}_cards.csv that have a "Mill" theme.')
|
||||
|
|
@ -3780,6 +3810,21 @@ def tag_for_monarch(df, color):
|
|||
|
||||
print(f'"Monarch" cards in {color}_cards.csv have been tagged.\n')
|
||||
|
||||
## Multi-copy cards
|
||||
def tag_for_multiple_copies(df, color):
|
||||
print(f'Tagging cards in {color}_cards.csv that allow having multiple copies.')
|
||||
for index, row in df.iterrows():
|
||||
theme_tags = row['themeTags']
|
||||
if (row['name'] in multiple_copy_cards
|
||||
):
|
||||
tag_type = ['Multiple Copies', row['name']]
|
||||
for tag in tag_type:
|
||||
if tag not in theme_tags:
|
||||
theme_tags.extend([tag])
|
||||
df.at[index, 'themeTags'] = theme_tags
|
||||
|
||||
print(f'"Multiple-copy" cards in {color}_cards.csv have been tagged.\n')
|
||||
|
||||
## Planeswalkers
|
||||
def tag_for_planeswalkers(df, color):
|
||||
print(f'Tagging cards in {color}_cards.csv that fit the "Planeswalkers/Super Friends" theme.')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue