Compare commits

...

7 Commits

Author SHA1 Message Date
Tuan-Dat Tran
31a4995bbe feat: add deck builder with multiple collection support
- Add build_deck.py script for automated deck building
- Support multiple collection files for comprehensive deck building
- Rename main collection file to full_collection.json
- Add comprehensive documentation and usage examples
- Include design and implementation plans
- Enhance synergy detection and commander suggestion
2026-03-03 23:23:44 +01:00
Tuan-Dat Tran
c19ae87e4c Update cache and hydrated data with new card fetches 2026-02-27 02:38:31 +01:00
Tuan-Dat Tran
32ef77bc6f Strip set codes from card names before querying Scryfall 2026-02-27 02:35:32 +01:00
Tuan-Dat Tran
683de4a46a Add retry logic with exponential backoff for 429 rate limits 2026-02-27 02:31:35 +01:00
Tuan-Dat Tran
1cd9a90a27 Update collection files 2026-02-27 02:30:26 +01:00
Tuan-Dat Tran
9b5c81b0af Remove obsolete deck.json (merged into type-specific files) 2026-02-27 02:27:31 +01:00
Tuan-Dat Tran
75d669f2d5 Update card cache and hydrated data with new collection 2026-02-27 02:27:26 +01:00
17 changed files with 21253 additions and 1077 deletions

View File

@@ -102,6 +102,7 @@ Array of card objects with Scryfall fields:
| Deck | Colors | Commander | Archetype |
|------|--------|-----------|-----------|
| my_choco_deck | BUW | G'raha Tia, Scion Reborn | Auto-generated |
| Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall |
| Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats |
| Palamecia | UR | The Emperor of Palamecia // The Lord Master of Hell | Izzet Self-Mill Storm |

View File

@@ -64,6 +64,7 @@ python scripts/deck_report.py --collection output/hydrated/deck.json --decks-dir
| Deck | Colors | Commander | Archetype |
|------|--------|-----------|-----------|
| my_choco_deck | BUW | G'raha Tia, Scion Reborn | Auto-generated |
| Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall |
| Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats |
| Palamecia | UR | The Emperor of Palamecia // The Lord Master of Hell | Izzet Self-Mill Storm |
@@ -352,16 +353,16 @@ for name, count in sorted(cards.items()):
## Collection Stats
- **Total cards:** 1209
- **Unique cards:** 866
- **Total cards:** 1288
- **Unique cards:** 892
- **By type:**
- Artifacts: 84
- Creatures: 390
- Enchantments: 63
- Instants: 124
- Lands: 97
- Artifacts: 86
- Creatures: 419
- Enchantments: 67
- Instants: 134
- Lands: 76
- Planeswalkers: 2
- Sorceries: 105
- Sorceries: 107
---

17540
cache/card_cache.json vendored

File diff suppressed because it is too large Load Diff

View File

@@ -102,7 +102,7 @@
1 Gravblade Heavy (EOE) 102
1 Buzzard-Wasp Colony (TLA) 88
1 Soul-Shackled Zombie (FDN) 70
4 Dawnhand Eulogist (ECL) 99
5 Dawnhand Eulogist (ECL) 99
4 Dream Seizer (ECL) 101
1 Nightmare Sower (ECL) 114
1 Vincent Valentine // Galian Beast (FIN) 125
@@ -113,7 +113,7 @@
1 Hog-Monkey (TLA) 104
1 Pirate Peddlers (TLA) 115
1 Moonglove Extractor (ECL) 109
1 Heirloom Auntie (ECL) 107
2 Heirloom Auntie (ECL) 107
1 Gnarlbark Elm (ECL) 103
1 Retched Wretch (ECL) 117
1 June, Bounty Hunter (TLA) 106
@@ -271,7 +271,7 @@
1 Serah Farron // Crystallized Serah (FIN) 240
1 Wary Farmer (ECL) 251
1 Garnet, Princess of Alexandria (FIN) 222
1 Catharsis (ECL) 209
2 Catharsis (ECL) 209
1 Cloud, Planet's Champion (FIN) 552 *F*
1 Sami, Ship's Engineer (EOE) 225
2 Feisty Spikeling (ECL) 223
@@ -366,7 +366,7 @@
1 Disturbing Mirth (DSK) 212
1 Eusocial Engineering (EOE) 181
1 Wall Crawl (SPM) 121
6 Gilt-Leaf's Embrace (ECL) 177
7 Gilt-Leaf's Embrace (ECL) 177
1 Campsite Cuisine (FIC) 464
1 Triple Triad (FIN) 340
1 Breaching Dragonstorm (TDM) 101
@@ -394,7 +394,7 @@
1 Kinbinding (ECL) 407 *F*
1 Southern Air Temple (TLA) 36
2 Clachan Festival (ECL) 10
5 Spiral into Solitude (ECL) 36
6 Spiral into Solitude (ECL) 36
1 Evershrike's Gift (ECL) 15
2 Kraven's Last Hunt (SPM) 105
2 Prismatic Undercurrents (ECL) 189
@@ -436,7 +436,7 @@
1 Extract a Confession (MKM) 84
4 Bogslither's Embrace (ECL) 94
1 Reanimate (ARC) 21
1 Wanderwine Farewell (ECL) 83
2 Wanderwine Farewell (ECL) 83
1 Waterbending Lesson (TLA) 80
1 Lingering Souls (FIC) 245
1 Cut a Deal (FIC) 238
@@ -503,7 +503,7 @@
1 Accumulate Wisdom (TLA) 44
1 Whoosh! (SPM) 48
3 Run Away Together (ECL) 67
5 Wild Unraveling (ECL) 84
6 Wild Unraveling (ECL) 84
1 Get Out (DSK) 60
1 Long River's Pull (BLB) 58
1 Stolen Uniform (FIN) 75
@@ -575,7 +575,6 @@
1 Patchwork Banner (BLB) 247
1 Kastral, the Windcrested (BLB) 221
1 Restless Anchorage (LCI) 347
2 Plains (FIN) 295
1 Watcher of the Spheres (M21) 227
1 Murmuration (BLC) 10
1 Talisman of Progress (FIC) 367
@@ -584,7 +583,6 @@
1 Demolition Field (FDN) 687
1 Adarkar Wastes (EOC) 147
1 Empyrean Eagle (FDN) 239
1 Forest (FIN) 307
1 Seaside Citadel (FIC) 420
1 Warden of Evos Isle (CMR) 106
2 Exotic Orchard (BLC) 131
@@ -614,24 +612,17 @@
1 Dusk // Dawn (DRC) 65
1 Nature's Lore (FIC) 311
1 Terramorphic Expanse (TDC) 408
1 Plains (FIN) 294
1 Yavimaya Coast (TDC) 413
1 Seaside Haven (ONS) 323
1 Forest (FIN) 306
1 Gwaihir the Windlord (LTR) 210
1 City Pigeon (SPM) 4
1 Command Tower (FIC) 484
1 Sevinne's Reclamation (MH3) 267
1 Kangee, Sky Warden (CMR) 283
3 Plains (FIC) 478 *F*
1 Sidequest: Raise a Chocobo // Black Chocobo (FIN) 201
1 Generous Gift (BLC) 106
4 Island (FIC) 479 *F*
1 Canopy Vista (FIC) 378
2 Forest (FIC) 482 *F*
1 Choco, Seeker of Paradise (FIN) 215
3 Plains (FIN) 296
2 Mountain (FIC) 481 *F*
1 Mana Geyser (TDC) 223
1 Big Score (TDC) 206
1 Ovika, Enigma Goliath (ONE) 322
@@ -644,7 +635,6 @@
1 Drown in Dreams (M3C) 181
1 Vivi Ornitier (FIN) 248
1 Ultros, Obnoxious Octopus (FIN) 83
1 Island (TLA) 293
1 Decaying Time Loop (WHO) 80
1 Tellah, Great Sage (FIN) 244
1 Traumatize (MAR) 15
@@ -693,12 +683,6 @@
1 Peter Parker's Camera (SPM) 171
1 Pinnacle Monk // Mystic Peak (MH3) 246
1 Cut Your Losses (SNC) 38
2 Mountain (FIN) 304
6 Island (FIN) 297
4 Mountain (FIN) 305
6 Island (FIN) 298
4 Mountain (FIN) 303
5 Island (FIN) 299
1 Garruk, Cursed Huntsman (BLC) 99
1 Moldervine Reclamation (BLC) 255
1 Chitterspitter (BLC) 211
@@ -779,10 +763,6 @@
1 Bastion of Remembrance (TDC) 171
1 Grim Backwoods (DSC) 281
1 Hazel of the Rootbloom (BLC) 2
5 Forest (BLB) 377
4 Swamp (BLB) 374
4 Forest (BLB) 378
3 Swamp (BLB) 373
1 Exotic Orchard (FIC) 390
1 Ultima (FIN) 38
1 Darkwater Catacombs (FIC) 384
@@ -806,7 +786,6 @@
1 Rite of Replication (FIC) 270
1 Underground River (FIC) 439
1 Archaeomancer's Map (FIC) 230
2 Swamp (FIC) 480 *F*
1 Vanish from Sight (DSK) 82
1 Scavenger Grounds (FIC) 419
1 Fetid Heath (FIC) 391
@@ -814,7 +793,6 @@
1 Brainstorm (FCA) 28
1 Urza's Saga (MH2) 259
1 Krile Baldesion (FIC) 86
1 Swamp (FIN) 301
1 Amazing Acrobatics (SPM) 25
1 Ash Barrens (FIC) 374
1 Riverwalk Technique (TDM) 54
@@ -864,13 +842,12 @@
1 Cloud, Midgar Mercenary (FIN) 564 *F*
1 Wakka, Devoted Guardian (FIC) 477 *F*
1 Elspeth, Storm Slayer (TDM) 11
1 Forest (ECL) 273
2 Boneclub Berserker (ECL) 126
1 Flitterwing Nuisance (ECL) 48
1 Thoughtweft Lieutenant (ECL) 343
1 Rime Chill (ECL) 64
1 Flock Impostor (ECL) 16
2 Reckless Ransacking (ECL) 152
3 Reckless Ransacking (ECL) 152
1 Requiting Hex (ECL) 116
1 Bristlebane Battler (ECL) 168
2 Sting-Slinger (ECL) 161
@@ -895,7 +872,7 @@
3 Evolving Wilds (ECL) 264
2 Giantfall (ECL) 141
2 Rimekin Recluse (ECL) 66
3 Foraging Wickermaw (ECL) 256
4 Foraging Wickermaw (ECL) 256
2 Lasting Tarfire (ECL) 149
2 Elder Auntie (ECL) 133
1 Blight Rot (ECL) 89
@@ -906,5 +883,10 @@
1 Gangly Stompling (ECL) 226
1 Spider-Sense (SPM) 284 *F*
1 Abigale, Eloquent First-Year (ECL) 204
1 Swamp (ECL) 271
1 Pallimud (TMP) 195
1 Burdened Stoneback (ECL) 8
1 Morcant's Eyes (ECL) 185
1 Goatnap (ECL) 142
1 Chitinous Graspling (ECL) 211
1 Crushing Disappointment (STX) 68
1 Flubs, the Fool (BLC) 356

View File

