Added score tracking, implemented zone drag-and-drop🐉

This commit is contained in:
Seoxi Ryouko
2025-03-06 08:38:18 -06:00
parent 67cc148bb8
commit 89856cc32b
6 changed files with 509 additions and 88 deletions

View File

@ -5,14 +5,14 @@ specifically designed to be used when players share a deck
definitely not a shameless untap clone
# todo
+ Add hotkeys for every zone (*Really* almost done!)
+ Add hotkeys for every zone (Basically done!)
+ ~~Add untap all hotkey~~
+ Add viewing top of deck
+ ~~Add searching deck~~
+ Add score values (usable for life counters)
+ ~~Add score values (usable for life counters)~~
+ Make events visible
# maybe todo
+ Add drag-and-drop from hand and piles
+ ~~Add drag-and-drop from hand and piles~~
+ Make deck information, piles, and score info json-configurable
+ Use cookies instead of hardcoded url uuid

View File

@ -48,10 +48,13 @@ enum Event {
Draw(Player, usize),
Pass(Player, Player),
ChangeLifeTotal(Player, isize, isize),
FadeFromPlay(Uuid, bool),
FadeFromPile(Uuid, bool),
FadeFromHand(Player),
PlayFromHand(Player),
Bounce(Player),
Tap(Uuid),
TransferOwnership(Uuid),
Discard(Player),
Undiscard(Player),
Kill(Uuid, bool),
@ -253,6 +256,59 @@ pub fn change_life(game_state: &mut GameState, diff: isize, player: Player) {
};
}
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,
@ -281,7 +337,7 @@ pub fn play_from_hand(
game_state: &mut GameState,
hand_index: usize,
player: Player,
) -> Result<(), String> {
) -> Result<Uuid, String> {
let player_hand = match player {
Player::A => &mut game_state.hands[0],
Player::B => &mut game_state.hands[1],
@ -292,6 +348,7 @@ pub fn play_from_hand(
}
let card = player_hand.cards.remove(hand_index);
let play_uuid = Uuid::new_v4();
let return_uuid = play_uuid.clone();
game_state.play.insert(
play_uuid,
InPlay {
@ -303,7 +360,7 @@ pub fn play_from_hand(
play_id: play_uuid,
},
);
Ok(())
Ok(return_uuid)
}
pub fn bounce(game_state: &mut GameState, play_id: Uuid) -> Result<(), String> {
@ -330,6 +387,21 @@ pub fn tap(game_state: &mut GameState, play_id: Uuid) -> Result<(), String> {
}
}
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,
@ -415,7 +487,7 @@ pub fn unkill(
player: Player,
pile_index: usize,
shadow: bool,
) -> Result<(), String> {
) -> Result<Uuid, String> {
let pile = if shadow {
&mut game_state.shadow_realm
} else {
@ -428,13 +500,14 @@ pub fn unkill(
"discard pile"
};
return Err(format!(
"Transfer index out of bounds: {} ({})",
"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 return_uuid = play_uuid.clone();
game_state.play.insert(
play_uuid,
InPlay {
@ -446,7 +519,7 @@ pub fn unkill(
play_id: play_uuid,
},
);
Ok(())
Ok(return_uuid)
}
pub fn transfer_dead_card(

View File

@ -48,7 +48,7 @@ macro_rules! respond_ok {
macro_rules! respond {
($state:expr) => {
match $state {
Ok(()) => respond_ok!(),
Ok(_) => respond_ok!(),
Err(err_string) => (Status::BadRequest, (ContentType::Plain, err_string)),
}
};
@ -144,6 +144,18 @@ fn tap(
respond!(game::tap(&mut game_state, play_id))
}
#[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(
uuid: Uuid,
@ -156,6 +168,24 @@ fn play(
respond!(game::play_from_hand(&mut game_state, index, player.clone()))
}
#[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(
uuid: Uuid,
@ -180,6 +210,42 @@ fn unbanish(
respond!(game::unkill(&mut game_state, player.clone(), index, true))
}
#[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(
uuid: Uuid,
@ -240,6 +306,54 @@ fn unshadow(
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,
@ -414,16 +528,40 @@ fn fade_bottom(
))
}
#[post("/<uuid>/life/<count>")]
fn life(
#[post("/<uuid>/fade_from_play/<play_id>")]
fn fade_from_play(
uuid: Uuid,
count: isize,
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::fade_from_play(&mut game_state, play_id, false))
}
#[post("/<uuid>/fade_bottom_from_play/<play_id>")]
fn fade_bottom_from_play(
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::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, count, player.clone());
game::change_life(&mut game_state, diff, player.clone());
respond_ok!()
}
@ -473,12 +611,14 @@ fn rocket() -> _ {
draw,
shuffle,
pass,
life,
life_diff,
fade,
fade_bottom,
play,
play_at,
bounce,
tap,
transfer_ownership,
move_card,
discard,
kill,
@ -486,6 +626,8 @@ fn rocket() -> _ {
banish,
unkill,
unbanish,
unkill_at,
unbanish_at,
shadow,
unshadow,
undiscard,
@ -495,6 +637,12 @@ fn rocket() -> _ {
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 {

View File

@ -11,9 +11,11 @@
<main>
<div class="hand" id="opponent-hand"></div>
<div id="border"></div>
<div id="play"></div>
<div id="play" data-border-text=" "></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>
<aside id="view-card-container">
<div id="param-container">
@ -38,5 +40,9 @@
<span id="shadow-realm-count"></span>
</div>
</aside>
<aside id="counts">
<div class="count" id="opponent-score"></div>
<div class="count" id="own-score"></div>
</aside>
</body>
</html>

View File

@ -1,28 +1,41 @@
(->>
const err = -> console.error it
const err = ->
console.error it
$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 (->
(fetch req .then ->>
throw await it.text! unless it.ok
console.log it
it
) .catch err
const life-diff$ = ->
fetch-post "./life_diff/#it"
life-index = +('B' == state.player-state.you)
current-life = state.player-state.life_totals[life-index]
$own-score.inner-text = current-life + it
window.fetch-post = fetch-post
const $opponent-hand = document.query-selector '#opponent-hand'
const $own-hand = document.query-selector '#own-hand'
const $view-card = document.query-selector '#view-card'
const $view-card-container = document.query-selector '#view-card-container'
const $param = document.query-selector '#param'
const $play = document.query-selector '#play'
const $discard-pile = document.query-selector '#discard-pile'
const $discard-pile-img = document.query-selector '#discard-pile-img'
const $discard-pile-count = document.query-selector '#discard-pile-count'
const $shadow-realm = document.query-selector '#shadow-realm'
const $shadow-realm-img = document.query-selector '#shadow-realm-img'
const $shadow-realm-count = document.query-selector '#shadow-realm-count'
const $deck = document.query-selector '#deck'
const $deck-img = document.query-selector '#deck-img'
const $deck-count = document.query-selector '#deck-count'
const $extra = document.query-selector '#extra'
const $ = -> document.query-selector it
const $opponent-hand = $ \#opponent-hand
const $own-hand = $ \#own-hand
const $view-card = $ \#view-card
const $view-card-container = $ \#view-card-container
const $param = $ \#param
const $play = $ \#play
const $discard-pile = $ \#discard-pile
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] = (->
param = ""
clear-param-timer = 0
@ -52,7 +65,7 @@
const get-play-size = -> $play.get-bounding-client-rect!
const gen-array = -> Array.from { length: it }, (_, i) -> i
const gen-array-of = (it, val) -> Array.from { length: it }, -> val
const cards = await fetch \/cards.json .then (.json!)
const cards = await fetch \/cards.json .then (.json!)
window.cards = cards
const $make-card = ->
$img = document.create-element \img
@ -70,9 +83,9 @@
| _
void
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
events-json = await fetch \/get_events .then (.text!)
events-json = await fetch \/get_events .then (.text!)
events = JSON.parse events-json
play-map = new Map player-state.play.map -> [it.id, it]
return { player-state-json, player-state, events-json, events, play-map }
@ -87,6 +100,10 @@
state = await get-state!
const apply-state$ = ->>
{ 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
if document.body.class-list.contains \my-turn
document.body.class-list.remove \my-turn if player-state.you != player-state.turn_player
@ -124,6 +141,23 @@
..set-attribute \data-hand-index index
..set-attribute \data-text card-data.txt
..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$
$own-hand.append-child card
next-play-id-list = [...player-state.play.map (.play_id)].sort!join \|
@ -135,6 +169,7 @@
relative-y = it.position_y
else
relative-y = 100 - it.position_y
{ play_id } = it # for closure purposes
img = $make-card card-data.jpg
img
..class-list.add \in-play-card
@ -149,6 +184,12 @@
..set-attribute \data-owner it.owner
..set-attribute \data-owned it.owner == player-state.you
..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 \dragstart ->
it.target.set-attribute \data-start-x it.target.offset-left
@ -156,26 +197,33 @@
it.target.set-attribute \data-client-x it.client-x
it.target.set-attribute \data-client-y it.client-y
..add-event-listener \dragend ->
const start-x = +it.target.get-attribute \data-start-x
const start-y = +it.target.get-attribute \data-start-y
const start-client-x = +it.target.get-attribute \data-client-x
const start-client-y = +it.target.get-attribute \data-client-y
const new-x = it.client-x + (start-x - start-client-x)
const new-y = it.client-y + (start-y - start-client-y)
const scale-x = +start-x / +(it.target.get-attribute \data-play-x)
const scale-y = +start-y / +(it.target.get-attribute \data-play-y)
const x = clamp 5 Math.round(new-x / scale-x), 95
const y = clamp 5 Math.round(new-y / scale-y), 95
it.target.style
..left = x + \%
..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"
switch $drop-zone
| $discard-pile => fetch-post "./kill/#play_id"
| $shadow-realm => fetch-post "./banish/#play_id"
| $deck => fetch-post "./fade/#index"
| $play =>
const start-x = +it.target.get-attribute \data-start-x
const start-y = +it.target.get-attribute \data-start-y
const start-client-x = +it.target.get-attribute \data-client-x
const start-client-y = +it.target.get-attribute \data-client-y
const new-x = it.client-x + (start-x - start-client-x)
const new-y = it.client-y + (start-y - start-client-y)
const scale-x = +start-x / +(it.target.get-attribute \data-play-x)
const scale-y = +start-y / +(it.target.get-attribute \data-play-y)
const x = clamp 5 Math.round(new-x / scale-x), 95
const y = clamp 5 Math.round(new-y / scale-y), 95
if y > 90 && \true == img.get-attribute \data-owned
return fetch-post "./bounce/#play_id"
it.target.style
..left = x + \%
..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-id-list := next-play-id-list
else
@ -189,39 +237,102 @@
$element.style
..left = it.position_x + \%
..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-y relative-y
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) ->
if card-id
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-index" index
ret.set-attribute "data-list-type" list-type
ret.set-attribute \data-index index
ret.set-attribute \data-list-type list-type
ret.add-event-listener \mouseenter show-card$
ret
else
$make-blank-card!
$extra.innerHTML = ""
$card-list.for-each -> $extra.append it
$extra-list.innerHTML = ""
$card-list.for-each -> $extra-list.append it
$extra.set-attribute \data-pile-type list-type
$extra.class-list.remove \hidden
$discard-pile-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-count.add-event-listener \click -> open-list state.player-state.discard_pile, \discard
$shadow-realm-img.add-event-listener \click -> open-list state.player-state.shadow_realm, \shadow
$shadow-realm-count.add-event-listener \click -> open-list state.player-state.shadow_realm, \shadow
$deck-img.add-event-listener \click -> open-list (gen-array-of state.player-state.deck_size, null), \deck
$deck-count.add-event-listener \click -> open-list (gen-array-of state.player-state.deck_size, null), \deck
$discard-pile.add-event-listener \click -> open-list state.player-state.discard_pile, \discard (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 "./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
$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 (->>
const new-state = await get-state!
unless new-state.player-state-json == state.player-state-json
@ -230,6 +341,19 @@
window.state = state
), 1000
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
..add-event-listener \mousemove ->
mouse-x = it.client-x
@ -240,20 +364,45 @@
$view-card-container.class-list.add \right
..add-event-listener \keyup ->
$current-mouse-node = [...document.query-selector-all \:hover][* - 1]
switch it.key.to-lower-case!
key = it.key.to-lower-case!
key = "!#key" if it.alt-key
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 => fetch-post "./draw/#{pop-param!}"
| \c , \^d => fetch-post "./draw/#{pop-param!}"
| \e => fetch-post \./pass
| \0 \1 \2 \3 \4 \5 \6 \7 \8 \9 => push-param that
| \t =>
if $current-mouse-node?.has-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 =>
if $current-mouse-node?.has-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
fetch-post "./play/#{$current-mouse-node.get-attribute \data-hand-index}"
@ -272,9 +421,7 @@
| \shadow
fetch-post "./unbanish/#{$current-mouse-node.get-attribute \data-index}"
$current-mouse-node.parent-element.remove-child $current-mouse-node
if !$extra.child-element-count
$extra.class-list.add \hidden
$extra.remove-attribute \data-pile-type
clear-extra-on-empty$!
| \r =>
if $current-mouse-node?.has-attribute \data-play-id
fetch-post "./bounce/#{$current-mouse-node.get-attribute \data-play-id}"
@ -287,9 +434,7 @@
| \shadow
fetch-post "./remember/#{$current-mouse-node.get-attribute \data-index}"
$current-mouse-node.parent-element.remove-child $current-mouse-node
if !$extra.child-element-count
$extra.class-list.add \hidden
$extra.remove-attribute \data-pile-type
clear-extra-on-empty$!
| \d =>
if $current-mouse-node?.has-attribute \data-hand-index
fetch-post "./discard/#{$current-mouse-node.get-attribute \data-hand-index}"
@ -304,9 +449,7 @@
| \shadow
fetch-post "./unshadow/#{$current-mouse-node.get-attribute \data-index}"
$current-mouse-node.parent-element.remove-child $current-mouse-node
if !$extra.child-element-count
$extra.class-list.add \hidden
$extra.remove-attribute \data-pile-type
clear-extra-on-empty$!
| \s =>
if $current-mouse-node?.has-attribute \data-hand-index
fetch-post "./forget/#{$current-mouse-node.get-attribute \data-hand-index}"
@ -320,14 +463,12 @@
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
if !$extra.child-element-count
$extra.class-list.add \hidden
$extra.remove-attribute \data-pile-type
| \q =>
$extra.class-list.add \hidden
$extra.remove-attribute \data-pile-type
| \x =>
| \x , \+x =>
if it.shift-key || state.player-state.turn_player == state.player-state.you
fetch-post \./untap_all
[...$play.children]
@ -337,4 +478,18 @@
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
)!

View File

@ -35,7 +35,6 @@ main {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0.2%;
align-items: center;
img {
display: flex;
@ -49,7 +48,7 @@ main {
#extra {
transition: height 0.5s;
overflow: auto;
overflow: scroll;
background-color: #222d;
}
@ -57,6 +56,14 @@ main {
height: 0;
}
#extra-list {
height: 100%;
display: flex;
flex-direction: row;
gap: 0.2vw;
overflow: scroll;
}
aside {
position: absolute;
height: 100%;
@ -129,13 +136,15 @@ aside {
}
#play::before {
content: " ";
content: attr(data-border-text);
position: absolute;
top: 48%;
bottom: 48%;
left: 0;
right: 0;
background-color: inherit;
color: #fff;
text-align: center;
}
.my-turn #play {
@ -170,3 +179,33 @@ aside {
img {
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;
}