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 | | Deck | Colors | Commander | Archetype |
|------|--------|-----------|-----------| |------|--------|-----------|-----------|
| my_choco_deck | BUW | G'raha Tia, Scion Reborn | Auto-generated |
| Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall | | Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall |
| Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats | | Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats |
| Palamecia | UR | The Emperor of Palamecia // The Lord Master of Hell | Izzet Self-Mill Storm | | 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 | | Deck | Colors | Commander | Archetype |
|------|--------|-----------|-----------| |------|--------|-----------|-----------|
| my_choco_deck | BUW | G'raha Tia, Scion Reborn | Auto-generated |
| Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall | | Choco | UGW | Choco, Seeker of Paradise | Bird Tribal Landfall |
| Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats | | Hazel | BG | Hazel of the Rootbloom | Golgari Aristocrats |
| Palamecia | UR | The Emperor of Palamecia // The Lord Master of Hell | Izzet Self-Mill Storm | | 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 ## Collection Stats
- **Total cards:** 1209 - **Total cards:** 1288
- **Unique cards:** 866 - **Unique cards:** 892
- **By type:** - **By type:**
- Artifacts: 84 - Artifacts: 86
- Creatures: 390 - Creatures: 419
- Enchantments: 63 - Enchantments: 67
- Instants: 124 - Instants: 134
- Lands: 97 - Lands: 76
- Planeswalkers: 2 - 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 Gravblade Heavy (EOE) 102
1 Buzzard-Wasp Colony (TLA) 88 1 Buzzard-Wasp Colony (TLA) 88
1 Soul-Shackled Zombie (FDN) 70 1 Soul-Shackled Zombie (FDN) 70
4 Dawnhand Eulogist (ECL) 99 5 Dawnhand Eulogist (ECL) 99
4 Dream Seizer (ECL) 101 4 Dream Seizer (ECL) 101
1 Nightmare Sower (ECL) 114 1 Nightmare Sower (ECL) 114
1 Vincent Valentine // Galian Beast (FIN) 125 1 Vincent Valentine // Galian Beast (FIN) 125
@@ -113,7 +113,7 @@
1 Hog-Monkey (TLA) 104 1 Hog-Monkey (TLA) 104
1 Pirate Peddlers (TLA) 115 1 Pirate Peddlers (TLA) 115
1 Moonglove Extractor (ECL) 109 1 Moonglove Extractor (ECL) 109
1 Heirloom Auntie (ECL) 107 2 Heirloom Auntie (ECL) 107
1 Gnarlbark Elm (ECL) 103 1 Gnarlbark Elm (ECL) 103
1 Retched Wretch (ECL) 117 1 Retched Wretch (ECL) 117
1 June, Bounty Hunter (TLA) 106 1 June, Bounty Hunter (TLA) 106
@@ -271,7 +271,7 @@
1 Serah Farron // Crystallized Serah (FIN) 240 1 Serah Farron // Crystallized Serah (FIN) 240
1 Wary Farmer (ECL) 251 1 Wary Farmer (ECL) 251
1 Garnet, Princess of Alexandria (FIN) 222 1 Garnet, Princess of Alexandria (FIN) 222
1 Catharsis (ECL) 209 2 Catharsis (ECL) 209
1 Cloud, Planet's Champion (FIN) 552 *F* 1 Cloud, Planet's Champion (FIN) 552 *F*
1 Sami, Ship's Engineer (EOE) 225 1 Sami, Ship's Engineer (EOE) 225
2 Feisty Spikeling (ECL) 223 2 Feisty Spikeling (ECL) 223
@@ -366,7 +366,7 @@
1 Disturbing Mirth (DSK) 212 1 Disturbing Mirth (DSK) 212
1 Eusocial Engineering (EOE) 181 1 Eusocial Engineering (EOE) 181
1 Wall Crawl (SPM) 121 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 Campsite Cuisine (FIC) 464
1 Triple Triad (FIN) 340 1 Triple Triad (FIN) 340
1 Breaching Dragonstorm (TDM) 101 1 Breaching Dragonstorm (TDM) 101
@@ -394,7 +394,7 @@
1 Kinbinding (ECL) 407 *F* 1 Kinbinding (ECL) 407 *F*
1 Southern Air Temple (TLA) 36 1 Southern Air Temple (TLA) 36
2 Clachan Festival (ECL) 10 2 Clachan Festival (ECL) 10
5 Spiral into Solitude (ECL) 36 6 Spiral into Solitude (ECL) 36
1 Evershrike's Gift (ECL) 15 1 Evershrike's Gift (ECL) 15
2 Kraven's Last Hunt (SPM) 105 2 Kraven's Last Hunt (SPM) 105
2 Prismatic Undercurrents (ECL) 189 2 Prismatic Undercurrents (ECL) 189
@@ -436,7 +436,7 @@
1 Extract a Confession (MKM) 84 1 Extract a Confession (MKM) 84
4 Bogslither's Embrace (ECL) 94 4 Bogslither's Embrace (ECL) 94
1 Reanimate (ARC) 21 1 Reanimate (ARC) 21
1 Wanderwine Farewell (ECL) 83 2 Wanderwine Farewell (ECL) 83
1 Waterbending Lesson (TLA) 80 1 Waterbending Lesson (TLA) 80
1 Lingering Souls (FIC) 245 1 Lingering Souls (FIC) 245
1 Cut a Deal (FIC) 238 1 Cut a Deal (FIC) 238
@@ -503,7 +503,7 @@
1 Accumulate Wisdom (TLA) 44 1 Accumulate Wisdom (TLA) 44
1 Whoosh! (SPM) 48 1 Whoosh! (SPM) 48
3 Run Away Together (ECL) 67 3 Run Away Together (ECL) 67
5 Wild Unraveling (ECL) 84 6 Wild Unraveling (ECL) 84
1 Get Out (DSK) 60 1 Get Out (DSK) 60
1 Long River's Pull (BLB) 58 1 Long River's Pull (BLB) 58
1 Stolen Uniform (FIN) 75 1 Stolen Uniform (FIN) 75
@@ -575,7 +575,6 @@
1 Patchwork Banner (BLB) 247 1 Patchwork Banner (BLB) 247
1 Kastral, the Windcrested (BLB) 221 1 Kastral, the Windcrested (BLB) 221
1 Restless Anchorage (LCI) 347 1 Restless Anchorage (LCI) 347
2 Plains (FIN) 295
1 Watcher of the Spheres (M21) 227 1 Watcher of the Spheres (M21) 227
1 Murmuration (BLC) 10 1 Murmuration (BLC) 10
1 Talisman of Progress (FIC) 367 1 Talisman of Progress (FIC) 367
@@ -584,7 +583,6 @@
1 Demolition Field (FDN) 687 1 Demolition Field (FDN) 687
1 Adarkar Wastes (EOC) 147 1 Adarkar Wastes (EOC) 147
1 Empyrean Eagle (FDN) 239 1 Empyrean Eagle (FDN) 239
1 Forest (FIN) 307
1 Seaside Citadel (FIC) 420 1 Seaside Citadel (FIC) 420
1 Warden of Evos Isle (CMR) 106 1 Warden of Evos Isle (CMR) 106
2 Exotic Orchard (BLC) 131 2 Exotic Orchard (BLC) 131
@@ -614,24 +612,17 @@
1 Dusk // Dawn (DRC) 65 1 Dusk // Dawn (DRC) 65
1 Nature's Lore (FIC) 311 1 Nature's Lore (FIC) 311
1 Terramorphic Expanse (TDC) 408 1 Terramorphic Expanse (TDC) 408
1 Plains (FIN) 294
1 Yavimaya Coast (TDC) 413 1 Yavimaya Coast (TDC) 413
1 Seaside Haven (ONS) 323 1 Seaside Haven (ONS) 323
1 Forest (FIN) 306
1 Gwaihir the Windlord (LTR) 210 1 Gwaihir the Windlord (LTR) 210
1 City Pigeon (SPM) 4 1 City Pigeon (SPM) 4
1 Command Tower (FIC) 484 1 Command Tower (FIC) 484
1 Sevinne's Reclamation (MH3) 267 1 Sevinne's Reclamation (MH3) 267
1 Kangee, Sky Warden (CMR) 283 1 Kangee, Sky Warden (CMR) 283
3 Plains (FIC) 478 *F*
1 Sidequest: Raise a Chocobo // Black Chocobo (FIN) 201 1 Sidequest: Raise a Chocobo // Black Chocobo (FIN) 201
1 Generous Gift (BLC) 106 1 Generous Gift (BLC) 106
4 Island (FIC) 479 *F*
1 Canopy Vista (FIC) 378 1 Canopy Vista (FIC) 378
2 Forest (FIC) 482 *F*
1 Choco, Seeker of Paradise (FIN) 215 1 Choco, Seeker of Paradise (FIN) 215
3 Plains (FIN) 296
2 Mountain (FIC) 481 *F*
1 Mana Geyser (TDC) 223 1 Mana Geyser (TDC) 223
1 Big Score (TDC) 206 1 Big Score (TDC) 206
1 Ovika, Enigma Goliath (ONE) 322 1 Ovika, Enigma Goliath (ONE) 322
@@ -644,7 +635,6 @@
1 Drown in Dreams (M3C) 181 1 Drown in Dreams (M3C) 181
1 Vivi Ornitier (FIN) 248 1 Vivi Ornitier (FIN) 248
1 Ultros, Obnoxious Octopus (FIN) 83 1 Ultros, Obnoxious Octopus (FIN) 83
1 Island (TLA) 293
1 Decaying Time Loop (WHO) 80 1 Decaying Time Loop (WHO) 80
1 Tellah, Great Sage (FIN) 244 1 Tellah, Great Sage (FIN) 244
1 Traumatize (MAR) 15 1 Traumatize (MAR) 15
@@ -693,12 +683,6 @@
1 Peter Parker's Camera (SPM) 171 1 Peter Parker's Camera (SPM) 171
1 Pinnacle Monk // Mystic Peak (MH3) 246 1 Pinnacle Monk // Mystic Peak (MH3) 246
1 Cut Your Losses (SNC) 38 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 Garruk, Cursed Huntsman (BLC) 99
1 Moldervine Reclamation (BLC) 255 1 Moldervine Reclamation (BLC) 255
1 Chitterspitter (BLC) 211 1 Chitterspitter (BLC) 211
@@ -779,10 +763,6 @@
1 Bastion of Remembrance (TDC) 171 1 Bastion of Remembrance (TDC) 171
1 Grim Backwoods (DSC) 281 1 Grim Backwoods (DSC) 281
1 Hazel of the Rootbloom (BLC) 2 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 Exotic Orchard (FIC) 390
1 Ultima (FIN) 38 1 Ultima (FIN) 38
1 Darkwater Catacombs (FIC) 384 1 Darkwater Catacombs (FIC) 384
@@ -806,7 +786,6 @@
1 Rite of Replication (FIC) 270 1 Rite of Replication (FIC) 270
1 Underground River (FIC) 439 1 Underground River (FIC) 439
1 Archaeomancer's Map (FIC) 230 1 Archaeomancer's Map (FIC) 230
2 Swamp (FIC) 480 *F*
1 Vanish from Sight (DSK) 82 1 Vanish from Sight (DSK) 82
1 Scavenger Grounds (FIC) 419 1 Scavenger Grounds (FIC) 419
1 Fetid Heath (FIC) 391 1 Fetid Heath (FIC) 391
@@ -814,7 +793,6 @@
1 Brainstorm (FCA) 28 1 Brainstorm (FCA) 28
1 Urza's Saga (MH2) 259 1 Urza's Saga (MH2) 259
1 Krile Baldesion (FIC) 86 1 Krile Baldesion (FIC) 86
1 Swamp (FIN) 301
1 Amazing Acrobatics (SPM) 25 1 Amazing Acrobatics (SPM) 25
1 Ash Barrens (FIC) 374 1 Ash Barrens (FIC) 374
1 Riverwalk Technique (TDM) 54 1 Riverwalk Technique (TDM) 54
@@ -864,13 +842,12 @@
1 Cloud, Midgar Mercenary (FIN) 564 *F* 1 Cloud, Midgar Mercenary (FIN) 564 *F*
1 Wakka, Devoted Guardian (FIC) 477 *F* 1 Wakka, Devoted Guardian (FIC) 477 *F*
1 Elspeth, Storm Slayer (TDM) 11 1 Elspeth, Storm Slayer (TDM) 11
1 Forest (ECL) 273
2 Boneclub Berserker (ECL) 126 2 Boneclub Berserker (ECL) 126
1 Flitterwing Nuisance (ECL) 48 1 Flitterwing Nuisance (ECL) 48
1 Thoughtweft Lieutenant (ECL) 343 1 Thoughtweft Lieutenant (ECL) 343
1 Rime Chill (ECL) 64 1 Rime Chill (ECL) 64
1 Flock Impostor (ECL) 16 1 Flock Impostor (ECL) 16
2 Reckless Ransacking (ECL) 152 3 Reckless Ransacking (ECL) 152
1 Requiting Hex (ECL) 116 1 Requiting Hex (ECL) 116
1 Bristlebane Battler (ECL) 168 1 Bristlebane Battler (ECL) 168
2 Sting-Slinger (ECL) 161 2 Sting-Slinger (ECL) 161
@@ -895,7 +872,7 @@
3 Evolving Wilds (ECL) 264 3 Evolving Wilds (ECL) 264
2 Giantfall (ECL) 141 2 Giantfall (ECL) 141
2 Rimekin Recluse (ECL) 66 2 Rimekin Recluse (ECL) 66
3 Foraging Wickermaw (ECL) 256 4 Foraging Wickermaw (ECL) 256
2 Lasting Tarfire (ECL) 149 2 Lasting Tarfire (ECL) 149
2 Elder Auntie (ECL) 133 2 Elder Auntie (ECL) 133
1 Blight Rot (ECL) 89 1 Blight Rot (ECL) 89
@@ -906,5 +883,10 @@
1 Gangly Stompling (ECL) 226 1 Gangly Stompling (ECL) 226
1 Spider-Sense (SPM) 284 *F* 1 Spider-Sense (SPM) 284 *F*
1 Abigale, Eloquent First-Year (ECL) 204 1 Abigale, Eloquent First-Year (ECL) 204
1 Swamp (ECL) 271
1 Pallimud (TMP) 195 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 1 Steam Vents (ECL) 267
2 Champions of the Shoal (ECL) 46 2 Champions of the Shoal (ECL) 46
1 End-Blaze Epiphany (ECL) 134 1 End-Blaze Epiphany (ECL) 134
1 Dundoolin Weaver (ECL) 175 2 Dundoolin Weaver (ECL) 175
1 Boggart Cursecrafter (ECL) 206 1 Boggart Cursecrafter (ECL) 206
1 Mirrormind Crown (ECL) 258 1 Mirrormind Crown (ECL) 258
1 Celestial Reunion (ECL) 170 1 Celestial Reunion (ECL) 170
1 Morcant's Loyalist (ECL) 236 1 Morcant's Loyalist (ECL) 236
3 Assert Perfection (ECL) 164 4 Assert Perfection (ECL) 164
1 Dream Harvest (ECL) 216 1 Dream Harvest (ECL) 216
1 Eclipsed Kithkin (ECL) 220 2 Eclipsed Kithkin (ECL) 220
2 Sizzling Changeling (ECL) 155 4 Sizzling Changeling (ECL) 155
3 Wanderbrine Preacher (ECL) 41 4 Wanderbrine Preacher (ECL) 41
3 Summit Sentinel (ECL) 73 5 Summit Sentinel (ECL) 73
3 Mistmeadow Council (ECL) 183 3 Mistmeadow Council (ECL) 183
2 Burning Curiosity (ECL) 129 4 Burning Curiosity (ECL) 129
1 Goliath Daydreamer (ECL) 143 1 Goliath Daydreamer (ECL) 143
1 Nameless Inversion (ECL) 113 3 Nameless Inversion (ECL) 113
1 Lavaleaper (ECL) 318 1 Lavaleaper (ECL) 318
1 Burdened Stoneback (ECL) 8 1 Burdened Stoneback (ECL) 8
2 Gathering Stone (ECL) 257 2 Gathering Stone (ECL) 257
1 Deepchannel Duelist (ECL) 213 1 Deepchannel Duelist (ECL) 213
3 Gangly Stompling (ECL) 226 3 Gangly Stompling (ECL) 226
2 Merrow Skyswimmer (ECL) 234 3 Merrow Skyswimmer (ECL) 234
1 Appeal to Eirdu (ECL) 5 3 Appeal to Eirdu (ECL) 5
3 Elder Auntie (ECL) 133 4 Elder Auntie (ECL) 133
1 Sygg's Command (ECL) 244 1 Sygg's Command (ECL) 244
1 Pitiless Fists (ECL) 187 1 Pitiless Fists (ECL) 187
2 Encumbered Reejerey (ECL) 14 2 Encumbered Reejerey (ECL) 14
@@ -33,43 +33,43 @@
2 Blossombind (ECL) 45 2 Blossombind (ECL) 45
1 Trystan, Callous Cultivator // Trystan, Penitent Culler (ECL) 199 1 Trystan, Callous Cultivator // Trystan, Penitent Culler (ECL) 199
1 Glamer Gifter (ECL) 49 1 Glamer Gifter (ECL) 49
2 Keep Out (ECL) 19 3 Keep Out (ECL) 19
1 Barbed Bloodletter (ECL) 86 1 Barbed Bloodletter (ECL) 86
1 Sun-Dappled Celebrant (ECL) 37 1 Sun-Dappled Celebrant (ECL) 37
2 Wanderwine Distracter (ECL) 82 2 Wanderwine Distracter (ECL) 82
1 Lluwen, Imperfect Naturalist (ECL) 232 1 Lluwen, Imperfect Naturalist (ECL) 232
1 Deceit (ECL) 212 1 Deceit (ECL) 212
1 Iron-Shield Elf (ECL) 108 1 Iron-Shield Elf (ECL) 108
1 Illusion Spinners (ECL) 55 2 Illusion Spinners (ECL) 55
1 Kindle the Inner Flame (ECL) 147 2 Kindle the Inner Flame (ECL) 147
2 Mischievous Sneakling (ECL) 235 2 Mischievous Sneakling (ECL) 235
1 Stratosoarer (ECL) 72 2 Stratosoarer (ECL) 72
2 Run Away Together (ECL) 67 3 Run Away Together (ECL) 67
2 Shore Lurker (ECL) 34 3 Shore Lurker (ECL) 34
2 Reckless Ransacking (ECL) 152 3 Reckless Ransacking (ECL) 152
2 Dawnhand Eulogist (ECL) 99 2 Dawnhand Eulogist (ECL) 99
1 Evershrike's Gift (ECL) 15 2 Evershrike's Gift (ECL) 15
1 Dawnhand Dissident (ECL) 311 1 Dawnhand Dissident (ECL) 311
1 Bark of Doran (ECL) 6 1 Bark of Doran (ECL) 6
1 Gnarlbark Elm (ECL) 103 1 Gnarlbark Elm (ECL) 103
1 Luminollusk (ECL) 179 2 Luminollusk (ECL) 179
1 Flaring Cinder (ECL) 225 2 Flaring Cinder (ECL) 225
1 Dose of Dawnglow (ECL) 100 1 Dose of Dawnglow (ECL) 100
1 Ashling, Rekindled // Ashling, Rimebound (ECL) 124 1 Ashling, Rekindled // Ashling, Rimebound (ECL) 124
1 Morcant's Eyes (ECL) 185 1 Morcant's Eyes (ECL) 185
1 Wanderwine Farewell (ECL) 83 1 Wanderwine Farewell (ECL) 83
1 Pyrrhic Strike (ECL) 30 1 Pyrrhic Strike (ECL) 30
1 Chaos Spewer (ECL) 210 1 Chaos Spewer (ECL) 210
2 Blighted Blackthorn (ECL) 90 3 Blighted Blackthorn (ECL) 90
2 Great Forest Druid (ECL) 178 2 Great Forest Druid (ECL) 178
2 Thoughtweft Charge (ECL) 198 2 Thoughtweft Charge (ECL) 198
2 Unexpected Assistance (ECL) 80 3 Unexpected Assistance (ECL) 80
2 Safewright Cavalry (ECL) 191 3 Safewright Cavalry (ECL) 191
4 Bogslither's Embrace (ECL) 94 4 Bogslither's Embrace (ECL) 94
3 Riverguard's Reflexes (ECL) 33 3 Riverguard's Reflexes (ECL) 33
2 Unbury (ECL) 123 2 Unbury (ECL) 123
3 Midnight Tilling (ECL) 182 3 Midnight Tilling (ECL) 182
2 Flamekin Gildweaver (ECL) 140 3 Flamekin Gildweaver (ECL) 140
2 Stalactite Dagger (ECL) 261 2 Stalactite Dagger (ECL) 261
2 Cinder Strike (ECL) 131 2 Cinder Strike (ECL) 131
3 Lys Alana Informant (ECL) 181 3 Lys Alana Informant (ECL) 181
@@ -79,28 +79,28 @@
2 Puca's Eye (ECL) 259 2 Puca's Eye (ECL) 259
1 Loch Mare (ECL) 57 1 Loch Mare (ECL) 57
1 Sygg, Wanderwine Wisdom // Sygg, Wanderbrine Shield (ECL) 76 1 Sygg, Wanderwine Wisdom // Sygg, Wanderbrine Shield (ECL) 76
2 Moonglove Extractor (ECL) 109 4 Moonglove Extractor (ECL) 109
2 Wildvine Pummeler (ECL) 203 3 Wildvine Pummeler (ECL) 203
1 Aquitect's Defenses (ECL) 44 4 Aquitect's Defenses (ECL) 44
1 Gallant Fowlknight (ECL) 17 3 Gallant Fowlknight (ECL) 17
2 Wary Farmer (ECL) 251 4 Wary Farmer (ECL) 251
2 Reaping Willow (ECL) 240 2 Reaping Willow (ECL) 240
1 Moon-Vigil Adherents (ECL) 184 2 Moon-Vigil Adherents (ECL) 184
1 Dawn-Blessed Pennant (ECL) 254 1 Dawn-Blessed Pennant (ECL) 254
1 Unwelcome Sprite (ECL) 81 2 Unwelcome Sprite (ECL) 81
1 Glen Elendra Guardian (ECL) 51 1 Glen Elendra Guardian (ECL) 51
1 Thoughtweft Lieutenant (ECL) 246 1 Thoughtweft Lieutenant (ECL) 246
1 Meek Attack (ECL) 151 1 Meek Attack (ECL) 151
1 Hallowed Fountain (ECL) 265 1 Hallowed Fountain (ECL) 265
1 Vinebred Brawler (ECL) 201 2 Vinebred Brawler (ECL) 201
2 Personify (ECL) 28 2 Personify (ECL) 28
2 Rooftop Percher (ECL) 2 2 Rooftop Percher (ECL) 2
1 Blight Rot (ECL) 89 3 Blight Rot (ECL) 89
2 Liminal Hold (ECL) 24 3 Liminal Hold (ECL) 24
1 Kulrath Mystic (ECL) 56 3 Kulrath Mystic (ECL) 56
1 Crossroads Watcher (ECL) 173 3 Crossroads Watcher (ECL) 173
1 Flame-Chain Mauler (ECL) 138 3 Flame-Chain Mauler (ECL) 138
2 Scarblade's Malice (ECL) 119 4 Scarblade's Malice (ECL) 119
1 Bitterblossom (SPG) 133 1 Bitterblossom (SPG) 133
1 Rimefire Torque (ECL) 65 1 Rimefire Torque (ECL) 65
1 Shimmerwilds Growth (ECL) 194 1 Shimmerwilds Growth (ECL) 194
@@ -109,38 +109,38 @@
1 Shimmercreep (ECL) 120 1 Shimmercreep (ECL) 120
1 Flamebraider (ECL) 139 1 Flamebraider (ECL) 139
1 Tanufel Rimespeaker (ECL) 77 1 Tanufel Rimespeaker (ECL) 77
2 Glamermite (ECL) 50 3 Glamermite (ECL) 50
2 Heirloom Auntie (ECL) 107 3 Heirloom Auntie (ECL) 107
2 Gutsplitter Gang (ECL) 106 3 Gutsplitter Gang (ECL) 106
4 Chitinous Graspling (ECL) 211 5 Chitinous Graspling (ECL) 211
2 Sear (ECL) 154 2 Sear (ECL) 154
2 Clachan Festival (ECL) 10 2 Clachan Festival (ECL) 10
1 Oko, Lorwyn Liege // Oko, Shadowmoor Scion (ECL) 61 1 Oko, Lorwyn Liege // Oko, Shadowmoor Scion (ECL) 61
1 Selfless Safewright (ECL) 193 1 Selfless Safewright (ECL) 193
2 Creakwood Safewright (ECL) 96 2 Creakwood Safewright (ECL) 96
1 Noggle Robber (ECL) 237 2 Noggle Robber (ECL) 237
2 Brambleback Brute (ECL) 128 3 Brambleback Brute (ECL) 128
1 Dawn's Light Archer (ECL) 174 3 Dawn's Light Archer (ECL) 174
2 Feed the Flames (ECL) 137 3 Feed the Flames (ECL) 137
1 Auntie's Sentence (ECL) 85 3 Auntie's Sentence (ECL) 85
1 Kinscaer Sentry (ECL) 22 1 Kinscaer Sentry (ECL) 22
1 Spell Snare (ECL) 71 2 Spell Snare (ECL) 71
2 Virulent Emissary (ECL) 202 2 Virulent Emissary (ECL) 202
1 Explosive Prodigy (ECL) 136 1 Explosive Prodigy (ECL) 136
1 Foraging Wickermaw (ECL) 256 2 Foraging Wickermaw (ECL) 256
1 Silvergill Peddler (ECL) 70 3 Silvergill Peddler (ECL) 70
2 Goldmeadow Nomad (ECL) 18 3 Goldmeadow Nomad (ECL) 18
1 Harmonized Crescendo (ECL) 54 1 Harmonized Crescendo (ECL) 54
1 Shinestriker (ECL) 68 2 Shinestriker (ECL) 68
1 Chomping Changeling (ECL) 172 1 Chomping Changeling (ECL) 172
1 Retched Wretch (ECL) 117 2 Retched Wretch (ECL) 117
1 Soulbright Seeker (ECL) 157 1 Soulbright Seeker (ECL) 157
1 Stoic Grove-Guide (ECL) 243 2 Stoic Grove-Guide (ECL) 243
2 Surly Farrier (ECL) 196 3 Surly Farrier (ECL) 196
1 Scarblade Scout (ECL) 118 3 Scarblade Scout (ECL) 118
1 Timid Shieldbearer (ECL) 39 3 Timid Shieldbearer (ECL) 39
1 Unforgiving Aim (ECL) 200 4 Unforgiving Aim (ECL) 200
1 Kulrath Zealot (ECL) 148 3 Kulrath Zealot (ECL) 148
1 Shadow Urchin (ECL) 242 1 Shadow Urchin (ECL) 242
1 Rimekin Recluse (ECL) 66 1 Rimekin Recluse (ECL) 66
1 Sting-Slinger (ECL) 161 1 Sting-Slinger (ECL) 161
@@ -150,9 +150,55 @@
1 Champion of the Clachan (ECL) 9 1 Champion of the Clachan (ECL) 9
1 Blossoming Defense (ECL) 167 1 Blossoming Defense (ECL) 167
1 Hovel Hurler (ECL) 230 1 Hovel Hurler (ECL) 230
1 Tweeze (ECL) 162 2 Tweeze (ECL) 162
1 Spiral into Solitude (ECL) 36 1 Spiral into Solitude (ECL) 36
2 Gravelgill Scoundrel (ECL) 53 3 Gravelgill Scoundrel (ECL) 53
1 Twilight Diviner (ECL) 122 1 Twilight Diviner (ECL) 122
1 Warren Torchmaster (ECL) 163 2 Warren Torchmaster (ECL) 163
1 Noggle the Mind (ECL) 60 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 RATE_LIMIT_DELAY = 0.1 # 100ms between requests
FIELDS = [ FIELDS = [
"name", "mana_cost", "cmc", "colors", "color_identity", "name",
"type_line", "oracle_text", "power", "toughness", "loyalty" "mana_cost",
"cmc",
"colors",
"color_identity",
"type_line",
"oracle_text",
"power",
"toughness",
"loyalty",
] ]
@@ -36,30 +44,71 @@ def parse_decklist(filepath: str) -> list[dict]:
continue continue
match = re.match(r"^(\d+)x?\s+(.+)$", line, re.IGNORECASE) match = re.match(r"^(\d+)x?\s+(.+)$", line, re.IGNORECASE)
if match: 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 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.""" """Fetch card data from Scryfall API using fuzzy search."""
original_name = name
name = strip_set_code(name)
encoded = urllib.parse.quote(name) encoded = urllib.parse.quote(name)
url = f"{SCRYFALL_API}/cards/named?fuzzy={encoded}" url = f"{SCRYFALL_API}/cards/named?fuzzy={encoded}"
for attempt in range(retry_count):
try: try:
req = urllib.request.Request(url, headers={ req = urllib.request.Request(
"User-Agent": "EDHDeckBuilder/1.0", url, headers={"User-Agent": "EDHDeckBuilder/1.0", "Accept": "*/*"}
"Accept": "*/*" )
})
with urllib.request.urlopen(req, timeout=30) as response: with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode("utf-8")) return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
print(f" Error fetching '{name}': HTTP {e.code}", file=sys.stderr) 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 return None
except urllib.error.URLError as e: except urllib.error.URLError as e:
print(f" Error fetching '{name}': {e.reason}", file=sys.stderr) print(f" Error fetching '{original_name}': {e.reason}", file=sys.stderr)
return None return None
except json.JSONDecodeError: except json.JSONDecodeError:
print(f" Error parsing response for '{name}'", file=sys.stderr) print(f" Error parsing response for '{original_name}'", file=sys.stderr)
return None
return None return None
@@ -99,7 +148,9 @@ def categorize_by_type(cards: list[dict]) -> dict[str, list[dict]]:
for card in cards: for card in cards:
type_line = card.get("type_line", "").lower() 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"]: if not categories["commander"]:
categories["commander"].append(card) categories["commander"].append(card)
continue continue
@@ -124,7 +175,9 @@ def categorize_by_type(cards: list[dict]) -> dict[str, list[dict]]:
return {k: v for k, v in categories.items() if v} 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.""" """Main hydration function."""
cache = {} cache = {}
if cache_file and os.path.exists(cache_file): if cache_file and os.path.exists(cache_file):
@@ -166,10 +219,10 @@ def hydrate_decklist(input_file: str, output_dir: str, cache_file: Optional[str]
os.makedirs(output_dir, exist_ok=True) 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: with open(all_cards_path, "w", encoding="utf-8") as f:
json.dump(hydrated, f, indent=2) 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(): for category, cards in categories.items():
cat_path = os.path.join(output_dir, f"{category}.json") cat_path = os.path.join(output_dir, f"{category}.json")
@@ -187,11 +240,7 @@ def create_deck(deck_name: str, base_dir: str = "decks") -> str:
deck_path = os.path.join(base_dir, deck_name) deck_path = os.path.join(base_dir, deck_name)
os.makedirs(deck_path, exist_ok=True) os.makedirs(deck_path, exist_ok=True)
template = { template = {"name": deck_name, "commander": None, "cards": []}
"name": deck_name,
"commander": None,
"cards": []
}
with open(os.path.join(deck_path, "deck.json"), "w", encoding="utf-8") as f: with open(os.path.join(deck_path, "deck.json"), "w", encoding="utf-8") as f:
json.dump(template, f, indent=2) json.dump(template, f, indent=2)
@@ -201,17 +250,30 @@ def create_deck(deck_name: str, base_dir: str = "decks") -> str:
def main(): 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") 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("input", help="Input decklist file")
hydrate_parser.add_argument("-o", "--output", default="output/hydrated", help="Output directory") hydrate_parser.add_argument(
hydrate_parser.add_argument("-c", "--cache", default="cache/card_cache.json", help="Cache file for card data") "-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 = subparsers.add_parser("new", help="Create a new deck folder")
new_parser.add_argument("name", help="Deck name") 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() args = parser.parse_args()

View File

@@ -115,7 +115,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/259/pucas-eye?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/259/pucas-eye?utm_source=api",
@@ -177,7 +177,7 @@
"count": 1 "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", "name": "Sol Ring",
"mana_cost": "{1}", "mana_cost": "{1}",
"cmc": 1.0, "cmc": 1.0,
@@ -188,7 +188,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/eoc/137/everflowing-chalice?utm_source=api", "scryfall_uri": "https://scryfall.com/card/eoc/137/everflowing-chalice?utm_source=api",
@@ -326,7 +326,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fic/35/blue-mages-cane?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fic/35/blue-mages-cane?utm_source=api",
@@ -808,7 +808,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 2 "count": 4
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/64/the-prima-vista?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/64/the-prima-vista?utm_source=api",
@@ -1000,7 +1000,7 @@
"count": 2 "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", "name": "Arcane Signet",
"mana_cost": "{2}", "mana_cost": "{2}",
"cmc": 2.0, "cmc": 2.0,
@@ -1092,7 +1092,7 @@
"count": 1 "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", "name": "Sword of the Squeak",
"mana_cost": "{2}", "mana_cost": "{2}",
"cmc": 2.0, "cmc": 2.0,
@@ -1168,7 +1168,7 @@
"count": 1 "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", "name": "Arcane Signet",
"mana_cost": "{2}", "mana_cost": "{2}",
"cmc": 2.0, "cmc": 2.0,
@@ -1196,7 +1196,7 @@
"count": 1 "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", "name": "Sol Ring",
"mana_cost": "{1}", "mana_cost": "{1}",
"cmc": 1.0, "cmc": 1.0,
@@ -1228,7 +1228,7 @@
"count": 1 "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", "name": "Reaper's Scythe",
"mana_cost": "{2}{B}", "mana_cost": "{2}{B}",
"cmc": 3.0, "cmc": 3.0,
@@ -1246,7 +1246,7 @@
"count": 1 "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", "name": "Dancer's Chakrams",
"mana_cost": "{3}{W}", "mana_cost": "{3}{W}",
"cmc": 4.0, "cmc": 4.0,
@@ -1264,7 +1264,7 @@
"count": 1 "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", "name": "Sol Ring",
"mana_cost": "{1}", "mana_cost": "{1}",
"cmc": 1.0, "cmc": 1.0,
@@ -1321,5 +1321,37 @@
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "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", "power": "3",
"toughness": "5", "toughness": "5",
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fic/37/hraesvelgr-of-the-first-brood?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fic/37/hraesvelgr-of-the-first-brood?utm_source=api",
@@ -105,7 +105,7 @@
"power": "3", "power": "3",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/82/wanderwine-distracter?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/82/wanderwine-distracter?utm_source=api",
@@ -123,10 +123,10 @@
"power": "4", "power": "4",
"toughness": "3", "toughness": "3",
"loyalty": null, "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", "name": "Alphinaud Leveilleur",
"mana_cost": "{3}{U}", "mana_cost": "{3}{U}",
"cmc": 4.0, "cmc": 4.0,
@@ -162,7 +162,7 @@
"count": 1 "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", "name": "Surveillance Monitor",
"mana_cost": "{3}{U}", "mana_cost": "{3}{U}",
"cmc": 4.0, "cmc": 4.0,
@@ -213,7 +213,7 @@
"power": "2", "power": "2",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/50/glamermite?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/50/glamermite?utm_source=api",
@@ -445,7 +445,7 @@
"power": "3", "power": "3",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tdm/23/salt-road-packbeast?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tdm/23/salt-road-packbeast?utm_source=api",
@@ -499,7 +499,7 @@
"power": "5", "power": "5",
"toughness": "6", "toughness": "6",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fic/26/summon:-good-king-mog-xii?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fic/26/summon:-good-king-mog-xii?utm_source=api",
@@ -556,7 +556,7 @@
"count": 1 "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", "name": "Thancred Waters",
"mana_cost": "{4}{W}", "mana_cost": "{4}{W}",
"cmc": 5.0, "cmc": 5.0,
@@ -769,7 +769,7 @@
"power": "3", "power": "3",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 1 "count": 4
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tdm/2/anafenza-unyielding-lineage?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tdm/2/anafenza-unyielding-lineage?utm_source=api",
@@ -916,7 +916,7 @@
"count": 1 "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", "name": "Alisaie Leveilleur",
"mana_cost": "{2}{W}", "mana_cost": "{2}{W}",
"cmc": 3.0, "cmc": 3.0,
@@ -1003,7 +1003,7 @@
"power": "3", "power": "3",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/40/tributary-vaulter?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/40/tributary-vaulter?utm_source=api",
@@ -1184,7 +1184,7 @@
"power": "2", "power": "2",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/14/encumbered-reejerey?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/14/encumbered-reejerey?utm_source=api",
@@ -1708,7 +1708,7 @@
"power": "3", "power": "3",
"toughness": "7", "toughness": "7",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/dsc/158/syr-konrad-the-grim?utm_source=api", "scryfall_uri": "https://scryfall.com/card/dsc/158/syr-konrad-the-grim?utm_source=api",
@@ -1866,7 +1866,7 @@
"power": "3", "power": "3",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 1 "count": 5
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/101/dream-seizer?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/101/dream-seizer?utm_source=api",
@@ -1884,7 +1884,7 @@
"power": "3", "power": "3",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 4
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/114/nightmare-sower?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/114/nightmare-sower?utm_source=api",
@@ -2062,7 +2062,7 @@
"power": "4", "power": "4",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/103/gnarlbark-elm?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/103/gnarlbark-elm?utm_source=api",
@@ -2278,7 +2278,7 @@
"power": "1", "power": "1",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/87/bile-vial-boggart?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/87/bile-vial-boggart?utm_source=api",
@@ -2296,7 +2296,7 @@
"power": "1", "power": "1",
"toughness": "1", "toughness": "1",
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/121/summon:-primal-odin?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/121/summon:-primal-odin?utm_source=api",
@@ -2548,7 +2548,7 @@
"power": "6", "power": "6",
"toughness": "5", "toughness": "5",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/139/gilgamesh-master-at-arms?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/139/gilgamesh-master-at-arms?utm_source=api",
@@ -2890,7 +2890,7 @@
"power": "4", "power": "4",
"toughness": "5", "toughness": "5",
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fdn/191/brazen-scourge?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fdn/191/brazen-scourge?utm_source=api",
@@ -3070,7 +3070,7 @@
"power": "2", "power": "2",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/141/hill-gigas?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/141/hill-gigas?utm_source=api",
@@ -3250,7 +3250,7 @@
"power": "3", "power": "3",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 2 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/153/sabotender?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/153/sabotender?utm_source=api",
@@ -3322,7 +3322,7 @@
"power": "1", "power": "1",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 2 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/130/blazing-bomb?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/130/blazing-bomb?utm_source=api",
@@ -3502,7 +3502,7 @@
"power": "4", "power": "4",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tla/168/the-boulder-ready-to-rumble?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tla/168/the-boulder-ready-to-rumble?utm_source=api",
@@ -3717,7 +3717,7 @@
"power": "3", "power": "3",
"toughness": "1", "toughness": "1",
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/180/lys-alana-dignitary?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/180/lys-alana-dignitary?utm_source=api",
@@ -3861,7 +3861,7 @@
"power": "1", "power": "1",
"toughness": "1", "toughness": "1",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/spm/114/spider-ham-peter-porker?utm_source=api", "scryfall_uri": "https://scryfall.com/card/spm/114/spider-ham-peter-porker?utm_source=api",
@@ -3951,7 +3951,7 @@
"power": "4", "power": "4",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/211/vanille-cheerful-lcie?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/211/vanille-cheerful-lcie?utm_source=api",
@@ -4006,7 +4006,7 @@
"power": "3", "power": "3",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 2 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tla/202/walltop-sentries?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tla/202/walltop-sentries?utm_source=api",
@@ -4197,7 +4197,7 @@
"count": 1 "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", "name": "G'raha Tia, Scion Reborn",
"mana_cost": "{W}{U}{B}", "mana_cost": "{W}{U}{B}",
"cmc": 3.0, "cmc": 3.0,
@@ -4333,7 +4333,7 @@
"power": "5", "power": "5",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tla/214/dai-li-agents?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tla/214/dai-li-agents?utm_source=api",
@@ -4453,7 +4453,7 @@
"power": "5", "power": "5",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/214/black-waltz-no-3?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/214/black-waltz-no-3?utm_source=api",
@@ -4653,7 +4653,7 @@
"power": "3", "power": "3",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/241/sanar-innovative-first-year?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/241/sanar-innovative-first-year?utm_source=api",
@@ -4713,7 +4713,7 @@
"power": "3", "power": "3",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/124/ashling-rekindled-ashling-rimebound?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/124/ashling-rekindled-ashling-rimebound?utm_source=api",
@@ -4978,7 +4978,7 @@
"power": "3", "power": "3",
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/552/cloud-planets-champion?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/552/cloud-planets-champion?utm_source=api",
@@ -5038,7 +5038,7 @@
"power": "2", "power": "2",
"toughness": "1", "toughness": "1",
"loyalty": null, "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", "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 "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", "name": "Ardbert, Warrior of Darkness",
"mana_cost": "{1}{W}{B}", "mana_cost": "{1}{W}{B}",
"cmc": 3.0, "cmc": 3.0,
@@ -5389,7 +5389,7 @@
"power": "2", "power": "2",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 2 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/250/voracious-tome-skimmer?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/250/voracious-tome-skimmer?utm_source=api",
@@ -5531,7 +5531,7 @@
"power": "3", "power": "3",
"toughness": "3", "toughness": "3",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/cmm/967/palladium-myr?utm_source=api", "scryfall_uri": "https://scryfall.com/card/cmm/967/palladium-myr?utm_source=api",
@@ -5559,7 +5559,7 @@
"power": "1", "power": "1",
"toughness": "2", "toughness": "2",
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/spm/173/spider-bot?utm_source=api", "scryfall_uri": "https://scryfall.com/card/spm/173/spider-bot?utm_source=api",
@@ -6050,7 +6050,7 @@
"count": 1 "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", "name": "Hermes, Overseer of Elpis",
"mana_cost": "{3}{U}", "mana_cost": "{3}{U}",
"cmc": 4.0, "cmc": 4.0,
@@ -6350,7 +6350,7 @@
"count": 1 "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", "name": "Moonstone Eulogist",
"mana_cost": "{3}{B}{B}", "mana_cost": "{3}{B}{B}",
"cmc": 5.0, "cmc": 5.0,
@@ -6481,7 +6481,7 @@
"count": 1 "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", "name": "Hazel's Brewmaster",
"mana_cost": "{3}{B}", "mana_cost": "{3}{B}",
"cmc": 4.0, "cmc": 4.0,
@@ -6607,7 +6607,7 @@
"count": 1 "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", "name": "Insatiable Frugivore",
"mana_cost": "{3}{B}", "mana_cost": "{3}{B}",
"cmc": 4.0, "cmc": 4.0,
@@ -6841,7 +6841,7 @@
"count": 1 "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", "name": "Scurry of Squirrels",
"mana_cost": "{2}{G}", "mana_cost": "{2}{G}",
"cmc": 3.0, "cmc": 3.0,
@@ -6879,7 +6879,7 @@
"count": 1 "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", "name": "Krile Baldesion",
"mana_cost": "{W}{U}", "mana_cost": "{W}{U}",
"cmc": 2.0, "cmc": 2.0,
@@ -6937,7 +6937,7 @@
"count": 1 "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", "name": "Fandaniel, Telophoroi Ascian",
"mana_cost": "{4}{B}", "mana_cost": "{4}{B}",
"cmc": 5.0, "cmc": 5.0,
@@ -6973,7 +6973,7 @@
"count": 1 "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", "name": "Papalymo Totolymo",
"mana_cost": "{W}{B}", "mana_cost": "{W}{B}",
"cmc": 2.0, "cmc": 2.0,
@@ -7049,7 +7049,7 @@
"count": 1 "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", "name": "Y'shtola, Night's Blessed",
"mana_cost": "{1}{W}{U}{B}", "mana_cost": "{1}{W}{U}{B}",
"cmc": 4.0, "cmc": 4.0,
@@ -7127,5 +7127,541 @@
"toughness": "4", "toughness": "4",
"loyalty": null, "loyalty": null,
"count": 1 "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, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 7
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fic/464/campsite-cuisine?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fic/464/campsite-cuisine?utm_source=api",
@@ -251,7 +251,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tdm/75/corroding-dragonstorm?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tdm/75/corroding-dragonstorm?utm_source=api",
@@ -377,7 +377,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/60/noggle-the-mind?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/60/noggle-the-mind?utm_source=api",
@@ -539,7 +539,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/36/spiral-into-solitude?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/36/spiral-into-solitude?utm_source=api",
@@ -557,7 +557,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 6
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/15/evershrikes-gift?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/15/evershrikes-gift?utm_source=api",
@@ -647,7 +647,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 2 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/31/sidequest:-catch-a-fish-cooking-campsite?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/31/sidequest:-catch-a-fish-cooking-campsite?utm_source=api",
@@ -702,7 +702,7 @@
"count": 2 "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", "name": "Murmuration",
"mana_cost": "{4}{W}", "mana_cost": "{4}{W}",
"cmc": 5.0, "cmc": 5.0,
@@ -792,7 +792,7 @@
"count": 2 "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", "name": "The Rollercrusher Ride",
"mana_cost": "{X}{2}{R}", "mana_cost": "{X}{2}{R}",
"cmc": 3.0, "cmc": 3.0,
@@ -1118,7 +1118,7 @@
"count": 1 "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", "name": "Champions from Beyond",
"mana_cost": "{X}{W}{W}", "mana_cost": "{X}{W}{W}",
"cmc": 2.0, "cmc": 2.0,
@@ -1134,5 +1134,77 @@
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "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, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tla/187/origin-of-metalbending?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tla/187/origin-of-metalbending?utm_source=api",
@@ -181,7 +181,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fdn/223/giant-growth?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fdn/223/giant-growth?utm_source=api",
@@ -289,10 +289,10 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": 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", "name": "Laughing Mad",
"mana_cost": "{2}{R}", "mana_cost": "{2}{R}",
"cmc": 3.0, "cmc": 3.0,
@@ -469,7 +469,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/eld/50/into-the-story?utm_source=api", "scryfall_uri": "https://scryfall.com/card/eld/50/into-the-story?utm_source=api",
@@ -523,7 +523,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fdn/53/uncharted-voyage?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fdn/53/uncharted-voyage?utm_source=api",
@@ -739,7 +739,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/ecl/84/wild-unraveling?utm_source=api", "scryfall_uri": "https://scryfall.com/card/ecl/84/wild-unraveling?utm_source=api",
@@ -757,7 +757,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 6
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/dsk/60/get-out?utm_source=api", "scryfall_uri": "https://scryfall.com/card/dsk/60/get-out?utm_source=api",
@@ -886,7 +886,7 @@
"count": 1 "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", "name": "Razor Rings",
"mana_cost": "{1}{W}", "mana_cost": "{1}{W}",
"cmc": 2.0, "cmc": 2.0,
@@ -937,7 +937,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tla/43/yip-yip!?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tla/43/yip-yip!?utm_source=api",
@@ -1458,7 +1458,7 @@
"count": 1 "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", "name": "Negate",
"mana_cost": "{1}{U}", "mana_cost": "{1}{U}",
"cmc": 2.0, "cmc": 2.0,
@@ -2194,7 +2194,7 @@
"count": 1 "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", "name": "Transpose",
"mana_cost": "{2}{B}", "mana_cost": "{2}{B}",
"cmc": 3.0, "cmc": 3.0,
@@ -2248,5 +2248,187 @@
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "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 "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", "name": "Tranquil Landscape",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -116,7 +116,7 @@
"count": 1 "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", "name": "Hinterland Harbor",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -166,22 +166,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/fin/292/windurst-federation-center?utm_source=api",
"name": "Windurst, Federation Center", "name": "Windurst, Federation Center",
@@ -247,22 +231,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/ecc/166/seaside-citadel?utm_source=api",
"name": "Seaside Citadel", "name": "Seaside Citadel",
@@ -282,7 +250,7 @@
"count": 1 "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", "name": "Exotic Orchard",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -293,10 +261,10 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": 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", "name": "Evolving Wilds",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -307,10 +275,10 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": 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", "name": "Path of Ancestry",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -321,7 +289,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/fin/288/sharlayan-nation-of-scholars?utm_source=api", "scryfall_uri": "https://scryfall.com/card/fin/288/sharlayan-nation-of-scholars?utm_source=api",
@@ -388,22 +356,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/tdc/413/yavimaya-coast?utm_source=api",
"name": "Yavimaya Coast", "name": "Yavimaya Coast",
@@ -439,23 +391,7 @@
"count": 1 "count": 1
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tmc/63/command-tower?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",
"name": "Command Tower", "name": "Command Tower",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -468,38 +404,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/tdc/343/canopy-vista?utm_source=api",
"name": "Canopy Vista", "name": "Canopy Vista",
@@ -517,70 +421,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/eoc/184/sulfur-falls?utm_source=api",
"name": "Sulfur Falls", "name": "Sulfur Falls",
@@ -599,7 +439,7 @@
"count": 1 "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", "name": "Command Tower",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -663,102 +503,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/eoc/190/viridescent-bog?utm_source=api",
"name": "Viridescent Bog", "name": "Viridescent Bog",
@@ -794,7 +538,7 @@
"count": 1 "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", "name": "Command Tower",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -1053,71 +797,7 @@
"count": 1 "count": 1
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tmt/195/forest?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tmc/66/exotic-orchard?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",
"name": "Exotic Orchard", "name": "Exotic Orchard",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -1214,7 +894,7 @@
"count": 1 "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", "name": "Evolving Wilds",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -1244,22 +924,6 @@
"loyalty": null, "loyalty": null,
"count": 1 "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", "scryfall_uri": "https://scryfall.com/card/otc/316/scavenger-grounds?utm_source=api",
"name": "Scavenger Grounds", "name": "Scavenger Grounds",
@@ -1292,23 +956,7 @@
"count": 1 "count": 1
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tmt/193/swamp?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tmc/60/ash-barrens?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",
"name": "Ash Barrens", "name": "Ash Barrens",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -1387,7 +1035,7 @@
"count": 1 "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", "name": "Sunken Hollow",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -1438,7 +1086,7 @@
"count": 1 "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", "name": "Path of Ancestry",
"mana_cost": "", "mana_cost": "",
"cmc": 0.0, "cmc": 0.0,
@@ -1563,5 +1211,19 @@
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "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 "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", "name": "Circle of Power",
"mana_cost": "{3}{B}", "mana_cost": "{3}{B}",
"cmc": 4.0, "cmc": 4.0,
@@ -589,7 +589,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 4
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/dsc/155/reanimate?utm_source=api", "scryfall_uri": "https://scryfall.com/card/dsc/155/reanimate?utm_source=api",
@@ -625,7 +625,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "count": 2
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/tla/80/waterbending-lesson?utm_source=api", "scryfall_uri": "https://scryfall.com/card/tla/80/waterbending-lesson?utm_source=api",
@@ -806,7 +806,7 @@
"power": null, "power": null,
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 2 "count": 3
}, },
{ {
"scryfall_uri": "https://scryfall.com/card/spm/73/venoms-hunger?utm_source=api", "scryfall_uri": "https://scryfall.com/card/spm/73/venoms-hunger?utm_source=api",
@@ -1115,7 +1115,7 @@
"count": 1 "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", "name": "Cultivate",
"mana_cost": "{2}{G}", "mana_cost": "{2}{G}",
"cmc": 3.0, "cmc": 3.0,
@@ -1463,7 +1463,7 @@
"count": 1 "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", "name": "Blasphemous Act",
"mana_cost": "{8}{R}", "mana_cost": "{8}{R}",
"cmc": 9.0, "cmc": 9.0,
@@ -1917,5 +1917,41 @@
"toughness": null, "toughness": null,
"loyalty": null, "loyalty": null,
"count": 1 "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()