mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-17 08:00:13 +01:00
Moved the builder, tagger, and setup modules into their own folders, along with constants to help provide better clarity and readability. Additionally added a missing call for the tag_for_artifcact_triggers() function
This commit is contained in:
parent
3a5beebfe2
commit
dbbc8bc66e
20 changed files with 1525 additions and 1737 deletions
7
code/deck_builder/__init__.py
Normal file
7
code/deck_builder/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from .builder import DeckBuilder
|
||||||
|
from .builder_utils import *
|
||||||
|
from .builder_constants import *
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'DeckBuilder',
|
||||||
|
]
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
import math
|
import math
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os
|
import os
|
||||||
|
|
@ -16,8 +15,9 @@ import pprint
|
||||||
from fuzzywuzzy import process
|
from fuzzywuzzy import process
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from settings import (
|
from settings import CSV_DIRECTORY, MULTIPLE_COPY_CARDS
|
||||||
BASIC_LANDS, CARD_TYPES, CSV_DIRECTORY, multiple_copy_cards, DEFAULT_NON_BASIC_LAND_SLOTS,
|
from .builder_constants import (
|
||||||
|
BASIC_LANDS, CARD_TYPES, DEFAULT_NON_BASIC_LAND_SLOTS,
|
||||||
COMMANDER_CSV_PATH, FUZZY_MATCH_THRESHOLD, MAX_FUZZY_CHOICES, FETCH_LAND_DEFAULT_COUNT,
|
COMMANDER_CSV_PATH, FUZZY_MATCH_THRESHOLD, MAX_FUZZY_CHOICES, FETCH_LAND_DEFAULT_COUNT,
|
||||||
COMMANDER_POWER_DEFAULT, COMMANDER_TOUGHNESS_DEFAULT, COMMANDER_MANA_COST_DEFAULT,
|
COMMANDER_POWER_DEFAULT, COMMANDER_TOUGHNESS_DEFAULT, COMMANDER_MANA_COST_DEFAULT,
|
||||||
COMMANDER_MANA_VALUE_DEFAULT, COMMANDER_TYPE_DEFAULT, COMMANDER_TEXT_DEFAULT,
|
COMMANDER_MANA_VALUE_DEFAULT, COMMANDER_TYPE_DEFAULT, COMMANDER_TEXT_DEFAULT,
|
||||||
|
|
@ -29,8 +29,8 @@ from settings import (
|
||||||
MISC_LAND_POOL_SIZE, LAND_REMOVAL_MAX_ATTEMPTS, PROTECTED_LANDS,
|
MISC_LAND_POOL_SIZE, LAND_REMOVAL_MAX_ATTEMPTS, PROTECTED_LANDS,
|
||||||
MANA_COLORS, MANA_PIP_PATTERNS, THEME_WEIGHT_MULTIPLIER
|
MANA_COLORS, MANA_PIP_PATTERNS, THEME_WEIGHT_MULTIPLIER
|
||||||
)
|
)
|
||||||
import builder_utils
|
from . import builder_utils
|
||||||
import setup_utils
|
from file_setup import setup_utils
|
||||||
from input_handler import InputHandler
|
from input_handler import InputHandler
|
||||||
from exceptions import (
|
from exceptions import (
|
||||||
BasicLandCountError,
|
BasicLandCountError,
|
||||||
|
|
@ -78,6 +78,14 @@ from type_definitions import (
|
||||||
PlaneswalkerDF,
|
PlaneswalkerDF,
|
||||||
NonPlaneswalkerDF)
|
NonPlaneswalkerDF)
|
||||||
|
|
||||||
|
import logging_util
|
||||||
|
|
||||||
|
# Create logger for this module
|
||||||
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
|
logger.addHandler(logging_util.stream_handler)
|
||||||
|
|
||||||
# Try to import scrython and price_checker
|
# Try to import scrython and price_checker
|
||||||
try:
|
try:
|
||||||
import scrython
|
import scrython
|
||||||
|
|
@ -87,38 +95,9 @@ except ImportError:
|
||||||
scrython = None
|
scrython = None
|
||||||
PriceChecker = None
|
PriceChecker = None
|
||||||
use_scrython = False
|
use_scrython = False
|
||||||
logging.warning("Scrython is not installed. Price checking features will be unavailable."
|
logger.warning("Scrython is not installed. Price checking features will be unavailable."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create logs directory if it doesn't exist
|
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/deck_builder.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logger.setLevel(LOG_LEVEL)
|
|
||||||
|
|
||||||
# Add handlers to logger
|
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
pd.set_option('display.max_columns', None)
|
pd.set_option('display.max_columns', None)
|
||||||
pd.set_option('display.max_rows', None)
|
pd.set_option('display.max_rows', None)
|
||||||
pd.set_option('display.max_colwidth', 50)
|
pd.set_option('display.max_colwidth', 50)
|
||||||
|
|
@ -640,7 +619,7 @@ class DeckBuilder:
|
||||||
Returns:
|
Returns:
|
||||||
True if color identity was handled, False otherwise
|
True if color identity was handled, False otherwise
|
||||||
"""
|
"""
|
||||||
from settings import OTHER_COLOR_MAP
|
from builder_constants import OTHER_COLOR_MAP
|
||||||
|
|
||||||
if color_identity in OTHER_COLOR_MAP:
|
if color_identity in OTHER_COLOR_MAP:
|
||||||
identity_info = OTHER_COLOR_MAP[color_identity]
|
identity_info = OTHER_COLOR_MAP[color_identity]
|
||||||
|
|
@ -1137,7 +1116,7 @@ class DeckBuilder:
|
||||||
PriceTimeoutError: If the price check times out
|
PriceTimeoutError: If the price check times out
|
||||||
PriceValidationError: If the price data is invalid
|
PriceValidationError: If the price data is invalid
|
||||||
"""
|
"""
|
||||||
multiple_copies = BASIC_LANDS + multiple_copy_cards
|
multiple_copies = BASIC_LANDS + MULTIPLE_COPY_CARDS
|
||||||
|
|
||||||
# Skip if card already exists and isn't allowed multiple copies
|
# Skip if card already exists and isn't allowed multiple copies
|
||||||
if card in pd.Series(self.card_library['Card Name']).values and card not in multiple_copies:
|
if card in pd.Series(self.card_library['Card Name']).values and card not in multiple_copies:
|
||||||
|
|
@ -1303,7 +1282,7 @@ class DeckBuilder:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get list of cards that can have duplicates
|
# Get list of cards that can have duplicates
|
||||||
duplicate_lists = BASIC_LANDS + multiple_copy_cards
|
duplicate_lists = BASIC_LANDS + MULTIPLE_COPY_CARDS
|
||||||
|
|
||||||
# Process duplicates using helper function
|
# Process duplicates using helper function
|
||||||
self.card_library = builder_utils.process_duplicate_cards(
|
self.card_library = builder_utils.process_duplicate_cards(
|
||||||
|
|
@ -2084,7 +2063,7 @@ class DeckBuilder:
|
||||||
cards_added = []
|
cards_added = []
|
||||||
for card in selected_cards:
|
for card in selected_cards:
|
||||||
# Handle multiple copy cards
|
# Handle multiple copy cards
|
||||||
if card['name'] in multiple_copy_cards:
|
if card['name'] in MULTIPLE_COPY_CARDS:
|
||||||
copies = {
|
copies = {
|
||||||
'Nazgûl': 9,
|
'Nazgûl': 9,
|
||||||
'Seven Dwarves': 7
|
'Seven Dwarves': 7
|
||||||
|
|
@ -2196,7 +2175,7 @@ class DeckBuilder:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle multiple-copy cards
|
# Handle multiple-copy cards
|
||||||
if card['name'] in multiple_copy_cards:
|
if card['name'] in MULTIPLE_COPY_CARDS:
|
||||||
existing_copies = len(self.card_library[self.card_library['Card Name'] == card['name']])
|
existing_copies = len(self.card_library[self.card_library['Card Name'] == card['name']])
|
||||||
if existing_copies < ideal_value:
|
if existing_copies < ideal_value:
|
||||||
cards_to_add.append(card)
|
cards_to_add.append(card)
|
||||||
437
code/deck_builder/builder_constants.py
Normal file
437
code/deck_builder/builder_constants.py
Normal file
|
|
@ -0,0 +1,437 @@
|
||||||
|
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
|
||||||
|
import ast
|
||||||
|
|
||||||
|
# Commander selection configuration
|
||||||
|
# Format string for displaying duplicate cards in deck lists
|
||||||
|
FUZZY_MATCH_THRESHOLD: Final[int] = 90 # Threshold for fuzzy name matching
|
||||||
|
MAX_FUZZY_CHOICES: Final[int] = 5 # Maximum number of fuzzy match choices
|
||||||
|
|
||||||
|
# Commander-related constants
|
||||||
|
DUPLICATE_CARD_FORMAT: Final[str] = '{card_name} x {count}'
|
||||||
|
COMMANDER_CSV_PATH: Final[str] = 'csv_files/commander_cards.csv'
|
||||||
|
DECK_DIRECTORY = '../deck_files'
|
||||||
|
COMMANDER_CONVERTERS: Final[Dict[str, str]] = {'themeTags': ast.literal_eval, 'creatureTypes': ast.literal_eval} # CSV loading converters
|
||||||
|
COMMANDER_POWER_DEFAULT: Final[int] = 0
|
||||||
|
COMMANDER_TOUGHNESS_DEFAULT: Final[int] = 0
|
||||||
|
COMMANDER_MANA_VALUE_DEFAULT: Final[int] = 0
|
||||||
|
COMMANDER_TYPE_DEFAULT: Final[str] = ''
|
||||||
|
COMMANDER_TEXT_DEFAULT: Final[str] = ''
|
||||||
|
COMMANDER_MANA_COST_DEFAULT: Final[str] = ''
|
||||||
|
COMMANDER_COLOR_IDENTITY_DEFAULT: Final[str] = ''
|
||||||
|
COMMANDER_COLORS_DEFAULT: Final[List[str]] = []
|
||||||
|
COMMANDER_CREATURE_TYPES_DEFAULT: Final[str] = ''
|
||||||
|
COMMANDER_TAGS_DEFAULT: Final[List[str]] = []
|
||||||
|
COMMANDER_THEMES_DEFAULT: Final[List[str]] = []
|
||||||
|
|
||||||
|
CARD_TYPES = ['Artifact','Creature', 'Enchantment', 'Instant', 'Land', 'Planeswalker', 'Sorcery',
|
||||||
|
'Kindred', 'Dungeon', 'Battle']
|
||||||
|
|
||||||
|
# Basic mana colors
|
||||||
|
MANA_COLORS: Final[List[str]] = ['W', 'U', 'B', 'R', 'G']
|
||||||
|
|
||||||
|
# Mana pip patterns for each color
|
||||||
|
MANA_PIP_PATTERNS: Final[Dict[str, str]] = {
|
||||||
|
color: f'{{{color}}}' for color in MANA_COLORS
|
||||||
|
}
|
||||||
|
|
||||||
|
MONO_COLOR_MAP: Final[Dict[str, Tuple[str, List[str]]]] = {
|
||||||
|
'COLORLESS': ('Colorless', ['colorless']),
|
||||||
|
'W': ('White', ['colorless', 'white']),
|
||||||
|
'U': ('Blue', ['colorless', 'blue']),
|
||||||
|
'B': ('Black', ['colorless', 'black']),
|
||||||
|
'R': ('Red', ['colorless', 'red']),
|
||||||
|
'G': ('Green', ['colorless', 'green'])
|
||||||
|
}
|
||||||
|
|
||||||
|
DUAL_COLOR_MAP: Final[Dict[str, Tuple[str, List[str], List[str]]]] = {
|
||||||
|
'B, G': ('Golgari: Black/Green', ['B', 'G', 'B, G'], ['colorless', 'black', 'green', 'golgari']),
|
||||||
|
'B, R': ('Rakdos: Black/Red', ['B', 'R', 'B, R'], ['colorless', 'black', 'red', 'rakdos']),
|
||||||
|
'B, U': ('Dimir: Black/Blue', ['B', 'U', 'B, U'], ['colorless', 'black', 'blue', 'dimir']),
|
||||||
|
'B, W': ('Orzhov: Black/White', ['B', 'W', 'B, W'], ['colorless', 'black', 'white', 'orzhov']),
|
||||||
|
'G, R': ('Gruul: Green/Red', ['G', 'R', 'G, R'], ['colorless', 'green', 'red', 'gruul']),
|
||||||
|
'G, U': ('Simic: Green/Blue', ['G', 'U', 'G, U'], ['colorless', 'green', 'blue', 'simic']),
|
||||||
|
'G, W': ('Selesnya: Green/White', ['G', 'W', 'G, W'], ['colorless', 'green', 'white', 'selesnya']),
|
||||||
|
'R, U': ('Izzet: Blue/Red', ['U', 'R', 'U, R'], ['colorless', 'blue', 'red', 'izzet']),
|
||||||
|
'U, W': ('Azorius: Blue/White', ['U', 'W', 'U, W'], ['colorless', 'blue', 'white', 'azorius']),
|
||||||
|
'R, W': ('Boros: Red/White', ['R', 'W', 'R, W'], ['colorless', 'red', 'white', 'boros'])
|
||||||
|
}
|
||||||
|
|
||||||
|
TRI_COLOR_MAP: Final[Dict[str, Tuple[str, List[str], List[str]]]] = {
|
||||||
|
'B, G, U': ('Sultai: Black/Blue/Green', ['B', 'G', 'U', 'B, G', 'B, U', 'G, U', 'B, G, U'],
|
||||||
|
['colorless', 'black', 'blue', 'green', 'dimir', 'golgari', 'simic', 'sultai']),
|
||||||
|
'B, G, R': ('Jund: Black/Red/Green', ['B', 'G', 'R', 'B, G', 'B, R', 'G, R', 'B, G, R'],
|
||||||
|
['colorless', 'black', 'green', 'red', 'golgari', 'rakdos', 'gruul', 'jund']),
|
||||||
|
'B, G, W': ('Abzan: Black/Green/White', ['B', 'G', 'W', 'B, G', 'B, W', 'G, W', 'B, G, W'],
|
||||||
|
['colorless', 'black', 'green', 'white', 'golgari', 'orzhov', 'selesnya', 'abzan']),
|
||||||
|
'B, R, U': ('Grixis: Black/Blue/Red', ['B', 'R', 'U', 'B, R', 'B, U', 'R, U', 'B, R, U'],
|
||||||
|
['colorless', 'black', 'blue', 'red', 'dimir', 'rakdos', 'izzet', 'grixis']),
|
||||||
|
'B, R, W': ('Mardu: Black/Red/White', ['B', 'R', 'W', 'B, R', 'B, W', 'R, W', 'B, R, W'],
|
||||||
|
['colorless', 'black', 'red', 'white', 'rakdos', 'orzhov', 'boros', 'mardu']),
|
||||||
|
'B, U, W': ('Esper: Black/Blue/White', ['B', 'U', 'W', 'B, U', 'B, W', 'U, W', 'B, U, W'],
|
||||||
|
['colorless', 'black', 'blue', 'white', 'dimir', 'orzhov', 'azorius', 'esper']),
|
||||||
|
'G, R, U': ('Temur: Blue/Green/Red', ['G', 'R', 'U', 'G, R', 'G, U', 'R, U', 'G, R, U'],
|
||||||
|
['colorless', 'green', 'red', 'blue', 'simic', 'izzet', 'gruul', 'temur']),
|
||||||
|
'G, R, W': ('Naya: Green/Red/White', ['G', 'R', 'W', 'G, R', 'G, W', 'R, W', 'G, R, W'],
|
||||||
|
['colorless', 'green', 'red', 'white', 'gruul', 'selesnya', 'boros', 'naya']),
|
||||||
|
'G, U, W': ('Bant: Blue/Green/White', ['G', 'U', 'W', 'G, U', 'G, W', 'U, W', 'G, U, W'],
|
||||||
|
['colorless', 'green', 'blue', 'white', 'simic', 'azorius', 'selesnya', 'bant']),
|
||||||
|
'R, U, W': ('Jeskai: Blue/Red/White', ['R', 'U', 'W', 'R, U', 'U, W', 'R, W', 'R, U, W'],
|
||||||
|
['colorless', 'blue', 'red', 'white', 'izzet', 'azorius', 'boros', 'jeskai'])
|
||||||
|
}
|
||||||
|
|
||||||
|
OTHER_COLOR_MAP: Final[Dict[str, Tuple[str, List[str], List[str]]]] = {
|
||||||
|
'B, G, R, U': ('Glint: Black/Blue/Green/Red',
|
||||||
|
['B', 'G', 'R', 'U', 'B, G', 'B, R', 'B, U', 'G, R', 'G, U', 'R, U', 'B, G, R',
|
||||||
|
'B, G, U', 'B, R, U', 'G, R, U', 'B, G, R, U'],
|
||||||
|
['colorless', 'black', 'blue', 'green', 'red', 'golgari', 'rakdos', 'dimir',
|
||||||
|
'gruul', 'simic', 'izzet', 'jund', 'sultai', 'grixis', 'temur', 'glint']),
|
||||||
|
'B, G, R, W': ('Dune: Black/Green/Red/White',
|
||||||
|
['B', 'G', 'R', 'W', 'B, G', 'B, R', 'B, W', 'G, R', 'G, W', 'R, W', 'B, G, R',
|
||||||
|
'B, G, W', 'B, R, W', 'G, R, W', 'B, G, R, W'],
|
||||||
|
['colorless', 'black', 'green', 'red', 'white', 'golgari', 'rakdos', 'orzhov',
|
||||||
|
'gruul', 'selesnya', 'boros', 'jund', 'abzan', 'mardu', 'naya', 'dune']),
|
||||||
|
'B, G, U, W': ('Witch: Black/Blue/Green/White',
|
||||||
|
['B', 'G', 'U', 'W', 'B, G', 'B, U', 'B, W', 'G, U', 'G, W', 'U, W', 'B, G, U',
|
||||||
|
'B, G, W', 'B, U, W', 'G, U, W', 'B, G, U, W'],
|
||||||
|
['colorless', 'black', 'blue', 'green', 'white', 'golgari', 'dimir', 'orzhov',
|
||||||
|
'simic', 'selesnya', 'azorius', 'sultai', 'abzan', 'esper', 'bant', 'witch']),
|
||||||
|
'B, R, U, W': ('Yore: Black/Blue/Red/White',
|
||||||
|
['B', 'R', 'U', 'W', 'B, R', 'B, U', 'B, W', 'R, U', 'R, W', 'U, W', 'B, R, U',
|
||||||
|
'B, R, W', 'B, U, W', 'R, U, W', 'B, R, U, W'],
|
||||||
|
['colorless', 'black', 'blue', 'red', 'white', 'rakdos', 'dimir', 'orzhov',
|
||||||
|
'izzet', 'boros', 'azorius', 'grixis', 'mardu', 'esper', 'jeskai', 'yore']),
|
||||||
|
'G, R, U, W': ('Ink: Blue/Green/Red/White',
|
||||||
|
['G', 'R', 'U', 'W', 'G, R', 'G, U', 'G, W', 'R, U', 'R, W', 'U, W', 'G, R, U',
|
||||||
|
'G, R, W', 'G, U, W', 'R, U, W', 'G, R, U, W'],
|
||||||
|
['colorless', 'blue', 'green', 'red', 'white', 'gruul', 'simic', 'selesnya',
|
||||||
|
'izzet', 'boros', 'azorius', 'temur', 'naya', 'bant', 'jeskai', 'ink']),
|
||||||
|
'B, G, R, U, W': ('WUBRG: All colors',
|
||||||
|
['B', 'G', 'R', 'U', 'W', 'B, G', 'B, R', 'B, U', 'B, W', 'G, R', 'G, U',
|
||||||
|
'G, W', 'R, U', 'R, W', 'U, W', 'B, G, R', 'B, G, U', 'B, G, W', 'B, R, U',
|
||||||
|
'B, R, W', 'B, U, W', 'G, R, U', 'G, R, W', 'G, U, W', 'R, U, W',
|
||||||
|
'B, G, R, U', 'B, G, R, W', 'B, G, U, W', 'B, R, U, W', 'G, R, U, W',
|
||||||
|
'B, G, R, U, W'],
|
||||||
|
['colorless', 'black', 'green', 'red', 'blue', 'white', 'golgari', 'rakdos',
|
||||||
|
'dimir', 'orzhov', 'gruul', 'simic', 'selesnya', 'izzet', 'boros', 'azorius',
|
||||||
|
'jund', 'sultai', 'abzan', 'grixis', 'mardu', 'esper', 'temur', 'naya',
|
||||||
|
'bant', 'jeskai', 'glint', 'dune', 'witch', 'yore', 'ink', 'wubrg'])
|
||||||
|
}
|
||||||
|
|
||||||
|
# Price checking configuration
|
||||||
|
DEFAULT_PRICE_DELAY: Final[float] = 0.1 # Delay between price checks in seconds
|
||||||
|
MAX_PRICE_CHECK_ATTEMPTS: Final[int] = 3 # Maximum attempts for price checking
|
||||||
|
PRICE_CACHE_SIZE: Final[int] = 128 # Size of price check LRU cache
|
||||||
|
PRICE_CHECK_TIMEOUT: Final[int] = 30 # Timeout for price check requests in seconds
|
||||||
|
PRICE_TOLERANCE_MULTIPLIER: Final[float] = 1.1 # Multiplier for price tolerance
|
||||||
|
DEFAULT_MAX_CARD_PRICE: Final[float] = 20.0 # Default maximum price per card
|
||||||
|
|
||||||
|
# Deck composition defaults
|
||||||
|
DEFAULT_RAMP_COUNT: Final[int] = 8 # Default number of ramp pieces
|
||||||
|
DEFAULT_LAND_COUNT: Final[int] = 35 # Default total land count
|
||||||
|
DEFAULT_BASIC_LAND_COUNT: Final[int] = 20 # Default minimum basic lands
|
||||||
|
DEFAULT_NON_BASIC_LAND_SLOTS: Final[int] = 10 # Default number of non-basic land slots to reserve
|
||||||
|
DEFAULT_BASICS_PER_COLOR: Final[int] = 5 # Default number of basic lands to add per color
|
||||||
|
|
||||||
|
# Miscellaneous land configuration
|
||||||
|
MISC_LAND_MIN_COUNT: Final[int] = 5 # Minimum number of miscellaneous lands to add
|
||||||
|
MISC_LAND_MAX_COUNT: Final[int] = 10 # Maximum number of miscellaneous lands to add
|
||||||
|
MISC_LAND_POOL_SIZE: Final[int] = 100 # Maximum size of initial land pool to select from
|
||||||
|
|
||||||
|
# Default fetch land count
|
||||||
|
FETCH_LAND_DEFAULT_COUNT: Final[int] = 3 # Default number of fetch lands to include
|
||||||
|
|
||||||
|
# Basic Lands
|
||||||
|
BASIC_LANDS = ['Plains', 'Island', 'Swamp', 'Mountain', 'Forest']
|
||||||
|
|
||||||
|
# Basic land mappings
|
||||||
|
COLOR_TO_BASIC_LAND: Final[Dict[str, str]] = {
|
||||||
|
'W': 'Plains',
|
||||||
|
'U': 'Island',
|
||||||
|
'B': 'Swamp',
|
||||||
|
'R': 'Mountain',
|
||||||
|
'G': 'Forest',
|
||||||
|
'C': 'Wastes'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dual land type mappings
|
||||||
|
DUAL_LAND_TYPE_MAP: Final[Dict[str, str]] = {
|
||||||
|
'azorius': 'Plains Island',
|
||||||
|
'dimir': 'Island Swamp',
|
||||||
|
'rakdos': 'Swamp Mountain',
|
||||||
|
'gruul': 'Mountain Forest',
|
||||||
|
'selesnya': 'Forest Plains',
|
||||||
|
'orzhov': 'Plains Swamp',
|
||||||
|
'golgari': 'Swamp Forest',
|
||||||
|
'simic': 'Forest Island',
|
||||||
|
'izzet': 'Island Mountain',
|
||||||
|
'boros': 'Mountain Plains'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Triple land type mappings
|
||||||
|
TRIPLE_LAND_TYPE_MAP: Final[Dict[str, str]] = {
|
||||||
|
'bant': 'Forest Plains Island',
|
||||||
|
'esper': 'Plains Island Swamp',
|
||||||
|
'grixis': 'Island Swamp Mountain',
|
||||||
|
'jund': 'Swamp Mountain Forest',
|
||||||
|
'naya': 'Mountain Forest Plains',
|
||||||
|
'mardu': 'Mountain Plains Swamp',
|
||||||
|
'abzan': 'Plains Swamp Forest',
|
||||||
|
'sultai': 'Swamp Forest Island',
|
||||||
|
'temur': 'Forest Island Mountain',
|
||||||
|
'jeskai': 'Island Mountain Plains'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default preference for including dual lands
|
||||||
|
DEFAULT_DUAL_LAND_ENABLED: Final[bool] = True
|
||||||
|
|
||||||
|
# Default preference for including triple lands
|
||||||
|
DEFAULT_TRIPLE_LAND_ENABLED: Final[bool] = True
|
||||||
|
|
||||||
|
SNOW_COVERED_BASIC_LANDS: Final[Dict[str, str]] = {
|
||||||
|
'W': 'Snow-Covered Plains',
|
||||||
|
'U': 'Snow-Covered Island',
|
||||||
|
'B': 'Snow-Covered Swamp',
|
||||||
|
'G': 'Snow-Covered Forest'
|
||||||
|
}
|
||||||
|
|
||||||
|
SNOW_BASIC_LAND_MAPPING: Final[Dict[str, str]] = {
|
||||||
|
'W': 'Snow-Covered Plains',
|
||||||
|
'U': 'Snow-Covered Island',
|
||||||
|
'B': 'Snow-Covered Swamp',
|
||||||
|
'R': 'Snow-Covered Mountain',
|
||||||
|
'G': 'Snow-Covered Forest',
|
||||||
|
'C': 'Wastes' # Note: No snow-covered version exists for Wastes
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generic fetch lands list
|
||||||
|
GENERIC_FETCH_LANDS: Final[List[str]] = [
|
||||||
|
'Evolving Wilds',
|
||||||
|
'Terramorphic Expanse',
|
||||||
|
'Shire Terrace',
|
||||||
|
'Escape Tunnel',
|
||||||
|
'Promising Vein',
|
||||||
|
'Myriad Landscape',
|
||||||
|
'Fabled Passage',
|
||||||
|
'Terminal Moraine',
|
||||||
|
'Prismatic Vista'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Kindred land constants
|
||||||
|
KINDRED_STAPLE_LANDS: Final[List[Dict[str, str]]] = [
|
||||||
|
{
|
||||||
|
'name': 'Path of Ancestry',
|
||||||
|
'type': 'Land'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Three Tree City',
|
||||||
|
'type': 'Legendary Land'
|
||||||
|
},
|
||||||
|
{'name': 'Cavern of Souls', 'type': 'Land'}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Color-specific fetch land mappings
|
||||||
|
COLOR_TO_FETCH_LANDS: Final[Dict[str, List[str]]] = {
|
||||||
|
'W': [
|
||||||
|
'Flooded Strand',
|
||||||
|
'Windswept Heath',
|
||||||
|
'Marsh Flats',
|
||||||
|
'Arid Mesa',
|
||||||
|
'Brokers Hideout',
|
||||||
|
'Obscura Storefront',
|
||||||
|
'Cabaretti Courtyard'
|
||||||
|
],
|
||||||
|
'U': [
|
||||||
|
'Flooded Strand',
|
||||||
|
'Polluted Delta',
|
||||||
|
'Scalding Tarn',
|
||||||
|
'Misty Rainforest',
|
||||||
|
'Brokers Hideout',
|
||||||
|
'Obscura Storefront',
|
||||||
|
'Maestros Theater'
|
||||||
|
],
|
||||||
|
'B': [
|
||||||
|
'Polluted Delta',
|
||||||
|
'Bloodstained Mire',
|
||||||
|
'Marsh Flats',
|
||||||
|
'Verdant Catacombs',
|
||||||
|
'Obscura Storefront',
|
||||||
|
'Maestros Theater',
|
||||||
|
'Riveteers Overlook'
|
||||||
|
],
|
||||||
|
'R': [
|
||||||
|
'Bloodstained Mire',
|
||||||
|
'Wooded Foothills',
|
||||||
|
'Scalding Tarn',
|
||||||
|
'Arid Mesa',
|
||||||
|
'Maestros Theater',
|
||||||
|
'Riveteers Overlook',
|
||||||
|
'Cabaretti Courtyard'
|
||||||
|
],
|
||||||
|
'G': [
|
||||||
|
'Wooded Foothills',
|
||||||
|
'Windswept Heath',
|
||||||
|
'Verdant Catacombs',
|
||||||
|
'Misty Rainforest',
|
||||||
|
'Brokers Hideout',
|
||||||
|
'Riveteers Overlook',
|
||||||
|
'Cabaretti Courtyard'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Staple land conditions mapping
|
||||||
|
STAPLE_LAND_CONDITIONS: Final[Dict[str, Callable[[List[str], List[str], int], bool]]] = {
|
||||||
|
'Reliquary Tower': lambda commander_tags, colors, commander_power: True, # Always include
|
||||||
|
'Ash Barrens': lambda commander_tags, colors, commander_power: 'Landfall' not in commander_tags,
|
||||||
|
'Command Tower': lambda commander_tags, colors, commander_power: len(colors) > 1,
|
||||||
|
'Exotic Orchard': lambda commander_tags, colors, commander_power: len(colors) > 1,
|
||||||
|
'War Room': lambda commander_tags, colors, commander_power: len(colors) <= 2,
|
||||||
|
'Rogue\'s Passage': lambda commander_tags, colors, commander_power: commander_power >= 5
|
||||||
|
}
|
||||||
|
|
||||||
|
# Constants for land removal functionality
|
||||||
|
LAND_REMOVAL_MAX_ATTEMPTS: Final[int] = 3
|
||||||
|
|
||||||
|
# Protected lands that cannot be removed during land removal process
|
||||||
|
PROTECTED_LANDS: Final[List[str]] = BASIC_LANDS + [land['name'] for land in KINDRED_STAPLE_LANDS]
|
||||||
|
|
||||||
|
# Other defaults
|
||||||
|
DEFAULT_CREATURE_COUNT: Final[int] = 25 # Default number of creatures
|
||||||
|
DEFAULT_REMOVAL_COUNT: Final[int] = 10 # Default number of spot removal spells
|
||||||
|
DEFAULT_WIPES_COUNT: Final[int] = 2 # Default number of board wipes
|
||||||
|
|
||||||
|
DEFAULT_CARD_ADVANTAGE_COUNT: Final[int] = 10 # Default number of card advantage pieces
|
||||||
|
DEFAULT_PROTECTION_COUNT: Final[int] = 8 # Default number of protection spells
|
||||||
|
|
||||||
|
# Deck composition prompts
|
||||||
|
DECK_COMPOSITION_PROMPTS: Final[Dict[str, str]] = {
|
||||||
|
'ramp': 'Enter desired number of ramp pieces (default: 8):',
|
||||||
|
'lands': 'Enter desired number of total lands (default: 35):',
|
||||||
|
'basic_lands': 'Enter minimum number of basic lands (default: 20):',
|
||||||
|
'creatures': 'Enter desired number of creatures (default: 25):',
|
||||||
|
'removal': 'Enter desired number of spot removal spells (default: 10):',
|
||||||
|
'wipes': 'Enter desired number of board wipes (default: 2):',
|
||||||
|
'card_advantage': 'Enter desired number of card advantage pieces (default: 10):',
|
||||||
|
'protection': 'Enter desired number of protection spells (default: 8):',
|
||||||
|
'max_deck_price': 'Enter maximum total deck price in dollars (default: 400.0):',
|
||||||
|
'max_card_price': 'Enter maximum price per card in dollars (default: 20.0):'
|
||||||
|
}
|
||||||
|
DEFAULT_MAX_DECK_PRICE: Final[float] = 400.0 # Default maximum total deck price
|
||||||
|
BATCH_PRICE_CHECK_SIZE: Final[int] = 50 # Number of cards to check prices for in one batch
|
||||||
|
# Constants for input validation
|
||||||
|
|
||||||
|
# Type aliases
|
||||||
|
CardName = str
|
||||||
|
CardType = str
|
||||||
|
ThemeTag = str
|
||||||
|
ColorIdentity = str
|
||||||
|
ColorList = List[str]
|
||||||
|
ColorInfo = Tuple[str, List[str], List[str]]
|
||||||
|
|
||||||
|
INPUT_VALIDATION = {
|
||||||
|
'max_attempts': 3,
|
||||||
|
'default_text_message': 'Please enter a valid text response.',
|
||||||
|
'default_number_message': 'Please enter a valid number.',
|
||||||
|
'default_confirm_message': 'Please enter Y/N or Yes/No.',
|
||||||
|
'default_choice_message': 'Please select a valid option from the list.'
|
||||||
|
}
|
||||||
|
|
||||||
|
QUESTION_TYPES = [
|
||||||
|
'Text',
|
||||||
|
'Number',
|
||||||
|
'Confirm',
|
||||||
|
'Choice'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for theme weight management and selection
|
||||||
|
# Multiplier for initial card pool size during theme-based selection
|
||||||
|
THEME_POOL_SIZE_MULTIPLIER: Final[float] = 2.0
|
||||||
|
|
||||||
|
# Bonus multiplier for cards that match multiple deck themes
|
||||||
|
THEME_PRIORITY_BONUS: Final[float] = 1.2
|
||||||
|
|
||||||
|
# Safety multiplier to avoid overshooting target counts
|
||||||
|
THEME_WEIGHT_MULTIPLIER: Final[float] = 0.9
|
||||||
|
|
||||||
|
THEME_WEIGHTS_DEFAULT: Final[Dict[str, float]] = {
|
||||||
|
'primary': 1.0,
|
||||||
|
'secondary': 0.6,
|
||||||
|
'tertiary': 0.3,
|
||||||
|
'hidden': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
WEIGHT_ADJUSTMENT_FACTORS: Final[Dict[str, float]] = {
|
||||||
|
'kindred_primary': 1.5, # Boost for Kindred themes as primary
|
||||||
|
'kindred_secondary': 1.3, # Boost for Kindred themes as secondary
|
||||||
|
'kindred_tertiary': 1.2, # Boost for Kindred themes as tertiary
|
||||||
|
'theme_synergy': 1.2 # Boost for themes that work well together
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_THEME_TAGS = [
|
||||||
|
'Aggro', 'Aristocrats', 'Artifacts Matter', 'Big Mana', 'Blink',
|
||||||
|
'Board Wipes', 'Burn', 'Cantrips', 'Card Draw', 'Clones',
|
||||||
|
'Combat Matters', 'Control', 'Counters Matter', 'Energy',
|
||||||
|
'Enter the Battlefield', 'Equipment', 'Exile Matters', 'Infect',
|
||||||
|
'Interaction', 'Lands Matter', 'Leave the Battlefield', 'Legends Matter',
|
||||||
|
'Life Matters', 'Mill', 'Monarch', 'Protection', 'Ramp', 'Reanimate',
|
||||||
|
'Removal', 'Sacrifice Matters', 'Spellslinger', 'Stax', 'Super Friends',
|
||||||
|
'Theft', 'Token Creation', 'Tokens Matter', 'Voltron', 'X Spells'
|
||||||
|
]
|
||||||
|
|
||||||
|
# CSV processing configuration
|
||||||
|
CSV_READ_TIMEOUT: Final[int] = 30 # Timeout in seconds for CSV read operations
|
||||||
|
CSV_PROCESSING_BATCH_SIZE: Final[int] = 1000 # Number of rows to process in each batch
|
||||||
|
|
||||||
|
# CSV validation configuration
|
||||||
|
CSV_VALIDATION_RULES: Final[Dict[str, Dict[str, Union[str, int, float]]]] = {
|
||||||
|
'name': {'type': ('str', 'object'), 'required': True, 'unique': True},
|
||||||
|
'edhrecRank': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 100000},
|
||||||
|
'manaValue': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 20},
|
||||||
|
'power': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'},
|
||||||
|
'toughness': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Required columns for CSV validation
|
||||||
|
CSV_REQUIRED_COLUMNS: Final[List[str]] = [
|
||||||
|
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
|
||||||
|
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
|
||||||
|
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
|
||||||
|
]
|
||||||
|
|
||||||
|
# DataFrame processing configuration
|
||||||
|
BATCH_SIZE: Final[int] = 1000 # Number of records to process at once
|
||||||
|
DATAFRAME_BATCH_SIZE: Final[int] = 500 # Batch size for DataFrame operations
|
||||||
|
TRANSFORM_BATCH_SIZE: Final[int] = 250 # Batch size for data transformations
|
||||||
|
CSV_DOWNLOAD_TIMEOUT: Final[int] = 30 # Timeout in seconds for CSV downloads
|
||||||
|
PROGRESS_UPDATE_INTERVAL: Final[int] = 100 # Number of records between progress updates
|
||||||
|
|
||||||
|
# DataFrame operation timeouts
|
||||||
|
DATAFRAME_READ_TIMEOUT: Final[int] = 30 # Timeout for DataFrame read operations
|
||||||
|
DATAFRAME_WRITE_TIMEOUT: Final[int] = 30 # Timeout for DataFrame write operations
|
||||||
|
DATAFRAME_TRANSFORM_TIMEOUT: Final[int] = 45 # Timeout for DataFrame transformations
|
||||||
|
DATAFRAME_VALIDATION_TIMEOUT: Final[int] = 20 # Timeout for DataFrame validation
|
||||||
|
|
||||||
|
# Required DataFrame columns
|
||||||
|
DATAFRAME_REQUIRED_COLUMNS: Final[List[str]] = [
|
||||||
|
'name', 'type', 'colorIdentity', 'manaValue', 'text',
|
||||||
|
'edhrecRank', 'themeTags', 'keywords'
|
||||||
|
]
|
||||||
|
|
||||||
|
# DataFrame validation rules
|
||||||
|
DATAFRAME_VALIDATION_RULES: Final[Dict[str, Dict[str, Union[str, int, float, bool]]]] = {
|
||||||
|
'name': {'type': ('str', 'object'), 'required': True, 'unique': True},
|
||||||
|
'edhrecRank': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 100000},
|
||||||
|
'manaValue': {'type': ('str', 'int', 'float', 'object'), 'min': 0, 'max': 20},
|
||||||
|
'power': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'},
|
||||||
|
'toughness': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'},
|
||||||
|
'colorIdentity': {'type': ('str', 'object'), 'required': True},
|
||||||
|
'text': {'type': ('str', 'object'), 'required': False}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Card type sorting order for organizing libraries
|
||||||
|
# This constant defines the order in which different card types should be sorted
|
||||||
|
# when organizing a deck library. The order is designed to group cards logically,
|
||||||
|
# starting with Planeswalkers and ending with Lands.
|
||||||
|
CARD_TYPE_SORT_ORDER: Final[List[str]] = [
|
||||||
|
'Planeswalker', 'Battle', 'Creature', 'Instant', 'Sorcery',
|
||||||
|
'Artifact', 'Enchantment', 'Land'
|
||||||
|
]
|
||||||
|
|
@ -28,8 +28,6 @@ Typical usage example:
|
||||||
|
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
import functools
|
import functools
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union, cast
|
from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union, cast
|
||||||
|
|
||||||
|
|
@ -56,7 +54,7 @@ from exceptions import (
|
||||||
)
|
)
|
||||||
from input_handler import InputHandler
|
from input_handler import InputHandler
|
||||||
from price_check import PriceChecker
|
from price_check import PriceChecker
|
||||||
from settings import (
|
from .builder_constants import (
|
||||||
CARD_TYPE_SORT_ORDER, COLOR_TO_BASIC_LAND, COMMANDER_CONVERTERS,
|
CARD_TYPE_SORT_ORDER, COLOR_TO_BASIC_LAND, COMMANDER_CONVERTERS,
|
||||||
COMMANDER_CSV_PATH, DATAFRAME_BATCH_SIZE,
|
COMMANDER_CSV_PATH, DATAFRAME_BATCH_SIZE,
|
||||||
DATAFRAME_REQUIRED_COLUMNS, DATAFRAME_TRANSFORM_TIMEOUT,
|
DATAFRAME_REQUIRED_COLUMNS, DATAFRAME_TRANSFORM_TIMEOUT,
|
||||||
|
|
@ -72,35 +70,13 @@ from settings import (
|
||||||
WEIGHT_ADJUSTMENT_FACTORS
|
WEIGHT_ADJUSTMENT_FACTORS
|
||||||
)
|
)
|
||||||
from type_definitions import CardLibraryDF, CommanderDF, LandDF
|
from type_definitions import CardLibraryDF, CommanderDF, LandDF
|
||||||
|
import logging_util
|
||||||
# Create logs directory if it doesn't exist
|
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/builder_utils.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
# Add handlers to logger
|
logger.addHandler(logging_util.stream_handler)
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
# Type variables for generic functions
|
# Type variables for generic functions
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
@ -993,7 +969,7 @@ def get_available_kindred_lands(land_df: pd.DataFrame, colors: List[str], comman
|
||||||
|
|
||||||
# Find lands specific to each creature type
|
# Find lands specific to each creature type
|
||||||
for creature_type in creature_types:
|
for creature_type in creature_types:
|
||||||
logging.info(f'Searching for {creature_type}-specific lands')
|
logger.info(f'Searching for {creature_type}-specific lands')
|
||||||
|
|
||||||
# Filter lands by creature type mentions in text or type
|
# Filter lands by creature type mentions in text or type
|
||||||
type_specific = land_df[
|
type_specific = land_df[
|
||||||
8
code/file_setup/__init__.py
Normal file
8
code/file_setup/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
"""Initialize the file_setup package."""
|
||||||
|
|
||||||
|
from .setup import setup, regenerate_csv_by_color
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'setup',
|
||||||
|
'regenerate_csv_by_color'
|
||||||
|
]
|
||||||
|
|
@ -28,15 +28,11 @@ from typing import Union, List, Dict, Any
|
||||||
import inquirer
|
import inquirer
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
# Local application imports
|
# Local imports
|
||||||
from settings import (
|
import logging_util
|
||||||
banned_cards,
|
from settings import CSV_DIRECTORY
|
||||||
COLOR_ABRV,
|
from .setup_constants import BANNED_CARDS, SETUP_COLORS, COLOR_ABRV, MTGJSON_API_URL
|
||||||
CSV_DIRECTORY,
|
from .setup_utils import (
|
||||||
MTGJSON_API_URL,
|
|
||||||
SETUP_COLORS
|
|
||||||
)
|
|
||||||
from setup_utils import (
|
|
||||||
download_cards_csv,
|
download_cards_csv,
|
||||||
filter_by_color_identity,
|
filter_by_color_identity,
|
||||||
filter_dataframe,
|
filter_dataframe,
|
||||||
|
|
@ -50,34 +46,15 @@ from exceptions import (
|
||||||
MTGJSONDownloadError
|
MTGJSONDownloadError
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create logs directory if it doesn't exist
|
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/setup.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
|
logger.addHandler(logging_util.stream_handler)
|
||||||
|
|
||||||
# Add handlers to logger
|
# Create CSV directory if it doesn't exist
|
||||||
logger.addHandler(file_handler)
|
if not os.path.exists(CSV_DIRECTORY):
|
||||||
logger.addHandler(stream_handler)
|
os.makedirs(CSV_DIRECTORY)
|
||||||
|
|
||||||
def check_csv_exists(file_path: Union[str, Path]) -> bool:
|
def check_csv_exists(file_path: Union[str, Path]) -> bool:
|
||||||
"""Check if a CSV file exists at the specified path.
|
"""Check if a CSV file exists at the specified path.
|
||||||
|
|
@ -208,7 +185,7 @@ def determine_commanders() -> None:
|
||||||
|
|
||||||
# Apply standard filters
|
# Apply standard filters
|
||||||
logger.info('Applying standard card filters')
|
logger.info('Applying standard card filters')
|
||||||
filtered_df = filter_dataframe(filtered_df, banned_cards)
|
filtered_df = filter_dataframe(filtered_df, BANNED_CARDS)
|
||||||
|
|
||||||
# Save commander cards
|
# Save commander cards
|
||||||
logger.info('Saving validated commander cards')
|
logger.info('Saving validated commander cards')
|
||||||
118
code/file_setup/setup_constants.py
Normal file
118
code/file_setup/setup_constants.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
|
||||||
|
|
||||||
|
BANNED_CARDS: List[str] = [# in commander
|
||||||
|
'Ancestral Recall', 'Balance', 'Biorhythm', 'Black Lotus',
|
||||||
|
'Braids, Cabal Minion', 'Chaos Orb', 'Coalition Victory',
|
||||||
|
'Channel', 'Dockside Extortionist', 'Emrakul, the Aeons Torn',
|
||||||
|
'Erayo, Soratami Ascendant', 'Falling Star', 'Fastbond',
|
||||||
|
'Flash', 'Gifts Ungiven', 'Golos, Tireless Pilgrim',
|
||||||
|
'Griselbrand', 'Hullbreacher', 'Iona, Shield of Emeria',
|
||||||
|
'Karakas', 'Jeweled Lotus', 'Leovold, Emissary of Trest',
|
||||||
|
'Library of Alexandria', 'Limited Resources', 'Lutri, the Spellchaser',
|
||||||
|
'Mana Crypt', 'Mox Emerald', 'Mox Jet', 'Mox Pearl', 'Mox Ruby',
|
||||||
|
'Mox Sapphire', 'Nadu, Winged Wisdom', 'Panoptic Mirror',
|
||||||
|
'Paradox Engine', 'Primeval Titan', 'Prophet of Kruphix',
|
||||||
|
'Recurring Nightmare', 'Rofellos, Llanowar Emissary', 'Shahrazad',
|
||||||
|
'Sundering Titan', 'Sway of the Stars', 'Sylvan Primordial',
|
||||||
|
'Time Vault', 'Time Walk', 'Tinker', 'Tolarian Academy',
|
||||||
|
'Trade Secrets', 'Upheaval', 'Yawgmoth\'s Bargain',
|
||||||
|
|
||||||
|
# In constructed
|
||||||
|
'Invoke Prejudice', 'Cleanse', 'Stone-Throwing Devils', 'Pradesh Gypsies',
|
||||||
|
'Jihad', 'Imprison', 'Crusade'
|
||||||
|
]
|
||||||
|
|
||||||
|
SETUP_COLORS: List[str] = ['colorless', 'white', 'blue', 'black', 'green', 'red',
|
||||||
|
'azorius', 'orzhov', 'selesnya', 'boros', 'dimir',
|
||||||
|
'simic', 'izzet', 'golgari', 'rakdos', 'gruul',
|
||||||
|
'bant', 'esper', 'grixis', 'jund', 'naya',
|
||||||
|
'abzan', 'jeskai', 'mardu', 'sultai', 'temur',
|
||||||
|
'dune', 'glint', 'ink', 'witch', 'yore', 'wubrg']
|
||||||
|
|
||||||
|
COLOR_ABRV: List[str] = ['Colorless', 'W', 'U', 'B', 'G', 'R',
|
||||||
|
'U, W', 'B, W', 'G, W', 'R, W', 'B, U',
|
||||||
|
'G, U', 'R, U', 'B, G', 'B, R', 'G, R',
|
||||||
|
'G, U, W', 'B, U, W', 'B, R, U', 'B, G, R', 'G, R, W',
|
||||||
|
'B, G, W', 'R, U, W', 'B, R, W', 'B, G, U', 'G, R, U',
|
||||||
|
'B, G, R, W', 'B, G, R, U', 'G, R, U, W', 'B, G, U, W',
|
||||||
|
'B, R, U, W', 'B, G, R, U, W']
|
||||||
|
|
||||||
|
# Constants for setup and CSV processing
|
||||||
|
MTGJSON_API_URL: str = 'https://mtgjson.com/api/v5/csv/cards.csv'
|
||||||
|
|
||||||
|
LEGENDARY_OPTIONS: List[str] = [
|
||||||
|
'Legendary Creature',
|
||||||
|
'Legendary Artifact',
|
||||||
|
'Legendary Artifact Creature',
|
||||||
|
'Legendary Enchantment Creature',
|
||||||
|
'Legendary Planeswalker'
|
||||||
|
]
|
||||||
|
|
||||||
|
NON_LEGAL_SETS: List[str] = [
|
||||||
|
'PHTR', 'PH17', 'PH18', 'PH19', 'PH20', 'PH21',
|
||||||
|
'UGL', 'UND', 'UNH', 'UST'
|
||||||
|
]
|
||||||
|
|
||||||
|
CARD_TYPES_TO_EXCLUDE: List[str] = [
|
||||||
|
'Plane —',
|
||||||
|
'Conspiracy',
|
||||||
|
'Vanguard',
|
||||||
|
'Scheme',
|
||||||
|
'Phenomenon',
|
||||||
|
'Stickers',
|
||||||
|
'Attraction',
|
||||||
|
'Hero',
|
||||||
|
'Contraption'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Columns to keep when processing CSV files
|
||||||
|
CSV_PROCESSING_COLUMNS: List[str] = [
|
||||||
|
'name', # Card name
|
||||||
|
'faceName', # Name of specific face for multi-faced cards
|
||||||
|
'edhrecRank', # Card's rank on EDHREC
|
||||||
|
'colorIdentity', # Color identity for Commander format
|
||||||
|
'colors', # Actual colors in card's mana cost
|
||||||
|
'manaCost', # Mana cost string
|
||||||
|
'manaValue', # Converted mana cost
|
||||||
|
'type', # Card type line
|
||||||
|
'layout', # Card layout (normal, split, etc)
|
||||||
|
'text', # Card text/rules
|
||||||
|
'power', # Power (for creatures)
|
||||||
|
'toughness', # Toughness (for creatures)
|
||||||
|
'keywords', # Card's keywords
|
||||||
|
'side' # Side identifier for multi-faced cards
|
||||||
|
]
|
||||||
|
|
||||||
|
# Configuration for DataFrame sorting operations
|
||||||
|
SORT_CONFIG = {
|
||||||
|
'columns': ['name', 'side'], # Columns to sort by
|
||||||
|
'case_sensitive': False # Ignore case when sorting
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuration for DataFrame filtering operations
|
||||||
|
FILTER_CONFIG: Dict[str, Dict[str, List[str]]] = {
|
||||||
|
'layout': {
|
||||||
|
'exclude': ['reversible_card']
|
||||||
|
},
|
||||||
|
'availability': {
|
||||||
|
'require': ['paper']
|
||||||
|
},
|
||||||
|
'promoTypes': {
|
||||||
|
'exclude': ['playtest']
|
||||||
|
},
|
||||||
|
'securityStamp': {
|
||||||
|
'exclude': ['Heart', 'Acorn']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
COLUMN_ORDER: List[str] = [
|
||||||
|
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
|
||||||
|
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
|
||||||
|
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
|
||||||
|
]
|
||||||
|
|
||||||
|
TAGGED_COLUMN_ORDER: List[str] = [
|
||||||
|
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
|
||||||
|
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
|
||||||
|
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
|
||||||
|
]
|
||||||
|
|
@ -28,17 +28,14 @@ import pandas as pd
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
# Local application imports
|
# Local application imports
|
||||||
from settings import (
|
from .setup_constants import (
|
||||||
CSV_PROCESSING_COLUMNS,
|
CSV_PROCESSING_COLUMNS,
|
||||||
CARD_TYPES_TO_EXCLUDE,
|
CARD_TYPES_TO_EXCLUDE,
|
||||||
NON_LEGAL_SETS,
|
NON_LEGAL_SETS,
|
||||||
LEGENDARY_OPTIONS,
|
LEGENDARY_OPTIONS,
|
||||||
FILL_NA_COLUMNS,
|
|
||||||
SORT_CONFIG,
|
SORT_CONFIG,
|
||||||
FILTER_CONFIG,
|
FILTER_CONFIG,
|
||||||
COLUMN_ORDER,
|
COLUMN_ORDER,
|
||||||
PRETAG_COLUMN_ORDER,
|
|
||||||
EXCLUDED_CARD_TYPES,
|
|
||||||
TAGGED_COLUMN_ORDER
|
TAGGED_COLUMN_ORDER
|
||||||
)
|
)
|
||||||
from exceptions import (
|
from exceptions import (
|
||||||
|
|
@ -48,35 +45,14 @@ from exceptions import (
|
||||||
CommanderValidationError
|
CommanderValidationError
|
||||||
)
|
)
|
||||||
from type_definitions import CardLibraryDF
|
from type_definitions import CardLibraryDF
|
||||||
|
from settings import FILL_NA_COLUMNS
|
||||||
# Create logs directory if it doesn't exist
|
import logging_util
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/setup_utils.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
# Add handlers to logger
|
logger.addHandler(logging_util.stream_handler)
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
# Type definitions
|
# Type definitions
|
||||||
class FilterRule(TypedDict):
|
class FilterRule(TypedDict):
|
||||||
|
|
@ -8,7 +8,9 @@ from typing import Any, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import inquirer.prompt
|
import inquirer.prompt
|
||||||
from settings import (
|
from settings import (
|
||||||
COLORS, COLOR_ABRV, DEFAULT_MAX_CARD_PRICE,
|
COLORS, COLOR_ABRV
|
||||||
|
)
|
||||||
|
from deck_builder.builder_constants import (DEFAULT_MAX_CARD_PRICE,
|
||||||
DEFAULT_MAX_DECK_PRICE, DEFAULT_THEME_TAGS, MONO_COLOR_MAP,
|
DEFAULT_MAX_DECK_PRICE, DEFAULT_THEME_TAGS, MONO_COLOR_MAP,
|
||||||
DUAL_COLOR_MAP, TRI_COLOR_MAP, OTHER_COLOR_MAP
|
DUAL_COLOR_MAP, TRI_COLOR_MAP, OTHER_COLOR_MAP
|
||||||
)
|
)
|
||||||
|
|
@ -28,36 +30,13 @@ from exceptions import (
|
||||||
PriceLimitError,
|
PriceLimitError,
|
||||||
PriceValidationError
|
PriceValidationError
|
||||||
)
|
)
|
||||||
|
import logging_util
|
||||||
# Create logs directory if it doesn't exist
|
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/input_handler.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
# Add handlers to logger
|
logger.addHandler(logging_util.stream_handler)
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
|
|
||||||
class InputHandler:
|
class InputHandler:
|
||||||
"""Handles user input operations with validation and error handling.
|
"""Handles user input operations with validation and error handling.
|
||||||
29
code/logging_util.py
Normal file
29
code/logging_util.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from settings import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Create logs directory if it doesn't exist
|
||||||
|
if not os.path.exists('logs'):
|
||||||
|
os.makedirs('logs')
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
LOG_DIR = 'logs'
|
||||||
|
LOG_FILE = os.path.join(LOG_DIR, 'deck_builder.log')
|
||||||
|
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
LOG_LEVEL = logging.INFO
|
||||||
|
|
||||||
|
# Create formatters and handlers
|
||||||
|
# Create a formatter that removes double underscores
|
||||||
|
class NoDunderFormatter(logging.Formatter):
|
||||||
|
def format(self, record):
|
||||||
|
record.name = record.name.replace("__", "")
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
# File handler
|
||||||
|
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
||||||
|
file_handler.setFormatter(NoDunderFormatter(LOG_FORMAT))
|
||||||
|
|
||||||
|
# Stream handler
|
||||||
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.setFormatter(NoDunderFormatter(LOG_FORMAT))
|
||||||
|
|
@ -9,8 +9,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
import sys
|
import sys
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NoReturn, Optional
|
from typing import NoReturn, Optional
|
||||||
|
|
||||||
|
|
@ -18,38 +16,16 @@ from typing import NoReturn, Optional
|
||||||
import inquirer.prompt
|
import inquirer.prompt
|
||||||
|
|
||||||
# Local imports
|
# Local imports
|
||||||
import deck_builder
|
from deck_builder import DeckBuilder
|
||||||
import setup
|
from file_setup import setup
|
||||||
import tagger
|
from tagging import tagger
|
||||||
|
import logging_util
|
||||||
# Create logs directory if it doesn't exist
|
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = os.path.join(LOG_DIR, 'main.log')
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
# Add handlers to logger
|
logger.addHandler(logging_util.stream_handler)
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
# Menu constants
|
# Menu constants
|
||||||
MENU_SETUP = 'Setup'
|
MENU_SETUP = 'Setup'
|
||||||
|
|
@ -58,6 +34,8 @@ MENU_BUILD_DECK = 'Build a Deck'
|
||||||
MENU_QUIT = 'Quit'
|
MENU_QUIT = 'Quit'
|
||||||
|
|
||||||
MENU_CHOICES = [MENU_SETUP, MAIN_TAG, MENU_BUILD_DECK, MENU_QUIT]
|
MENU_CHOICES = [MENU_SETUP, MAIN_TAG, MENU_BUILD_DECK, MENU_QUIT]
|
||||||
|
|
||||||
|
builder = DeckBuilder()
|
||||||
def get_menu_choice() -> Optional[str]:
|
def get_menu_choice() -> Optional[str]:
|
||||||
"""Display the main menu and get user choice.
|
"""Display the main menu and get user choice.
|
||||||
|
|
||||||
|
|
@ -124,11 +102,11 @@ def run_menu() -> NoReturn:
|
||||||
|
|
||||||
match choice:
|
match choice:
|
||||||
case 'Setup':
|
case 'Setup':
|
||||||
setup.setup()
|
setup()
|
||||||
case 'Tag CSV Files':
|
case 'Tag CSV Files':
|
||||||
tagger.run_tagging()
|
tagger.run_tagging()
|
||||||
case 'Build a Deck':
|
case 'Build a Deck':
|
||||||
deck_builder.main()
|
builder.determine_commander()
|
||||||
case 'Quit':
|
case 'Quit':
|
||||||
logger.info("Exiting application")
|
logger.info("Exiting application")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
@ -8,7 +8,6 @@ price lookups.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
import logging
|
|
||||||
import time
|
import time
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Dict, List, Optional, Tuple, Union
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
|
@ -24,7 +23,7 @@ from exceptions import (
|
||||||
PriceTimeoutError,
|
PriceTimeoutError,
|
||||||
PriceValidationError
|
PriceValidationError
|
||||||
)
|
)
|
||||||
from settings import (
|
from deck_builder.builder_constants import (
|
||||||
BATCH_PRICE_CHECK_SIZE,
|
BATCH_PRICE_CHECK_SIZE,
|
||||||
DEFAULT_MAX_CARD_PRICE,
|
DEFAULT_MAX_CARD_PRICE,
|
||||||
DEFAULT_MAX_DECK_PRICE,
|
DEFAULT_MAX_DECK_PRICE,
|
||||||
|
|
@ -35,31 +34,13 @@ from settings import (
|
||||||
PRICE_TOLERANCE_MULTIPLIER
|
PRICE_TOLERANCE_MULTIPLIER
|
||||||
)
|
)
|
||||||
from type_definitions import PriceCache
|
from type_definitions import PriceCache
|
||||||
|
import logging_util
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/price_check.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
# Add handlers to logger
|
logger.addHandler(logging_util.stream_handler)
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
class PriceChecker:
|
class PriceChecker:
|
||||||
"""Class for handling MTG card price checking and validation.
|
"""Class for handling MTG card price checking and validation.
|
||||||
40
code/settings.py
Normal file
40
code/settings.py
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# Standard library imports
|
||||||
|
import os
|
||||||
|
from sys import exit
|
||||||
|
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
|
||||||
|
|
||||||
|
# Third-party imports
|
||||||
|
|
||||||
|
COLORS = ['colorless', 'white', 'blue', 'black', 'red', 'green',
|
||||||
|
'azorius', 'orzhov', 'selesnya', 'boros', 'dimir',
|
||||||
|
'simic', 'izzet', 'golgari', 'rakdos', 'gruul',
|
||||||
|
'bant', 'esper', 'grixis', 'jund', 'naya',
|
||||||
|
'abzan', 'jeskai', 'mardu', 'sultai', 'temur',
|
||||||
|
'dune', 'glint', 'ink', 'witch', 'yore', 'wubrg',
|
||||||
|
'commander']
|
||||||
|
|
||||||
|
COLOR_ABRV: List[str] = ['Colorless', 'W', 'U', 'B', 'G', 'R',
|
||||||
|
'U, W', 'B, W', 'G, W', 'R, W', 'B, U',
|
||||||
|
'G, U', 'R, U', 'B, G', 'B, R', 'G, R',
|
||||||
|
'G, U, W', 'B, U, W', 'B, R, U', 'B, G, R', 'G, R, W',
|
||||||
|
'B, G, W', 'R, U, W', 'B, R, W', 'B, G, U', 'G, R, U',
|
||||||
|
'B, G, R, W', 'B, G, R, U', 'G, R, U, W', 'B, G, U, W',
|
||||||
|
'B, R, U, W', 'B, G, R, U, W']
|
||||||
|
|
||||||
|
MAIN_MENU_ITEMS: List[str] = ['Build A Deck', 'Setup CSV Files', 'Tag CSV Files', 'Quit']
|
||||||
|
|
||||||
|
SETUP_MENU_ITEMS: List[str] = ['Initial Setup', 'Regenerate CSV', 'Main Menu']
|
||||||
|
|
||||||
|
CSV_DIRECTORY: str = 'csv_files'
|
||||||
|
|
||||||
|
# Configuration for handling null/NA values in DataFrame columns
|
||||||
|
FILL_NA_COLUMNS: Dict[str, Optional[str]] = {
|
||||||
|
'colorIdentity': 'Colorless', # Default color identity for cards without one
|
||||||
|
'faceName': None # Use card's name column value when face name is not available
|
||||||
|
}
|
||||||
|
|
||||||
|
MULTIPLE_COPY_CARDS = ['Dragon\'s Approach', 'Hare Apparent', 'Nazgûl', 'Persistent Petitioners',
|
||||||
|
'Rat Colony', 'Relentless Rats', 'Seven Dwarves', 'Shadowborn Apostle',
|
||||||
|
'Slime Against Humanity', 'Templar Knight']
|
||||||
4
code/tagging/__init__.py
Normal file
4
code/tagging/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
This module initializes the tagging package, which contains functionality for
|
||||||
|
handling card tagging and related operations in the MTG deck builder application.
|
||||||
|
"""
|
||||||
718
code/tagging/tag_constants.py
Normal file
718
code/tagging/tag_constants.py
Normal file
|
|
@ -0,0 +1,718 @@
|
||||||
|
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
|
||||||
|
|
||||||
|
TRIGGERS: List[str] = ['when', 'whenever', 'at']
|
||||||
|
|
||||||
|
NUM_TO_SEARCH: List[str] = ['a', 'an', 'one', '1', 'two', '2', 'three', '3', 'four','4', 'five', '5',
|
||||||
|
'six', '6', 'seven', '7', 'eight', '8', 'nine', '9', 'ten', '10',
|
||||||
|
'x','one or more']
|
||||||
|
|
||||||
|
|
||||||
|
# Constants for common tag groupings
|
||||||
|
TAG_GROUPS: Dict[str, List[str]] = {
|
||||||
|
"Cantrips": ["Cantrips", "Card Draw", "Spellslinger", "Spells Matter"],
|
||||||
|
"Tokens": ["Token Creation", "Tokens Matter"],
|
||||||
|
"Counters": ["Counters Matter"],
|
||||||
|
"Combat": ["Combat Matters", "Combat Tricks"],
|
||||||
|
"Artifacts": ["Artifacts Matter", "Artifact Tokens"],
|
||||||
|
"Enchantments": ["Enchantments Matter", "Enchantment Tokens"],
|
||||||
|
"Lands": ["Lands Matter"],
|
||||||
|
"Spells": ["Spellslinger", "Spells Matter"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Common regex patterns
|
||||||
|
PATTERN_GROUPS: Dict[str, Optional[str]] = {
|
||||||
|
"draw": r"draw[s]? a card|draw[s]? one card",
|
||||||
|
"combat": r"attack[s]?|block[s]?|combat damage",
|
||||||
|
"tokens": r"create[s]? .* token|put[s]? .* token",
|
||||||
|
"counters": r"\+1/\+1 counter|\-1/\-1 counter|loyalty counter",
|
||||||
|
"sacrifice": r"sacrifice[s]? .*|sacrificed",
|
||||||
|
"exile": r"exile[s]? .*|exiled",
|
||||||
|
"cost_reduction": r"cost[s]? \{[\d\w]\} less|affinity for|cost[s]? less to cast|chosen type cost|copy cost|from exile cost|from exile this turn cost|from your graveyard cost|has undaunted|have affinity for artifacts|other than your hand cost|spells cost|spells you cast cost|that target .* cost|those spells cost|you cast cost|you pay cost"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Creature/Counter types
|
||||||
|
COUNTER_TYPES: List[str] = [r'\+0/\+1', r'\+0/\+2', r'\+1/\+0', r'\+1/\+2', r'\+2/\+0', r'\+2/\+2',
|
||||||
|
'-0/-1', '-0/-2', '-1/-0', '-1/-2', '-2/-0', '-2/-2',
|
||||||
|
'Acorn', 'Aegis', 'Age', 'Aim', 'Arrow', 'Arrowhead','Awakening',
|
||||||
|
'Bait', 'Blaze', 'Blessing', 'Blight',' Blood', 'Bloddline',
|
||||||
|
'Bloodstain', 'Book', 'Bounty', 'Brain', 'Bribery', 'Brick',
|
||||||
|
'Burden', 'Cage', 'Carrion', 'Charge', 'Coin', 'Collection',
|
||||||
|
'Component', 'Contested', 'Corruption', 'CRANK!', 'Credit',
|
||||||
|
'Croak', 'Corpse', 'Crystal', 'Cube', 'Currency', 'Death',
|
||||||
|
'Defense', 'Delay', 'Depletion', 'Descent', 'Despair', 'Devotion',
|
||||||
|
'Divinity', 'Doom', 'Dream', 'Duty', 'Echo', 'Egg', 'Elixir',
|
||||||
|
'Ember', 'Energy', 'Enlightened', 'Eon', 'Eruption', 'Everything',
|
||||||
|
'Experience', 'Eyeball', 'Eyestalk', 'Fade', 'Fate', 'Feather',
|
||||||
|
'Feeding', 'Fellowship', 'Fetch', 'Filibuster', 'Finality', 'Flame',
|
||||||
|
'Flood', 'Foreshadow', 'Fungus', 'Fury', 'Fuse', 'Gem', 'Ghostform',
|
||||||
|
'Glpyh', 'Gold', 'Growth', 'Hack', 'Harmony', 'Hatching', 'Hatchling',
|
||||||
|
'Healing', 'Hit', 'Hope',' Hone', 'Hoofprint', 'Hour', 'Hourglass',
|
||||||
|
'Hunger', 'Ice', 'Imposter', 'Incarnation', 'Incubation', 'Infection',
|
||||||
|
'Influence', 'Ingenuity', 'Intel', 'Intervention', 'Invitation',
|
||||||
|
'Isolation', 'Javelin', 'Judgment', 'Keyword', 'Ki', 'Kick',
|
||||||
|
'Knickknack', 'Knowledge', 'Landmark', 'Level', 'Loot', 'Lore',
|
||||||
|
'Loyalty', 'Luck', 'Magnet', 'Manabond', 'Manifestation', 'Mannequin',
|
||||||
|
'Mask', 'Matrix', 'Memory', 'Midway', 'Mine', 'Mining', 'Mire',
|
||||||
|
'Music', 'Muster', 'Necrodermis', 'Nest', 'Net', 'Night', 'Oil',
|
||||||
|
'Omen', 'Ore', 'Page', 'Pain', 'Palliation', 'Paralyzing', 'Pause',
|
||||||
|
'Petal', 'Petrification', 'Phyresis', 'Phylatery', 'Pin', 'Plague',
|
||||||
|
'Plot', 'Point', 'Poison', 'Polyp', 'Possession', 'Pressure', 'Prey',
|
||||||
|
'Pupa', 'Quest', 'Rad', 'Rejection', 'Reprieve', 'Rev', 'Revival',
|
||||||
|
'Ribbon', 'Ritual', 'Rope', 'Rust', 'Scream', 'Scroll', 'Shell',
|
||||||
|
'Shield', 'Silver', 'Shred', 'Sleep', 'Sleight', 'Slime', 'Slumber',
|
||||||
|
'Soot', 'Soul', 'Spark', 'Spite', 'Spore', 'Stash', 'Storage',
|
||||||
|
'Story', 'Strife', 'Study', 'Stun', 'Supply', 'Suspect', 'Takeover',
|
||||||
|
'Task', 'Ticket', 'Tide', 'Time', 'Tower', 'Training', 'Trap',
|
||||||
|
'Treasure', 'Unity', 'Unlock', 'Valor', 'Velocity', 'Verse',
|
||||||
|
'Vitality', 'Void', 'Volatile', 'Vortex', 'Vow', 'Voyage', 'Wage',
|
||||||
|
'Winch', 'Wind', 'Wish']
|
||||||
|
|
||||||
|
CREATURE_TYPES: List[str] = ['Advisor', 'Aetherborn', 'Alien', 'Ally', 'Angel', 'Antelope', 'Ape', 'Archer', 'Archon', 'Armadillo',
|
||||||
|
'Army', 'Artificer', 'Assassin', 'Assembly-Worker', 'Astartes', 'Atog', 'Aurochs', 'Automaton',
|
||||||
|
'Avatar', 'Azra', 'Badger', 'Balloon', 'Barbarian', 'Bard', 'Basilisk', 'Bat', 'Bear', 'Beast', 'Beaver',
|
||||||
|
'Beeble', 'Beholder', 'Berserker', 'Bird', 'Blinkmoth', 'Boar', 'Brainiac', 'Bringer', 'Brushwagg',
|
||||||
|
'C\'tan', 'Camarid', 'Camel', 'Capybara', 'Caribou', 'Carrier', 'Cat', 'Centaur', 'Chicken', 'Child',
|
||||||
|
'Chimera', 'Citizen', 'Cleric', 'Clown', 'Cockatrice', 'Construct', 'Coward', 'Coyote', 'Crab', 'Crocodile',
|
||||||
|
'Custodes', 'Cyberman', 'Cyclops', 'Dalek', 'Dauthi', 'Demigod', 'Demon', 'Deserter', 'Detective', 'Devil',
|
||||||
|
'Dinosaur', 'Djinn', 'Doctor', 'Dog', 'Dragon', 'Drake', 'Dreadnought', 'Drone', 'Druid', 'Dryad', 'Dwarf',
|
||||||
|
'Efreet', 'Egg', 'Elder', 'Eldrazi', 'Elemental', 'Elephant', 'Elf', 'Elk', 'Employee', 'Eye', 'Faerie',
|
||||||
|
'Ferret', 'Fish', 'Flagbearer', 'Fox', 'Fractal', 'Frog', 'Fungus', 'Gamer', 'Gargoyle', 'Germ', 'Giant',
|
||||||
|
'Gith', 'Glimmer', 'Gnoll', 'Gnome', 'Goat', 'Goblin', 'God', 'Golem', 'Gorgon', 'Graveborn', 'Gremlin',
|
||||||
|
'Griffin', 'Guest', 'Hag', 'Halfling', 'Hamster', 'Harpy', 'Head', 'Hellion', 'Hero', 'Hippo', 'Hippogriff',
|
||||||
|
'Homarid', 'Homunculus', 'Hornet', 'Horror', 'Horse', 'Human', 'Hydra', 'Hyena', 'Illusion', 'Imp',
|
||||||
|
'Incarnation', 'Inkling', 'Inquisitor', 'Insect', 'Jackal', 'Jellyfish', 'Juggernaut', 'Kavu', 'Kirin',
|
||||||
|
'Kithkin', 'Knight', 'Kobold', 'Kor', 'Kraken', 'Lamia', 'Lammasu', 'Leech', 'Leviathan', 'Lhurgoyf',
|
||||||
|
'Licid', 'Lizard', 'Manticore', 'Masticore', 'Mercenary', 'Merfolk', 'Metathran', 'Minion', 'Minotaur',
|
||||||
|
'Mite', 'Mole', 'Monger', 'Mongoose', 'Monk', 'Monkey', 'Moonfolk', 'Mount', 'Mouse', 'Mutant', 'Myr',
|
||||||
|
'Mystic', 'Naga', 'Nautilus', 'Necron', 'Nephilim', 'Nightmare', 'Nightstalker', 'Ninja', 'Noble', 'Noggle',
|
||||||
|
'Nomad', 'Nymph', 'Octopus', 'Ogre', 'Ooze', 'Orb', 'Orc', 'Orgg', 'Otter', 'Ouphe', 'Ox', 'Oyster', 'Pangolin',
|
||||||
|
'Peasant', 'Pegasus', 'Pentavite', 'Performer', 'Pest', 'Phelddagrif', 'Phoenix', 'Phyrexian', 'Pilot',
|
||||||
|
'Pincher', 'Pirate', 'Plant', 'Porcupine', 'Possum', 'Praetor', 'Primarch', 'Prism', 'Processor', 'Rabbit',
|
||||||
|
'Raccoon', 'Ranger', 'Rat', 'Rebel', 'Reflection', 'Reveler', 'Rhino', 'Rigger', 'Robot', 'Rogue', 'Rukh',
|
||||||
|
'Sable', 'Salamander', 'Samurai', 'Sand', 'Saproling', 'Satyr', 'Scarecrow', 'Scientist', 'Scion', 'Scorpion',
|
||||||
|
'Scout', 'Sculpture', 'Serf', 'Serpent', 'Servo', 'Shade', 'Shaman', 'Shapeshifter', 'Shark', 'Sheep', 'Siren',
|
||||||
|
'Skeleton', 'Skunk', 'Slith', 'Sliver', 'Sloth', 'Slug', 'Snail', 'Snake', 'Soldier', 'Soltari', 'Spawn',
|
||||||
|
'Specter', 'Spellshaper', 'Sphinx', 'Spider', 'Spike', 'Spirit', 'Splinter', 'Sponge', 'Spy', 'Squid',
|
||||||
|
'Squirrel', 'Starfish', 'Surrakar', 'Survivor', 'Synth', 'Teddy', 'Tentacle', 'Tetravite', 'Thalakos',
|
||||||
|
'Thopter', 'Thrull', 'Tiefling', 'Time Lord', 'Toy', 'Treefolk', 'Trilobite', 'Triskelavite', 'Troll',
|
||||||
|
'Turtle', 'Tyranid', 'Unicorn', 'Urzan', 'Vampire', 'Varmint', 'Vedalken', 'Volver', 'Wall', 'Walrus',
|
||||||
|
'Warlock', 'Warrior', 'Wasp', 'Weasel', 'Weird', 'Werewolf', 'Whale', 'Wizard', 'Wolf', 'Wolverine', 'Wombat',
|
||||||
|
'Worm', 'Wraith', 'Wurm', 'Yeti', 'Zombie', 'Zubera']
|
||||||
|
|
||||||
|
NON_CREATURE_TYPES: List[str] = ['Legendary', 'Creature', 'Enchantment', 'Artifact',
|
||||||
|
'Battle', 'Sorcery', 'Instant', 'Land', '-', '—',
|
||||||
|
'Blood', 'Clue', 'Food', 'Gold', 'Incubator',
|
||||||
|
'Junk', 'Map', 'Powerstone', 'Treasure',
|
||||||
|
'Equipment', 'Fortification', 'vehicle',
|
||||||
|
'Bobblehead', 'Attraction', 'Contraption',
|
||||||
|
'Siege',
|
||||||
|
'Aura', 'Background', 'Saga', 'Role', 'Shard',
|
||||||
|
'Cartouche', 'Case', 'Class', 'Curse', 'Rune',
|
||||||
|
'Shrine',
|
||||||
|
'Plains', 'Island', 'Swamp', 'Forest', 'Mountain',
|
||||||
|
'Cave', 'Desert', 'Gate', 'Lair', 'Locus', 'Mine',
|
||||||
|
'Power-Plant', 'Sphere', 'Tower', 'Urza\'s']
|
||||||
|
|
||||||
|
OUTLAW_TYPES: List[str] = ['Assassin', 'Mercenary', 'Pirate', 'Rogue', 'Warlock']
|
||||||
|
|
||||||
|
ENCHANTMENT_TOKENS: List[str] = ['Cursed Role', 'Monster Role', 'Royal Role', 'Sorcerer Role',
|
||||||
|
'Virtuous Role', 'Wicked Role', 'Young Hero Role', 'Shard']
|
||||||
|
ARTIFACT_TOKENS: List[str] = ['Blood', 'Clue', 'Food', 'Gold', 'Incubator',
|
||||||
|
'Junk','Map','Powerstone', 'Treasure']
|
||||||
|
|
||||||
|
# Constants for DataFrame validation and processing
|
||||||
|
REQUIRED_COLUMNS: List[str] = [
|
||||||
|
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
|
||||||
|
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
|
||||||
|
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Mapping of card types to their corresponding theme tags
|
||||||
|
TYPE_TAG_MAPPING: List[str] = {
|
||||||
|
'Artifact': ['Artifacts Matter'],
|
||||||
|
'Battle': ['Battles Matter'],
|
||||||
|
#'Creature': [],
|
||||||
|
'Enchantment': ['Enchantments Matter'],
|
||||||
|
'Equipment': ['Equipment', 'Voltron'],
|
||||||
|
'Aura': ['Auras', 'Voltron'],
|
||||||
|
'Instant': ['Spells Matter', 'Spellslinger'],
|
||||||
|
'Land': ['Lands Matter'],
|
||||||
|
'Planeswalker': ['Superfriends'],
|
||||||
|
'Sorcery': ['Spells Matter', 'Spellslinger']
|
||||||
|
}
|
||||||
|
|
||||||
|
# Constants for draw-related functionality
|
||||||
|
DRAW_RELATED_TAGS: List[str] = [
|
||||||
|
'Card Draw', # General card draw effects
|
||||||
|
'Conditional Draw', # Draw effects with conditions/triggers
|
||||||
|
'Cycling', # Cycling and similar discard-to-draw effects
|
||||||
|
'Life to Draw', # Draw effects that require paying life
|
||||||
|
'Loot', # Draw + discard effects
|
||||||
|
'Replacement Draw', # Effects that modify or replace draws
|
||||||
|
'Sacrifice to Draw', # Draw effects requiring sacrificing permanents
|
||||||
|
'Unconditional Draw' # Pure card draw without conditions
|
||||||
|
]
|
||||||
|
|
||||||
|
# Text patterns that exclude cards from being tagged as unconditional draw
|
||||||
|
DRAW_EXCLUSION_PATTERNS: List[str] = [
|
||||||
|
'annihilator', # Eldrazi mechanic that can match 'draw' patterns
|
||||||
|
'ravenous', # Keyword that can match 'draw' patterns
|
||||||
|
]
|
||||||
|
|
||||||
|
# Equipment-related constants
|
||||||
|
EQUIPMENT_EXCLUSIONS: List[str] = [
|
||||||
|
'Bruenor Battlehammer', # Equipment cost reduction
|
||||||
|
'Nazahn, Revered Bladesmith', # Equipment tutor
|
||||||
|
'Stonehewer Giant', # Equipment tutor
|
||||||
|
]
|
||||||
|
|
||||||
|
EQUIPMENT_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Ardenn, Intrepid Archaeologist', # Equipment movement
|
||||||
|
'Armory Automaton', # Mass equip ability
|
||||||
|
'Brass Squire', # Free equip ability
|
||||||
|
'Danitha Capashen, Paragon', # Equipment cost reduction
|
||||||
|
'Halvar, God of Battle', # Equipment movement
|
||||||
|
'Kemba, Kha Regent', # Equipment payoff
|
||||||
|
'Kosei, Penitent Warlord', # Wants to be eequipped
|
||||||
|
'Puresteel Paladin', # Equipment draw engine
|
||||||
|
'Reyav, Master Smith', # Equipment combat boost
|
||||||
|
'Sram, Senior Edificer', # Equipment card draw
|
||||||
|
'Valduk, Keeper of the Flame' # Equipment token creation
|
||||||
|
]
|
||||||
|
|
||||||
|
EQUIPMENT_RELATED_TAGS: List[str] = [
|
||||||
|
'Equipment', # Base equipment tag
|
||||||
|
'Equipment Matters', # Cards that care about equipment
|
||||||
|
'Voltron', # Commander-focused equipment strategy
|
||||||
|
'Artifacts Matter', # Equipment are artifacts
|
||||||
|
'Warriors Matter', # Common equipment tribal synergy
|
||||||
|
'Knights Matter' # Common equipment tribal synergy
|
||||||
|
]
|
||||||
|
|
||||||
|
EQUIPMENT_TEXT_PATTERNS: List[str] = [
|
||||||
|
'attach', # Equipment attachment
|
||||||
|
'equip', # Equipment keyword
|
||||||
|
'equipped', # Equipment state
|
||||||
|
'equipment', # Equipment type
|
||||||
|
'unattach', # Equipment removal
|
||||||
|
'unequip', # Equipment removal
|
||||||
|
]
|
||||||
|
|
||||||
|
# Aura-related constants
|
||||||
|
AURA_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Ardenn, Intrepid Archaeologist', # Aura movement
|
||||||
|
'Calix, Guided By Fate', # Create duplicate Auras
|
||||||
|
'Gilwain, Casting Director', # Creates role tokens
|
||||||
|
'Ivy, Gleeful Spellthief', # Copies spells that have single target
|
||||||
|
'Killian, Ink Duelist', # Targetted spell cost reduction
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for Voltron strategy
|
||||||
|
VOLTRON_COMMANDER_CARDS: List[str] = [
|
||||||
|
'Akiri, Line-Slinger',
|
||||||
|
'Ardenn, Intrepid Archaeologist',
|
||||||
|
'Bruna, Light of Alabaster',
|
||||||
|
'Danitha Capashen, Paragon',
|
||||||
|
'Greven, Predator Captain',
|
||||||
|
'Halvar, God of Battle',
|
||||||
|
'Kaldra Compleat',
|
||||||
|
'Kemba, Kha Regent',
|
||||||
|
'Light-Paws, Emperor\'s Voice',
|
||||||
|
'Nahiri, the Lithomancer',
|
||||||
|
'Rafiq of the Many',
|
||||||
|
'Reyav, Master Smith',
|
||||||
|
'Rograkh, Son of Rohgahh',
|
||||||
|
'Sram, Senior Edificer',
|
||||||
|
'Syr Gwyn, Hero of Ashvale',
|
||||||
|
'Tiana, Ship\'s Caretaker',
|
||||||
|
'Uril, the Miststalker',
|
||||||
|
'Valduk, Keeper of the Flame',
|
||||||
|
'Wyleth, Soul of Steel'
|
||||||
|
]
|
||||||
|
|
||||||
|
VOLTRON_PATTERNS: List[str] = [
|
||||||
|
'attach',
|
||||||
|
'aura you control',
|
||||||
|
'enchant creature',
|
||||||
|
'enchanted creature',
|
||||||
|
'equipped creature',
|
||||||
|
'equipment you control',
|
||||||
|
'fortify',
|
||||||
|
'living weapon',
|
||||||
|
'reconfigure'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for lands matter functionality
|
||||||
|
LANDS_MATTER_PATTERNS: Dict[str, List[str]] = {
|
||||||
|
'land_play': [
|
||||||
|
'play a land',
|
||||||
|
'play an additional land',
|
||||||
|
'play two additional lands',
|
||||||
|
'play lands from',
|
||||||
|
'put a land card',
|
||||||
|
'put a basic land card'
|
||||||
|
],
|
||||||
|
'land_search': [
|
||||||
|
'search your library for a basic land card',
|
||||||
|
'search your library for a land card',
|
||||||
|
'search your library for up to two basic land',
|
||||||
|
'search their library for a basic land card'
|
||||||
|
],
|
||||||
|
'land_state': [
|
||||||
|
'land enters',
|
||||||
|
'land card is put into your graveyard',
|
||||||
|
'number of lands you control',
|
||||||
|
'one or more land cards',
|
||||||
|
'sacrifice a land',
|
||||||
|
'target land'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
DOMAIN_PATTERNS: List[str] = {
|
||||||
|
'keyword': ['domain'],
|
||||||
|
'text': ['basic land types among lands you control']
|
||||||
|
}
|
||||||
|
|
||||||
|
LANDFALL_PATTERNS: List[str] = {
|
||||||
|
'keyword': ['landfall'],
|
||||||
|
'triggers': [
|
||||||
|
'whenever a land enters the battlefield under your control',
|
||||||
|
'when a land enters the battlefield under your control'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
LANDWALK_PATTERNS: List[str] = {
|
||||||
|
'basic': [
|
||||||
|
'plainswalker',
|
||||||
|
'islandwalk',
|
||||||
|
'swampwalk',
|
||||||
|
'mountainwalk',
|
||||||
|
'forestwalk'
|
||||||
|
],
|
||||||
|
'nonbasic': [
|
||||||
|
'nonbasic landwalk',
|
||||||
|
'landwalk'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
LAND_TYPES: List[str] = [
|
||||||
|
# Basic lands
|
||||||
|
'Plains', 'Island', 'Swamp', 'Mountain', 'Forest',
|
||||||
|
# Special lands
|
||||||
|
'Cave', 'Desert', 'Gate', 'Lair', 'Locus', 'Mine',
|
||||||
|
'Power-Plant', 'Sphere', 'Tower', 'Urza\'s'
|
||||||
|
]
|
||||||
|
|
||||||
|
LANDS_MATTER_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Abundance',
|
||||||
|
'Archdruid\'s Charm',
|
||||||
|
'Archelos, Lagoon Mystic',
|
||||||
|
'Catacylsmic Prospecting',
|
||||||
|
'Coiling Oracle',
|
||||||
|
'Disorienting Choice',
|
||||||
|
'Eerie Ultimatum',
|
||||||
|
'Gitrog Monster',
|
||||||
|
'Mana Reflection',
|
||||||
|
'Nahiri\'s Lithoforming',
|
||||||
|
'Nine-fingers Keene',
|
||||||
|
'Open the Way',
|
||||||
|
'Realms Uncharted',
|
||||||
|
'Reshape the Earth',
|
||||||
|
'Scapeshift',
|
||||||
|
'Yarok, the Desecrated',
|
||||||
|
'Wonderscape Sage'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for aristocrats functionality
|
||||||
|
ARISTOCRAT_TEXT_PATTERNS: List[str] = [
|
||||||
|
'another creature dies',
|
||||||
|
'creature dies',
|
||||||
|
'creature dying',
|
||||||
|
'creature you control dies',
|
||||||
|
'creature you own dies',
|
||||||
|
'dies this turn',
|
||||||
|
'dies, create',
|
||||||
|
'dies, draw',
|
||||||
|
'dies, each opponent',
|
||||||
|
'dies, exile',
|
||||||
|
'dies, put',
|
||||||
|
'dies, return',
|
||||||
|
'dies, sacrifice',
|
||||||
|
'dies, you',
|
||||||
|
'has blitz',
|
||||||
|
'have blitz',
|
||||||
|
'permanents were sacrificed',
|
||||||
|
'sacrifice a creature',
|
||||||
|
'sacrifice another',
|
||||||
|
'sacrifice another creature',
|
||||||
|
'sacrifice a nontoken',
|
||||||
|
'sacrifice a permanent',
|
||||||
|
'sacrifice another nontoken',
|
||||||
|
'sacrifice another permanent',
|
||||||
|
'sacrifice another token',
|
||||||
|
'sacrifices a creature',
|
||||||
|
'sacrifices another',
|
||||||
|
'sacrifices another creature',
|
||||||
|
'sacrifices another nontoken',
|
||||||
|
'sacrifices another permanent',
|
||||||
|
'sacrifices another token',
|
||||||
|
'sacrifices a nontoken',
|
||||||
|
'sacrifices a permanent',
|
||||||
|
'sacrifices a token',
|
||||||
|
'when this creature dies',
|
||||||
|
'whenever a food',
|
||||||
|
'whenever you sacrifice'
|
||||||
|
]
|
||||||
|
|
||||||
|
ARISTOCRAT_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Ashnod, Flesh Mechanist',
|
||||||
|
'Blood Artist',
|
||||||
|
'Butcher of Malakir',
|
||||||
|
'Chatterfang, Squirrel General',
|
||||||
|
'Cruel Celebrant',
|
||||||
|
'Dictate of Erebos',
|
||||||
|
'Endrek Sahr, Master Breeder',
|
||||||
|
'Gisa, Glorious Resurrector',
|
||||||
|
'Grave Pact',
|
||||||
|
'Grim Haruspex',
|
||||||
|
'Judith, the Scourge Diva',
|
||||||
|
'Korvold, Fae-Cursed King',
|
||||||
|
'Mayhem Devil',
|
||||||
|
'Midnight Reaper',
|
||||||
|
'Mikaeus, the Unhallowed',
|
||||||
|
'Pitiless Plunderer',
|
||||||
|
'Poison-Tip Archer',
|
||||||
|
'Savra, Queen of the Golgari',
|
||||||
|
'Sheoldred, the Apocalypse',
|
||||||
|
'Syr Konrad, the Grim',
|
||||||
|
'Teysa Karlov',
|
||||||
|
'Viscera Seer',
|
||||||
|
'Yawgmoth, Thran Physician',
|
||||||
|
'Zulaport Cutthroat'
|
||||||
|
]
|
||||||
|
|
||||||
|
ARISTOCRAT_EXCLUSION_PATTERNS: List[str] = [
|
||||||
|
'blocking enchanted',
|
||||||
|
'blocking it',
|
||||||
|
'blocked by',
|
||||||
|
'end the turn',
|
||||||
|
'from your graveyard',
|
||||||
|
'from your hand',
|
||||||
|
'from your library',
|
||||||
|
'into your hand'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for stax functionality
|
||||||
|
STAX_TEXT_PATTERNS: List[str] = [
|
||||||
|
'an opponent controls'
|
||||||
|
'can\'t attack',
|
||||||
|
'can\'t be cast',
|
||||||
|
'can\'t be activated',
|
||||||
|
'can\'t cast spells',
|
||||||
|
'can\'t enter',
|
||||||
|
'can\'t search',
|
||||||
|
'can\'t untap',
|
||||||
|
'don\'t untap',
|
||||||
|
'don\'t cause abilities',
|
||||||
|
'each other player\'s',
|
||||||
|
'each player\'s upkeep',
|
||||||
|
'opponent would search',
|
||||||
|
'opponents cast cost',
|
||||||
|
'opponents can\'t',
|
||||||
|
'opponents control',
|
||||||
|
'opponents control can\'t',
|
||||||
|
'opponents control enter tapped',
|
||||||
|
'spells cost {1} more',
|
||||||
|
'spells cost {2} more',
|
||||||
|
'spells cost {3} more',
|
||||||
|
'spells cost {4} more',
|
||||||
|
'spells cost {5} more',
|
||||||
|
'that player doesn\'t',
|
||||||
|
'unless that player pays',
|
||||||
|
'you control your opponent',
|
||||||
|
'you gain protection'
|
||||||
|
]
|
||||||
|
|
||||||
|
STAX_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Archon of Emeria',
|
||||||
|
'Drannith Magistrate',
|
||||||
|
'Ethersworn Canonist',
|
||||||
|
'Grand Arbiter Augustin IV',
|
||||||
|
'Hokori, Dust Drinker',
|
||||||
|
'Kataki, War\'s Wage',
|
||||||
|
'Lavinia, Azorius Renegade',
|
||||||
|
'Leovold, Emissary of Trest',
|
||||||
|
'Magus of the Moon',
|
||||||
|
'Narset, Parter of Veils',
|
||||||
|
'Opposition Agent',
|
||||||
|
'Rule of Law',
|
||||||
|
'Sanctum Prelate',
|
||||||
|
'Thalia, Guardian of Thraben',
|
||||||
|
'Winter Orb'
|
||||||
|
]
|
||||||
|
|
||||||
|
STAX_EXCLUSION_PATTERNS: List[str] = [
|
||||||
|
'blocking enchanted',
|
||||||
|
'blocking it',
|
||||||
|
'blocked by',
|
||||||
|
'end the turn',
|
||||||
|
'from your graveyard',
|
||||||
|
'from your hand',
|
||||||
|
'from your library',
|
||||||
|
'into your hand'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for removal functionality
|
||||||
|
REMOVAL_TEXT_PATTERNS: List[str] = [
|
||||||
|
'destroy target',
|
||||||
|
'destroys target',
|
||||||
|
'exile target',
|
||||||
|
'exiles target',
|
||||||
|
'sacrifices target',
|
||||||
|
'return target.*to.*hand',
|
||||||
|
'returns target.*to.*hand'
|
||||||
|
]
|
||||||
|
|
||||||
|
REMOVAL_SPECIFIC_CARDS: List[str] = ['from.*graveyard.*hand']
|
||||||
|
|
||||||
|
REMOVAL_EXCLUSION_PATTERNS: List[str] = []
|
||||||
|
|
||||||
|
REMOVAL_KEYWORDS: List[str] = []
|
||||||
|
|
||||||
|
# Constants for counterspell functionality
|
||||||
|
COUNTERSPELL_TEXT_PATTERNS: List[str] = [
|
||||||
|
'control counters a',
|
||||||
|
'counter target',
|
||||||
|
'counter that spell',
|
||||||
|
'counter all',
|
||||||
|
'counter each',
|
||||||
|
'counter the next',
|
||||||
|
'counters a spell',
|
||||||
|
'counters target',
|
||||||
|
'return target spell',
|
||||||
|
'exile target spell',
|
||||||
|
'counter unless',
|
||||||
|
'unless its controller pays'
|
||||||
|
]
|
||||||
|
|
||||||
|
COUNTERSPELL_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Arcane Denial',
|
||||||
|
'Counterspell',
|
||||||
|
"Dovin's Veto",
|
||||||
|
'Force of Will',
|
||||||
|
'Mana Drain',
|
||||||
|
'Mental Misstep',
|
||||||
|
'Mindbreak Trap',
|
||||||
|
'Mystic Confluence',
|
||||||
|
'Pact of Negation',
|
||||||
|
'Swan Song'
|
||||||
|
]
|
||||||
|
|
||||||
|
COUNTERSPELL_EXCLUSION_PATTERNS: List[str] = [
|
||||||
|
'counter on',
|
||||||
|
'counter from',
|
||||||
|
'remove a counter',
|
||||||
|
'move a counter',
|
||||||
|
'distribute counter',
|
||||||
|
'proliferate'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for theft functionality
|
||||||
|
THEFT_TEXT_PATTERNS: List[str] = [
|
||||||
|
'cast a spell you don\'t own',
|
||||||
|
'cast but don\'t own',
|
||||||
|
'cost to cast this spell, sacrifice',
|
||||||
|
'control but don\'t own',
|
||||||
|
'exile top of target player\'s library',
|
||||||
|
'exile top of each player\'s library',
|
||||||
|
'gain control of',
|
||||||
|
'target opponent\'s library',
|
||||||
|
'that player\'s library',
|
||||||
|
'you control enchanted creature'
|
||||||
|
]
|
||||||
|
|
||||||
|
THEFT_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Adarkar Valkyrie',
|
||||||
|
'Captain N\'gathrod',
|
||||||
|
'Hostage Taker',
|
||||||
|
'Siphon Insight',
|
||||||
|
'Thief of Sanity',
|
||||||
|
'Xanathar, Guild Kingpin',
|
||||||
|
'Zara, Renegade Recruiter'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for big mana functionality
|
||||||
|
BIG_MANA_TEXT_PATTERNS: List[str] = [
|
||||||
|
'add {w}{u}{b}{r}{g}',
|
||||||
|
'card onto the battlefield',
|
||||||
|
'control with power [3-5] or greater',
|
||||||
|
'creature with power [3-5] or greater',
|
||||||
|
'double the power',
|
||||||
|
'from among them onto the battlefield',
|
||||||
|
'from among them without paying',
|
||||||
|
'hand onto the battlefield',
|
||||||
|
'mana, add one mana',
|
||||||
|
'mana, it produces twice',
|
||||||
|
'mana, it produces three',
|
||||||
|
'mana, its controller adds',
|
||||||
|
'pay {w}{u}{b}{r}{g}',
|
||||||
|
'spell with power 5 or greater',
|
||||||
|
'value [5-7] or greater',
|
||||||
|
'you may cast it without paying'
|
||||||
|
]
|
||||||
|
|
||||||
|
BIG_MANA_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Akroma\'s Memorial',
|
||||||
|
'Apex Devastator',
|
||||||
|
'Apex of Power',
|
||||||
|
'Brass\'s Bounty',
|
||||||
|
'Cabal Coffers',
|
||||||
|
'Caged Sun',
|
||||||
|
'Doubling Cube',
|
||||||
|
'Forsaken Monument',
|
||||||
|
'Guardian Project',
|
||||||
|
'Mana Reflection',
|
||||||
|
'Nyxbloom Ancient',
|
||||||
|
'Omniscience',
|
||||||
|
'One with the Multiverse',
|
||||||
|
'Portal to Phyrexia',
|
||||||
|
'Vorinclex, Voice of Hunger'
|
||||||
|
]
|
||||||
|
|
||||||
|
BIG_MANA_KEYWORDS: List[str] = [
|
||||||
|
'Cascade',
|
||||||
|
'Convoke',
|
||||||
|
'Discover',
|
||||||
|
'Emerge',
|
||||||
|
'Improvise',
|
||||||
|
'Surge'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for board wipe effects
|
||||||
|
BOARD_WIPE_TEXT_PATTERNS: Dict[str, List[str]] = {
|
||||||
|
'mass_destruction': [
|
||||||
|
'destroy all',
|
||||||
|
'destroy each',
|
||||||
|
'destroy the rest',
|
||||||
|
'destroys all',
|
||||||
|
'destroys each',
|
||||||
|
'destroys the rest'
|
||||||
|
],
|
||||||
|
'mass_exile': [
|
||||||
|
'exile all',
|
||||||
|
'exile each',
|
||||||
|
'exile the rest',
|
||||||
|
'exiles all',
|
||||||
|
'exiles each',
|
||||||
|
'exiles the rest'
|
||||||
|
],
|
||||||
|
'mass_bounce': [
|
||||||
|
'return all',
|
||||||
|
'return each',
|
||||||
|
'put all creatures',
|
||||||
|
'returns all',
|
||||||
|
'returns each',
|
||||||
|
'puts all creatures'
|
||||||
|
],
|
||||||
|
'mass_sacrifice': [
|
||||||
|
'sacrifice all',
|
||||||
|
'sacrifice each',
|
||||||
|
'sacrifice the rest',
|
||||||
|
'sacrifices all',
|
||||||
|
'sacrifices each',
|
||||||
|
'sacrifices the rest'
|
||||||
|
],
|
||||||
|
'mass_damage': [
|
||||||
|
'deals damage to each',
|
||||||
|
'deals damage to all',
|
||||||
|
'deals X damage to each',
|
||||||
|
'deals X damage to all',
|
||||||
|
'deals that much damage to each',
|
||||||
|
'deals that much damage to all'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
BOARD_WIPE_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Akroma\'s Vengeance',
|
||||||
|
'All Is Dust',
|
||||||
|
'Austere Command',
|
||||||
|
'Blasphemous Act',
|
||||||
|
'Cleansing Nova',
|
||||||
|
'Cyclonic Rift',
|
||||||
|
'Damnation',
|
||||||
|
'Day of Judgment',
|
||||||
|
'Decree of Pain',
|
||||||
|
'Devastation Tide',
|
||||||
|
'Evacuation',
|
||||||
|
'Extinction Event',
|
||||||
|
'Farewell',
|
||||||
|
'Hour of Devastation',
|
||||||
|
'In Garruk\'s Wake',
|
||||||
|
'Living Death',
|
||||||
|
'Living End',
|
||||||
|
'Merciless Eviction',
|
||||||
|
'Nevinyrral\'s Disk',
|
||||||
|
'Oblivion Stone',
|
||||||
|
'Planar Cleansing',
|
||||||
|
'Ravnica at War',
|
||||||
|
'Shatter the Sky',
|
||||||
|
'Supreme Verdict',
|
||||||
|
'Terminus',
|
||||||
|
'Time Wipe',
|
||||||
|
'Toxic Deluge',
|
||||||
|
'Vanquish the Horde',
|
||||||
|
'Wrath of God'
|
||||||
|
]
|
||||||
|
|
||||||
|
BOARD_WIPE_EXCLUSION_PATTERNS: List[str] = [
|
||||||
|
'blocking enchanted',
|
||||||
|
'blocking it',
|
||||||
|
'blocked by',
|
||||||
|
'end the turn',
|
||||||
|
'from your graveyard',
|
||||||
|
'from your hand',
|
||||||
|
'from your library',
|
||||||
|
'into your hand',
|
||||||
|
'target player\'s library',
|
||||||
|
'that player\'s library'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Constants for topdeck manipulation
|
||||||
|
TOPDECK_TEXT_PATTERNS: List[str] = [
|
||||||
|
'from the top',
|
||||||
|
'look at the top',
|
||||||
|
'reveal the top',
|
||||||
|
'scries',
|
||||||
|
'surveils',
|
||||||
|
'top of your library',
|
||||||
|
'you scry',
|
||||||
|
'you surveil'
|
||||||
|
]
|
||||||
|
|
||||||
|
TOPDECK_KEYWORDS: List[str] = [
|
||||||
|
'Miracle',
|
||||||
|
'Scry',
|
||||||
|
'Surveil'
|
||||||
|
]
|
||||||
|
|
||||||
|
TOPDECK_SPECIFIC_CARDS: List[str] = [
|
||||||
|
'Aminatou, the Fateshifter',
|
||||||
|
'Brainstorm',
|
||||||
|
'Counterbalance',
|
||||||
|
'Delver of Secrets',
|
||||||
|
'Jace, the Mind Sculptor',
|
||||||
|
'Lantern of Insight',
|
||||||
|
'Melek, Izzet Paragon',
|
||||||
|
'Mystic Forge',
|
||||||
|
'Sensei\'s Divining Top',
|
||||||
|
'Soothsaying',
|
||||||
|
'Temporal Mastery',
|
||||||
|
'Vampiric Tutor'
|
||||||
|
]
|
||||||
|
|
||||||
|
TOPDECK_EXCLUSION_PATTERNS: List[str] = [
|
||||||
|
'from the top of target player\'s library',
|
||||||
|
'from the top of their library',
|
||||||
|
'look at the top card of target player\'s library',
|
||||||
|
'reveal the top card of target player\'s library'
|
||||||
|
]
|
||||||
|
|
@ -22,7 +22,8 @@ from typing import List, Set, Union, Any
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
# Local application imports
|
# Local application imports
|
||||||
import settings
|
from . import tag_constants
|
||||||
|
|
||||||
def pluralize(word: str) -> str:
|
def pluralize(word: str) -> str:
|
||||||
"""Convert a word to its plural form using basic English pluralization rules.
|
"""Convert a word to its plural form using basic English pluralization rules.
|
||||||
|
|
||||||
|
|
@ -319,10 +320,10 @@ def create_mass_effect_mask(df: pd.DataFrame, effect_type: str) -> pd.Series[boo
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If effect_type is not recognized
|
ValueError: If effect_type is not recognized
|
||||||
"""
|
"""
|
||||||
if effect_type not in settings.BOARD_WIPE_TEXT_PATTERNS:
|
if effect_type not in tag_constants.BOARD_WIPE_TEXT_PATTERNS:
|
||||||
raise ValueError(f"Unknown effect type: {effect_type}")
|
raise ValueError(f"Unknown effect type: {effect_type}")
|
||||||
|
|
||||||
patterns = settings.BOARD_WIPE_TEXT_PATTERNS[effect_type]
|
patterns = tag_constants.BOARD_WIPE_TEXT_PATTERNS[effect_type]
|
||||||
return create_text_mask(df, patterns)
|
return create_text_mask(df, patterns)
|
||||||
|
|
||||||
def create_damage_pattern(number: Union[int, str]) -> str:
|
def create_damage_pattern(number: Union[int, str]) -> str:
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
@ -9,65 +8,18 @@ from typing import Union
|
||||||
# Third-party imports
|
# Third-party imports
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
import settings
|
|
||||||
import tag_utils
|
|
||||||
|
|
||||||
# Local application imports
|
# Local application imports
|
||||||
from settings import CSV_DIRECTORY, multiple_copy_cards, num_to_search, triggers
|
from . import tag_utils
|
||||||
from setup import regenerate_csv_by_color
|
from . import tag_constants
|
||||||
|
from settings import CSV_DIRECTORY, MULTIPLE_COPY_CARDS, COLORS
|
||||||
|
import logging_util
|
||||||
# Constants for common tag groupings
|
from file_setup import setup
|
||||||
TAG_GROUPS = {
|
|
||||||
"Cantrips": ["Cantrips", "Card Draw", "Spellslinger", "Spells Matter"],
|
|
||||||
"Tokens": ["Token Creation", "Tokens Matter"],
|
|
||||||
"Counters": ["Counters Matter"],
|
|
||||||
"Combat": ["Combat Matters", "Combat Tricks"],
|
|
||||||
"Artifacts": ["Artifacts Matter", "Artifact Tokens"],
|
|
||||||
"Enchantments": ["Enchantments Matter", "Enchantment Tokens"],
|
|
||||||
"Lands": ["Lands Matter"],
|
|
||||||
"Spells": ["Spellslinger", "Spells Matter"]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Common regex patterns
|
|
||||||
PATTERN_GROUPS = {
|
|
||||||
"draw": r"draw[s]? a card|draw[s]? one card",
|
|
||||||
"combat": r"attack[s]?|block[s]?|combat damage",
|
|
||||||
"tokens": r"create[s]? .* token|put[s]? .* token",
|
|
||||||
"counters": r"\+1/\+1 counter|\-1/\-1 counter|loyalty counter",
|
|
||||||
"sacrifice": r"sacrifice[s]? .*|sacrificed",
|
|
||||||
"exile": r"exile[s]? .*|exiled",
|
|
||||||
"cost_reduction": r"cost[s]? \{[\d\w]\} less|affinity for|cost[s]? less to cast|chosen type cost|copy cost|from exile cost|from exile this turn cost|from your graveyard cost|has undaunted|have affinity for artifacts|other than your hand cost|spells cost|spells you cast cost|that target .* cost|those spells cost|you cast cost|you pay cost"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create logs directory if it doesn't exist
|
|
||||||
if not os.path.exists('logs'):
|
|
||||||
os.makedirs('logs')
|
|
||||||
|
|
||||||
# Logging configuration
|
|
||||||
LOG_DIR = 'logs'
|
|
||||||
LOG_FILE = f'{LOG_DIR}/tagger.log'
|
|
||||||
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
|
|
||||||
# Create formatters and handlers
|
|
||||||
formatter = logging.Formatter(LOG_FORMAT)
|
|
||||||
|
|
||||||
# File handler
|
|
||||||
file_handler = logging.FileHandler(LOG_FILE, mode='w', encoding='utf-8')
|
|
||||||
file_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Stream handler
|
|
||||||
stream_handler = logging.StreamHandler()
|
|
||||||
stream_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Create logger for this module
|
# Create logger for this module
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging_util.logging.getLogger(__name__)
|
||||||
logger.setLevel(LOG_LEVEL)
|
logger.setLevel(logging_util.LOG_LEVEL)
|
||||||
|
logger.addHandler(logging_util.file_handler)
|
||||||
# Add handlers to logger
|
logger.addHandler(logging_util.stream_handler)
|
||||||
logger.addHandler(file_handler)
|
|
||||||
logger.addHandler(stream_handler)
|
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
## Load the dataframe
|
## Load the dataframe
|
||||||
|
|
@ -88,7 +40,7 @@ def load_dataframe(color: str) -> None:
|
||||||
# Check if file exists, regenerate if needed
|
# Check if file exists, regenerate if needed
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
logger.warning(f'{color}_cards.csv not found, regenerating it.')
|
logger.warning(f'{color}_cards.csv not found, regenerating it.')
|
||||||
regenerate_csv_by_color(color)
|
setup.regenerate_csv_by_color(color)
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
raise FileNotFoundError(f"Failed to generate {filepath}")
|
raise FileNotFoundError(f"Failed to generate {filepath}")
|
||||||
|
|
||||||
|
|
@ -213,8 +165,8 @@ def kindred_tagging(df: pd.DataFrame, color: str) -> None:
|
||||||
for idx, row in creature_rows.iterrows():
|
for idx, row in creature_rows.iterrows():
|
||||||
types = tag_utils.extract_creature_types(
|
types = tag_utils.extract_creature_types(
|
||||||
row['type'],
|
row['type'],
|
||||||
settings.creature_types,
|
tag_constants.CREATURE_TYPES,
|
||||||
settings.non_creature_types
|
tag_constants.NON_CREATURE_TYPES
|
||||||
)
|
)
|
||||||
if types:
|
if types:
|
||||||
df.at[idx, 'creatureTypes'] = types
|
df.at[idx, 'creatureTypes'] = types
|
||||||
|
|
@ -225,7 +177,7 @@ def kindred_tagging(df: pd.DataFrame, color: str) -> None:
|
||||||
|
|
||||||
logger.info(f'Setting Outlaw creature type tags on {color}_cards.csv')
|
logger.info(f'Setting Outlaw creature type tags on {color}_cards.csv')
|
||||||
# Process outlaw types
|
# Process outlaw types
|
||||||
outlaws = settings.OUTLAW_TYPES
|
outlaws = tag_constants.OUTLAW_TYPES
|
||||||
df['creatureTypes'] = df.apply(
|
df['creatureTypes'] = df.apply(
|
||||||
lambda row: tag_utils.add_outlaw_type(row['creatureTypes'], outlaws)
|
lambda row: tag_utils.add_outlaw_type(row['creatureTypes'], outlaws)
|
||||||
if isinstance(row['creatureTypes'], list) else row['creatureTypes'],
|
if isinstance(row['creatureTypes'], list) else row['creatureTypes'],
|
||||||
|
|
@ -249,7 +201,7 @@ def kindred_tagging(df: pd.DataFrame, color: str) -> None:
|
||||||
text_types = tag_utils.find_types_in_text(
|
text_types = tag_utils.find_types_in_text(
|
||||||
row['text'],
|
row['text'],
|
||||||
row['name'],
|
row['name'],
|
||||||
settings.creature_types
|
tag_constants.CREATURE_TYPES
|
||||||
)
|
)
|
||||||
if text_types:
|
if text_types:
|
||||||
current_types = row['creatureTypes']
|
current_types = row['creatureTypes']
|
||||||
|
|
@ -270,7 +222,7 @@ def kindred_tagging(df: pd.DataFrame, color: str) -> None:
|
||||||
'keywords', 'layout', 'side'
|
'keywords', 'layout', 'side'
|
||||||
]
|
]
|
||||||
df = df[columns_to_keep]
|
df = df[columns_to_keep]
|
||||||
df.to_csv(f'{settings.CSV_DIRECTORY}/{color}_cards.csv', index=False)
|
df.to_csv(f'{tag_constants.CSV_DIRECTORY}/{color}_cards.csv', index=False)
|
||||||
total_time = pd.Timestamp.now() - start_time
|
total_time = pd.Timestamp.now() - start_time
|
||||||
logger.info(f'Creature type tagging completed in {total_time.total_seconds():.2f}s')
|
logger.info(f'Creature type tagging completed in {total_time.total_seconds():.2f}s')
|
||||||
|
|
||||||
|
|
@ -308,7 +260,7 @@ def create_theme_tags(df: pd.DataFrame, color: str) -> None:
|
||||||
raise TypeError("df must be a pandas DataFrame")
|
raise TypeError("df must be a pandas DataFrame")
|
||||||
if not isinstance(color, str):
|
if not isinstance(color, str):
|
||||||
raise TypeError("color must be a string")
|
raise TypeError("color must be a string")
|
||||||
if color not in settings.COLORS:
|
if color not in COLORS:
|
||||||
raise ValueError(f"Invalid color: {color}")
|
raise ValueError(f"Invalid color: {color}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -327,7 +279,7 @@ def create_theme_tags(df: pd.DataFrame, color: str) -> None:
|
||||||
raise ValueError(f"Missing required columns: {missing}")
|
raise ValueError(f"Missing required columns: {missing}")
|
||||||
|
|
||||||
# Define column order
|
# Define column order
|
||||||
columns_to_keep = settings.REQUIRED_COLUMNS
|
columns_to_keep = tag_constants.REQUIRED_COLUMNS
|
||||||
|
|
||||||
# Reorder columns efficiently
|
# Reorder columns efficiently
|
||||||
available_cols = [col for col in columns_to_keep if col in df.columns]
|
available_cols = [col for col in columns_to_keep if col in df.columns]
|
||||||
|
|
@ -335,7 +287,7 @@ def create_theme_tags(df: pd.DataFrame, color: str) -> None:
|
||||||
|
|
||||||
# Save results
|
# Save results
|
||||||
try:
|
try:
|
||||||
df.to_csv(f'{settings.CSV_DIRECTORY}/{color}_cards.csv', index=False)
|
df.to_csv(f'{CSV_DIRECTORY}/{color}_cards.csv', index=False)
|
||||||
total_time = pd.Timestamp.now() - start_time
|
total_time = pd.Timestamp.now() - start_time
|
||||||
logger.info(f'Creature type tagging completed in {total_time.total_seconds():.2f}s')
|
logger.info(f'Creature type tagging completed in {total_time.total_seconds():.2f}s')
|
||||||
|
|
||||||
|
|
@ -375,7 +327,7 @@ def tag_for_card_types(df: pd.DataFrame, color: str) -> None:
|
||||||
raise ValueError(f"Missing required columns: {required_cols - set(df.columns)}")
|
raise ValueError(f"Missing required columns: {required_cols - set(df.columns)}")
|
||||||
|
|
||||||
# Define type-to-tag mapping
|
# Define type-to-tag mapping
|
||||||
type_tag_map = settings.TYPE_TAG_MAPPING
|
type_tag_map = tag_constants.TYPE_TAG_MAPPING
|
||||||
|
|
||||||
# Process each card type
|
# Process each card type
|
||||||
for card_type, tags in type_tag_map.items():
|
for card_type, tags in type_tag_map.items():
|
||||||
|
|
@ -518,7 +470,7 @@ def tag_for_cost_reduction(df: pd.DataFrame, color: str) -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create masks for different cost reduction patterns
|
# Create masks for different cost reduction patterns
|
||||||
cost_mask = tag_utils.create_text_mask(df, PATTERN_GROUPS['cost_reduction'])
|
cost_mask = tag_utils.create_text_mask(df, tag_constants.PATTERN_GROUPS['cost_reduction'])
|
||||||
|
|
||||||
# Add specific named cards
|
# Add specific named cards
|
||||||
named_cards = [
|
named_cards = [
|
||||||
|
|
@ -634,15 +586,15 @@ def create_unconditional_draw_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards have unconditional draw effects
|
Boolean Series indicating which cards have unconditional draw effects
|
||||||
"""
|
"""
|
||||||
# Create pattern for draw effects using num_to_search
|
# Create pattern for draw effects using num_to_search
|
||||||
draw_patterns = [f'draw {num} card' for num in num_to_search]
|
draw_patterns = [f'draw {num} card' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
draw_mask = tag_utils.create_text_mask(df, draw_patterns)
|
draw_mask = tag_utils.create_text_mask(df, draw_patterns)
|
||||||
|
|
||||||
# Create exclusion mask for conditional effects
|
# Create exclusion mask for conditional effects
|
||||||
excluded_tags = settings.DRAW_RELATED_TAGS
|
excluded_tags = tag_constants.DRAW_RELATED_TAGS
|
||||||
tag_mask = tag_utils.create_tag_mask(df, excluded_tags)
|
tag_mask = tag_utils.create_tag_mask(df, excluded_tags)
|
||||||
|
|
||||||
# Create text-based exclusions
|
# Create text-based exclusions
|
||||||
text_patterns = settings.DRAW_EXCLUSION_PATTERNS
|
text_patterns = tag_constants.DRAW_EXCLUSION_PATTERNS
|
||||||
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
||||||
|
|
||||||
return draw_mask & ~(tag_mask | text_mask)
|
return draw_mask & ~(tag_mask | text_mask)
|
||||||
|
|
@ -687,11 +639,11 @@ def create_conditional_draw_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
# Create tag-based exclusions
|
# Create tag-based exclusions
|
||||||
excluded_tags = settings.DRAW_RELATED_TAGS
|
excluded_tags = tag_constants.DRAW_RELATED_TAGS
|
||||||
tag_mask = tag_utils.create_tag_mask(df, excluded_tags)
|
tag_mask = tag_utils.create_tag_mask(df, excluded_tags)
|
||||||
|
|
||||||
# Create text-based exclusions
|
# Create text-based exclusions
|
||||||
text_patterns = settings.DRAW_EXCLUSION_PATTERNS + ['whenever you draw a card']
|
text_patterns = tag_constants.DRAW_EXCLUSION_PATTERNS + ['whenever you draw a card']
|
||||||
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
||||||
|
|
||||||
# Create name-based exclusions
|
# Create name-based exclusions
|
||||||
|
|
@ -711,7 +663,7 @@ def create_conditional_draw_trigger_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""
|
"""
|
||||||
# Build trigger patterns
|
# Build trigger patterns
|
||||||
trigger_patterns = []
|
trigger_patterns = []
|
||||||
for trigger in triggers:
|
for trigger in tag_constants.TRIGGERS:
|
||||||
# Permanent/creature/player triggers
|
# Permanent/creature/player triggers
|
||||||
trigger_patterns.extend([
|
trigger_patterns.extend([
|
||||||
f'{trigger} a permanent',
|
f'{trigger} a permanent',
|
||||||
|
|
@ -747,7 +699,7 @@ def create_conditional_draw_effect_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards have draw effects
|
Boolean Series indicating which cards have draw effects
|
||||||
"""
|
"""
|
||||||
# Create draw patterns using num_to_search
|
# Create draw patterns using num_to_search
|
||||||
draw_patterns = [f'draw {num} card' for num in num_to_search]
|
draw_patterns = [f'draw {num} card' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
|
|
||||||
# Add token and 'draw for each' patterns
|
# Add token and 'draw for each' patterns
|
||||||
draw_patterns.extend([
|
draw_patterns.extend([
|
||||||
|
|
@ -787,7 +739,7 @@ def tag_for_conditional_draw(df: pd.DataFrame, color: str) -> None:
|
||||||
trigger_mask = create_conditional_draw_trigger_mask(df)
|
trigger_mask = create_conditional_draw_trigger_mask(df)
|
||||||
|
|
||||||
# Create draw effect mask
|
# Create draw effect mask
|
||||||
draw_patterns = [f'draw {num} card' for num in num_to_search]
|
draw_patterns = [f'draw {num} card' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
|
|
||||||
# Add token and 'draw for each' patterns
|
# Add token and 'draw for each' patterns
|
||||||
draw_patterns.extend([
|
draw_patterns.extend([
|
||||||
|
|
@ -824,7 +776,7 @@ def create_loot_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
has_other_loot = tag_utils.create_tag_mask(df, ['Cycling', 'Connive']) | df['text'].str.contains('blood token', case=False, na=False)
|
has_other_loot = tag_utils.create_tag_mask(df, ['Cycling', 'Connive']) | df['text'].str.contains('blood token', case=False, na=False)
|
||||||
|
|
||||||
# Match draw + discard patterns
|
# Match draw + discard patterns
|
||||||
draw_patterns = [f'draw {num} card' for num in num_to_search]
|
draw_patterns = [f'draw {num} card' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
discard_patterns = [
|
discard_patterns = [
|
||||||
'discard the rest',
|
'discard the rest',
|
||||||
'for each card drawn this way, discard',
|
'for each card drawn this way, discard',
|
||||||
|
|
@ -959,7 +911,7 @@ def create_replacement_draw_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""
|
"""
|
||||||
# Create trigger patterns
|
# Create trigger patterns
|
||||||
trigger_patterns = []
|
trigger_patterns = []
|
||||||
for trigger in triggers:
|
for trigger in tag_constants.TRIGGERS:
|
||||||
trigger_patterns.extend([
|
trigger_patterns.extend([
|
||||||
f'{trigger} a player.*instead.*draw',
|
f'{trigger} a player.*instead.*draw',
|
||||||
f'{trigger} an opponent.*instead.*draw',
|
f'{trigger} an opponent.*instead.*draw',
|
||||||
|
|
@ -981,7 +933,7 @@ def create_replacement_draw_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
base_mask = tag_utils.create_text_mask(df, all_patterns)
|
base_mask = tag_utils.create_text_mask(df, all_patterns)
|
||||||
|
|
||||||
# Add mask for specific card numbers
|
# Add mask for specific card numbers
|
||||||
number_patterns = [f'draw {num} card' for num in num_to_search]
|
number_patterns = [f'draw {num} card' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
number_mask = tag_utils.create_text_mask(df, number_patterns)
|
number_mask = tag_utils.create_text_mask(df, number_patterns)
|
||||||
|
|
||||||
# Add mask for non-specific numbers
|
# Add mask for non-specific numbers
|
||||||
|
|
@ -999,11 +951,11 @@ def create_replacement_draw_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
# Create tag-based exclusions
|
# Create tag-based exclusions
|
||||||
excluded_tags = settings.DRAW_RELATED_TAGS
|
excluded_tags = tag_constants.DRAW_RELATED_TAGS
|
||||||
tag_mask = tag_utils.create_tag_mask(df, excluded_tags)
|
tag_mask = tag_utils.create_tag_mask(df, excluded_tags)
|
||||||
|
|
||||||
# Create text-based exclusions
|
# Create text-based exclusions
|
||||||
text_patterns = settings.DRAW_EXCLUSION_PATTERNS + ['skips that turn instead']
|
text_patterns = tag_constants.DRAW_EXCLUSION_PATTERNS + ['skips that turn instead']
|
||||||
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
||||||
|
|
||||||
return tag_mask | text_mask
|
return tag_mask | text_mask
|
||||||
|
|
@ -1114,7 +1066,7 @@ def tag_for_wheels(df: pd.DataFrame, color: str) -> None:
|
||||||
tag_utils.apply_tag_vectorized(df, final_mask, ['Card Draw', 'Wheels'])
|
tag_utils.apply_tag_vectorized(df, final_mask, ['Card Draw', 'Wheels'])
|
||||||
|
|
||||||
# Add Draw Triggers tag for cards with trigger words
|
# Add Draw Triggers tag for cards with trigger words
|
||||||
trigger_pattern = '|'.join(triggers)
|
trigger_pattern = '|'.join(tag_constants.TRIGGERS)
|
||||||
trigger_mask = final_mask & df['text'].str.contains(trigger_pattern, case=False, na=False)
|
trigger_mask = final_mask & df['text'].str.contains(trigger_pattern, case=False, na=False)
|
||||||
tag_utils.apply_tag_vectorized(df, trigger_mask, ['Draw Triggers'])
|
tag_utils.apply_tag_vectorized(df, trigger_mask, ['Draw Triggers'])
|
||||||
|
|
||||||
|
|
@ -1161,11 +1113,15 @@ def tag_for_artifacts(df: pd.DataFrame, color: str) -> None:
|
||||||
required_cols = {'text', 'themeTags'}
|
required_cols = {'text', 'themeTags'}
|
||||||
tag_utils.validate_dataframe_columns(df, required_cols)
|
tag_utils.validate_dataframe_columns(df, required_cols)
|
||||||
|
|
||||||
# Process each type of draw effect
|
# Process each type of artifact effect
|
||||||
tag_for_artifact_tokens(df, color)
|
tag_for_artifact_tokens(df, color)
|
||||||
logger.info('Completed Artifact token tagging')
|
logger.info('Completed Artifact token tagging')
|
||||||
print('\n==========\n')
|
print('\n==========\n')
|
||||||
|
|
||||||
|
tag_for_artifact_triggers(df, color)
|
||||||
|
logger.info('Completed Artifact trigger tagging')
|
||||||
|
print('\n==========\n')
|
||||||
|
|
||||||
tag_equipment(df, color)
|
tag_equipment(df, color)
|
||||||
logger.info('Completed Equipment tagging')
|
logger.info('Completed Equipment tagging')
|
||||||
print('\n==========\n')
|
print('\n==========\n')
|
||||||
|
|
@ -1314,7 +1270,7 @@ def create_predefined_artifact_mask(df: pd.DataFrame) -> tuple[pd.Series, dict[i
|
||||||
# Create masks for each token type
|
# Create masks for each token type
|
||||||
token_masks = []
|
token_masks = []
|
||||||
|
|
||||||
for token in settings.artifact_tokens:
|
for token in tag_constants.ARTIFACT_TOKENS:
|
||||||
token_mask = tag_utils.create_text_mask(df, token.lower())
|
token_mask = tag_utils.create_text_mask(df, token.lower())
|
||||||
|
|
||||||
# Handle exclusions
|
# Handle exclusions
|
||||||
|
|
@ -1493,7 +1449,7 @@ def create_equipment_cares_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
keyword_mask = tag_utils.create_keyword_mask(df, keyword_patterns)
|
keyword_mask = tag_utils.create_keyword_mask(df, keyword_patterns)
|
||||||
|
|
||||||
# Create specific cards mask
|
# Create specific cards mask
|
||||||
specific_cards = settings.EQUIPMENT_SPECIFIC_CARDS
|
specific_cards = tag_constants.EQUIPMENT_SPECIFIC_CARDS
|
||||||
name_mask = tag_utils.create_name_mask(df, specific_cards)
|
name_mask = tag_utils.create_name_mask(df, specific_cards)
|
||||||
|
|
||||||
return text_mask | keyword_mask | name_mask
|
return text_mask | keyword_mask | name_mask
|
||||||
|
|
@ -1767,7 +1723,7 @@ def create_predefined_enchantment_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
|
|
||||||
# Create masks for each token type
|
# Create masks for each token type
|
||||||
token_masks = []
|
token_masks = []
|
||||||
for token in settings.enchantment_tokens:
|
for token in tag_constants.ENCHANTMENT_TOKENS:
|
||||||
token_mask = tag_utils.create_text_mask(df, token.lower())
|
token_mask = tag_utils.create_text_mask(df, token.lower())
|
||||||
|
|
||||||
token_masks.append(token_mask)
|
token_masks.append(token_mask)
|
||||||
|
|
@ -1887,7 +1843,7 @@ def tag_auras(df: pd.DataFrame, color: str) -> None:
|
||||||
'aura you control enters',
|
'aura you control enters',
|
||||||
'enchanted'
|
'enchanted'
|
||||||
]
|
]
|
||||||
cares_mask = tag_utils.create_text_mask(df, text_patterns) | tag_utils.create_name_mask(df, settings.AURA_SPECIFIC_CARDS)
|
cares_mask = tag_utils.create_text_mask(df, text_patterns) | tag_utils.create_name_mask(df, tag_constants.AURA_SPECIFIC_CARDS)
|
||||||
if cares_mask.any():
|
if cares_mask.any():
|
||||||
tag_utils.apply_tag_vectorized(df, cares_mask,
|
tag_utils.apply_tag_vectorized(df, cares_mask,
|
||||||
['Auras', 'Enchantments Matter', 'Voltron'])
|
['Auras', 'Enchantments Matter', 'Voltron'])
|
||||||
|
|
@ -2793,8 +2749,8 @@ def tag_for_lifegain(df: pd.DataFrame, color: str) -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create masks for different lifegain patterns
|
# Create masks for different lifegain patterns
|
||||||
gain_patterns = [f'gain {num} life' for num in settings.num_to_search]
|
gain_patterns = [f'gain {num} life' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
gain_patterns.extend([f'gains {num} life' for num in settings.num_to_search])
|
gain_patterns.extend([f'gains {num} life' for num in tag_constants.NUM_TO_SEARCH])
|
||||||
gain_patterns.extend(['gain life', 'gains life'])
|
gain_patterns.extend(['gain life', 'gains life'])
|
||||||
|
|
||||||
gain_mask = tag_utils.create_text_mask(df, gain_patterns)
|
gain_mask = tag_utils.create_text_mask(df, gain_patterns)
|
||||||
|
|
@ -3144,7 +3100,7 @@ def tag_for_special_counters(df: pd.DataFrame, color: str) -> None:
|
||||||
try:
|
try:
|
||||||
# Process each counter type
|
# Process each counter type
|
||||||
counter_counts = {}
|
counter_counts = {}
|
||||||
for counter_type in settings.counter_types:
|
for counter_type in tag_constants.COUNTER_TYPES:
|
||||||
# Create pattern for this counter type
|
# Create pattern for this counter type
|
||||||
pattern = f'{counter_type} counter'
|
pattern = f'{counter_type} counter'
|
||||||
mask = tag_utils.create_text_mask(df, pattern)
|
mask = tag_utils.create_text_mask(df, pattern)
|
||||||
|
|
@ -3177,7 +3133,7 @@ def create_voltron_commander_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards are Voltron commanders
|
Boolean Series indicating which cards are Voltron commanders
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_name_mask(df, settings.VOLTRON_COMMANDER_CARDS)
|
return tag_utils.create_name_mask(df, tag_constants.VOLTRON_COMMANDER_CARDS)
|
||||||
|
|
||||||
def create_voltron_support_mask(df: pd.DataFrame) -> pd.Series:
|
def create_voltron_support_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards that support Voltron strategies.
|
"""Create a boolean mask for cards that support Voltron strategies.
|
||||||
|
|
@ -3188,7 +3144,7 @@ def create_voltron_support_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards support Voltron strategies
|
Boolean Series indicating which cards support Voltron strategies
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.VOLTRON_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.VOLTRON_PATTERNS)
|
||||||
|
|
||||||
def create_voltron_equipment_mask(df: pd.DataFrame) -> pd.Series:
|
def create_voltron_equipment_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for Equipment-based Voltron cards.
|
"""Create a boolean mask for Equipment-based Voltron cards.
|
||||||
|
|
@ -3283,12 +3239,12 @@ def create_lands_matter_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards have lands matter effects
|
Boolean Series indicating which cards have lands matter effects
|
||||||
"""
|
"""
|
||||||
# Create mask for named cards
|
# Create mask for named cards
|
||||||
name_mask = tag_utils.create_name_mask(df, settings.LANDS_MATTER_SPECIFIC_CARDS)
|
name_mask = tag_utils.create_name_mask(df, tag_constants.LANDS_MATTER_SPECIFIC_CARDS)
|
||||||
|
|
||||||
# Create text pattern masks
|
# Create text pattern masks
|
||||||
play_mask = tag_utils.create_text_mask(df, settings.LANDS_MATTER_PATTERNS['land_play'])
|
play_mask = tag_utils.create_text_mask(df, tag_constants.LANDS_MATTER_PATTERNS['land_play'])
|
||||||
search_mask = tag_utils.create_text_mask(df, settings.LANDS_MATTER_PATTERNS['land_search'])
|
search_mask = tag_utils.create_text_mask(df, tag_constants.LANDS_MATTER_PATTERNS['land_search'])
|
||||||
state_mask = tag_utils.create_text_mask(df, settings.LANDS_MATTER_PATTERNS['land_state'])
|
state_mask = tag_utils.create_text_mask(df, tag_constants.LANDS_MATTER_PATTERNS['land_state'])
|
||||||
|
|
||||||
# Combine all masks
|
# Combine all masks
|
||||||
return name_mask | play_mask | search_mask | state_mask
|
return name_mask | play_mask | search_mask | state_mask
|
||||||
|
|
@ -3302,8 +3258,8 @@ def create_domain_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have domain effects
|
Boolean Series indicating which cards have domain effects
|
||||||
"""
|
"""
|
||||||
keyword_mask = tag_utils.create_keyword_mask(df, settings.DOMAIN_PATTERNS['keyword'])
|
keyword_mask = tag_utils.create_keyword_mask(df, tag_constants.DOMAIN_PATTERNS['keyword'])
|
||||||
text_mask = tag_utils.create_text_mask(df, settings.DOMAIN_PATTERNS['text'])
|
text_mask = tag_utils.create_text_mask(df, tag_constants.DOMAIN_PATTERNS['text'])
|
||||||
return keyword_mask | text_mask
|
return keyword_mask | text_mask
|
||||||
|
|
||||||
def create_landfall_mask(df: pd.DataFrame) -> pd.Series:
|
def create_landfall_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
|
|
@ -3315,8 +3271,8 @@ def create_landfall_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have landfall effects
|
Boolean Series indicating which cards have landfall effects
|
||||||
"""
|
"""
|
||||||
keyword_mask = tag_utils.create_keyword_mask(df, settings.LANDFALL_PATTERNS['keyword'])
|
keyword_mask = tag_utils.create_keyword_mask(df, tag_constants.LANDFALL_PATTERNS['keyword'])
|
||||||
trigger_mask = tag_utils.create_text_mask(df, settings.LANDFALL_PATTERNS['triggers'])
|
trigger_mask = tag_utils.create_text_mask(df, tag_constants.LANDFALL_PATTERNS['triggers'])
|
||||||
return keyword_mask | trigger_mask
|
return keyword_mask | trigger_mask
|
||||||
|
|
||||||
def create_landwalk_mask(df: pd.DataFrame) -> pd.Series:
|
def create_landwalk_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
|
|
@ -3328,8 +3284,8 @@ def create_landwalk_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have landwalk abilities
|
Boolean Series indicating which cards have landwalk abilities
|
||||||
"""
|
"""
|
||||||
basic_mask = tag_utils.create_text_mask(df, settings.LANDWALK_PATTERNS['basic'])
|
basic_mask = tag_utils.create_text_mask(df, tag_constants.LANDWALK_PATTERNS['basic'])
|
||||||
nonbasic_mask = tag_utils.create_text_mask(df, settings.LANDWALK_PATTERNS['nonbasic'])
|
nonbasic_mask = tag_utils.create_text_mask(df, tag_constants.LANDWALK_PATTERNS['nonbasic'])
|
||||||
return basic_mask | nonbasic_mask
|
return basic_mask | nonbasic_mask
|
||||||
|
|
||||||
def create_land_types_mask(df: pd.DataFrame) -> pd.Series:
|
def create_land_types_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
|
|
@ -3342,11 +3298,11 @@ def create_land_types_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards care about specific land types
|
Boolean Series indicating which cards care about specific land types
|
||||||
"""
|
"""
|
||||||
# Create type-based mask
|
# Create type-based mask
|
||||||
type_mask = tag_utils.create_type_mask(df, settings.LAND_TYPES)
|
type_mask = tag_utils.create_type_mask(df, tag_constants.LAND_TYPES)
|
||||||
|
|
||||||
# Create text pattern masks for each land type
|
# Create text pattern masks for each land type
|
||||||
text_masks = []
|
text_masks = []
|
||||||
for land_type in settings.LAND_TYPES:
|
for land_type in tag_constants.LAND_TYPES:
|
||||||
patterns = [
|
patterns = [
|
||||||
f'search your library for a {land_type.lower()}',
|
f'search your library for a {land_type.lower()}',
|
||||||
f'search your library for up to two {land_type.lower()}',
|
f'search your library for up to two {land_type.lower()}',
|
||||||
|
|
@ -3654,7 +3610,7 @@ def tag_for_cantrips(df: pd.DataFrame, color: str) -> None:
|
||||||
excluded_names = df['name'].isin(EXCLUDED_NAMES)
|
excluded_names = df['name'].isin(EXCLUDED_NAMES)
|
||||||
|
|
||||||
# Create cantrip condition masks
|
# Create cantrip condition masks
|
||||||
has_draw = tag_utils.create_text_mask(df, PATTERN_GROUPS['draw'])
|
has_draw = tag_utils.create_text_mask(df, tag_constants.PATTERN_GROUPS['draw'])
|
||||||
low_cost = df['manaValue'].fillna(float('inf')) <= 2
|
low_cost = df['manaValue'].fillna(float('inf')) <= 2
|
||||||
|
|
||||||
# Combine conditions
|
# Combine conditions
|
||||||
|
|
@ -3668,7 +3624,7 @@ def tag_for_cantrips(df: pd.DataFrame, color: str) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply tags
|
# Apply tags
|
||||||
tag_utils.apply_tag_vectorized(df, cantrip_mask, TAG_GROUPS['Cantrips'])
|
tag_utils.apply_tag_vectorized(df, cantrip_mask, tag_constants.TAG_GROUPS['Cantrips'])
|
||||||
|
|
||||||
# Log results
|
# Log results
|
||||||
cantrip_count = cantrip_mask.sum()
|
cantrip_count = cantrip_mask.sum()
|
||||||
|
|
@ -4169,7 +4125,7 @@ def create_aristocrat_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have aristocrat text patterns
|
Boolean Series indicating which cards have aristocrat text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.ARISTOCRAT_TEXT_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.ARISTOCRAT_TEXT_PATTERNS)
|
||||||
|
|
||||||
def create_aristocrat_name_mask(df: pd.DataFrame) -> pd.Series:
|
def create_aristocrat_name_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for specific aristocrat-related cards.
|
"""Create a boolean mask for specific aristocrat-related cards.
|
||||||
|
|
@ -4180,7 +4136,7 @@ def create_aristocrat_name_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards are specific aristocrat cards
|
Boolean Series indicating which cards are specific aristocrat cards
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_name_mask(df, settings.ARISTOCRAT_SPECIFIC_CARDS)
|
return tag_utils.create_name_mask(df, tag_constants.ARISTOCRAT_SPECIFIC_CARDS)
|
||||||
|
|
||||||
def create_aristocrat_self_sacrifice_mask(df: pd.DataFrame) -> pd.Series:
|
def create_aristocrat_self_sacrifice_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for creatures with self-sacrifice effects.
|
"""Create a boolean mask for creatures with self-sacrifice effects.
|
||||||
|
|
@ -4225,7 +4181,7 @@ def create_aristocrat_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.ARISTOCRAT_EXCLUSION_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.ARISTOCRAT_EXCLUSION_PATTERNS)
|
||||||
|
|
||||||
def tag_for_aristocrats(df: pd.DataFrame, color: str) -> None:
|
def tag_for_aristocrats(df: pd.DataFrame, color: str) -> None:
|
||||||
"""Tag cards that fit the Aristocrats or Sacrifice Matters themes using vectorized operations.
|
"""Tag cards that fit the Aristocrats or Sacrifice Matters themes using vectorized operations.
|
||||||
|
|
@ -4332,10 +4288,10 @@ def tag_for_big_mana(df: pd.DataFrame, color: str) -> None:
|
||||||
tag_utils.validate_dataframe_columns(df, required_cols)
|
tag_utils.validate_dataframe_columns(df, required_cols)
|
||||||
|
|
||||||
# Create masks for different big mana patterns
|
# Create masks for different big mana patterns
|
||||||
text_mask = tag_utils.create_text_mask(df, settings.BIG_MANA_TEXT_PATTERNS)
|
text_mask = tag_utils.create_text_mask(df, tag_constants.BIG_MANA_TEXT_PATTERNS)
|
||||||
keyword_mask = tag_utils.create_keyword_mask(df, settings.BIG_MANA_KEYWORDS)
|
keyword_mask = tag_utils.create_keyword_mask(df, tag_constants.BIG_MANA_KEYWORDS)
|
||||||
cost_mask = create_big_mana_cost_mask(df)
|
cost_mask = create_big_mana_cost_mask(df)
|
||||||
specific_mask = tag_utils.create_name_mask(df, settings.BIG_MANA_SPECIFIC_CARDS)
|
specific_mask = tag_utils.create_name_mask(df, tag_constants.BIG_MANA_SPECIFIC_CARDS)
|
||||||
tag_mask = tag_utils.create_tag_mask(df, 'Cost Reduction')
|
tag_mask = tag_utils.create_tag_mask(df, 'Cost Reduction')
|
||||||
|
|
||||||
# Combine all masks
|
# Combine all masks
|
||||||
|
|
@ -5106,8 +5062,8 @@ def create_mill_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
text_mask = tag_utils.create_text_mask(df, text_patterns)
|
||||||
|
|
||||||
# Create mill number patterns
|
# Create mill number patterns
|
||||||
mill_patterns = [f'mill {num}' for num in settings.num_to_search]
|
mill_patterns = [f'mill {num}' for num in tag_constants.NUM_TO_SEARCH]
|
||||||
mill_patterns.extend([f'mills {num}' for num in settings.num_to_search])
|
mill_patterns.extend([f'mills {num}' for num in tag_constants.NUM_TO_SEARCH])
|
||||||
number_mask = tag_utils.create_text_mask(df, mill_patterns)
|
number_mask = tag_utils.create_text_mask(df, mill_patterns)
|
||||||
|
|
||||||
return text_mask | number_mask
|
return text_mask | number_mask
|
||||||
|
|
@ -5261,7 +5217,7 @@ def tag_for_multiple_copies(df: pd.DataFrame, color: str) -> None:
|
||||||
tag_utils.validate_dataframe_columns(df, required_cols)
|
tag_utils.validate_dataframe_columns(df, required_cols)
|
||||||
|
|
||||||
# Create mask for multiple copy cards
|
# Create mask for multiple copy cards
|
||||||
multiple_copies_mask = tag_utils.create_name_mask(df, multiple_copy_cards)
|
multiple_copies_mask = tag_utils.create_name_mask(df, MULTIPLE_COPY_CARDS)
|
||||||
|
|
||||||
# Apply tags
|
# Apply tags
|
||||||
if multiple_copies_mask.any():
|
if multiple_copies_mask.any():
|
||||||
|
|
@ -5487,7 +5443,7 @@ def create_stax_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have stax text patterns
|
Boolean Series indicating which cards have stax text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.STAX_TEXT_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.STAX_TEXT_PATTERNS)
|
||||||
|
|
||||||
def create_stax_name_mask(df: pd.DataFrame) -> pd.Series:
|
def create_stax_name_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards used in stax strategies.
|
"""Create a boolean mask for cards used in stax strategies.
|
||||||
|
|
@ -5498,7 +5454,7 @@ def create_stax_name_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have stax text patterns
|
Boolean Series indicating which cards have stax text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.STAX_SPECIFIC_CARDS)
|
return tag_utils.create_text_mask(df, tag_constants.STAX_SPECIFIC_CARDS)
|
||||||
|
|
||||||
def create_stax_tag_mask(df: pd.DataFrame) -> pd.Series:
|
def create_stax_tag_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards with stax-related tags.
|
"""Create a boolean mask for cards with stax-related tags.
|
||||||
|
|
@ -5521,7 +5477,7 @@ def create_stax_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
# Add specific exclusion patterns here if needed
|
# Add specific exclusion patterns here if needed
|
||||||
return tag_utils.create_text_mask(df, settings.STAX_EXCLUSION_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.STAX_EXCLUSION_PATTERNS)
|
||||||
|
|
||||||
def tag_for_stax(df: pd.DataFrame, color: str) -> None:
|
def tag_for_stax(df: pd.DataFrame, color: str) -> None:
|
||||||
"""Tag cards that fit the Stax theme using vectorized operations.
|
"""Tag cards that fit the Stax theme using vectorized operations.
|
||||||
|
|
@ -5577,7 +5533,7 @@ def create_theft_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have theft text patterns
|
Boolean Series indicating which cards have theft text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.THEFT_TEXT_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.THEFT_TEXT_PATTERNS)
|
||||||
|
|
||||||
def create_theft_name_mask(df: pd.DataFrame) -> pd.Series:
|
def create_theft_name_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for specific theft-related cards.
|
"""Create a boolean mask for specific theft-related cards.
|
||||||
|
|
@ -5588,7 +5544,7 @@ def create_theft_name_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards are specific theft cards
|
Boolean Series indicating which cards are specific theft cards
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_name_mask(df, settings.THEFT_SPECIFIC_CARDS)
|
return tag_utils.create_name_mask(df, tag_constants.THEFT_SPECIFIC_CARDS)
|
||||||
|
|
||||||
def tag_for_theft(df: pd.DataFrame, color: str) -> None:
|
def tag_for_theft(df: pd.DataFrame, color: str) -> None:
|
||||||
"""Tag cards that steal or use opponents' resources using vectorized operations.
|
"""Tag cards that steal or use opponents' resources using vectorized operations.
|
||||||
|
|
@ -5751,7 +5707,7 @@ def create_topdeck_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have topdeck text patterns
|
Boolean Series indicating which cards have topdeck text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.TOPDECK_TEXT_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.TOPDECK_TEXT_PATTERNS)
|
||||||
|
|
||||||
def create_topdeck_keyword_mask(df: pd.DataFrame) -> pd.Series:
|
def create_topdeck_keyword_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards with topdeck-related keywords.
|
"""Create a boolean mask for cards with topdeck-related keywords.
|
||||||
|
|
@ -5762,7 +5718,7 @@ def create_topdeck_keyword_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have topdeck keywords
|
Boolean Series indicating which cards have topdeck keywords
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_keyword_mask(df, settings.TOPDECK_KEYWORDS)
|
return tag_utils.create_keyword_mask(df, tag_constants.TOPDECK_KEYWORDS)
|
||||||
|
|
||||||
def create_topdeck_specific_mask(df: pd.DataFrame) -> pd.Series:
|
def create_topdeck_specific_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for specific topdeck-related cards.
|
"""Create a boolean mask for specific topdeck-related cards.
|
||||||
|
|
@ -5773,7 +5729,7 @@ def create_topdeck_specific_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards are specific topdeck cards
|
Boolean Series indicating which cards are specific topdeck cards
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_name_mask(df, settings.TOPDECK_SPECIFIC_CARDS)
|
return tag_utils.create_name_mask(df, tag_constants.TOPDECK_SPECIFIC_CARDS)
|
||||||
|
|
||||||
def create_topdeck_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
def create_topdeck_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards that should be excluded from topdeck effects.
|
"""Create a boolean mask for cards that should be excluded from topdeck effects.
|
||||||
|
|
@ -5784,7 +5740,7 @@ def create_topdeck_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.TOPDECK_EXCLUSION_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.TOPDECK_EXCLUSION_PATTERNS)
|
||||||
|
|
||||||
def tag_for_topdeck(df: pd.DataFrame, color: str) -> None:
|
def tag_for_topdeck(df: pd.DataFrame, color: str) -> None:
|
||||||
"""Tag cards that manipulate the top of library using vectorized operations.
|
"""Tag cards that manipulate the top of library using vectorized operations.
|
||||||
|
|
@ -5990,7 +5946,7 @@ def create_counterspell_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have counterspell text patterns
|
Boolean Series indicating which cards have counterspell text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.COUNTERSPELL_TEXT_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.COUNTERSPELL_TEXT_PATTERNS)
|
||||||
|
|
||||||
def create_counterspell_specific_mask(df: pd.DataFrame) -> pd.Series:
|
def create_counterspell_specific_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for specific counterspell cards.
|
"""Create a boolean mask for specific counterspell cards.
|
||||||
|
|
@ -6001,7 +5957,7 @@ def create_counterspell_specific_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards are specific counterspell cards
|
Boolean Series indicating which cards are specific counterspell cards
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_name_mask(df, settings.COUNTERSPELL_SPECIFIC_CARDS)
|
return tag_utils.create_name_mask(df, tag_constants.COUNTERSPELL_SPECIFIC_CARDS)
|
||||||
|
|
||||||
def create_counterspell_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
def create_counterspell_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards that should be excluded from counterspell effects.
|
"""Create a boolean mask for cards that should be excluded from counterspell effects.
|
||||||
|
|
@ -6012,7 +5968,7 @@ def create_counterspell_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.COUNTERSPELL_EXCLUSION_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.COUNTERSPELL_EXCLUSION_PATTERNS)
|
||||||
|
|
||||||
def tag_for_counterspells(df: pd.DataFrame, color: str) -> None:
|
def tag_for_counterspells(df: pd.DataFrame, color: str) -> None:
|
||||||
"""Tag cards that counter spells using vectorized operations.
|
"""Tag cards that counter spells using vectorized operations.
|
||||||
|
|
@ -6101,10 +6057,10 @@ def tag_for_board_wipes(df: pd.DataFrame, color: str) -> None:
|
||||||
damage_mask = tag_utils.create_mass_damage_mask(df)
|
damage_mask = tag_utils.create_mass_damage_mask(df)
|
||||||
|
|
||||||
# Create exclusion mask
|
# Create exclusion mask
|
||||||
exclusion_mask = tag_utils.create_text_mask(df, settings.BOARD_WIPE_EXCLUSION_PATTERNS)
|
exclusion_mask = tag_utils.create_text_mask(df, tag_constants.BOARD_WIPE_EXCLUSION_PATTERNS)
|
||||||
|
|
||||||
# Create specific cards mask
|
# Create specific cards mask
|
||||||
specific_mask = tag_utils.create_name_mask(df, settings.BOARD_WIPE_SPECIFIC_CARDS)
|
specific_mask = tag_utils.create_name_mask(df, tag_constants.BOARD_WIPE_SPECIFIC_CARDS)
|
||||||
|
|
||||||
# Combine all masks
|
# Combine all masks
|
||||||
final_mask = (
|
final_mask = (
|
||||||
|
|
@ -6407,7 +6363,7 @@ def create_removal_text_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards have removal text patterns
|
Boolean Series indicating which cards have removal text patterns
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.REMOVAL_TEXT_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.REMOVAL_TEXT_PATTERNS)
|
||||||
|
|
||||||
def create_removal_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
def create_removal_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
"""Create a boolean mask for cards that should be excluded from removal effects.
|
"""Create a boolean mask for cards that should be excluded from removal effects.
|
||||||
|
|
@ -6418,7 +6374,7 @@ def create_removal_exclusion_mask(df: pd.DataFrame) -> pd.Series:
|
||||||
Returns:
|
Returns:
|
||||||
Boolean Series indicating which cards should be excluded
|
Boolean Series indicating which cards should be excluded
|
||||||
"""
|
"""
|
||||||
return tag_utils.create_text_mask(df, settings.REMOVAL_EXCLUSION_PATTERNS)
|
return tag_utils.create_text_mask(df, tag_constants.REMOVAL_EXCLUSION_PATTERNS)
|
||||||
|
|
||||||
|
|
||||||
def tag_for_removal(df: pd.DataFrame, color: str) -> None:
|
def tag_for_removal(df: pd.DataFrame, color: str) -> None:
|
||||||
|
|
@ -6474,7 +6430,7 @@ def tag_for_removal(df: pd.DataFrame, color: str) -> None:
|
||||||
|
|
||||||
def run_tagging():
|
def run_tagging():
|
||||||
start_time = pd.Timestamp.now()
|
start_time = pd.Timestamp.now()
|
||||||
for color in settings.COLORS:
|
for color in COLORS:
|
||||||
load_dataframe(color)
|
load_dataframe(color)
|
||||||
duration = (pd.Timestamp.now() - start_time).total_seconds()
|
duration = (pd.Timestamp.now() - start_time).total_seconds()
|
||||||
logger.info(f'Tagged cards in {duration:.2f}s')
|
logger.info(f'Tagged cards in {duration:.2f}s')
|
||||||
1376
settings.py
1376
settings.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue