Separate play and list pages

This commit is contained in:
Christoph Hagen 2022-10-13 09:13:16 +02:00
parent 15ff775a68
commit 54978b546b
5 changed files with 674 additions and 23 deletions

36
Public/list.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'/>
<title>Schafkopf</title>
<meta name='viewport' content='width=device-width, initial-scale=1'/>
<link rel='stylesheet' type='text/css' media='screen' href='style.css'/>
<script src='api.js'></script>
<script src='storage.js'></script>
<script src='list.js'></script>
</head>
<body>
<div id="top-bar">
<div id="player-info">
<div id="player-name"></div>
<button id="logout-button" class="standard-button" onclick="logoutUser()">Log out</button>
</div>
<div id="table-list-bar">
<button id="refresh-tables" class="standard-button" onclick="refreshTables()">Refresh list</button>
</div>
</div>
<div id="main">
<div id="main-spacer"></div>
<div id="table-list">
<input type="text" id="table-name-field" name="tablename" placeholder="Create new table..." required>
<input type="checkbox" id="table-public-checkbox" name="public-table" checked="checked">
<span id="table-public-label">Public</span>
<button id="create-table-button" class="standard-button" onclick="createTable()">Create table</button>
</div>
</div>
<script>
loadExistingSession()
</script>
</body>
</html>

149
Public/list.js Normal file
View File

@ -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 = '<div class="table-row">'
+ '<button class="table-join-btn" onclick="createTable()">Create</button>'
+ '<input type="text" id="table-name-field" name="tablename" placeholder="Create new table..." required>'
+ '<input type="checkbox" id="table-public-checkbox" name="public-table" checked="checked">'
+ '<span id="table-public-label">Public</span>'
+ '</div>'
function processTableList(tables) {
var html = createTableRow;
for (let i = 0, len = tables.length; i < len; i++) {
tableInfo = tables[i]
html += "<div class=\"table-row\">"
if (tableInfo.players.length < 4) {
html += "<button class=\"table-join-btn\" onclick=\"joinTable('" + tableInfo.id + "')\">Join</button>"
} else {
html += "<button class=\"table-join-btn\" disabled>Full</button>"
}
html += "<div class=\"table-title\">" + tableInfo.name + "</div>"
if (tableInfo.players.length == 0) {
html += "<div class=\"table-subtitle\">No players</div>"
} else {
const names = tableInfo.players.join(", ")
html += "<div class=\"table-subtitle\">" + names + "</div>"
}
html += "</div>" // table-row
}
setTableListContent(html)
}

View File

@ -1,14 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'/>
<title>Schafkopf</title> <title>Schafkopf</title>
<meta name='viewport' content='width=device-width, initial-scale=1'> <meta name='viewport' content='width=device-width, initial-scale=1'/>
<link rel='stylesheet' type='text/css' media='screen' href='style.css'> <link rel='stylesheet' type='text/css' media='screen' href='style.css'/>
<script src='elements.js?v=1'></script>
<script src='api.js'></script> <script src='api.js'></script>
<script src='storage.js'></script> <script src='storage.js'></script>
<script src='game.js'></script> <script src='play.js'></script>
</head> </head>
<body> <body>
<div id="top-bar"> <div id="top-bar">
@ -16,9 +15,6 @@
<div id="player-name"></div> <div id="player-name"></div>
<button id="logout-button" class="standard-button" onclick="logoutUser()">Log out</button> <button id="logout-button" class="standard-button" onclick="logoutUser()">Log out</button>
</div> </div>
<div id="table-list-bar">
<button id="refresh-tables" class="standard-button" onclick="refreshTables()">Refresh list</button>
</div>
<div id="game-bar"> <div id="game-bar">
<button id="leave-table" class="standard-button" onclick="leaveTable()">Leave table</button> <button id="leave-table" class="standard-button" onclick="leaveTable()">Leave table</button>
<span id="table-name-label">Table</span> <span id="table-name-label">Table</span>
@ -27,12 +23,6 @@
<div id="main"> <div id="main">
<div id="main-spacer"></div> <div id="main-spacer"></div>
<div id="table-list">
<input type="text" id="table-name-field" name="tablename" placeholder="Create new table..." required>
<input type="checkbox" id="table-public-checkbox" name="public-table" checked="checked">
<span id="table-public-label">Public</span>
<button id="create-table-button" class="standard-button" onclick="createTable()">Create table</button>
</div>
<div id="table-players"> <div id="table-players">
<div id="table-player-card-top" class="table-card"></div> <div id="table-player-card-top" class="table-card"></div>
<div id="table-player-card-right" class="table-card"></div> <div id="table-player-card-right" class="table-card"></div>

458
Public/play.js Normal file
View File

@ -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 += "<div class=\"table-row\">"
if (tableInfo.players.length < 4) {
html += "<button class=\"table-join-btn\" onclick=\"joinTable('" + tableInfo.id + "')\">Join</button>"
} else {
html += "<button class=\"table-join-btn\" disabled>Full</button>"
}
html += "<div class=\"table-title\">" + tableInfo.name + "</div>"
if (tableInfo.players.length == 0) {
html += "<div class=\"table-subtitle\">No players</div>"
} else {
const names = tableInfo.players.join(", ")
html += "<div class=\"table-subtitle\">" + names + "</div>"
}
html += "</div>" // 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("<br>")
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 += "<button class=\"standard-button action-button\" " +
"onclick=\"performAction('" + action + "')\">" + content + "</button>"
}
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 += "<div class=\"standard-button available-game\">" + content + "</div>"
}
document.getElementById(elementIdAvailableGamesList).innerHTML = html
}

View File

@ -1,6 +1,7 @@
// Local storage element identifiers // Local storage element identifiers
const localStorageTokenId = "token"; const localStorageTokenId = "token";
const localStoragePlayerName = "name"; const localStoragePlayerName = "name";
const localStorageTableId = "table";
// Can prevent loading of session token, to allow multiple players per browser // Can prevent loading of session token, to allow multiple players per browser
const debugMode = false const debugMode = false
@ -11,28 +12,45 @@ const debugMode = false
* Parameter token: The session token for the player session * Parameter token: The session token for the player session
*/ */
function storePlayerNameAndToken(name, token) { function storePlayerNameAndToken(name, token) {
storePlayerName(name);
storeSessionToken(token);
}
function storePlayerName(name) {
localStorage.setItem(localStoragePlayerName, 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. * Get the last session token from local storage.
*/ */
function loadSessionToken() { function loadSessionToken() {
if (debugMode) {
return debugSessionToken
}
return localStorage.getItem(localStorageTokenId) return localStorage.getItem(localStorageTokenId)
} }
function storeSessionToken(token) { function storeSessionToken(token) {
if (debugMode) {
debugSessionToken = token
return
}
localStorage.setItem(localStorageTokenId, token) localStorage.setItem(localStorageTokenId, token)
} }
function deleteSessionToken() { 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);
} }