Compare commits
9 Commits
0aa5dbd899
...
meta
| Author | SHA1 | Date | |
|---|---|---|---|
| 89856cc32b | |||
| 67cc148bb8 | |||
| 159126e7ca | |||
| e329b1f223 | |||
| e0eb67b299 | |||
| 066a9472ac | |||
| 67057773ae | |||
| be5a969600 | |||
| 02f7d391fc |
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,4 +3,4 @@ Rocket.toml
|
|||||||
/web/dynamic/script.js
|
/web/dynamic/script.js
|
||||||
/web/dynamic/style.css
|
/web/dynamic/style.css
|
||||||
/web/static
|
/web/static
|
||||||
|
.env
|
||||||
|
|||||||
12
README.md
12
README.md
@ -5,8 +5,14 @@ specifically designed to be used when players share a deck
|
|||||||
definitely not a shameless untap clone
|
definitely not a shameless untap clone
|
||||||
|
|
||||||
# todo
|
# todo
|
||||||
+ Add drag-and-drop from hand and piles
|
+ Add hotkeys for every zone (Basically done!)
|
||||||
+ Add hotkeys for every zone
|
+ ~~Add untap all hotkey~~
|
||||||
+ Add untap all hotkey
|
|
||||||
+ Add viewing top of deck
|
+ Add viewing top of deck
|
||||||
|
+ ~~Add searching deck~~
|
||||||
|
+ ~~Add score values (usable for life counters)~~
|
||||||
|
+ Make events visible
|
||||||
|
|
||||||
|
# maybe todo
|
||||||
|
+ ~~Add drag-and-drop from hand and piles~~
|
||||||
|
+ Make deck information, piles, and score info json-configurable
|
||||||
+ Use cookies instead of hardcoded url uuid
|
+ Use cookies instead of hardcoded url uuid
|
||||||
|
|||||||
2
build
2
build
@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
mkdir -p web/static
|
mkdir -p web/static
|
||||||
|
touch .env web/static/userstyle.css
|
||||||
|
source .env
|
||||||
lsc --no-header -bco web/dynamic/ web/src/script.ls || exit 1
|
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
|
npx sass --no-source-map web/src/style.scss web/dynamic/style.css || exit 2
|
||||||
rustfmt src/*.rs || exit 3
|
rustfmt src/*.rs || exit 3
|
||||||
|
|||||||
202
src/cards.json
202
src/cards.json
@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
1
src/cards.json
Symbolic link
1
src/cards.json
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/seoxi/Dev/rust/dandadan/src/cards_nuts.json
|
||||||
@ -1,152 +1,202 @@
|
|||||||
{
|
[
|
||||||
"3df927c0-9aa9-450b-ab2a-ae12c5489d57": {
|
{
|
||||||
|
"id": "{3df927c0-9aa9-450b-ab2a-ae12c5489d57}",
|
||||||
"name": "Supplant Form",
|
"name": "Supplant Form",
|
||||||
"png": "https://cards.scryfall.io/png/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.png?1562824437",
|
"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",
|
"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",
|
"name": "Memory Lapse",
|
||||||
"png": "https://cards.scryfall.io/png/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.png?1634131658",
|
"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",
|
"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",
|
"name": "Accumulated Knowledge",
|
||||||
"png": "https://cards.scryfall.io/png/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.png?1562439718",
|
"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",
|
"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",
|
"name": "Mystic Retrieval",
|
||||||
"png": "https://cards.scryfall.io/png/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.png?1736467785",
|
"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",
|
"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",
|
"name": "Mystical Tutor",
|
||||||
"png": "https://cards.scryfall.io/png/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.png?1675199375",
|
"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",
|
"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",
|
"name": "Svyelunite Temple",
|
||||||
"png": "https://cards.scryfall.io/png/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.png?1562867851",
|
"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",
|
"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",
|
"name": "Brainstorm",
|
||||||
"png": "https://cards.scryfall.io/png/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.png?1726284649",
|
"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",
|
"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",
|
"name": "Dance of the Skywise",
|
||||||
"png": "https://cards.scryfall.io/png/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.png?1562792779",
|
"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",
|
"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",
|
"name": "Island",
|
||||||
"png": "https://cards.scryfall.io/png/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.png?1655495395",
|
"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",
|
"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",
|
"name": "Ray of Command",
|
||||||
"png": "https://cards.scryfall.io/png/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.png?1592754536",
|
"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",
|
"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",
|
"name": "Unsubstantiate",
|
||||||
"png": "https://cards.scryfall.io/png/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.png?1594735888",
|
"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",
|
"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",
|
"name": "Dandân",
|
||||||
"png": "https://cards.scryfall.io/png/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.png?1562781784",
|
"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",
|
"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",
|
"name": "Mind Bend",
|
||||||
"png": "https://cards.scryfall.io/png/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.png?1562549129",
|
"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",
|
"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",
|
"name": "Remote Isle",
|
||||||
"png": "https://cards.scryfall.io/png/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.png?1709139228",
|
"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",
|
"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",
|
"name": "Insidious Will",
|
||||||
"png": "https://cards.scryfall.io/png/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.png?1576381260",
|
"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",
|
"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",
|
"name": "Izzet Boilerworks",
|
||||||
"png": "https://cards.scryfall.io/png/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.png?1712355049",
|
"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",
|
"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",
|
"name": "Predict",
|
||||||
"png": "https://cards.scryfall.io/png/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.png?1592710621",
|
"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",
|
"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",
|
"name": "Diminishing Returns",
|
||||||
"png": "https://cards.scryfall.io/png/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.png?1580013930",
|
"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",
|
"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",
|
"name": "Vision Charm",
|
||||||
"png": "https://cards.scryfall.io/png/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.png?1562277700",
|
"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",
|
"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",
|
"name": "Crystal Spray",
|
||||||
"png": "https://cards.scryfall.io/png/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.png?1562922403",
|
"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",
|
"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",
|
"name": "Halimar Depths",
|
||||||
"png": "https://cards.scryfall.io/png/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.png?1726285434",
|
"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",
|
"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",
|
"name": "Metamorphose",
|
||||||
"png": "https://cards.scryfall.io/png/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.png?1562533013",
|
"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",
|
"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",
|
"name": "Mystic Sanctuary",
|
||||||
"png": "https://cards.scryfall.io/png/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.png?1706241201",
|
"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",
|
"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",
|
"name": "Lonely Sandbar",
|
||||||
"png": "https://cards.scryfall.io/png/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.png?1708054955",
|
"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",
|
"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",
|
"name": "Temple of Epiphany",
|
||||||
"png": "https://cards.scryfall.io/png/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.png?1730491248",
|
"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",
|
"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
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
1
src/cards_nuts.json
Normal file
1
src/cards_nuts.json
Normal file
File diff suppressed because one or more lines are too long
398
src/game.rs
398
src/game.rs
@ -16,7 +16,7 @@ struct CardInfo {
|
|||||||
count: usize,
|
count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Player {
|
pub enum Player {
|
||||||
A,
|
A,
|
||||||
B,
|
B,
|
||||||
@ -47,14 +47,23 @@ enum Event {
|
|||||||
Shuffle(Player),
|
Shuffle(Player),
|
||||||
Draw(Player, usize),
|
Draw(Player, usize),
|
||||||
Pass(Player, Player),
|
Pass(Player, Player),
|
||||||
ChangeLifeTotal(Player, i32, i32),
|
ChangeLifeTotal(Player, isize, isize),
|
||||||
|
FadeFromPlay(Uuid, bool),
|
||||||
|
FadeFromPile(Uuid, bool),
|
||||||
FadeFromHand(Player),
|
FadeFromHand(Player),
|
||||||
PlayFromHand(Player),
|
PlayFromHand(Player),
|
||||||
Bounce(Player),
|
Bounce(Player),
|
||||||
Tap(Uuid),
|
Tap(Uuid),
|
||||||
|
TransferOwnership(Uuid),
|
||||||
Discard(Player),
|
Discard(Player),
|
||||||
|
Undiscard(Player),
|
||||||
Kill(Uuid, bool),
|
Kill(Uuid, bool),
|
||||||
Unkill(Uuid, bool),
|
Unkill(Uuid, bool),
|
||||||
|
TransferDeadCard(Uuid, bool),
|
||||||
|
UntapAll(Player),
|
||||||
|
ViewDeck(Player),
|
||||||
|
Yoink(Player),
|
||||||
|
YoinkMill(Uuid),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone, Debug)]
|
#[derive(Serialize, Clone, Debug)]
|
||||||
@ -63,6 +72,15 @@ struct CountedEvent {
|
|||||||
event: Event,
|
event: Event,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! gen_event {
|
||||||
|
($list:expr,$event:expr) => {
|
||||||
|
$list.push(CountedEvent {
|
||||||
|
id: $list.len(),
|
||||||
|
event: $event,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
deck: VecDeque<Uuid>,
|
deck: VecDeque<Uuid>,
|
||||||
@ -70,7 +88,7 @@ pub struct GameState {
|
|||||||
shadow_realm: Vec<Uuid>,
|
shadow_realm: Vec<Uuid>,
|
||||||
play: HashMap<Uuid, InPlay>,
|
play: HashMap<Uuid, InPlay>,
|
||||||
hands: Vec<Hand>,
|
hands: Vec<Hand>,
|
||||||
life_totals: Vec<i32>,
|
life_totals: Vec<isize>,
|
||||||
turn_player: Player,
|
turn_player: Player,
|
||||||
events: Vec<CountedEvent>,
|
events: Vec<CountedEvent>,
|
||||||
}
|
}
|
||||||
@ -81,7 +99,7 @@ pub struct OnePlayerGameState {
|
|||||||
shadow_realm: Vec<Uuid>,
|
shadow_realm: Vec<Uuid>,
|
||||||
play: Vec<InPlay>,
|
play: Vec<InPlay>,
|
||||||
hand: Vec<Uuid>,
|
hand: Vec<Uuid>,
|
||||||
life_totals: Vec<i32>,
|
life_totals: Vec<isize>,
|
||||||
turn_player: Player,
|
turn_player: Player,
|
||||||
opponent_cards_in_hand: usize,
|
opponent_cards_in_hand: usize,
|
||||||
you: Player,
|
you: Player,
|
||||||
@ -114,10 +132,6 @@ const CARD_DATA_JSON: &str = include_str!("cards.json");
|
|||||||
|
|
||||||
pub fn new() -> GameState {
|
pub fn new() -> GameState {
|
||||||
let parsed_cards: Vec<CardInfo> = serde_json::from_str(CARD_DATA_JSON).unwrap();
|
let parsed_cards: Vec<CardInfo> = serde_json::from_str(CARD_DATA_JSON).unwrap();
|
||||||
/*let card_map = parsed_cards
|
|
||||||
.into_iter()
|
|
||||||
.map(|card| (card.id, card))
|
|
||||||
.collect::<HashMap<_, _>>();*/
|
|
||||||
let starting_player = match random_bool(0.5) {
|
let starting_player = match random_bool(0.5) {
|
||||||
true => Player::A,
|
true => Player::A,
|
||||||
_ => Player::B,
|
_ => Player::B,
|
||||||
@ -148,28 +162,60 @@ pub fn new() -> GameState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(game_state: &mut GameState, count: usize, player: Player) {
|
pub fn draw(game_state: &mut GameState, count: usize, player: Player) -> Result<(), String> {
|
||||||
let player_hand = match player {
|
let player_hand = match player {
|
||||||
Player::A => &mut game_state.hands[0],
|
Player::A => &mut game_state.hands[0],
|
||||||
_ => &mut game_state.hands[1],
|
Player::B => &mut game_state.hands[1],
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
let remaining_cards =
|
||||||
id: game_state.events.len(),
|
std::cmp::max(0, game_state.deck.len() as isize - count as isize) as usize;
|
||||||
event: Event::Draw(player.clone(), count.clone()),
|
gen_event!(
|
||||||
});
|
game_state.events,
|
||||||
player_hand.cards.extend_from_slice(
|
Event::Draw(player.clone(), count.clone())
|
||||||
game_state
|
|
||||||
.deck
|
|
||||||
.split_off(game_state.deck.len() - count)
|
|
||||||
.make_contiguous(),
|
|
||||||
);
|
);
|
||||||
|
player_hand
|
||||||
|
.cards
|
||||||
|
.extend_from_slice(game_state.deck.split_off(remaining_cards).make_contiguous());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
|
Player::B => &mut game_state.hands[1],
|
||||||
|
};
|
||||||
|
gen_event!(game_state.events, Event::Yoink(player.clone()));
|
||||||
|
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,
|
||||||
|
) -> 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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen_event!(game_state.events, Event::YoinkMill(card.clone()));
|
||||||
|
if shadow {
|
||||||
|
game_state.shadow_realm.push(card);
|
||||||
|
} else {
|
||||||
|
game_state.discard_pile.push(card);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shuffle(game_state: &mut GameState, player: Player) {
|
pub fn shuffle(game_state: &mut GameState, player: Player) {
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(game_state.events, Event::Shuffle(player));
|
||||||
id: game_state.events.len(),
|
|
||||||
event: Event::Shuffle(player),
|
|
||||||
});
|
|
||||||
game_state.deck.make_contiguous().shuffle(&mut rng());
|
game_state.deck.make_contiguous().shuffle(&mut rng());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,22 +233,22 @@ pub fn pass(game_state: &mut GameState) {
|
|||||||
Player::A => Player::B,
|
Player::A => Player::B,
|
||||||
Player::B => Player::A,
|
Player::B => Player::A,
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(
|
||||||
id: game_state.events.len(),
|
game_state.events,
|
||||||
event: Event::Pass(*current_player, new_player.clone()),
|
Event::Pass(*current_player, new_player.clone())
|
||||||
});
|
);
|
||||||
game_state.turn_player = new_player;
|
game_state.turn_player = new_player;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_life(game_state: &mut GameState, diff: i32, player: Player) {
|
pub fn change_life(game_state: &mut GameState, diff: isize, player: Player) {
|
||||||
let mut player_life = match player {
|
let mut player_life = match player {
|
||||||
Player::A => game_state.life_totals[0].clone(),
|
Player::A => game_state.life_totals[0].clone(),
|
||||||
Player::B => game_state.life_totals[1].clone(),
|
Player::B => game_state.life_totals[1].clone(),
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(
|
||||||
id: game_state.events.len(),
|
game_state.events,
|
||||||
event: Event::ChangeLifeTotal(player, player_life.clone(), player_life.clone() + diff),
|
Event::ChangeLifeTotal(player, player_life.clone(), player_life.clone() + diff)
|
||||||
});
|
);
|
||||||
player_life += diff;
|
player_life += diff;
|
||||||
match player {
|
match player {
|
||||||
Player::A => game_state.life_totals[0] = player_life,
|
Player::A => game_state.life_totals[0] = player_life,
|
||||||
@ -210,34 +256,99 @@ pub fn change_life(game_state: &mut GameState, diff: i32, player: Player) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fade_from_hand(game_state: &mut GameState, hand_index: usize, player: Player, bottom: bool) {
|
pub fn fade_from_play(
|
||||||
|
game_state: &mut GameState,
|
||||||
|
play_id: Uuid,
|
||||||
|
bottom: bool,
|
||||||
|
) -> 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)),
|
||||||
|
};
|
||||||
|
gen_event!(
|
||||||
|
game_state.events,
|
||||||
|
Event::FadeFromPlay(played_card.id.clone(), bottom)
|
||||||
|
);
|
||||||
|
if bottom {
|
||||||
|
game_state.deck.push_front(played_card.id);
|
||||||
|
} else {
|
||||||
|
game_state.deck.push_back(played_card.id);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fade_from_pile(
|
||||||
|
game_state: &mut GameState,
|
||||||
|
pile_index: usize,
|
||||||
|
shadow: bool,
|
||||||
|
bottom: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let pile = if shadow {
|
||||||
|
&mut game_state.shadow_realm
|
||||||
|
} else {
|
||||||
|
&mut game_state.discard_pile
|
||||||
|
};
|
||||||
|
if None == pile.get(pile_index) {
|
||||||
|
let pile_name = if shadow {
|
||||||
|
"shadow realm"
|
||||||
|
} else {
|
||||||
|
"discard pile"
|
||||||
|
};
|
||||||
|
return Err(format!(
|
||||||
|
"Unkill index out of bounds: {} ({})",
|
||||||
|
pile_index, pile_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let card = pile.remove(pile_index);
|
||||||
|
gen_event!(game_state.events, Event::FadeFromPile(card.clone(), shadow));
|
||||||
|
if bottom {
|
||||||
|
game_state.deck.push_front(card);
|
||||||
|
} else {
|
||||||
|
game_state.deck.push_back(card);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fade_from_hand(
|
||||||
|
game_state: &mut GameState,
|
||||||
|
hand_index: usize,
|
||||||
|
player: Player,
|
||||||
|
bottom: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
let player_hand = match player {
|
let player_hand = match player {
|
||||||
Player::A => &mut game_state.hands[0],
|
Player::A => &mut game_state.hands[0],
|
||||||
Player::B => &mut game_state.hands[1],
|
Player::B => &mut game_state.hands[1],
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(game_state.events, Event::FadeFromHand(player.clone()));
|
||||||
id: game_state.events.len(),
|
|
||||||
event: Event::FadeFromHand(player.clone()),
|
if None == player_hand.cards.get(hand_index) {
|
||||||
});
|
return Err(format!("Fade index out of bounds: {}", hand_index));
|
||||||
|
}
|
||||||
let card = player_hand.cards.remove(hand_index);
|
let card = player_hand.cards.remove(hand_index);
|
||||||
if bottom {
|
if bottom {
|
||||||
game_state.deck.push_front(card);
|
game_state.deck.push_front(card);
|
||||||
} else {
|
} else {
|
||||||
game_state.deck.push_back(card);
|
game_state.deck.push_back(card);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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<Uuid, String> {
|
||||||
let player_hand = match player {
|
let player_hand = match player {
|
||||||
Player::A => &mut game_state.hands[0],
|
Player::A => &mut game_state.hands[0],
|
||||||
Player::B => &mut game_state.hands[1],
|
Player::B => &mut game_state.hands[1],
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(game_state.events, Event::PlayFromHand(player.clone()));
|
||||||
id: game_state.events.len(),
|
if None == player_hand.cards.get(hand_index) {
|
||||||
event: Event::PlayFromHand(player.clone()),
|
return Err(format!("Play index out of bounds: {}", hand_index));
|
||||||
});
|
}
|
||||||
let card = player_hand.cards.remove(hand_index);
|
let card = player_hand.cards.remove(hand_index);
|
||||||
let play_uuid = Uuid::new_v4();
|
let play_uuid = Uuid::new_v4();
|
||||||
|
let return_uuid = play_uuid.clone();
|
||||||
game_state.play.insert(
|
game_state.play.insert(
|
||||||
play_uuid,
|
play_uuid,
|
||||||
InPlay {
|
InPlay {
|
||||||
@ -249,79 +360,154 @@ pub fn play_from_hand(game_state: &mut GameState, hand_index: usize, player: Pla
|
|||||||
play_id: play_uuid,
|
play_id: play_uuid,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
Ok(return_uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bounce(game_state: &mut GameState, play_id: Uuid) {
|
pub fn bounce(game_state: &mut GameState, play_id: Uuid) -> Result<(), String> {
|
||||||
let played_card = game_state.play.remove(&play_id).unwrap();
|
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 {
|
let player_hand = match played_card.owner {
|
||||||
Player::A => &mut game_state.hands[0],
|
Player::A => &mut game_state.hands[0],
|
||||||
Player::B => &mut game_state.hands[1],
|
Player::B => &mut game_state.hands[1],
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(game_state.events, Event::Bounce(played_card.owner));
|
||||||
id: game_state.events.len(),
|
Ok(player_hand.cards.push(played_card.id))
|
||||||
event: Event::Bounce(played_card.owner),
|
|
||||||
});
|
|
||||||
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> {
|
||||||
//let played_card = game_state.play.remove(&play_id).unwrap();
|
gen_event!(game_state.events, Event::Tap(play_id.clone()));
|
||||||
game_state.events.push(CountedEvent {
|
match game_state.play.get_mut(&play_id) {
|
||||||
id: game_state.events.len(),
|
Some(played_card) => {
|
||||||
event: Event::Tap(play_id.clone()),
|
played_card.tapped = !played_card.tapped;
|
||||||
});
|
Ok(())
|
||||||
let played_card = game_state.play.get_mut(&play_id).unwrap();
|
}
|
||||||
played_card.tapped = !played_card.tapped;
|
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 discard(game_state: &mut GameState, hand_index: usize, player: Player, shadow: bool) {
|
pub fn transfer_ownership(game_state: &mut GameState, play_id: Uuid) -> Result<(), String> {
|
||||||
|
gen_event!(game_state.events, Event::TransferOwnership(play_id.clone()));
|
||||||
|
match game_state.play.get_mut(&play_id) {
|
||||||
|
Some(played_card) => {
|
||||||
|
if played_card.owner == Player::A {
|
||||||
|
played_card.owner = Player::B;
|
||||||
|
} else {
|
||||||
|
played_card.owner = Player::A;
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
// no event made, intentionally
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard(
|
||||||
|
game_state: &mut GameState,
|
||||||
|
hand_index: usize,
|
||||||
|
player: Player,
|
||||||
|
shadow: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
let player_hand = match player {
|
let player_hand = match player {
|
||||||
Player::A => &mut game_state.hands[0],
|
Player::A => &mut game_state.hands[0],
|
||||||
Player::B => &mut game_state.hands[1],
|
Player::B => &mut game_state.hands[1],
|
||||||
};
|
};
|
||||||
game_state.events.push(CountedEvent {
|
gen_event!(game_state.events, Event::Discard(player.clone()));
|
||||||
id: game_state.events.len(),
|
if None == player_hand.cards.get(hand_index) {
|
||||||
event: Event::Discard(player.clone()),
|
return Err(format!("Discard index out of bounds: {}", hand_index));
|
||||||
});
|
}
|
||||||
let card = player_hand.cards.remove(hand_index);
|
let card = player_hand.cards.remove(hand_index);
|
||||||
if shadow {
|
if shadow {
|
||||||
game_state.shadow_realm.push(card);
|
game_state.shadow_realm.push(card);
|
||||||
} else {
|
} else {
|
||||||
game_state.discard_pile.push(card);
|
game_state.discard_pile.push(card);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill(game_state: &mut GameState, play_id: Uuid, shadow: bool) {
|
pub fn undiscard(
|
||||||
let played_card = game_state.play.remove(&play_id).unwrap();
|
game_state: &mut GameState,
|
||||||
game_state.events.push(CountedEvent {
|
pile_index: usize,
|
||||||
id: game_state.events.len(),
|
player: Player,
|
||||||
event: Event::Kill(played_card.id.clone(), shadow),
|
shadow: bool,
|
||||||
});
|
) -> Result<(), String> {
|
||||||
|
let player_hand = match player {
|
||||||
|
Player::A => &mut game_state.hands[0],
|
||||||
|
Player::B => &mut game_state.hands[1],
|
||||||
|
};
|
||||||
|
let pile = if shadow {
|
||||||
|
&mut game_state.shadow_realm
|
||||||
|
} else {
|
||||||
|
&mut game_state.discard_pile
|
||||||
|
};
|
||||||
|
if None == pile.get(pile_index) {
|
||||||
|
return Err(format!("Discard index out of bounds: {}", pile_index));
|
||||||
|
}
|
||||||
|
gen_event!(game_state.events, Event::Undiscard(player.clone()));
|
||||||
|
player_hand.cards.push(pile.remove(pile_index));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill(game_state: &mut GameState, play_id: Uuid, shadow: bool) -> 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)),
|
||||||
|
};
|
||||||
|
gen_event!(
|
||||||
|
game_state.events,
|
||||||
|
Event::Kill(played_card.id.clone(), shadow)
|
||||||
|
);
|
||||||
if shadow {
|
if shadow {
|
||||||
game_state.shadow_realm.push(played_card.id);
|
game_state.shadow_realm.push(played_card.id);
|
||||||
} else {
|
} else {
|
||||||
game_state.discard_pile.push(played_card.id);
|
game_state.discard_pile.push(played_card.id);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unkill(game_state: &mut GameState, player: Player, pile_index: usize, shadow: bool) {
|
pub fn unkill(
|
||||||
let card = if shadow {
|
game_state: &mut GameState,
|
||||||
game_state.shadow_realm.remove(pile_index)
|
player: Player,
|
||||||
|
pile_index: usize,
|
||||||
|
shadow: bool,
|
||||||
|
) -> Result<Uuid, String> {
|
||||||
|
let pile = if shadow {
|
||||||
|
&mut game_state.shadow_realm
|
||||||
} else {
|
} else {
|
||||||
game_state.discard_pile.remove(pile_index)
|
&mut game_state.discard_pile
|
||||||
};
|
};
|
||||||
|
if None == pile.get(pile_index) {
|
||||||
|
let pile_name = if shadow {
|
||||||
|
"shadow realm"
|
||||||
|
} else {
|
||||||
|
"discard pile"
|
||||||
|
};
|
||||||
|
return Err(format!(
|
||||||
|
"Unkill index out of bounds: {} ({})",
|
||||||
|
pile_index, pile_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let card = pile.remove(pile_index);
|
||||||
|
gen_event!(game_state.events, Event::Unkill(card, shadow));
|
||||||
let play_uuid = Uuid::new_v4();
|
let play_uuid = Uuid::new_v4();
|
||||||
game_state.events.push(CountedEvent {
|
let return_uuid = play_uuid.clone();
|
||||||
id: game_state.events.len(),
|
|
||||||
event: Event::Unkill(card, shadow),
|
|
||||||
});
|
|
||||||
game_state.play.insert(
|
game_state.play.insert(
|
||||||
play_uuid,
|
play_uuid,
|
||||||
InPlay {
|
InPlay {
|
||||||
@ -333,4 +519,52 @@ pub fn unkill(game_state: &mut GameState, player: Player, pile_index: usize, sha
|
|||||||
play_id: play_uuid,
|
play_id: play_uuid,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
Ok(return_uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_dead_card(
|
||||||
|
game_state: &mut GameState,
|
||||||
|
pile_index: usize,
|
||||||
|
shadow_to_discard: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let (source, target) = if shadow_to_discard {
|
||||||
|
(&mut game_state.shadow_realm, &mut game_state.discard_pile)
|
||||||
|
} else {
|
||||||
|
(&mut game_state.discard_pile, &mut game_state.shadow_realm)
|
||||||
|
};
|
||||||
|
|
||||||
|
if None == source.get(pile_index) {
|
||||||
|
let direction = if shadow_to_discard {
|
||||||
|
"shadow realm -> discard pile"
|
||||||
|
} else {
|
||||||
|
"discard pile -> shadow realm"
|
||||||
|
};
|
||||||
|
return Err(format!(
|
||||||
|
"Transfer index out of bounds: {} ({})",
|
||||||
|
pile_index, direction
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let card = source.remove(pile_index);
|
||||||
|
let card_id = card.clone();
|
||||||
|
gen_event!(
|
||||||
|
game_state.events,
|
||||||
|
Event::TransferDeadCard(card_id, shadow_to_discard)
|
||||||
|
);
|
||||||
|
target.push(card);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn untap_all(game_state: &mut GameState, player: Player) {
|
||||||
|
gen_event!(game_state.events, Event::UntapAll(player));
|
||||||
|
for (_, card) in game_state.play.iter_mut() {
|
||||||
|
if card.owner == player {
|
||||||
|
card.tapped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_deck(game_state: &mut GameState, player: Player) -> String {
|
||||||
|
gen_event!(game_state.events, Event::ViewDeck(player));
|
||||||
|
let deck_copy = game_state.deck.clone();
|
||||||
|
serde_json::to_string(&deck_copy).unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
649
src/main.rs
649
src/main.rs
@ -4,26 +4,82 @@ mod game;
|
|||||||
use game::GameState;
|
use game::GameState;
|
||||||
use game::Player;
|
use game::Player;
|
||||||
use rocket::fs::{relative, FileServer};
|
use rocket::fs::{relative, FileServer};
|
||||||
|
use rocket::http::{ContentType, Status};
|
||||||
use rocket::response::content;
|
use rocket::response::content;
|
||||||
use rocket::response::status::BadRequest;
|
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use uuid::{uuid, Uuid};
|
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_ok {
|
||||||
|
() => {
|
||||||
|
(Status::Ok, (ContentType::Plain, "Ok!".to_string()))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! respond {
|
||||||
|
($state:expr) => {
|
||||||
|
match $state {
|
||||||
|
Ok(_) => respond_ok!(),
|
||||||
|
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("/")]
|
#[get("/")]
|
||||||
fn index(player_uuids: &State<PlayerUuids>) -> content::RawHtml<String> {
|
fn index(player_uuids: &State<PlayerUuids>) -> content::RawHtml<String> {
|
||||||
content::RawHtml(format!(
|
content::RawHtml(format!(
|
||||||
"<a href=\"localhost:8000/{}/\">localhost:8000/{}</a><br /><a href=\"localhost:8000/{}/\">localhost:8000/{}</a>",
|
"<a href=\"/{}/\">/{}</a><br /><a href=\"/{}/\">/{}</a>",
|
||||||
player_uuids.a, player_uuids.a, player_uuids.b, player_uuids.b
|
player_uuids.a, player_uuids.a, player_uuids.b, player_uuids.b
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/get_events")]
|
#[get("/get_events")]
|
||||||
fn get_events(game_state_arc: &State<ArcMutexGameState>) -> String {
|
fn get_events(game_state_arc: &State<ArcMutexGameState>) -> content::RawJson<String> {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
unlock_state!(game_state_arc, mutex, game_state);
|
||||||
let game_state = game_state_mutex.lock().unwrap();
|
content::RawJson(format!("{}", game::get_events(&game_state)))
|
||||||
format!("{}", game::get_events(&game_state))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<uuid>/shuffle")]
|
#[post("/<uuid>/shuffle")]
|
||||||
@ -31,51 +87,38 @@ fn shuffle(
|
|||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
let player = get_player!(player_uuids, uuid);
|
||||||
let mut game_state = game_state_mutex.lock().unwrap();
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
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());
|
game::shuffle(&mut game_state, player.clone());
|
||||||
Ok(format!("Deck Shuffled!"))
|
respond_ok!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/pass")]
|
#[post("/<uuid>/pass")]
|
||||||
fn pass(
|
fn pass(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
match player_uuids.map.get(&uuid) {
|
assert_player!(player_uuids, uuid);
|
||||||
Some(_) => (),
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
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);
|
game::pass(&mut game_state);
|
||||||
Ok(format!("{}", game::get_events(&game_state)))
|
respond_ok!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/bounce/<play_id>")]
|
#[post("/<uuid>/bounce/<play_id>")]
|
||||||
fn bounce(
|
fn bounce(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
play_id: Uuid,
|
play_id: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
match player_uuids.map.get(&uuid) {
|
assert_player!(player_uuids, uuid);
|
||||||
Some(_) => (),
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::bounce(&mut game_state, play_id))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/move/<play_id>/<x>/<y>")]
|
#[post("/<uuid>/move/<play_id>/<x>/<y>")]
|
||||||
fn move_card(
|
fn move_card(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
play_id: Uuid,
|
play_id: Uuid,
|
||||||
@ -83,151 +126,314 @@ fn move_card(
|
|||||||
y: u8,
|
y: u8,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
match player_uuids.map.get(&uuid) {
|
assert_player!(player_uuids, uuid);
|
||||||
Some(_) => (),
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::move_played_card(&mut game_state, play_id, x, y))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/tap/<play_id>")]
|
#[post("/<uuid>/tap/<play_id>")]
|
||||||
fn tap(
|
fn tap(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
play_id: Uuid,
|
play_id: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
match player_uuids.map.get(&uuid) {
|
assert_player!(player_uuids, uuid);
|
||||||
Some(_) => (),
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::tap(&mut game_state, play_id))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/play/<index>")]
|
#[post("/<uuid>/transfer_ownership/<play_id>")]
|
||||||
|
fn transfer_ownership(
|
||||||
|
uuid: Uuid,
|
||||||
|
play_id: Uuid,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
assert_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
respond!(game::transfer_ownership(&mut game_state, play_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/play/<index>")]
|
||||||
fn play(
|
fn play(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
let player = get_player!(player_uuids, uuid);
|
||||||
Some(player) => player,
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::play_from_hand(&mut game_state, index, player.clone()))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/unkill/<index>")]
|
#[post("/<uuid>/play_at/<index>/<x>/<y>")]
|
||||||
|
fn play_at(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
x: u8,
|
||||||
|
y: u8,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
let player = get_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
let res = game::play_from_hand(&mut game_state, index, player.clone());
|
||||||
|
match res {
|
||||||
|
Ok(play_id) => respond!(game::move_played_card(&mut game_state, play_id, x, y)),
|
||||||
|
Err(_) => respond!(res),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/unkill/<index>")]
|
||||||
fn unkill(
|
fn unkill(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
let player = get_player!(player_uuids, uuid);
|
||||||
Some(player) => player,
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::unkill(&mut game_state, player.clone(), index, false))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/unbanish/<index>")]
|
#[post("/<uuid>/unbanish/<index>")]
|
||||||
fn unbanish(
|
fn unbanish(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
let player = get_player!(player_uuids, uuid);
|
||||||
Some(player) => player,
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::unkill(&mut game_state, player.clone(), index, true))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/discard/<index>")]
|
#[post("/<uuid>/unkill_at/<index>/<x>/<y>")]
|
||||||
|
fn unkill_at(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
x: u8,
|
||||||
|
y: u8,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
let player = get_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
let res = game::unkill(&mut game_state, player.clone(), index, false);
|
||||||
|
match res {
|
||||||
|
Ok(play_id) => respond!(game::move_played_card(&mut game_state, play_id, x, y)),
|
||||||
|
Err(_) => respond!(res),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/unbanish_at/<index>/<x>/<y>")]
|
||||||
|
fn unbanish_at(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
x: u8,
|
||||||
|
y: u8,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
let player = get_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
let res = game::unkill(&mut game_state, player.clone(), index, true);
|
||||||
|
match res {
|
||||||
|
Ok(play_id) => respond!(game::move_played_card(&mut game_state, play_id, x, y)),
|
||||||
|
Err(_) => respond!(res),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/discard/<index>")]
|
||||||
fn discard(
|
fn discard(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
let player = get_player!(player_uuids, uuid);
|
||||||
Some(player) => player,
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::discard(&mut game_state, index, player.clone(), false))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/forget/<index>")]
|
#[post("/<uuid>/forget/<index>")]
|
||||||
fn forget(
|
fn forget(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
let player = get_player!(player_uuids, uuid);
|
||||||
Some(player) => player,
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::discard(&mut game_state, index, player.clone(), true))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/kill/<play_id>")]
|
#[post("/<uuid>/kill/<play_id>")]
|
||||||
fn kill(
|
fn kill(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
play_id: Uuid,
|
play_id: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
match player_uuids.map.get(&uuid) {
|
assert_player!(player_uuids, uuid);
|
||||||
Some(_) => (),
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::kill(&mut game_state, play_id, false))
|
||||||
};
|
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/banish/<play_id>")]
|
#[post("/<uuid>/banish/<play_id>")]
|
||||||
fn banish(
|
fn banish(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
play_id: Uuid,
|
play_id: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
match player_uuids.map.get(&uuid) {
|
assert_player!(player_uuids, uuid);
|
||||||
Some(_) => (),
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
respond!(game::kill(&mut game_state, play_id, true))
|
||||||
};
|
}
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
|
||||||
let mut game_state = game_state_mutex.lock().unwrap();
|
#[post("/<uuid>/unshadow/<index>")]
|
||||||
game::kill(&mut game_state, play_id, true);
|
fn unshadow(
|
||||||
Ok(format!("{}", game::get_events(&game_state)))
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/fade_from_discard/<index>")]
|
||||||
|
fn fade_from_discard(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
assert_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
respond!(game::fade_from_pile(&mut game_state, index, false, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/fade_bottom_from_discard/<index>")]
|
||||||
|
fn fade_bottom_from_discard(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
assert_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
respond!(game::fade_from_pile(&mut game_state, index, false, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/fade_from_shadow/<index>")]
|
||||||
|
fn fade_from_shadow(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
assert_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
respond!(game::fade_from_pile(&mut game_state, index, true, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/fade_bottom_from_shadow/<index>")]
|
||||||
|
fn fade_bottom_from_shadow(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
assert_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
respond!(game::fade_from_pile(&mut game_state, index, true, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/yoink/<index>")]
|
||||||
|
fn yoink(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/yoink_mill/<index>")]
|
||||||
|
fn yoink_mill(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/yoink_xmill/<index>")]
|
||||||
|
fn yoink_xmill(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/undiscard/<index>")]
|
||||||
|
fn undiscard(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/remember/<index>")]
|
||||||
|
fn remember(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/shadow/<index>")]
|
||||||
|
fn shadow(
|
||||||
|
uuid: Uuid,
|
||||||
|
index: usize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/get_state")]
|
#[get("/<uuid>/get_state")]
|
||||||
@ -235,97 +441,128 @@ fn get_state(
|
|||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
let player = get_player!(player_uuids, uuid);
|
||||||
let game_state = game_state_mutex.lock().unwrap();
|
unlock_state!(game_state_arc, mutex, game_state);
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
(
|
||||||
Some(player) => player,
|
Status::Ok,
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
(
|
||||||
};
|
ContentType::JSON,
|
||||||
Ok(format!(
|
format!("{}", game::get_game_one_player(&game_state, player.clone())),
|
||||||
"{}",
|
),
|
||||||
game::get_game_one_player(&game_state, player.clone())
|
)
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/draw/<count>")]
|
#[post("/<uuid>/get_deck")]
|
||||||
|
fn get_deck(
|
||||||
|
uuid: Uuid,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (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("/<uuid>/untap_all")]
|
||||||
|
fn untap_all(
|
||||||
|
uuid: Uuid,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
let player = get_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
game::untap_all(&mut game_state, player.clone());
|
||||||
|
respond_ok!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/draw/<count>")]
|
||||||
fn draw(
|
fn draw(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
count: usize,
|
count: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
let player = get_player!(player_uuids, uuid);
|
||||||
let mut game_state = game_state_mutex.lock().unwrap();
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
respond!(game::draw(&mut game_state, count, player.clone()))
|
||||||
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())
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/fade/<index>")]
|
#[post("/<uuid>/fade/<index>")]
|
||||||
fn fade(
|
fn fade(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
let player = get_player!(player_uuids, uuid);
|
||||||
let mut game_state = game_state_mutex.lock().unwrap();
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
respond!(game::fade_from_hand(
|
||||||
Some(player) => player,
|
&mut game_state,
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
index,
|
||||||
};
|
player.clone(),
|
||||||
game::fade_from_hand(&mut game_state, index, player.clone(), false);
|
false
|
||||||
Ok(format!(
|
|
||||||
"{}",
|
|
||||||
game::get_game_one_player(&game_state, player.clone())
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/fade-bottom/<index>")]
|
#[post("/<uuid>/fade_bottom/<index>")]
|
||||||
fn fade_bottom(
|
fn fade_bottom(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
index: usize,
|
index: usize,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
let player = get_player!(player_uuids, uuid);
|
||||||
let mut game_state = game_state_mutex.lock().unwrap();
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
respond!(game::fade_from_hand(
|
||||||
Some(player) => player,
|
&mut game_state,
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
index,
|
||||||
};
|
player.clone(),
|
||||||
game::fade_from_hand(&mut game_state, index, player.clone(), true);
|
true
|
||||||
Ok(format!(
|
|
||||||
"{}",
|
|
||||||
game::get_game_one_player(&game_state, player.clone())
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<uuid>/life/<count>")]
|
#[post("/<uuid>/fade_from_play/<play_id>")]
|
||||||
fn life(
|
fn fade_from_play(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
count: i32,
|
play_id: Uuid,
|
||||||
game_state_arc: &State<ArcMutexGameState>,
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
player_uuids: &State<PlayerUuids>,
|
player_uuids: &State<PlayerUuids>,
|
||||||
) -> Result<String, BadRequest<String>> {
|
) -> (Status, (ContentType, String)) {
|
||||||
let game_state_mutex = Arc::clone(&game_state_arc.state);
|
assert_player!(player_uuids, uuid);
|
||||||
let mut game_state = game_state_mutex.lock().unwrap();
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
let player = match player_uuids.map.get(&uuid) {
|
respond!(game::fade_from_play(&mut game_state, play_id, false))
|
||||||
Some(player) => player,
|
}
|
||||||
None => return Err(BadRequest(format!("Invalid player {}.", uuid))),
|
|
||||||
};
|
#[post("/<uuid>/fade_bottom_from_play/<play_id>")]
|
||||||
game::change_life(&mut game_state, count, player.clone());
|
fn fade_bottom_from_play(
|
||||||
Ok(format!(
|
uuid: Uuid,
|
||||||
"{}",
|
play_id: Uuid,
|
||||||
game::get_game_one_player(&game_state, player.clone())
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
))
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
assert_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
respond!(game::fade_from_play(&mut game_state, play_id, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<uuid>/life_diff/<diff>")]
|
||||||
|
fn life_diff(
|
||||||
|
uuid: Uuid,
|
||||||
|
diff: isize,
|
||||||
|
game_state_arc: &State<ArcMutexGameState>,
|
||||||
|
player_uuids: &State<PlayerUuids>,
|
||||||
|
) -> (Status, (ContentType, String)) {
|
||||||
|
let player = get_player!(player_uuids, uuid);
|
||||||
|
unlock_state_mut!(game_state_arc, mutex, game_state);
|
||||||
|
game::change_life(&mut game_state, diff, player.clone());
|
||||||
|
respond_ok!()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ArcMutexGameState {
|
struct ArcMutexGameState {
|
||||||
@ -340,19 +577,16 @@ struct PlayerUuids {
|
|||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
let mut game_state = game::new();
|
let game_state = game::new();
|
||||||
//game::draw(&mut game_state, 7, Player::A);
|
|
||||||
//game::draw(&mut game_state, 7, Player::B);
|
|
||||||
let game_state_arc = Arc::new(Mutex::new(game_state));
|
let game_state_arc = Arc::new(Mutex::new(game_state));
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let a_uuid: Uuid = uuid!("9b0a2a95-72a9-4e03-930e-a9583d2a2a5a");
|
let (a_uuid, b_uuid) = (
|
||||||
|
uuid!("9b0a2a95-72a9-4e03-930e-a9583d2a2a5a"),
|
||||||
|
uuid!("2efc0332-975d-4f71-9dd0-ffc9213098a5"),
|
||||||
|
);
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let a_uuid: Uuid = Uuid::new_v4();
|
let (a_uuid, b_uuid) = (Uuid::new_v4(), Uuid::new_v4());
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
let b_uuid: Uuid = uuid!("2efc0332-975d-4f71-9dd0-ffc9213098a5");
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
let b_uuid: Uuid = Uuid::new_v4();
|
|
||||||
|
|
||||||
let uuid_map = HashMap::from([(a_uuid, Player::A), (b_uuid, Player::B)]);
|
let uuid_map = HashMap::from([(a_uuid, Player::A), (b_uuid, Player::B)]);
|
||||||
println!("A: {}", a_uuid);
|
println!("A: {}", a_uuid);
|
||||||
@ -377,19 +611,38 @@ fn rocket() -> _ {
|
|||||||
draw,
|
draw,
|
||||||
shuffle,
|
shuffle,
|
||||||
pass,
|
pass,
|
||||||
life,
|
life_diff,
|
||||||
fade,
|
fade,
|
||||||
fade_bottom,
|
fade_bottom,
|
||||||
play,
|
play,
|
||||||
|
play_at,
|
||||||
bounce,
|
bounce,
|
||||||
tap,
|
tap,
|
||||||
|
transfer_ownership,
|
||||||
move_card,
|
move_card,
|
||||||
discard,
|
discard,
|
||||||
kill,
|
kill,
|
||||||
forget,
|
forget,
|
||||||
banish,
|
banish,
|
||||||
unkill,
|
unkill,
|
||||||
unbanish
|
unbanish,
|
||||||
|
unkill_at,
|
||||||
|
unbanish_at,
|
||||||
|
shadow,
|
||||||
|
unshadow,
|
||||||
|
undiscard,
|
||||||
|
remember,
|
||||||
|
untap_all,
|
||||||
|
get_deck,
|
||||||
|
yoink,
|
||||||
|
yoink_mill,
|
||||||
|
yoink_xmill,
|
||||||
|
fade_from_play,
|
||||||
|
fade_bottom_from_play,
|
||||||
|
fade_from_discard,
|
||||||
|
fade_bottom_from_discard,
|
||||||
|
fade_from_shadow,
|
||||||
|
fade_bottom_from_shadow,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.manage(ArcMutexGameState {
|
.manage(ArcMutexGameState {
|
||||||
|
|||||||
@ -11,9 +11,11 @@
|
|||||||
<main>
|
<main>
|
||||||
<div class="hand" id="opponent-hand"></div>
|
<div class="hand" id="opponent-hand"></div>
|
||||||
<div id="border"></div>
|
<div id="border"></div>
|
||||||
<div id="play"></div>
|
<div id="play" data-border-text=" "></div>
|
||||||
<div class="hand" id="own-hand"></div>
|
<div class="hand" id="own-hand"></div>
|
||||||
<div id="extra" class="hidden"></div>
|
<div id="extra" class="hidden">
|
||||||
|
<div id="extra-list"></div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<aside id="view-card-container">
|
<aside id="view-card-container">
|
||||||
<div id="param-container">
|
<div id="param-container">
|
||||||
@ -26,7 +28,7 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<aside id="piles">
|
<aside id="piles">
|
||||||
<div class="pile" id="deck">
|
<div class="pile" id="deck">
|
||||||
<img src="./card_back.png" alt="" id="deck" />
|
<img src="./card_back.png" alt="" id="deck-img" />
|
||||||
<span id="deck-count"></span>
|
<span id="deck-count"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pile" id="discard-pile">
|
<div class="pile" id="discard-pile">
|
||||||
@ -38,5 +40,9 @@
|
|||||||
<span id="shadow-realm-count"></span>
|
<span id="shadow-realm-count"></span>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
<aside id="counts">
|
||||||
|
<div class="count" id="opponent-score"></div>
|
||||||
|
<div class="count" id="own-score"></div>
|
||||||
|
</aside>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,25 +1,41 @@
|
|||||||
(->>
|
(->>
|
||||||
const err = -> console.error it
|
const err = ->
|
||||||
const fetch-log = ->
|
console.error it
|
||||||
fetch it .then (->
|
$play.set-attribute \data-border-text it
|
||||||
|
set-timeout (-> $play.set-attribute \data-border-text ' '), 3000
|
||||||
|
const fetch-post = ->
|
||||||
|
const req = new Request it, { method: \POST }
|
||||||
|
(fetch req .then ->>
|
||||||
|
throw await it.text! unless it.ok
|
||||||
console.log it
|
console.log it
|
||||||
it
|
it
|
||||||
) .catch err
|
) .catch err
|
||||||
const $opponent-hand = document.query-selector '#opponent-hand'
|
const life-diff$ = ->
|
||||||
const $own-hand = document.query-selector '#own-hand'
|
fetch-post "./life_diff/#it"
|
||||||
const $view-card = document.query-selector '#view-card'
|
life-index = +('B' == state.player-state.you)
|
||||||
const $view-card-container = document.query-selector '#view-card-container'
|
current-life = state.player-state.life_totals[life-index]
|
||||||
const $param = document.query-selector '#param'
|
$own-score.inner-text = current-life + it
|
||||||
const $play = document.query-selector '#play'
|
window.fetch-post = fetch-post
|
||||||
const $discard-pile = document.query-selector '#discard-pile'
|
const $ = -> document.query-selector it
|
||||||
const $discard-pile-img = document.query-selector '#discard-pile-img'
|
const $opponent-hand = $ \#opponent-hand
|
||||||
const $discard-pile-count = document.query-selector '#discard-pile-count'
|
const $own-hand = $ \#own-hand
|
||||||
const $shadow-realm = document.query-selector '#shadow-realm'
|
const $view-card = $ \#view-card
|
||||||
const $shadow-realm-img = document.query-selector '#shadow-realm-img'
|
const $view-card-container = $ \#view-card-container
|
||||||
const $shadow-realm-count = document.query-selector '#shadow-realm-count'
|
const $param = $ \#param
|
||||||
const $deck = document.query-selector '#deck'
|
const $play = $ \#play
|
||||||
const $deck-count = document.query-selector '#deck-count'
|
const $discard-pile = $ \#discard-pile
|
||||||
const $extra = document.query-selector '#extra'
|
const $discard-pile-img = $ \#discard-pile-img
|
||||||
|
const $discard-pile-count = $ \#discard-pile-count
|
||||||
|
const $shadow-realm = $ \#shadow-realm
|
||||||
|
const $shadow-realm-img = $ \#shadow-realm-img
|
||||||
|
const $shadow-realm-count = $ \#shadow-realm-count
|
||||||
|
const $deck = $ \#deck
|
||||||
|
const $deck-img = $ \#deck-img
|
||||||
|
const $deck-count = $ \#deck-count
|
||||||
|
const $extra = $ \#extra
|
||||||
|
const $extra-list = $ \#extra-list
|
||||||
|
const $opponent-score = $ \#opponent-score
|
||||||
|
const $own-score = $ \#own-score
|
||||||
const [push-param, pop-param] = (->
|
const [push-param, pop-param] = (->
|
||||||
param = ""
|
param = ""
|
||||||
clear-param-timer = 0
|
clear-param-timer = 0
|
||||||
@ -49,7 +65,8 @@
|
|||||||
const get-play-size = -> $play.get-bounding-client-rect!
|
const get-play-size = -> $play.get-bounding-client-rect!
|
||||||
const gen-array = -> Array.from { length: it }, (_, i) -> i
|
const gen-array = -> Array.from { length: it }, (_, i) -> i
|
||||||
const gen-array-of = (it, val) -> Array.from { length: it }, -> val
|
const gen-array-of = (it, val) -> Array.from { length: it }, -> val
|
||||||
const cards = await fetch-log \./cards.json .then (.json!)
|
const cards = await fetch \/cards.json .then (.json!)
|
||||||
|
window.cards = cards
|
||||||
const $make-card = ->
|
const $make-card = ->
|
||||||
$img = document.create-element \img
|
$img = document.create-element \img
|
||||||
$img.src = it
|
$img.src = it
|
||||||
@ -66,9 +83,9 @@
|
|||||||
| _
|
| _
|
||||||
void
|
void
|
||||||
const get-state = ->>
|
const get-state = ->>
|
||||||
player-state-json = await fetch \./get_state .then (.text!)
|
player-state-json = await fetch \./get_state .then (.text!)
|
||||||
player-state = JSON.parse player-state-json
|
player-state = JSON.parse player-state-json
|
||||||
events-json = await fetch \/get_events .then (.text!)
|
events-json = await fetch \/get_events .then (.text!)
|
||||||
events = JSON.parse events-json
|
events = JSON.parse events-json
|
||||||
play-map = new Map player-state.play.map -> [it.id, it]
|
play-map = new Map player-state.play.map -> [it.id, it]
|
||||||
return { player-state-json, player-state, events-json, events, play-map }
|
return { player-state-json, player-state, events-json, events, play-map }
|
||||||
@ -81,8 +98,12 @@
|
|||||||
$view-card.style.opacity = 1
|
$view-card.style.opacity = 1
|
||||||
e.target.add-event-listener \mouseleave hide-card$
|
e.target.add-event-listener \mouseleave hide-card$
|
||||||
state = await get-state!
|
state = await get-state!
|
||||||
const apply-state$ = ->
|
const apply-state$ = ->>
|
||||||
{ player-state } = it
|
{ player-state } = it
|
||||||
|
if player-state.you == \A
|
||||||
|
[$own-score.inner-text, $opponent-score.inner-text] = player-state.life_totals
|
||||||
|
else
|
||||||
|
[$opponent-score.inner-text, $own-score.inner-text] = player-state.life_totals
|
||||||
gen-opponent-cards$ player-state.opponent_cards_in_hand
|
gen-opponent-cards$ player-state.opponent_cards_in_hand
|
||||||
if document.body.class-list.contains \my-turn
|
if document.body.class-list.contains \my-turn
|
||||||
document.body.class-list.remove \my-turn if player-state.you != player-state.turn_player
|
document.body.class-list.remove \my-turn if player-state.you != player-state.turn_player
|
||||||
@ -108,7 +129,8 @@
|
|||||||
| \discard => player-state.discard_pile
|
| \discard => player-state.discard_pile
|
||||||
| \shadow => player-state.shadow_realm
|
| \shadow => player-state.shadow_realm
|
||||||
| \deck => gen-array-of state.player-state.deck_size, null
|
| \deck => gen-array-of state.player-state.deck_size, null
|
||||||
| _ => (console.log that) || []
|
| \deck-revealed => await fetch-post \./get_deck .then (.json!)
|
||||||
|
| _ => (err that) || []
|
||||||
open-list list, $extra.get-attribute \data-pile-type
|
open-list list, $extra.get-attribute \data-pile-type
|
||||||
if player-state.hand.length != $own-hand.children.length
|
if player-state.hand.length != $own-hand.children.length
|
||||||
$own-hand.innerHTML = ''
|
$own-hand.innerHTML = ''
|
||||||
@ -119,6 +141,23 @@
|
|||||||
..set-attribute \data-hand-index index
|
..set-attribute \data-hand-index index
|
||||||
..set-attribute \data-text card-data.txt
|
..set-attribute \data-text card-data.txt
|
||||||
..set-attribute \data-png card-data.png
|
..set-attribute \data-png card-data.png
|
||||||
|
..set-attribute \draggable \true
|
||||||
|
..add-event-listener \dragend ->
|
||||||
|
console.log $drop-zone
|
||||||
|
switch $drop-zone
|
||||||
|
| $play
|
||||||
|
const x-percent = Math.round (100 * it.client-x / document.document-element.client-width)
|
||||||
|
const height = document.document-element.client-height
|
||||||
|
const y-top-offset = 0.14 * height
|
||||||
|
y-bottom-offset = y-top-offset
|
||||||
|
y-bottom-offset *= 2 unless $extra.class-list.contains \hidden
|
||||||
|
const y-percent = Math.round (100 * (it.client-y - y-top-offset) / (height - y-bottom-offset - y-top-offset))
|
||||||
|
const x = clamp 5 x-percent, 95
|
||||||
|
const y = clamp 5 y-percent, 95
|
||||||
|
fetch-post "./play_at/#index/#x/#y"
|
||||||
|
| $discard-pile => fetch-post "./discard/#index"
|
||||||
|
| $shadow-realm => fetch-post "./forget/#index"
|
||||||
|
| $deck => fetch-post "./fade/#index"
|
||||||
..add-event-listener \mouseenter show-card$
|
..add-event-listener \mouseenter show-card$
|
||||||
$own-hand.append-child card
|
$own-hand.append-child card
|
||||||
next-play-id-list = [...player-state.play.map (.play_id)].sort!join \|
|
next-play-id-list = [...player-state.play.map (.play_id)].sort!join \|
|
||||||
@ -130,6 +169,7 @@
|
|||||||
relative-y = it.position_y
|
relative-y = it.position_y
|
||||||
else
|
else
|
||||||
relative-y = 100 - it.position_y
|
relative-y = 100 - it.position_y
|
||||||
|
{ play_id } = it # for closure purposes
|
||||||
img = $make-card card-data.jpg
|
img = $make-card card-data.jpg
|
||||||
img
|
img
|
||||||
..class-list.add \in-play-card
|
..class-list.add \in-play-card
|
||||||
@ -144,6 +184,12 @@
|
|||||||
..set-attribute \data-owner it.owner
|
..set-attribute \data-owner it.owner
|
||||||
..set-attribute \data-owned it.owner == player-state.you
|
..set-attribute \data-owned it.owner == player-state.you
|
||||||
..set-attribute \data-tapped it.tapped
|
..set-attribute \data-tapped it.tapped
|
||||||
|
..add-event-listener \dblclick (_) ->
|
||||||
|
if \true == img.get-attribute \data-tapped
|
||||||
|
img.set-attribute \data-tapped \false
|
||||||
|
else
|
||||||
|
img.set-attribute \data-tapped \true
|
||||||
|
fetch-post "./tap/#play_id"
|
||||||
..add-event-listener \mouseenter show-card$
|
..add-event-listener \mouseenter show-card$
|
||||||
..add-event-listener \dragstart ->
|
..add-event-listener \dragstart ->
|
||||||
it.target.set-attribute \data-start-x it.target.offset-left
|
it.target.set-attribute \data-start-x it.target.offset-left
|
||||||
@ -151,26 +197,33 @@
|
|||||||
it.target.set-attribute \data-client-x it.client-x
|
it.target.set-attribute \data-client-x it.client-x
|
||||||
it.target.set-attribute \data-client-y it.client-y
|
it.target.set-attribute \data-client-y it.client-y
|
||||||
..add-event-listener \dragend ->
|
..add-event-listener \dragend ->
|
||||||
const start-x = +it.target.get-attribute \data-start-x
|
switch $drop-zone
|
||||||
const start-y = +it.target.get-attribute \data-start-y
|
| $discard-pile => fetch-post "./kill/#play_id"
|
||||||
const start-client-x = +it.target.get-attribute \data-client-x
|
| $shadow-realm => fetch-post "./banish/#play_id"
|
||||||
const start-client-y = +it.target.get-attribute \data-client-y
|
| $deck => fetch-post "./fade/#index"
|
||||||
const new-x = it.client-x + (start-x - start-client-x)
|
| $play =>
|
||||||
const new-y = it.client-y + (start-y - start-client-y)
|
const start-x = +it.target.get-attribute \data-start-x
|
||||||
const scale-x = +start-x / +(it.target.get-attribute \data-play-x)
|
const start-y = +it.target.get-attribute \data-start-y
|
||||||
const scale-y = +start-y / +(it.target.get-attribute \data-play-y)
|
const start-client-x = +it.target.get-attribute \data-client-x
|
||||||
const x = clamp 5 Math.round(new-x / scale-x), 95
|
const start-client-y = +it.target.get-attribute \data-client-y
|
||||||
const y = clamp 5 Math.round(new-y / scale-y), 95
|
const new-x = it.client-x + (start-x - start-client-x)
|
||||||
it.target.style
|
const new-y = it.client-y + (start-y - start-client-y)
|
||||||
..left = x + \%
|
const scale-x = +start-x / +(it.target.get-attribute \data-play-x)
|
||||||
..top = y + \%
|
const scale-y = +start-y / +(it.target.get-attribute \data-play-y)
|
||||||
it.target.set-attribute \data-play-x x
|
const x = clamp 5 Math.round(new-x / scale-x), 95
|
||||||
it.target.set-attribute \data-play-y y
|
const y = clamp 5 Math.round(new-y / scale-y), 95
|
||||||
if state.player-state.you == it.target.get-attribute \data-owner
|
if y > 90 && \true == img.get-attribute \data-owned
|
||||||
absolute-y = y
|
return fetch-post "./bounce/#play_id"
|
||||||
else
|
it.target.style
|
||||||
absolute-y = 100 - y
|
..left = x + \%
|
||||||
fetch-log "./move/#{it.target.get-attribute \data-play-id}/#x/#absolute-y"
|
..top = y + \%
|
||||||
|
it.target.set-attribute \data-play-x x
|
||||||
|
it.target.set-attribute \data-play-y y
|
||||||
|
if state.player-state.you == it.target.get-attribute \data-owner
|
||||||
|
absolute-y = y
|
||||||
|
else
|
||||||
|
absolute-y = 100 - y
|
||||||
|
fetch-post "./move/#{it.target.get-attribute \data-play-id}/#x/#absolute-y"
|
||||||
$play.append-child img
|
$play.append-child img
|
||||||
play-id-list := next-play-id-list
|
play-id-list := next-play-id-list
|
||||||
else
|
else
|
||||||
@ -184,36 +237,102 @@
|
|||||||
$element.style
|
$element.style
|
||||||
..left = it.position_x + \%
|
..left = it.position_x + \%
|
||||||
..top = relative-y + \%
|
..top = relative-y + \%
|
||||||
|
$element.set-attribute \data-owner it.owner
|
||||||
|
$element.set-attribute \data-owned it.owner == player-state.you
|
||||||
$element.set-attribute \data-play-x it.position_x
|
$element.set-attribute \data-play-x it.position_x
|
||||||
$element.set-attribute \data-play-y relative-y
|
$element.set-attribute \data-play-y relative-y
|
||||||
|
|
||||||
|
|
||||||
apply-state$ state
|
apply-state$ state
|
||||||
|
|
||||||
const open-list = (list, list-type) -->
|
const open-list = (list, list-type, dragend-callback) -->
|
||||||
const $card-list = list.map (card-id, index) ->
|
const $card-list = list.map (card-id, index) ->
|
||||||
if card-id
|
if card-id
|
||||||
ret = $make-card cards[card-id].jpg
|
ret = $make-card cards[card-id].jpg
|
||||||
|
ret.add-event-listener \dragend -> dragend-callback it, index
|
||||||
|
ret.set-attribute \draggable \true
|
||||||
ret.set-attribute \data-png cards[card-id].png
|
ret.set-attribute \data-png cards[card-id].png
|
||||||
ret.set-attribute "data-index" index
|
ret.set-attribute \data-index index
|
||||||
ret.set-attribute "data-list-type" list-type
|
ret.set-attribute \data-list-type list-type
|
||||||
ret.add-event-listener \mouseenter show-card$
|
ret.add-event-listener \mouseenter show-card$
|
||||||
ret
|
ret
|
||||||
else
|
else
|
||||||
$make-blank-card!
|
$make-blank-card!
|
||||||
$extra.innerHTML = ""
|
$extra-list.innerHTML = ""
|
||||||
$card-list.for-each -> $extra.append it
|
$card-list.for-each -> $extra-list.append it
|
||||||
$extra.set-attribute \data-pile-type list-type
|
$extra.set-attribute \data-pile-type list-type
|
||||||
$extra.class-list.remove \hidden
|
$extra.class-list.remove \hidden
|
||||||
|
|
||||||
$discard-pile-img.add-event-listener \mouseenter show-card$
|
$discard-pile-img.add-event-listener \mouseenter show-card$
|
||||||
$shadow-realm-img.add-event-listener \mouseenter show-card$
|
$shadow-realm-img.add-event-listener \mouseenter show-card$
|
||||||
$play.add-event-listener \dragover (.prevent-default!)
|
|
||||||
|
|
||||||
$discard-pile-img.add-event-listener \click -> open-list state.player-state.discard_pile, \discard
|
$discard-pile.add-event-listener \click -> open-list state.player-state.discard_pile, \discard (it, index) ->
|
||||||
$shadow-realm-img.add-event-listener \click -> open-list state.player-state.shadow_realm, \shadow
|
switch $drop-zone
|
||||||
|
| $play
|
||||||
|
const x-percent = Math.round (100 * it.client-x / document.document-element.client-width)
|
||||||
|
const height = document.document-element.client-height
|
||||||
|
const y-top-offset = 0.14 * height
|
||||||
|
y-bottom-offset = y-top-offset
|
||||||
|
y-bottom-offset *= 2 unless $extra.class-list.contains \hidden
|
||||||
|
const y-percent = Math.round (100 * (it.client-y - y-top-offset) / (height - y-bottom-offset - y-top-offset))
|
||||||
|
const x = clamp 5 x-percent, 95
|
||||||
|
const y = clamp 5 y-percent, 95
|
||||||
|
fetch-post "./unkill_at/#index/#x/#y"
|
||||||
|
| $discard-pile => return
|
||||||
|
| $shadow-realm => fetch-post "./shadow/#index"
|
||||||
|
| $deck => fetch-post "./fade_from_discard/#index"
|
||||||
|
| $own-hand => "./undiscard/#index"
|
||||||
|
it.target.parent-element.remove-child it.target
|
||||||
|
clear-extra-on-empty$!
|
||||||
|
$shadow-realm.add-event-listener \click -> open-list state.player-state.shadow_realm, \shadow (it, index) ->
|
||||||
|
switch $drop-zone
|
||||||
|
| $play
|
||||||
|
const x-percent = Math.round (100 * it.client-x / document.document-element.client-width)
|
||||||
|
const height = document.document-element.client-height
|
||||||
|
const y-top-offset = 0.14 * height
|
||||||
|
y-bottom-offset = y-top-offset
|
||||||
|
y-bottom-offset *= 2 unless $extra.class-list.contains \hidden
|
||||||
|
const y-percent = Math.round (100 * (it.client-y - y-top-offset) / (height - y-bottom-offset - y-top-offset))
|
||||||
|
const x = clamp 5 x-percent, 95
|
||||||
|
const y = clamp 5 y-percent, 95
|
||||||
|
fetch-post "./unbanish_at/#index/#x/#y"
|
||||||
|
| $discard-pile => fetch-post "./unshadow/#index"
|
||||||
|
| $shadow-realm => return
|
||||||
|
| $deck => fetch-post "./fade_from_shadow/#index"
|
||||||
|
| $own-hand => "./remember/#index"
|
||||||
|
it.target.parent-element.remove-child it.target
|
||||||
|
clear-extra-on-empty$!
|
||||||
$deck.add-event-listener \click -> open-list (gen-array-of state.player-state.deck_size, null), \deck
|
$deck.add-event-listener \click -> open-list (gen-array-of state.player-state.deck_size, null), \deck
|
||||||
|
|
||||||
|
$drop-zone = $play
|
||||||
|
const set-drop-zone$ = ->
|
||||||
|
it.prevent-default!
|
||||||
|
$drop-zone := it.target
|
||||||
|
#console.log "New drop zone: ", $drop-zone
|
||||||
|
$play.add-event-listener \dragover set-drop-zone$
|
||||||
|
$discard-pile.add-event-listener \dragover set-drop-zone$
|
||||||
|
$shadow-realm.add-event-listener \dragover set-drop-zone$
|
||||||
|
$deck.add-event-listener \dragover set-drop-zone$
|
||||||
|
$own-hand.add-event-listener \dragover set-drop-zone$
|
||||||
|
|
||||||
|
wheel-wait = false
|
||||||
|
$own-score.add-event-listener \wheel ->
|
||||||
|
return if wheel-wait
|
||||||
|
diff = switch
|
||||||
|
| it.delta-x < 0 => -5
|
||||||
|
| it.delta-x > 0 => 5
|
||||||
|
| it.delta-y > 0 => -10
|
||||||
|
| it.delta-y < 0 => 10
|
||||||
|
diff *= 2 if it.shift-key
|
||||||
|
diff *= -1 if it.alt-key
|
||||||
|
life-diff$ diff
|
||||||
|
wheel-wait := true
|
||||||
|
set-timeout (-> wheel-wait := false), 250
|
||||||
|
const clear-extra-on-empty$ = ->
|
||||||
|
if !$extra-list.child-element-count
|
||||||
|
$extra.class-list.add \hidden
|
||||||
|
$extra.remove-attribute \data-pile-type
|
||||||
|
|
||||||
set-interval (->>
|
set-interval (->>
|
||||||
const new-state = await get-state!
|
const new-state = await get-state!
|
||||||
unless new-state.player-state-json == state.player-state-json
|
unless new-state.player-state-json == state.player-state-json
|
||||||
@ -222,6 +341,19 @@
|
|||||||
window.state = state
|
window.state = state
|
||||||
), 1000
|
), 1000
|
||||||
mouse-x = mouse-y = 0
|
mouse-x = mouse-y = 0
|
||||||
|
$own-score.add-event-listener \click ->
|
||||||
|
diff = 1
|
||||||
|
diff *= 10 if it.shift-key
|
||||||
|
diff *= 2 if it.ctrl-key
|
||||||
|
diff *= -1 if it.alt-key
|
||||||
|
life-diff$ diff
|
||||||
|
$own-score.add-event-listener \contextmenu ->
|
||||||
|
it.prevent-default!
|
||||||
|
diff = -1
|
||||||
|
diff *= 10 if it.shift-key
|
||||||
|
diff *= 2 if it.ctrl-key
|
||||||
|
diff *= -1 if it.alt-key
|
||||||
|
life-diff$ diff
|
||||||
document
|
document
|
||||||
..add-event-listener \mousemove ->
|
..add-event-listener \mousemove ->
|
||||||
mouse-x = it.client-x
|
mouse-x = it.client-x
|
||||||
@ -232,50 +364,132 @@
|
|||||||
$view-card-container.class-list.add \right
|
$view-card-container.class-list.add \right
|
||||||
..add-event-listener \keyup ->
|
..add-event-listener \keyup ->
|
||||||
$current-mouse-node = [...document.query-selector-all \:hover][* - 1]
|
$current-mouse-node = [...document.query-selector-all \:hover][* - 1]
|
||||||
switch it.key
|
key = it.key.to-lower-case!
|
||||||
| \c => fetch-log "./draw/#{pop-param!}"
|
key = "!#key" if it.alt-key
|
||||||
| \e => fetch-log "./pass"
|
key = "+#key" if it.shift-key
|
||||||
|
key = "^#key" if it.ctrl-key
|
||||||
|
switch key
|
||||||
|
| \v =>
|
||||||
|
fetch-post \./shuffle
|
||||||
|
if $extra.has-attribute \data-pile-type and \deck-revealed == $extra.get-attribute \data-pile-type
|
||||||
|
$extra.class-list.add \hidden
|
||||||
|
| \c , \^d => fetch-post "./draw/#{pop-param!}"
|
||||||
|
| \e => fetch-post \./pass
|
||||||
| \0 \1 \2 \3 \4 \5 \6 \7 \8 \9 => push-param that
|
| \0 \1 \2 \3 \4 \5 \6 \7 \8 \9 => push-param that
|
||||||
| \t =>
|
| \t =>
|
||||||
if $current-mouse-node?.has-attribute \data-hand-index
|
if $current-mouse-node?.has-attribute \data-hand-index
|
||||||
fetch-log "./fade/#{$current-mouse-node.get-attribute \data-hand-index}"
|
fetch-post "./fade/#{$current-mouse-node.get-attribute \data-hand-index}"
|
||||||
|
else if $current-mouse-node?.has-attribute \data-list-type
|
||||||
|
switch $current-mouse-node.get-attribute \data-list-type
|
||||||
|
| \discard
|
||||||
|
fetch-post "./fade_from_discard/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \deck , \deck-revealed => return
|
||||||
|
| \shadow
|
||||||
|
fetch-post "./fade_from_shadow/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
||||||
|
clear-extra-on-empty$!
|
||||||
| \y =>
|
| \y =>
|
||||||
if $current-mouse-node?.has-attribute \data-hand-index
|
if $current-mouse-node?.has-attribute \data-hand-index
|
||||||
fetch-log "./fade_bottom/#{$current-mouse-node.get-attribute \data-hand-index}"
|
fetch-post "./fade_bottom/#{$current-mouse-node.get-attribute \data-hand-index}"
|
||||||
|
else if $current-mouse-node?.has-attribute \data-list-type
|
||||||
|
switch $current-mouse-node.get-attribute \data-list-type
|
||||||
|
| \discard
|
||||||
|
fetch-post "./fade_bottom_from_discard/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \deck , \deck-revealed => return
|
||||||
|
| \shadow
|
||||||
|
fetch-post "./fade_bottom_from_shadow/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
||||||
|
clear-extra-on-empty$!
|
||||||
|
| \b =>
|
||||||
|
if $current-mouse-node?.has-attribute \data-play-id
|
||||||
|
fetch-post "./transfer_ownership/#{$current-mouse-node.get-attribute \data-play-id}"
|
||||||
| ' ' =>
|
| ' ' =>
|
||||||
if $current-mouse-node?.has-attribute \data-hand-index
|
if $current-mouse-node?.has-attribute \data-hand-index
|
||||||
fetch-log "./play/#{$current-mouse-node.get-attribute \data-hand-index}"
|
fetch-post "./play/#{$current-mouse-node.get-attribute \data-hand-index}"
|
||||||
else if $current-mouse-node?.has-attribute \data-play-id
|
else if $current-mouse-node?.has-attribute \data-play-id
|
||||||
if \true == $current-mouse-node.get-attribute \data-tapped
|
if \true == $current-mouse-node.get-attribute \data-tapped
|
||||||
$current-mouse-node.set-attribute \data-tapped \false
|
$current-mouse-node.set-attribute \data-tapped \false
|
||||||
else
|
else
|
||||||
$current-mouse-node.set-attribute \data-tapped \true
|
$current-mouse-node.set-attribute \data-tapped \true
|
||||||
fetch-log "./tap/#{$current-mouse-node.get-attribute \data-play-id}"
|
fetch-post "./tap/#{$current-mouse-node.get-attribute \data-play-id}"
|
||||||
else if $current-mouse-node?.has-attribute \data-list-type
|
else if $current-mouse-node?.has-attribute \data-list-type
|
||||||
switch $current-mouse-node.get-attribute \data-list-type
|
switch $current-mouse-node.get-attribute \data-list-type
|
||||||
| \discard
|
| \discard
|
||||||
fetch-log "./unkill/#{$current-mouse-node.get-attribute \data-index}"
|
fetch-post "./unkill/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \deck , \deck-revealed
|
||||||
|
return # will add play button
|
||||||
| \shadow
|
| \shadow
|
||||||
fetch-log "./unbanish/#{$current-mouse-node.get-attribute \data-index}"
|
fetch-post "./unbanish/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
||||||
if !$extra.child-element-count
|
clear-extra-on-empty$!
|
||||||
$extra.class-list.add \hidden
|
|
||||||
$extra.remove-attribute \data-pile-type
|
|
||||||
| \r =>
|
| \r =>
|
||||||
if $current-mouse-node?.has-attribute \data-play-id
|
if $current-mouse-node?.has-attribute \data-play-id
|
||||||
fetch-log "./bounce/#{$current-mouse-node.get-attribute \data-play-id}"
|
fetch-post "./bounce/#{$current-mouse-node.get-attribute \data-play-id}"
|
||||||
|
else if $current-mouse-node?.has-attribute \data-list-type
|
||||||
|
switch $current-mouse-node.get-attribute \data-list-type
|
||||||
|
| \discard
|
||||||
|
fetch-post "./undiscard/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \deck , \deck-revealed
|
||||||
|
fetch-post "./yoink/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \shadow
|
||||||
|
fetch-post "./remember/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
||||||
|
clear-extra-on-empty$!
|
||||||
| \d =>
|
| \d =>
|
||||||
if $current-mouse-node?.has-attribute \data-hand-index
|
if $current-mouse-node?.has-attribute \data-hand-index
|
||||||
fetch-log "./discard/#{$current-mouse-node.get-attribute \data-hand-index}"
|
fetch-post "./discard/#{$current-mouse-node.get-attribute \data-hand-index}"
|
||||||
else if $current-mouse-node?.has-attribute \data-play-id
|
else if $current-mouse-node?.has-attribute \data-play-id
|
||||||
fetch-log "./kill/#{$current-mouse-node.get-attribute \data-play-id}"
|
fetch-post "./kill/#{$current-mouse-node.get-attribute \data-play-id}"
|
||||||
|
else if $current-mouse-node?.has-attribute \data-list-type
|
||||||
|
switch $current-mouse-node.get-attribute \data-list-type
|
||||||
|
| \discard
|
||||||
|
return
|
||||||
|
| \deck , \deck-revealed
|
||||||
|
fetch-post "./yoink_mill/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \shadow
|
||||||
|
fetch-post "./unshadow/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
||||||
|
clear-extra-on-empty$!
|
||||||
| \s =>
|
| \s =>
|
||||||
if $current-mouse-node?.has-attribute \data-hand-index
|
if $current-mouse-node?.has-attribute \data-hand-index
|
||||||
fetch-log "./forget/#{$current-mouse-node.get-attribute \data-hand-index}"
|
fetch-post "./forget/#{$current-mouse-node.get-attribute \data-hand-index}"
|
||||||
else if $current-mouse-node?.has-attribute \data-play-id
|
else if $current-mouse-node?.has-attribute \data-play-id
|
||||||
fetch-log "./banish/#{$current-mouse-node.get-attribute \data-play-id}"
|
fetch-post "./banish/#{$current-mouse-node.get-attribute \data-play-id}"
|
||||||
|
else if $current-mouse-node?.has-attribute \data-list-type
|
||||||
|
switch $current-mouse-node.get-attribute \data-list-type
|
||||||
|
| \discard
|
||||||
|
fetch-post "./shadow/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \deck , \deck-revealed
|
||||||
|
fetch-post "./yoink_xmill/#{$current-mouse-node.get-attribute \data-index}"
|
||||||
|
| \shadow
|
||||||
|
return
|
||||||
|
clear-extra-on-empty$!
|
||||||
|
$current-mouse-node.parent-element.remove-child $current-mouse-node
|
||||||
| \q =>
|
| \q =>
|
||||||
$extra.class-list.add \hidden
|
$extra.class-list.add \hidden
|
||||||
$extra.remove-attribute \data-pile-type
|
$extra.remove-attribute \data-pile-type
|
||||||
|
| \x , \+x =>
|
||||||
|
if it.shift-key || state.player-state.turn_player == state.player-state.you
|
||||||
|
fetch-post \./untap_all
|
||||||
|
[...$play.children]
|
||||||
|
.filter -> \true == it.get-attribute \data-owned
|
||||||
|
.for-each -> it.set-attribute \data-tapped false
|
||||||
|
| \f =>
|
||||||
|
fetch-post \./get_deck .then ->>
|
||||||
|
const deck = await it.json!
|
||||||
|
open-list deck, \deck-revealed
|
||||||
|
| \^e =>
|
||||||
|
alert 1
|
||||||
|
count = +prompt "Number to draw:"
|
||||||
|
if count != count or count < 0
|
||||||
|
return err "Invalid number: #count"
|
||||||
|
fetch-post "./draw/#count"
|
||||||
|
| \l =>
|
||||||
|
it.prevent-default!
|
||||||
|
count = +prompt "New life total:"
|
||||||
|
if count != count or count < 0
|
||||||
|
return err "Invalid number: #count"
|
||||||
|
life-index = if state.player-state.you == \a then 0 else 1
|
||||||
|
diff = count - state.player-state.life_totals[life-index]
|
||||||
|
life-diff$ diff
|
||||||
)!
|
)!
|
||||||
|
|||||||
@ -35,7 +35,6 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.2%;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
img {
|
img {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -49,7 +48,7 @@ main {
|
|||||||
|
|
||||||
#extra {
|
#extra {
|
||||||
transition: height 0.5s;
|
transition: height 0.5s;
|
||||||
overflow: hidden;
|
overflow: scroll;
|
||||||
background-color: #222d;
|
background-color: #222d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +56,14 @@ main {
|
|||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#extra-list {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.2vw;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -129,13 +136,15 @@ aside {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#play::before {
|
#play::before {
|
||||||
content: " ";
|
content: attr(data-border-text);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 48%;
|
top: 48%;
|
||||||
bottom: 48%;
|
bottom: 48%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-turn #play {
|
.my-turn #play {
|
||||||
@ -170,3 +179,33 @@ aside {
|
|||||||
img {
|
img {
|
||||||
border-radius: 10%;
|
border-radius: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#counts {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 7vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
height: 14vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#opponent-score {
|
||||||
|
background-color: #f002;
|
||||||
|
}
|
||||||
|
|
||||||
|
#own-score {
|
||||||
|
background-color: #0ff2;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user