From 4fe71136a289692bc12b91b82c601d92a3c40000 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Wed, 1 Dec 2021 22:50:42 +0100 Subject: [PATCH] Show cards on client side --- Public/api.js | 6 ++ Public/elements.js | 101 ++++++++++++++++---- Public/game.js | 90 ++++++++++++++---- Public/schafkopf.html | 41 +++++++- Public/style.css | 211 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 408 insertions(+), 41 deletions(-) diff --git a/Public/api.js b/Public/api.js index a477897..fc18305 100644 --- a/Public/api.js +++ b/Public/api.js @@ -60,6 +60,12 @@ async function performLeaveTableRequest(token) { .then(function(value) {}) } +async function performDealCardsRequest(token) { + return fetch("/deal", { method: 'POST', body: token }) + .then(convertServerResponse) + .then(function(value) {}) +} + function convertServerResponse(response) { switch (response.status) { case 200: // Success diff --git a/Public/elements.js b/Public/elements.js index 508cfb8..b6ae698 100644 --- a/Public/elements.js +++ b/Public/elements.js @@ -2,38 +2,65 @@ * This file acts as an abstraction layer between HTML and JS. */ -function hideLoginWindow() { - document.getElementById("signup-window").style.display = "none" +var playerName = "" + +function setDisplayStyle(id, style) { + document.getElementById(id).style.display = style } -function showLoginWindow() { - document.getElementById("signup-window").style.display = "table" -} - -function hideTableListElements() { - document.getElementById("table-list-bar").style.display = "none" - document.getElementById("table-list").style.display = "none" +function showLoginElements() { + setDisplayStyle("login-window", "table") + setDisplayStyle("top-bar", "none") + setDisplayStyle("table-list-bar", "none") + setDisplayStyle("table-list", "none") + setDisplayStyle("game-bar", "none") + setDisplayStyle("table-players", "none") + setDisplayStyle("player-cards", "none") } function showTableListElements() { - document.getElementById("table-list-bar").style.display = "grid" - document.getElementById("table-list").style.display = "inherit" + setDisplayStyle("login-window", "none") + setDisplayStyle("top-bar", "inherit") + setDisplayStyle("table-list-bar", "grid") + setDisplayStyle("table-list", "inherit") + setDisplayStyle("game-bar", "none") + setDisplayStyle("table-players", "none") + setDisplayStyle("player-cards", "none") } function showGameElements() { - document.getElementById("game-bar").style.display = "grid" + setDisplayStyle("login-window", "none") + setDisplayStyle("top-bar", "inherit") + setDisplayStyle("table-list-bar", "none") + setDisplayStyle("table-list", "none") + setDisplayStyle("game-bar", "grid") + setDisplayStyle("table-players", "grid") + setDisplayStyle("player-cards", "grid") } -function hideGameElements() { - document.getElementById("game-bar").style.display = "none" +function showConnectedState() { + const label = document.getElementById("table-connected-label") + label.innerHTML = "Connected" + label.style.color = "var(--text-color)" +} + +function showDisconnectedState() { + const label = document.getElementById("table-connected-label") + label.innerHTML = "Disconnected" + label.style.color = "var(--alert-color)" +} + +function showDealButton() { + setDisplayStyle("deal-button", "inherit") +} + +function hideDealButton() { + setDisplayStyle("deal-button", "none") } function setPlayerName(name) { document.getElementById("player-name").innerHTML = name -} - -function getPlayerName() { - return document.getElementById("player-name").innerHTML + playerName = name } function getLoginName() { @@ -83,3 +110,41 @@ function getTableVisibility() { function setTableListContent(content) { document.getElementById("table-list").innerHTML = content } + +function setTablePlayerInfo(nr, name, connected, active) { + const infoElement = "table-player-name" + nr.toString() + const connectionElement = "table-player-state" + nr + const nameElement = document.getElementById(infoElement) + if (name == "") { + nameElement.innerHTML = "Empty" + nameElement.style.color = "var(--secondary-text-color)" + setDisplayStyle(connectionElement, "none") + return + } + nameElement.innerHTML = name + nameElement.style.color = active ? "var(--button-color)" : "var(--text-color)" + if (connected) { + setDisplayStyle(connectionElement, "none") + } else { + setDisplayStyle(connectionElement, "inherit") + } +} + +function setTableCard(index, card) { + const id = "table-player-card" + index.toString() + setCard(id, card) +} + +function setHandCard(index, card, playable) { + const id = "player-card" + index.toString() + setCard(id, card) + document.getElementById(id).disabled = true +} + +function setCard(id, card) { + if (card == "") { + document.getElementById(id).style.backgroundColor = "var(--element-background)" + } else { + document.getElementById(id).style.backgroundImage = "url('/cards/" + card + ".jpeg')" + } +} \ No newline at end of file diff --git a/Public/game.js b/Public/game.js index 7a82c3c..bf408f1 100644 --- a/Public/game.js +++ b/Public/game.js @@ -10,9 +10,12 @@ function closeSocketIfNeeded() { } } +function didLeaveTable() { + tableId = "" +} + function didCloseSocket() { socket = null - tableId = "" } function setTableId(table) { @@ -21,25 +24,20 @@ function setTableId(table) { function showBlankLoginScreen(text) { closeSocketIfNeeded() + didLeaveTable() clearLoginPassword() clearLoginName() deleteSessionToken() - showLoginWindow() + showLoginElements() setLoginError(text) } -function showTableList() { - showTableListElements() - hideLoginWindow() -} - function showGame(tableId) { setTableId(tableId) const token = getSessionToken() if (token) { - openSocket(token) - hideTableListElements() showGameElements() + openSocket(token) // TODO: Show interface console.log("Show table " + tableId) } else { @@ -62,7 +60,7 @@ function registerUser() { .then(function(token) { setSessionToken(token) setPlayerName(username) - showTableList() + showTableListElements() loadCurrentTable(token) }).catch(function(error) { setLoginError(error.message) @@ -79,6 +77,7 @@ function deletePlayerAccount() { console.log("Player deleted") } else { closeSocketIfNeeded() + didLeaveTable() deleteSessionToken() alert(error) console.log(error) @@ -101,7 +100,7 @@ function loginUser() { .then(function(token) { setSessionToken(token) setPlayerName(username) - showTableList() + showTableListElements() loadCurrentTable(token) }).catch(function(error) { setLoginError(error.message) @@ -129,7 +128,7 @@ function loadExistingSession() { resumeSessionRequest(token) .then(function(name) { setPlayerName(name) - showTableList() + showTableListElements() loadCurrentTable(token) }).catch(function(error) { showBlankLoginScreen(error.message) @@ -143,9 +142,11 @@ function loadCurrentTable(token) { performGetCurrentTableRequest(token) .then(function(tableId) { if (tableId == "") { + didLeaveTable() refreshTables() return } + console.log("Loaded table " + tableId) showGame(tableId) }).catch(function(error) { showBlankLoginScreen(error.message) @@ -192,8 +193,10 @@ function leaveTable() { if (token) { performLeaveTableRequest(token) .then(function() { - showTableList() - hideGameElements() + showTableListElements() + closeSocketIfNeeded() + didLeaveTable() + refreshTables() }) .catch(function(error) { showBlankLoginScreen(error.message) @@ -208,22 +211,26 @@ function openSocket(token) { socket.onopen = function(e) { socket.send(token); + showConnectedState() }; socket.onmessage = function(event) { // TODO: Handle server data //event.data + handleServerUpdates(event.data) }; socket.onclose = function(event) { if (event.wasClean) { - didCloseSocket() + } else { // e.g. server process killed or network down // event.code is usually 1006 in this case // TODO: Retry several times to open socket, // stop after too many failed attempts } + didCloseSocket() + showDisconnectedState() }; socket.onerror = function(error) { @@ -231,6 +238,39 @@ function openSocket(token) { }; } +function handleServerUpdates(data) { + const info = JSON.parse(data.substring(1)) + if (data.startsWith("t")) { + handleTableInfoUpdate(info) + } else if (data.startsWith("c")) { + handleCardInfoUpdate(info) + } else { + console.log("Unhandled update: " + data) + } +} + +function handleTableInfoUpdate(info) { + for (let i = 0, len = info.players.length; i < len; i += 1) { + const player = info.players[i] + // TODO: Mark active player + setTablePlayerInfo(i + 1, player.name, player.connected, false) + } + if (info.tableIsFull) { + showDealButton() + } else { + hideDealButton() + } +} + +function handleCardInfoUpdate(info) { + for (let i = 0, len = info.cards.length; i < len; i += 1) { + setHandCard(i+1, info.cards[i].card, info.cards[i].playable) + } + for (let i = 0, len = info.tableCards.length; i < len; i += 1) { + setTableCard(i+1, info.tableCards[i]) + } +} + function refreshTables() { const token = getSessionToken() if (token) { @@ -240,7 +280,6 @@ function refreshTables() { setTableListContent(html) }).catch(function(error) { showBlankLoginScreen(error.message) - return }) } else { showBlankLoginScreen() @@ -258,8 +297,25 @@ function processTableList(tables) { html += "" } html += "
" + tableInfo.name + "
" - html += "
Players: " + tableInfo.players.join(", ") + "
" + if (tableInfo.players.length == 0) { + html += "
No players
" + } else { + const names = tableInfo.players.map(function(player) { return player.name }).join(", ") + html += "
Players: " + names + "
" + } html += "" // table-row } return html +} + +function dealCards() { + const token = getSessionToken() + if (token) { + performDealCardsRequest(token) + .catch(function(error) { + showBlankLoginScreen(error.message) + }) + } else { + showBlankLoginScreen() + } } \ No newline at end of file diff --git a/Public/schafkopf.html b/Public/schafkopf.html index 7f86337..6695e22 100644 --- a/Public/schafkopf.html +++ b/Public/schafkopf.html @@ -24,10 +24,49 @@
+ Disconnected
-
+
+
+
+
+
+ + + +
+
+
+
+
+
Player 1
+
Offline
+
+
+
Player 2
+
Offline
+
+
+
Player 3
+
Offline
+
+
+
Player 4
+
Offline
+
+
+
+ + + + + + + + +
diff --git a/Public/style.css b/Public/style.css index 5592238..dd4131e 100644 --- a/Public/style.css +++ b/Public/style.css @@ -1,13 +1,16 @@ :root { /* Color definitions for light mode */ - --button-color: rgb(255, 172, 39); - --button-hover: rgb(255, 185, 72); + --button-color: rgb(233, 144, 0); + --button-hover: rgb(255, 191, 89); --button-disabled: rgb(128, 88, 24); --button-text: rgb(0,0,0); --standard-background: rgb(54, 54, 54); --element-background: rgb(42, 42, 42); --element-border: rgb(27, 27, 27); --text-color: rgb(255,255,255); + --secondary-text-color: rgb(27, 27, 27); + --card-color: rgb(255,255,255); + --alert-color: rgb(255,0,0); } /* Style all input fields */ @@ -47,10 +50,15 @@ html * { body, html { min-height: 100%; height: 100%; + width: 100%; margin: 0px; background-color: var(--standard-background); } +body { + position: absolute; +} + #login-window { background-color: var(--standard-background); height: 100%; @@ -91,15 +99,19 @@ body, html { height: 50px; background-color: var(--element-background); padding: 0px; + z-index: 1; + width: 100%; + position: absolute; } #player-info { position: absolute; right: 8px; height: 50px; + width: 205px; display: grid; align-items: center; - grid-template-columns: auto auto; + grid-template-columns: 120px 80px; column-gap: 5px; } @@ -107,6 +119,7 @@ body, html { text-align: right; grid-column: 1; padding-right: 5px; + width: 120px; } #logout-button { @@ -164,12 +177,12 @@ body, html { #game-bar { position: absolute; - width: 120px; + width: 270px; height: 40px; top: 5px; left: 10px; display: none; - grid-template-columns: 1200px; + grid-template-columns: 120px 140px; column-gap: 10px; align-items: center; justify-content: center; @@ -183,6 +196,26 @@ body, html { height: 34px; } +#table-connected-label { + grid-column: 2; + width: 140px; +} + +#main { + height: 100%; + max-height: 100%; + width: 100%; + overflow: scroll; +} + +#main-spacer { + height: 50px; + position: relative; + top: 0px; + width: 100%; +} + + #table-list { margin: 20px; padding-right: 20px; @@ -236,3 +269,171 @@ body, html { grid-row: 2; font-size: small; } + +#table-players { + display: none; + margin-top: 20px; + margin-left: auto; + margin-right: auto; + width: 549px; + height: 437px; + grid-template-columns: 150px 71px 36px 35px 36px 71px 150px; + grid-template-rows: 111px 76px 35px 76px 111px; + column-gap: 0px; + row-gap: 0px; + align-items: center; +} + +#deal-button { + display: none; + grid-column: 7; + grid-row: 5; +} + +#fold-button { + display: none; + grid-column: 1; + grid-row: 5; +} + +#bid-button { + display: none; + grid-column: 7; + grid-row: 5; +} + +.table-card { + height: 187px; + border-radius: 17px; + background-color: var(--element-background); +} + +#table-player-card1 { /* bottom */ + grid-column: 3 / span 3; + grid-row: 4 / span 2; + z-index: 2; +} + +#table-player-card2 { /* left */ + grid-column: 2 / span 2; + grid-row: 2 / span 3; + z-index: 1; +} + +#table-player-card3 { /* top */ + grid-column: 3 / span 3; + grid-row: 1 / span 2; + z-index: 4; +} + +#table-player-card4 { /* right */ + grid-column: 5 / span 2; + grid-row: 2 / span 3; + z-index: 3; +} + +#table-player-info1 { /* bottom */ + grid-column: 1 / span 2; + grid-row: 5; + font-size: large; + text-align: right; + margin-right: 15px; + margin-top: 50px; +} + +#table-player-info2 { /* left */ + grid-column: 1; + grid-row: 3; + font-size: large; + text-align: right; + margin-right: 15px; +} + +#table-player-info3 { /* top */ + grid-column: 1 / span 2; + grid-row: 1; + font-size: large; + text-align: right; + margin-right: 15px; + margin-bottom: 50px; +} + +#table-player-info4 { /* right */ + grid-column: 7; + grid-row: 3; + font-size: large; + margin-left: 15px; +} + +.player-connection-state { + font-size: x-small; + color: var(--alert-color); +} + +#table-player-state1 { + display: none; +} + +#table-player-state2 { + display: none; +} + +#player-cards { + display: none; + margin-left: auto; + margin-right: auto; + width: 923px; + grid-template-columns: 111px 111px 111px 111px 111px 111px 111px 111px; + column-gap: 5px; + margin-top: 10px; +} + +.player-card { + width: 111px; + height: 191px; + border: 2px solid var(--standard-background); + background-color: var(--element-background); + border-radius: 17px; + transition-duration: 0.2s; +} + +.player-card:disabled, +.player-card[disabled] { + cursor: not-allowed; +} + +.player-card:hover:enabled { + border-color: var(--button-hover); +} + +#player-card1 { + grid-column: 1; +} + +#player-card2 { + grid-column: 2; +} + +#player-card3 { + grid-column: 3; +} + +#player-card4 { + grid-column: 4; +} + +#player-card5 { + grid-column: 5; +} + +#player-card6 { + grid-column: 6; +} + +#player-card7 { + grid-column: 7; +} + +#player-card8 { + grid-column: 8; +} \ No newline at end of file