@@ -1,29 +1,29 @@
1 Steam Vents (ECL) 267
2 Champions of the Shoal (ECL) 46
1 End-Blaze Epiphany (ECL) 134
1 Dundoolin Weaver (ECL) 175
2 Dundoolin Weaver (ECL) 175
1 Boggart Cursecrafter (ECL) 206
1 Mirrormind Crown (ECL) 258
1 Celestial Reunion (ECL) 170
1 Morcant's Loyalist (ECL) 236
3 Assert Perfection (ECL) 164
4 Assert Perfection (ECL) 164
1 Dream Harvest (ECL) 216
1 Eclipsed Kithkin (ECL) 220
2 Sizzling Changeling (ECL) 155
3 Wanderbrine Preacher (ECL) 41
3 Summit Sentinel (ECL) 73
2 Eclipsed Kithkin (ECL) 220
4 Sizzling Changeling (ECL) 155
4 Wanderbrine Preacher (ECL) 41
5 Summit Sentinel (ECL) 73
3 Mistmeadow Council (ECL) 183
2 Burning Curiosity (ECL) 129
4 Burning Curiosity (ECL) 129
1 Goliath Daydreamer (ECL) 143
1 Nameless Inversion (ECL) 113
3 Nameless Inversion (ECL) 113
1 Lavaleaper (ECL) 318
1 Burdened Stoneback (ECL) 8
2 Gathering Stone (ECL) 257
1 Deepchannel Duelist (ECL) 213
3 Gangly Stompling (ECL) 226
2 Merrow Skyswimmer (ECL) 234
1 Appeal to Eirdu (ECL) 5
3 Elder Auntie (ECL) 133
3 Merrow Skyswimmer (ECL) 234
3 Appeal to Eirdu (ECL) 5
4 Elder Auntie (ECL) 133
1 Sygg's Command (ECL) 244
1 Pitiless Fists (ECL) 187
2 Encumbered Reejerey (ECL) 14
@@ -33,43 +33,43 @@
2 Blossombind (ECL) 45
1 Trystan, Callous Cultivator // Trystan, Penitent Culler (ECL) 199
1 Glamer Gifter (ECL) 49
2 Keep Out (ECL) 19
3 Keep Out (ECL) 19
1 Barbed Bloodletter (ECL) 86
1 Sun-Dappled Celebrant (ECL) 37
2 Wanderwine Distracter (ECL) 82
1 Lluwen, Imperfect Naturalist (ECL) 232
1 Deceit (ECL) 212
1 Iron-Shield Elf (ECL) 108
1 Illusion Spinners (ECL) 55
1 Kindle the Inner Flame (ECL) 147
2 Illusion Spinners (ECL) 55
2 Kindle the Inner Flame (ECL) 147
2 Mischievous Sneakling (ECL) 235
1 Stratosoarer (ECL) 72
2 Run Away Together (ECL) 67
2 Shore Lurker (ECL) 34
2 Reckless Ransacking (ECL) 152
2 Stratosoarer (ECL) 72
3 Run Away Together (ECL) 67
3 Shore Lurker (ECL) 34
3 Reckless Ransacking (ECL) 152
2 Dawnhand Eulogist (ECL) 99
1 Evershrike's Gift (ECL) 15
2 Evershrike's Gift (ECL) 15
1 Dawnhand Dissident (ECL) 311
1 Bark of Doran (ECL) 6
1 Gnarlbark Elm (ECL) 103
1 Luminollusk (ECL) 179
1 Flaring Cinder (ECL) 225
2 Luminollusk (ECL) 179
2 Flaring Cinder (ECL) 225
1 Dose of Dawnglow (ECL) 100
1 Ashling, Rekindled // Ashling, Rimebound (ECL) 124
1 Morcant's Eyes (ECL) 185
1 Wanderwine Farewell (ECL) 83
1 Pyrrhic Strike (ECL) 30
1 Chaos Spewer (ECL) 210
2 Blighted Blackthorn (ECL) 90
3 Blighted Blackthorn (ECL) 90
2 Great Forest Druid (ECL) 178
2 Thoughtweft Charge (ECL) 198
2 Unexpected Assistance (ECL) 80
2 Safewright Cavalry (ECL) 191
3 Unexpected Assistance (ECL) 80
3 Safewright Cavalry (ECL) 191
4 Bogslither's Embrace (ECL) 94
3 Riverguard's Reflexes (ECL) 33
2 Unbury (ECL) 123
3 Midnight Tilling (ECL) 182
2 Flamekin Gildweaver (ECL) 140
3 Flamekin Gildweaver (ECL) 140
2 Stalactite Dagger (ECL) 261
2 Cinder Strike (ECL) 131
3 Lys Alana Informant (ECL) 181
@@ -79,28 +79,28 @@
2 Puca's Eye (ECL) 259
1 Loch Mare (ECL) 57
1 Sygg, Wanderwine Wisdom // Sygg, Wanderbrine Shield (ECL) 76
2 Moonglove Extractor (ECL) 109
2 Wildvine Pummeler (ECL) 203
1 Aquitect's Defenses (ECL) 44
1 Gallant Fowlknight (ECL) 17
2 Wary Farmer (ECL) 251
4 Moonglove Extractor (ECL) 109
3 Wildvine Pummeler (ECL) 203
4 Aquitect's Defenses (ECL) 44
3 Gallant Fowlknight (ECL) 17
4 Wary Farmer (ECL) 251
2 Reaping Willow (ECL) 240
1 Moon-Vigil Adherents (ECL) 184
2 Moon-Vigil Adherents (ECL) 184
1 Dawn-Blessed Pennant (ECL) 254
1 Unwelcome Sprite (ECL) 81
2 Unwelcome Sprite (ECL) 81
1 Glen Elendra Guardian (ECL) 51
1 Thoughtweft Lieutenant (ECL) 246
1 Meek Attack (ECL) 151
1 Hallowed Fountain (ECL) 265
1 Vinebred Brawler (ECL) 201
2 Vinebred Brawler (ECL) 201
2 Personify (ECL) 28
2 Rooftop Percher (ECL) 2
1 Blight Rot (ECL) 89
2 Liminal Hold (ECL) 24
1 Kulrath Mystic (ECL) 56
1 Crossroads Watcher (ECL) 173
1 Flame-Chain Mauler (ECL) 138
2 Scarblade's Malice (ECL) 119
3 Blight Rot (ECL) 89
3 Liminal Hold (ECL) 24
3 Kulrath Mystic (ECL) 56
3 Crossroads Watcher (ECL) 173
3 Flame-Chain Mauler (ECL) 138
4 Scarblade's Malice (ECL) 119
1 Bitterblossom (SPG) 133
1 Rimefire Torque (ECL) 65
1 Shimmerwilds Growth (ECL) 194
@@ -109,38 +109,38 @@
1 Shimmercreep (ECL) 120
1 Flamebraider (ECL) 139
1 Tanufel Rimespeaker (ECL) 77
2 Glamermite (ECL) 50
2 Heirloom Auntie (ECL) 107
2 Gutsplitter Gang (ECL) 106
4 Chitinous Graspling (ECL) 211
3 Glamermite (ECL) 50
3 Heirloom Auntie (ECL) 107
3 Gutsplitter Gang (ECL) 106
5 Chitinous Graspling (ECL) 211
2 Sear (ECL) 154
2 Clachan Festival (ECL) 10
1 Oko, Lorwyn Liege // Oko, Shadowmoor Scion (ECL) 61
1 Selfless Safewright (ECL) 193
2 Creakwood Safewright (ECL) 96
1 Noggle Robber (ECL) 237
2 Brambleback Brute (ECL) 128
1 Dawn's Light Archer (ECL) 174
2 Feed the Flames (ECL) 137
1 Auntie's Sentence (ECL) 85
2 Noggle Robber (ECL) 237
3 Brambleback Brute (ECL) 128
3 Dawn's Light Archer (ECL) 174
3 Feed the Flames (ECL) 137
3 Auntie's Sentence (ECL) 85
1 Kinscaer Sentry (ECL) 22
1 Spell Snare (ECL) 71
2 Spell Snare (ECL) 71
2 Virulent Emissary (ECL) 202
1 Explosive Prodigy (ECL) 136
1 Foraging Wickermaw (ECL) 256
1 Silvergill Peddler (ECL) 70
2 Goldmeadow Nomad (ECL) 18
2 Foraging Wickermaw (ECL) 256
3 Silvergill Peddler (ECL) 70
3 Goldmeadow Nomad (ECL) 18
1 Harmonized Crescendo (ECL) 54
1 Shinestriker (ECL) 68
2 Shinestriker (ECL) 68
1 Chomping Changeling (ECL) 172
1 Retched Wretch (ECL) 117
2 Retched Wretch (ECL) 117
1 Soulbright Seeker (ECL) 157
1 Stoic Grove-Guide (ECL) 243
2 Surly Farrier (ECL) 196
1 Scarblade Scout (ECL) 118
1 Timid Shieldbearer (ECL) 39
1 Unforgiving Aim (ECL) 200
1 Kulrath Zealot (ECL) 148
2 Stoic Grove-Guide (ECL) 243
3 Surly Farrier (ECL) 196
3 Scarblade Scout (ECL) 118
3 Timid Shieldbearer (ECL) 39
4 Unforgiving Aim (ECL) 200
3 Kulrath Zealot (ECL) 148
1 Shadow Urchin (ECL) 242
1 Rimekin Recluse (ECL) 66
1 Sting-Slinger (ECL) 161
@@ -150,9 +150,55 @@
1 Champion of the Clachan (ECL) 9
1 Blossoming Defense (ECL) 167
1 Hovel Hurler (ECL) 230
1 Tweeze (ECL) 162
2 Tweeze (ECL) 162
1 Spiral into Solitude (ECL) 36
2 Gravelgill Scoundrel (ECL) 53
3 Gravelgill Scoundrel (ECL) 53
1 Twilight Diviner (ECL) 122
1 Warren Torchmaster (ECL) 163
1 Noggle the Mind (ECL) 60
2 Warren Torchmaster (ECL) 163
2 Noggle the Mind (ECL) 60
1 Champion of the Path (ECL) 130
1 Deepchannel Duelist (ECL) 333
1 Gristle Glutton (ECL) 144
1 Spry and Mighty (ECL) 195
1 Crib Swap (ECL) 11
1 Glister Bairn (ECL) 227
1 Champion of the Weird (ECL) 95
1 Wanderbrine Trapper (ECL) 42
1 Eclipsed Flamekin (ECL) 337
1 Graveshifter (ECL) 104
3 Firdoch Core (ECL) 255
1 Boneclub Berserker (ECL) 126
1 Boggart Prankster (ECL) 93
1 Darkness Descends (ECL) 97
1 Collective Inferno (ECL) 132
1 Bristlebane Outrider (ECL) 169
1 Mudbutton Cursetosser (ECL) 112
1 Silvergill Mentor (ECL) 69
2 Changeling Wayfinder (ECL) 1
1 Brigid's Command (ECL) 208
1 Thoughtweft Lieutenant (ECL) 343
1 Rime Chill (ECL) 64
1 Flock Impostor (ECL) 16
2 Prideful Feastling (ECL) 238
1 Adept Watershaper (ECL) 3
1 Springleaf Drum (ECL) 260
1 Soul Immolation (ECL) 156
1 Voracious Tome-Skimmer (ECL) 250
1 Boggart Mischief (ECL) 92
1 Bloom Tender (ECL) 166
1 Thoughtweft Imbuer (ECL) 38
3 Tend the Sprigs (ECL) 197
3 Tributary Vaulter (ECL) 40
2 Giantfall (ECL) 141
1 Figure of Fable (ECL) 224
1 Doran, Besieged by Time (ECL) 215
1 Perfect Intimidation (ECL) 115
1 Boulder Dash (ECL) 127
1 Eclipsed Merrow (ECL) 221
1 Pummeler for Hire (ECL) 190
2 Dream Seizer (ECL) 101
1 Swat Away (ECL) 75
1 Sunderflock (ECL) 74
1 Kithkeeper (ECL) 23
1 Eclipsed Boggart (ECL) 335
1 Bre of Clan Stoutarm (ECL) 207

View File

@@ -0,0 +1,107 @@
# Deck Builder Design
## Overview
Semi-automated tool to build Commander decks from existing card collections.
## Architecture
- Script: `scripts/build_deck.py`
- Input: Hydrated collection JSON (`output/hydrated/deck.json`)
- Output: Deck JSON file in `data/decks/`
## Components
### 1. Collection Analyzer
- Analyzes color distribution across collection
- Identifies card type distribution (creatures, spells, lands)
- Calculates CMC distribution
- Finds potential synergies (keywords, creature types)
### 2. Commander Suggester
- Identifies legendary creatures and planeswalkers in collection
- Scores commanders based on:
- Color support in collection
- Number of cards matching color identity
- Synergistic mechanics present
- Provides top 3-5 commander recommendations
### 3. Deck Generator
- Creates starter decklist based on chosen commander
- Includes:
- Commander (1 card)
- Lands (35-40 cards matching color identity)
- Creatures, spells, and artifacts matching color identity
- Balanced mana curve
- Ensures deck meets Commander format requirements
### 4. Refinement Tools
- Functions to add/remove specific cards
- Balance mana curve
- Adjust land count
- Filter by card type or CMC
## Data Flow
1. Load hydrated collection from `output/hydrated/deck.json`
2. Analyze collection statistics
3. Identify potential commanders
4. User selects commander
5. Generate starter decklist
6. Save deck JSON file
7. Provide analysis report
## Error Handling
- Validate input file exists and is properly formatted
- Handle cases where no suitable commanders are found
- Provide fallback options for incomplete collections
- Validate generated deck meets format requirements
## Testing Strategy
1. Test with existing collection files
2. Verify generated decks meet Commander format requirements
3. Check color identity matching works correctly
4. Validate mana curve balancing
5. Test edge cases (small collections, mono-color, multi-color)
## Usage Documentation
Add to README.md:
```markdown
### Build a Deck From Your Collection
```bash
# Analyze your collection and build a deck
python scripts/build_deck.py --collection output/hydrated/deck.json --name my_new_deck
# View suggested commanders
python scripts/build_deck.py --collection output/hydrated/deck.json --list-commanders
# Build deck with specific commander
python scripts/build_deck.py --collection output/hydrated/deck.json --name my_deck --commander "Choco, Seeker of Paradise"
```
The script will:
1. Analyze your collection for color distribution and card types
2. Suggest potential commanders from your collection
3. Generate a starter decklist matching the commander's color identity
4. Save the deck to `data/decks/<name>.json`
5. Provide a summary report with suggestions for improvements
After building, you can:
- Manually edit the deck file
- Run analysis to find upgrades: `python scripts/analyze_decks.py --collection output/hydrated/deck.json --deck-dir data/decks/`
- Generate a report: `python scripts/deck_report.py --collection output/hydrated/deck.json --decks-dir data/decks/`
```
## Implementation Plan
1. Create `scripts/build_deck.py` with argparse CLI
2. Implement collection analysis functions
3. Build commander suggestion algorithm
4. Create deck generation logic
5. Add refinement tools
6. Write comprehensive tests
7. Update README documentation
8. Add to pre-commit hook for auto-docs

View File

