From ad4a4ea757cce61cde102be2aea2d07fe55aaa2c Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Fri, 25 Jun 2021 17:41:05 +0200 Subject: New and improved join page with server sent events. --- public/join.js | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ public/style.css | 1 + server.js | 121 +++++++++++++++++++++++++++++++++++++++++++------------ views/create.ejs | 2 +- views/join.ejs | 93 ++++++++++++------------------------------ 5 files changed, 231 insertions(+), 95 deletions(-) create mode 100644 public/join.js diff --git a/public/join.js b/public/join.js new file mode 100644 index 0000000..96ca321 --- /dev/null +++ b/public/join.js @@ -0,0 +1,109 @@ +"use strict"; + +let evtsrc = null; + +function confirm_delete(status) { + let warning = "Are you sure you want to DELETE this game?"; + if (window.confirm(warning)) + window.open("/delete/" + game.game_id); +} + +function send(url) { + fetch(url) + .then(r => r.text()) + .then(t => window.error.textContent = (t === "SUCCESS") ? "" : t) + .catch(e => window.error.textContent = e ); + start_event_source(); + return void 0; +} + +function start_event_source() { + if (!evtsrc || evtsrc.readyState === 2) { + console.log("STARTING EVENT SOURCE"); + evtsrc = new EventSource("/join-events/" + game.game_id); + evtsrc.addEventListener("players", function (evt) { + console.log("PLAYERS:", evt.data); + players = JSON.parse(evt.data); + update(); + }); + evtsrc.addEventListener("ready", function (evt) { + console.log("READY:", evt.data); + ready = JSON.parse(evt.data); + update(); + }); + evtsrc.addEventListener("game", function (evt) { + console.log("GAME:", evt.data); + game = JSON.parse(evt.data); + if (game.status > 1) + evtsrc.close(); + update(); + }); + evtsrc.onerror = function (err) { + window.message.innerHTML = "Disconnected from server..."; + }; + } +} + +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, "_"); + if (game.random && game.status === 0) + role = "Random " + (i+1); + document.getElementById(role_id + "_name").textContent = role; + let player = players.find(p => p.role === role); + let element = document.getElementById(role_id); + if (player) { + if (game.status > 0) { + if (game.active === role || game.active === "Both" || game.active === "All") + element.className = "your_turn"; + else + element.className = ""; + if (player.user_id === user_id) + element.innerHTML = `Play`; + else + element.innerHTML = player.name; + } else { + if ((player.user_id === user_id) || (game.owner_id === user_id)) + element.innerHTML = `\u274c ${player.name}`; + else + element.innerHTML = player.name; + } + } else { + if (game.status === 0) + //element.innerHTML = `Join`; + element.innerHTML = `Join`; + else + element.innerHTML = "Empty"; + } + } + + let message = window.message; + if (game.status === 0) { + if (ready && (game.owner_id === user_id)) + message.innerHTML = "Ready to start..."; + else if (ready) + message.innerHTML = "Waiting for game to start..."; + else + message.innerHTML = "Waiting for players to join..."; + } else { + message.innerHTML = `Observe`; + } + + if (game.owner_id === user_id) { + window.start_button.disabled = !ready; + window.start_button.classList = (game.status === 0) ? "" : "hide"; + window.delete_button.classList = (game.status === 0 || solo) ? "" : "hide"; + } +} + +window.onload = function () { + update(); + if (game.status < 2) { + start_event_source(); + setInterval(start_event_source, 15000); + } +} diff --git a/public/style.css b/public/style.css index 157661b..0503574 100644 --- a/public/style.css +++ b/public/style.css @@ -44,6 +44,7 @@ button, select { margin: 5px 10px 5px 0; padding: 1px 10px; background-color: gainsboro; + vertical-align: top; } button:disabled { color: gray; diff --git a/server.js b/server.js index fbad44b..5540833 100644 --- a/server.js +++ b/server.js @@ -632,7 +632,7 @@ const QUERY_ROLE_FROM_GAME_AND_USER = db.prepare("SELECT role FROM players WHERE const QUERY_IS_SOLO = db.prepare("SELECT COUNT(DISTINCT user_id) = 1 FROM players WHERE game_id = ?").pluck(); const QUERY_JOIN_GAME = db.prepare("INSERT INTO players (user_id, game_id, role) VALUES (?,?,?)"); -const QUERY_PART_GAME = db.prepare("DELETE FROM players WHERE game_id = ? AND user_id = ? AND role = ?"); +const QUERY_PART_GAME = db.prepare("DELETE FROM players WHERE game_id = ? AND role = ?"); const QUERY_START_GAME = db.prepare("UPDATE games SET status = 1, state = ?, active = ? WHERE game_id = ?"); const QUERY_CREATE_GAME = db.prepare(` INSERT INTO games @@ -829,6 +829,39 @@ app.get('/rematch/:old_game_id', must_be_logged_in, function (req, res) { } }); +let join_clients = {}; + +function update_join_clients_players(game_id) { + let list = join_clients[game_id]; + if (list) { + console.log("UPDATE JOIN PLAYERS", game_id, list.length) + let players = QUERY_PLAYERS.all(game_id); + let ready = RULES[list.title_id].ready(list.scenario, players); + for (let res of list) { + console.log("PUSH JOIN PLAYERS", game_id); + res.write("retry: 10000\n"); + res.write("event: players\n"); + res.write("data: " + JSON.stringify(players) + "\n\n"); + res.write("event: ready\n"); + res.write("data: " + ready + "\n\n"); + } + } +} + +function update_join_clients_game(game_id) { + let list = join_clients[game_id]; + if (list && list.length > 0) { + console.log("UPDATE JOIN GAME", game_id, list.length) + let game = QUERY_GAME.get(game_id); + for (let res of list) { + console.log("PUSH JOIN GAME", game_id); + res.write('retry: 10000\n'); + res.write('event: game\n'); + res.write('data: ' + JSON.stringify(game) + '\n\n'); + } + } +} + app.get('/join/:game_id', must_be_logged_in, function (req, res) { LOG(req, "GET /join/" + req.params.game_id); let game_id = req.params.game_id | 0; @@ -838,10 +871,8 @@ app.get('/join/:game_id', must_be_logged_in, function (req, res) { return res.redirect('/'); } let roles = QUERY_ROLES.all(game.title_id); - if (game.random && game.status == 0) - for (let i = 0; i < roles.length; ++i) - roles[i] = "Random " + (i+1); let players = QUERY_PLAYERS.all(game_id); + let ready = (game.status == 0) && RULES[game.title_id].ready(game.scenario, players); res.set("Cache-Control", "no-store"); res.render('join.ejs', { user: req.user, @@ -849,34 +880,70 @@ app.get('/join/:game_id', must_be_logged_in, function (req, res) { roles: roles, players: players, solo: players.every(p => p.user_id == req.user.user_id), + ready: players.length == roles.length, message: req.flash('message') }); }); +app.get('/join-events/:game_id', must_be_logged_in, function (req, res) { + LOG(req, "GET /join-events/" + req.params.game_id); + let game_id = req.params.game_id | 0; + let players = QUERY_PLAYERS.all(game_id); + let game = QUERY_GAME.get(game_id); + + res.setHeader("Cache-Control", "no-store"); + res.setHeader("Content-Type", "text/event-stream"); + res.setHeader("Connection", "keep-alive"); + + if (!game) + return res.end(); + if (!(game_id in join_clients)) { + join_clients[game_id] = []; + join_clients[game_id].title_id = game.title_id; + join_clients[game_id].scenario = game.scenario; + } + join_clients[game_id].push(res); + + res.on('close', err => { + console.log("CLOSE JOIN EVENTS", err); + let list = join_clients[game_id]; + let i = list.indexOf(res); + if (i >= 0) + list.splice(i, 1); + }); + + res.write("retry: 15000\n\n"); + res.write("event: game\n"); + res.write("data: " + JSON.stringify(game) + "\n\n"); + res.write("event: players\n"); + res.write("data: " + JSON.stringify(players) + "\n\n"); +}); + app.get('/join/:game_id/:role', must_be_logged_in, function (req, res) { LOG(req, "GET /join/" + req.params.game_id + "/" + req.params.role); let game_id = req.params.game_id | 0; let role = req.params.role; try { QUERY_JOIN_GAME.run(req.user.user_id, game_id, role); - return res.redirect('/join/'+game_id); + update_join_clients_players(game_id); + res.send("SUCCESS"); } catch (err) { - req.flash('message', err.toString()); - return res.redirect('/join/'+game_id); + console.log(err); + res.send(err.toString()); } }); -app.get('/part/:game_id/:part_id/:role', must_be_logged_in, function (req, res) { - LOG(req, "GET /part/" + req.params.game_id + "/" + req.params.part_id + "/" + req.params.role); +app.get('/part/:game_id/:role', must_be_logged_in, function (req, res) { + LOG(req, "GET /part/" + req.params.game_id + "/" + req.params.role); let game_id = req.params.game_id | 0; - let part_id = req.params.part_id | 0; let role = req.params.role; try { - QUERY_PART_GAME.run(game_id, part_id, role); - return res.redirect('/join/'+game_id); + QUERY_PART_GAME.run(game_id, role); + update_join_clients_players(game_id); + res.send("SUCCESS"); } catch (err) { - req.flash('message', err.toString()); - return res.redirect('/join/'+game_id); + console.log(err); + res.send(err.toString()); } }); @@ -900,26 +967,27 @@ app.get('/start/:game_id', must_be_logged_in, function (req, res) { let game_id = req.params.game_id | 0; try { let game = QUERY_GAME_OWNER.get(game_id, req.user.user_id); - if (!game) { - req.flash('message', "Only the game owner can start the game!"); - return res.redirect('/join/'+game_id); - } - if (game.status != 0) { - req.flash('message', "The game is already started!"); - return res.redirect('/join/'+game_id); - } + if (!game) + return res.send("Only the game owner can start the game!"); + if (game.status != 0) + return res.send("The game is already started!"); let players = QUERY_PLAYERS.all(game_id); - if (game.random) + if (!RULES[game.title_id].ready(game.scenario, players)) + return res.send("Invalid player configuration!"); + if (game.random) { assign_random_roles(game, players); + update_join_clients_players(game_id); + } let state = RULES[game.title_id].setup(game.scenario, players); QUERY_START_GAME.run(JSON.stringify(state), state.active, game_id); let is_solo = players.every(p => p.user_id == players[0].user_id); if (is_solo) QUERY_UPDATE_GAME_SET_PRIVATE.run(game_id); - return res.redirect('/join/'+game_id); + update_join_clients_game(game_id); + res.send("SUCCESS"); } catch (err) { - req.flash('message', err.toString()); - return res.redirect('/join/'+game_id); + console.log(err); + res.send(err.toString()); } }); @@ -1131,6 +1199,7 @@ function put_game_state(game_id, state, old_active) { QUERY_UPDATE_GAME_STATE.run(JSON.stringify(state), state.active, status, result, game_id); for (let other of clients[game_id]) send_state(other, state); + update_join_clients_game(game_id); mail_your_turn_notification_to_offline_users(game_id, old_active, state.active); } diff --git a/views/create.ejs b/views/create.ejs index 5db8fe6..b995639 100644 --- a/views/create.ejs +++ b/views/create.ejs @@ -1,5 +1,5 @@ <%- include('header', { title: title.title_name }) %> - +

diff --git a/views/join.ejs b/views/join.ejs index 2da5f03..46c1e56 100644 --- a/views/join.ejs +++ b/views/join.ejs @@ -1,14 +1,23 @@ -<%- include('header', { title: game.title_name, refresh: game.status == 0 ? 15 : 0 }) %> +<%- include('header', { title: game.title_name }) -%> + + +

+

Owner: <%= game.owner_name %>
@@ -17,74 +26,22 @@ Private: <%= game.private ? "yes" : "no" %> Scenario: <%= game.scenario %>
Description: <%= game.description || "No description." %> +
+Status: +
+Result:
-

-<% - roles.forEach((role) => { - %><% }); %> + +<% roles.forEach((role) => { %><% }); %> -<% - roles.forEach((role) => { - if (game.active == role || game.active == "Both" || game.active == "All") { - %>
<%= role %><% - }); -%> +<% roles.forEach((role) => { %><%= role %>
-
<% - } else { - %><% - } - let p = players.find(p => p.role == role); - if (game.status == 0) { - if (p) { - if ((p.user_id == user.user_id) || (game.owner_id == user.user_id)) { - %> <% - %><%= p.name %><% - } else { - %><%= p.name %><% - } - } else { - %>Join<% - } - } else { - if (p) { - if (p.user_id == user.user_id) { - %>Play<% - } else { - %><%= p.name %><% - } - } else { - %>Empty<% - } - } - }); - if (game.status > 0 && !players.some(p => p.user_id == user.user_id)) { - %> -
View - <% - } -%> +-

-<% - if (game.status == 0) { - if (players.length == roles.length) { - if (game.owner_id == user.user_id) { - %>

<% - } else { - %>Waiting for <%= game.owner_name %> to start the game.<% - } - } else { - %>Waiting for players to join the game.<% - } - } - if (game.status == 2 && players.some(p => p.user_id == user.user_id)) { - %>
<% - } - if (game.owner_id == user.user_id && (game.status == 0 || solo)) { - %>


<% - } -%> + + -- cgit v1.2.3