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:
mwisnowski 2024-12-30 11:43:36 -08:00
parent c74d1ff03a
commit 0dfe53bb32
3 changed files with 561 additions and 304 deletions

View file

@ -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. is planned based on mana curve and ramp added.
""" """
def new_line(): def new_line(num_lines: int = 1) -> None:
print('\n') """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: class DeckBuilder:
def __init__(self): def __init__(self):
self.card_library = pd.DataFrame() self.card_library = pd.DataFrame()
self.card_library['Card Name'] = pd.Series(dtype='str') self.card_library['Card Name'] = pd.Series(dtype='str')
@ -62,15 +73,35 @@ class DeckBuilder:
self.set_max_card_price = False self.set_max_card_price = False
self.card_prices = {} if use_scrython else None 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()) 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: try:
return float(result) return float(result)
except ValueError: except (ValueError, TypeError):
return None return None
def validate_confirm(self, result): def validate_confirm(self, result):
return bool(result) return bool(result)
@ -144,7 +175,7 @@ class DeckBuilder:
logging.error(f"Invalid price format for '{card_name}': {card_price}") logging.error(f"Invalid price format for '{card_name}': {card_price}")
return 0.0 return 0.0
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}") logging.error(f"Scryfall API error for '{card_name}': {e}")
return 0.0 return 0.0
except TimeoutError: except TimeoutError:
@ -187,8 +218,9 @@ class DeckBuilder:
fuzzy_card_choices.append('Neither') fuzzy_card_choices.append('Neither')
print(fuzzy_card_choices) print(fuzzy_card_choices)
fuzzy_card_choice = self.questionnaire('Choice', choices_list=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] fuzzy_card_choice = fuzzy_card_choice[0]
if fuzzy_card_choice != 'Neither':
print(fuzzy_card_choice) print(fuzzy_card_choice)
fuzzy_chosen = True fuzzy_chosen = True
@ -209,12 +241,12 @@ class DeckBuilder:
self.commander_info = df_dict self.commander_info = df_dict
self.commander = self.commander_df.at[0, 'name'] self.commander = self.commander_df.at[0, 'name']
self.price_check(self.commander) self.price_check(self.commander)
logging.info(f"Commander selected: {self.commander}")
break break
#print(self.commander) #print(self.commander)
else: else:
commander_chosen = False commander_chosen = False
# Send commander info to setup commander, including extracting info on colors, color identity, # Send commander info to setup commander, including extracting info on colors, color identity,
# creature types, and other information, like keywords, abilities, etc... # creature types, and other information, like keywords, abilities, etc...
self.commander_setup() self.commander_setup()
@ -240,9 +272,15 @@ class DeckBuilder:
self.commander_mana_value = int(df.at[0, 'manaValue']) self.commander_mana_value = int(df.at[0, 'manaValue'])
# Set color identity # Set color identity
try:
self.color_identity = df.at[0, 'colorIdentity'] self.color_identity = df.at[0, 'colorIdentity']
if pd.isna(self.color_identity):
self.color_identity = 'COLORLESS'
self.color_identity_full = '' self.color_identity_full = ''
self.determine_color_identity() 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 # Set creature colors
if pd.notna(df.at[0, 'colors']) and df.at[0, 'colors'].strip(): 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.commander_tags = list(df.at[0, 'themeTags'])
self.determine_themes() 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 = { self.commander_dict = {
'Commander Name': self.commander, 'Commander Name': self.commander,
@ -307,69 +337,17 @@ class DeckBuilder:
print(f'Enchantment cards: {self.enchantment_cards}') print(f'Enchantment cards: {self.enchantment_cards}')
print(f'Land cards cards: {self.land_cards}') print(f'Land cards cards: {self.land_cards}')
print(f'Number of cards in Library: {len(self.card_library)}') print(f'Number of cards in Library: {len(self.card_library)}')
self.get_cmc()
self.count_pips()
self.concatenate_duplicates() self.concatenate_duplicates()
self.organize_library() self.organize_library()
self.sort_library() self.sort_library()
self.get_cmc()
self.commander_to_top() self.commander_to_top()
self.card_library.to_csv(f'{csv_directory}/test_deck_done.csv', index=False) 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) self.full_df.to_csv(f'{csv_directory}/test_all_after_done.csv', index=False)
def determine_color_identity(self): def determine_color_identity(self):
# Determine the color identity for later # 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 # Mono color
if self.color_identity == 'COLORLESS': if self.color_identity == 'COLORLESS':
self.color_identity_full = '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.') '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) choice = self.questionnaire('Choice', choices_list=themes)
self.primary_theme = choice 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.remove(choice)
themes.append('Stop Here') themes.append('Stop Here')
secondary_theme_chosen = False secondary_theme_chosen = False
tertiary_theme_chosen = False tertiary_theme_chosen = False
self.hidden_theme = False
while not secondary_theme_chosen: while not secondary_theme_chosen:
# Secondary theme # Secondary theme
@ -631,25 +615,20 @@ class DeckBuilder:
pass pass
else: else:
weights = weights_default # primary = 1.0, secondary = 0.0, tertiary = 0.0
self.secondary_theme = choice self.secondary_theme = choice
themes.remove(choice) themes.remove(choice)
secondary_theme_chosen = True secondary_theme_chosen = True
# Set weights for primary/secondary themes # Set weights for primary/secondary themes
if 'Kindred' in self.primary_theme: if 'Kindred' in self.primary_theme and 'Kindred' not in self.secondary_theme:
weights = { weights['primary'] -= 0.25 # 0.75
'primary': 0.75, weights['secondary'] += 0.25 # 0.25
'secondary': 0.25
}
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme: elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme:
weights = { weights['primary'] -= 0.45 # 0.55
'primary': 0.55, weights['secondary'] += 0.45 # 0.45
'secondary': 0.45
}
else: else:
weights = { weights['primary'] -= 0.4 # 0.6
'primary': 0.6, weights['secondary'] += 0.4 # 0.4
'secondary': 0.4
}
self.primary_weight = weights['primary'] self.primary_weight = weights['primary']
self.secondary_weight = weights['secondary'] self.secondary_weight = weights['secondary']
break break
@ -672,37 +651,115 @@ class DeckBuilder:
pass pass
else: else:
weights = weights_default # primary = 1.0, secondary = 0.0, tertiary = 0.0
self.tertiary_theme = choice self.tertiary_theme = choice
tertiary_theme_chosen = True tertiary_theme_chosen = True
if 'Kindred' in self.primary_theme: # Set weights for themes:
weights = { if 'Kindred' in self.primary_theme and 'Kindred' not in self.secondary_theme and 'Kindred' not in self.tertiary_theme:
'primary': 0.7, weights['primary'] -= 0.3 # 0.7
'secondary': 0.2, weights['secondary'] += 0.2 # 0.2
'tertiary': 0.1 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:
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme: weights['primary'] -= 0.45 # 0.55
weights = { weights['secondary'] += 0.35 # 0.35
'primary': 0.55, weights['tertiary'] += 0.1 # 0.1
'secondary': 0.35,
'tertiary': 0.1
}
elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme and 'Kindred' in self.tertiary_theme: elif 'Kindred' in self.primary_theme and 'Kindred' in self.secondary_theme and 'Kindred' in self.tertiary_theme:
weights = { weights['primary'] -= 0.5 # 0.5
'primary': 0.5, weights['secondary'] += 0.3 # 0.3
'secondary': 0.3, weights['tertiary'] += 0.2 # 0.2
'tertiary': 0.2
}
else: else:
weights = { weights['primary'] -= 0.6 # 0.4
'primary': 0.4, weights['secondary'] += 0.3 # 0.3
'secondary': 0.3, weights['tertiary'] += 0.3 # 0.3
'tertiary': 0.3
}
self.primary_weight = weights['primary'] self.primary_weight = weights['primary']
self.secondary_weight = weights['secondary'] self.secondary_weight = weights['secondary']
self.tertiary_weight = weights['tertiary'] self.tertiary_weight = weights['tertiary']
break 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 break
def determine_ideals(self): def determine_ideals(self):
@ -781,7 +838,7 @@ class DeckBuilder:
# Determine spot/targetted removal # Determine spot/targetted removal
print('How many spot removal pieces would you like to include?\n' 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' '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' 'If you\'re going spellslinger, more would be a good idea as you might have less cretaures.\n'
'Default: 10') 'Default: 10')
answer = self.questionnaire('Number', 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(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.') 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. """Add a card to the deck library with price checking if enabled.
Args: Args:
card (str): Name of the card card (str): Name of the card to add
card_type (str): Type of the card card_type (str): Type of the card (e.g., 'Creature', 'Instant')
mana_cost (str): Mana cost of the card mana_cost (str): Mana cost string representation
mana_value (int): Converted mana cost 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 multiple_copies = basic_lands + multiple_copy_cards
@ -863,18 +927,8 @@ class DeckBuilder:
logging.debug(f"Added {card} to deck library") logging.debug(f"Added {card} to deck library")
def organize_library(self): def organize_library(self):
# Initialize counters dictionary # Initialize counters dictionary dynamically from card_types
card_counters = { card_counters = {card_type: 0 for card_type in card_types}
'Artifact': 0,
'Battle': 0,
'Creature': 0,
'Enchantment': 0,
'Instant': 0,
'Kindred': 0,
'Land': 0,
'Planeswalker': 0,
'Sorcery': 0
}
# Count cards by type # Count cards by type
for card_type in card_types: for card_type in card_types:
@ -887,7 +941,7 @@ class DeckBuilder:
self.creature_cards = card_counters['Creature'] self.creature_cards = card_counters['Creature']
self.enchantment_cards = card_counters['Enchantment'] self.enchantment_cards = card_counters['Enchantment']
self.instant_cards = card_counters['Instant'] 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.land_cards = card_counters['Land']
self.planeswalker_cards = card_counters['Planeswalker'] self.planeswalker_cards = card_counters['Planeswalker']
self.sorcery_cards = card_counters['Sorcery'] self.sorcery_cards = card_counters['Sorcery']
@ -903,41 +957,82 @@ class DeckBuilder:
self.card_library.loc[index, 'Sort Order'] = card_type self.card_library.loc[index, 'Sort Order'] = card_type
custom_order = ['Planeswalker', 'Battle', 'Creature', 'Instant', 'Sorcery', 'Artifact', 'Enchantment', 'Land'] 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['Sort Order'] = pd.Categorical(
self.card_library = self.card_library.sort_values(by=['Sort Order', 'Card Name'], ascending=[True, True]) self.card_library['Sort Order'],
#self.card_library = self.card_library.reset_index(drop=True) categories=custom_order,
self.card_library = self.card_library.drop(columns=['Sort 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): def commander_to_top(self):
target_index = self.card_library[self.card_library['Commander']].index.to_list() """Move commander card to the top of the library."""
row_to_move = self.card_library.loc[target_index] try:
row_to_move.loc[1.5] = ['-'] * len(row_to_move.columns) # Extract commander row
row_to_move = row_to_move.sort_index().reset_index(drop=True) commander_row = self.card_library[self.card_library['Commander']].copy()
self.card_library = self.card_library.drop(target_index) if commander_row.empty:
self.card_library = pd.concat([row_to_move, self.card_library], ignore_index = False) logging.warning("No commander found in library")
self.card_library = self.card_library.reset_index(drop=True) 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']) 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): def concatenate_duplicates(self):
"""Handle duplicate cards in the library while maintaining data integrity."""
duplicate_lists = basic_lands + multiple_copy_cards duplicate_lists = basic_lands + multiple_copy_cards
# Create a count column for duplicates
self.card_library['Card Count'] = 1
for duplicate in duplicate_lists: for duplicate in duplicate_lists:
duplicate_search = self.card_library[self.card_library['Card Name'] == duplicate] mask = self.card_library['Card Name'] == duplicate
num_duplicates = len(duplicate_search) count = mask.sum()
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}'
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) 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: try:
dataframe.drop(index, inplace=True) dataframe.drop(index, inplace=True)
except KeyError: except KeyError:
pass # Index already dropped or does not exist logging.warning(f"Attempted to drop non-existent index {index}")
def add_lands(self): def add_lands(self):
""" """
Begin the process to add lands, the number will depend on ideal land count, ramp, 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.remove_basic()
self.organize_library() self.organize_library()
#if self.card_library < self.ideal_land_count:
# pass
print(f'Total lands: {self.land_cards}') print(f'Total lands: {self.land_cards}')
#print(self.total_basics)
def add_basics(self): def add_basics(self):
base_basics = self.ideal_land_count - 10 # Reserve 10 slots for non-basic lands 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) basic = color_to_basic.get(color)
if basic: if basic:
for _ in range(basics_per_color): 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 # Distribute remaining basics based on color requirements
if remaining_basics > 0: if remaining_basics > 0:
for color in self.colors[:remaining_basics]: for color in self.colors[:remaining_basics]:
basic = color_to_basic.get(color) basic = color_to_basic.get(color)
if basic: if basic:
self.add_card(basic, 'Basic Land', '', 0) self.add_card(basic, 'Basic Land', None, 0)
lands_to_remove = [] lands_to_remove = []
for key in color_to_basic: for key in color_to_basic:
@ -1051,11 +1143,10 @@ class DeckBuilder:
for card in self.staples: for card in self.staples:
if card not in self.card_library: if card not in self.card_library:
self.add_card(card, 'Land', '', 0) self.add_card(card, 'Land', None, 0)
else: else:
pass pass
lands_to_remove = self.staples lands_to_remove = self.staples
self.land_df = self.land_df[~self.land_df['name'].isin(lands_to_remove)] 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) 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' '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.') 'If you\'re doing Landfall, more fetches would be recommended just to get as many Landfall triggers per turn.')
answer = self.questionnaire('Number', 2) answer = self.questionnaire('Number', 2)
MAX_ATTEMPTS = 50 # Maximum attempts to prevent infinite loops
attempt_count = 0
desired_fetches = int(answer) desired_fetches = int(answer)
chosen_fetches = [] chosen_fetches = []
generic_fetches = ['Evolving Wilds', 'Terramorphic Expanse', 'Shire Terrace', 'Escape Tunnel', 'Promising Vein','Myriad Landscape', 'Fabled Passage', 'Terminal Moraine'] generic_fetches = [
fetches = generic_fetches 'Evolving Wilds', 'Terramorphic Expanse', 'Shire Terrace',
lands_to_remove = generic_fetches 'Escape Tunnel', 'Promising Vein', 'Myriad Landscape',
'Fabled Passage', 'Terminal Moraine'
]
fetches = generic_fetches.copy()
lands_to_remove = generic_fetches.copy()
# Adding in expensive fetches # Adding in expensive fetches
if (use_scrython and self.set_max_card_price): 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') lands_to_remove.append('Prismatic Vista')
fetches.append('Prismatic Vista') fetches.append('Prismatic Vista')
else: else:
@ -1107,28 +1203,34 @@ class DeckBuilder:
if fetch not in lands_to_remove: if fetch not in lands_to_remove:
lands_to_remove.extend(fetch) lands_to_remove.extend(fetch)
fetches_chosen = False
# Randomly choose fetches up to the desired number # Randomly choose fetches up to the desired number
while not fetches_chosen: while len(chosen_fetches) < desired_fetches + 3 and attempt_count < MAX_ATTEMPTS:
while len(chosen_fetches) < desired_fetches + 3: if not fetches: # If we run out of fetches to choose from
break
fetch_choice = random.choice(fetches) fetch_choice = random.choice(fetches)
if use_scrython and self.set_max_card_price: 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) chosen_fetches.append(fetch_choice)
fetches.remove(fetch_choice) fetches.remove(fetch_choice)
else: else:
chosen_fetches.append(fetch_choice) chosen_fetches.append(fetch_choice)
fetches.remove(fetch_choice) fetches.remove(fetch_choice)
attempt_count += 1
# Select final fetches to add
fetches_to_add = [] fetches_to_add = []
while len(fetches_to_add) < desired_fetches: available_fetches = chosen_fetches[:desired_fetches]
card = random.choice(fetches) for fetch in available_fetches:
if card not in fetches_to_add: if fetch not in fetches_to_add:
fetches_to_add.append(card) fetches_to_add.append(fetch)
fetches_chosen = True
if attempt_count >= MAX_ATTEMPTS:
logging.warning(f"Reached maximum attempts ({MAX_ATTEMPTS}) while selecting fetch lands")
for card in fetches_to_add: 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 = self.land_df[~self.land_df['name'].isin(lands_to_remove)]
self.land_df.to_csv(f'{csv_directory}/test_lands.csv', index=False) self.land_df.to_csv(f'{csv_directory}/test_lands.csv', index=False)
@ -1136,22 +1238,20 @@ class DeckBuilder:
def add_kindred_lands(self): def add_kindred_lands(self):
print('Adding lands that care about the commander having a Kindred theme.') print('Adding lands that care about the commander having a Kindred theme.')
print('Adding general Kindred lands.') 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 = [ kindred_lands = [
{'name': 'Path of Ancestry', create_land('Path of Ancestry', 'Land'),
'type': 'Land', create_land('Three Tree City', 'Legendary Land'),
'manaCost': '', create_land('Cavern of Souls', 'Land')
'manaValue': 0
},
{'name': 'Three Tree City',
'type': 'Legendary Land',
'manaCost': '',
'manaValue': 0
},
{'name': 'Cavern of Souls',
'type': 'Land',
'manaCost': '',
'manaValue': 0
},
] ]
for theme in self.themes: for theme in self.themes:
@ -1333,26 +1433,31 @@ class DeckBuilder:
print(f'Added {len(cards_to_add)} land cards.') print(f'Added {len(cards_to_add)} land cards.')
def check_basics(self): def check_basics(self):
basic_lands = ['Plains', 'Island', 'Swamp', 'Forest', 'Mountain'] """Check and display counts of each basic land type."""
self.total_basics = 0 basic_lands = {
self.total_basics += len(self.card_library[self.card_library['Card Name'].isin(basic_lands)]) 'Plains': 0,
print(f'Number of basic lands: {self.total_basics}') '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 = 0
self.total_basics += len(self.card_library[self.card_library['Card Name'].isin(basic_lands)]) for land in basic_lands:
for basic_land in basic_lands: count = len(self.card_library[self.card_library['Card Name'] == land])
basic_count = len(self.card_library[self.card_library['Card Name'] == basic_land]) basic_lands[land] = count
if basic_count > 0: self.total_basics += count
# Keep first occurrence and update its name to show count
mask = self.card_library['Card Name'] == basic_land print("\nBasic Land Counts:")
first_occurrence = mask.idxmax() for land, count in basic_lands.items():
self.card_library.loc[first_occurrence, 'Card Name'] = f'{basic_land} x {basic_count}' if count > 0:
# Drop other occurrences print(f"{land}: {count}")
indices_to_drop = self.card_library[mask].index[1:] print(f"Total basic lands: {self.total_basics}\n")
self.card_library.drop(indices_to_drop, inplace=True)
self.card_library.reset_index(drop=True, inplace=True)
def remove_basic(self): def remove_basic(self):
""" """
@ -1400,44 +1505,110 @@ class DeckBuilder:
except (IndexError, KeyError) as e: except (IndexError, KeyError) as e:
logging.error(f"Error removing {basic_land}: {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) basic_counts.pop(basic_land, None)
if basic_counts: if not basic_counts:
basic_land = max(basic_counts.items(), key=lambda x: x[1])[0]
self.remove_basic() # Recursive call with remaining basics
else:
logging.error("Failed to remove any basic land") 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: else:
print(f'Not enough basic lands to keep the minimum of {self.min_basics}.') print(f'Not enough basic lands to keep the minimum of {self.min_basics}.')
self.remove_land() self.remove_land()
def remove_land(self): def remove_land(self):
"""Remove a random non-basic, non-staple land from the deck."""
print('Removing a random nonbasic land.') 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 Plains', 'Snow-Covered Island', 'Snow-Covered Swamp',
'Snow-Covered Mountain', 'Snow-Covered Forest'] '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) try:
print(library_filter.loc[card, 'Card Name'].to_string(index=False)) # Filter for non-basic, non-staple lands
self.card_library.drop(card, inplace=True) 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) 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): def count_pips(self):
print('Checking the number of color pips in each color.') """Count and display the number of colored mana symbols in casting costs."""
mana_cost_list = self.card_library['Mana Cost'].tolist() print('Analyzing color pip distribution...')
print(mana_cost_list)
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): def get_cmc(self):
print('Getting the combined mana value of non-land cards.') """Calculate average converted mana cost of non-land cards."""
non_land = self.card_library[~self.card_library['Card Type'].str.contains('Land')].copy() 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() 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)}) 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): def weight_by_theme(self, tag, ideal=1, weight=1):
# First grab the first 50/30/20 cards that match each theme # 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) tag_df = tag_df.head(pool_size)
# Convert to list of card dictionaries # Convert to list of card dictionaries
card_pool = [] card_pool = [
for _, row in tag_df.iterrows(): {
card = {
'name': row['name'], 'name': row['name'],
'type': row['type'], 'type': row['type'],
'manaCost': row['manaCost'], 'manaCost': row['manaCost'],
'manaValue': row['manaValue'] 'manaValue': row['manaValue']
} }
card_pool.append(card) for _, row in tag_df.iterrows()
]
# Randomly select cards up to ideal value # Randomly select cards up to ideal value
cards_to_add = [] cards_to_add = []
@ -1531,15 +1702,15 @@ class DeckBuilder:
tag_df = tag_df.head(pool_size) tag_df = tag_df.head(pool_size)
# Convert to list of card dictionaries # Convert to list of card dictionaries
card_pool = [] card_pool = [
for _, row in tag_df.iterrows(): {
card = {
'name': row['name'], 'name': row['name'],
'type': row['type'], 'type': row['type'],
'manaCost': row['manaCost'], 'manaCost': row['manaCost'],
'manaValue': row['manaValue'] 'manaValue': row['manaValue']
} }
card_pool.append(card) for _, row in tag_df.iterrows()
]
# Randomly select cards up to ideal value # Randomly select cards up to ideal value
cards_to_add = [] 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}...') print(f'Adding creatures to deck based on the ideal creature count of {self.ideal_creature_count}...')
try: 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}') print(f'Processing primary theme: {self.primary_theme}')
self.weight_by_theme(self.primary_theme, self.ideal_creature_count, self.primary_weight) 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)) self.add_by_tags('Unconditional Draw', math.ceil(self.ideal_card_advantage * 0.8))
def fill_out_deck(self): 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.') 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 = DeckBuilder()
build_deck.determine_commander() build_deck.determine_commander()
pprint.pprint(build_deck.commander_dict, sort_dicts=False) pprint.pprint(build_deck.commander_dict, sort_dicts=False)
if __name__ == '__main__':
main()
#pprint.pprint(build_deck.card_library['Card Name'], sort_dicts = False) #pprint.pprint(build_deck.card_library['Card Name'], sort_dicts = False)

