Spyke

Replies

mtg

Comment on

What’s the most enjoyable casual way of playing Magic for you?

  • 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.
digital

Comment on

Looking for Decklists with Original Set Information for Preconstructed Decks

https://github.com/taw/magic-preconstructed-decks-data

This repository contains machine readable decklist data generated from:

Files

decks.json has traditional cards + sideboard structure, with commanders reusing sideboard.

decks_v2.json has cards + sideboard + commander structure. You should use this one.

Data format

Data file i a JSON array, with every element representing one deck.

Fields for each deck:

  • name - deck name
  • type - deck type
  • set_code - mtgjson set code
  • set_name - set name
  • release_date - deck release date (many decks are released much after their set)
  • cards - list of cards in the deck's mainboard
  • sideboard - list of cards in the deck's sideboard
  • commander - any commanders deck has (can be multiple for partners)

Each card is:

  • name - card name
  • set_code - mtgjson set card is from (decks often have cards from multiple sets)
  • number - card collector number
  • foil - is this a foil version
  • count - how many of given card
  • mtgjson_uuid - mtgjson uuid
  • multiverseid - Gatherer multiverseid of card if cards is on Gatherer (optional field)

Data Limitations

All precons ever released by Wizards of the Coast should be present, and decklists should always contain right cards, with correct number and foiling, and mainboard/sideboard/commander status.

Source decklists generally do not say which printing (set and card number) each card is from, so we need to use heuristics to figure that out.

We use a script to infer most likely set for each card based on some heuristics, and as far as I know, it always matches perfectly.

That just leaves situation where there are multiple printings of same card in same set.

If some of the printings are special (full art basics, Jumpstart basics, showcase frames etc.), these have been manually chosen to match every product.

If you see any errors for anything mentioned above, please report them, so they can be fixed.

That just leaves the case of multiple non-special printings of same card in same set - most commonly basic lands. In such case one of them is chosen arbitrarily, even though in reality a core set deck with 16 Forests would likely have 4 of each Forest in that core set, not 16 of one of them.

Feel free to create issue with data on exact priting if you want, but realistically we'll never get them all, and it's not something most people care about much.

mtg

Comment on

How can I play with custom MTG format with a shared card pool to check for legality?

My first try was using this script:
Query Scryfall + dump card names out for easy import into Moxfield

❯ python scryfall_search.py -q "f:standard f:penny usd<=1" --output-as-file "$HOME/desktop/out.csv"
Running Scryfall search on f:standard f:penny usd<=1 legal:commander
Found 1,197 total matches!

But when I tried importing the output csv in Moxfield, I got a bunch of No card name found on line x errors.

mtg

Comment on

Design a really cheap MTG format

Ideas to shake up the meta:

  1. Rotate the legal card list monthly instead of after each regular set release.
  2. Each rotation, ban the top 1% most played cards for a number of rotations. Or a random number of rotations for each card so they don't all become legal again simultaneously.
  3. Set a limit on the max number of copies allowed of each card. The limit could be randomized each rotation.
  4. Limit the number of rares/mythics allowed per deck.
  5. Require a minimum number of cards from the latest sets.
  6. Have occasional flashback weekends using previous cardpool rotations.
  7. Sometimes change to a different base cardpool like a block format or a format other than vintage.
digital

Comment on

Looking for Decklists with Original Set Information for Preconstructed Decks

Reply in thread

Here are step-by-step instructions to migrate decks_v2.json to .dck files with the desired structure, assuming no prior knowledge of the command line:

  1. Open a web browser and go to the following link: https://github.com/taw/magic-preconstructed-decks
  2. Click the green "Code" button and select "Download ZIP" to download the repository as a ZIP file.
  3. Extract the ZIP file to a folder on your computer.
  4. Open the folder and create a new file migrate_decks.py.
  5. Right-click on the file and select "Open With" and then choose a text editor such as Notepad or Sublime Text.
  6. Copy the following Python script and paste it into the text editor:
import json
import os
import re

from typing import List, Dict

DECKS_FOLDER = 'Preconstructed Decks'

def load_decks(file_path: str) -> List[Dict]:
    with open(file_path, 'r') as f:
        return json.load(f)

def format_deck_name(name: str) -> str:
    name = name.lower().replace(' ', '_').replace('-', '_')
    return re.sub(r'[^a-z0-9_]', '', name)

def get_deck_info(deck: Dict) -> Dict:
    return {
        'name': format_deck_name(deck['name']),
        'type': deck['type'],
        'set_code': deck['set_code'].upper(),
        'set_name': deck['set_name'],
        'release_date': deck['release_date'],
        'deck_folder': DECKS_FOLDER,
        'cards': deck['cards'],
        'sideboard': deck['sideboard']
    }

def build_deck_text(deck_info: Dict) -> str:
    lines = [
        f'// {deck_info["name"]}',
        f'// Set: {deck_info["set_name"]} ({deck_info["set_code"]})',
        f'// Release Date: {deck_info["release_date"]}',
        '',
    ]

    for card in deck_info['cards']:
        lines.append(f'{card["count"]} [{card["set_code"]}:{card["number"]}] {card["name"]}')

    lines.append('')
    lines.append('SB:')

    for card in deck_info['sideboard']:
        lines.append(f'{card["count"]} [{card["set_code"]}:{card["number"]}] {card["name"]}')

    return '\n'.join(lines)

