From 4310d3569ceb4bbfb10752c5c0ec712ca21499fb Mon Sep 17 00:00:00 2001 From: Seoxi Ryouko <3-Seoxi@users.noreply.git.starbit.dev> Date: Thu, 27 Feb 2025 08:35:14 -0600 Subject: [PATCH] =?UTF-8?q?Added=20piles,=20cleaned=20up=20dragging=20posi?= =?UTF-8?q?tioning,=20various=20improvements=20=F0=9F=93=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/game.rs | 61 +++++++++++++++- src/main.rs | 120 ++++++++++++++++++++++++++++++-- web/dynamic/index.html | 15 ++++ web/src/script.ls | 153 +++++++++++++++++++++++++++++++++-------- web/src/style.scss | 76 +++++++++++++++++--- 5 files changed, 379 insertions(+), 46 deletions(-) diff --git a/src/game.rs b/src/game.rs index f914dc3..bebcc48 100644 --- a/src/game.rs +++ b/src/game.rs @@ -52,6 +52,9 @@ enum Event { PlayFromHand(Player), Bounce(Player), Tap(Uuid), + Discard(Player), + Kill(Uuid, bool), + Unkill(Uuid, bool), } #[derive(Serialize, Clone, Debug)] @@ -82,6 +85,7 @@ pub struct OnePlayerGameState { turn_player: Player, opponent_cards_in_hand: usize, you: Player, + deck_size: usize, } impl GameState { @@ -101,6 +105,7 @@ impl GameState { _ => self.hands[0].cards.len(), }, you: player, + deck_size: self.deck.len(), } } } @@ -237,7 +242,7 @@ pub fn play_from_hand(game_state: &mut GameState, hand_index: usize, player: Pla play_uuid, InPlay { id: card, - position_x: 5, + position_x: 50, position_y: 50, owner: player, tapped: false, @@ -275,3 +280,57 @@ pub fn move_played_card(game_state: &mut GameState, play_id: Uuid, position_x: u played_card.position_y = position_y; } } + +pub fn discard(game_state: &mut GameState, hand_index: usize, player: Player, shadow: 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::Discard(player.clone()), + }); + let card = player_hand.cards.remove(hand_index); + if shadow { + game_state.shadow_realm.push(card); + } else { + game_state.discard_pile.push(card); + } +} + +pub fn kill(game_state: &mut GameState, play_id: Uuid, shadow: bool) { + let played_card = game_state.play.remove(&play_id).unwrap(); + game_state.events.push(CountedEvent { + id: game_state.events.len(), + event: Event::Kill(played_card.id.clone(), shadow), + }); + if shadow { + game_state.shadow_realm.push(played_card.id); + } else { + game_state.discard_pile.push(played_card.id); + } +} + +pub fn unkill(game_state: &mut GameState, player: Player, pile_index: usize, shadow: bool) { + let card = if shadow { + game_state.shadow_realm.remove(pile_index) + } else { + game_state.discard_pile.remove(pile_index) + }; + let play_uuid = Uuid::new_v4(); + game_state.events.push(CountedEvent { + id: game_state.events.len(), + event: Event::Unkill(card, shadow), + }); + game_state.play.insert( + play_uuid, + InPlay { + id: card, + position_x: 50, + position_y: 50, + owner: player, + tapped: false, + play_id: play_uuid, + }, + ); +} diff --git a/src/main.rs b/src/main.rs index 8f42797..b52eea0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod game; use game::GameState; use game::Player; use rocket::fs::{relative, FileServer}; +use rocket::response::content; use rocket::response::status::BadRequest; use rocket::State; use std::collections::HashMap; @@ -11,11 +12,11 @@ use std::sync::{Arc, Mutex}; use uuid::{uuid, Uuid}; #[get("/")] -fn index(player_uuids: &State) -> String { - format!( - "localhost:8000/{}/\nlocalhost:8000/{}/", - player_uuids.a, player_uuids.b - ) +fn index(player_uuids: &State) -> content::RawHtml { + content::RawHtml(format!( + "localhost:8000/{}
localhost:8000/{}", + player_uuids.a, player_uuids.a, player_uuids.b, player_uuids.b + )) } #[get("/get_events")] @@ -124,7 +125,108 @@ fn play( 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("//unkill/")] +fn unkill( + 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::unkill(&mut game_state, player.clone(), index, false); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//unbanish/")] +fn unbanish( + 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::unkill(&mut game_state, player.clone(), index, true); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//discard/")] +fn discard( + 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::discard(&mut game_state, index, player.clone(), false); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//forget/")] +fn forget( + 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::discard(&mut game_state, index, player.clone(), true); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//kill/")] +fn kill( + 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::kill(&mut game_state, play_id, false); + Ok(format!("{}", game::get_events(&game_state))) +} + +#[get("//banish/")] +fn banish( + 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::kill(&mut game_state, play_id, true); Ok(format!("{}", game::get_events(&game_state))) } @@ -282,6 +384,12 @@ fn rocket() -> _ { bounce, tap, move_card, + discard, + kill, + forget, + banish, + unkill, + unbanish ], ) .manage(ArcMutexGameState { diff --git a/web/dynamic/index.html b/web/dynamic/index.html index a4dc402..faa9a53 100644 --- a/web/dynamic/index.html +++ b/web/dynamic/index.html @@ -13,6 +13,7 @@
+ + diff --git a/web/src/script.ls b/web/src/script.ls index 839c68b..038d7a9 100644 --- a/web/src/script.ls +++ b/web/src/script.ls @@ -11,39 +11,50 @@ 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-count = document.query-selector '#deck-count' + const $extra = document.query-selector '#extra' 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 + $param.inner-text = param && "\xa0#param\xa0" ), 100 const push-param = -> param += it - $param.inner-text = param + $param.inner-text = "\xa0#param\xa0" clear-param-timer := 20 const pop-full-param = -> ret = param param := "" - $param.inner-text = param + $param.inner-text = "\xa0#param\xa0" if ret == "" 1 else +ret [push-param, pop-full-param] )! + play-id-list = "" const clamp = (n, v, x) -> Math.min (Math.max v, n), x const unwrap_uuid = (.replace /^\{|\}$/g '') 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-log \./cards.json .then (.json!) - const make-card = -> - img = document.create-element \img - img.src = it - img - const make-blank-card = -> make-card \./card_back.png + const $make-card = -> + $img = document.create-element \img + $img.src = it + $img + const $make-blank-card = -> $make-card \./card_back.png const gen-opponent-cards$ = -> diff = it - $opponent-hand.child-element-count switch @@ -51,7 +62,7 @@ while diff++ $opponent-hand.remove-child $opponent-hand.first-child | diff > 0 - $opponent-hand.append ...(gen-array diff .map make-blank-card) + $opponent-hand.append ...(gen-array diff .map $make-blank-card) | _ void const get-state = ->> @@ -60,7 +71,6 @@ events-json = await fetch \/get_events .then (.text!) events = JSON.parse events-json play-map = new Map player-state.play.map -> [it.id, it] - play-id-list = [...player-state.play.map (.play_id)].sort!join \| return { player-state-json, player-state, events-json, events, play-map } const show-card$ = (e) -> @@ -72,17 +82,39 @@ e.target.add-event-listener \mouseleave hide-card$ state = await get-state! const apply-state$ = -> - { player-state, play-id-list } = it + { player-state } = it 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 else document.body.class-list.add \my-turn if player-state.you == player-state.turn_player + $deck-count.inner-text = player-state.deck_size + if player-state.discard_pile.length + top-id = player-state.discard_pile[* - 1] + $discard-pile-img.src = cards[top-id].jpg + $discard-pile-img.set-attribute \data-png cards[top-id].png + else + $discard-pile-img.src = "" + $discard-pile-count.inner-text = player-state.discard_pile.length + if player-state.shadow_realm.length + top-id = player-state.shadow_realm[* - 1] + $shadow-realm-img.src = cards[top-id].jpg + $shadow-realm-img.set-attribute \data-png cards[top-id].png + else + $shadow-realm-img.src = "" + $shadow-realm-count.inner-text = player-state.shadow_realm.length + if $extra.has-attribute \data-pile-type + const list = switch $extra.get-attribute \data-pile-type + | \discard => player-state.discard_pile + | \shadow => player-state.shadow_realm + | \deck => gen-array-of state.player-state.deck_size, null + | _ => (console.log that) || [] + open-list list, $extra.get-attribute \data-pile-type if player-state.hand.length != $own-hand.children.length $own-hand.innerHTML = '' player-state.hand.for-each (card-id, index) -> card-data = cards[card-id] - card = make-card card-data.jpg + card = $make-card card-data.jpg card ..set-attribute \data-hand-index index ..set-attribute \data-text card-data.txt @@ -98,11 +130,10 @@ relative-y = it.position_y else relative-y = 100 - it.position_y - img = make-card card-data.jpg + img = $make-card card-data.jpg img ..class-list.add \in-play-card ..style - #..transform = 'translate(-50%, -50%)' ..left = it.position_x + \% ..top = relative-y + \% ..set-attribute \data-png card-data.png @@ -111,6 +142,7 @@ ..set-attribute \data-play-y relative-y ..set-attribute \draggable \true ..set-attribute \data-owner it.owner + ..set-attribute \data-owned it.owner == player-state.you ..set-attribute \data-tapped it.tapped ..add-event-listener \mouseenter show-card$ ..add-event-listener \dragstart -> @@ -139,11 +171,49 @@ else absolute-y = 100 - y fetch-log "./move/#{it.target.get-attribute \data-play-id}/#x/#absolute-y" - #it.target.style.left = new-x + \px - #it.target.style.top = new-y + \px $play.append-child img + play-id-list := next-play-id-list + else + player-state.play.for-each -> + $element = document.query-selector "[data-play-id=\"#{it.play_id}\"]" + if it.owner == player-state.you + relative-y = it.position_y + else + relative-y = 100 - it.position_y + $element.set-attribute \data-tapped it.tapped + $element.style + ..left = it.position_x + \% + ..top = relative-y + \% + $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 $card-list = list.map (card-id, index) -> + if card-id + ret = $make-card cards[card-id].jpg + ret.set-attribute \data-png cards[card-id].png + 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.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 + $shadow-realm-img.add-event-listener \click -> open-list state.player-state.shadow_realm, \shadow + $deck.add-event-listener \click -> open-list (gen-array-of state.player-state.deck_size, null), \deck + set-interval (->> const new-state = await get-state! unless new-state.player-state-json == state.player-state-json @@ -161,24 +231,51 @@ else if mouse-x < window.inner-width / 3 $view-card-container.class-list.add \right ..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 | \c => fetch-log "./draw/#{pop-param!}" | \e => fetch-log "./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-log "./fade/#{current-mouse-node.get-attribute \data-hand-index}" + if $current-mouse-node?.has-attribute \data-hand-index + fetch-log "./fade/#{$current-mouse-node.get-attribute \data-hand-index}" | \y => - if current-mouse-node?.has-attribute \data-hand-index - fetch-log "./fade_bottom/#{current-mouse-node.get-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}" | ' ' => - if current-mouse-node?.has-attribute \data-hand-index - fetch-log "./play/#{current-mouse-node.get-attribute \data-hand-index}" - else if current-mouse-node?.has-attribute \data-play-id - fetch-log "./tap/#{current-mouse-node.get-attribute \data-play-id}" + if $current-mouse-node?.has-attribute \data-hand-index + fetch-log "./play/#{$current-mouse-node.get-attribute \data-hand-index}" + else if $current-mouse-node?.has-attribute \data-play-id + if \true == $current-mouse-node.get-attribute \data-tapped + $current-mouse-node.set-attribute \data-tapped \false + else + $current-mouse-node.set-attribute \data-tapped \true + fetch-log "./tap/#{$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-log "./unkill/#{$current-mouse-node.get-attribute \data-index}" + | \shadow + fetch-log "./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 | \r => - if current-mouse-node?.has-attribute \data-play-id - fetch-log "./bounce/#{current-mouse-node.get-attribute \data-play-id}" + if $current-mouse-node?.has-attribute \data-play-id + fetch-log "./bounce/#{$current-mouse-node.get-attribute \data-play-id}" + | \d => + if $current-mouse-node?.has-attribute \data-hand-index + fetch-log "./discard/#{$current-mouse-node.get-attribute \data-hand-index}" + else if $current-mouse-node?.has-attribute \data-play-id + fetch-log "./kill/#{$current-mouse-node.get-attribute \data-play-id}" + | \s => + if $current-mouse-node?.has-attribute \data-hand-index + fetch-log "./forget/#{$current-mouse-node.get-attribute \data-hand-index}" + else if $current-mouse-node?.has-attribute \data-play-id + fetch-log "./banish/#{$current-mouse-node.get-attribute \data-play-id}" + | \q => + $extra.class-list.add \hidden + $extra.remove-attribute \data-pile-type )! diff --git a/web/src/style.scss b/web/src/style.scss index a4e03fb..400e4ed 100644 --- a/web/src/style.scss +++ b/web/src/style.scss @@ -1,6 +1,9 @@ +@import url('/userstyle.css'); + $view-container-side-offset: 5%; $param-container-side-offset: 5%; $img-hand-height: 95%; +/*$img-pile-width: 95%;*/ $hand-height: 14%; *, *::before, *::after { @@ -10,6 +13,7 @@ $hand-height: 14%; html, body, main { margin: 0; padding: 0; + background-color: #000; } body { @@ -23,12 +27,12 @@ main { display: flex; flex-direction: column; justify-content: space-between; + position: relative; } -.hand { +.hand, #extra { height: $hand-height; width: 100%; - background-color: #0002; display: flex; flex-direction: row; justify-content: center; @@ -40,10 +44,27 @@ main { } } +.hand { + background-color: #fff2; +} + +#extra { + transition: height 0.5s; + overflow: hidden; + background-color: #222d; +} + +#extra.hidden { + height: 0; +} + aside { position: absolute; height: 100%; top: 0; +} + +#view-card-container { left: $view-container-side-offset; display: flex; flex-direction: column; @@ -55,7 +76,7 @@ aside { } } -.right { +#view-card-container.right { left: initial; right: $view-container-side-offset; } @@ -73,39 +94,72 @@ aside { #param { font-size: 2rem; + background-color: #fffb; + border-radius: 0.5rem; + padding: 0.5rem 0; } .right #param { text-align: right; } -#border { +#piles { + display: flex; + flex-direction: column; + justify-content: center; +} + +.pile { + position: relative; + height: $hand-height; + display: flex; + justify-content: center; + align-items: center; + img { + height: $img-hand-height; + } + span { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background-color: #fffb; + border-radius: 0.25rem; + padding: 0.1rem; + } +} + +#play::before { + content: " "; position: absolute; top: 48%; bottom: 48%; left: 0; right: 0; - background-color: #f002; + background-color: inherit; } -.my-turn #border { +.my-turn #play { background-color: #0ff2; } #play { + background-color: #f002; flex-grow: 1; -} - -#play { position: relative; } .in-play-card { position: absolute; - transition: all 0.3s; + transition: transform 0.3s, top 0.3s, left 0.3s; + transform: translate(-50%, -50%) rotate(0deg); height: calc(($img-hand-height / 100%) * ($hand-height / 100%) * 100vh); } [data-tapped = true] { - transform: rotate(90deg); + transform: translate(-50%, -50%) rotate(90deg); +} + +img { + border-radius: 10%; }