summaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
Diffstat (limited to 'public')
-rw-r--r--public/common/client.js702
-rw-r--r--public/common/grid.css17
-rw-r--r--public/join.js5
3 files changed, 484 insertions, 240 deletions
diff --git a/public/common/client.js b/public/common/client.js
index 01b6c05..d9a1f48 100644
--- a/public/common/client.js
+++ b/public/common/client.js
@@ -1,28 +1,25 @@
"use strict";
-/* global io, on_update */
+/* URL: /$title_id/(re)play:$game_id:$role */
-/* URL: /$title_id/play:$game_id:$role */
-if (!/\/[\w-]+\/play:\d+(:[\w-]+)?/.test(window.location.pathname)) {
+if (!/\/[\w-]+\/(re)?play:\d+(:[\w-]+)?/.test(window.location.pathname)) {
document.getElementById("prompt").textContent = "Invalid game ID.";
throw Error("Invalid game ID.");
}
-const param_title_id = window.location.pathname.split("/")[1];
-const param_game_id = decodeURIComponent(window.location.pathname.split("/")[2]).split(":")[1] | 0;
-const param_role = decodeURIComponent(window.location.pathname.split("/")[2]).split(":")[2] || "Observer";
+let params = {
+ mode: window.location.pathname.split("/")[2].split(":")[0],
+ title_id: window.location.pathname.split("/")[1],
+ game_id: decodeURIComponent(window.location.pathname.split("/")[2]).split(":")[1] | 0,
+ role: decodeURIComponent(window.location.pathname.split("/")[2]).split(":")[2] || "Observer",
+}
-let game = null;
-let game_over = false;
-let player = null;
-let socket = null;
+let roles = Array.from(document.querySelectorAll(".role")).map(x=>({id:x.id,role:x.id.replace(/^role_/,"").replace(/_/g," ")}));
-let chat_is_visible = false;
-let chat_text = null;
-let chat_key = null;
-let chat_last_day = null;
-let chat_log = 0;
-let chat_seen = 0;
+let view = null;
+let player = "Observer";
+let socket = null;
+let chat = null;
function scroll_with_middle_mouse(panel_sel, multiplier) {
let panel = document.querySelector(panel_sel);
@@ -35,8 +32,8 @@ function scroll_with_middle_mouse(panel_sel, multiplier) {
down_y = e.clientY;
scroll_x = panel.scrollLeft;
scroll_y = panel.scrollTop;
- window.addEventListener('mousemove', mm);
- window.addEventListener('mouseup', mu);
+ window.addEventListener("mousemove", mm);
+ window.addEventListener("mouseup", mu);
e.preventDefault();
}
}
@@ -49,12 +46,12 @@ function scroll_with_middle_mouse(panel_sel, multiplier) {
}
function mu(e) {
if (e.button === 1) {
- window.removeEventListener('mousemove', mm);
- window.removeEventListener('mouseup', mu);
+ window.removeEventListener("mousemove", mm);
+ window.removeEventListener("mouseup", mu);
e.preventDefault();
}
}
- panel.addEventListener('mousedown', md);
+ panel.addEventListener("mousedown", md);
}
function drag_element_with_mouse(element_sel, grabber_sel) {
@@ -65,8 +62,8 @@ function drag_element_with_mouse(element_sel, grabber_sel) {
if (e.button === 0) {
save_x = e.clientX;
save_y = e.clientY;
- window.addEventListener('mousemove', mm);
- window.addEventListener('mouseup', mu);
+ window.addEventListener("mousemove", mm);
+ window.addEventListener("mouseup", mu);
e.preventDefault();
}
}
@@ -81,12 +78,12 @@ function drag_element_with_mouse(element_sel, grabber_sel) {
}
function mu(e) {
if (e.button === 0) {
- window.removeEventListener('mousemove', mm);
- window.removeEventListener('mouseup', mu);
+ window.removeEventListener("mousemove", mm);
+ window.removeEventListener("mouseup", mu);
e.preventDefault();
}
}
- grabber.addEventListener('mousedown', md);
+ grabber.addEventListener("mousedown", md);
}
/* TITLE BLINKER */
@@ -115,7 +112,18 @@ function stop_blinker() {
window.addEventListener("focus", stop_blinker);
-function load_chat() {
+/* CHAT */
+
+function init_chat() {
+ // only fetch new messages when we reconnect!
+ if (chat !== null) {
+ console.log("RECONNECT CHAT");
+ socket.emit("getchat", chat.log);
+ return;
+ }
+
+ console.log("CONNECT CHAT");
+
let chat_window = document.createElement("div");
chat_window.id = "chat_window";
chat_window.innerHTML = `
@@ -132,15 +140,50 @@ function load_chat() {
chat_button.addEventListener("click", toggle_chat);
document.querySelector("header").insertBefore(chat_button, document.getElementById("prompt"));
- chat_key = "chat/" + param_game_id;
- chat_text = document.getElementById("chat_text");
- chat_last_day = null;
- chat_log = 0;
- chat_seen = window.localStorage.getItem(chat_key) | 0;
+ chat = {
+ is_visible: false,
+ text_element: document.getElementById("chat_text"),
+ key: "chat/" + params.game_id,
+ last_day: null,
+ log: 0
+ }
+
+ chat.seen = window.localStorage.getItem(chat.key) | 0;
+
+ drag_element_with_mouse("#chat_window", "#chat_header");
+
+ document.getElementById("chat_form").addEventListener("submit", e => {
+ let input = document.getElementById("chat_input");
+ e.preventDefault();
+ if (input.value) {
+ socket.emit("chat", input.value);
+ input.value = "";
+ } else {
+ hide_chat();
+ }
+ });
+
+ document.querySelector("body").addEventListener("keydown", e => {
+ if (e.key === "Escape") {
+ if (chat.is_visible) {
+ e.preventDefault();
+ hide_chat();
+ }
+ }
+ if (e.key === "Enter") {
+ let input = document.getElementById("chat_input");
+ if (document.activeElement !== input) {
+ e.preventDefault();
+ show_chat();
+ }
+ }
+ });
+
+ socket.emit("getchat", 0);
}
function save_chat() {
- window.localStorage.setItem(chat_key, chat_log);
+ window.localStorage.setItem(chat.key, chat.log);
}
function update_chat(chat_id, utc_date, user, message) {
@@ -155,38 +198,70 @@ function update_chat(chat_id, utc_date, user, message) {
let line = document.createElement("div");
line.className = "date";
line.textContent = "~ " + date + " ~";
- chat_text.appendChild(line);
+ chat.text_element.appendChild(line);
}
function add_chat_line(time, user, message) {
let line = document.createElement("div");
line.textContent = "[" + time + "] " + user + " \xbb " + message;
- chat_text.appendChild(line);
- chat_text.scrollTop = chat_text.scrollHeight;
+ chat.text_element.appendChild(line);
+ chat.text_element.scrollTop = chat.text_element.scrollHeight;
}
- if (chat_id > chat_log) {
- chat_log = chat_id;
+ if (chat_id > chat.log) {
+ chat.log = chat_id;
let date = new Date(utc_date + "Z");
let day = date.toDateString();
- if (day !== chat_last_day) {
+ if (day !== chat.last_day) {
add_date_line(day);
- chat_last_day = day;
+ chat.last_day = day;
}
add_chat_line(format_time(date), user, message);
}
- if (chat_id > chat_seen) {
+ if (chat_id > chat.seen) {
let button = document.getElementById("chat_button");
start_blinker("NEW MESSAGE");
- if (!chat_is_visible)
+ if (!chat.is_visible)
button.classList.add("new");
else
save_chat();
}
}
-function init_client(roles) {
- game = null;
- player = null;
+function show_chat() {
+ if (!chat.is_visible) {
+ document.getElementById("chat_button").classList.remove("new");
+ document.getElementById("chat_window").classList.add("show");
+ document.getElementById("chat_input").focus();
+ chat.is_visible = true;
+ save_chat();
+ }
+}
+
+function hide_chat() {
+ if (chat.is_visible) {
+ document.getElementById("chat_window").classList.remove("show");
+ document.getElementById("chat_input").blur();
+ chat.is_visible = false;
+ }
+}
+
+function toggle_chat() {
+ if (chat.is_visible)
+ hide_chat();
+ else
+ show_chat();
+}
+
+/* CONNECT TO GAME SERVER */
+
+function init_player_names(players) {
+ for (let i = 0; i < roles.length; ++i) {
+ let sel = "#" + roles[i].id + " .role_user";
+ let p = players.find(p => p.role === roles[i].role);
+ document.querySelector(sel).textContent = p ? p.name : "NONE";
+ }
+}
+function init_play_client() {
const ROLE_SEL = [
".role.one",
".role.two",
@@ -197,162 +272,119 @@ function init_client(roles) {
".role.seven",
];
- const USER_SEL = [
- ".role.one .role_user",
- ".role.two .role_user",
- ".role.three .role_user",
- ".role.four .role_user",
- ".role.five .role_user",
- ".role.six .role_user",
- ".role.seven .role_user",
- ];
-
- load_chat();
-
- console.log("JOINING", param_title_id + "/" + param_game_id + "/" + param_role);
+ console.log("JOINING", params.title_id + "/" + params.game_id + "/" + params.role);
socket = io({
- transports: ['websocket'],
- query: { title: param_title_id, game: param_game_id, role: param_role },
+ transports: ["websocket"],
+ query: { title: params.title_id, game: params.game_id, role: params.role },
});
- socket.on('connect', () => {
+ socket.on("connect", () => {
console.log("CONNECTED");
- document.querySelector("header").classList.remove('disconnected');
- socket.emit('getchat', chat_log); // only send new messages when we reconnect!
+ document.querySelector("header").classList.remove("disconnected");
});
- socket.on('disconnect', () => {
+ socket.on("disconnect", () => {
console.log("DISCONNECTED");
document.getElementById("prompt").textContent = "Disconnected from server!";
- document.querySelector("header").classList.add('disconnected');
+ document.querySelector("header").classList.add("disconnected");
});
- socket.on('roles', (me, players) => {
- console.log("ROLES", me, JSON.stringify(players));
- player = me.replace(/ /g, '_');
- if (player === "Observer")
- document.getElementById("chat_button").style.display = "none";
- document.querySelector("body").classList.add(player);
- for (let i = 0; i < roles.length; ++i) {
- let pr = players.find(p => p.role === roles[i]);
- document.querySelector(USER_SEL[i]).textContent = pr ? pr.name : "NONE";
- }
+ socket.on("roles", (me, players) => {
+ console.log("PLAYERS", me, JSON.stringify(players));
+ player = me;
+ document.querySelector("body").classList.add(player.replace(/ /g, "_"));
+ if (player !== "Observer")
+ init_chat();
+ init_player_names(players);
});
- socket.on('presence', (presence) => {
+ socket.on("presence", (presence) => {
console.log("PRESENCE", JSON.stringify(presence));
for (let i = 0; i < roles.length; ++i) {
- let elt = document.querySelector(ROLE_SEL[i]);
- if (roles[i] in presence)
- elt.classList.add('present');
+ let elt = document.getElementById(roles[i].id);
+ if (roles[i].role in presence)
+ elt.classList.add("present");
else
- elt.classList.remove('present');
+ elt.classList.remove("present");
}
});
- socket.on('state', (new_game, new_game_over) => {
- console.log("STATE", !!new_game.actions, new_game_over);
- game = new_game;
- game_over = new_game_over;
- on_update_bar();
+ socket.on("state", (new_view) => {
+ console.log("STATE");
+ view = new_view;
+ on_update_header();
on_update();
- on_game_over();
on_update_log();
});
- socket.on('save', (msg) => {
+ socket.on("save", (msg) => {
console.log("SAVE");
- window.localStorage[param_title_id + '/save'] = msg;
+ window.localStorage[params.title_id + "/save"] = msg;
});
- socket.on('error', (msg) => {
+ socket.on("error", (msg) => {
console.log("ERROR", msg);
document.getElementById("prompt").textContent = msg;
});
- socket.on('chat', function (item) {
- console.log("CHAT", JSON.stringify(item));
+ socket.on("chat", function (item) {
update_chat(item[0], item[1], item[2], item[3]);
});
-
- document.getElementById("chat_form").addEventListener("submit", e => {
- let input = document.getElementById("chat_input");
- e.preventDefault();
- if (input.value) {
- socket.emit('chat', input.value);
- input.value = '';
- } else {
- hide_chat();
- }
- });
-
- document.querySelector("body").addEventListener("keydown", e => {
- if (player && player !== "Observer") {
- if (e.key === "Escape") {
- if (chat_is_visible) {
- e.preventDefault();
- hide_chat();
- }
- }
- if (e.key === "Enter") {
- let input = document.getElementById("chat_input");
- if (document.activeElement !== input) {
- e.preventDefault();
- show_chat();
- }
- }
- }
- });
-
- drag_element_with_mouse("#chat_window", "#chat_header");
}
+/* HEADER */
+
let is_your_turn = false;
let old_active = null;
-function on_update_bar() {
- document.getElementById("prompt").textContent = game.prompt;
- if (game.actions) {
+function on_update_header() {
+ document.getElementById("prompt").textContent = view.prompt;
+ if (params.mode === "replay")
+ return;
+ if (view.actions) {
document.querySelector("header").classList.add("your_turn");
- if (!is_your_turn || old_active !== game.active)
+ if (!is_your_turn || old_active !== view.active)
start_blinker("YOUR TURN");
is_your_turn = true;
} else {
document.querySelector("header").classList.remove("your_turn");
is_your_turn = false;
}
- old_active = game.active;
+ old_active = view.active;
}
+/* LOG */
+
let create_log_entry = function (text) {
- let p = document.createElement("div");
- p.textContent = text;
- return p;
+ let div = document.createElement("div");
+ div.textContent = text;
+ return div;
}
-let log_scroller = document.getElementById("log");
-
function on_update_log() {
- let parent = document.getElementById("log");
- let to_delete = parent.children.length - game.log_start;
+ let div = document.getElementById("log");
+ let to_delete = div.children.length - view.log_start;
while (to_delete-- > 0)
- parent.removeChild(parent.lastChild);
- for (let entry of game.log)
- parent.appendChild(create_log_entry(entry));
- log_scroller.scrollTop = log_scroller.scrollHeight;
+ div.removeChild(div.lastChild);
+ for (let entry of view.log)
+ div.appendChild(create_log_entry(entry));
+ scroll_log_to_end();
+}
+
+function scroll_log_to_end() {
+ let div = document.getElementById("log");
+ div.scrollTop = div.scrollHeight;
}
try {
- new ResizeObserver(entries => {
- log_scroller.scrollTop = log_scroller.scrollHeight;
- }).observe(log_scroller);
+ new ResizeObserver(scroll_log_to_end).observe(document.getElementById("log"));
} catch (err) {
- window.addEventListener("resize", evt => {
- log_scroller.scrollTop = log_scroller.scrollHeight;
- });
+ window.addEventListener("resize", scroll_log_to_end);
}
+/* MAP ZOOM */
+
function toggle_fullscreen() {
if (document.fullscreen)
document.exitFullscreen();
@@ -360,78 +392,60 @@ function toggle_fullscreen() {
document.documentElement.requestFullscreen();
}
-function show_chat() {
- if (!chat_is_visible) {
- document.getElementById("chat_button").classList.remove("new");
- document.getElementById("chat_window").classList.add("show");
- document.getElementById("chat_input").focus();
- chat_is_visible = true;
- save_chat();
- }
+function toggle_log() {
+ document.querySelector("aside").classList.toggle("hide");
+ zoom_map();
}
-function hide_chat() {
- if (chat_is_visible) {
- document.getElementById("chat_window").classList.remove("show");
- document.getElementById("chat_input").blur();
- chat_is_visible = false;
+function toggle_zoom() {
+ let mapwrap = document.getElementById("mapwrap");
+ if (mapwrap) {
+ mapwrap.classList.toggle("fit");
+ zoom_map();
}
}
-function toggle_chat() {
- if (chat_is_visible)
- hide_chat();
- else
- show_chat();
-}
-
function zoom_map() {
- let grid = document.querySelector("main");
let mapwrap = document.getElementById("mapwrap");
- let map = document.getElementById("map");
- map.style.transform = null;
- mapwrap.style.width = null;
- mapwrap.style.height = null;
- if (mapwrap.classList.contains("fit")) {
- let { width: gw, height: gh } = grid.getBoundingClientRect();
- let { width: ww, height: wh } = mapwrap.getBoundingClientRect();
- let { width: cw, height: ch } = map.getBoundingClientRect();
- let scale = Math.min(ww / cw, gh / ch);
- if (scale < 1) {
- map.style.transform = "scale(" + scale + ")";
- mapwrap.style.width = (cw * scale) + "px";
- mapwrap.style.height = (ch * scale) + "px";
+ if (mapwrap) {
+ let main = document.querySelector("main");
+ let map = document.getElementById("map");
+ map.style.transform = null;
+ mapwrap.style.width = null;
+ mapwrap.style.height = null;
+ if (mapwrap.classList.contains("fit")) {
+ let { width: gw, height: gh } = main.getBoundingClientRect();
+ let { width: ww, height: wh } = mapwrap.getBoundingClientRect();
+ let { width: cw, height: ch } = map.getBoundingClientRect();
+ let scale = Math.min(ww / cw, gh / ch);
+ if (scale < 1) {
+ map.style.transform = "scale(" + scale + ")";
+ mapwrap.style.width = (cw * scale) + "px";
+ mapwrap.style.height = (ch * scale) + "px";
+ }
}
}
}
-function toggle_zoom() {
- document.getElementById("mapwrap").classList.toggle('fit');
- zoom_map();
-}
+zoom_map();
-function init_map_zoom() {
- window.addEventListener('resize', zoom_map);
- zoom_map();
-}
+window.addEventListener("resize", zoom_map);
-function init_shift_zoom() {
- window.addEventListener("keydown", (evt) => {
- if (evt.key === "Shift")
- document.querySelector("body").classList.add("shift");
- });
- window.addEventListener("keyup", (evt) => {
- if (evt.key === "Shift")
- document.querySelector("body").classList.remove("shift");
- });
-}
+window.addEventListener("keydown", (evt) => {
+ if (evt.key === "Shift")
+ document.querySelector("body").classList.add("shift");
+});
-function toggle_log() {
- document.querySelector("aside").classList.toggle("hide");
- zoom_map();
-}
+window.addEventListener("keyup", (evt) => {
+ if (evt.key === "Shift")
+ document.querySelector("body").classList.remove("shift");
+});
+
+/* ACTIONS */
function action_button(action, label) {
+ if (params.mode === "replay")
+ return;
let id = action + "_button";
let button = document.getElementById(id);
if (!button) {
@@ -441,11 +455,11 @@ function action_button(action, label) {
button.addEventListener("click", evt => send_action(action));
document.getElementById("actions").appendChild(button);
}
- if (game.actions && action in game.actions) {
+ if (view.actions && action in view.actions) {
button.classList.remove("hide");
- if (game.actions[action]) {
+ if (view.actions[action]) {
if (label === undefined)
- button.textContent = game.actions[action];
+ button.textContent = view.actions[action];
button.disabled = false;
} else {
button.disabled = true;
@@ -455,66 +469,282 @@ function action_button(action, label) {
}
}
-function confirm_resign() {
- if (window.confirm("Are you sure that you want to resign?"))
- socket.emit('resign');
-}
-
function send_action(verb, noun) {
+ if (params.mode === "replay")
+ return;
// Reset action list here so we don't send more than one action per server prompt!
if (noun !== undefined) {
- if (game.actions && game.actions[verb] && game.actions[verb].includes(noun)) {
- game.actions = null;
+ if (view.actions && view.actions[verb] && view.actions[verb].includes(noun)) {
+ view.actions = null;
console.log("ACTION", verb, JSON.stringify(noun));
- socket.emit('action', verb, noun);
+ socket.emit("action", verb, noun);
return true;
}
} else {
- if (game.actions && game.actions[verb]) {
- game.actions = null;
+ if (view.actions && view.actions[verb]) {
+ view.actions = null;
console.log("ACTION", verb);
- socket.emit('action', verb);
+ socket.emit("action", verb);
return true;
}
}
return false;
}
+function confirm_resign() {
+ if (window.confirm("Are you sure that you want to resign?"))
+ socket.emit("resign");
+}
+
+/* DEBUGGING */
+
function send_save() {
- socket.emit('save');
+ socket.emit("save");
}
function send_restore() {
- socket.emit('restore', window.localStorage[param_title_id + '/save']);
+ socket.emit("restore", window.localStorage[params.title_id + "/save"]);
}
function send_restart(scenario) {
- socket.emit('restart', scenario);
+ socket.emit("restart", scenario);
}
-function on_game_over() {
- if (player) {
- let exit_button = document.getElementById("exit_button");
- if (exit_button) {
- if (game_over || player === "Observer")
- exit_button.classList.remove("hide");
- else
- exit_button.classList.add("hide");
+/* REPLAY */
+
+function adler32(data) {
+ let a = 1, b = 0;
+ for (let i = 0, n = data.length; i < n; ++i) {
+ a = (a + data.charCodeAt(i)) % 65521;
+ b = (b + a) % 65521;
+ }
+ return (b << 16) | a;
+}
+
+async function require(path) {
+ let cache = {};
+
+ if (!path.endsWith(".js"))
+ path = path + ".js";
+ if (path.startsWith("./"))
+ path = path.substring(2);
+
+ console.log("REQUIRE", path);
+
+ let response = await fetch(path);
+ let source = await response.text();
+
+ for (let [_, subpath] of source.matchAll(/require\(['"]([^)]*)['"]\)/g))
+ if (cache[subpath] === undefined)
+ cache[subpath] = await require(subpath);
+
+ let module = { exports: {} };
+ Function("module", "exports", "require", source)
+ (module, module.exports, path => cache[path]);
+ return module.exports;
+}
+
+let replay = null;
+
+async function init_replay_client() {
+ document.getElementById("prompt").textContent = "Loading replay...";
+ document.querySelector("body").classList.add("replay");
+
+ console.log("LOADING RULES");
+ let rules = await require("rules.js");
+
+ console.log("LOADING REPLAY");
+ let response = await fetch("/replay/" + params.game_id);
+ let body = await response.json();
+ replay = body.replay;
+
+ init_player_names(body.players);
+
+ let viewpoint = "Observer";
+ let log_length = 0;
+ let p = 0;
+ let s = {};
+
+ function eval_action(item) {
+ switch (item.action) {
+ case "setup":
+ s = rules.setup(item.arguments[0], item.arguments[1], item.arguments[2]);
+ break;
+ case "resign":
+ s = rules.resign(s, item.role);
+ break;
+ default:
+ s = rules.action(s, item.role, item.action, item.arguments);
+ break;
}
- let rematch_button = document.getElementById("rematch_button");
- if (rematch_button) {
- if (game_over && player !== "Observer")
- rematch_button.classList.remove("hide");
+ }
+
+ let ss;
+ for (p = 0; p < replay.length; ++p) {
+ replay[p].arguments = JSON.parse(replay[p].arguments);
+
+ if (rules.is_checkpoint) {
+ replay[p].is_checkpoint = (p > 0 && rules.is_checkpoint(ss, s));
+ ss = Object.assign({}, s);
+ }
+
+ eval_action(replay[p]);
+
+ replay[p].digest = adler32(JSON.stringify(s));
+ for (let k = p-1; k > 0; --k) {
+ if (replay[k].digest === replay[p].digest && !replay[k].is_undone) {
+ for (let a = k+1; a <= p; ++a)
+ if (!replay[a].is_undone)
+ replay[a].is_undone = true;
+ break;
+ }
+ }
+ }
+
+ replay = replay.filter(x => !x.is_undone);
+
+ function set_hash(n) {
+ history.replaceState(null, "", window.location.pathname + "#" + n);
+ }
+
+ let timer = 0;
+ function play_pause_replay(evt) {
+ if (timer === 0) {
+ evt.target.textContent = "Stop";
+ timer = setInterval(() => {
+ if (p < replay.length)
+ goto_replay(p+1);
+ else
+ play_pause_replay(evt);
+ }, 1000);
+ } else {
+ evt.target.textContent = "Run";
+ clearInterval(timer);
+ timer = 0;
+ }
+ }
+
+ function prev() {
+ for (let i = p - 1; i > 1; --i)
+ if (replay[i].is_checkpoint)
+ return i;
+ return 1;
+ }
+
+ function next() {
+ for (let i = p + 1; i < replay.length; ++i)
+ if (replay[i].is_checkpoint)
+ return i;
+ return replay.length;
+ }
+
+ function on_hash_change() {
+ goto_replay(parseInt(window.location.hash.slice(1)) || 1);
+ }
+
+ function goto_replay(np) {
+ if (np < 1)
+ np = 1;
+ if (np > replay.length)
+ np = replay.length;
+ set_hash(np);
+ if (p > np)
+ p = 0, s = {};
+ while (p < np)
+ eval_action(replay[p++]);
+ update_replay_view();
+ }
+
+ function update_replay_view() {
+ player = viewpoint;
+
+ if (viewpoint === "Active") {
+ player = s.active;
+ if (player === "All" || player === "Both" || !player)
+ player = "Observer";
+ }
+
+ let body = document.querySelector("body");
+ body.classList.remove("Observer");
+ for (let i = 0; i < roles.length; ++i)
+ body.classList.remove(roles[i].role.replace(/ /g, "_"));
+ body.classList.add(player.replace(/ /g, "_"));
+
+ view = rules.view(s, player);
+ view.actions = null;
+
+ if (viewpoint === "Observer")
+ view.game_over = 1;
+ if (s.state === "game_over")
+ view.game_over = 1;
+
+ if (replay.length > 0) {
+ if (document.querySelector("body").classList.contains("shift"))
+ view.prompt = `[${p}/${replay.length}] ${s.active} / ${s.state} / ${replay[p].action} ${replay[p].arguments}`;
else
- rematch_button.classList.add("hide");
+ view.prompt = "[" + p + "/" + replay.length + "] " + view.prompt;
}
+ if (log_length < view.log.length)
+ view.log_start = log_length;
+ else
+ view.log_start = view.log.length;
+ log_length = view.log.length;
+ view.log = view.log.slice(view.log_start);
+
+ on_update_header();
+ on_update();
+ on_update_log();
}
-}
-function send_rematch() {
- window.location = '/rematch/' + param_game_id + '/' + param_role;
-}
+ function replay_button(div, label, fn) {
+ let button = document.createElement("button");
+ button.addEventListener("click", fn);
+ button.className = "replay_button";
+ button.textContent = label;
+ div.appendChild(button);
+ return button;
+ }
-function send_exit() {
- window.location = '/info/' + param_title_id;
+ function set_viewpoint(vp) {
+ viewpoint = vp;
+ update_replay_view();
+ }
+
+ if (replay.length > 0) {
+ console.log("REPLAY READY");
+
+ let div = document.createElement("div");
+ div.className = "replay";
+ replay_button(div, "Active", () => set_viewpoint("Active"));
+ for (let r of roles)
+ replay_button(div, r.role, () => set_viewpoint(r.role));
+ replay_button(div, "Observer", () => set_viewpoint("Observer"));
+ document.querySelector("header").appendChild(div);
+
+ div = document.createElement("div");
+ div.className = "replay";
+ replay_button(div, "<<<", () => goto_replay(1));
+ replay_button(div, "<<", () => goto_replay(prev()));
+ replay_button(div, "<\xa0", () => goto_replay(p-1));
+ replay_button(div, "\xa0>", () => goto_replay(p+1));
+ replay_button(div, ">>", () => goto_replay(next()));
+ replay_button(div, "Run", play_pause_replay).style.width = "65px";
+ document.querySelector("header").appendChild(div);
+
+ if (window.location.hash === "")
+ set_hash(replay.length);
+
+ on_hash_change();
+
+ window.addEventListener("hashchange", on_hash_change);
+ } else {
+ console.log("REPLAY NOT AVAILABLE");
+ s = JSON.parse(body.state);
+ update_replay_view();
+ }
}
+
+if (params.mode === "play")
+ init_play_client();
+if (params.mode === "replay")
+ init_replay_client();
diff --git a/public/common/grid.css b/public/common/grid.css
index 0532648..228c0aa 100644
--- a/public/common/grid.css
+++ b/public/common/grid.css
@@ -25,14 +25,16 @@ body:not(.shift) .debug {
display: none;
}
-body.Observer .resign {
+body.Observer .resign, body.replay .resign {
display: none;
}
button {
- font-size: 1rem;
+ box-sizing: border-box;
+ font-size: 16px;
+ height: 28px;
margin: 0;
- padding: 1px 12px;
+ padding: 1px 12px 1px 12px;
background-color: gainsboro;
}
button:disabled {
@@ -177,6 +179,15 @@ header button {
margin: 0 10px;
}
+header .replay {
+ margin: 0 10px;
+ display: flex;
+}
+
+header button.replay_button {
+ margin: 0;
+}
+
#prompt {
margin: 0 20px;
font-size: 18px;
diff --git a/public/join.js b/public/join.js
index 24e10ab..eef3833 100644
--- a/public/join.js
+++ b/public/join.js
@@ -68,11 +68,14 @@ function start_event_source() {
});
evtsrc.addEventListener("deleted", function (evt) {
console.log("DELETED");
- window.location.href = '/info/' + game.title_id;
+ window.location.href = '/' + game.title_id;
});
evtsrc.onerror = function (err) {
window.message.innerHTML = "Disconnected from server...";
};
+ window.addEventListener('beforeunload', function (evt) {
+ evtsrc.close();
+ });
}
}