CwCardUtils
A Ruby gem for analyzing Magic: The Gathering decklists and calculating various metrics.
Note: A new preferred namespace is available:
CracklingWit::CardUtils. The legacyCwCardUtilsnamespace remains for backward compatibility and will be softly deprecated.New usage (preferred):
require "crackling_wit/card_utils" a = CracklingWit::CardUtils::DecklistParser::Parser.new(decklist_a).parse b = CracklingWit::CardUtils::DecklistParser::Parser.new(decklist_b).parse cmp = CracklingWit::CardUtils::DeckComparator.new(a, b)Legacy usage (still supported):
require "cw_card_utils" a = CwCardUtils::DecklistParser::Parser.new(decklist_a).parse
Features
- Parse decklists (MTGA/MTGO/Moxfield) into a rich
Deckmodel - Compute curves: raw, normalized, and collapsed-normalized
- Detect archetypes from tag ratios, average CMC, and color/tribe labels
- Tag cards using lightweight text/keyword heuristics (threat, interaction, ramp, synergy, tribal)
- Calculate synergy probabilities for single/pair/triple combos
- Compare two decks and produce on-play/on-draw matchup insights
Requirements
- Ruby 3.0+ (tested on 3.x)
- No external services required for defaults (bundled Scryfall JSON)
Installation
Add this line to your application's Gemfile:
gem 'cw_card_utils'
And then execute:
bundle install
Or install directly:
gem install cw_card_utils
Quickstart
require "cw_card_utils"
decklist_a = <<~DECK
4 Lightning Bolt
4 Monastery Swiftspear
20 Mountain
DECK
decklist_b = <<~DECK
4 Counterspell
4 Memory Deluge
4 Supreme Verdict
24 Island
DECK
a = CwCardUtils::DecklistParser::Parser.new(decklist_a).parse
b = CwCardUtils::DecklistParser::Parser.new(decklist_b).parse
puts a.archetype # => e.g., "Mono-Red Aggro"
puts a.color_identity_string # => "Red"
puts a.collapsed_normalized_curve # => { "0-1"=>..., "2"=>..., ... }
cmp = CwCardUtils::DeckComparator.new(a, b)
pp cmp.compare[:on_play] # => win_rate_a, favored, notes, etc.
New namespace quickstart:
require "crackling_wit/card_utils"
a = CracklingWit::CardUtils::DecklistParser::Parser.new(decklist_a).parse
b = CracklingWit::CardUtils::DecklistParser::Parser.new(decklist_b).parse
cmp = CracklingWit::CardUtils::DeckComparator.new(a, b)
pp cmp.compare[:on_play]
Configuration
You can configure the card data source used by the library:
# Legacy namespace
CwCardUtils.configure do |config|
config.card_data_source = MyCustomDataSource.new
end
# New namespace (delegates to the same config under the hood)
CracklingWit::CardUtils.configure do |config|
config.card_data_source = MyCustomDataSource.new
end
# Or set it directly
CwCardUtils.card_data_source = MyCustomDataSource.new
CracklingWit::CardUtils.card_data_source = MyCustomDataSource.new
The default data source is CwCardUtils::ScryfallCmcData.instance, which loads card data from a local JSON file.
Usage
Deck Parsing and Formats
require 'cw_card_utils'
decklist = <<~DECK
4 Lightning Bolt
4 Mountain
2 Shock
DECK
deck = CwCardUtils::DecklistParser::Parser.new(decklist).parse
# Common accessors
puts deck.mainboard_size # => 10
puts deck.color_identity # => ["R"]
puts deck.archetype # => "Mono-Red Aggro" (string label)
puts deck.format # => :standard, :modern, :commander (heuristic)
Parses common formats (MTGA, MTGO, Moxfield). Sideboard sections are detected (e.g., lines starting with "Sideboard").
Curves and Summaries
deck.curve # => { 0=>2, 1=>8, 2=>6, 3=>4, ... }
deck.normalized_curve # => { 1=>0.22, 2=>0.18, ... }
deck.collapsed_curve # => { "0-1"=>10, "2"=>6, "3"=>4, ... }
deck.collapsed_normalized_curve
Archetype Detection and Tags
deck.archetype # => "Izzet Control", "Selesnya Midrange", etc.
deck.main.first. # => [:interaction, :draw, :etb, ...]
deck.color_identity_string# => "Azorius"
Synergy Probabilities
sp = CwCardUtils::SynergyProbability.new(deck, deck_size: deck.size)
sp.prob_single(["Lightning Bolt"], 7) # => Float 0..1
sp.prob_combo(["Card A", "Card B"], 10) # => Float 0..1
Deck Comparison (Matchup)
cmp = CwCardUtils::DeckComparator.new(deck_a, deck_b)
pp cmp.compare # => { on_play: {...}, on_draw: {...} }
Generating API Docs (RDoc)
Bilingual (EN/JA) RDoc is embedded in the source. Generate HTML docs:
rdoc -f darkfish -o doc lib README.md --title "Crackling Wit: Card Utilities" --exclude '\.json$'
Then open doc/index.html.
Custom Data Sources
You can implement your own card data source by inheriting from CwCardUtils::CardDataSource:
class MyCustomDataSource < CwCardUtils::CardDataSource
def find_card(name)
# Your implementation here
# Should return a hash with card data or nil
end
end
# Configure the library to use your data source
CwCardUtils.card_data_source = MyCustomDataSource.new
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/cracklingwit/cw_card_utils.
Data Sources
The default data source for this gem is an extraction from the wonderful Scryfall Bulk Data set. It is intended to be used only to test the functionality of the gem and serve as a fallback source of data.
You should use your own source, preferably backed by a database that conforms to our card_data_source API for your own production projects.
A huge, huge thank you to the wonderful folks at Scryfall for the hard work they put into keeping their data accurate, up to date, and free for the community to use. We are not endorsed or supported by Scryfall in any way.
Fan Content Policy
Crackling Wit's Card Utils gem is unofficial Fan Content permitted under the Fan Content Policy. It is not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. © Wizards of the Coast LLC.