diff --git a/.gitignore b/.gitignore index c1cdd55..e4d72c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Rocket.toml +/static diff --git a/Cargo.lock b/Cargo.lock index fe188bb..9bd6b80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -95,6 +110,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "binascii" version = "0.1.4" @@ -107,6 +128,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "bytemuck" version = "1.21.0" @@ -140,6 +167,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + [[package]] name = "cookie" version = "0.18.1" @@ -151,17 +191,60 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "dandadan" version = "0.1.0" dependencies = [ "rand 0.9.0", "rocket", + "serde", "serde_json", + "serde_with", "url", "uuid", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "deranged" version = "0.3.11" @@ -169,6 +252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -406,13 +490,19 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -431,6 +521,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.12" @@ -500,6 +596,29 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -618,6 +737,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -639,6 +764,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.7.1" @@ -646,7 +782,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", "serde", ] @@ -673,6 +809,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -804,6 +950,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -1093,7 +1248,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap", + "indexmap 2.7.1", "log", "memchr", "multer", @@ -1126,7 +1281,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 2.7.1", "proc-macro2", "quote", "rocket_http", @@ -1146,7 +1301,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper", - "indexmap", + "indexmap 2.7.1", "log", "memchr", "pear", @@ -1247,6 +1402,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.1", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1326,6 +1511,12 @@ dependencies = [ "loom", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.98" @@ -1492,7 +1683,7 @@ version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ - "indexmap", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -1686,6 +1877,64 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1717,6 +1966,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 40b258e..7a654a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +serde = "1.0.217" serde_json = "1.0" rand = "0.9.0" +serde_with = "3.12.0" [dependencies.url] version = "2.5.4" diff --git a/src/cards.json b/src/cards.json index c0f16f3..7e7cce0 100644 --- a/src/cards.json +++ b/src/cards.json @@ -1,6 +1,6 @@ [ { - "scryfall_id": "{3df927c0-9aa9-450b-ab2a-ae12c5489d57}", + "id": "{3df927c0-9aa9-450b-ab2a-ae12c5489d57}", "name": "Supplant Form", "png": "https://cards.scryfall.io/png/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.png?1562824437", "jpg": "https://cards.scryfall.io/small/front/3/d/3df927c0-9aa9-450b-ab2a-ae12c5489d57.jpg?1562824437", @@ -8,7 +8,7 @@ "count": 2 }, { - "scryfall_id": "{30202613-d05f-4f47-af97-d0b75ccac293}", + "id": "{30202613-d05f-4f47-af97-d0b75ccac293}", "name": "Memory Lapse", "png": "https://cards.scryfall.io/png/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.png?1634131658", "jpg": "https://cards.scryfall.io/small/front/3/0/30202613-d05f-4f47-af97-d0b75ccac293.jpg?1634131658", @@ -16,7 +16,7 @@ "count": 8 }, { - "scryfall_id": "{ad88e5ee-0eee-47af-a7b4-9bac044e1c8c}", + "id": "{ad88e5ee-0eee-47af-a7b4-9bac044e1c8c}", "name": "Accumulated Knowledge", "png": "https://cards.scryfall.io/png/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.png?1562439718", "jpg": "https://cards.scryfall.io/small/front/a/d/ad88e5ee-0eee-47af-a7b4-9bac044e1c8c.jpg?1562439718", @@ -24,7 +24,7 @@ "count": 4 }, { - "scryfall_id": "{806e5536-103b-4d6c-83b4-8659a55b25b5}", + "id": "{806e5536-103b-4d6c-83b4-8659a55b25b5}", "name": "Mystic Retrieval", "png": "https://cards.scryfall.io/png/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.png?1736467785", "jpg": "https://cards.scryfall.io/small/front/8/0/806e5536-103b-4d6c-83b4-8659a55b25b5.jpg?1736467785", @@ -32,7 +32,7 @@ "count": 2 }, { - "scryfall_id": "{36fa9a0b-b0c9-43ea-ba11-99d7982f974e}", + "id": "{36fa9a0b-b0c9-43ea-ba11-99d7982f974e}", "name": "Mystical Tutor", "png": "https://cards.scryfall.io/png/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.png?1675199375", "jpg": "https://cards.scryfall.io/small/front/3/6/36fa9a0b-b0c9-43ea-ba11-99d7982f974e.jpg?1675199375", @@ -40,7 +40,7 @@ "count": 2 }, { - "scryfall_id": "{21fe2f1b-bfd1-4682-ae23-069300de3791}", + "id": "{21fe2f1b-bfd1-4682-ae23-069300de3791}", "name": "Svyelunite Temple", "png": "https://cards.scryfall.io/png/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.png?1562867851", "jpg": "https://cards.scryfall.io/small/front/2/1/21fe2f1b-bfd1-4682-ae23-069300de3791.jpg?1562867851", @@ -48,7 +48,7 @@ "count": 2 }, { - "scryfall_id": "{8beb987c-1b67-4a4e-ae71-58547afad2a0}", + "id": "{8beb987c-1b67-4a4e-ae71-58547afad2a0}", "name": "Brainstorm", "png": "https://cards.scryfall.io/png/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.png?1726284649", "jpg": "https://cards.scryfall.io/small/front/8/b/8beb987c-1b67-4a4e-ae71-58547afad2a0.jpg?1726284649", @@ -56,7 +56,7 @@ "count": 2 }, { - "scryfall_id": "{c548d140-3d81-4b33-9985-87703d316a83}", + "id": "{c548d140-3d81-4b33-9985-87703d316a83}", "name": "Dance of the Skywise", "png": "https://cards.scryfall.io/png/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.png?1562792779", "jpg": "https://cards.scryfall.io/small/front/c/5/c548d140-3d81-4b33-9985-87703d316a83.jpg?1562792779", @@ -64,7 +64,7 @@ "count": 2 }, { - "scryfall_id": "{7132999f-6e2d-4689-8131-7b12076a348d}", + "id": "{7132999f-6e2d-4689-8131-7b12076a348d}", "name": "Island", "png": "https://cards.scryfall.io/png/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.png?1655495395", "jpg": "https://cards.scryfall.io/small/front/7/1/7132999f-6e2d-4689-8131-7b12076a348d.jpg?1655495395", @@ -72,7 +72,7 @@ "count": 18 }, { - "scryfall_id": "{7ca8a427-5c20-4650-bbb5-9895e4f2c009}", + "id": "{7ca8a427-5c20-4650-bbb5-9895e4f2c009}", "name": "Ray of Command", "png": "https://cards.scryfall.io/png/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.png?1592754536", "jpg": "https://cards.scryfall.io/small/front/7/c/7ca8a427-5c20-4650-bbb5-9895e4f2c009.jpg?1592754536", @@ -80,7 +80,7 @@ "count": 2 }, { - "scryfall_id": "{2dc0bafd-debc-4b62-9fe0-56b4aad02484}", + "id": "{2dc0bafd-debc-4b62-9fe0-56b4aad02484}", "name": "Unsubstantiate", "png": "https://cards.scryfall.io/png/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.png?1594735888", "jpg": "https://cards.scryfall.io/small/front/2/d/2dc0bafd-debc-4b62-9fe0-56b4aad02484.jpg?1594735888", @@ -88,7 +88,7 @@ "count": 2 }, { - "scryfall_id": "{ac2e32d0-f172-4934-9d73-1bc2ab86586e}", + "id": "{ac2e32d0-f172-4934-9d73-1bc2ab86586e}", "name": "Dandân", "png": "https://cards.scryfall.io/png/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.png?1562781784", "jpg": "https://cards.scryfall.io/small/front/a/c/ac2e32d0-f172-4934-9d73-1bc2ab86586e.jpg?1562781784", @@ -96,7 +96,7 @@ "count": 10 }, { - "scryfall_id": "{692a5bfa-9a50-4610-acd7-19fc0a53f798}", + "id": "{692a5bfa-9a50-4610-acd7-19fc0a53f798}", "name": "Mind Bend", "png": "https://cards.scryfall.io/png/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.png?1562549129", "jpg": "https://cards.scryfall.io/small/front/6/9/692a5bfa-9a50-4610-acd7-19fc0a53f798.jpg?1562549129", @@ -104,7 +104,7 @@ "count": 2 }, { - "scryfall_id": "{a340af37-c0a7-4f26-974b-95397b5c32f7}", + "id": "{a340af37-c0a7-4f26-974b-95397b5c32f7}", "name": "Remote Isle", "png": "https://cards.scryfall.io/png/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.png?1709139228", "jpg": "https://cards.scryfall.io/small/front/a/3/a340af37-c0a7-4f26-974b-95397b5c32f7.jpg?1709139228", @@ -112,7 +112,7 @@ "count": 2 }, { - "scryfall_id": "{8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2}", + "id": "{8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2}", "name": "Insidious Will", "png": "https://cards.scryfall.io/png/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.png?1576381260", "jpg": "https://cards.scryfall.io/small/front/8/e/8eafb2bb-58bf-4c6b-ae8f-91bcea12c7d2.jpg?1576381260", @@ -120,7 +120,7 @@ "count": 2 }, { - "scryfall_id": "{bfac9395-7ca5-48dd-ab83-7c26ada12f61}", + "id": "{bfac9395-7ca5-48dd-ab83-7c26ada12f61}", "name": "Izzet Boilerworks", "png": "https://cards.scryfall.io/png/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.png?1712355049", "jpg": "https://cards.scryfall.io/small/front/b/f/bfac9395-7ca5-48dd-ab83-7c26ada12f61.jpg?1712355049", @@ -128,7 +128,7 @@ "count": 2 }, { - "scryfall_id": "{349ac7e6-af38-4dc3-abfe-369564c75630}", + "id": "{349ac7e6-af38-4dc3-abfe-369564c75630}", "name": "Predict", "png": "https://cards.scryfall.io/png/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.png?1592710621", "jpg": "https://cards.scryfall.io/small/front/3/4/349ac7e6-af38-4dc3-abfe-369564c75630.jpg?1592710621", @@ -136,7 +136,7 @@ "count": 2 }, { - "scryfall_id": "{f9ed455f-95cd-48ca-8b0d-4db259347bb6}", + "id": "{f9ed455f-95cd-48ca-8b0d-4db259347bb6}", "name": "Diminishing Returns", "png": "https://cards.scryfall.io/png/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.png?1580013930", "jpg": "https://cards.scryfall.io/small/front/f/9/f9ed455f-95cd-48ca-8b0d-4db259347bb6.jpg?1580013930", @@ -144,7 +144,7 @@ "count": 2 }, { - "scryfall_id": "{78b384d3-3adf-493a-8b89-bfe68fd1c3e2}", + "id": "{78b384d3-3adf-493a-8b89-bfe68fd1c3e2}", "name": "Vision Charm", "png": "https://cards.scryfall.io/png/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.png?1562277700", "jpg": "https://cards.scryfall.io/small/front/7/8/78b384d3-3adf-493a-8b89-bfe68fd1c3e2.jpg?1562277700", @@ -152,7 +152,7 @@ "count": 2 }, { - "scryfall_id": "{8798a4f1-34bb-449d-a8cc-faf8bda8e0ab}", + "id": "{8798a4f1-34bb-449d-a8cc-faf8bda8e0ab}", "name": "Crystal Spray", "png": "https://cards.scryfall.io/png/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.png?1562922403", "jpg": "https://cards.scryfall.io/small/front/8/7/8798a4f1-34bb-449d-a8cc-faf8bda8e0ab.jpg?1562922403", @@ -160,7 +160,7 @@ "count": 2 }, { - "scryfall_id": "{63eb18e0-c723-4de4-8498-c5362c75b2b4}", + "id": "{63eb18e0-c723-4de4-8498-c5362c75b2b4}", "name": "Halimar Depths", "png": "https://cards.scryfall.io/png/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.png?1726285434", "jpg": "https://cards.scryfall.io/small/front/6/3/63eb18e0-c723-4de4-8498-c5362c75b2b4.jpg?1726285434", @@ -168,7 +168,7 @@ "count": 2 }, { - "scryfall_id": "{a0f0c20c-184e-4d27-ae8b-933abb6fee0c}", + "id": "{a0f0c20c-184e-4d27-ae8b-933abb6fee0c}", "name": "Metamorphose", "png": "https://cards.scryfall.io/png/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.png?1562533013", "jpg": "https://cards.scryfall.io/small/front/a/0/a0f0c20c-184e-4d27-ae8b-933abb6fee0c.jpg?1562533013", @@ -176,7 +176,7 @@ "count": 2 }, { - "scryfall_id": "{67652446-6d12-4e2a-bb51-ba685f2e79d1}", + "id": "{67652446-6d12-4e2a-bb51-ba685f2e79d1}", "name": "Mystic Sanctuary", "png": "https://cards.scryfall.io/png/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.png?1706241201", "jpg": "https://cards.scryfall.io/small/front/6/7/67652446-6d12-4e2a-bb51-ba685f2e79d1.jpg?1706241201", @@ -184,7 +184,7 @@ "count": 2 }, { - "scryfall_id": "{5833825f-aeb0-41bb-92a4-13e869765295}", + "id": "{5833825f-aeb0-41bb-92a4-13e869765295}", "name": "Lonely Sandbar", "png": "https://cards.scryfall.io/png/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.png?1708054955", "jpg": "https://cards.scryfall.io/small/front/5/8/5833825f-aeb0-41bb-92a4-13e869765295.jpg?1708054955", @@ -192,7 +192,7 @@ "count": 2 }, { - "scryfall_id": "{224e8255-7bc6-44a2-86af-14f8446f4f77}", + "id": "{224e8255-7bc6-44a2-86af-14f8446f4f77}", "name": "Temple of Epiphany", "png": "https://cards.scryfall.io/png/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.png?1730491248", "jpg": "https://cards.scryfall.io/small/front/2/2/224e8255-7bc6-44a2-86af-14f8446f4f77.jpg?1730491248", diff --git a/src/game.rs b/src/game.rs index 1edd652..c5ce3d6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,14 +1,16 @@ use rand::seq::SliceRandom; use rand::{random_bool, rng}; -use rocket::serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use std::collections::{HashMap, VecDeque}; use url::Url; use uuid::Uuid; -#[derive(Deserialize, Serialize, Clone)] -#[serde(crate = "rocket::serde")] +#[derive(Deserialize, Serialize, Clone, Debug)] +//#[serde(crate = "rocket::serde")] struct CardInfo { #[serde(with = "uuid::serde::braced")] - scryfall_id: Uuid, + id: Uuid, name: String, png: Url, jpg: Url, @@ -16,65 +18,79 @@ struct CardInfo { count: usize, } -#[derive(Deserialize, Serialize, Clone, Copy)] -#[serde(crate = "rocket::serde")] +#[derive(Deserialize, Serialize, Clone, Copy, Debug)] +//#[serde(crate = "rocket::serde")] pub enum Player { A, B, } -#[derive(Deserialize, Serialize, Clone)] -#[serde(crate = "rocket::serde")] +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(deny_unknown_fields)] +//#[serde(crate = "rocket::serde")] struct InPlay { + #[serde(with = "uuid::serde::braced")] id: Uuid, position_x: u16, position_y: u16, owner: Player, -} - -#[derive(Serialize, Clone)] -#[serde(crate = "rocket::serde")] -struct Hand { - cards: Vec, - owner: Player, + tapped: bool, + #[serde(with = "uuid::serde::braced")] + play_id: Uuid, //locked: bool, // prevents untapping } -#[derive(Serialize, Clone)] -#[serde(crate = "rocket::serde")] +#[derive(Serialize, Clone, Debug)] +//#[serde(crate = "rocket::serde")] +struct Hand { + cards: Vec, + owner: Player, +} + +#[derive(Serialize, Clone, Debug)] +//#[serde(crate = "rocket::serde")] enum Event { Shuffle(Player), Draw(Player, usize), Pass(Player, Player), ChangeLifeTotal(Player, i32, i32), + FadeFromHand(Player), + PlayFromHand(Player), + Bounce(Player), + Tap(Uuid), } -#[derive(Serialize, Clone)] -#[serde(crate = "rocket::serde")] +#[derive(Serialize, Clone, Debug)] +//#[serde(crate = "rocket::serde")] struct CountedEvent { id: usize, event: Event, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GameState { - deck: Vec, + deck: VecDeque, discard_pile: Vec, shadow_realm: Vec, - play: Vec, + play: HashMap, hands: Vec, life_totals: Vec, turn_player: Player, - //card_map: HashMap, events: Vec, } +#[serde_as] #[derive(Serialize)] -#[serde(crate = "rocket::serde")] +#[serde(deny_unknown_fields)] +//#[serde(crate = "rocket::serde")] pub struct OnePlayerGameState { discard_pile: Vec, shadow_realm: Vec, - play: Vec, + // I hate my life. Uuid seems to be causing issues and for some + // reason serde just keeps giving an empty object instead of + // serializing properly. + #[serde_as(as = "Vec<(_, _)>")] + play: HashMap, hand: Vec, life_totals: Vec, turn_player: Player, @@ -87,7 +103,12 @@ impl GameState { OnePlayerGameState { discard_pile: self.discard_pile.clone(), shadow_realm: self.shadow_realm.clone(), - play: self.play.clone(), + play: self + .play + .clone() + .into_iter() + .map(|(key, value)| (format!("{}", key.hyphenated()), value)) + .collect(), hand: match player { Player::A => self.hands[0].cards.clone(), _ => self.hands[1].cards.clone(), @@ -109,22 +130,22 @@ pub fn new() -> GameState { let parsed_cards: Vec = serde_json::from_str(CARD_DATA_JSON).unwrap(); /*let card_map = parsed_cards .into_iter() - .map(|card| (card.scryfall_id, card)) + .map(|card| (card.id, card)) .collect::>();*/ let starting_player = match random_bool(0.5) { true => Player::A, _ => Player::B, }; - let mut deck: Vec = parsed_cards + let mut deck: VecDeque = parsed_cards .iter() - .flat_map(move |card| vec![card.scryfall_id.clone(); card.count]) - .collect(); - deck.shuffle(&mut rng()); + .flat_map(move |card| vec![card.id.clone(); card.count]) + .collect::>(); + deck.make_contiguous().shuffle(&mut rng()); GameState { deck, discard_pile: vec![], shadow_realm: vec![], - play: vec![], + play: HashMap::new(), hands: vec![ Hand { cards: vec![], @@ -137,7 +158,6 @@ pub fn new() -> GameState { ], life_totals: vec![20; 2], turn_player: starting_player, - //card_map, events: vec![], } } @@ -155,7 +175,7 @@ pub fn draw(game_state: &mut GameState, count: usize, player: Player) { game_state .deck .split_off(game_state.deck.len() - count) - .as_mut_slice(), + .make_contiguous(), ); } @@ -164,7 +184,7 @@ pub fn shuffle(game_state: &mut GameState, player: Player) { id: game_state.events.len(), event: Event::Shuffle(player), }); - game_state.deck.shuffle(&mut rng()); + game_state.deck.make_contiguous().shuffle(&mut rng()); } pub fn get_game_one_player(game_state: &GameState, player: Player) -> String { @@ -179,7 +199,7 @@ pub fn pass(game_state: &mut GameState) { let current_player = &game_state.turn_player; let new_player = match current_player { Player::A => Player::B, - _ => Player::A, + Player::B => Player::A, }; game_state.events.push(CountedEvent { id: game_state.events.len(), @@ -191,9 +211,8 @@ pub fn pass(game_state: &mut GameState) { pub fn change_life(game_state: &mut GameState, diff: i32, player: Player) { let mut player_life = match player { Player::A => game_state.life_totals[0].clone(), - _ => game_state.life_totals[1].clone(), + Player::B => game_state.life_totals[1].clone(), }; - //let diffed_player = player_life.clone() + diff; game_state.events.push(CountedEvent { id: game_state.events.len(), event: Event::ChangeLifeTotal(player, player_life.clone(), player_life.clone() + diff), @@ -201,6 +220,69 @@ pub fn change_life(game_state: &mut GameState, diff: i32, player: Player) { player_life += diff; match player { Player::A => game_state.life_totals[0] = player_life, - _ => game_state.life_totals[1] = player_life, + Player::B => game_state.life_totals[1] = player_life, }; } + +pub fn fade_from_hand(game_state: &mut GameState, hand_index: usize, player: Player, bottom: bool) { + let player_hand = match player { + Player::A => &mut game_state.hands[0], + Player::B => &mut game_state.hands[1], + }; + game_state.events.push(CountedEvent { + id: game_state.events.len(), + event: Event::FadeFromHand(player.clone()), + }); + let card = player_hand.cards.remove(hand_index); + if bottom { + game_state.deck.push_front(card); + } else { + game_state.deck.push_back(card); + } +} + +pub fn play_from_hand(game_state: &mut GameState, hand_index: usize, player: Player) { + let player_hand = match player { + Player::A => &mut game_state.hands[0], + Player::B => &mut game_state.hands[1], + }; + game_state.events.push(CountedEvent { + id: game_state.events.len(), + event: Event::PlayFromHand(player.clone()), + }); + let card = player_hand.cards.remove(hand_index); + let play_uuid = Uuid::new_v4(); + game_state.play.insert( + play_uuid, + InPlay { + id: card, + position_x: 5, + position_y: 50, + owner: player, + tapped: false, + play_id: play_uuid, + }, + ); +} + +pub fn bounce(game_state: &mut GameState, play_id: Uuid) { + let played_card = game_state.play.remove(&play_id).unwrap(); + let player_hand = match played_card.owner { + Player::A => &mut game_state.hands[0], + Player::B => &mut game_state.hands[1], + }; + game_state.events.push(CountedEvent { + id: game_state.events.len(), + event: Event::Bounce(played_card.owner), + }); + player_hand.cards.push(played_card.id); +} + +pub fn tap(game_state: &mut GameState, play_id: Uuid) { + game_state.events.push(CountedEvent { + id: game_state.events.len(), + event: Event::Tap(play_id.clone()), + }); + let played_card = game_state.play.get_mut(&play_id).unwrap(); + played_card.tapped = !played_card.tapped; +} diff --git a/src/main.rs b/src/main.rs index 7f32c24..8342ed8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,12 @@ use rocket::response::status::BadRequest; use rocket::State; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use uuid::Uuid; +use uuid::{uuid, Uuid}; #[get("/")] fn index(player_uuids: &State) -> String { format!( - "a: localhost:8000/{}/\nb: localhost:8000/{}/", + "localhost:8000/{}/\nlocalhost:8000/{}/", player_uuids.a, player_uuids.b ) } @@ -57,6 +57,58 @@ fn pass( Ok(format!("{}", game::get_events(&game_state))) } +#[get("//bounce/")] +fn bounce( + uuid: Uuid, + play_id: Uuid, + game_state_arc: &State, + player_uuids: &State, +) -> Result> { + match player_uuids.map.get(&uuid) { + Some(_) => (), + None => return Err(BadRequest(format!("Invalid player {}.", uuid))), + }; + let game_state_mutex = Arc::clone(&game_state_arc.state); + let mut game_state = game_state_mutex.lock().unwrap(); + game::bounce(&mut game_state, play_id); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//tap/")] +fn tap( + uuid: Uuid, + play_id: Uuid, + game_state_arc: &State, + player_uuids: &State, +) -> Result> { + match player_uuids.map.get(&uuid) { + Some(_) => (), + None => return Err(BadRequest(format!("Invalid player {}.", uuid))), + }; + let game_state_mutex = Arc::clone(&game_state_arc.state); + let mut game_state = game_state_mutex.lock().unwrap(); + game::tap(&mut game_state, play_id); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//play/")] +fn play( + uuid: Uuid, + index: usize, + game_state_arc: &State, + player_uuids: &State, +) -> Result> { + let player = match player_uuids.map.get(&uuid) { + Some(player) => player, + None => return Err(BadRequest(format!("Invalid player {}.", uuid))), + }; + let game_state_mutex = Arc::clone(&game_state_arc.state); + let mut game_state = game_state_mutex.lock().unwrap(); + game::play_from_hand(&mut game_state, index, player.clone()); + //println!("{:#?}", game_state); + Ok(format!("{}", game::get_events(&game_state))) +} + #[get("//get_state")] fn get_state( uuid: Uuid, @@ -95,6 +147,46 @@ fn draw( )) } +#[get("//fade/")] +fn fade( + uuid: Uuid, + index: usize, + game_state_arc: &State, + player_uuids: &State, +) -> Result> { + let game_state_mutex = Arc::clone(&game_state_arc.state); + let mut game_state = game_state_mutex.lock().unwrap(); + let player = match player_uuids.map.get(&uuid) { + Some(player) => player, + None => return Err(BadRequest(format!("Invalid player {}.", uuid))), + }; + game::fade_from_hand(&mut game_state, index, player.clone(), false); + Ok(format!( + "{}", + game::get_game_one_player(&game_state, player.clone()) + )) +} + +#[get("//fade-bottom/")] +fn fade_bottom( + uuid: Uuid, + index: usize, + game_state_arc: &State, + player_uuids: &State, +) -> Result> { + let game_state_mutex = Arc::clone(&game_state_arc.state); + let mut game_state = game_state_mutex.lock().unwrap(); + let player = match player_uuids.map.get(&uuid) { + Some(player) => player, + None => return Err(BadRequest(format!("Invalid player {}.", uuid))), + }; + game::fade_from_hand(&mut game_state, index, player.clone(), true); + Ok(format!( + "{}", + game::get_game_one_player(&game_state, player.clone()) + )) +} + #[get("//life/")] fn life( uuid: Uuid, @@ -127,15 +219,26 @@ struct PlayerUuids { #[launch] fn rocket() -> _ { - let game_state = game::new(); + let mut 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)); + + #[cfg(debug_assertions)] + let a_uuid: Uuid = uuid!("9b0a2a95-72a9-4e03-930e-a9583d2a2a5a"); + #[cfg(not(debug_assertions))] let a_uuid: Uuid = 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)]); println!("A: {}", a_uuid); println!("B: {}", b_uuid); rocket::build() + .mount("/", FileServer::from(relative!("static/assets")).rank(0)) .mount( format!("/{}", a_uuid.hyphenated()), FileServer::from(relative!("static")), @@ -146,7 +249,20 @@ fn rocket() -> _ { ) .mount( "/", - routes![index, get_events, get_state, draw, shuffle, pass, life,], + routes![ + index, + get_events, + get_state, + draw, + shuffle, + pass, + life, + fade, + fade_bottom, + play, + bounce, + tap, + ], ) .manage(ArcMutexGameState { state: game_state_arc,