• 18 Posts
  • 26 Comments
Joined 2 years ago
cake
Cake day: October 19th, 2023

help-circle


  • counterspellOPtoMTGMTG Deck Legality Checker for custom formats!
    link
    fedilink
    English
    arrow-up
    1
    ·
    edit-2
    8 days ago

    This tool isn’t really needed given that there are multiple websites, where you can upload your collection and your deck, and they will tell you exactly which cards in the deck are missing from your collection.

    For example:

    • Archidekt — collection tracker + deck builder
    • Moxfield — modern deck builder with collection support
    • Deckstats — deck-building + collection management
    • Deckbox — collection manager that shows missing cards for a deck
    • ManaBox — app for collection tracking + deck building
    • MTGGoldfish — has features to input your collection and compare against decks
    • Untapped.gg — for MTG Arena, tells you which decks you can build given your collection

    So while a custom format legality checker is neat, if your goal is simply “which cards do I lack in this deck given my collection”, one of those sites will do just fine.


    • Commander with low power brackets, no tutors, no infinite combos, and mostly budget decks.

    • Cube Draft.

    • Playing precons against the computer using Forge. I copy the names of the precons from a block, then play them against each other using a bit of randomization:

    import itertools, random
    
    decks = ["Deck A", "Deck B", "Deck C", ...]
    matchups = list(itertools.combinations(decks, 2))
    random.shuffle(matchups)
    
    for i, (deck1, deck2) in enumerate(matchups, 1):
        print(f"Match {i}: {deck1} vs {deck2}")
    
    • Aside from the usual Theme Decks, some of the most interesting precons are Duel Decks, Pro Tour Collector Sets, Salvat 2005 and 2011, and Commander decks. You can find a full list of precon decks here.
    • Penny Dreadful on MTGO when I want constructed but cheap play.
    • I’ve played a bunch of Draft and Standard on MTG Arena but I didn’t really enjoy it that much. It felt like a chore doing the dailies and just caring about getting the four wins per day.





  • counterspellOPtoMTGMTG Deck Legality Checker for custom formats!
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    20 days ago

    MTG Deck Legality Web Checker

    A simple web tool for validating Magic: The Gathering decklists in a custom format.


    Inspiration

    This project was inspired by several sources:

    • Badaro’s Validator GitHub, a simple web tool for checking card lists in “nostalgia” Magic the Gathering formats: https://badaro.github.io/validator
    • Penny Dreadful, an unofficial, budget-friendly MTG Constructed format where only cards costing 0.02 tix or less are legal. Decks are 60 cards with a 15-card sideboard, promoting creative play with inexpensive cards.
    • Heirloom Constructed, a fan-created format combining Legacy-style interactions with price caps. Cards are legal based on current prices, with different caps for mythics, rares, uncommons, and commons. The format rotates weekly after new Standard set releases, providing a dynamic but affordable competitive environment.

    Features

    • Automatic setup: Downloads Oracle bulk-data from Scryfall and builds legality files on first run.
    • Custom format validation: Checks decks for banned or out-of-format cards.
    • Browser interface: Paste a decklist, click Validate, and view results instantly.

    Installation

    It is recommended to use a virtual environment to keep dependencies isolated.

    1. Clone the Repository

    git clone https://git.disroot.org/hirrolot19/mtg-legality-checker.git
    cd mtg-legality-checker
    

    2. Create and Activate a Virtual Environment

    python -m venv venv
    source venv/bin/activate
    

    3. Install Dependencies

    pip install -r requirements.txt
    

    Running the App

    From the project root (with the virtual environment activated):

    python app.py
    

    Then open your browser and navigate to:

    http://127.0.0.1:5000/
    

    First Run Behavior

    On first launch, the app will:

    1. Download Scryfall’s Oracle card data.
    2. Filter legal cards for the custom format based on a Scryfall query. Default query is f:standard usd<=1 tix<=0.1
    3. Convert the filtered data into a validation JSON file.

    This process may take a few minutes.
    Once complete, cached files are stored persistently for future sessions.


    Using the Web Checker

    1. Paste your decklist into the text box.
    2. Click Validate.
    3. The app displays any cards that are banned or not legal in the format.

    Decklist Rules

    • One card per line.
    • Quantities accepted (4 Lightning Bolt, 2x Opt).
    • Comments start with #.
    • “Deck” or “Sideboard” headers ignored.

    Advanced Usage

    For detailed information about the supporting scripts and command-line tools, see tools/README.md.




  • I’ve managed to write another script that seems to work:

    import json
    import re
    
    def load_legal_cards(json_file):
        """
        Load legal cards from a JSON file with structure:
        { "sets": [], "cards": [], "banned": [] }
        """
        with open(json_file, 'r', encoding='utf-8') as f:
            data = json.load(f)
        legal_cards = [card.lower() for card in data.get('cards', [])]
        banned_cards = [card.lower() for card in data.get('banned', [])] if 'banned' in data else []
        return legal_cards, banned_cards
    
    def clean_line(line):
        """
        Remove quantities, set info, markers, and whitespace
        Skip lines that are section headers like 'Deck', 'Sideboard'
        """
        line = re.sub(r'^\d+\s*x?\s*', '', line)  # "2 " or "2x "
        line = re.sub(r'\(.*?\)', '', line)        # "(SET)"
        line = re.sub(r'\*\w+\*', '', line)        # "*F*"
        line = line.strip()
        if re.match(r'^(deck|sideboard)\s*:?\s*$', line, re.IGNORECASE):
            return None
        return line if line else None
    
    def validate_deck(deck_file, legal_cards, banned_cards):
        """
        Returns a list of illegal cards
        """
        illegal_cards = []
        with open(deck_file, 'r', encoding='utf-8') as f:
            lines = f.readlines()
    
        for line in lines:
            card_name = clean_line(line)
            if not card_name or card_name.startswith("#"):
                continue  # skip empty or comment lines
    
            card_lower = card_name.lower()
            if card_lower in banned_cards or card_lower not in legal_cards:
                illegal_cards.append(card_name)
    
        return illegal_cards
    
    def main():
        legal_cards_file = 'legal_cards.json'   # JSON with "cards" and optional "banned"
        decklist_file = 'decklist.txt'          # Your decklist input
    
        legal_cards, banned_cards = load_legal_cards(legal_cards_file)
        illegal_cards = validate_deck(decklist_file, legal_cards, banned_cards)
    
        if illegal_cards:
            print("Illegal cards:")
            for card in illegal_cards:
                print(card)
    
    if __name__ == "__main__":
        main()
    

  • I exported the Standard Penny collection from Moxfield to JSON using a Python script:

    import csv
    import json
    
    input_csv = 'moxfield_haves_2025-10-21-1123Z.csv'
    output_json = 'standard_penny.json'
    
    sets = set()
    cards = []
    
    with open(input_csv, newline='', encoding='utf-8') as csvfile:
        reader = csv.DictReader(csvfile)
        for row in reader:
            name = row.get('Name')
            edition = row.get('Edition')
            if name:
                cards.append(name)
            if edition:
                sets.add(edition.upper())
    
    sets = sorted(list(sets))
    
    output_data = {
        "sets": sets,
        "cards": cards
    }
    
    with open(output_json, 'w', encoding='utf-8') as jsonfile:
        json.dump(output_data, jsonfile, indent=2)
    
    print(f"JSON saved to {output_json}")
    

    I saved the JSON file as validator/formats/standardpenny.json and added it to the validator’s config:

    { "name": "Standard Penny", "key": "standardpenny", "datafile":"formats/standardpenny.json" },
    

    Then I tried to validate this deck exported as Plain Text from Moxfield and got the error.






  • I’m not aware of a single tool, but you could ensure the deck is standard legal in any normal deck building tool, then additionally check it against the Penny Dreadful deck checker - if it passes both, it should be legal in your format (assuming I understand what you’re doing correctly.)

    Edit: Nevermind, I see you’re limiting it to $1, not $0.01, despite borrowing the name. Penny Dreadful checker won’t work.

    Yeah Penny Dreadful uses tix<=0.02 and this uses both tix<=0.1 and usd<=1



  • ✅ This will create a fully Moxfield-compatible CSV with all cards from a Scryfall search.

    import requests
    import csv
    import time
    
    QUERY = "f:standard f:penny usd<=1"
    BASE_URL = "https://api.scryfall.com/cards/search"
    PARAMS = {
        "q": QUERY,
        "unique": "cards",
        "format": "json"
    }
    
    OUTPUT_FILE = "moxfield_import.csv"
    
    FIELDNAMES = [
        "Count",
        "Tradelist Count",
        "Name",
        "Edition",
        "Condition",
        "Language",
        "Foil",
        "Tags",
        "Last Modified",
        "Collector Number",
        "Alter",
        "Proxy",
        "Purchase Price"
    ]
    
    def fetch_all_cards():
        url = BASE_URL
        params = PARAMS.copy()
        while True:
            resp = requests.get(url, params=params)
            resp.raise_for_status()
            data = resp.json()
            for card in data.get("data", []):
                yield card
            if not data.get("has_more"):
                break
            url = data["next_page"]
            params = None
            time.sleep(0.2)
    
    def write_cards_to_csv(filename):
        with open(filename, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
            writer.writeheader()
            for card in fetch_all_cards():
                row = {
                    "Count": 1,
                    "Tradelist Count": "",
                    "Name": card.get("name"),
                    "Edition": card.get("set"),
                    "Condition": "",
                    "Language": card.get("lang"),
                    "Foil": "Yes" if card.get("foil") else "No",
                    "Tags": "",
                    "Last Modified": "",
                    "Collector Number": card.get("collector_number"),
                    "Alter": "",
                    "Proxy": "",
                    "Purchase Price": ""
                }
                writer.writerow(row)
    
    if __name__ == "__main__":
        write_cards_to_csv(OUTPUT_FILE)
        print(f"Saved all cards to {OUTPUT_FILE}")
    


  • Is there a deckbuilder that allows using just that list to build decks? How would I import it?

    #!/bin/bash
    
    url="https://api.scryfall.com/cards/search?q=f%3Astandard+f%3Apenny+usd<=1"
    data=()
    
    while [ -n "$url" ]; do
        response=$(curl -s "$url")
        data_chunk=$(echo "$response" | jq -c '.data[]')
        while read -r card; do
            data+=("$card")
        done <<< "$data_chunk"
    
        has_more=$(echo "$response" | jq -r '.has_more')
        if [ "$has_more" = "true" ]; then
            url=$(echo "$response" | jq -r '.next_page')
        else
            url=""
        fi
    done
    
    for card_json in "${data[@]}"; do
        echo "$card_json" | jq -r '.name'
    done
    








  • You can either go here https://mtg.wtf/deck Or the same data is also exported to mtgjson if you want it in JSON format https://mtgjson.com/ The same data is also available in a few other export formats.

    Source data for it is in https://github.com/taw/magic-preconstructed-decks with source URLs for every deck (some of these expired by now and you’d need to go to the Web Archive - WotC redesigns its website every few years, killing old URLs).

    Inferring exact set and collector number based on all available information is done algorithmically.

    Everything should have correct names, quantities, and set codes.

    A few cards won’t have correct collector numbers. The list of cards which are generally expected to not have exact collector number: “Plains”, “Island”, “Swamp”, “Mountain”, “Forest”, “Wastes”, “Azorius Guildgate”, “Boros Guildgate”, “Dimir Guildgate”, “Golgari Guildgate”, “Gruul Guildgate”, “Izzet Guildgate”, “Orzhov Guildgate”, “Rakdos Guildgate”, “Selesnya Guildgate”, “Simic Guildgate”

    For everything else, the algorithm is exact as far as we know. Anything the algorithm can’t detect automatically it flags, and we resolve it manually.

    Tomasz Wegrzanowski

    I noticed that the default deck download format on the website doesn’t include set code and collector number information.

    If you’re fine with JSON, you can use mtgjson, or this file: https://raw.githubusercontent.com/taw/magic-preconstructed-decks-data/master/decks_v2.json (which is exported to mtgjson).

    In case it matters, collector numbers are Gatherer-style not Scryfall-style (so DFCs are 123a / 123b, not 123 etc.). This only really affects cards with multiple parts.

    Do you have any more questions?

    Tomasz Wegrzanowski




  • https://github.com/taw/mtg

    mtg

    Magic the Gathering scripts.

    scripts

    • analyze_deck_colors - reports colors of the deck according to correct algorithm [ http://t-a-w.blogspot.com/2013/03/simple-and-correct-algorithm-for.html ]
    • clean_up_decklist - clean up manually created decklist
    • cod2dck - convert Cockatrice’s .cod to XMage’s .dck
    • cod2txt - convert Cockatrice’s .cod to .txt format
    • txt2cod - convert plaintext deck formats to Cockatrice’s cod
    • txt2dck - convert plaintext deck format to XMage
    • txt2txt - convert plaintext deck format to plaintext deck format (i.e. normalize the decklist)
    • url2cod - download decklists from URL and convert to .cod (a few popular websites supported)
    • url2dck - download decklists from URL and convert to XMage .dck format
    • url2txt - download decklists from URL and convert to .txt format

    data management

    These are used to generate data in data/, you probably won’t need to run them yourself

    • generate_colors_tsv_mtgjson - generate data/colors.tsv from mtgjson’s AllSets-x.json (recommended)
    • generate_colors_tsv_cockatrice - generate data/colors.tsv from cockatrice’s cards.xml (use mtgjson instead)
    • mage_card_map_generator - generate data/mage_cards.txt