@@ -0,0 +1,475 @@
# Deck Builder Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Build a semi-automated deck builder that creates Commander decks from existing card collections
**Architecture:** Python script that analyzes collection data, suggests commanders, and generates starter decklists
**Tech Stack:** Python 3 standard library (argparse, json, collections)
---
### Task 1: Create Deck Builder Script
**Files:**
- Create: `scripts/build_deck.py`
**Step 1: Write script skeleton with CLI interface**
```python
#!/usr/bin/env python3
"""
Deck builder for MTG Commander decks from existing collections.
"""
import argparse
import json
import os
from collections import Counter
from typing import List, Dict, Optional
def load_collection(collection_path: str) -> List[dict]:
"""Load hydrated collection from JSON file."""
with open(collection_path, 'r', encoding='utf-8') as f:
return json.load(f)
def analyze_collection(cards: List[dict]) -> Dict:
"""Analyze collection statistics."""
colors = Counter()
types = Counter()
cmc_dist = Counter()
for card in cards:
# Count colors
for color in card.get('color_identity', []):
colors[color] += card.get('count', 1)
# Count types
type_line = card.get('type_line', '')
if 'Creature' in type_line:
types['creatures'] += card.get('count', 1)
elif 'Instant' in type_line:
types['instants'] += card.get('count', 1)
elif 'Sorcery' in type_line:
types['sorceries'] += card.get('count', 1)
elif 'Artifact' in type_line:
types['artifacts'] += card.get('count', 1)
elif 'Enchantment' in type_line:
types['enchantments'] += card.get('count', 1)
elif 'Land' in type_line:
types['lands'] += card.get('count', 1)
# Count CMC
cmc = card.get('cmc')
if cmc:
cmc_dist[int(cmc)] += card.get('count', 1)
return {
'colors': dict(colors),
'types': dict(types),
'cmc_distribution': dict(cmc_dist),
'total_cards': sum(card.get('count', 1) for card in cards),
'unique_cards': len(cards)
}
def find_commanders(cards: List[dict]) -> List[dict]:
"""Find potential commander cards in collection."""
commanders = []
for card in cards:
type_line = card.get('type_line', '')
if ('Legendary' in type_line and
('Creature' in type_line or 'Planeswalker' in type_line)):
commanders.append(card)
return commanders
def score_commander(commander: dict, collection_stats: Dict) -> float:
"""Score commander based on collection support."""
color_identity = commander.get('color_identity', [])
score = 0.0
# Score based on color support
total_colors = sum(collection_stats['colors'].values())
if total_colors > 0:
color_support = sum(collection_stats['colors'].get(c, 0) for c in color_identity)
score += (color_support / total_colors) * 100
# Bonus for multi-color commanders with good support
if len(color_identity) > 1:
score += 10
return score
def suggest_commanders(cards: List[dict]) -> List[dict]:
"""Suggest top commanders from collection."""
commanders = find_commanders(cards)
if not commanders:
return []
stats = analyze_collection(cards)
scored = [(score_commander(cmd, stats), cmd) for cmd in commanders]
scored.sort(reverse=True, key=lambda x: x[0])
return [cmd for (score, cmd) in scored[:5]]
def generate_deck(commander: dict, cards: List[dict]) -> Dict:
"""Generate a decklist based on chosen commander."""
color_identity = commander.get('color_identity', [])
deck_cards = []
# Add commander
deck_cards.append({
'name': commander['name'],
'count': 1,
'data': commander
})
# Filter cards matching color identity
valid_cards = []
for card in cards:
if card['name'] == commander['name']:
continue # Skip commander in main deck
card_colors = card.get('color_identity', [])
# Check if card colors are subset of commander colors
if all(c in color_identity for c in card_colors):
valid_cards.append(card)
# Sort by CMC and add cards
valid_cards.sort(key=lambda x: x.get('cmc', 0))
# Add lands (target ~38 lands)
land_count = 0
lands_added = []
for card in valid_cards:
if 'Land' in card.get('type_line', '') and land_count < 38:
count = min(card.get('count', 1), 38 - land_count)
if count > 0:
deck_cards.append({
'name': card['name'],
'count': count,
'data': card
})
land_count += count
lands_added.append(card['name'])
# Add non-land cards (target 99 total)
non_land_cards = [c for c in valid_cards if c['name'] not in lands_added]
non_land_count = 0
for card in non_land_cards:
if non_land_count >= 61: # 99 - 38 lands - 1 commander
break
count = min(card.get('count', 1), 61 - non_land_count)
if count > 0:
deck_cards.append({
'name': card['name'],
'count': count,
'data': card
})
non_land_count += count
return {
'name': f"{commander['name']} Deck",
'commander': commander['name'],
'colors': color_identity,
'archetype': 'Auto-generated',
'cards': {card['name']: card['count'] for card in deck_cards}
}
def save_deck(deck: Dict, output_dir: str = 'data/decks') -> str:
"""Save deck to JSON file."""
os.makedirs(output_dir, exist_ok=True)
# Create safe filename
commander_name = deck['commander'].replace(' ', '_').replace(',', '').replace("'", '')
filename = f"{commander_name}.json"
filepath = os.path.join(output_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(deck, f, indent=2)
return filepath
def main():
parser = argparse.ArgumentParser(description='Build MTG Commander decks from collection')
parser.add_argument('--collection', required=True, help='Path to hydrated collection JSON')
parser.add_argument('--name', help='Deck name (defaults to commander name)')
parser.add_argument('--commander', help='Specific commander to use')
parser.add_argument('--list-commanders', action='store_true', help='List suggested commanders')
parser.add_argument('--output-dir', default='data/decks', help='Output directory for deck files')
args = parser.parse_args()
# Load collection
cards = load_collection(args.collection)
if args.list_commanders:
commanders = suggest_commanders(cards)
print(f"Found {len(commanders)} potential commanders:")
for i, cmd in enumerate(commanders, 1):
colors = ''.join(cmd.get('color_identity', []))
print(f"{i}. {cmd['name']} ({colors})")
return
# Find or select commander
if args.commander:
# Find specific commander
commanders = [c for c in cards if c['name'] == args.commander]
if not commanders:
print(f"Error: Commander '{args.commander}' not found in collection")
return
commander = commanders[0]
else:
# Suggest and use top commander
commanders = suggest_commanders(cards)
if not commanders:
print("Error: No suitable commanders found in collection")
return
commander = commanders[0]
print(f"Using suggested commander: {commander['name']}")
# Generate deck
deck = generate_deck(commander, cards)
# Customize deck name if provided
if args.name:
deck['name'] = args.name
# Save deck
filepath = save_deck(deck, args.output_dir)
print(f"Deck saved to: {filepath}")
print(f"\nDeck Summary:")
print(f" Name: {deck['name']}")
print(f" Commander: {deck['commander']}")
print(f" Colors: {', '.join(deck['colors'])}")
print(f" Total cards: {sum(deck['cards'].values())}")
print(f" Unique cards: {len(deck['cards'])}")
if __name__ == '__main__':
main()
```
**Step 2: Test basic functionality**
Run: `python scripts/build_deck.py --help`
Expected: Help message with all arguments
**Step 3: Test with existing collection**
Run: `python scripts/build_deck.py --collection output/hydrated/deck.json --list-commanders`
Expected: List of potential commanders from collection
**Step 4: Generate a test deck**
Run: `python scripts/build_deck.py --collection output/hydrated/deck.json --name test_deck`
Expected: Deck file created in data/decks/
**Step 5: Commit initial implementation**
```bash
git add scripts/build_deck.py
git commit -m "feat: add deck builder script"
```
---
### Task 2: Add Documentation to README
**Files:**
- Modify: `README.md` (add usage section)
**Step 1: Add deck builder usage to README**
Add to README.md after the "Find Synergies" section:
```markdown
### Build a Deck From Your Collection
```bash
# List suggested commanders from your collection
python scripts/build_deck.py --collection output/hydrated/deck.json --list-commanders
# Build deck with suggested commander
python scripts/build_deck.py --collection output/hydrated/deck.json --name my_new_deck
# Build deck with specific commander
python scripts/build_deck.py --collection output/hydrated/deck.json --name my_deck --commander "Choco, Seeker of Paradise"
```
The deck builder will:
1. Analyze your collection for color distribution and card types
2. Suggest potential commanders from your collection
3. Generate a starter decklist matching the commander's color identity
4. Save the deck to `data/decks/<name>.json`
5. Provide a summary report
After building, refine your deck:
- Edit the deck file manually
- Find upgrades: `python scripts/analyze_decks.py --collection output/hydrated/deck.json --deck-dir data/decks/`
- Generate reports: `python scripts/deck_report.py --collection output/hydrated/deck.json --decks-dir data/decks/`
```
**Step 2: Update README**
Run: Edit README.md to add the new section
**Step 3: Commit documentation**
```bash
git add README.md
git commit -m "docs: add deck builder usage to README"
```
---
### Task 3: Enhance Deck Generation Logic
**Files:**
- Modify: `scripts/build_deck.py` (improve generate_deck function)
**Step 1: Improve land selection**
Update generate_deck to better handle land selection:
- Prioritize basic lands
- Include color-fixing lands when available
- Balance land types for multi-color decks
**Step 2: Add mana curve balancing**
Add function to balance mana curve:
- Target distribution: 10-12 cards at 2 CMC, 8-10 at 3, 6-8 at 4, etc.
- Adjust card selection to match curve
**Step 3: Test enhanced generation**
Run: `python scripts/build_deck.py --collection output/hydrated/deck.json --name enhanced_deck`
Expected: Better balanced deck with improved mana curve
**Step 4: Commit improvements**
```bash
git add scripts/build_deck.py
git commit -m "feat: improve deck generation logic"
```
---
### Task 4: Add Testing and Validation
**Files:**
- Create: `tests/test_build_deck.py`
**Step 1: Write unit tests**
```python
import json
import tempfile
import os
from scripts.build_deck import load_collection, analyze_collection, find_commanders
def test_load_collection():
"""Test loading collection from JSON."""
test_data = [
{"name": "Test Card", "count": 1, "color_identity": ["U"], "type_line": "Creature"}
]
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump(test_data, f)
filepath = f.name
try:
result = load_collection(filepath)
assert len(result) == 1
assert result[0]['name'] == "Test Card"
finally:
os.unlink(filepath)
def test_analyze_collection():
"""Test collection analysis."""
test_cards = [
{"name": "Blue Card", "count": 2, "color_identity": ["U"], "type_line": "Creature", "cmc": 3},
{"name": "Red Card", "count": 1, "color_identity": ["R"], "type_line": "Instant", "cmc": 2},
]
result = analyze_collection(test_cards)
assert result['total_cards'] == 3
assert result['colors']['U'] == 2
assert result['types']['creatures'] == 2
assert result['cmc_distribution'][2] == 1
def test_find_commanders():
"""Test commander finding."""
test_cards = [
{"name": "Legendary Creature", "type_line": "Legendary Creature", "color_identity": ["G"]},
{"name": "Regular Creature", "type_line": "Creature"},
]
commanders = find_commanders(test_cards)
assert len(commanders) == 1
assert commanders[0]['name'] == "Legendary Creature"
```
**Step 2: Run tests**
Run: `python -m pytest tests/test_build_deck.py -v`
Expected: All tests pass
**Step 3: Commit tests**
```bash
git add tests/test_build_deck.py
git commit -m "test: add unit tests for deck builder"
```
---
### Task 5: Final Integration and Testing
**Files:**
- Modify: `scripts/update_docs.py` (add deck builder to auto-docs)
**Step 1: Update documentation generator**
Add deck builder to the auto-generated documentation
**Step 2: Test complete workflow**
```bash
# Test complete workflow
python hydrate.py hydrate data/collection/Box1\ 2026-01-30.txt -o output/hydrated/ -c cache/card_cache.json
python scripts/build_deck.py --collection output/hydrated/deck.json --list-commanders
python scripts/build_deck.py --collection output/hydrated/deck.json --name my_test_deck
python scripts/analyze_decks.py --collection output/hydrated/deck.json --deck-dir data/decks/
```
**Step 3: Verify all tests pass**
Run: `python -m pytest tests/ -v`
Expected: All tests pass
**Step 4: Final commit**
```bash
git add .
git commit -m "feat: complete deck builder implementation"
```
---
## Plan Complete
**Execution Options:**
1. **Subagent-Driven (this session)** - I'll implement task-by-task with your review
2. **Parallel Session** - You can run this plan in a separate session
Which approach would you prefer?

View File