def build_deck_path(deck_info: Dict) -> str:
    return os.path.join(deck_info['deck_folder'],
                        deck_info['type'],
                        deck_info['set_code'])

def write_deck_file(deck_info: Dict, deck_text: str) -> None:
    deck_path = build_deck_path(deck_info)
    os.makedirs(deck_path, exist_ok=True)

    filename = f"{deck_info['name']}.dck"
    file_path = os.path.join(deck_path, filename)

    with open(file_path, 'w') as f:
        f.write(deck_text)

def migrate_decks(input_file: str, error_file: str) -> None:
    decks = load_decks(input_file)

    error_decks: List[Dict] = []
    for deck in decks:
        try:
            deck_info = get_deck_info(deck)
            deck_text = build_deck_text(deck_info)
            write_deck_file(deck_info, deck_text)
        except KeyError:
            error_decks.append(deck)

    if error_decks:
        with open(error_file, 'w') as f:
            json.dump(error_decks, f)

if __name__ == '__main__':
    migrate_decks('decks_v2.json', 'error_decks.json')
  1. Open a terminal or command prompt on your computer. On Windows, you can do this by pressing the Windows key and typing "cmd" and then pressing Enter.
  2. Navigate to the folder where the decks_v2.json file and the migrate_decks.py file are located. You can do this by typing cd followed by the path to the folder, such as cd C:\Users\YourName\Downloads\magic-preconstructed-decks-master.
  3. Type python migrate_decks.py and press Enter to run the Python script.
  4. Wait for the script to finish running. It will create a .dck file for each deck in the decks_v2.json file, with the desired structure.

Note: If you don't have Python installed on your computer, you can download it from the official website: https://www.python.org/downloads/. Choose the latest version for your operating system and follow the installation instructions.

digital

Comment on

Looking for Decklists with Original Set Information for Preconstructed Decks

tappedout exports as csv with set information:

.csv
Board,Qty,Name,Printing,Foil,Alter,Signed,Condition,Language
main,1,Beacon Bolt,GRN,,,,,

.dck
1 [GRN:?] Beacon Bolt

Here is a Python script that reads the .csv file and writes the required format to a .dck file. This script uses the csv module to read the .csv file and write to the .dck file.

import csv

def read_csv_file(file_path):
    with open(file_path, 'r') as file:
        return list(csv.reader(file))

def write_to_dck_file(file_path, data):
    with open(file_path, 'w') as file:
        file.writelines(data)

def convert_csv_to_dck_format(csv_data):
    csv_header, *csv_rows = csv_data
    return [format_dck_line(row) for row in csv_rows]

def format_dck_line(row):
    quantity, name, printing = row[1], row[2], row[3]
    return f"{quantity} [{printing}:?] {name}\n"

csv_data = read_csv_file('input.csv')
dck_data = convert_csv_to_dck_format(csv_data)
write_to_dck_file('output.dck', dck_data)

This script works as follows:

  1. It opens the .csv file in read mode.
  2. It creates a csv reader object to read the .csv file.
  3. It skips the header row using the next() function.
  4. It opens the .dck file in write mode.
  5. For each row in the .csv file, it formats the line as per the .dck file format. The format is "Quantity [Printing:?] Name". Here, Quantity is the second column in the .csv file, Printing is the fourth column, and Name is the third column.
  6. It writes the formatted line to the .dck file.

Please replace 'input.csv' with the path to your .csv file and 'output.dck' with the path where you want to create the .dck file. Run this script in a Python environment, and it will create the .dck file with the required format.

mtg

Comment on

How can I play with custom MTG format with a shared card pool to check for legality?

✅ 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}")
mtg

Comment on

How to check deck legality and build decks on a custom format?

Reply in thread

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

mtg

Comment on

How to check deck legality and build decks on a custom format?

Reply in thread

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.

mtg

Comment on

How to check deck legality and build decks on a custom format?

Reply in thread

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()
mtg

Comment on

MTG Deck Legality Checker for custom formats!

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.

mtg

Comment on

Design a really cheap MTG format

A Flashback Draft is a limited-time event on Magic Online where players can draft and play with sets from the past. The sets available for Flashback Drafts change regularly, and Wizards of the Coast does not publish a schedule for them. However, players can stay up to date on upcoming Flashback Drafts by checking the Magic Online website or following Magic Online's social media accounts. Flashback Drafts are a popular way for players to experience sets that they may have missed or to revisit sets that they enjoyed in the past. The entry fee for Flashback Drafts varies depending on the set and the type of draft league, but players can typically use event tickets or play points to enter.

Flashback Format

  • Inspired by the MTGO Flashback Drafts, but for constructed play

  • Minimum deck size: 60 cards

  • No more than 4 copies of any card, except basic lands

  • Cards can be of any rarity

  • The legal card pool changes every month, based on a randomly selected block from Magic's history

  • You can only use cards from the chosen block, and only from the sets that were released at that point in time

  • For example, if the block is Innistrad, you can use cards from Innistrad, Dark Ascension, and Avacyn Restored, but not from Shadows over Innistrad or Eldritch Moon

  • Additionally, you can only use cards that cost less than $1 according to Scryfall's market price

  • This format lets you revisit old sets and experience different eras of Magic with a budget-friendly twist

  • It challenges you to adapt to changing metagames and discover new synergies with limited card choices