View file

@ -114,8 +114,8 @@ enchantment_tokens = ['Cursed Role', 'Monster Role', 'Royal Role', 'Sorcerer Rol
'Virtuous Role', 'Wicked Role', 'Young Hero Role', 'Shard'] 'Virtuous Role', 'Wicked Role', 'Young Hero Role', 'Shard']
multiple_copy_cards = ['Dragon\'s Approach', 'Hare Apparent', 'Nazgûl', 'Persistent Petitioners', multiple_copy_cards = ['Dragon\'s Approach', 'Hare Apparent', 'Nazgûl', 'Persistent Petitioners',
'Rat Colony','Relentless Rars', 'Seven Dwarves', 'Shadowborn Apostle', 'Rat Colony', 'Relentless Rats', 'Seven Dwarves', 'Shadowborn Apostle',
'Slime Against Humanity', 'Templar Knights'] 'Slime Against Humanity', 'Templar Knight']
non_creature_types = ['Legendary', 'Creature', 'Enchantment', 'Artifact', non_creature_types = ['Legendary', 'Creature', 'Enchantment', 'Artifact',
'Battle', 'Sorcery', 'Instant', 'Land', '-', '', 'Battle', 'Sorcery', 'Instant', 'Land', '-', '',

View file

@ -5,7 +5,7 @@ import pandas as pd # type: ignore
import settings 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 setup import regenerate_csv_by_color
from utility import pluralize, sort_list from utility import pluralize, sort_list
@ -3176,10 +3176,14 @@ def tag_for_themes(df, color):
print('==========\n') print('==========\n')
search_for_legends(df, color) search_for_legends(df, color)
print('==========\n') print('==========\n')
tag_for_little_guys(df, color)
print('==========\n')
tag_for_mill(df, color) tag_for_mill(df, color)
print('==========\n') print('==========\n')
tag_for_monarch(df, color) tag_for_monarch(df, color)
print('==========\n') print('==========\n')
tag_for_multiple_copies(df, color)
print('==========\n')
tag_for_planeswalkers(df, color) tag_for_planeswalkers(df, color)
print('==========\n') print('==========\n')
tag_for_reanimate(df, color) 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') 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 ## Mill
def tag_for_mill(df, color): def tag_for_mill(df, color):
print(f'Tagging cards in {color}_cards.csv that have a "Mill" theme.') 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') 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 ## Planeswalkers
def tag_for_planeswalkers(df, color): def tag_for_planeswalkers(df, color):
print(f'Tagging cards in {color}_cards.csv that fit the "Planeswalkers/Super Friends" theme.') print(f'Tagging cards in {color}_cards.csv that fit the "Planeswalkers/Super Friends" theme.')