diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-10-14 16:54:58 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-10-15 00:35:59 +0200 |
commit | 47a375277e0f5ffcd92ee491371c1fe616707b2f (patch) | |
tree | bc1ad89ae2d9c34f7864ba30e6165557fbc3e01a | |
parent | d445a6a168cd3948f682df9771a8b8e24418a10c (diff) | |
download | pax-pamir-47a375277e0f5ffcd92ee491371c1fe616707b2f.tar.gz |
Add animations to cards and pieces.
Show hand cards tucked under player boards when minimized.
-rw-r--r-- | about.html | 8 | ||||
-rw-r--r-- | play.css | 39 | ||||
-rw-r--r-- | play.html | 10 | ||||
-rw-r--r-- | play.js | 163 |
4 files changed, 199 insertions, 21 deletions
@@ -4,13 +4,13 @@ authority has collapsed. Rivals both old and new have emerged from the shadows. It’s up to the players to see if a fledgling Afghan state might come into being. -<br clear=left> - <p> -Designer: Cole Wehrle +Designer: Cole Wehrle. <br> Publisher: -<a href="https://wehrlegig.com/collections/frontpage/products/pax-pamir-second-edition">Wehrlegig Games llc</a> +<a href="https://wehrlegig.com/collections/frontpage/products/pax-pamir-second-edition">Wehrlegig Games llc</a>. +<br> +Programming: Tor Andersson. <p> This game is licensed under Creative Commons <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">BY-NC-SA 4.0</a>. @@ -189,6 +189,7 @@ body.shift #tooltip.focus { display: block; } background-repeat: no-repeat; border: 1px solid #4d452b; box-shadow: 0 2px 3px rgba(0,0,0,0.5); + transition: filter 500ms ease; } #tooltip.card { @@ -203,6 +204,7 @@ body.shift #tooltip.focus { display: block; } .card .spyrow { position: absolute; + pointer-events: none; display: flex; flex-wrap: wrap; width: 168px; @@ -278,9 +280,9 @@ body.shift #tooltip.focus { display: block; } /* PIECES */ -#board div { - transition: top 500ms, left 500ms; -} +.card { z-index: 10 } +.block { z-index: 20 } +.cylinder { z-index: 20 } .coin { position: absolute; @@ -306,6 +308,7 @@ body.shift #tooltip.focus { display: block; } background-size: contain; background-repeat: no-repeat; filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5)); + pointer-events: auto; } #board .cylinder { @@ -586,12 +589,29 @@ body.shift #tooltip.focus { display: block; } padding: 15px; margin: 15px auto 0 auto; gap: 15px; - min-height: 260px; + min-height: calc(260px + 30px); max-width: min(calc(100% - 20px), 1260px); } -.hand.hide { - display: none; +.hand .card { + z-index: auto; +} + +.hand.minimize + .player_court { + margin-top: calc(-15px - 195px); +} + +/* hide backs when minimized hands */ +/* +body.open .hand.minimize .card { + pointer-events: none; + background-image: url(cards/card_back_116.jpg); + filter: none; +} +*/ + +body.open .hand.minimize .card { + filter: brightness(60%); } .player_area { @@ -603,6 +623,7 @@ body.shift #tooltip.focus { display: block; } } .player_court { + position: relative; display: flex; padding: 10px 15px; min-height: 283px; @@ -613,6 +634,7 @@ body.shift #tooltip.focus { display: block; } background-repeat: no-repeat; background-size: 100%; background-position: center bottom; + transition: margin-top 500ms ease; } #player_court_0 { background-color: #b7b2b0; background-image: url(backgrounds/mountains_gray.jpg) } @@ -900,3 +922,8 @@ body.shift #tooltip.focus { display: block; } border-radius: 13px; } } + +@media (max-width: 1200px) { + .hand.minimize + .player_court { margin-top: -290px; } + body.open .hand.minimize + .player_court { margin-top: -290px; } +} @@ -346,7 +346,7 @@ <div id="player_area_list"> <div class="hide player_area" id="player_area_0"> -<div class="hide hand" id="player_hand_0"></div> +<div class="minimize hand" id="player_hand_0"></div> <div class="player_court" id="player_court_0"> <div class="player_pool" id="player_pool_0"> <div class="player_dial p0" id="player_dial_0"> @@ -362,7 +362,7 @@ </div> <div class="hide player_area" id="player_area_1"> -<div class="hide hand" id="player_hand_1"></div> +<div class="minimize hand" id="player_hand_1"></div> <div class="player_court" id="player_court_1"> <div class="player_pool" id="player_pool_1"> <div class="player_dial p1" id="player_dial_1"> @@ -378,7 +378,7 @@ </div> <div class="hide player_area" id="player_area_2"> -<div class="hide hand" id="player_hand_2"></div> +<div class="minimize hand" id="player_hand_2"></div> <div class="player_court" id="player_court_2"> <div class="player_pool" id="player_pool_2"> <div class="player_dial p2" id="player_dial_2"> @@ -394,7 +394,7 @@ </div> <div class="hide player_area" id="player_area_3"> -<div class="hide hand" id="player_hand_3"></div> +<div class="minimize hand" id="player_hand_3"></div> <div class="player_court" id="player_court_3"> <div class="player_pool" id="player_pool_3"> <div class="player_dial p3" id="player_dial_3"> @@ -410,7 +410,7 @@ </div> <div class="hide player_area" id="player_area_4"> -<div class="hide hand" id="player_hand_4"></div> +<div class="minimize hand" id="player_hand_4"></div> <div class="player_court" id="player_court_4"> <div class="player_pool" id="player_pool_4"> <div class="player_dial p4" id="player_dial_4"> @@ -1,5 +1,88 @@ "use strict" +function remember_position(e) { + if (e.parentElement) { + let prect = e.parentElement.getBoundingClientRect() + let rect = e.getBoundingClientRect() + e.my_visible = 1 + e.my_parent = e.parentElement + e.my_px = prect.x + e.my_py = prect.y + e.my_x = rect.x + e.my_y = rect.y + } else { + e.my_visible = 0 + e.my_parent = e.parentElement + e.my_x = 0 + e.my_y = 0 + e.my_z = 0 + } +} + +function animate_position(e) { + if (e.parentElement && e.my_visible) { + let prect = e.parentElement.getBoundingClientRect() + let rect = e.getBoundingClientRect() + let dx, dy + if (e.parentElement === e.my_parent) { + // animate cylinders within same card... + dx = (e.my_x - e.my_px) - (rect.x - prect.x) + dy = (e.my_y - e.my_py) - (rect.y - prect.y) + } else { + dx = e.my_x - rect.x + dy = e.my_y - rect.y + } + + // fade in + if (!e.my_parent) { + e.animate( + [ + { opacity: 0 }, + { opacity: 1 } + ], + { duration: 350, easing: "ease" } + ) + } + + if (dx !== 0 || dy !== 0) { + let dist = Math.sqrt((dx * dx) + (dy * dy)) + let time = Math.max(350, Math.min(1000, dist / 2)) + e.animate( + [ + { transform: `translate(${dx}px, ${dy}px)`, }, + { transform: "translate(0, 0)", }, + ], + { duration: time, easing: "ease" } + ) + } + } +} + +function deep_copy(original) { + if (Array.isArray(original)) { + let n = original.length + let copy = new Array(n) + for (let i = 0; i < n; ++i) { + let v = original[i] + if (typeof v === "object" && v !== null) + copy[i] = deep_copy(v) + else + copy[i] = v + } + return copy + } else { + let copy = {} + for (let i in original) { + let v = original[i] + if (typeof v === "object" && v !== null) + copy[i] = deep_copy(v) + else + copy[i] = v + } + return copy + } +} + // CONSTANTS const player_names = [ "Gray", "Blue", "Tan", "Red", "Black", "None" ] @@ -269,7 +352,7 @@ function toggle_open_hands() { open_toggle = !open_toggle for (let p = 0; p < view.players.length; ++p) if (p !== player_index[player]) - ui.player[p].hand.classList.toggle("hide", open_toggle) + ui.player[p].hand.classList.toggle("minimize", open_toggle) } function on_blur() { @@ -313,7 +396,7 @@ function on_click_cylinder(evt) { } function toggle_hand(p) { - ui.player[p].hand.classList.toggle("hide") + ui.player[p].hand.classList.toggle("minimize") } // CARD MENU @@ -494,6 +577,32 @@ function layout_border(r, xc, yc, line) { // UPDATE UI let once = true +let old_view = null + +function is_card_visible(game, c) { + for (let row = 0; row < 2; ++row) + for (let col = 0; col < 6; ++col) + if (game.market_cards[row][col] === c) + return true + for (let p = 0; p < game.players.length; ++p) { + let pp = game.players[p] + if (pp.court.includes(c)) + return true + if (pp.hand.includes(c)) + return true + for (let evt in pp.events) + if (event_cards[evt] === c) + return true + } + return false +} + +function is_card_on_market(game, row, c) { + for (let col = 0; col < 6; ++col) + if (game.market_cards[row][col] === c) + return true + return false +} function on_update() { if (once) { @@ -501,6 +610,36 @@ function on_update() { once = false } + ui.cards.forEach(remember_position) + ui.pieces.forEach(remember_position) + + if (old_view) { + let anchor = ui.main.getBoundingClientRect() + let anchor_a = ui.market_a.getBoundingClientRect() + let anchor_b = ui.market_b.getBoundingClientRect() + for (let i = 1; i < cards.length; ++i) { + // animate from card deck onto market + let anchor_x = Math.min(anchor_a.right, anchor.right - 190) + if (!is_card_visible(old_view, i)) { + if (is_card_on_market(view, 0, i)) { + ui.cards[i].my_visible = 1 + ui.cards[i].my_x = anchor_x + ui.cards[i].my_y = anchor_a.bottom - 260 + } + else if (is_card_on_market(view, 1, i)) { + ui.cards[i].my_visible = 1 + ui.cards[i].my_x = anchor_x + ui.cards[i].my_y = anchor_b.bottom - 260 + } + } + } + } else { + for (let e of ui.pieces) + e.my_visible = 0 + } + + old_view = deep_copy(view) + function update_event_cards(node, events) { for (let evt in events) node.appendChild(ui.cards[event_cards[evt]]) @@ -549,6 +688,8 @@ function on_update() { action_button("cancel", "Cancel") action_button("undo", "Undo") + ui.body.classList.toggle("open", !!view.open) + ui.favored1.className = view.favored ui.favored2.className = view.favored + " icon" @@ -572,6 +713,10 @@ function on_update() { ui.market_coin[row][col].textContent = "" ui.market_coin[row][col].className = "coin hide" } + if (ce) + ce.appendChild(ui.market_coin[row][col]) + else + ui.market_coin[row][col].remove() } } @@ -621,7 +766,7 @@ function on_update() { while (me.firstChild) me.removeChild(me.firstChild) if (p === player_index[player]) - me.classList.remove("hide") + me.classList.remove("minimize") for (let i = 0; i < pp.hand.length; ++i) { let ce = ui.cards[pp.hand[i]] if (p !== player_index[player] && !view.open) @@ -668,7 +813,7 @@ function on_update() { ui.player[p].score.style.left = (VP_OFFSET[p][0] + VP_TRACK[view.players[p].vp][0]) + "px" ui.player[p].score.style.top = (VP_OFFSET[p][1] + VP_TRACK[view.players[p].vp][1]) + "px" - for (let i = 0; i < 10; ++i) { + for (let i = 9; i >= 0; --i) { let x = 36 + p * 10 + i let s = view.pieces[x] if (s === 0 || s === Safe_House) @@ -736,6 +881,9 @@ function on_update() { e.classList.toggle("action", is_card_action(action, c)) } } + + ui.pieces.forEach(animate_position) + ui.cards.forEach(animate_position) } // BUILD UI @@ -824,8 +972,7 @@ function build_ui() { ui.player[p].dial.addEventListener("click", () => send_action('player_' + p)) - ui.player[p].hand_size.addEventListener("click", - () => toggle_hand(p)) + ui.player[p].hand_size.addEventListener("click", () => toggle_hand(p)) for (let i = 0; i < 10; ++i) { let x = 36 + p * 10 + i @@ -850,10 +997,14 @@ function build_ui() { document.querySelector(`#board .rule.Punjab`), ] + ui.body = document.querySelector("body") + ui.main = document.querySelector("main") ui.prompt = document.getElementById("prompt") ui.deck_info = document.getElementById("deck_info") ui.board = document.getElementById("board") ui.market = document.getElementById("market") + ui.market_a = document.getElementById("market_a") + ui.market_b = document.getElementById("market_b") ui.status = document.getElementById("status") ui.tooltip = document.getElementById("tooltip") ui.favored1 = document.getElementById("favored_suit_marker") |