import os import re import requests import csv import time # Optional: For adding delays between API requests # Initialize variables unique_cards = set() card_types = {} card_tags = {} deck_data = [] # Tags of interest tags_of_interest = [ 'ramp', 'draw', 'tutor', 'counterspell', 'removal', 'stax', 'protection', 'boardwipe' ] # Function to process a decklist file and separate it into deck, sideboard, and commander sections def process_decklist_file(filepath): with open(filepath, 'r', encoding='utf-8') as file: lines = file.readlines() # Keep all lines including empty ones, strip newline characters lines = [line.rstrip('\n') for line in lines] deck_cards = [] sideboard_cards = [] commander_cards = [] section = 'deck' i = 0 while i < len(lines): line = lines[i].strip() if section == 'deck': if line == '': # Empty line indicates potential section change # Check if next non-empty line is 'SIDEBOARD:' j = i + 1 while j < len(lines) and lines[j].strip() == '': j += 1 if j < len(lines) and lines[j].strip().upper() == 'SIDEBOARD:': section = 'sideboard' i = j # Move to 'SIDEBOARD:' line else: # Otherwise, assume commanders are after the empty line section = 'commander' i += 1 # Skip the empty line continue elif line.upper() == 'SIDEBOARD:': section = 'sideboard' else: deck_cards.append(line) elif section == 'sideboard': if line == '': # Empty line after sideboard indicates commanders section = 'commander' i += 1 # Move to next line after the empty line continue elif line.upper() == 'SIDEBOARD:': pass # Already in sideboard section else: sideboard_cards.append(line) elif section == 'commander': if line != '': commander_cards.append(line) i += 1 return deck_cards, sideboard_cards, commander_cards # Function to fetch cards for a given tag from Scryfall def fetch_cards_for_tag(tag): card_names = set() page = 1 has_more = True while has_more: query = f"f:edh otag:{tag}" url = "https://api.scryfall.com/cards/search" params = {'q': query, 'page': page} response = requests.get(url, params=params) if response.status_code == 200: data = response.json() for card in data['data']: card_names.add(card['name']) has_more = data.get('has_more', False) page += 1 time.sleep(0.1) # Sleep to respect rate limits else: print(f"Error fetching cards for tag: {tag}") break return card_names # Step 4: Collect Unique Card Names decklist_dir = os.path.dirname(os.path.abspath(__file__)) for filename in os.listdir(decklist_dir): # Process only decklist files matching the pattern if filename.endswith('.txt') and re.match(r'\d+-\d+-.*\.txt', filename): filepath = os.path.join(decklist_dir, filename) deck_cards, sideboard_cards, commander_cards = process_decklist_file(filepath) # Collect unique cards from the deck and commanders, skip sideboard for line in deck_cards + commander_cards: match = re.match(r'(\d+)\s+(.*)', line) if match: quantity = int(match.group(1)) card_name = match.group(2) unique_cards.add(card_name) # Step 5: Retrieve Card Types Using Scryfall API scryfall_api_url = 'https://api.scryfall.com/cards/named?exact=' for card_name in unique_cards: encoded_name = requests.utils.quote(card_name) url = scryfall_api_url + encoded_name response = requests.get(url) if response.status_code == 200: data = response.json() types = data['type_line'] main_types = types.split('—')[0].strip() type_list = [t.strip() for t in main_types.split()] card_types[card_name] = type_list else: print(f"Error fetching data for card: {card_name}") card_types[card_name] = [] # Optional: Add a delay to respect API rate limits time.sleep(0.1) # Sleep for 100 milliseconds # Step 5b: Build Card Tags Mapping # Initialize card_tags dictionary card_tags = {card_name: [] for card_name in unique_cards} # Fetch cards for each tag and build the mapping for tag in tags_of_interest: print(f"Fetching cards for tag: {tag}") tagged_cards = fetch_cards_for_tag(tag) for card_name in tagged_cards: if card_name in card_tags: card_tags[card_name].append(tag.capitalize()) else: # Handle cases where card name variations exist for unique_card in unique_cards: if card_name.lower() == unique_card.lower(): card_tags[unique_card].append(tag.capitalize()) break # Step 6: Process Each Decklist to Collect Deck Data for filename in os.listdir(decklist_dir): if filename.endswith('.txt') and re.match(r'\d+-\d+-.*\.txt', filename): match = re.match(r'(\d+)-(\d+)-.*\.txt', filename) if match: rank = int(match.group(1)) tournament_size = int(match.group(2)) else: rank = None tournament_size = None filepath = os.path.join(decklist_dir, filename) deck_cards, sideboard_cards, commander_cards = process_decklist_file(filepath) deck = {} commander = None partner = None num_artifacts = num_creatures = num_enchantments = 0 num_instants = num_sorceries = num_planeswalkers = 0 num_lands = 0 # Initialize tag card sets to store unique cards per tag tag_card_sets = {tag.capitalize(): set() for tag in tags_of_interest} # Process main deck cards for line in deck_cards: match = re.match(r'(\d+)\s+(.*)', line) if match: quantity = int(match.group(1)) card_name = match.group(2) deck[card_name] = quantity types = card_types.get(card_name, []) if 'Artifact' in types: num_artifacts += quantity if 'Creature' in types: num_creatures += quantity if 'Enchantment' in types: num_enchantments += quantity if 'Instant' in types: num_instants += quantity if 'Sorcery' in types: num_sorceries += quantity if 'Planeswalker' in types: num_planeswalkers += quantity if 'Land' in types: num_lands += quantity # Process tags tags = card_tags.get(card_name, []) for tag in tags: if tag in tag_card_sets: tag_card_sets[tag].add(card_name) # Process commander cards for idx, line in enumerate(commander_cards): match = re.match(r'(\d+)\s+(.*)', line) if match: card_name = match.group(2) if idx == 0: commander = card_name elif idx == 1: partner = card_name # Compute tag counts as the number of unique cards per tag tag_counts = {tag: len(cards) for tag, cards in tag_card_sets.items()} deck_row = { 'rank': rank, 'tournament_size': tournament_size, 'commander': commander, 'partner': partner, 'num_artifacts': num_artifacts, 'num_creatures': num_creatures, 'num_enchantments': num_enchantments, 'num_instants': num_instants, 'num_sorceries': num_sorceries, 'num_planeswalkers': num_planeswalkers, 'num_lands': num_lands, 'tag_counts': tag_counts, 'deck': deck } deck_data.append(deck_row) # Step 7: Prepare the CSV Header header = ['Rank', 'TournamentSize', 'Commander', 'Partner', 'Num_Artifacts', 'Num_Creatures', 'Num_Enchantments', 'Num_Instants', 'Num_Sorceries', 'Num_Planeswalkers', 'Num_Lands'] # Add tag columns header.extend(['Num_' + tag.capitalize() for tag in tags_of_interest]) sorted_cards = sorted(unique_cards) header.extend(sorted_cards) # Step 8: Write Data to CSV File with open('deck_data.csv', 'w', newline='', encoding='utf-8') as csvfile: writer = csv.writer(csvfile) writer.writerow(header) for deck in deck_data: row = [ deck['rank'], deck['tournament_size'], deck['commander'], deck['partner'], deck['num_artifacts'], deck['num_creatures'], deck['num_enchantments'], deck['num_instants'], deck['num_sorceries'], deck['num_planeswalkers'], deck['num_lands'] ] # Add tag counts for tag in tags_of_interest: row.append(deck['tag_counts'][tag.capitalize()]) # Add card presence (1 or 0) for card_name in sorted_cards: row.append(1 if card_name in deck['deck'] else 0) # If you prefer to include quantities, use: # row.append(deck['deck'].get(card_name, 0)) writer.writerow(row)