From 35c3df0bf9209e79fb93875b1fc3e5afee032028 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sat, 23 Oct 2021 23:15:45 +0200 Subject: Add PRNG seed to game state. Log all game actions to a table so they can be replayed. --- server.js | 24 +++++++++++++++++++++--- tools/rerun.js | 24 ++++++++++++++++++++++++ tools/sql/schema.txt | 9 +++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tools/rerun.js diff --git a/server.js b/server.js index cc81a18..321ff44 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,10 @@ const SQLiteStore = require('./connect-better-sqlite3')(express_session); require('dotenv').config(); +function random_seed() { + return crypto.randomInt(1, 0x7ffffffe); +} + const SESSION_SECRET = "Caesar has a big head!"; const MAX_OPEN_GAMES = 5; @@ -922,7 +926,7 @@ app.get('/part/:game_id/:role', must_be_logged_in, function (req, res) { function assign_random_roles(game, players) { function pick_random_item(list) { - let k = Math.floor(Math.random() * list.length); + let k = crypto.randomInt(list.length); let r = list[k]; list.splice(k, 1); return r; @@ -952,7 +956,9 @@ app.get('/start/:game_id', must_be_logged_in, function (req, res) { assign_random_roles(game, players); update_join_clients_players(game_id); } - let state = RULES[game.title_id].setup(game.scenario, players); + let seed = random_seed(); + let state = RULES[game.title_id].setup(seed, game.scenario, players); + put_replay(game_id, null, 'setup', [seed, 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) @@ -1194,6 +1200,14 @@ function put_game_state(game_id, state, old_active) { mail_your_turn_notification_to_offline_users(game_id, old_active, state.active); } +const QUERY_INSERT_REPLAY = db.prepare("INSERT INTO replay ( game_id, time, role, action, arguments ) VALUES ( ?, datetime('now'), ?, ?, ? )"); + +function put_replay(game_id, role, action, args) { + if (args !== undefined && args !== null) + args = JSON.stringify(args); + QUERY_INSERT_REPLAY.run(game_id, role, action, args); +} + function on_action(socket, action, arg) { SLOG(socket, "--> ACTION", action, arg); try { @@ -1201,6 +1215,7 @@ function on_action(socket, action, arg) { let old_active = state.active; socket.rules.action(state, socket.role, action, arg); put_game_state(socket.game_id, state, old_active); + put_replay(socket.game_id, socket.role, action, arg); } catch (err) { console.log(err); return socket.emit('error', err.toString()); @@ -1214,6 +1229,7 @@ function on_resign(socket) { let old_active = state.active; socket.rules.resign(state, socket.role); put_game_state(socket.game_id, state, old_active); + put_replay(socket.game_id, socket.role, 'resign', null); } catch (err) { console.log(err); return socket.emit('error', err.toString()); @@ -1369,7 +1385,9 @@ io.on('connection', (socket) => { socket.on('restore', (state) => on_restore(socket, state)); socket.on('restart', (scenario) => { try { - let state = socket.rules.setup(scenario, players); + let seed = random_seed(); + let state = socket.rules.setup(seed, scenario, players); + put_replay(socket.game_id, null, 'setup', [seed, scenario, players]); for (let other of clients[socket.game_id]) { other.log_length = 0; send_state(other, state); diff --git a/tools/rerun.js b/tools/rerun.js new file mode 100644 index 0000000..d1534e6 --- /dev/null +++ b/tools/rerun.js @@ -0,0 +1,24 @@ +const sqlite3 = require('better-sqlite3'); + +let db = new sqlite3("./db"); +let game_id = process.argv[2] | 0; +let title_id = db.prepare("SELECT title_id FROM games WHERE game_id = ?").pluck().get(game_id); +let rules = require("./public/" + title_id + "/rules.js"); + +console.log("// TITLE", title_id) +let log = db.prepare("SELECT * FROM game_log WHERE game_id = ?").all(game_id); +let game = null; +log.forEach(item => { + let args = JSON.parse(item.arguments); + if (item.action === 'setup') { + console.log("// SETUP", item.arguments) + game = rules.setup(args[0], args[1], args[2]); + } else if (item.action === 'resign') { + console.log("// RESIGN", item.role); + game = rules.resign(game, item.role); + } else { + console.log("// ACTION", item.role, item.action, item.arguments); + game = rules.action(game, item.role, item.action, args); + } + console.log(JSON.stringify(game)); +}); diff --git a/tools/sql/schema.txt b/tools/sql/schema.txt index e731c8c..1623ec3 100644 --- a/tools/sql/schema.txt +++ b/tools/sql/schema.txt @@ -55,6 +55,14 @@ CREATE TABLE IF NOT EXISTS games ( state TEXT ); +CREATE TABLE IF NOT EXISTS replay ( + game_id INTEGER, + time TIMESTAMP, + role TEXT, + action TEXT, + arguments TEXT +); + CREATE TABLE IF NOT EXISTS chats ( game_id INTEGER PRIMARY KEY, time TIMESTAMP, @@ -110,6 +118,7 @@ BEGIN DELETE FROM players WHERE game_id = old.game_id; DELETE FROM notifications WHERE game_id = old.game_id; DELETE FROM chats WHERE game_id = old.game_id; + DELETE FROM replay WHERE game_id = old.game_id; END; DROP VIEW IF EXISTS player_view; -- cgit v1.2.3