diff --git a/Public/list.html b/Public/list.html new file mode 100644 index 0000000..870feb4 --- /dev/null +++ b/Public/list.html @@ -0,0 +1,36 @@ + + + + + Schafkopf + + + + + + + +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + Public + +
+
+ + + diff --git a/Public/list.js b/Public/list.js new file mode 100644 index 0000000..f9a52f9 --- /dev/null +++ b/Public/list.js @@ -0,0 +1,149 @@ +const elementIdTableListBar = "table-list-bar" +const elementIdTableList = "table-list" +const elementIdTableNameField = "table-name-field" +const elementIdPublicTableCheckbox = "table-public-checkbox" + +function setTableListContent(content) { + document.getElementById(elementIdTableList).innerHTML = content; +} + +function getTableName() { + return document.getElementById(elementIdTableNameField).value +} + +function getTableVisibility() { + return document.getElementById(elementIdPublicTableCheckbox).checked +} + +function showAlertWithError(error) { + window.alert(error.message); +} + +function showLoginPage() { + deleteSessionToken(); + window.location.href = "login.html"; +} + +function didJoinTable(table, token) { + console.log("Current table loaded"); + storeTableId(table); + window.location.href = "play.html"; +} + +function logoutUser() { + const token = loadSessionToken(); + if (token == null) { + showLoginPage(); + return + } + performLogoutRequest(token) + .then(function() { + showLoginPage(); + }).catch(showAlertWithError) +} + +function loadExistingSession() { + const token = loadSessionToken(); + if (token == null) { + console.log("No session token, returning to login page"); + showLoginPage(); + return + } + console.log(token); + resumeSessionRequest(token) + .catch(function(error) { + console.log("Failed to resume session"); + console.log(error); + showLoginPage(); + }) + .then(function(name) { + storePlayerName(name); + loadCurrentTable(token); + }) +} + +function loadCurrentTable(token) { + performGetCurrentTableRequest(token) + .then(function(tableId) { + if (tableId == null) { + console.log("No table previously joined."); + refreshTables(); + return; + } + didJoinTable(tableId, token); + }) + .catch(showAlertWithError) +} + +function createTable() { + const tableName = getTableName() + if (tableName == "") { + return + } + const token = loadSessionToken() + if (token == null) { + showLoginPage(); + return; + } + const isVisible = getTableVisibility() + performCreateTableRequest(token, tableName, isVisible) + .then(function(tableId) { + didJoinTable(tableId, token); + }) + .catch(showAlertWithError) +} + +function joinTable(tableId) { + const token = loadSessionToken() + if (token == null) { + showLoginPage(); + return; + } + performJoinTableRequest(tableId, token) + .then(function(tableId) { + didJoinTable(tableId, token); + }) + .catch(showAlertWithError) +} + +function refreshTables() { + const token = loadSessionToken(); + if (token == null) { + console.log("No token to refresh tables"); + showLoginPage(); + return; + } + console.log("Refreshing"); + performGetPublicTablesRequest(token) + .then(processTableList) + .catch(showAlertWithError) +} + +const createTableRow = '
' ++ '' ++ '' ++ '' ++ 'Public' ++ '
' + +function processTableList(tables) { + var html = createTableRow; + for (let i = 0, len = tables.length; i < len; i++) { + tableInfo = tables[i] + html += "
" + if (tableInfo.players.length < 4) { + html += "" + } else { + html += "" + } + html += "
" + tableInfo.name + "
" + if (tableInfo.players.length == 0) { + html += "
No players
" + } else { + const names = tableInfo.players.join(", ") + html += "
" + names + "
" + } + html += "
" // table-row + } + setTableListContent(html) +} diff --git a/Public/schafkopf.html b/Public/play.html similarity index 80% rename from Public/schafkopf.html rename to Public/play.html index 95ec3bc..a99e633 100644 --- a/Public/schafkopf.html +++ b/Public/play.html @@ -1,14 +1,13 @@ - + Schafkopf - - - + + - +
@@ -16,9 +15,6 @@
-
- -
Table @@ -27,12 +23,6 @@
-
- - - Public - -
diff --git a/Public/play.js b/Public/play.js new file mode 100644 index 0000000..6c647d9 --- /dev/null +++ b/Public/play.js @@ -0,0 +1,458 @@ + +const offlineText = "Offline" +const missingPlayerText = "Leer" + +const elementIdActionBar = "action-bar" +const elementIdTableName = "table-name-label" +const elementIdAvailableGamesList = "available-games-list" +const elementIdGameSummary = "game-summary" + +// The web socket to connect to the server +var socket = null; + +function closeSocketIfNeeded() { + if (socket) { + socket.close() + didCloseSocket() + } +} + +function didCloseSocket() { + socket = null +} + +function showAlertWithError(error) { + window.alert(error.message) +} + +function showLoginPage() { + deleteSessionToken(); + deleteTableId(); + closeSocketIfNeeded(); + window.location.href = "login.html"; +} + +function showListPage() { + deleteTableId(); + closeSocketIfNeeded(); + window.location.href = "list.html"; +} + +function logoutUser() { + const token = loadSessionToken() + if (token == null) { + showLoginPage(); + return + } + performLogoutRequest(token) + .then(function() { + showLoginPage(); + }).catch(showAlertWithError) +} + +function loadExistingSession() { + const token = loadSessionToken() + if (token == null) { + showLoginPage(); + return + } + resumeSessionRequest(token) + .then(function(name) { + storePlayerName(name); + loadCurrentTable(token) + }).catch(function(error) { + console.log("Failed to resume session"); + console.log(error); + showLoginPage(); + }) +} + +function loadCurrentTable(token) { + performGetCurrentTableRequest(token) + .then(function(table) { + if (table == null) { + showListPage(); + } else { + storeTableId(table.id); + updateTableInfo(table); + openSocket(token); + } + }) + .catch(showAlertWithError) +} + +function leaveTable() { + const token = loadSessionToken() + if (token == null) { + showLoginPage(); + return + } + performLeaveTableRequest(token) + .then(function() { + showListPage(); + }) + .catch(showAlertWithError) +} + +function openSocket(token) { + const socketPath = webSocketPath() + socket = new WebSocket(socketPath) + + socket.onopen = function(e) { + socket.send(token); + showConnectedState() + }; + + socket.onmessage = function(event) { + const table = convertJsonResponse(event.data) + updateTableInfo(table) + }; + + socket.onclose = function(event) { + if (event.wasClean) { + + } 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) { + // error.message + }; +} + +function refreshTables() { + const token = loadSessionToken() + if (token == null) { + showLoginPage() + return + } + performGetPublicTablesRequest(token) + .then(processTableList) + .catch(showAlertWithError) +} + +function processTableList(tables) { + var html = createTableRow + for (let i = 0, len = tables.length; i < len; i++) { + tableInfo = tables[i] + html += "
" + if (tableInfo.players.length < 4) { + html += "" + } else { + html += "" + } + html += "
" + tableInfo.name + "
" + if (tableInfo.players.length == 0) { + html += "
No players
" + } else { + const names = tableInfo.players.join(", ") + html += "
" + names + "
" + } + html += "
" // table-row + } + setTableListContent(html) +} + +function dealCards() { + const token = loadSessionToken() + if (token == null) { + showLoginPage() + return + } + performDealCardsRequest(token) + .catch(showAlertWithError) +} + +function playCard(card) { + const token = loadSessionToken() + if (token == null) { + showLoginPage() + return + } + performPlayCardRequest(token, card) + .catch(function(error) { + console.log(error) + }) +} + +function setDisplayStyle(id, style) { + document.getElementById(id).style.display = style +} + +function hide(elementId) { + setDisplayStyle(elementId, "none") +} + +function showTableName(name) { + document.getElementById(elementIdTableName).innerHTML = name +} + +function showConnectedState() { + showPlayerState("bottom", "") +} + +function showDisconnectedState() { + showPlayerDisconnected("bottom") +} + +function setEmptyPlayerInfo(position) { + setTablePlayerName(position, null, false) + showPlayerState(position, "") + setTableCard(position, "", 1) +} + +function setTablePlayerName(position, name, active) { + const nameElement = document.getElementById("table-player-name-" + position) + if (name == null) { + nameElement.style.color = "var(--secondary-text-color)" + nameElement.innerHTML = missingPlayerText + } else { + nameElement.style.color = active ? "var(--button-color)" : "var(--text-color)" + nameElement.innerHTML = name + } +} + +function showPlayerDisconnected(position) { + setPlayerState(position, "var(--alert-color)", offlineText) +} + +function showPlayerState(position, state) { + setPlayerState(position, "var(--secondary-text-color)", state) +} + +function setPlayerState(position, color, text) { + const connectionElement = "table-player-state-" + position + const element = document.getElementById(connectionElement) + element.style.color = color + element.innerHTML = text +} + +function setTableCard(position, card, layer) { + const id = "table-player-card-" + position + setCard(id, card, layer) +} + +function setHandCard(index, card, playable) { + const id = "player-card" + index.toString() + setCard(id, card, 1) + const button = document.getElementById(id) + if (playable) { + button.disabled = false + button.onclick = function() { + playCard(card) + } + } else { + button.disabled = true + button.onclick = function() { } + } +} + +function setCard(id, card, layer) { + const element = document.getElementById(id) + if (card == "") { + element.style.backgroundColor = "var(--element-background)" + element.style.backgroundImage = "" + element.style.zIndex = 0 + + } else { + element.style.backgroundColor = "" + element.style.backgroundImage = "url('cards/" + card + ".jpeg')" + element.style.zIndex = layer + 1 + } +} + +function updateTableInfo(table) { + showTableName(table.name) + // Set own cards + const cardCount = table.cards.length + for (let i = 0; i < cardCount; i += 1) { + const card = table.cards[i] + setHandCard(i+1, card.card, card.playable) + } + for (let i = cardCount; i < 8; i += 1) { + setHandCard(i+1, "", false) + } + + let playedGame = null + if (table.hasOwnProperty("game")) { + playedGame = textForAction(table.game) + } + + // Show player info + console.log(table) + setInfoForPlayer(table.player, "bottom", playedGame) + if (table.playerSelectsGame) { + setActionsForOwnPlayer(table.playableGames) + showAvailableGames([]) + } else { + if (table.player.active) { + showAvailableGames(table.playableGames) + } else { + showAvailableGames([]) + } + setActionsForOwnPlayer(table.actions) + } + if (table.hasOwnProperty("playerLeft")) { + setInfoForPlayer(table.playerLeft, "left", playedGame) + } else { + setEmptyPlayerInfo("left") + } + if (table.hasOwnProperty("playerAcross")) { + setInfoForPlayer(table.playerAcross, "top", playedGame) + } else { + setEmptyPlayerInfo("top") + } + if (table.hasOwnProperty("playerRight")) { + setInfoForPlayer(table.playerRight, "right", playedGame) + } else { + setEmptyPlayerInfo("right") + } + if (table.hasOwnProperty("summary")) { + setGameSummary(table.summary) + } else { + hideGameSummary() + } +} + +function setGameSummary(summary) { + document.getElementById(elementIdGameSummary).innerHTML = summary.text + setDisplayStyle(elementIdGameSummary, "inherit") +} + +function hideGameSummary() { + hide(elementIdGameSummary) +} + +function setInfoForPlayer(player, position, game) { + var card = "" + if (player.hasOwnProperty("card")) { + card = player.card + } + const layer = player.position + setTableCard(position, card, layer) + setTablePlayerName(position, player.name, player.active) + + var state = player.state + if (game != null && state.indexOf("selects") > -1) { + const i = state.indexOf("selects") + state[i] = game + } + + const text = state.map(x => convertStateToString(x)).join("
") + showPlayerState(position, text) +} + +function doubleText(doubles) { + if (doubles == 0) { + return null + } + if (doubles == 1) { + return "gedoppelt" + } + return doubles.toString() + "x gedoppelt" +} + +function clearInfoForPlayer(position) { + setEmptyPlayerInfo(position) +} + +function setActionsForOwnPlayer(actions) { + var len = actions.length + var html = "" + for (let i = 0; i < len; i += 1) { + const action = actions[i] + const content = textForAction(action) + html += "" + } + document.getElementById(elementIdActionBar).innerHTML = html +} + +function textForAction(action) { + switch (action) { + /// The player can request cards to be dealt + case "deal": + return "Austeilen" + + /// The player doubles on the initial four cards + case "double": + return "Legen" + + /// The player does not double on the initial four cards + case "skip": + return "Nicht legen" + + /// The player offers a wedding (one trump card) + case "wedding": + return "Hochzeit anbieten" + + /// The player can choose to accept the wedding + case "accept": + return "Hochzeit akzeptieren" + + /// The player matches or increases the game during auction + case "bid": + return "Spielen" + + /// The player does not want to play + case "out": + return "Weg" + + /// The player claims to win and doubles the game cost ("schießen") + case "raise": + return "Schießen" + + case "ruf": + return "Ruf" + case "ruf-eichel": + return "Ruf Eichel" + case "ruf-blatt": + return "Ruf Blatt" + case "ruf-schelln": + return "Ruf Schelln" + case "bettel": + return "Bettel" + case "wenz": + return "Wenz" + case "geier": + return "Geier" + case "solo": + return "Solo" + case "solo-eichel": + return "Eichel Solo" + case "solo-blatt": + return "Blatt Solo" + case "solo-herz": + return "Herz Solo" + case "solo-schelln": + return "Schelln Solo" + } + return action +} + +function performAction(action) { + const token = loadSessionToken() + if (token == null) { + showBlankLogin() + return + } + performPlayerActionRequest(token, action) + // TODO: Handle errors and success +} + +function showAvailableGames(games) { + var len = games.length + var html = "" + for (let i = 0; i < len; i += 1) { + const game = games[i] + const content = textForAction(game) + html += "
" + content + "
" + } + document.getElementById(elementIdAvailableGamesList).innerHTML = html +} diff --git a/Public/storage.js b/Public/storage.js index ac1b00b..7656b88 100644 --- a/Public/storage.js +++ b/Public/storage.js @@ -1,6 +1,7 @@ // Local storage element identifiers const localStorageTokenId = "token"; const localStoragePlayerName = "name"; +const localStorageTableId = "table"; // Can prevent loading of session token, to allow multiple players per browser const debugMode = false @@ -11,28 +12,45 @@ const debugMode = false * Parameter token: The session token for the player session */ function storePlayerNameAndToken(name, token) { + storePlayerName(name); + storeSessionToken(token); +} + +function storePlayerName(name) { localStorage.setItem(localStoragePlayerName, name); - localStorage.setItem(localStorageTokenId, token); +} + +function loadPlayerName() { + return localStorage.getItem(localStoragePlayerName); +} + +function deletePlayerName() { + localStorage.removeItem(localStoragePlayerName); } /* * Get the last session token from local storage. */ function loadSessionToken() { - if (debugMode) { - return debugSessionToken - } return localStorage.getItem(localStorageTokenId) } function storeSessionToken(token) { - if (debugMode) { - debugSessionToken = token - return - } localStorage.setItem(localStorageTokenId, token) } function deleteSessionToken() { - localStorage.removeItem(localStorageTokenId) + localStorage.removeItem(localStorageTokenId); +} + +function storeTableId(table) { + localStorage.setItem(localStorageTableId, table); +} + +function getTableId() { + return localStorage.getItem(localStorageTableId); +} + +function deleteTableId() { + localStorage.removeItem(localStorageTableId); }