@@ -21,8 +21,16 @@ SCRYFALL_API = "https://api.scryfall.com"
RATE_LIMIT_DELAY = 0.1 # 100ms between requests
FIELDS = [
"name", "mana_cost", "cmc", "colors", "color_identity",
"type_line", "oracle_text", "power", "toughness", "loyalty"
"name",
"mana_cost",
"cmc",
"colors",
"color_identity",
"type_line",
"oracle_text",
"power",
"toughness",
"loyalty",
]
@@ -36,49 +44,90 @@ def parse_decklist(filepath: str) -> list[dict]:
continue
match = re.match(r"^(\d+)x?\s+(.+)$", line, re.IGNORECASE)
if match:
cards.append({"count": int(match.group(1)), "name": match.group(2).strip()})
cards.append(
{"count": int(match.group(1)), "name": match.group(2).strip()}
)
return cards
def fetch_card(name: str) -> Optional[dict]:
def strip_set_code(name: str) -> str:
"""Remove set code and collector number from card name.
E.g., "Keep Out (ECL) 19" -> "Keep Out"
"""
name = re.sub(r"\s*\([^)]+\)\s*\d+ *$", "", name)
name = re.sub(r"\s*\*F\*$", "", name)
return name.strip()
def fetch_card(name: str, retry_count: int = 3) -> Optional[dict]:
"""Fetch card data from Scryfall API using fuzzy search."""
original_name = name
name = strip_set_code(name)
encoded = urllib.parse.quote(name)
url = f"{SCRYFALL_API}/cards/named?fuzzy={encoded}"
try:
req = urllib.request.Request(url, headers={
"User-Agent": "EDHDeckBuilder/1.0",
"Accept": "*/*"
})
with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as e:
print(f" Error fetching '{name}': HTTP {e.code}", file=sys.stderr)
return None
except urllib.error.URLError as e:
print(f" Error fetching '{name}': {e.reason}", file=sys.stderr)
return None
except json.JSONDecodeError:
print(f" Error parsing response for '{name}'", file=sys.stderr)
return None
for attempt in range(retry_count):
try:
req = urllib.request.Request(
url, headers={"User-Agent": "EDHDeckBuilder/1.0", "Accept": "*/*"}
)
with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as e:
if e.code == 429:
retry_after = int(e.headers.get("Retry-After", 60))
print(f" Rate limited. Waiting {retry_after}s...", file=sys.stderr)
time.sleep(retry_after)
elif e.code == 404:
if name != original_name:
print(
f" Not found with set code, retrying: '{name}'",
file=sys.stderr,
)
return fetch_card(name, retry_count=1)
print(f" Error fetching '{original_name}': Not found", file=sys.stderr)
return None
else:
if attempt < retry_count - 1:
wait_time = 2**attempt
print(
f" HTTP {e.code}, retrying in {wait_time}s...", file=sys.stderr
)
time.sleep(wait_time)
else:
print(
f" Error fetching '{original_name}': HTTP {e.code}",
file=sys.stderr,
)
return None
except urllib.error.URLError as e:
print(f" Error fetching '{original_name}': {e.reason}", file=sys.stderr)
return None
except json.JSONDecodeError:
print(f" Error parsing response for '{original_name}'", file=sys.stderr)
return None
return None
def extract_card_info(card_data: dict) -> dict:
"""Extract relevant fields from Scryfall card data."""
result = {"scryfall_uri": card_data.get("scryfall_uri", "")}
for field in FIELDS:
result[field] = card_data.get(field)
if card_data.get("card_faces"):
face = card_data["card_faces"][0]
for field in ["mana_cost", "type_line", "oracle_text", "power", "toughness"]:
if result.get(field) is None:
result[field] = face.get(field)
result["colors"] = card_data.get("colors", [])
result["color_identity"] = card_data.get("color_identity", [])
return result
@@ -95,15 +144,17 @@ def categorize_by_type(cards: list[dict]) -> dict[str, list[dict]]:
"lands": [],
"other": [],
}
for card in cards:
type_line = card.get("type_line", "").lower()
if "legendary" in type_line and ("creature" in type_line or "planeswalker" in type_line):
if "legendary" in type_line and (
"creature" in type_line or "planeswalker" in type_line
):
if not categories["commander"]:
categories["commander"].append(card)
continue
if "creature" in type_line:
categories["creatures"].append(card)
elif "instant" in type_line:
@@ -120,27 +171,29 @@ def categorize_by_type(cards: list[dict]) -> dict[str, list[dict]]:
categories["lands"].append(card)
else:
categories["other"].append(card)
return {k: v for k, v in categories.items() if v}
def hydrate_decklist(input_file: str, output_dir: str, cache_file: Optional[str] = None) -> None:
def hydrate_decklist(
input_file: str, output_dir: str, cache_file: Optional[str] = None
) -> None:
"""Main hydration function."""
cache = {}
if cache_file and os.path.exists(cache_file):
with open(cache_file, "r", encoding="utf-8") as f:
cache = json.load(f)
print(f"Loaded {len(cache)} cached cards")
print(f"Parsing decklist: {input_file}")
entries = parse_decklist(input_file)
print(f"Found {len(entries)} unique card entries")
hydrated = []
for i, entry in enumerate(entries, 1):
name = entry["name"]
count = entry["count"]
if name in cache:
card_info = cache[name]
print(f"[{i}/{len(entries)}] {name} (cached)")
@@ -153,30 +206,30 @@ def hydrate_decklist(input_file: str, output_dir: str, cache_file: Optional[str]
time.sleep(RATE_LIMIT_DELAY)
else:
card_info = {"name": name, "error": "not found"}
card_info["count"] = count
hydrated.append(card_info)
if cache_file:
with open(cache_file, "w", encoding="utf-8") as f:
json.dump(cache, f, indent=2)
print(f"Cached {len(cache)} cards to {cache_file}")
categories = categorize_by_type(hydrated)
os.makedirs(output_dir, exist_ok=True)
all_cards_path = os.path.join(output_dir, "deck.json")
all_cards_path = os.path.join(output_dir, "full_collection.json")
with open(all_cards_path, "w", encoding="utf-8") as f:
json.dump(hydrated, f, indent=2)
print(f"Wrote full decklist to {all_cards_path}")
print(f"Wrote full collection to {all_cards_path}")
for category, cards in categories.items():
cat_path = os.path.join(output_dir, f"{category}.json")
with open(cat_path, "w", encoding="utf-8") as f:
json.dump(cards, f, indent=2)
print(f" {category}: {len(cards)} cards -> {cat_path}")
print(f"\nDeck summary:")
print(f" Total cards: {sum(c.get('count', 1) for c in hydrated)}")
print(f" Unique cards: {len(hydrated)}")
@@ -186,35 +239,44 @@ def create_deck(deck_name: str, base_dir: str = "decks") -> str:
"""Create a new deck folder structure."""
deck_path = os.path.join(base_dir, deck_name)
os.makedirs(deck_path, exist_ok=True)
template = {
"name": deck_name,
"commander": None,
"cards": []
}
template = {"name": deck_name, "commander": None, "cards": []}
with open(os.path.join(deck_path, "deck.json"), "w", encoding="utf-8") as f:
json.dump(template, f, indent=2)
print(f"Created deck: {deck_path}")
return deck_path
def main():
parser = argparse.ArgumentParser(description="Hydrate MTG decklists with Scryfall data")
parser = argparse.ArgumentParser(
description="Hydrate MTG decklists with Scryfall data"
)
subparsers = parser.add_subparsers(dest="command", help="Commands")
hydrate_parser = subparsers.add_parser("hydrate", help="Hydrate a decklist with Scryfall data")
hydrate_parser = subparsers.add_parser(
"hydrate", help="Hydrate a decklist with Scryfall data"
)
hydrate_parser.add_argument("input", help="Input decklist file")
hydrate_parser.add_argument("-o", "--output", default="output/hydrated", help="Output directory")
hydrate_parser.add_argument("-c", "--cache", default="cache/card_cache.json", help="Cache file for card data")
hydrate_parser.add_argument(
"-o", "--output", default="output/hydrated", help="Output directory"
)
hydrate_parser.add_argument(
"-c",
"--cache",
default="cache/card_cache.json",
help="Cache file for card data",
)
new_parser = subparsers.add_parser("new", help="Create a new deck folder")
new_parser.add_argument("name", help="Deck name")
new_parser.add_argument("-d", "--dir", default="data/decks", help="Base directory for decks")
new_parser.add_argument(
"-d", "--dir", default="data/decks", help="Base directory for decks"
)
args = parser.parse_args()
if args.command == "hydrate":
hydrate_decklist(args.input, args.output, args.cache)
elif args.command == "new":

View File

@@ -115,7 +115,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/259/pucas-eye?utm_source=api",
@@ -177,7 +177,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/57/sol-ring?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/59/sol-ring?utm_source=api",
"name": "Sol Ring",
"mana_cost": "{1}",
"cmc": 1.0,
@@ -188,7 +188,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/eoc/137/everflowing-chalice?utm_source=api",
@@ -326,7 +326,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fic/35/blue-mages-cane?utm_source=api",
@@ -808,7 +808,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/fin/64/the-prima-vista?utm_source=api",
@@ -1000,7 +1000,7 @@
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/55/arcane-signet?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/57/arcane-signet?utm_source=api",
"name": "Arcane Signet",
"mana_cost": "{2}",
"cmc": 2.0,
@@ -1092,7 +1092,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/blc/40/sword-of-the-squeak?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/blc/72/sword-of-the-squeak?utm_source=api",
"name": "Sword of the Squeak",
"mana_cost": "{2}",
"cmc": 2.0,
@@ -1168,7 +1168,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/55/arcane-signet?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/57/arcane-signet?utm_source=api",
"name": "Arcane Signet",
"mana_cost": "{2}",
"cmc": 2.0,
@@ -1196,7 +1196,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/57/sol-ring?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/59/sol-ring?utm_source=api",
"name": "Sol Ring",
"mana_cost": "{1}",
"cmc": 1.0,
@@ -1228,7 +1228,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/48/reapers-scythe?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/117/reapers-scythe?utm_source=api",
"name": "Reaper's Scythe",
"mana_cost": "{2}{B}",
"cmc": 3.0,
@@ -1246,7 +1246,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/17/dancers-chakrams?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/105/dancers-chakrams?utm_source=api",
"name": "Dancer's Chakrams",
"mana_cost": "{3}{W}",
"cmc": 4.0,
@@ -1264,7 +1264,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/57/sol-ring?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/59/sol-ring?utm_source=api",
"name": "Sol Ring",
"mana_cost": "{1}",
"cmc": 1.0,
@@ -1321,5 +1321,37 @@
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/6/bark-of-doran?utm_source=api",
"name": "Bark of Doran",
"mana_cost": "{1}{W}",
"cmc": 2.0,
"colors": [
"W"
],
"color_identity": [
"W"
],
"type_line": "Artifact \u2014 Equipment",
"oracle_text": "Equipped creature gets +0/+1.\nAs long as equipped creature's toughness is greater than its power, it assigns combat damage equal to its toughness rather than its power.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/254/dawn-blessed-pennant?utm_source=api",
"name": "Dawn-Blessed Pennant",
"mana_cost": "{1}",
"cmc": 1.0,
"colors": [],
"color_identity": [],
"type_line": "Artifact",
"oracle_text": "As this artifact enters, choose Elemental, Elf, Faerie, Giant, Goblin, Kithkin, Merfolk, or Treefolk.\nWhenever a permanent you control of the chosen type enters, you gain 1 life.\n{2}, {T}, Sacrifice this artifact: Return target card of the chosen type from your graveyard to your hand.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
}
]

View File

