From 327acfe1124cdafc5eb460a039222a160f867ba3 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Wed, 17 Nov 2021 13:39:16 +0100 Subject: Simplify URL for playing games. --- connect-better-sqlite3.js | 2 +- public/common/client.js | 38 ++++++++++----------- public/join.js | 11 +++--- server.js | 86 +++++++++++++++++++++++++++++++++++------------ views/games.ejs | 44 ++++++++++++++---------- views/info.ejs | 74 ++++++++++++++++++++++++---------------- views/join.ejs | 16 +-------- views/profile.ejs | 55 +++++++++++++++++------------- 8 files changed, 193 insertions(+), 133 deletions(-) diff --git a/connect-better-sqlite3.js b/connect-better-sqlite3.js index 008114b..6574346 100644 --- a/connect-better-sqlite3.js +++ b/connect-better-sqlite3.js @@ -31,7 +31,7 @@ module.exports = function (session) { let db = new SQLite(db_path, options.mode); db.pragma("journal_mode = WAL"); db.pragma("synchronous = OFF"); - db.exec("CREATE TABLE IF NOT EXISTS "+table+" (sid PRIMARY KEY, expires INTEGER, sess TEXT)"); + db.exec("CREATE TABLE IF NOT EXISTS "+table+" (sid PRIMARY KEY, expires INTEGER, sess TEXT) WITHOUT ROWID"); db.exec("DELETE FROM "+table+" WHERE "+now()+" > expires"); db.exec("VACUUM"); db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); diff --git a/public/common/client.js b/public/common/client.js index 1e31f79..ccc9718 100644 --- a/public/common/client.js +++ b/public/common/client.js @@ -2,6 +2,16 @@ /* global io, on_update */ +/* URL: /$title_id/play:$game_id:$role */ +if (!/\/[\w-]+\/play:\d+(:[\w-]+)?/.test(window.location.pathname)) { + document.querySelector("#prompt").textContent = "Invalid game ID."; + throw Error("Invalid game ID."); +} + +const param_title_id = window.location.pathname.split("/")[1]; +const param_game_id = window.location.pathname.split("/")[2].split(":")[1] | 0; +const param_role = window.location.pathname.split("/")[2].split(":")[2] || "Observer"; + let game = null; let game_over = false; let player = null; @@ -105,8 +115,8 @@ function stop_blinker() { window.addEventListener("focus", stop_blinker); -function load_chat(game_id) { - chat_key = "chat/" + game_id; +function load_chat() { + chat_key = "chat/" + param_game_id; chat_text = document.querySelector(".chat_text"); chat_last_day = null; chat_log = 0; @@ -158,11 +168,6 @@ function update_chat(chat_id, utc_date, user, message) { } function init_client(roles) { - let params = new URLSearchParams(window.location.search); - let title = window.location.pathname.split("/")[1]; - let game_id = params.get("game"); - let role = params.get("role"); - game = null; player = null; @@ -186,13 +191,13 @@ function init_client(roles) { ".role.seven .role_user", ]; - load_chat(game_id); + load_chat(); - console.log("JOINING", title + "/" + game_id + "/" + role); + console.log("JOINING", param_title_id + "/" + param_game_id + "/" + param_role); socket = io({ transports: ['websocket'], - query: { title: title, game: game_id, role: role }, + query: { title: param_title_id, game: param_game_id, role: param_role }, }); socket.on('connect', () => { @@ -242,7 +247,7 @@ function init_client(roles) { socket.on('save', (msg) => { console.log("SAVE"); - window.localStorage[title + '/save'] = msg; + window.localStorage[param_title_id + '/save'] = msg; }); socket.on('error', (msg) => { @@ -446,8 +451,7 @@ function send_save() { } function send_restore() { - let title = window.location.pathname.split("/")[1]; - socket.emit('restore', window.localStorage[title + '/save']); + socket.emit('restore', window.localStorage[param_title_id + '/save']); } function send_restart(scenario) { @@ -474,13 +478,9 @@ function on_game_over() { } function send_rematch() { - let params = new URLSearchParams(window.location.search); - let game_id = params.get("game"); - let role_id = params.get("role"); - window.location = '/rematch/' + game_id + '/' + role_id; + window.location = '/rematch/' + param_game_id + '/' + param_role; } function send_exit() { - let title = window.location.pathname.split("/")[1]; - window.location = '/info/' + title; + window.location = '/info/' + param_title_id; } diff --git a/public/join.js b/public/join.js index ce3d0d0..898113b 100644 --- a/public/join.js +++ b/public/join.js @@ -60,7 +60,7 @@ function start_event_source() { evtsrc.addEventListener("game", function (evt) { console.log("GAME:", evt.data); game = JSON.parse(evt.data); - if (game.status > 1) { + if (game.status > 0) { clearInterval(timer); evtsrc.close(); } @@ -85,9 +85,6 @@ function is_solo() { } function update() { - window.game_status.textContent = ["Open","Active","Finished","Abandoned"][game.status]; - window.game_result.textContent = game.result || "\u2014"; - for (let i = 0; i < roles.length; ++i) { let role = roles[i]; let role_id = "role_" + role.replace(/ /g, "_"); @@ -103,7 +100,7 @@ function update() { else element.className = ""; if (player.user_id === user_id) - element.innerHTML = `Play`; + element.innerHTML = `Play`; else element.innerHTML = player.name; } else { @@ -129,7 +126,7 @@ function update() { else message.innerHTML = "Waiting for players to join..."; } else { - message.innerHTML = `Observe`; + message.innerHTML = `Observe`; } if (game.owner_id === user_id) { @@ -150,7 +147,7 @@ function update() { window.onload = function () { update(); - if (game.status < 2) { + if (game.status === 0) { start_event_source(); timer = setInterval(start_event_source, 15000); } diff --git a/server.js b/server.js index 02411db..c017b0c 100644 --- a/server.js +++ b/server.js @@ -907,7 +907,9 @@ const SQL_SELECT_PLAYERS_JOIN = SQL("SELECT role, user_id, name FROM players NAT const SQL_SELECT_PLAYER_ROLE = SQL("SELECT role FROM players WHERE game_id=? AND user_id=?").pluck(); const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id) VALUES (?,?,?)"); const SQL_DELETE_PLAYER_ROLE = SQL("DELETE FROM players WHERE game_id=? AND role=?"); -const SQL_UPDATE_PLAYER_ROLE = db.prepare("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?"); +const SQL_UPDATE_PLAYER_ROLE = SQL("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?"); + +const SQL_AUTHORIZE_GAME_ROLE = SQL("SELECT 1 FROM players NATURAL JOIN games WHERE title_id=? AND game_id=? AND role=? AND user_id=?").pluck(); const SQL_SELECT_OPEN_GAMES = db.prepare("SELECT * FROM games WHERE status=0"); const SQL_COUNT_OPEN_GAMES = SQL("SELECT COUNT(*) FROM games WHERE owner_id=? AND status=0").pluck(); @@ -927,12 +929,14 @@ const SQL_INSERT_REMATCH = SQL(` const QUERY_LIST_GAMES = SQL(` SELECT * FROM game_view WHERE private=0 AND status=? + AND EXISTS ( SELECT 1 FROM players WHERE players.game_id = game_view.game_id AND user_id = game_view.owner_id ) ORDER BY mtime DESC `); const QUERY_LIST_GAMES_OF_TITLE = SQL(` SELECT * FROM game_view WHERE private=0 AND title_id=? AND status=? + AND EXISTS ( SELECT 1 FROM players WHERE players.game_id = game_view.game_id AND user_id = game_view.owner_id ) ORDER BY mtime DESC LIMIT ? `); @@ -967,24 +971,44 @@ function is_solo(players) { return players.every(p => p.user_id === players[0].user_id) } -function annotate_games(games, user_id) { - for (let i = 0; i < games.length; ++i) { - let game = games[i]; - let players = SQL_SELECT_PLAYERS_JOIN.all(game.game_id); - game.player_names = players.map(p => { - let name = p.name.replace(/ /g, '\xa0'); - return p.user_id > 0 ? `${name}` : name; - }).join(", "); - game.is_active = is_active(game, players, user_id); - game.is_shared = is_shared(game, players, user_id); - game.is_yours = false; - if (user_id > 0) - for (let i = 0; i < players.length; ++i) - if (players[i].user_id === user_id) - game.is_yours = 1; - game.ctime = human_date(game.ctime); - game.mtime = human_date(game.mtime); +function format_options(options) { + function to_english(k) { + if (k === true) return 'yes'; + if (k === false) return 'no'; + return k.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()); } + if (!options || options === '{}') + return "None"; + options = JSON.parse(options); + return Object.entries(options||{}).map(([k,v]) => v === true ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", "); +} + +function annotate_game(game, user_id) { + let players = SQL_SELECT_PLAYERS_JOIN.all(game.game_id); + game.player_names = players.map(p => { + let name = p.name.replace(/ /g, '\xa0'); + return p.user_id > 0 ? `${name}` : name; + }).join(", "); + game.options = format_options(game.options); + game.is_active = is_active(game, players, user_id); + game.is_shared = is_shared(game, players, user_id); + game.is_yours = false; + game.your_role = null; + if (user_id > 0) { + for (let i = 0; i < players.length; ++i) { + if (players[i].user_id === user_id) { + game.is_yours = 1; + game.your_role = players[i].role; + } + } + } + game.ctime = human_date(game.ctime); + game.mtime = human_date(game.mtime); +} + +function annotate_games(games, user_id) { + for (let i = 0; i < games.length; ++i) + annotate_game(games[i], user_id); } app.get('/games', may_be_logged_in, function (req, res) { @@ -1189,6 +1213,7 @@ app.get('/join/:game_id', must_be_logged_in, function (req, res) { let game = SQL_SELECT_GAME_VIEW.get(game_id); if (!game) return res.status(404).send("Invalid game ID."); + annotate_game(game, req.user.user_id); let roles = ROLES[game.title_id]; let players = SQL_SELECT_PLAYERS_JOIN.all(game_id); let ready = (game.status === 0) && RULES[game.title_id].ready(game.scenario, game.options, players); @@ -1310,7 +1335,7 @@ app.get('/play/:game_id/:role', may_be_logged_in, function (req, res) { let title = SQL_SELECT_GAME_TITLE.get(game_id); if (!title) return res.redirect('/join/'+game_id); - res.redirect('/'+title+'/play.html?game='+game_id+'&role='+role); + res.redirect('/'+title+'/play:'+game_id+':'+role); }); app.get('/play/:game_id', may_be_logged_in, function (req, res) { @@ -1322,8 +1347,27 @@ app.get('/play/:game_id', may_be_logged_in, function (req, res) { return res.redirect('/join/'+game_id); let role = SQL_SELECT_PLAYER_ROLE.get(game_id, user_id); if (!role) - return res.redirect('/'+title+'/play.html?game='+game_id+'&role=Observer'); - return res.redirect('/'+title+'/play.html?game='+game_id+'&role='+role); + role = "Observer"; + res.redirect('/'+title+'/play:'+game_id+':'+role); +}); + +app.get('/:title_id/play\::game_id\::role', must_be_logged_in, function (req, res) { + let user_id = req.user ? req.user.user_id : 0; + let title_id = req.params.title_id + let game_id = req.params.game_id; + let role = req.params.role; + if (!SQL_AUTHORIZE_GAME_ROLE.get(title_id, game_id, role, user_id)) + return res.send("You are not assigned that role."); + return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); +}); + +app.get('/:title_id/play\::game_id', may_be_logged_in, function (req, res) { + let title_id = req.params.title_id + let game_id = req.params.game_id; + let a_title = SQL_SELECT_GAME_TITLE.get(game_id); + if (a_title !== title_id) + return res.send("Invalid game ID."); + return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); }); /* diff --git a/views/games.ejs b/views/games.ejs index 3b3f485..86a2502 100644 --- a/views/games.ejs +++ b/views/games.ejs @@ -2,41 +2,49 @@

Open

-
IDTitleScenarioPlayersDescriptionCreated -<% if (open_games.length > 0) { %> -<% open_games.forEach((row) => { %> +
IDTitleScenarioOptionsPlayersDescriptionCreated +<%_ if (open_games.length > 0) { _%> +<%_ open_games.forEach((row) => { _%>
<%= row.game_id %> <%= row.title_name %> <%= row.scenario %> -<%- row.player_names || row.owner_name %> +<%- row.options %> +<%- row.player_names %> <%= row.description %> <%= row.ctime %> Join -<% }); } else { %> -
No open games. -<% } %> +<%_ }); } else { _%> +
No open games. +<%_ } _%>

Active

-
IDTitleScenarioPlayersDescriptionChangedActive -<% if (active_games.length > 0) { %> -<% active_games.forEach((row) => { %> +
IDTitleScenarioOptionsPlayersDescriptionChangedActive +<%_ if (active_games.length > 0) { _%> +<%_ active_games.forEach((row) => { _%>
<%= row.game_id %> <%= row.title_name %> <%= row.scenario %> +<%- row.options %> <%- row.player_names %> <%= row.description %> <%= row.mtime %> "><%= row.active %> -<% if (row.is_yours) { %> -Enter -<% } else { %> -View -<% } %> -<% }); } else { %> -
No active games. -<% } %> +<%_ + if (row.is_yours) { + if (row.is_shared) { + %>View<% + } else { + %>View<% + } + } else { + %>View<% + } +_%> +<%_ }); } else { _%> +
No active games. +<%_ } _%>
diff --git a/views/info.ejs b/views/info.ejs index 7c98e21..4a515ff 100644 --- a/views/info.ejs +++ b/views/info.ejs @@ -1,4 +1,4 @@ -<%- include('header', { title: title.title_name, refresh: (user ? 300 : 0) }) %> +<%- include('header', { title: title.title_name, refresh: (user ? 300 : 0) }) _%> <%- include('../public/' + title.title_id + '/about.html') %>

@@ -7,62 +7,78 @@ Read more about the game on

Open Games

-
IDScenarioPlayersDescriptionCreated -<% if (open_games.length > 0) { %> -<% open_games.forEach((row) => { %> +
IDScenarioOptionsPlayersDescriptionCreated +<%_ if (open_games.length > 0) { _%> +<%_ open_games.forEach((row) => { _%>
<%= row.game_id %> -<%= row.scenario %> -<%- row.player_names || `${row.owner_name}` %> +<%= row.scenario %> +<%- row.options %> +<%- row.player_names %> <%= row.description %> <%= row.ctime %> Join -<% }); } else { %> -
No open games. -<% } %> +<%_ }); } else { _%> +
No open games. +<%_ } _%>

Create a new game. -<% if (active_games.length > 0) { %> +<%_ if (active_games.length > 0) { _%>

Active Games

-
IDScenarioPlayersDescriptionChangedTurn -<% active_games.forEach((row) => { %> +
IDScenarioOptionsPlayersDescriptionChangedTurn +<%_ active_games.forEach((row) => { _%>
<%= row.game_id %> <%= row.scenario %> +<%- row.options %> <%- row.player_names %> <%= row.description %> <%= row.mtime %> "><%= row.active %> -<% if (row.is_yours) { %> -Enter -<% } else { %> -View -<% } %> -<% }); %> +<%_ + if (row.is_yours) { + if (row.is_shared) { + %>Play<% + } else { + %>Play<% + } + } else { + %>View<% + + } +_%> +<%_ }); _%>
-<% } %> +<%_ } _%> -<% if (finished_games.length > 0) { %> +<%_ if (finished_games.length > 0) { _%>

Finished Games

-
IDScenarioPlayersDescriptionFinishedResult -<% finished_games.forEach((row) => { %> +
IDScenarioOptionsPlayersDescriptionFinishedResult +<%_ finished_games.forEach((row) => { _%>
<%= row.game_id %> <%= row.scenario %> +<%- row.options %> <%- row.player_names %> <%= row.description %> <%= row.mtime %> <%= row.result %> -<% if (row.is_yours) { %> -Enter -<% } else { %> -View -<% } %> -<% }); %> +<%_ + if (row.is_yours) { + if (row.is_shared) { + %>View<% + } else { + %>View<% + } + } else { + %>View<% + } +_%> +<%_ }); _%>
-<% } %> +<%_ } _%> diff --git a/views/join.ejs b/views/join.ejs index fa33d5c..6558745 100644 --- a/views/join.ejs +++ b/views/join.ejs @@ -1,14 +1,4 @@ <%- include('header', { title: game.title_name }) -%> -<% -function to_english(k) { - if (k === true) return 'yes'; - if (k === false) return 'no'; - return k.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()); -} -function format_options(options) { - return Object.entries(options||{}).map(([k,v]) => v === true ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", "); -} --%>