diff --git a/.gitignore b/.gitignore index 773d297..66827f2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ Rocket.toml /web/dynamic/script.js /web/dynamic/style.css /web/static - +.env diff --git a/build b/build index 1f24046..9f28fee 100755 --- a/build +++ b/build @@ -1,5 +1,7 @@ #!/usr/bin/env bash mkdir -p web/static +touch .env web/static/userstyle.css +source .env lsc --no-header -bco web/dynamic/ web/src/script.ls || exit 1 npx sass --no-source-map web/src/style.scss web/dynamic/style.css || exit 2 rustfmt src/*.rs || exit 3 diff --git a/src/cards.json b/src/cards.json deleted file mode 100644 index 7e7cce0..0000000 --- a/src/cards.json +++ /dev/null @@ -1,202 +0,0 @@ -[ - { - "id": "{3df927c0-9aa9-450b-ab2a-ae12c5489d57}", - "name": "Supplant Form", - "png": "https://cards.scryfall.io/png/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.png?1562824437", - "jpg": "https://cards.scryfall.io/small/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.jpg?1562824437", - "text": "Return target creature to its owner's hand. You create a token that's a copy of that creature.", - "count": 2 - }, - { - "id": "{30202613-d05f-4f47-af97-d0b75ccac293}", - "name": "Memory Lapse", - "png": "https://cards.scryfall.io/png/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.png?1634131658", - "jpg": "https://cards.scryfall.io/small/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.jpg?1634131658", - "text": "Counter target spell. If that spell is countered this way, put it on top of its owner's library instead of into that player's graveyard.", - "count": 8 - }, - { - "id": "{ad88e5ee-0eee-47af-a7b4-9bac044e1c8c}", - "name": "Accumulated Knowledge", - "png": "https://cards.scryfall.io/png/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.png?1562439718", - "jpg": "https://cards.scryfall.io/small/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.jpg?1562439718", - "text": "Draw a card, then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards.", - "count": 4 - }, - { - "id": "{806e5536-103b-4d6c-83b4-8659a55b25b5}", - "name": "Mystic Retrieval", - "png": "https://cards.scryfall.io/png/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.png?1736467785", - "jpg": "https://cards.scryfall.io/small/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.jpg?1736467785", - "text": "Return target instant or sorcery card from your graveyard to your hand.\nFlashback {2}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)", - "count": 2 - }, - { - "id": "{36fa9a0b-b0c9-43ea-ba11-99d7982f974e}", - "name": "Mystical Tutor", - "png": "https://cards.scryfall.io/png/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.png?1675199375", - "jpg": "https://cards.scryfall.io/small/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.jpg?1675199375", - "text": "Search your library for an instant or sorcery card, reveal it, then shuffle and put that card on top.", - "count": 2 - }, - { - "id": "{21fe2f1b-bfd1-4682-ae23-069300de3791}", - "name": "Svyelunite Temple", - "png": "https://cards.scryfall.io/png/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.png?1562867851", - "jpg": "https://cards.scryfall.io/small/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.jpg?1562867851", - "text": "Svyelunite Temple enters tapped.\n{T}: Add {U}.\n{T}, Sacrifice Svyelunite Temple: Add {U}{U}.", - "count": 2 - }, - { - "id": "{8beb987c-1b67-4a4e-ae71-58547afad2a0}", - "name": "Brainstorm", - "png": "https://cards.scryfall.io/png/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.png?1726284649", - "jpg": "https://cards.scryfall.io/small/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.jpg?1726284649", - "text": "Draw three cards, then put two cards from your hand on top of your library in any order.", - "count": 2 - }, - { - "id": "{c548d140-3d81-4b33-9985-87703d316a83}", - "name": "Dance of the Skywise", - "png": "https://cards.scryfall.io/png/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.png?1562792779", - "jpg": "https://cards.scryfall.io/small/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.jpg?1562792779", - "text": "Until end of turn, target creature you control becomes a blue Dragon Illusion with base power and toughness 4/4, loses all abilities, and gains flying.", - "count": 2 - }, - { - "id": "{7132999f-6e2d-4689-8131-7b12076a348d}", - "name": "Island", - "png": "https://cards.scryfall.io/png/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.png?1655495395", - "jpg": "https://cards.scryfall.io/small/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.jpg?1655495395", - "text": "({T}: Add {U}.)", - "count": 18 - }, - { - "id": "{7ca8a427-5c20-4650-bbb5-9895e4f2c009}", - "name": "Ray of Command", - "png": "https://cards.scryfall.io/png/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.png?1592754536", - "jpg": "https://cards.scryfall.io/small/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.jpg?1592754536", - "text": "Untap target creature an opponent controls and gain control of it until end of turn. That creature gains haste until end of turn. When you lose control of the creature, tap it.", - "count": 2 - }, - { - "id": "{2dc0bafd-debc-4b62-9fe0-56b4aad02484}", - "name": "Unsubstantiate", - "png": "https://cards.scryfall.io/png/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.png?1594735888", - "jpg": "https://cards.scryfall.io/small/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.jpg?1594735888", - "text": "Return target spell or creature to its owner's hand.", - "count": 2 - }, - { - "id": "{ac2e32d0-f172-4934-9d73-1bc2ab86586e}", - "name": "Dandân", - "png": "https://cards.scryfall.io/png/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.png?1562781784", - "jpg": "https://cards.scryfall.io/small/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.jpg?1562781784", - "text": "Dandân can't attack unless defending player controls an Island.\nWhen you control no Islands, sacrifice Dandân.", - "count": 10 - }, - { - "id": "{692a5bfa-9a50-4610-acd7-19fc0a53f798}", - "name": "Mind Bend", - "png": "https://cards.scryfall.io/png/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.png?1562549129", - "jpg": "https://cards.scryfall.io/small/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.jpg?1562549129", - "text": "Change the text of target permanent by replacing all instances of one color word with another or one basic land type with another. (For example, you may change \"nonblack creature\" to \"nongreen creature\" or \"forestwalk\" to \"islandwalk.\" This effect lasts indefinitely.)", - "count": 2 - }, - { - "id": "{a340af37-c0a7-4f26-974b-95397b5c32f7}", - "name": "Remote Isle", - "png": "https://cards.scryfall.io/png/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.png?1709139228", - "jpg": "https://cards.scryfall.io/small/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.jpg?1709139228", - "text": "Remote Isle enters tapped.\n{T}: Add {U}.\nCycling {2} ({2}, Discard this card: Draw a card.)", - "count": 2 - }, - { - "id": "{8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2}", - "name": "Insidious Will", - "png": "https://cards.scryfall.io/png/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.png?1576381260", - "jpg": "https://cards.scryfall.io/small/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.jpg?1576381260", - "text": "Choose one —\n• Counter target spell.\n• You may choose new targets for target spell.\n• Copy target instant or sorcery spell. You may choose new targets for the copy.", - "count": 2 - }, - { - "id": "{bfac9395-7ca5-48dd-ab83-7c26ada12f61}", - "name": "Izzet Boilerworks", - "png": "https://cards.scryfall.io/png/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.png?1712355049", - "jpg": "https://cards.scryfall.io/small/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.jpg?1712355049", - "text": "Izzet Boilerworks enters tapped.\nWhen Izzet Boilerworks enters, return a land you control to its owner's hand.\n{T}: Add {U}{R}.", - "count": 2 - }, - { - "id": "{349ac7e6-af38-4dc3-abfe-369564c75630}", - "name": "Predict", - "png": "https://cards.scryfall.io/png/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.png?1592710621", - "jpg": "https://cards.scryfall.io/small/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.jpg?1592710621", - "text": "Choose a card name, then target player mills a card. If a card with the chosen name was milled this way, you draw two cards. Otherwise, you draw a card.", - "count": 2 - }, - { - "id": "{f9ed455f-95cd-48ca-8b0d-4db259347bb6}", - "name": "Diminishing Returns", - "png": "https://cards.scryfall.io/png/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.png?1580013930", - "jpg": "https://cards.scryfall.io/small/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.jpg?1580013930", - "text": "Each player shuffles their hand and graveyard into their library. You exile the top ten cards of your library. Then each player draws up to seven cards.", - "count": 2 - }, - { - "id": "{78b384d3-3adf-493a-8b89-bfe68fd1c3e2}", - "name": "Vision Charm", - "png": "https://cards.scryfall.io/png/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.png?1562277700", - "jpg": "https://cards.scryfall.io/small/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.jpg?1562277700", - "text": "Choose one —\n• Target player mills four cards.\n• Choose a land type and a basic land type. Each land of the first chosen type becomes the second chosen type until end of turn.\n• Target artifact phases out. (While it's phased out, it's treated as though it doesn't exist. It phases in before its controller untaps during their next untap step.)", - "count": 2 - }, - { - "id": "{8798a4f1-34bb-449d-a8cc-faf8bda8e0ab}", - "name": "Crystal Spray", - "png": "https://cards.scryfall.io/png/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.png?1562922403", - "jpg": "https://cards.scryfall.io/small/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.jpg?1562922403", - "text": "Change the text of target spell or permanent by replacing all instances of one color word with another or one basic land type with another until end of turn.\nDraw a card.", - "count": 2 - }, - { - "id": "{63eb18e0-c723-4de4-8498-c5362c75b2b4}", - "name": "Halimar Depths", - "png": "https://cards.scryfall.io/png/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.png?1726285434", - "jpg": "https://cards.scryfall.io/small/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.jpg?1726285434", - "text": "Halimar Depths enters tapped.\nWhen Halimar Depths enters, look at the top three cards of your library, then put them back in any order.\n{T}: Add {U}.", - "count": 2 - }, - { - "id": "{a0f0c20c-184e-4d27-ae8b-933abb6fee0c}", - "name": "Metamorphose", - "png": "https://cards.scryfall.io/png/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.png?1562533013", - "jpg": "https://cards.scryfall.io/small/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.jpg?1562533013", - "text": "Put target permanent an opponent controls on top of its owner's library. That opponent may put an artifact, creature, enchantment, or land card from their hand onto the battlefield.", - "count": 2 - }, - { - "id": "{67652446-6d12-4e2a-bb51-ba685f2e79d1}", - "name": "Mystic Sanctuary", - "png": "https://cards.scryfall.io/png/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.png?1706241201", - "jpg": "https://cards.scryfall.io/small/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.jpg?1706241201", - "text": "({T}: Add {U}.)\nMystic Sanctuary enters tapped unless you control three or more other Islands.\nWhen Mystic Sanctuary enters untapped, you may put target instant or sorcery card from your graveyard on top of your library.", - "count": 2 - }, - { - "id": "{5833825f-aeb0-41bb-92a4-13e869765295}", - "name": "Lonely Sandbar", - "png": "https://cards.scryfall.io/png/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.png?1708054955", - "jpg": "https://cards.scryfall.io/small/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.jpg?1708054955", - "text": "Lonely Sandbar enters tapped.\n{T}: Add {U}.\nCycling {U} ({U}, Discard this card: Draw a card.)", - "count": 2 - }, - { - "id": "{224e8255-7bc6-44a2-86af-14f8446f4f77}", - "name": "Temple of Epiphany", - "png": "https://cards.scryfall.io/png/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.png?1730491248", - "jpg": "https://cards.scryfall.io/small/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.jpg?1730491248", - "text": "This land enters tapped.\nWhen this land enters, scry 1. (Look at the top card of your library. You may put that card on the bottom.)\n{T}: Add {U} or {R}.", - "count": 2 - } -] diff --git a/src/cards.json b/src/cards.json new file mode 120000 index 0000000..e391c6d --- /dev/null +++ b/src/cards.json @@ -0,0 +1 @@ +/home/seoxi/Dev/rust/dandadan/src/cards_nuts.json \ No newline at end of file diff --git a/web/dynamic/cards.json b/src/cards_dandan.json similarity index 76% rename from web/dynamic/cards.json rename to src/cards_dandan.json index 5d296db..abe241f 100644 --- a/web/dynamic/cards.json +++ b/src/cards_dandan.json @@ -1,152 +1,202 @@ -{ - "3df927c0-9aa9-450b-ab2a-ae12c5489d57": { +[ + { + "id": "{3df927c0-9aa9-450b-ab2a-ae12c5489d57}", "name": "Supplant Form", "png": "https://cards.scryfall.io/png/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.png?1562824437", "jpg": "https://cards.scryfall.io/small/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.jpg?1562824437", - "text": "Return target creature to its owner's hand. You create a token that's a copy of that creature." + "text": "Return target creature to its owner's hand. You create a token that's a copy of that creature.", + "count": 2 }, - "30202613-d05f-4f47-af97-d0b75ccac293": { + { + "id": "{30202613-d05f-4f47-af97-d0b75ccac293}", "name": "Memory Lapse", "png": "https://cards.scryfall.io/png/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.png?1634131658", "jpg": "https://cards.scryfall.io/small/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.jpg?1634131658", - "text": "Counter target spell. If that spell is countered this way, put it on top of its owner's library instead of into that player's graveyard." + "text": "Counter target spell. If that spell is countered this way, put it on top of its owner's library instead of into that player's graveyard.", + "count": 8 }, - "ad88e5ee-0eee-47af-a7b4-9bac044e1c8c": { + { + "id": "{ad88e5ee-0eee-47af-a7b4-9bac044e1c8c}", "name": "Accumulated Knowledge", "png": "https://cards.scryfall.io/png/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.png?1562439718", "jpg": "https://cards.scryfall.io/small/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.jpg?1562439718", - "text": "Draw a card, then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards." + "text": "Draw a card, then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards.", + "count": 4 }, - "806e5536-103b-4d6c-83b4-8659a55b25b5": { + { + "id": "{806e5536-103b-4d6c-83b4-8659a55b25b5}", "name": "Mystic Retrieval", "png": "https://cards.scryfall.io/png/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.png?1736467785", "jpg": "https://cards.scryfall.io/small/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.jpg?1736467785", - "text": "Return target instant or sorcery card from your graveyard to your hand.\nFlashback {2}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)" + "text": "Return target instant or sorcery card from your graveyard to your hand.\nFlashback {2}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.)", + "count": 2 }, - "36fa9a0b-b0c9-43ea-ba11-99d7982f974e": { + { + "id": "{36fa9a0b-b0c9-43ea-ba11-99d7982f974e}", "name": "Mystical Tutor", "png": "https://cards.scryfall.io/png/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.png?1675199375", "jpg": "https://cards.scryfall.io/small/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.jpg?1675199375", - "text": "Search your library for an instant or sorcery card, reveal it, then shuffle and put that card on top." + "text": "Search your library for an instant or sorcery card, reveal it, then shuffle and put that card on top.", + "count": 2 }, - "21fe2f1b-bfd1-4682-ae23-069300de3791": { + { + "id": "{21fe2f1b-bfd1-4682-ae23-069300de3791}", "name": "Svyelunite Temple", "png": "https://cards.scryfall.io/png/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.png?1562867851", "jpg": "https://cards.scryfall.io/small/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.jpg?1562867851", - "text": "Svyelunite Temple enters tapped.\n{T}: Add {U}.\n{T}, Sacrifice Svyelunite Temple: Add {U}{U}." + "text": "Svyelunite Temple enters tapped.\n{T}: Add {U}.\n{T}, Sacrifice Svyelunite Temple: Add {U}{U}.", + "count": 2 }, - "8beb987c-1b67-4a4e-ae71-58547afad2a0": { + { + "id": "{8beb987c-1b67-4a4e-ae71-58547afad2a0}", "name": "Brainstorm", "png": "https://cards.scryfall.io/png/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.png?1726284649", "jpg": "https://cards.scryfall.io/small/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.jpg?1726284649", - "text": "Draw three cards, then put two cards from your hand on top of your library in any order." + "text": "Draw three cards, then put two cards from your hand on top of your library in any order.", + "count": 2 }, - "c548d140-3d81-4b33-9985-87703d316a83": { + { + "id": "{c548d140-3d81-4b33-9985-87703d316a83}", "name": "Dance of the Skywise", "png": "https://cards.scryfall.io/png/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.png?1562792779", "jpg": "https://cards.scryfall.io/small/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.jpg?1562792779", - "text": "Until end of turn, target creature you control becomes a blue Dragon Illusion with base power and toughness 4/4, loses all abilities, and gains flying." + "text": "Until end of turn, target creature you control becomes a blue Dragon Illusion with base power and toughness 4/4, loses all abilities, and gains flying.", + "count": 2 }, - "7132999f-6e2d-4689-8131-7b12076a348d": { + { + "id": "{7132999f-6e2d-4689-8131-7b12076a348d}", "name": "Island", "png": "https://cards.scryfall.io/png/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.png?1655495395", "jpg": "https://cards.scryfall.io/small/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.jpg?1655495395", - "text": "({T}: Add {U}.)" + "text": "({T}: Add {U}.)", + "count": 18 }, - "7ca8a427-5c20-4650-bbb5-9895e4f2c009": { + { + "id": "{7ca8a427-5c20-4650-bbb5-9895e4f2c009}", "name": "Ray of Command", "png": "https://cards.scryfall.io/png/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.png?1592754536", "jpg": "https://cards.scryfall.io/small/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.jpg?1592754536", - "text": "Untap target creature an opponent controls and gain control of it until end of turn. That creature gains haste until end of turn. When you lose control of the creature, tap it." + "text": "Untap target creature an opponent controls and gain control of it until end of turn. That creature gains haste until end of turn. When you lose control of the creature, tap it.", + "count": 2 }, - "2dc0bafd-debc-4b62-9fe0-56b4aad02484": { + { + "id": "{2dc0bafd-debc-4b62-9fe0-56b4aad02484}", "name": "Unsubstantiate", "png": "https://cards.scryfall.io/png/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.png?1594735888", "jpg": "https://cards.scryfall.io/small/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.jpg?1594735888", - "text": "Return target spell or creature to its owner's hand." + "text": "Return target spell or creature to its owner's hand.", + "count": 2 }, - "ac2e32d0-f172-4934-9d73-1bc2ab86586e": { + { + "id": "{ac2e32d0-f172-4934-9d73-1bc2ab86586e}", "name": "Dandân", "png": "https://cards.scryfall.io/png/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.png?1562781784", "jpg": "https://cards.scryfall.io/small/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.jpg?1562781784", - "text": "Dandân can't attack unless defending player controls an Island.\nWhen you control no Islands, sacrifice Dandân." + "text": "Dandân can't attack unless defending player controls an Island.\nWhen you control no Islands, sacrifice Dandân.", + "count": 10 }, - "692a5bfa-9a50-4610-acd7-19fc0a53f798": { + { + "id": "{692a5bfa-9a50-4610-acd7-19fc0a53f798}", "name": "Mind Bend", "png": "https://cards.scryfall.io/png/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.png?1562549129", "jpg": "https://cards.scryfall.io/small/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.jpg?1562549129", - "text": "Change the text of target permanent by replacing all instances of one color word with another or one basic land type with another. (For example, you may change \"nonblack creature\" to \"nongreen creature\" or \"forestwalk\" to \"islandwalk.\" This effect lasts indefinitely.)" + "text": "Change the text of target permanent by replacing all instances of one color word with another or one basic land type with another. (For example, you may change \"nonblack creature\" to \"nongreen creature\" or \"forestwalk\" to \"islandwalk.\" This effect lasts indefinitely.)", + "count": 2 }, - "a340af37-c0a7-4f26-974b-95397b5c32f7": { + { + "id": "{a340af37-c0a7-4f26-974b-95397b5c32f7}", "name": "Remote Isle", "png": "https://cards.scryfall.io/png/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.png?1709139228", "jpg": "https://cards.scryfall.io/small/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.jpg?1709139228", - "text": "Remote Isle enters tapped.\n{T}: Add {U}.\nCycling {2} ({2}, Discard this card: Draw a card.)" + "text": "Remote Isle enters tapped.\n{T}: Add {U}.\nCycling {2} ({2}, Discard this card: Draw a card.)", + "count": 2 }, - "8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2": { + { + "id": "{8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2}", "name": "Insidious Will", "png": "https://cards.scryfall.io/png/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.png?1576381260", "jpg": "https://cards.scryfall.io/small/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.jpg?1576381260", - "text": "Choose one —\n• Counter target spell.\n• You may choose new targets for target spell.\n• Copy target instant or sorcery spell. You may choose new targets for the copy." + "text": "Choose one —\n• Counter target spell.\n• You may choose new targets for target spell.\n• Copy target instant or sorcery spell. You may choose new targets for the copy.", + "count": 2 }, - "bfac9395-7ca5-48dd-ab83-7c26ada12f61": { + { + "id": "{bfac9395-7ca5-48dd-ab83-7c26ada12f61}", "name": "Izzet Boilerworks", "png": "https://cards.scryfall.io/png/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.png?1712355049", "jpg": "https://cards.scryfall.io/small/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.jpg?1712355049", - "text": "Izzet Boilerworks enters tapped.\nWhen Izzet Boilerworks enters, return a land you control to its owner's hand.\n{T}: Add {U}{R}." + "text": "Izzet Boilerworks enters tapped.\nWhen Izzet Boilerworks enters, return a land you control to its owner's hand.\n{T}: Add {U}{R}.", + "count": 2 }, - "349ac7e6-af38-4dc3-abfe-369564c75630": { + { + "id": "{349ac7e6-af38-4dc3-abfe-369564c75630}", "name": "Predict", "png": "https://cards.scryfall.io/png/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.png?1592710621", "jpg": "https://cards.scryfall.io/small/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.jpg?1592710621", - "text": "Choose a card name, then target player mills a card. If a card with the chosen name was milled this way, you draw two cards. Otherwise, you draw a card." + "text": "Choose a card name, then target player mills a card. If a card with the chosen name was milled this way, you draw two cards. Otherwise, you draw a card.", + "count": 2 }, - "f9ed455f-95cd-48ca-8b0d-4db259347bb6": { + { + "id": "{f9ed455f-95cd-48ca-8b0d-4db259347bb6}", "name": "Diminishing Returns", "png": "https://cards.scryfall.io/png/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.png?1580013930", "jpg": "https://cards.scryfall.io/small/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.jpg?1580013930", - "text": "Each player shuffles their hand and graveyard into their library. You exile the top ten cards of your library. Then each player draws up to seven cards." + "text": "Each player shuffles their hand and graveyard into their library. You exile the top ten cards of your library. Then each player draws up to seven cards.", + "count": 2 }, - "78b384d3-3adf-493a-8b89-bfe68fd1c3e2": { + { + "id": "{78b384d3-3adf-493a-8b89-bfe68fd1c3e2}", "name": "Vision Charm", "png": "https://cards.scryfall.io/png/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.png?1562277700", "jpg": "https://cards.scryfall.io/small/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.jpg?1562277700", - "text": "Choose one —\n• Target player mills four cards.\n• Choose a land type and a basic land type. Each land of the first chosen type becomes the second chosen type until end of turn.\n• Target artifact phases out. (While it's phased out, it's treated as though it doesn't exist. It phases in before its controller untaps during their next untap step.)" + "text": "Choose one —\n• Target player mills four cards.\n• Choose a land type and a basic land type. Each land of the first chosen type becomes the second chosen type until end of turn.\n• Target artifact phases out. (While it's phased out, it's treated as though it doesn't exist. It phases in before its controller untaps during their next untap step.)", + "count": 2 }, - "8798a4f1-34bb-449d-a8cc-faf8bda8e0ab": { + { + "id": "{8798a4f1-34bb-449d-a8cc-faf8bda8e0ab}", "name": "Crystal Spray", "png": "https://cards.scryfall.io/png/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.png?1562922403", "jpg": "https://cards.scryfall.io/small/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.jpg?1562922403", - "text": "Change the text of target spell or permanent by replacing all instances of one color word with another or one basic land type with another until end of turn.\nDraw a card." + "text": "Change the text of target spell or permanent by replacing all instances of one color word with another or one basic land type with another until end of turn.\nDraw a card.", + "count": 2 }, - "63eb18e0-c723-4de4-8498-c5362c75b2b4": { + { + "id": "{63eb18e0-c723-4de4-8498-c5362c75b2b4}", "name": "Halimar Depths", "png": "https://cards.scryfall.io/png/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.png?1726285434", "jpg": "https://cards.scryfall.io/small/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.jpg?1726285434", - "text": "Halimar Depths enters tapped.\nWhen Halimar Depths enters, look at the top three cards of your library, then put them back in any order.\n{T}: Add {U}." + "text": "Halimar Depths enters tapped.\nWhen Halimar Depths enters, look at the top three cards of your library, then put them back in any order.\n{T}: Add {U}.", + "count": 2 }, - "a0f0c20c-184e-4d27-ae8b-933abb6fee0c": { + { + "id": "{a0f0c20c-184e-4d27-ae8b-933abb6fee0c}", "name": "Metamorphose", "png": "https://cards.scryfall.io/png/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.png?1562533013", "jpg": "https://cards.scryfall.io/small/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.jpg?1562533013", - "text": "Put target permanent an opponent controls on top of its owner's library. That opponent may put an artifact, creature, enchantment, or land card from their hand onto the battlefield." + "text": "Put target permanent an opponent controls on top of its owner's library. That opponent may put an artifact, creature, enchantment, or land card from their hand onto the battlefield.", + "count": 2 }, - "67652446-6d12-4e2a-bb51-ba685f2e79d1": { + { + "id": "{67652446-6d12-4e2a-bb51-ba685f2e79d1}", "name": "Mystic Sanctuary", "png": "https://cards.scryfall.io/png/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.png?1706241201", "jpg": "https://cards.scryfall.io/small/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.jpg?1706241201", - "text": "({T}: Add {U}.)\nMystic Sanctuary enters tapped unless you control three or more other Islands.\nWhen Mystic Sanctuary enters untapped, you may put target instant or sorcery card from your graveyard on top of your library." + "text": "({T}: Add {U}.)\nMystic Sanctuary enters tapped unless you control three or more other Islands.\nWhen Mystic Sanctuary enters untapped, you may put target instant or sorcery card from your graveyard on top of your library.", + "count": 2 }, - "5833825f-aeb0-41bb-92a4-13e869765295": { + { + "id": "{5833825f-aeb0-41bb-92a4-13e869765295}", "name": "Lonely Sandbar", "png": "https://cards.scryfall.io/png/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.png?1708054955", "jpg": "https://cards.scryfall.io/small/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.jpg?1708054955", - "text": "Lonely Sandbar enters tapped.\n{T}: Add {U}.\nCycling {U} ({U}, Discard this card: Draw a card.)" + "text": "Lonely Sandbar enters tapped.\n{T}: Add {U}.\nCycling {U} ({U}, Discard this card: Draw a card.)", + "count": 2 }, - "224e8255-7bc6-44a2-86af-14f8446f4f77": { + { + "id": "{224e8255-7bc6-44a2-86af-14f8446f4f77}", "name": "Temple of Epiphany", "png": "https://cards.scryfall.io/png/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.png?1730491248", "jpg": "https://cards.scryfall.io/small/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.jpg?1730491248", - "text": "This land enters tapped.\nWhen this land enters, scry 1. (Look at the top card of your library. You may put that card on the bottom.)\n{T}: Add {U} or {R}." + "text": "This land enters tapped.\nWhen this land enters, scry 1. (Look at the top card of your library. You may put that card on the bottom.)\n{T}: Add {U} or {R}.", + "count": 2 } -} +] diff --git a/src/cards_nuts.json b/src/cards_nuts.json new file mode 100644 index 0000000..ec91f0d --- /dev/null +++ b/src/cards_nuts.json @@ -0,0 +1 @@ +[{"id":"{e21aa266-4264-4e02-9403-02930e641573}","name":"Bonus Round","png":"https://cards.scryfall.io/png/front/e/2/e21aa266-4264-4e02-9403-02930e641573.png?1562940436","jpg":"https://cards.scryfall.io/small/front/e/2/e21aa266-4264-4e02-9403-02930e641573.jpg?1562940436","text":"Until end of turn, whenever a player casts an instant or sorcery spell, that player copies it and may choose new targets for the copy.","count":1},{"id":"{e781b55f-6388-4ab8-be74-7c56eaba1c4d}","name":"Vandalblast","png":"https://cards.scryfall.io/png/front/e/7/e781b55f-6388-4ab8-be74-7c56eaba1c4d.png?1712354517","jpg":"https://cards.scryfall.io/small/front/e/7/e781b55f-6388-4ab8-be74-7c56eaba1c4d.jpg?1712354517","text":"Destroy target artifact you don't control.\nOverload {4}{R} (You may cast this spell for its overload cost. If you do, change \"target\" in its text to \"each.\")","count":2},{"id":"{b36fa6f3-29e8-4788-bfcd-59576187c399}","name":"Cathartic Reunion","png":"https://cards.scryfall.io/png/front/b/3/b36fa6f3-29e8-4788-bfcd-59576187c399.png?1599706266","jpg":"https://cards.scryfall.io/small/front/b/3/b36fa6f3-29e8-4788-bfcd-59576187c399.jpg?1599706266","text":"As an additional cost to cast this spell, discard two cards.\nDraw three cards.","count":8},{"id":"{06a981cd-1951-438e-95c9-68294795638e}","name":"Krark, the Thumbless","png":"https://cards.scryfall.io/png/front/0/6/06a981cd-1951-438e-95c9-68294795638e.png?1617148336","jpg":"https://cards.scryfall.io/small/front/0/6/06a981cd-1951-438e-95c9-68294795638e.jpg?1617148336","text":"Whenever you cast an instant or sorcery spell, flip a coin. If you lose the flip, return that spell to its owner's hand. If you win the flip, copy that spell, and you may choose new targets for the copy.\nPartner (You can have two commanders if both have partner.)","count":1},{"id":"{77c6fa74-5543-42ac-9ead-0e890b188e99}","name":"Lightning Bolt","png":"https://cards.scryfall.io/png/front/7/7/77c6fa74-5543-42ac-9ead-0e890b188e99.png?1706239968","jpg":"https://cards.scryfall.io/small/front/7/7/77c6fa74-5543-42ac-9ead-0e890b188e99.jpg?1706239968","text":"Lightning Bolt deals 3 damage to any target.","count":14},{"id":"{ba2d026f-bdca-4376-9d70-c81c707598e8}","name":"Mechanized Warfare","png":"https://cards.scryfall.io/png/front/b/a/ba2d026f-bdca-4376-9d70-c81c707598e8.png?1674421169","jpg":"https://cards.scryfall.io/small/front/b/a/ba2d026f-bdca-4376-9d70-c81c707598e8.jpg?1674421169","text":"If a red or artifact source you control would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus 1 instead.","count":2},{"id":"{4abf9b03-4157-42f0-ab3d-484bd9b64bb2}","name":"Charmbreaker Devils","png":"https://cards.scryfall.io/png/front/4/a/4abf9b03-4157-42f0-ab3d-484bd9b64bb2.png?1625193603","jpg":"https://cards.scryfall.io/small/front/4/a/4abf9b03-4157-42f0-ab3d-484bd9b64bb2.jpg?1625193603","text":"At the beginning of your upkeep, return an instant or sorcery card at random from your graveyard to your hand.\nWhenever you cast an instant or sorcery spell, this creature gets +4/+0 until end of turn.","count":1},{"id":"{d7f0e720-3c32-4040-b663-7f99ad5bc810}","name":"Furnace of Rath","png":"https://cards.scryfall.io/png/front/d/7/d7f0e720-3c32-4040-b663-7f99ad5bc810.png?1562843335","jpg":"https://cards.scryfall.io/small/front/d/7/d7f0e720-3c32-4040-b663-7f99ad5bc810.jpg?1562843335","text":"If a source would deal damage to a permanent or player, it deals double that damage to that permanent or player instead.","count":1},{"id":"{9de2ad47-c6d5-4cd8-ae32-66ae167e25a0}","name":"Dire Fleet Daredevil","png":"https://cards.scryfall.io/png/front/9/d/9de2ad47-c6d5-4cd8-ae32-66ae167e25a0.png?1712354407","jpg":"https://cards.scryfall.io/small/front/9/d/9de2ad47-c6d5-4cd8-ae32-66ae167e25a0.jpg?1712354407","text":"First strike\nWhen this creature enters, exile target instant or sorcery card from an opponent's graveyard. You may cast it this turn, and mana of any type can be spent to cast that spell. If that spell would be put into a graveyard, exile it instead.","count":2},{"id":"{fd97b3cf-924e-4f77-bb82-0bf19592389f}","name":"Dreadhorde Arcanist","png":"https://cards.scryfall.io/png/front/f/d/fd97b3cf-924e-4f77-bb82-0bf19592389f.png?1613386994","jpg":"https://cards.scryfall.io/small/front/f/d/fd97b3cf-924e-4f77-bb82-0bf19592389f.jpg?1613386994","text":"Trample\nWhenever this creature attacks, you may cast target instant or sorcery card with mana value less than or equal to this creature's power from your graveyard without paying its mana cost. If that spell would be put into your graveyard, exile it instead.","count":1},{"id":"{2970c679-562b-48ee-b631-2e3016f4f37c}","name":"Izzet Chemister","png":"https://cards.scryfall.io/png/front/2/9/2970c679-562b-48ee-b631-2e3016f4f37c.png?1674142036","jpg":"https://cards.scryfall.io/small/front/2/9/2970c679-562b-48ee-b631-2e3016f4f37c.jpg?1674142036","text":"Haste\n{R}, {T}: Exile target instant or sorcery card from your graveyard.\n{1}{R}, {T}, Sacrifice this creature: Cast any number of cards exiled with this creature without paying their mana costs.","count":1},{"id":"{85169934-1033-49d7-8d42-45e982077a23}","name":"Jaya Ballard","png":"https://cards.scryfall.io/png/front/8/5/85169934-1033-49d7-8d42-45e982077a23.png?1625193945","jpg":"https://cards.scryfall.io/small/front/8/5/85169934-1033-49d7-8d42-45e982077a23.jpg?1625193945","text":"+1: Add {R}{R}{R}. Spend this mana only to cast instant or sorcery spells.\n+1: Discard up to three cards, then draw that many cards.\n−8: You get an emblem with \"You may cast instant and sorcery spells from your graveyard. If a spell cast this way would be put into your graveyard, exile it instead.\"","count":1},{"id":"{cf01af2c-68e5-4bb5-81de-f3a1b860fb2e}","name":"Bolt Bend","png":"https://cards.scryfall.io/png/front/c/f/cf01af2c-68e5-4bb5-81de-f3a1b860fb2e.png?1730490945","jpg":"https://cards.scryfall.io/small/front/c/f/cf01af2c-68e5-4bb5-81de-f3a1b860fb2e.jpg?1730490945","text":"This spell costs {3} less to cast if you control a creature with power 4 or greater.\nChange the target of target spell or ability with a single target.","count":1},{"id":"{6741ab27-9e1f-4aa5-96b9-b450eda7c5c2}","name":"Elixir of Immortality","png":"https://cards.scryfall.io/png/front/6/7/6741ab27-9e1f-4aa5-96b9-b450eda7c5c2.png?1625978606","jpg":"https://cards.scryfall.io/small/front/6/7/6741ab27-9e1f-4aa5-96b9-b450eda7c5c2.jpg?1625978606","text":"{2}, {T}: You gain 5 life. Shuffle this artifact and your graveyard into their owner's library.","count":4},{"id":"{5996feb4-02ac-45e8-a7f2-966cf74391dc}","name":"Reverberate","png":"https://cards.scryfall.io/png/front/5/9/5996feb4-02ac-45e8-a7f2-966cf74391dc.png?1562554142","jpg":"https://cards.scryfall.io/small/front/5/9/5996feb4-02ac-45e8-a7f2-966cf74391dc.jpg?1562554142","text":"Copy target instant or sorcery spell. You may choose new targets for the copy.","count":2},{"id":"{1b597992-8e11-41a0-ac87-33fffd82b993}","name":"Double Vision","png":"https://cards.scryfall.io/png/front/1/b/1b597992-8e11-41a0-ac87-33fffd82b993.png?1673484331","jpg":"https://cards.scryfall.io/small/front/1/b/1b597992-8e11-41a0-ac87-33fffd82b993.jpg?1673484331","text":"Whenever you cast your first instant or sorcery spell each turn, copy that spell. You may choose new targets for the copy.","count":2},{"id":"{5db4baf3-f0dc-4b27-8323-a76764332590}","name":"Howl of the Horde","png":"https://cards.scryfall.io/png/front/5/d/5db4baf3-f0dc-4b27-8323-a76764332590.png?1562787225","jpg":"https://cards.scryfall.io/small/front/5/d/5db4baf3-f0dc-4b27-8323-a76764332590.jpg?1562787225","text":"When you next cast an instant or sorcery spell this turn, copy that spell. You may choose new targets for the copy.\nRaid — If you attacked this turn, when you next cast an instant or sorcery spell this turn, copy that spell an additional time. You may choose new targets for the copy.","count":1},{"id":"{f340d8a4-196a-4433-807c-b7124e96e44f}","name":"Pyromancer's Goggles","png":"https://cards.scryfall.io/png/front/f/3/f340d8a4-196a-4433-807c-b7124e96e44f.png?1736305795","jpg":"https://cards.scryfall.io/small/front/f/3/f340d8a4-196a-4433-807c-b7124e96e44f.jpg?1736305795","text":"{T}: Add {R}. When that mana is spent to cast a red instant or sorcery spell, copy that spell and you may choose new targets for the copy.","count":2},{"id":"{99302c41-434f-41ff-a61a-2c3681a0c135}","name":"Reiterate","png":"https://cards.scryfall.io/png/front/9/9/99302c41-434f-41ff-a61a-2c3681a0c135.png?1619397418","jpg":"https://cards.scryfall.io/small/front/9/9/99302c41-434f-41ff-a61a-2c3681a0c135.jpg?1619397418","text":"Buyback {3} (You may pay an additional {3} as you cast this spell. If you do, put this card into your hand as it resolves.)\nCopy target instant or sorcery spell. You may choose new targets for the copy.","count":4},{"id":"{1cb03593-9369-4c26-bfcc-8819648dc018}","name":"Repeated Reverberation","png":"https://cards.scryfall.io/png/front/1/c/1cb03593-9369-4c26-bfcc-8819648dc018.png?1690004682","jpg":"https://cards.scryfall.io/small/front/1/c/1cb03593-9369-4c26-bfcc-8819648dc018.jpg?1690004682","text":"When you next cast an instant spell, cast a sorcery spell, or activate a loyalty ability this turn, copy that spell or ability twice. You may choose new targets for the copies.","count":2},{"id":"{78a655b3-969f-40d7-9d69-61abf338df86}","name":"Storm King's Thunder","png":"https://cards.scryfall.io/png/front/7/8/78a655b3-969f-40d7-9d69-61abf338df86.png?1674136747","jpg":"https://cards.scryfall.io/small/front/7/8/78a655b3-969f-40d7-9d69-61abf338df86.jpg?1674136747","text":"When you next cast an instant or sorcery spell this turn, copy that spell X times. You may choose new targets for the copies.","count":1},{"id":"{d4d379b5-7f56-4a7d-a4ac-131fc3d579c6}","name":"Primal Amulet // Primal Wellspring","png":"https://cards.scryfall.io/png/front/d/4/d4d379b5-7f56-4a7d-a4ac-131fc3d579c6.png?1562564625","jpg":"https://cards.scryfall.io/small/front/d/4/d4d379b5-7f56-4a7d-a4ac-131fc3d579c6.jpg?1562564625","text":"Instant and sorcery spells you cast cost {1} less to cast.\nWhenever you cast an instant or sorcery spell, put a charge counter on this artifact. Then if there are four or more charge counters on it, you may remove those counters and transform it.","count":2},{"id":"{5e54b10e-5307-4138-8f80-28406170f2c6}","name":"Shunt","png":"https://cards.scryfall.io/png/front/5/e/5e54b10e-5307-4138-8f80-28406170f2c6.png?1562548483","jpg":"https://cards.scryfall.io/small/front/5/e/5e54b10e-5307-4138-8f80-28406170f2c6.jpg?1562548483","text":"Change the target of target spell with a single target.","count":2},{"id":"{72204934-f5aa-4559-8f7e-7b0b223580d0}","name":"Spellskite","png":"https://cards.scryfall.io/png/front/7/2/72204934-f5aa-4559-8f7e-7b0b223580d0.png?1674142724","jpg":"https://cards.scryfall.io/small/front/7/2/72204934-f5aa-4559-8f7e-7b0b223580d0.jpg?1674142724","text":"{U/P}: Change a target of target spell or ability to this creature. ({U/P} can be paid with either {U} or 2 life.)","count":2},{"id":"{f3766140-b755-4e4b-9d98-0cc0b889f15a}","name":"Desert of the Fervent","png":"https://cards.scryfall.io/png/front/f/3/f3766140-b755-4e4b-9d98-0cc0b889f15a.png?1712354971","jpg":"https://cards.scryfall.io/small/front/f/3/f3766140-b755-4e4b-9d98-0cc0b889f15a.jpg?1712354971","text":"This land enters tapped.\n{T}: Add {R}.\nCycling {1}{R} ({1}{R}, Discard this card: Draw a card.)","count":4},{"id":"{62b0b5f8-6b51-4fa5-85ae-290475525a9d}","name":"Forgotten Cave","png":"https://cards.scryfall.io/png/front/6/2/62b0b5f8-6b51-4fa5-85ae-290475525a9d.png?1625980082","jpg":"https://cards.scryfall.io/small/front/6/2/62b0b5f8-6b51-4fa5-85ae-290475525a9d.jpg?1625980082","text":"This land enters tapped.\n{T}: Add {R}.\nCycling {R} ({R}, Discard this card: Draw a card.)","count":6},{"id":"{224e8255-7bc6-44a2-86af-14f8446f4f77}","name":"Temple of Epiphany","png":"https://cards.scryfall.io/png/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.png?1730491248","jpg":"https://cards.scryfall.io/small/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.jpg?1730491248","text":"This land enters tapped.\nWhen this land enters, scry 1. (Look at the top card of your library. You may put that card on the bottom.)\n{T}: Add {U} or {R}.","count":2},{"id":"{fb76cb6c-2f4d-4ca2-876f-d49ea529e59b}","name":"Mountain","png":"https://cards.scryfall.io/png/front/f/b/fb76cb6c-2f4d-4ca2-876f-d49ea529e59b.png?1738357054","jpg":"https://cards.scryfall.io/small/front/f/b/fb76cb6c-2f4d-4ca2-876f-d49ea529e59b.jpg?1738357054","text":"({T}: Add {R}.)","count":18},{"id":"{79f591cd-d277-4ba5-b1bf-1c09cac9cb8a}","name":"Torbran, Thane of Red Fell","png":"https://cards.scryfall.io/png/front/7/9/79f591cd-d277-4ba5-b1bf-1c09cac9cb8a.png?1572490491","jpg":"https://cards.scryfall.io/small/front/7/9/79f591cd-d277-4ba5-b1bf-1c09cac9cb8a.jpg?1572490491","text":"If a red source you control would deal damage to an opponent or a permanent an opponent controls, it deals that much damage plus 2 instead.","count":1},{"id":"{f3af0acc-258e-4979-bab7-7792006ec7bc}","name":"Fire Servant","png":"https://cards.scryfall.io/png/front/f/3/f3af0acc-258e-4979-bab7-7792006ec7bc.png?1562480814","jpg":"https://cards.scryfall.io/small/front/f/3/f3af0acc-258e-4979-bab7-7792006ec7bc.jpg?1562480814","text":"If a red instant or sorcery spell you control would deal damage, it deals double that damage instead.","count":1},{"id":"{f2181ef5-963a-4e3b-805d-b3a04f4bef5e}","name":"Wildfire Devils","png":"https://cards.scryfall.io/png/front/f/2/f2181ef5-963a-4e3b-805d-b3a04f4bef5e.png?1625194173","jpg":"https://cards.scryfall.io/small/front/f/2/f2181ef5-963a-4e3b-805d-b3a04f4bef5e.jpg?1625194173","text":"When this creature enters and at the beginning of your upkeep, choose a player at random. That player exiles an instant or sorcery card from their graveyard. Copy that card. You may cast the copy without paying its mana cost.","count":1},{"id":"{11e4ba4b-62d1-43ed-82e8-cd45d7a62c65}","name":"Spinerock Knoll","png":"https://cards.scryfall.io/png/front/1/1/11e4ba4b-62d1-43ed-82e8-cd45d7a62c65.png?1726285500","jpg":"https://cards.scryfall.io/small/front/1/1/11e4ba4b-62d1-43ed-82e8-cd45d7a62c65.jpg?1726285500","text":"Hideaway 4 (When this land enters, look at the top four cards of your library, exile one face down, then put the rest on the bottom in a random order.)\nThis land enters tapped.\n{T}: Add {R}.\n{R}, {T}: You may play the exiled card without paying its mana cost if an opponent was dealt 7 or more damage this turn.","count":1},{"id":"{82bc7c6b-2e3d-42d1-b2bb-b37b6f34d33b}","name":"Sandstone Needle","png":"https://cards.scryfall.io/png/front/8/2/82bc7c6b-2e3d-42d1-b2bb-b37b6f34d33b.png?1562381828","jpg":"https://cards.scryfall.io/small/front/8/2/82bc7c6b-2e3d-42d1-b2bb-b37b6f34d33b.jpg?1562381828","text":"This land enters tapped with two depletion counters on it.\n{T}, Remove a depletion counter from this land: Add {R}{R}. If there are no depletion counters on this land, sacrifice it.","count":2},{"id":"{0b6adef9-a1c5-452d-94c7-4110f4998180}","name":"Dwarven Ruins","png":"https://cards.scryfall.io/png/front/0/b/0b6adef9-a1c5-452d-94c7-4110f4998180.png?1562867826","jpg":"https://cards.scryfall.io/small/front/0/b/0b6adef9-a1c5-452d-94c7-4110f4998180.jpg?1562867826","text":"This land enters tapped.\n{T}: Add {R}.\n{T}, Sacrifice this land: Add {R}{R}.","count":2},{"id":"{5294d359-c599-40ed-9e06-2a3cc8624d6a}","name":"Harness the Storm","png":"https://cards.scryfall.io/png/front/5/2/5294d359-c599-40ed-9e06-2a3cc8624d6a.png?1576384783","jpg":"https://cards.scryfall.io/small/front/5/2/5294d359-c599-40ed-9e06-2a3cc8624d6a.jpg?1576384783","text":"Whenever you cast an instant or sorcery spell from your hand, you may cast target card with the same name as that spell from your graveyard. (You still pay its costs.)","count":1},{"id":"{ef7e6e2d-d984-483e-b117-b1c5d640dc23}","name":"Arcane Bombardment","png":"https://cards.scryfall.io/png/front/e/f/ef7e6e2d-d984-483e-b117-b1c5d640dc23.png?1712354373","jpg":"https://cards.scryfall.io/small/front/e/f/ef7e6e2d-d984-483e-b117-b1c5d640dc23.jpg?1712354373","text":"Whenever you cast your first instant or sorcery spell each turn, exile an instant or sorcery card at random from your graveyard. Then copy each card exiled with this enchantment. You may cast any number of the copies without paying their mana costs.","count":1}] diff --git a/src/game.rs b/src/game.rs index fffa604..b3d7ac8 100644 --- a/src/game.rs +++ b/src/game.rs @@ -153,7 +153,7 @@ pub fn new() -> GameState { pub fn draw(game_state: &mut GameState, count: usize, player: Player) { let player_hand = match player { Player::A => &mut game_state.hands[0], - _ => &mut game_state.hands[1], + Player::B => &mut game_state.hands[1], }; game_state.events.push(CountedEvent { id: game_state.events.len(), @@ -167,22 +167,35 @@ pub fn draw(game_state: &mut GameState, count: usize, player: Player) { ); } -pub fn yoink(game_state: &mut GameState, deck_index: usize, player: Player) { +pub fn yoink(game_state: &mut GameState, deck_index: usize, player: Player) -> Result<(), String> { let player_hand = match player { Player::A => &mut game_state.hands[0], - _ => &mut game_state.hands[1], + Player::B => &mut game_state.hands[1], }; game_state.events.push(CountedEvent { id: game_state.events.len(), event: Event::Yoink(player.clone()), }); - player_hand - .cards - .push(game_state.deck.remove(deck_index).unwrap()); + match game_state.deck.remove(deck_index) { + Some(card) => Ok(player_hand.cards.push(card)), + None => Err(format!("Yoink index out of bounds: {}", deck_index)), + } } -pub fn yoink_mill(game_state: &mut GameState, deck_index: usize, shadow: bool) { - let card = game_state.deck.remove(deck_index).unwrap(); +pub fn yoink_mill( + game_state: &mut GameState, + deck_index: usize, + shadow: bool, +) -> Result<(), String> { + let card = match game_state.deck.remove(deck_index) { + Some(card) => card, + None => { + return Err(format!( + "Yoinkmill index out of bounds: {} (shadow: {})", + deck_index, shadow + )) + } + }; game_state.events.push(CountedEvent { id: game_state.events.len(), event: Event::YoinkMill(card.clone()), @@ -192,6 +205,7 @@ pub fn yoink_mill(game_state: &mut GameState, deck_index: usize, shadow: bool) { } else { game_state.discard_pile.push(card); } + Ok(()) } pub fn shuffle(game_state: &mut GameState, player: Player) { @@ -256,7 +270,11 @@ pub fn fade_from_hand(game_state: &mut GameState, hand_index: usize, player: Pla } } -pub fn play_from_hand(game_state: &mut GameState, hand_index: usize, player: Player) { +pub fn play_from_hand( + game_state: &mut GameState, + hand_index: usize, + player: Player, +) -> Result<(), String> { let player_hand = match player { Player::A => &mut game_state.hands[0], Player::B => &mut game_state.hands[1], @@ -265,6 +283,9 @@ pub fn play_from_hand(game_state: &mut GameState, hand_index: usize, player: Pla id: game_state.events.len(), event: Event::PlayFromHand(player.clone()), }); + if None == player_hand.cards.get(hand_index) { + return Err(format!("Yoink index out of bounds: {}", hand_index)); + } let card = player_hand.cards.remove(hand_index); let play_uuid = Uuid::new_v4(); game_state.play.insert( @@ -278,10 +299,14 @@ pub fn play_from_hand(game_state: &mut GameState, hand_index: usize, player: Pla play_id: play_uuid, }, ); + Ok(()) } -pub fn bounce(game_state: &mut GameState, play_id: Uuid) { - let played_card = game_state.play.remove(&play_id).unwrap(); +pub fn bounce(game_state: &mut GameState, play_id: Uuid) -> Result<(), String> { + let played_card = match game_state.play.remove(&play_id) { + Some(played_card) => played_card, + None => return Err(format!("Nonexistant play id: {}", play_id)), + }; let player_hand = match played_card.owner { Player::A => &mut game_state.hands[0], Player::B => &mut game_state.hands[1], @@ -290,22 +315,36 @@ pub fn bounce(game_state: &mut GameState, play_id: Uuid) { id: game_state.events.len(), event: Event::Bounce(played_card.owner), }); - player_hand.cards.push(played_card.id); + Ok(player_hand.cards.push(played_card.id)) } -pub fn tap(game_state: &mut GameState, play_id: Uuid) { +pub fn tap(game_state: &mut GameState, play_id: Uuid) -> Result<(), String> { game_state.events.push(CountedEvent { id: game_state.events.len(), event: Event::Tap(play_id.clone()), }); - let played_card = game_state.play.get_mut(&play_id).unwrap(); - played_card.tapped = !played_card.tapped; + match game_state.play.get_mut(&play_id) { + Some(played_card) => { + played_card.tapped = !played_card.tapped; + Ok(()) + } + None => return Err(format!("Nonexistant play id: {}", play_id)), + } } -pub fn move_played_card(game_state: &mut GameState, play_id: Uuid, position_x: u8, position_y: u8) { - if let Some(played_card) = game_state.play.get_mut(&play_id) { - played_card.position_x = position_x; - played_card.position_y = position_y; +pub fn move_played_card( + game_state: &mut GameState, + play_id: Uuid, + position_x: u8, + position_y: u8, +) -> Result<(), String> { + match game_state.play.get_mut(&play_id) { + Some(played_card) => { + played_card.position_x = position_x; + played_card.position_y = position_y; + Ok(()) + } + None => return Err(format!("Nonexistant play id: {}", play_id)), } } diff --git a/src/main.rs b/src/main.rs index ef196ff..93b2229 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,26 +4,76 @@ mod game; use game::GameState; use game::Player; use rocket::fs::{relative, FileServer}; +use rocket::http::{ContentType, Status}; use rocket::response::content; -use rocket::response::status::BadRequest; use rocket::State; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use uuid::{uuid, Uuid}; +macro_rules! assert_player { + ($player_uuids:expr,$uuid:expr) => { + match $player_uuids.map.get(&$uuid) { + Some(_) => (), + None => { + return ( + Status::Unauthorized, + (ContentType::Plain, format!("Invalid player {}.", $uuid)), + ) + } + } + }; +} + +macro_rules! get_player { + ($player_uuids:expr,$uuid:expr) => { + match $player_uuids.map.get(&$uuid) { + Some(player) => player, + None => { + return ( + Status::Unauthorized, + (ContentType::Plain, format!("Invalid player {}.", $uuid)), + ) + } + } + }; +} + +macro_rules! respond { + ($state:expr) => { + match $state { + Ok(()) => (Status::Ok, (ContentType::Plain, "Ok!".to_string())), + Err(err_string) => (Status::BadRequest, (ContentType::Plain, err_string)), + } + }; +} + +macro_rules! unlock_state { + ($arc:expr,$mutex:ident,$name:ident) => { + let $mutex = Arc::clone(&$arc.state); + let $name = $mutex.lock().unwrap(); + }; +} + +macro_rules! unlock_state_mut { + ($arc:expr,$mutex:ident,$name:ident) => { + let $mutex = Arc::clone(&$arc.state); + let mut $name = $mutex.lock().unwrap(); + }; +} + #[get("/")] fn index(player_uuids: &State) -> content::RawHtml { content::RawHtml(format!( - "localhost:8000/{}
localhost:8000/{}", + "/{}
/{}", player_uuids.a, player_uuids.a, player_uuids.b, player_uuids.b )) } #[get("/get_events")] -fn get_events(game_state_arc: &State) -> String { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let game_state = game_state_mutex.lock().unwrap(); - format!("{}", game::get_events(&game_state)) +fn get_events(game_state_arc: &State) -> content::RawJson { + unlock_state!(game_state_arc, mutex, game_state); + content::RawJson(format!("{}", game::get_events(&game_state))) } #[post("//shuffle")] @@ -31,15 +81,10 @@ fn shuffle( uuid: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - game::shuffle(&mut game_state, player.clone()); - Ok(format!("Deck Shuffled!")) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::shuffle(&mut game_state, player.clone())) } #[post("//pass")] @@ -47,15 +92,10 @@ fn pass( uuid: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::pass(&mut game_state); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::pass(&mut game_state)) } #[post("//bounce/")] @@ -64,15 +104,10 @@ fn bounce( play_id: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::bounce(&mut game_state, play_id); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::bounce(&mut game_state, play_id)) } #[post("//move///")] @@ -83,15 +118,10 @@ fn move_card( y: u8, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::move_played_card(&mut game_state, play_id, x, y); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::move_played_card(&mut game_state, play_id, x, y)) } #[post("//tap/")] @@ -100,15 +130,10 @@ fn tap( play_id: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::tap(&mut game_state, play_id); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::tap(&mut game_state, play_id)) } #[post("//play/")] @@ -117,15 +142,10 @@ fn play( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::play_from_hand(&mut game_state, index, player.clone()); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::play_from_hand(&mut game_state, index, player.clone())) } #[post("//unkill/")] @@ -134,15 +154,10 @@ fn unkill( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::unkill(&mut game_state, player.clone(), index, false); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::unkill(&mut game_state, player.clone(), index, false)) } #[post("//unbanish/")] @@ -151,15 +166,10 @@ fn unbanish( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::unkill(&mut game_state, player.clone(), index, true); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::unkill(&mut game_state, player.clone(), index, true)) } #[post("//discard/")] @@ -168,15 +178,10 @@ fn discard( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::discard(&mut game_state, index, player.clone(), false); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::discard(&mut game_state, index, player.clone(), false)) } #[post("//forget/")] @@ -185,15 +190,10 @@ fn forget( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::discard(&mut game_state, index, player.clone(), true); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::discard(&mut game_state, index, player.clone(), true)) } #[post("//kill/")] @@ -202,15 +202,10 @@ fn kill( play_id: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::kill(&mut game_state, play_id, false); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::kill(&mut game_state, play_id, false)) } #[post("//banish/")] @@ -219,15 +214,10 @@ fn banish( play_id: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::kill(&mut game_state, play_id, true); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::kill(&mut game_state, play_id, true)) } #[post("//unshadow/")] @@ -236,15 +226,10 @@ fn unshadow( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::transfer_dead_card(&mut game_state, index, true); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::transfer_dead_card(&mut game_state, index, true)) } #[post("//yoink/")] @@ -253,15 +238,10 @@ fn yoink( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::yoink(&mut game_state, index, player.clone()); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::yoink(&mut game_state, index, player.clone())) } #[post("//yoink_mill/")] @@ -270,15 +250,10 @@ fn yoink_mill( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::yoink_mill(&mut game_state, index, false); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::yoink_mill(&mut game_state, index, false)) } #[post("//yoink_xmill/")] @@ -287,15 +262,10 @@ fn yoink_xmill( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::yoink_mill(&mut game_state, index, true); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::yoink_mill(&mut game_state, index, true)) } #[post("//undiscard/")] @@ -304,15 +274,15 @@ fn undiscard( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::undiscard(&mut game_state, index, player.clone(), false); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::undiscard( + &mut game_state, + index, + player.clone(), + false + )) } #[post("//remember/")] @@ -321,15 +291,15 @@ fn remember( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::undiscard(&mut game_state, index, player.clone(), true); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::undiscard( + &mut game_state, + index, + player.clone(), + true + )) } #[post("//shadow/")] @@ -338,15 +308,10 @@ fn shadow( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - match player_uuids.map.get(&uuid) { - Some(_) => (), - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - game::transfer_dead_card(&mut game_state, index, false); - Ok(format!("{}", game::get_events(&game_state))) +) -> (Status, (ContentType, String)) { + assert_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::transfer_dead_card(&mut game_state, index, false)) } #[get("//get_state")] @@ -354,17 +319,13 @@ fn get_state( uuid: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - Ok(format!( +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state!(game_state_arc, mutex, game_state); + (Status::Ok, (ContentType::JSON, format!( "{}", game::get_game_one_player(&game_state, player.clone()) - )) + ))) } #[post("//get_deck")] @@ -372,17 +333,13 @@ fn get_deck( uuid: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - Ok(format!( +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + (Status::Ok, (ContentType::JSON, format!( "{}", game::get_deck(&mut game_state, player.clone()) - )) + ))) } #[post("//untap_all")] @@ -390,18 +347,10 @@ fn untap_all( uuid: Uuid, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - game::untap_all(&mut game_state, player.clone()); - Ok(format!( - "{}", - game::get_game_one_player(&game_state, player.clone()) - )) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::untap_all(&mut game_state, player.clone())) } #[post("//draw/")] @@ -410,18 +359,10 @@ fn draw( count: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - game::draw(&mut game_state, count, player.clone()); - Ok(format!( - "{}", - game::get_game_one_player(&game_state, player.clone()) - )) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::draw(&mut game_state, count, player.clone())) } #[post("//fade/")] @@ -430,37 +371,31 @@ fn fade( index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - game::fade_from_hand(&mut game_state, index, player.clone(), false); - Ok(format!( - "{}", - game::get_game_one_player(&game_state, player.clone()) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::fade_from_hand( + &mut game_state, + index, + player.clone(), + false )) } -#[post("//fade-bottom/")] +#[post("//fade_bottom/")] fn fade_bottom( uuid: Uuid, index: usize, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - game::fade_from_hand(&mut game_state, index, player.clone(), true); - Ok(format!( - "{}", - game::get_game_one_player(&game_state, player.clone()) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::fade_from_hand( + &mut game_state, + index, + player.clone(), + true )) } @@ -470,18 +405,10 @@ fn life( count: i32, game_state_arc: &State, player_uuids: &State, -) -> Result> { - let game_state_mutex = Arc::clone(&game_state_arc.state); - let mut game_state = game_state_mutex.lock().unwrap(); - let player = match player_uuids.map.get(&uuid) { - Some(player) => player, - None => return Err(BadRequest(format!("Invalid player {}.", uuid))), - }; - game::change_life(&mut game_state, count, player.clone()); - Ok(format!( - "{}", - game::get_game_one_player(&game_state, player.clone()) - )) +) -> (Status, (ContentType, String)) { + let player = get_player!(player_uuids, uuid); + unlock_state_mut!(game_state_arc, mutex, game_state); + respond!(game::change_life(&mut game_state, count, player.clone())) } struct ArcMutexGameState { diff --git a/web/src/script.ls b/web/src/script.ls index 3c54c11..165a603 100644 --- a/web/src/script.ls +++ b/web/src/script.ls @@ -6,6 +6,7 @@ console.log it it ) .catch err + window.fetch-post = fetch-post const $opponent-hand = document.query-selector '#opponent-hand' const $own-hand = document.query-selector '#own-hand' const $view-card = document.query-selector '#view-card' @@ -51,7 +52,8 @@ const get-play-size = -> $play.get-bounding-client-rect! const gen-array = -> Array.from { length: it }, (_, i) -> i const gen-array-of = (it, val) -> Array.from { length: it }, -> val - const cards = await fetch \./cards.json .then (.json!) + const cards = await fetch \/cards.json .then (.json!) + window.cards = cards const $make-card = -> $img = document.create-element \img $img.src = it