Added frontend, build scripts
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
/target
|
||||
Rocket.toml
|
||||
/static
|
||||
/static/script.js
|
||||
/static/style.css
|
||||
/static/style.css.map
|
||||
/static/assets
|
||||
|
||||
4
build
Executable file
4
build
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
lsc -bc --no-header -o static/ web-src/script.ls
|
||||
npx sass web-src/style.scss static/style.css
|
||||
cargo $(basename $0) "${@:1}"
|
||||
152
static/cards.json
Normal file
152
static/cards.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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.)"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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}."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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}.)"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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.)"
|
||||
},
|
||||
"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.)"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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}."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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.)"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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}."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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.)"
|
||||
},
|
||||
"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}."
|
||||
}
|
||||
}
|
||||
23
static/index.html
Normal file
23
static/index.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Dandadân</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script defer src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="hand" id="opponent-hand"></div>
|
||||
<div id="border"></div>
|
||||
<div class="hand" id="own-hand"></div>
|
||||
</main>
|
||||
<aside id="view-card-container">
|
||||
<div id="param-container">
|
||||
<span id="param"></span>
|
||||
</div>
|
||||
<img src=".." alt="" id="view-card" />
|
||||
</aside>
|
||||
</body>
|
||||
</html>
|
||||
107
web-src/script.ls
Normal file
107
web-src/script.ls
Normal file
@ -0,0 +1,107 @@
|
||||
(->>
|
||||
const err = -> console.error it
|
||||
const $opponent-hand = document.query-selector '#opponent-hand'
|
||||
const $own-hand = document.query-selector '#own-hand'
|
||||
const $view-card = document.query-selector '#view-card'
|
||||
const $view-card-container = document.query-selector '#view-card-container'
|
||||
const $param = document.query-selector '#param'
|
||||
const [push-param, pop-param] = (->
|
||||
param = ""
|
||||
clear-param-timer = 0
|
||||
setInterval (->
|
||||
clear-param-timer-- if clear-param-timer
|
||||
param := "" unless clear-param-timer
|
||||
$param.inner-text = param
|
||||
), 100
|
||||
const push-param = ->
|
||||
param += it
|
||||
$param.inner-text = param
|
||||
clear-param-timer := 20
|
||||
const pop-full-param = ->
|
||||
ret = param
|
||||
param := ""
|
||||
$param.inner-text = param
|
||||
if ret == ""
|
||||
1
|
||||
else
|
||||
+ret
|
||||
[push-param, pop-full-param]
|
||||
)!
|
||||
|
||||
const gen-array = -> Array.from { length: it }, (_, i) -> i
|
||||
const cards = await fetch \./cards.json .then (.json!) .catch err
|
||||
const make-card = ->
|
||||
img = document.create-element \img
|
||||
img.src = it
|
||||
img
|
||||
const make-blank-card = -> make-card \assets/card_back.jpg
|
||||
const gen-opponent-cards$ = ->
|
||||
diff = it - $opponent-hand.child-element-count
|
||||
switch
|
||||
| diff < 0
|
||||
while diff++
|
||||
$opponent-hand.remove-child $opponent-hand.first-child
|
||||
| diff > 0
|
||||
$opponent-hand.append ...(gen-array diff .map make-blank-card)
|
||||
| _
|
||||
void
|
||||
const get-state = ->>
|
||||
player-state-json = await fetch \./get_state .then (.text!) .catch err
|
||||
player-state = JSON.parse player-state-json
|
||||
events-json = await fetch \/get_events .then (.text!) .catch err
|
||||
events = JSON.parse events-json
|
||||
return { player-state-json, player-state, events-json, events }
|
||||
|
||||
const show-card$ = (e) ->
|
||||
const hide-card$ = ->
|
||||
$view-card.style.opacity = 0
|
||||
e.target.remove-event-listener \mouseleave hide-card$
|
||||
$view-card.src = e.target.get-attribute \data-png
|
||||
$view-card.style.opacity = 1
|
||||
e.target.add-event-listener \mouseleave hide-card$
|
||||
state = await get-state!
|
||||
window.state = state
|
||||
const apply-state$ = ->
|
||||
gen-opponent-cards$ it.opponent_cards_in_hand
|
||||
#const my-card-ids = [...$own-hand.children].map ->
|
||||
# it.get-attribute \data-id*/
|
||||
if document.body.class-list.contains \my-turn
|
||||
document.body.class-list.remove \my-turn if it.you != it.turn_player
|
||||
else
|
||||
document.body.class-list.add \my-turn if it.you == it.turn_player
|
||||
if it.hand.length != $own-hand.children.length
|
||||
$own-hand.innerHTML = ''
|
||||
it.hand.for-each (card-id, index) ->
|
||||
card-data = cards[card-id]
|
||||
card = make-card card-data.jpg
|
||||
card.set-attribute \data-index index
|
||||
card.add-event-listener \mouseenter show-card$
|
||||
card.set-attribute \data-png card-data.png
|
||||
$own-hand.append-child card
|
||||
apply-state$ state.player-state
|
||||
|
||||
set-interval (->>
|
||||
const new-state = await get-state!
|
||||
unless new-state.player-state-json == state.player-state-json
|
||||
apply-state$ new-state.player-state
|
||||
state := new-state
|
||||
), 1000
|
||||
mouse-x = mouse-y = 0
|
||||
document
|
||||
..addEventListener \mousemove ->
|
||||
mouse-x = it.client-x
|
||||
mouse-y = it.client-y
|
||||
if mouse-x > window.inner-width / 2
|
||||
$view-card-container.class-list.remove \right
|
||||
else
|
||||
$view-card-container.class-list.add \right
|
||||
..addEventListener \keyup ->
|
||||
current-mouse-node = [...document.query-selector-all \:hover][* - 1]
|
||||
switch it.key
|
||||
| \c => fetch "./draw/#{pop-param!}"
|
||||
| \e => fetch "./pass"
|
||||
| \0 \1 \2 \3 \4 \5 \6 \7 \8 \9 => push-param that
|
||||
| \t =>
|
||||
if current-mouse-node?.has-attribute \data-index
|
||||
fetch "./fade/#{current-mouse-node.get-attribute \data-index}"
|
||||
)!
|
||||
91
web-src/style.scss
Normal file
91
web-src/style.scss
Normal file
@ -0,0 +1,91 @@
|
||||
$view-container-side-offset: 5%;
|
||||
$param-container-side-offset: 5%;
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, main {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hand {
|
||||
height: 14%;
|
||||
width: 100%;
|
||||
background-color: #0002;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 0.2%;
|
||||
align-items: center;
|
||||
img {
|
||||
display: flex;
|
||||
height: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
aside {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: $view-container-side-offset;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
img {
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
left: initial;
|
||||
right: $view-container-side-offset;
|
||||
}
|
||||
|
||||
#param-container {
|
||||
position: absolute;
|
||||
top: 5%;
|
||||
left: $param-container-side-offset;
|
||||
}
|
||||
|
||||
.right #param-container {
|
||||
left: initial;
|
||||
right: $param-container-side-offset;
|
||||
}
|
||||
|
||||
#param {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.right #param {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#border {
|
||||
position: absolute;
|
||||
top: 48%;
|
||||
bottom: 48%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #f002;
|
||||
}
|
||||
|
||||
.my-turn #border {
|
||||
background-color: #0ff2;
|
||||
}
|
||||
Reference in New Issue
Block a user