summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJomar Milan <jomarm@jomarm.com>2026-06-09 17:15:24 -0700
committerJomar Milan <jomarm@jomarm.com>2026-06-09 17:15:24 -0700
commitacf5e40d02a25a6e99ef23ef61aca8cd261de9d3 (patch)
treeeb7da2b27e8a47ac1005ded7a0d70f5579ab635b
parent27e1ef1ad8d7d834054b750708343378ce9e9ec5 (diff)
Add player hand display in browser
-rw-r--r--assets/session.css6
-rw-r--r--assets/session.js46
-rw-r--r--src/main.rs5
-rw-r--r--src/play.rs2
-rw-r--r--src/session.rs2
-rw-r--r--templates/session.html3
6 files changed, 61 insertions, 3 deletions
diff --git a/assets/session.css b/assets/session.css
new file mode 100644
index 0000000..03db59d
--- /dev/null
+++ b/assets/session.css
@@ -0,0 +1,6 @@
+#hand {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ flex-wrap: wrap;
+} \ No newline at end of file
diff --git a/assets/session.js b/assets/session.js
index 981cff1..3f17ad0 100644
--- a/assets/session.js
+++ b/assets/session.js
@@ -1,4 +1,5 @@
const colorSelect = document.getElementById('color-select');
+const hand = document.getElementById('hand');
const id = document.getElementById('session-script').dataset.id;
const websocket = new WebSocket(`/session/${id}/play`);
@@ -28,6 +29,49 @@ websocket.addEventListener('message', (event) => {
} else if (Object.hasOwn(message, 'Hand')) {
const payload = message.Hand;
colorSelect.disabled = false;
+
+ Array.from(hand.children).forEach((child) => {
+ child.remove();
+ })
+
+ payload.forEach((handObject) => {
+ if (Object.hasOwn(handObject, 'CustomDeck')) {
+ const customDeck = handObject.CustomDeck;
+
+ /*
+ const cardWidthRatio = 100 / customDeck.width;
+ const cardHeightRatio = 100 / customDeck.height;
+ const cardHorizontalOffset = cardWidthRatio * (customDeck.card_id % customDeck.width);
+ const cardVerticalOffset = cardHeightRatio * Math.floor(customDeck.card_id / customDeck.width);
+
+ const card = document.createElement('img');
+ card.src = customDeck.face;
+ card.style.clipPath = `rect(${cardVerticalOffset}% ${cardWidthRatio}% ${cardVerticalOffset + cardHeightRatio}% ${cardHorizontalOffset}%`;
+ card.style.objectFit = 'none';
+ */
+
+ // 214x334
+ const cardBounds = document.createElement('div');
+ cardBounds.style.overflow = 'hidden';
+ const cardFace = document.createElement('img');
+ cardFace.src = customDeck.face;
+ cardFace.addEventListener('load', () => {
+ const width = cardFace.naturalWidth / customDeck.width;
+ const height = cardFace.naturalHeight / customDeck.height;
+ const offsetX = width * (customDeck.card_id % customDeck.width);
+ const offsetY = height * Math.floor(customDeck.card_id / customDeck.width);
+
+ cardBounds.style.width = `${width}px`;
+ cardBounds.style.height = `${height}px`;
+ cardBounds.style.scale = '0.8';
+
+ cardFace.style.marginLeft = `-${offsetX}px`;
+ cardFace.style.marginTop = `-${offsetY}px`;
+ });
+ cardBounds.appendChild(cardFace);
+ hand.appendChild(cardBounds);
+ }
+ });
}
});
@@ -37,7 +81,7 @@ websocket.addEventListener('close', () => {
colorSelect.addEventListener('change', () => {
if (colorSelect.value === '') return;
-
+
colorSelect.disabled = true;
websocket.send(JSON.stringify({
diff --git a/src/main.rs b/src/main.rs
index d9852e3..fc0a7bb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -65,6 +65,7 @@ async fn serve_static(Path(path): Path<String>) -> Response {
Some(content) => {
let mime = match path.split('.').next_back() {
Some("js") => "application/javascript",
+ Some("css") => "text/css",
_ => "application/octet-stream",
};
([(header::CONTENT_TYPE, mime)], content.data).into_response()
@@ -73,7 +74,9 @@ async fn serve_static(Path(path): Path<String>) -> Response {
}
}
-async fn find_session(Query(query): Query<HashMap<String, String>>) -> axum::response::Result<Redirect> {
+async fn find_session(
+ Query(query): Query<HashMap<String, String>>,
+) -> axum::response::Result<Redirect> {
let id = query.get("id").ok_or(StatusCode::NOT_FOUND)?;
Ok(Redirect::to(format!("/session/{}", id).as_str()))
}
diff --git a/src/play.rs b/src/play.rs
index 574e8b8..e08a895 100644
--- a/src/play.rs
+++ b/src/play.rs
@@ -34,7 +34,7 @@ pub async fn handle_play(mut socket: WebSocket, app_state: Arc<AppState>) {
// Blocked so that the guard is dropped after cloning the color names,
// preventing a potential deadlock when using .await after sending something
// through the socket, which would be possible if using tokio::sync::Mutex.
- // Of course, the sessions HashMap is wrapped instd::sync types instead, which
+ // Of course, the sessions HashMap is wrapped in std::sync types instead, which
// does not enable locking the mutex through .await anyway.
let colors: Vec<String> = {
let sessions = app_state.sessions.read().unwrap();
diff --git a/src/session.rs b/src/session.rs
index 6aeee9f..e472adf 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -36,6 +36,8 @@ pub struct CustomDeck {
/// Whether the card back should be used as the hidden image (instead of the last slot of the
/// `face` image).
back_is_hidden: bool,
+ /// ID of the custom card within the deck.
+ card_id: f64,
}
impl Session {
diff --git a/templates/session.html b/templates/session.html
index 122c80b..d0b6ddd 100644
--- a/templates/session.html
+++ b/templates/session.html
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<title>{{session.steam_name}}'s game</title>
<script id="session-script" data-id="{{id}}" src="/dist/session.js" defer></script>
+ <link href="/dist/session.css" rel="stylesheet" type="text/css">
</head>
<body>
<h1>{{session.steam_name}}'s game</h1>
@@ -15,5 +16,7 @@
<hr>
</select>
</form>
+<h2>Hand</h2>
+<div id="hand"></div>
</body>
</html> \ No newline at end of file