@@ -51,7 +51,7 @@
"power": "3",
"toughness": "5",
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fic/37/hraesvelgr-of-the-first-brood?utm_source=api",
@@ -105,7 +105,7 @@
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/82/wanderwine-distracter?utm_source=api",
@@ -123,10 +123,10 @@
"power": "4",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fic/33/alphinaud-leveilleur?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/140/alphinaud-leveilleur?utm_source=api",
"name": "Alphinaud Leveilleur",
"mana_cost": "{3}{U}",
"cmc": 4.0,
@@ -162,7 +162,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/mkm/73/surveillance-monitor?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/mkm/73%E2%80%A0/surveillance-monitor?utm_source=api",
"name": "Surveillance Monitor",
"mana_cost": "{3}{U}",
"cmc": 4.0,
@@ -213,7 +213,7 @@
"power": "2",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/50/glamermite?utm_source=api",
@@ -445,7 +445,7 @@
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/tdm/23/salt-road-packbeast?utm_source=api",
@@ -499,7 +499,7 @@
"power": "5",
"toughness": "6",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fic/26/summon:-good-king-mog-xii?utm_source=api",
@@ -556,7 +556,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/31/thancred-waters?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/139/thancred-waters?utm_source=api",
"name": "Thancred Waters",
"mana_cost": "{4}{W}",
"cmc": 5.0,
@@ -769,7 +769,7 @@
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tdm/2/anafenza-unyielding-lineage?utm_source=api",
@@ -916,7 +916,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/9/alisaie-leveilleur?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/129/alisaie-leveilleur?utm_source=api",
"name": "Alisaie Leveilleur",
"mana_cost": "{2}{W}",
"cmc": 3.0,
@@ -1003,7 +1003,7 @@
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/40/tributary-vaulter?utm_source=api",
@@ -1184,7 +1184,7 @@
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/14/encumbered-reejerey?utm_source=api",
@@ -1708,7 +1708,7 @@
"power": "3",
"toughness": "7",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/dsc/158/syr-konrad-the-grim?utm_source=api",
@@ -1866,7 +1866,7 @@
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 5
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/101/dream-seizer?utm_source=api",
@@ -1884,7 +1884,7 @@
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/114/nightmare-sower?utm_source=api",
@@ -2062,7 +2062,7 @@
"power": "4",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/103/gnarlbark-elm?utm_source=api",
@@ -2278,7 +2278,7 @@
"power": "1",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/87/bile-vial-boggart?utm_source=api",
@@ -2296,7 +2296,7 @@
"power": "1",
"toughness": "1",
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fin/121/summon:-primal-odin?utm_source=api",
@@ -2548,7 +2548,7 @@
"power": "6",
"toughness": "5",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/139/gilgamesh-master-at-arms?utm_source=api",
@@ -2890,7 +2890,7 @@
"power": "4",
"toughness": "5",
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fdn/191/brazen-scourge?utm_source=api",
@@ -3070,7 +3070,7 @@
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/141/hill-gigas?utm_source=api",
@@ -3250,7 +3250,7 @@
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 2
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fin/153/sabotender?utm_source=api",
@@ -3322,7 +3322,7 @@
"power": "1",
"toughness": "3",
"loyalty": null,
"count": 2
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fin/130/blazing-bomb?utm_source=api",
@@ -3502,7 +3502,7 @@
"power": "4",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/tla/168/the-boulder-ready-to-rumble?utm_source=api",
@@ -3717,7 +3717,7 @@
"power": "3",
"toughness": "1",
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/180/lys-alana-dignitary?utm_source=api",
@@ -3861,7 +3861,7 @@
"power": "1",
"toughness": "1",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/spm/114/spider-ham-peter-porker?utm_source=api",
@@ -3951,7 +3951,7 @@
"power": "4",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/211/vanille-cheerful-lcie?utm_source=api",
@@ -4006,7 +4006,7 @@
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 2
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/tla/202/walltop-sentries?utm_source=api",
@@ -4197,7 +4197,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/3/graha-tia-scion-reborn?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/211/graha-tia-scion-reborn?utm_source=api",
"name": "G'raha Tia, Scion Reborn",
"mana_cost": "{W}{U}{B}",
"cmc": 3.0,
@@ -4333,7 +4333,7 @@
"power": "5",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/tla/214/dai-li-agents?utm_source=api",
@@ -4453,7 +4453,7 @@
"power": "5",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/214/black-waltz-no-3?utm_source=api",
@@ -4653,7 +4653,7 @@
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/241/sanar-innovative-first-year?utm_source=api",
@@ -4713,7 +4713,7 @@
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/124/ashling-rekindled-ashling-rimebound?utm_source=api",
@@ -4978,7 +4978,7 @@
"power": "3",
"toughness": "4",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/552/cloud-planets-champion?utm_source=api",
@@ -5038,7 +5038,7 @@
"power": "2",
"toughness": "1",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/13/eirdu-carrier-of-dawn-isilu-carrier-of-twilight?utm_source=api",
@@ -5098,7 +5098,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/77/ardbert-warrior-of-darkness?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/164/ardbert-warrior-of-darkness?utm_source=api",
"name": "Ardbert, Warrior of Darkness",
"mana_cost": "{1}{W}{B}",
"cmc": 3.0,
@@ -5389,7 +5389,7 @@
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 2
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/250/voracious-tome-skimmer?utm_source=api",
@@ -5531,7 +5531,7 @@
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/cmm/967/palladium-myr?utm_source=api",
@@ -5559,7 +5559,7 @@
"power": "1",
"toughness": "2",
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/spm/173/spider-bot?utm_source=api",
@@ -6050,7 +6050,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/36/hermes-overseer-of-elpis?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/141/hermes-overseer-of-elpis?utm_source=api",
"name": "Hermes, Overseer of Elpis",
"mana_cost": "{3}{U}",
"cmc": 4.0,
@@ -6350,7 +6350,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/blc/19/moonstone-eulogist?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/blc/54/moonstone-eulogist?utm_source=api",
"name": "Moonstone Eulogist",
"mana_cost": "{3}{B}{B}",
"cmc": 5.0,
@@ -6481,7 +6481,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/blc/17/hazels-brewmaster?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/blc/52/hazels-brewmaster?utm_source=api",
"name": "Hazel's Brewmaster",
"mana_cost": "{3}{B}",
"cmc": 4.0,
@@ -6607,7 +6607,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/blc/18/insatiable-frugivore?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/blc/53/insatiable-frugivore?utm_source=api",
"name": "Insatiable Frugivore",
"mana_cost": "{3}{B}",
"cmc": 4.0,
@@ -6841,7 +6841,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/blc/33/scurry-of-squirrels?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/blc/66/scurry-of-squirrels?utm_source=api",
"name": "Scurry of Squirrels",
"mana_cost": "{2}{G}",
"cmc": 3.0,
@@ -6879,7 +6879,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/86/krile-baldesion?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/176/krile-baldesion?utm_source=api",
"name": "Krile Baldesion",
"mana_cost": "{W}{U}",
"cmc": 2.0,
@@ -6937,7 +6937,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/46/fandaniel-telophoroi-ascian?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/146/fandaniel-telophoroi-ascian?utm_source=api",
"name": "Fandaniel, Telophoroi Ascian",
"mana_cost": "{4}{B}",
"cmc": 5.0,
@@ -6973,7 +6973,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/90/papalymo-totolymo?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/180/papalymo-totolymo?utm_source=api",
"name": "Papalymo Totolymo",
"mana_cost": "{W}{B}",
"cmc": 2.0,
@@ -7049,7 +7049,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/7/yshtola-nights-blessed?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/226/yshtola-nights-blessed?utm_source=api",
"name": "Y'shtola, Night's Blessed",
"mana_cost": "{1}{W}{U}{B}",
"cmc": 4.0,
@@ -7127,5 +7127,541 @@
"toughness": "4",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/126/boneclub-berserker?utm_source=api",
"name": "Boneclub Berserker",
"mana_cost": "{3}{R}",
"cmc": 4.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Creature \u2014 Goblin Berserker",
"oracle_text": "This creature gets +2/+0 for each other Goblin you control.",
"power": "2",
"toughness": "4",
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/48/flitterwing-nuisance?utm_source=api",
"name": "Flitterwing Nuisance",
"mana_cost": "{U}",
"cmc": 1.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Creature \u2014 Faerie Rogue",
"oracle_text": "Flying\nThis creature enters with a -1/-1 counter on it.\n{2}{U}, Remove a counter from this creature: Whenever a creature you control deals combat damage to a player or planeswalker this turn, draw a card.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/246/thoughtweft-lieutenant?utm_source=api",
"name": "Thoughtweft Lieutenant",
"mana_cost": "{G}{W}",
"cmc": 2.0,
"colors": [
"G",
"W"
],
"color_identity": [
"G",
"W"
],
"type_line": "Creature \u2014 Kithkin Soldier",
"oracle_text": "Whenever this creature or another Kithkin you control enters, target creature you control gets +1/+1 and gains trample until end of turn.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/16/flock-impostor?utm_source=api",
"name": "Flock Impostor",
"mana_cost": "{2}{W}",
"cmc": 3.0,
"colors": [
"W"
],
"color_identity": [
"W"
],
"type_line": "Creature \u2014 Shapeshifter",
"oracle_text": "Changeling (This card is every creature type.)\nFlash\nFlying\nWhen this creature enters, return up to one other target creature you control to its owner's hand.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/168/bristlebane-battler?utm_source=api",
"name": "Bristlebane Battler",
"mana_cost": "{1}{G}",
"cmc": 2.0,
"colors": [
"G"
],
"color_identity": [
"G"
],
"type_line": "Creature \u2014 Kithkin Soldier",
"oracle_text": "Trample, ward {2}\nThis creature enters with five -1/-1 counters on it.\nWhenever another creature you control enters while this creature has a -1/-1 counter on it, remove a -1/-1 counter from this creature.",
"power": "6",
"toughness": "6",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/161/sting-slinger?utm_source=api",
"name": "Sting-Slinger",
"mana_cost": "{2}{R}",
"cmc": 3.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Creature \u2014 Goblin Warrior",
"oracle_text": "{1}{R}, {T}, Blight 1: This creature deals 2 damage to each opponent. (To blight 1, put a -1/-1 counter on a creature you control.)",
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/196/surly-farrier?utm_source=api",
"name": "Surly Farrier",
"mana_cost": "{1}{G}",
"cmc": 2.0,
"colors": [
"G"
],
"color_identity": [
"G"
],
"type_line": "Creature \u2014 Kithkin Citizen",
"oracle_text": "{T}: Target creature you control gets +1/+1 and gains vigilance until end of turn. Activate only as a sorcery.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/53/gravelgill-scoundrel?utm_source=api",
"name": "Gravelgill Scoundrel",
"mana_cost": "{1}{U}",
"cmc": 2.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Creature \u2014 Merfolk Rogue",
"oracle_text": "Vigilance\nWhenever this creature attacks, you may tap another untapped creature you control. If you do, this creature can't be blocked this turn.",
"power": "1",
"toughness": "3",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/62/omni-changeling?utm_source=api",
"name": "Omni-Changeling",
"mana_cost": "{3}{U}{U}",
"cmc": 5.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Creature \u2014 Shapeshifter",
"oracle_text": "Changeling (This card is every creature type.)\nConvoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nYou may have this creature enter as a copy of any creature on the battlefield, except it has changeling.",
"power": "0",
"toughness": "0",
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/230/hovel-hurler?utm_source=api",
"name": "Hovel Hurler",
"mana_cost": "{3}{R/W}{R/W}",
"cmc": 5.0,
"colors": [
"R",
"W"
],
"color_identity": [
"R",
"W"
],
"type_line": "Creature \u2014 Giant Warrior",
"oracle_text": "This creature enters with two -1/-1 counters on it.\n{R/W}{R/W}, Remove a counter from this creature: Another target creature you control gets +1/+0 and gains flying until end of turn. Activate only as a sorcery.",
"power": "6",
"toughness": "7",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/172/chomping-changeling?utm_source=api",
"name": "Chomping Changeling",
"mana_cost": "{2}{G}",
"cmc": 3.0,
"colors": [
"G"
],
"color_identity": [
"G"
],
"type_line": "Creature \u2014 Shapeshifter",
"oracle_text": "Changeling (This card is every creature type.)\nWhen this creature enters, destroy up to one target artifact or enchantment.",
"power": "1",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/32/rhys-the-evermore?utm_source=api",
"name": "Rhys, the Evermore",
"mana_cost": "{1}{W}",
"cmc": 2.0,
"colors": [
"W"
],
"color_identity": [
"W"
],
"type_line": "Legendary Creature \u2014 Elf Warrior",
"oracle_text": "Flash\nWhen Rhys enters, another target creature you control gains persist until end of turn. (When it dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it.)\n{W}, {T}: Remove any number of counters from target creature you control. Activate only as a sorcery.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/121/taster-of-wares?utm_source=api",
"name": "Taster of Wares",
"mana_cost": "{2}{B}",
"cmc": 3.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Creature \u2014 Goblin Warlock",
"oracle_text": "When this creature enters, target opponent reveals X cards from their hand, where X is the number of Goblins you control. You choose one of those cards. That player exiles it. If an instant or sorcery card is exiled this way, you may cast it for as long as you control this creature, and mana of any type can be spent to cast that spell.",
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/77/tanufel-rimespeaker?utm_source=api",
"name": "Tanufel Rimespeaker",
"mana_cost": "{3}{U}",
"cmc": 4.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Creature \u2014 Elemental Wizard",
"oracle_text": "Whenever you cast a spell with mana value 4 or greater, draw a card.",
"power": "2",
"toughness": "4",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/214/deepway-navigator?utm_source=api",
"name": "Deepway Navigator",
"mana_cost": "{W}{U}",
"cmc": 2.0,
"colors": [
"U",
"W"
],
"color_identity": [
"U",
"W"
],
"type_line": "Creature \u2014 Merfolk Wizard",
"oracle_text": "Flash\nWhen this creature enters, untap each other Merfolk you control.\nAs long as you attacked with three or more Merfolk this turn, Merfolk you control get +1/+0.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/108/iron-shield-elf?utm_source=api",
"name": "Iron-Shield Elf",
"mana_cost": "{1}{B}",
"cmc": 2.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Creature \u2014 Elf Warrior",
"oracle_text": "Discard a card: This creature gains indestructible until end of turn. Tap it. (Damage and effects that say \"destroy\" don't destroy it. If its toughness is 0 or less, it still dies.)",
"power": "3",
"toughness": "1",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/66/rimekin-recluse?utm_source=api",
"name": "Rimekin Recluse",
"mana_cost": "{2}{U}",
"cmc": 3.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Creature \u2014 Elemental Wizard",
"oracle_text": "When this creature enters, return up to one other target creature to its owner's hand.",
"power": "3",
"toughness": "2",
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/256/foraging-wickermaw?utm_source=api",
"name": "Foraging Wickermaw",
"mana_cost": "{2}",
"cmc": 2.0,
"colors": [],
"color_identity": [],
"type_line": "Artifact Creature \u2014 Scarecrow",
"oracle_text": "When this creature enters, surveil 1. (Look at the top card of your library. You may put it into your graveyard.)\n{1}: Add one mana of any color. This creature becomes that color until end of turn. Activate only once each turn.",
"power": "1",
"toughness": "3",
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/133/elder-auntie?utm_source=api",
"name": "Elder Auntie",
"mana_cost": "{2}{R}",
"cmc": 3.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Creature \u2014 Goblin Warlock",
"oracle_text": "When this creature enters, create a 1/1 black and red Goblin creature token.",
"power": "2",
"toughness": "2",
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/18/goldmeadow-nomad?utm_source=api",
"name": "Goldmeadow Nomad",
"mana_cost": "{W}",
"cmc": 1.0,
"colors": [
"W"
],
"color_identity": [
"W"
],
"type_line": "Creature \u2014 Kithkin Scout",
"oracle_text": "{W}, Exile this card from your graveyard: Create a 1/1 green and white Kithkin creature token. Activate only as a sorcery.",
"power": "1",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/112/mudbutton-cursetosser?utm_source=api",
"name": "Mudbutton Cursetosser",
"mana_cost": "{B}",
"cmc": 1.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Creature \u2014 Goblin Warlock",
"oracle_text": "As an additional cost to cast this spell, behold a Goblin or pay {2}. (To behold a Goblin, choose a Goblin you control or reveal a Goblin card from your hand.)\nThis creature can't block.\nWhen this creature dies, destroy target creature an opponent controls with power 2 or less.",
"power": "2",
"toughness": "1",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/69/silvergill-mentor?utm_source=api",
"name": "Silvergill Mentor",
"mana_cost": "{1}{U}",
"cmc": 2.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Creature \u2014 Merfolk Wizard",
"oracle_text": "As an additional cost to cast this spell, behold a Merfolk or pay {2}. (To behold a Merfolk, choose a Merfolk you control or reveal a Merfolk from your hand.)\nWhen this creature enters, create a 1/1 white and blue Merfolk creature token.",
"power": "2",
"toughness": "1",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/237/noggle-robber?utm_source=api",
"name": "Noggle Robber",
"mana_cost": "{1}{R/G}{R/G}",
"cmc": 3.0,
"colors": [
"G",
"R"
],
"color_identity": [
"G",
"R"
],
"type_line": "Creature \u2014 Noggle Rogue",
"oracle_text": "When this creature enters or dies, create a Treasure token. (It's an artifact with \"{T}, Sacrifice this token: Add one mana of any color.\")",
"power": "3",
"toughness": "3",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/226/gangly-stompling?utm_source=api",
"name": "Gangly Stompling",
"mana_cost": "{2}{R/G}",
"cmc": 3.0,
"colors": [
"G",
"R"
],
"color_identity": [
"G",
"R"
],
"type_line": "Creature \u2014 Shapeshifter",
"oracle_text": "Changeling (This card is every creature type.)\nTrample",
"power": "4",
"toughness": "2",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/204/abigale-eloquent-first-year?utm_source=api",
"name": "Abigale, Eloquent First-Year",
"mana_cost": "{W/B}{W/B}",
"cmc": 2.0,
"colors": [
"B",
"W"
],
"color_identity": [
"B",
"W"
],
"type_line": "Legendary Creature \u2014 Bird Bard",
"oracle_text": "Flying, first strike, lifelink\nWhen Abigale enters, up to one other target creature loses all abilities. Put a flying counter, a first strike counter, and a lifelink counter on that creature.",
"power": "1",
"toughness": "1",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmp/195/pallimud?utm_source=api",
"name": "Pallimud",
"mana_cost": "{2}{R}",
"cmc": 3.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Creature \u2014 Beast",
"oracle_text": "As this creature enters, choose an opponent.\nPallimud's power is equal to the number of tapped lands the chosen player controls.",
"power": "*",
"toughness": "3",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/8/burdened-stoneback?utm_source=api",
"name": "Burdened Stoneback",
"mana_cost": "{1}{W}",
"cmc": 2.0,
"colors": [
"W"
],
"color_identity": [
"W"
],
"type_line": "Creature \u2014 Giant Warrior",
"oracle_text": "This creature enters with two -1/-1 counters on it.\n{1}{W}, Remove a counter from this creature: Target creature gains indestructible until end of turn. Activate only as a sorcery. (Damage and effects that say \"destroy\" don't destroy it. If its toughness is 0 or less, it still dies.)",
"power": "4",
"toughness": "4",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/211/chitinous-graspling?utm_source=api",
"name": "Chitinous Graspling",
"mana_cost": "{3}{G/U}",
"cmc": 4.0,
"colors": [
"G",
"U"
],
"color_identity": [
"G",
"U"
],
"type_line": "Creature \u2014 Shapeshifter",
"oracle_text": "Changeling (This card is every creature type.)\nReach",
"power": "3",
"toughness": "4",
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/blc/356/flubs-the-fool?utm_source=api",
"name": "Flubs, the Fool",
"mana_cost": "{G}{U}{R}",
"cmc": 3.0,
"colors": [
"G",
"R",
"U"
],
"color_identity": [
"G",
"R",
"U"
],
"type_line": "Legendary Creature \u2014 Frog Scout",
"oracle_text": "You may play an additional land on each of your turns.\nWhenever you play a land or cast a spell, draw a card if you have no cards in hand. Otherwise, discard a card.",
"power": "0",
"toughness": "5",
"loyalty": null,
"count": 1
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 7
},
{
"scryfall_uri": "https://scryfall.com/card/fic/464/campsite-cuisine?utm_source=api",
@@ -251,7 +251,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/tdm/75/corroding-dragonstorm?utm_source=api",
@@ -377,7 +377,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/60/noggle-the-mind?utm_source=api",
@@ -539,7 +539,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/36/spiral-into-solitude?utm_source=api",
@@ -557,7 +557,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 6
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/15/evershrikes-gift?utm_source=api",
@@ -647,7 +647,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fin/31/sidequest:-catch-a-fish-cooking-campsite?utm_source=api",
@@ -702,7 +702,7 @@
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/blc/10/murmuration?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/blc/46/murmuration?utm_source=api",
"name": "Murmuration",
"mana_cost": "{4}{W}",
"cmc": 5.0,
@@ -792,7 +792,7 @@
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/dsk/155/the-rollercrusher-ride?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/dsk/298/the-rollercrusher-ride?utm_source=api",
"name": "The Rollercrusher Ride",
"mana_cost": "{X}{2}{R}",
"cmc": 3.0,
@@ -1118,7 +1118,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/11/champions-from-beyond?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/101/champions-from-beyond?utm_source=api",
"name": "Champions from Beyond",
"mana_cost": "{X}{W}{W}",
"cmc": 2.0,
@@ -1134,5 +1134,77 @@
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/187/pitiless-fists?utm_source=api",
"name": "Pitiless Fists",
"mana_cost": "{3}{G}",
"cmc": 4.0,
"colors": [
"G"
],
"color_identity": [
"G"
],
"type_line": "Enchantment \u2014 Aura",
"oracle_text": "Enchant creature you control\nWhen this Aura enters, enchanted creature fights up to one target creature an opponent controls. (Each deals damage equal to its power to the other.)\nEnchanted creature gets +2/+2.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/58/lofty-dreams?utm_source=api",
"name": "Lofty Dreams",
"mana_cost": "{3}{U}{U}",
"cmc": 5.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Enchantment \u2014 Aura",
"oracle_text": "Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nEnchant creature\nWhen this Aura enters, draw a card.\nEnchanted creature gets +2/+2 and has flying.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/149/lasting-tarfire?utm_source=api",
"name": "Lasting Tarfire",
"mana_cost": "{1}{R}",
"cmc": 2.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Enchantment",
"oracle_text": "At the beginning of each end step, if you put a counter on a creature this turn, this enchantment deals 2 damage to each opponent.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/185/morcants-eyes?utm_source=api",
"name": "Morcant's Eyes",
"mana_cost": "{1}{G}",
"cmc": 2.0,
"colors": [
"G"
],
"color_identity": [
"G"
],
"type_line": "Kindred Enchantment \u2014 Elf",
"oracle_text": "At the beginning of your upkeep, surveil 1. (Look at the top card of your library. You may put it into your graveyard.)\n{4}{G}{G}, Sacrifice this enchantment: Create X 2/2 black and green Elf creature tokens, where X is the number of Elf cards in your graveyard. Activate only as a sorcery.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
}
]

View File

@@ -91,7 +91,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/tla/187/origin-of-metalbending?utm_source=api",
@@ -181,7 +181,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fdn/223/giant-growth?utm_source=api",
@@ -289,10 +289,10 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/143/laughing-mad?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fin/585/laughing-mad?utm_source=api",
"name": "Laughing Mad",
"mana_cost": "{2}{R}",
"cmc": 3.0,
@@ -469,7 +469,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/eld/50/into-the-story?utm_source=api",
@@ -523,7 +523,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fdn/53/uncharted-voyage?utm_source=api",
@@ -739,7 +739,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/84/wild-unraveling?utm_source=api",
@@ -757,7 +757,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 6
},
{
"scryfall_uri": "https://scryfall.com/card/dsk/60/get-out?utm_source=api",
@@ -886,7 +886,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tla/33/razor-rings?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tle/272/razor-rings?utm_source=api",
"name": "Razor Rings",
"mana_cost": "{1}{W}",
"cmc": 2.0,
@@ -937,7 +937,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/tla/43/yip-yip!?utm_source=api",
@@ -1458,7 +1458,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fdn/710/negate?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmt/47/negate?utm_source=api",
"name": "Negate",
"mana_cost": "{1}{U}",
"cmc": 2.0,
@@ -2194,7 +2194,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fic/52/transpose?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fic/119/transpose?utm_source=api",
"name": "Transpose",
"mana_cost": "{2}{B}",
"cmc": 3.0,
@@ -2248,5 +2248,187 @@
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/64/rime-chill?utm_source=api",
"name": "Rime Chill",
"mana_cost": "{6}{U}",
"cmc": 7.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Instant",
"oracle_text": "Vivid \u2014 This spell costs {1} less to cast for each color among permanents you control.\nTap up to two target creatures. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.)\nDraw a card.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/152/reckless-ransacking?utm_source=api",
"name": "Reckless Ransacking",
"mana_cost": "{1}{R}",
"cmc": 2.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Instant",
"oracle_text": "Target creature gets +3/+2 until end of turn. Create a Treasure token. (It's an artifact with \"{T}, Sacrifice this token: Add one mana of any color.\")",
"power": null,
"toughness": null,
"loyalty": null,
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/116/requiting-hex?utm_source=api",
"name": "Requiting Hex",
"mana_cost": "{B}",
"cmc": 1.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Instant",
"oracle_text": "As an additional cost to cast this spell, you may blight 1. (You may put a -1/-1 counter on a creature you control.)\nDestroy target creature with mana value 2 or less. If this spell's additional cost was paid, you gain 2 life.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/205/ashlings-command?utm_source=api",
"name": "Ashling's Command",
"mana_cost": "{3}{U}{R}",
"cmc": 5.0,
"colors": [
"R",
"U"
],
"color_identity": [
"R",
"U"
],
"type_line": "Kindred Instant \u2014 Elemental",
"oracle_text": "Choose two \u2014\n\u2022 Create a token that's a copy of target Elemental you control.\n\u2022 Target player draws two cards.\n\u2022 Ashling's Command deals 2 damage to each creature target player controls.\n\u2022 Target player creates two Treasure tokens.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/167/blossoming-defense?utm_source=api",
"name": "Blossoming Defense",
"mana_cost": "{G}",
"cmc": 1.0,
"colors": [
"G"
],
"color_identity": [
"G"
],
"type_line": "Instant",
"oracle_text": "Target creature you control gets +2/+2 and gains hexproof until end of turn. (It can't be the target of spells or abilities your opponents control.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/5/appeal-to-eirdu?utm_source=api",
"name": "Appeal to Eirdu",
"mana_cost": "{3}{W}",
"cmc": 4.0,
"colors": [
"W"
],
"color_identity": [
"W"
],
"type_line": "Instant",
"oracle_text": "Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nOne or two target creatures each get +2/+1 until end of turn.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/141/giantfall?utm_source=api",
"name": "Giantfall",
"mana_cost": "{1}{R}",
"cmc": 2.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Instant",
"oracle_text": "Choose one \u2014\n\u2022 Target creature you control deals damage equal to its power to target creature an opponent controls.\n\u2022 Destroy target artifact.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/89/blight-rot?utm_source=api",
"name": "Blight Rot",
"mana_cost": "{2}{B}",
"cmc": 3.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Instant",
"oracle_text": "Put four -1/-1 counters on target creature.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/spm/46/spider-sense?utm_source=api",
"name": "Spider-Sense",
"mana_cost": "{1}{U}",
"cmc": 2.0,
"colors": [
"U"
],
"color_identity": [
"U"
],
"type_line": "Instant",
"oracle_text": "Web-slinging {U} (You may cast this spell for {U} if you also return a tapped creature you control to its owner's hand.)\nCounter target instant spell, sorcery spell, or triggered ability.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/stx/68/crushing-disappointment?utm_source=api",
"name": "Crushing Disappointment",
"mana_cost": "{3}{B}",
"cmc": 4.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Instant",
"oracle_text": "Each player loses 2 life. You draw two cards.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
}
]

View File

@@ -81,7 +81,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/mh3/231/tranquil-landscape?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/mh3/520/tranquil-landscape?utm_source=api",
"name": "Tranquil Landscape",
"mana_cost": "",
"cmc": 0.0,
@@ -116,7 +116,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tdc/371/hinterland-harbor?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/69/hinterland-harbor?utm_source=api",
"name": "Hinterland Harbor",
"mana_cost": "",
"cmc": 0.0,
@@ -166,22 +166,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/269/plains?utm_source=api",
"name": "Plains",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"W"
],
"type_line": "Basic Land \u2014 Plains",
"oracle_text": "({T}: Add {W}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/fin/292/windurst-federation-center?utm_source=api",
"name": "Windurst, Federation Center",
@@ -247,22 +231,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api",
"name": "Forest",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"G"
],
"type_line": "Basic Land \u2014 Forest",
"oracle_text": "({T}: Add {G}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/166/seaside-citadel?utm_source=api",
"name": "Seaside Citadel",
@@ -282,7 +250,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/148/exotic-orchard?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/66/exotic-orchard?utm_source=api",
"name": "Exotic Orchard",
"mana_cost": "",
"cmc": 0.0,
@@ -293,10 +261,10 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/264/evolving-wilds?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/65/evolving-wilds?utm_source=api",
"name": "Evolving Wilds",
"mana_cost": "",
"cmc": 0.0,
@@ -307,10 +275,10 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/158/path-of-ancestry?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/70/path-of-ancestry?utm_source=api",
"name": "Path of Ancestry",
"mana_cost": "",
"cmc": 0.0,
@@ -321,7 +289,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/fin/288/sharlayan-nation-of-scholars?utm_source=api",
@@ -388,22 +356,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/269/plains?utm_source=api",
"name": "Plains",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"W"
],
"type_line": "Basic Land \u2014 Plains",
"oracle_text": "({T}: Add {W}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/tdc/413/yavimaya-coast?utm_source=api",
"name": "Yavimaya Coast",
@@ -439,23 +391,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api",
"name": "Forest",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"G"
],
"type_line": "Basic Land \u2014 Forest",
"oracle_text": "({T}: Add {G}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/59/command-tower?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/63/command-tower?utm_source=api",
"name": "Command Tower",
"mana_cost": "",
"cmc": 0.0,
@@ -468,38 +404,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/269/plains?utm_source=api",
"name": "Plains",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"W"
],
"type_line": "Basic Land \u2014 Plains",
"oracle_text": "({T}: Add {W}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/192/island?utm_source=api",
"name": "Island",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"U"
],
"type_line": "Basic Land \u2014 Island",
"oracle_text": "({T}: Add {U}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 5
},
{
"scryfall_uri": "https://scryfall.com/card/tdc/343/canopy-vista?utm_source=api",
"name": "Canopy Vista",
@@ -517,70 +421,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api",
"name": "Forest",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"G"
],
"type_line": "Basic Land \u2014 Forest",
"oracle_text": "({T}: Add {G}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/269/plains?utm_source=api",
"name": "Plains",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"W"
],
"type_line": "Basic Land \u2014 Plains",
"oracle_text": "({T}: Add {W}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/194/mountain?utm_source=api",
"name": "Mountain",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"R"
],
"type_line": "Basic Land \u2014 Mountain",
"oracle_text": "({T}: Add {R}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/192/island?utm_source=api",
"name": "Island",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"U"
],
"type_line": "Basic Land \u2014 Island",
"oracle_text": "({T}: Add {U}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 5
},
{
"scryfall_uri": "https://scryfall.com/card/eoc/184/sulfur-falls?utm_source=api",
"name": "Sulfur Falls",
@@ -599,7 +439,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/59/command-tower?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/63/command-tower?utm_source=api",
"name": "Command Tower",
"mana_cost": "",
"cmc": 0.0,
@@ -663,102 +503,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/194/mountain?utm_source=api",
"name": "Mountain",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"R"
],
"type_line": "Basic Land \u2014 Mountain",
"oracle_text": "({T}: Add {R}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/192/island?utm_source=api",
"name": "Island",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"U"
],
"type_line": "Basic Land \u2014 Island",
"oracle_text": "({T}: Add {U}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 5
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/194/mountain?utm_source=api",
"name": "Mountain",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"R"
],
"type_line": "Basic Land \u2014 Mountain",
"oracle_text": "({T}: Add {R}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/192/island?utm_source=api",
"name": "Island",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"U"
],
"type_line": "Basic Land \u2014 Island",
"oracle_text": "({T}: Add {U}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 5
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/194/mountain?utm_source=api",
"name": "Mountain",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"R"
],
"type_line": "Basic Land \u2014 Mountain",
"oracle_text": "({T}: Add {R}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/192/island?utm_source=api",
"name": "Island",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"U"
],
"type_line": "Basic Land \u2014 Island",
"oracle_text": "({T}: Add {U}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 5
},
{
"scryfall_uri": "https://scryfall.com/card/eoc/190/viridescent-bog?utm_source=api",
"name": "Viridescent Bog",
@@ -794,7 +538,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/59/command-tower?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/63/command-tower?utm_source=api",
"name": "Command Tower",
"mana_cost": "",
"cmc": 0.0,
@@ -1053,71 +797,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api",
"name": "Forest",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"G"
],
"type_line": "Basic Land \u2014 Forest",
"oracle_text": "({T}: Add {G}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/193/swamp?utm_source=api",
"name": "Swamp",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"B"
],
"type_line": "Basic Land \u2014 Swamp",
"oracle_text": "({T}: Add {B}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api",
"name": "Forest",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"G"
],
"type_line": "Basic Land \u2014 Forest",
"oracle_text": "({T}: Add {G}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/193/swamp?utm_source=api",
"name": "Swamp",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"B"
],
"type_line": "Basic Land \u2014 Swamp",
"oracle_text": "({T}: Add {B}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/148/exotic-orchard?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/66/exotic-orchard?utm_source=api",
"name": "Exotic Orchard",
"mana_cost": "",
"cmc": 0.0,
@@ -1214,7 +894,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/264/evolving-wilds?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/65/evolving-wilds?utm_source=api",
"name": "Evolving Wilds",
"mana_cost": "",
"cmc": 0.0,
@@ -1244,22 +924,6 @@
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/193/swamp?utm_source=api",
"name": "Swamp",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"B"
],
"type_line": "Basic Land \u2014 Swamp",
"oracle_text": "({T}: Add {B}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/otc/316/scavenger-grounds?utm_source=api",
"name": "Scavenger Grounds",
@@ -1292,23 +956,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmt/193/swamp?utm_source=api",
"name": "Swamp",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [
"B"
],
"type_line": "Basic Land \u2014 Swamp",
"oracle_text": "({T}: Add {B}.)",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tdc/339/ash-barrens?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/60/ash-barrens?utm_source=api",
"name": "Ash Barrens",
"mana_cost": "",
"cmc": 0.0,
@@ -1387,7 +1035,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tdc/398/sunken-hollow?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/76/sunken-hollow?utm_source=api",
"name": "Sunken Hollow",
"mana_cost": "",
"cmc": 0.0,
@@ -1438,7 +1086,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/158/path-of-ancestry?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/70/path-of-ancestry?utm_source=api",
"name": "Path of Ancestry",
"mana_cost": "",
"cmc": 0.0,
@@ -1563,5 +1211,19 @@
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/tmc/65/evolving-wilds?utm_source=api",
"name": "Evolving Wilds",
"mana_cost": "",
"cmc": 0.0,
"colors": [],
"color_identity": [],
"type_line": "Land",
"oracle_text": "{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 3
}
]

512
output/hydrated/other.json Normal file
View File

@@ -0,0 +1,512 @@
[
{
"name": "Giant Koi (TLA) 53",
"error": "not found",
"count": 1
},
{
"name": "Gaelicat (FIN) 22",
"error": "not found",
"count": 3
},
{
"name": "Tataru Taru (FIC) 30",
"error": "not found",
"count": 1
},
{
"name": "Coeurl (FIN) 12",
"error": "not found",
"count": 4
},
{
"name": "Ice Flan (FIN) 55",
"error": "not found",
"count": 2
},
{
"name": "Sahagin (FIN) 71",
"error": "not found",
"count": 3
},
{
"name": "G'raha Tia (FIN) 21",
"error": "not found",
"count": 2
},
{
"name": "Hog-Monkey (TLA) 104",
"error": "not found",
"count": 1
},
{
"name": "Malboro (FIN) 106",
"error": "not found",
"count": 2
},
{
"name": "Ahriman (FIN) 87",
"error": "not found",
"count": 3
},
{
"name": "Wolfbat (TLA) 122",
"error": "not found",
"count": 2
},
{
"name": "Hecteyes (FIN) 103",
"error": "not found",
"count": 5
},
{
"name": "Sandworm (FIN) 155",
"error": "not found",
"count": 1
},
{
"name": "Boar-q-pine (TLA) 124",
"error": "not found",
"count": 1
},
{
"name": "Hill Gigas (FIN) 141",
"error": "not found",
"count": 2
},
{
"name": "Cactuar (FIN) 177",
"error": "not found",
"count": 1
},
{
"name": "Gigantoad (FIN) 187",
"error": "not found",
"count": 6
},
{
"name": "Vibrance (ECL) 249",
"error": "not found",
"count": 1
},
{
"name": "Uncle Iroh (TLA) 248",
"error": "not found",
"count": 1
},
{
"name": "Locke Cole (FIN) 234",
"error": "not found",
"count": 1
},
{
"name": "Catharsis (ECL) 209",
"error": "not found",
"count": 2
},
{
"name": "Lyse Hext (FIC) 88",
"error": "not found",
"count": 1
},
{
"name": "Spider-Bot (SPM) 173",
"error": "not found",
"count": 1
},
{
"name": "Iron Giant (FIN) 260",
"error": "not found",
"count": 2
},
{
"name": "Magic Pot (FIN) 263",
"error": "not found",
"count": 7
},
{
"name": "Puca's Eye (ECL) 259",
"error": "not found",
"count": 1
},
{
"name": "Sol Ring (M3C) 305",
"error": "not found",
"count": 3
},
{
"name": "Blitzball (FIN) 254",
"error": "not found",
"count": 3
},
{
"name": "World Map (FIN) 270",
"error": "not found",
"count": 3
},
{
"name": "Monk's Fist (FIN) 265",
"error": "not found",
"count": 1
},
{
"name": "Bard's Bow (FIN) 174",
"error": "not found",
"count": 3
},
{
"name": "Wall Crawl (SPM) 121",
"error": "not found",
"count": 1
},
{
"name": "Kinbinding (ECL) 407 *F*",
"error": "not found",
"count": 1
},
{
"name": "Lie in Wait (TDM) 203",
"error": "not found",
"count": 1
},
{
"name": "Choco-Comet (FIN) 132",
"error": "not found",
"count": 1
},
{
"name": "Cut a Deal (FIC) 238",
"error": "not found",
"count": 1
},
{
"name": "Suplex (FIN) 164",
"error": "not found",
"count": 3
},
{
"name": "Tweeze (ECL) 162",
"error": "not found",
"count": 2
},
{
"name": "Fire Magic (FIN) 136",
"error": "not found",
"count": 2
},
{
"name": "Murder (DSK) 110",
"error": "not found",
"count": 1
},
{
"name": "Swat Away (ECL) 75",
"error": "not found",
"count": 1
},
{
"name": "Whoosh! (SPM) 48",
"error": "not found",
"count": 1
},
{
"name": "Get Out (DSK) 60",
"error": "not found",
"count": 1
},
{
"name": "Keep Out (ECL) 19",
"error": "not found",
"count": 1
},
{
"name": "Yip Yip! (TLA) 43",
"error": "not found",
"count": 1
},
{
"name": "Fight On! (FIN) 100",
"error": "not found",
"count": 3
},
{
"name": "Lost Days (TLA) 62",
"error": "not found",
"count": 2
},
{
"name": "Eject (FIN) 52",
"error": "not found",
"count": 2
},
{
"name": "Ice Magic (FIN) 56",
"error": "not found",
"count": 3
},
{
"name": "Farseek (FCA) 45",
"error": "not found",
"count": 1
},
{
"name": "Negate (BBD) 123",
"error": "not found",
"count": 1
},
{
"name": "Cultivate (PW23) 6 *F*",
"error": "not found",
"count": 1
},
{
"name": "Plains (FIN) 295",
"error": "not found",
"count": 2
},
{
"name": "Forest (FIN) 307",
"error": "not found",
"count": 1
},
{
"name": "Brushland (FIC) 377",
"error": "not found",
"count": 1
},
{
"name": "Dusk // Dawn (DRC) 65",
"error": "not found",
"count": 1
},
{
"name": "Plains (FIN) 294",
"error": "not found",
"count": 1
},
{
"name": "Forest (FIN) 306",
"error": "not found",
"count": 1
},
{
"name": "Plains (FIC) 478 *F*",
"error": "not found",
"count": 3
},
{
"name": "Island (FIC) 479 *F*",
"error": "not found",
"count": 4
},
{
"name": "Forest (FIC) 482 *F*",
"error": "not found",
"count": 2
},
{
"name": "Plains (FIN) 296",
"error": "not found",
"count": 3
},
{
"name": "Mountain (FIC) 481 *F*",
"error": "not found",
"count": 2
},
{
"name": "Big Score (TDC) 206",
"error": "not found",
"count": 1
},
{
"name": "Island (TLA) 293",
"error": "not found",
"count": 1
},
{
"name": "Snort (FIC) 58",
"error": "not found",
"count": 1
},
{
"name": "Aetherize (FDN) 151",
"error": "not found",
"count": 1
},
{
"name": "Consider (TDC) 148",
"error": "not found",
"count": 1
},
{
"name": "Ether (FIN) 53",
"error": "not found",
"count": 1
},
{
"name": "Rewind (M21) 63",
"error": "not found",
"count": 2
},
{
"name": "Mountain (FIN) 304",
"error": "not found",
"count": 2
},
{
"name": "Island (FIN) 297",
"error": "not found",
"count": 6
},
{
"name": "Mountain (FIN) 305",
"error": "not found",
"count": 4
},
{
"name": "Island (FIN) 298",
"error": "not found",
"count": 6
},
{
"name": "Mountain (FIN) 303",
"error": "not found",
"count": 4
},
{
"name": "Island (FIN) 299",
"error": "not found",
"count": 5
},
{
"name": "Bojuka Bog (EOC) 149",
"error": "not found",
"count": 1
},
{
"name": "Saw in Half (BLC) 113",
"error": "not found",
"count": 1
},
{
"name": "Putrefy (BLC) 257",
"error": "not found",
"count": 1
},
{
"name": "Cache Grab (BLB) 167",
"error": "not found",
"count": 1
},
{
"name": "Swarmyard (BLC) 133",
"error": "not found",
"count": 1
},
{
"name": "Sol Ring (BLC) 129",
"error": "not found",
"count": 1
},
{
"name": "Forest (BLB) 377",
"error": "not found",
"count": 5
},
{
"name": "Swamp (BLB) 374",
"error": "not found",
"count": 4
},
{
"name": "Forest (BLB) 378",
"error": "not found",
"count": 4
},
{
"name": "Swamp (BLB) 373",
"error": "not found",
"count": 3
},
{
"name": "Ultima (FIN) 38",
"error": "not found",
"count": 1
},
{
"name": "Void Rend (FIC) 331",
"error": "not found",
"count": 1
},
{
"name": "Vindicate (FIC) 330",
"error": "not found",
"count": 1
},
{
"name": "Overkill (FIN) 109",
"error": "not found",
"count": 1
},
{
"name": "Swamp (FIC) 480 *F*",
"error": "not found",
"count": 2
},
{
"name": "Urza's Saga (MH2) 259",
"error": "not found",
"count": 1
},
{
"name": "Swamp (FIN) 301",
"error": "not found",
"count": 1
},
{
"name": "Vote Out (EOE) 126",
"error": "not found",
"count": 1
},
{
"name": "Sol Ring (FIC) 359",
"error": "not found",
"count": 1
},
{
"name": "Snuff Out (FIC) 285",
"error": "not found",
"count": 1
},
{
"name": "Port Town (FIC) 412",
"error": "not found",
"count": 1
},
{
"name": "Forest (ECL) 273",
"error": "not found",
"count": 1
},
{
"name": "Giantfall (ECL) 141",
"error": "not found",
"count": 2
},
{
"name": "Swamp (ECL) 271",
"error": "not found",
"count": 1
},
{
"name": "Pallimud (TMP) 195",
"error": "not found",
"count": 1
},
{
"name": "Goatnap (ECL) 142",
"error": "not found",
"count": 1
}
]

View File

@@ -484,7 +484,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/fin/92/circle-of-power?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/fin/583/circle-of-power?utm_source=api",
"name": "Circle of Power",
"mana_cost": "{3}{B}",
"cmc": 4.0,
@@ -589,7 +589,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 4
},
{
"scryfall_uri": "https://scryfall.com/card/dsc/155/reanimate?utm_source=api",
@@ -625,7 +625,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
"count": 2
},
{
"scryfall_uri": "https://scryfall.com/card/tla/80/waterbending-lesson?utm_source=api",
@@ -806,7 +806,7 @@
"power": null,
"toughness": null,
"loyalty": null,
"count": 2
"count": 3
},
{
"scryfall_uri": "https://scryfall.com/card/spm/73/venoms-hunger?utm_source=api",
@@ -1115,7 +1115,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/103/cultivate?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/50/cultivate?utm_source=api",
"name": "Cultivate",
"mana_cost": "{2}{G}",
"cmc": 3.0,
@@ -1463,7 +1463,7 @@
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecc/89/blasphemous-act?utm_source=api",
"scryfall_uri": "https://scryfall.com/card/tmc/47/blasphemous-act?utm_source=api",
"name": "Blasphemous Act",
"mana_cost": "{8}{R}",
"cmc": 9.0,
@@ -1917,5 +1917,41 @@
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/115/perfect-intimidation?utm_source=api",
"name": "Perfect Intimidation",
"mana_cost": "{3}{B}",
"cmc": 4.0,
"colors": [
"B"
],
"color_identity": [
"B"
],
"type_line": "Sorcery",
"oracle_text": "Choose one or both \u2014\n\u2022 Target opponent exiles two cards from their hand.\n\u2022 Remove all counters from target creature.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
},
{
"scryfall_uri": "https://scryfall.com/card/ecl/142/goatnap?utm_source=api",
"name": "Goatnap",
"mana_cost": "{2}{R}",
"cmc": 3.0,
"colors": [
"R"
],
"color_identity": [
"R"
],
"type_line": "Sorcery",
"oracle_text": "Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. If that creature is a Goat, it also gets +3/+0 until end of turn.",
"power": null,
"toughness": null,
"loyalty": null,
"count": 1
}
]

410
scripts/build_deck.py Normal file
View File

@@ -0,0 +1,410 @@
#!/usr/bin/env python3
"""
Deck builder for MTG Commander decks from existing collections.
"""
import argparse
import json
import os
from collections import Counter
from typing import List, Dict, Optional
def load_collection(collection_path: str) -> List[dict]:
"""Load hydrated collection from JSON file."""
with open(collection_path, "r", encoding="utf-8") as f:
return json.load(f)
def load_multiple_collections(collection_paths: List[str]) -> List[dict]:
"""Load and merge multiple collection files."""
all_cards = []
for path in collection_paths:
cards = load_collection(path)
all_cards.extend(cards)
# Merge cards with same name by summing counts
merged_cards = {}
for card in all_cards:
name = card["name"]
if name in merged_cards:
merged_cards[name]["count"] += card.get("count", 1)
else:
merged_cards[name] = card.copy()
return list(merged_cards.values())
def analyze_collection(cards: List[dict]) -> Dict:
"""Analyze collection statistics."""
colors = Counter()
types = Counter()
cmc_dist = Counter()
for card in cards:
# Count colors
for color in card.get("color_identity", []):
colors[color] += card.get("count", 1)
# Count types
type_line = card.get("type_line", "")
if "Creature" in type_line:
types["creatures"] += card.get("count", 1)
elif "Instant" in type_line:
types["instants"] += card.get("count", 1)
elif "Sorcery" in type_line:
types["sorceries"] += card.get("count", 1)
elif "Artifact" in type_line:
types["artifacts"] += card.get("count", 1)
elif "Enchantment" in type_line:
types["enchantments"] += card.get("count", 1)
elif "Land" in type_line:
types["lands"] += card.get("count", 1)
# Count CMC
cmc = card.get("cmc")
if cmc:
cmc_dist[int(cmc)] += card.get("count", 1)
return {
"colors": dict(colors),
"types": dict(types),
"cmc_distribution": dict(cmc_dist),
"total_cards": sum(card.get("count", 1) for card in cards),
"unique_cards": len(cards),
}
def find_commanders(cards: List[dict]) -> List[dict]:
"""Find potential commander cards in collection."""
commanders = []
for card in cards:
type_line = card.get("type_line", "")
if "Legendary" in type_line and (
"Creature" in type_line or "Planeswalker" in type_line
):
commanders.append(card)
return commanders
def score_commander(commander: dict, collection_stats: Dict) -> float:
"""Score commander based on collection support."""
color_identity = commander.get("color_identity", [])
score = 0.0
# Score based on color support
total_colors = sum(collection_stats["colors"].values())
if total_colors > 0:
color_support = sum(
collection_stats["colors"].get(c, 0) for c in color_identity
)
score += (color_support / total_colors) * 100
# Bonus for multi-color commanders with good support
if len(color_identity) > 1:
score += 10
return score
def suggest_commanders(cards: List[dict]) -> List[dict]:
"""Suggest top commanders from collection."""
commanders = find_commanders(cards)
if not commanders:
return []
stats = analyze_collection(cards)
scored = [(score_commander(cmd, stats), cmd) for cmd in commanders]
scored.sort(reverse=True, key=lambda x: x[0])
return [cmd for (score, cmd) in scored[:5]]
def is_basic_land(card: dict) -> bool:
"""Check if a card is a basic land."""
name = card.get("name", "")
return name in ["Plains", "Island", "Swamp", "Mountain", "Forest"]
def balance_mana_curve(cards: List[dict], target_slots: int) -> List[dict]:
"""Balance mana curve for non-land cards."""
# Group cards by CMC
cmc_groups = {}
for card in cards:
cmc = card.get("cmc", 0)
if cmc not in cmc_groups:
cmc_groups[cmc] = []
cmc_groups[cmc].append(card)
# Target distribution (CMC: target count)
# This follows typical Commander mana curve recommendations
target_dist = {
1: 8, # 1-drops
2: 12, # 2-drops
3: 10, # 3-drops
4: 8, # 4-drops
5: 6, # 5-drops
6: 4, # 6-drops
7: 2, # 7+ drops (combined)
}
selected_cards = []
remaining_slots = target_slots
# Distribute cards according to target curve
for cmc, target_count in sorted(target_dist.items()):
if cmc == 7:
# Handle 7+ CMC cards together
available_cards = []
for higher_cmc in sorted(cmc_groups.keys()):
if higher_cmc >= 7:
available_cards.extend(cmc_groups[higher_cmc])
else:
available_cards = cmc_groups.get(cmc, [])
# Calculate how many we can take from this CMC group
slots_for_cmc = min(target_count, len(available_cards), remaining_slots)
if slots_for_cmc > 0:
# Take the first N cards (already sorted by CMC)
selected_cards.extend(available_cards[:slots_for_cmc])
remaining_slots -= slots_for_cmc
if remaining_slots <= 0:
break
# If we still have slots, fill with remaining cards
if remaining_slots > 0:
# Get all remaining cards not yet selected
remaining_cards = []
for cmc, card_list in cmc_groups.items():
if cmc < 7: # We already handled 7+ above
remaining_cards.extend(
card_list[
sorted(target_dist.items()).index((cmc, target_dist[cmc]))
if cmc in target_dist
else 0 :
]
)
# Add remaining cards until we fill all slots
fill_cards = remaining_cards[:remaining_slots]
selected_cards.extend(fill_cards)
return selected_cards
def select_lands(
valid_cards: List[dict], color_identity: List[str], target_land_count: int = 38
) -> List[dict]:
"""Select lands with priority for basic lands and color fixing."""
basic_lands = []
color_fixing_lands = []
other_lands = []
# Categorize lands
for card in valid_cards:
if "Land" not in card.get("type_line", ""):
continue
if is_basic_land(card):
basic_lands.append(card)
elif any(
color in card.get("name", "").lower()
for color in ["plains", "island", "swamp", "mountain", "forest"]
):
# Dual lands, shock lands, etc.
color_fixing_lands.append(card)
else:
other_lands.append(card)
selected_lands = []
land_count = 0
# Add basic lands first
for land in basic_lands:
if land_count >= target_land_count:
break
# Check if this basic land produces a color in our identity
land_color = None
if land["name"] == "Plains":
land_color = "W"
elif land["name"] == "Island":
land_color = "U"
elif land["name"] == "Swamp":
land_color = "B"
elif land["name"] == "Mountain":
land_color = "R"
elif land["name"] == "Forest":
land_color = "G"
if land_color and land_color in color_identity:
count = min(land.get("count", 1), target_land_count - land_count)
if count > 0:
selected_lands.append(land)
land_count += count
# Add color-fixing lands
for land in color_fixing_lands:
if land_count >= target_land_count:
break
count = min(land.get("count", 1), target_land_count - land_count)
if count > 0:
selected_lands.append(land)
land_count += count
# Add other lands if needed
for land in other_lands:
if land_count >= target_land_count:
break
count = min(land.get("count", 1), target_land_count - land_count)
if count > 0:
selected_lands.append(land)
land_count += count
return selected_lands
def generate_deck(commander: dict, cards: List[dict]) -> Dict:
"""Generate a decklist based on chosen commander."""
color_identity = commander.get("color_identity", [])
deck_cards = []
# Add commander
deck_cards.append({"name": commander["name"], "count": 1, "data": commander})
# Filter cards matching color identity
valid_cards = []
for card in cards:
if card["name"] == commander["name"]:
continue # Skip commander in main deck
card_colors = card.get("color_identity", [])
# Check if card colors are subset of commander colors
if all(c in color_identity for c in card_colors):
valid_cards.append(card)
# Select lands using improved logic
selected_lands = select_lands(valid_cards, color_identity)
# Get non-land cards
land_names = {land["name"] for land in selected_lands}
non_land_cards = [c for c in valid_cards if c["name"] not in land_names]
# Balance mana curve for non-land cards (target 61 cards)
balanced_non_lands = balance_mana_curve(non_land_cards, 61)
# Add lands to deck
for land in selected_lands:
count = land.get("count", 1)
deck_cards.append({"name": land["name"], "count": count, "data": land})
# Add non-land cards to deck
for card in balanced_non_lands:
count = card.get("count", 1)
deck_cards.append({"name": card["name"], "count": count, "data": card})
return {
"name": f"{commander['name']} Deck",
"commander": commander["name"],
"colors": color_identity,
"archetype": "Auto-generated",
"cards": {card["name"]: card["count"] for card in deck_cards},
}
def save_deck(deck: Dict, output_dir: str = "data/decks") -> str:
"""Save deck to JSON file."""
os.makedirs(output_dir, exist_ok=True)
# Create safe filename
commander_name = (
deck["commander"].replace(" ", "_").replace(",", "").replace("'", "")
)
filename = f"{commander_name}.json"
filepath = os.path.join(output_dir, filename)
with open(filepath, "w", encoding="utf-8") as f:
json.dump(deck, f, indent=2)
return filepath
def main():
parser = argparse.ArgumentParser(
description="Build MTG Commander decks from collection"
)
parser.add_argument(
"--collection",
nargs="+",
required=True,
help="Path(s) to hydrated collection JSON file(s)",
)
parser.add_argument("--name", help="Deck name (defaults to commander name)")
parser.add_argument("--commander", help="Specific commander to use")
parser.add_argument(
"--list-commanders", action="store_true", help="List suggested commanders"
)
parser.add_argument(
"--output-dir", default="data/decks", help="Output directory for deck files"
)
args = parser.parse_args()
# Load collection(s)
if isinstance(args.collection, list) and len(args.collection) > 1:
cards = load_multiple_collections(args.collection)
else:
cards = load_collection(
args.collection[0] if isinstance(args.collection, list) else args.collection
)
if args.list_commanders:
commanders = suggest_commanders(cards)
print(f"Found {len(commanders)} potential commanders:")
for i, cmd in enumerate(commanders, 1):
colors = "".join(cmd.get("color_identity", []))
print(f"{i}. {cmd['name']} ({colors})")
return
# Find or select commander
if args.commander:
# Find specific commander
commanders = [c for c in cards if c["name"] == args.commander]
if not commanders:
print(f"Error: Commander '{args.commander}' not found in collection")
return
commander = commanders[0]
else:
# Suggest and use top commander
commanders = suggest_commanders(cards)
if not commanders:
print("Error: No suitable commanders found in collection")
return
commander = commanders[0]
print(f"Using suggested commander: {commander['name']}")
# Generate deck
deck = generate_deck(commander, cards)
# Customize deck name if provided
if args.name:
deck["name"] = args.name
# Save deck
filepath = save_deck(deck, args.output_dir)
print(f"Deck saved to: {filepath}")
print(f"\nDeck Summary:")
print(f" Name: {deck['name']}")
print(f" Commander: {deck['commander']}")
print(f" Colors: {', '.join(deck['colors'])}")
print(f" Total cards: {sum(deck['cards'].values())}")
print(f" Unique cards: {len(deck['cards'])}")
if __name__ == "__main__":
main()