diff options
Diffstat (limited to 'public')
-rw-r--r-- | public/common/columbia.css (renamed from public/common/battle_abc.css) | 18 | ||||
-rw-r--r-- | public/common/play.css (renamed from public/common/grid.css) | 9 | ||||
-rw-r--r-- | public/common/play.js (renamed from public/common/client.js) | 245 | ||||
-rw-r--r-- | public/images/sherlock-holmes-mirror.svg | 1 |
4 files changed, 168 insertions, 105 deletions
diff --git a/public/common/battle_abc.css b/public/common/columbia.css index 3a211a1..1a646b6 100644 --- a/public/common/battle_abc.css +++ b/public/common/columbia.css @@ -1,4 +1,4 @@ -/* ABC BATTLE DISPLAY FOR COLUMBIA BLOCK GAMES */ +/* BATTLE DIALOG FOR COLUMBIA BLOCK GAMES */ #battle { position: absolute; @@ -11,18 +11,22 @@ box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.5); visibility: hidden; } + #battle.show { visibility: visible; } + #battle th { padding: 0; border: 1px solid black; } + #battle td { padding: 0; border: 1px solid black; vertical-align: top; } + #battle_header { font-weight: bold; height: 2em; @@ -31,12 +35,14 @@ user-select: none; cursor: move; } + #battle_message { font-weight: normal; height: 2em; line-height: 2em; padding: 0 8px; } + #FA, #FB, #FC, #FD, #FF, #FR, #EA, #EB, #EC, #ED, #EF, #ER { display: flex; flex-wrap: wrap; @@ -64,11 +70,6 @@ display: none; } -.battle_menu .action:hover { background-color: red; } -.battle_menu .action.retreat:hover { background-color: #eee; } -.battle_menu .action.withdraw:hover { background-color: #eee; } -.battle_menu .action.pass:hover { background-color: gray; } - .battle_menu_list { margin-top: 5px; min-height: 26px; @@ -82,6 +83,11 @@ display: none; } +.battle_menu .action:hover { background-color: red; } +.battle_menu .action.retreat:hover { background-color: #eee; } +.battle_menu .action.withdraw:hover { background-color: #eee; } +.battle_menu .action.pass:hover { background-color: gray; } + .battle_menu.fire .action.fire { display: inline; } .battle_menu.retreat .action.retreat { display: inline; } .battle_menu.pass .action.pass { display: inline; } diff --git a/public/common/grid.css b/public/common/play.css index 228c0aa..64e9202 100644 --- a/public/common/grid.css +++ b/public/common/play.css @@ -25,7 +25,7 @@ body:not(.shift) .debug { display: none; } -body.Observer .resign, body.replay .resign { +body.Observer .resign { display: none; } @@ -184,7 +184,7 @@ header .replay { display: flex; } -header button.replay_button { +header .replay button { margin: 0; } @@ -311,12 +311,11 @@ header button.replay_button { border: 1px solid black; background-color: white; box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.5); - display: none; - grid-template-rows: auto minmax(0, 1fr) auto; + visibility: hidden; } #chat_window.show { - display: grid; + visibility: visible; } #chat_header { diff --git a/public/common/client.js b/public/common/play.js index d9a1f48..ede3a5b 100644 --- a/public/common/client.js +++ b/public/common/play.js @@ -117,13 +117,10 @@ window.addEventListener("focus", stop_blinker); function init_chat() { // only fetch new messages when we reconnect! if (chat !== null) { - console.log("RECONNECT CHAT"); - socket.emit("getchat", chat.log); + send_message("getchat", chat.log); return; } - console.log("CONNECT CHAT"); - let chat_window = document.createElement("div"); chat_window.id = "chat_window"; chat_window.innerHTML = ` @@ -156,7 +153,7 @@ function init_chat() { let input = document.getElementById("chat_input"); e.preventDefault(); if (input.value) { - socket.emit("chat", input.value); + send_message("chat", input.value); input.value = ""; } else { hide_chat(); @@ -179,7 +176,7 @@ function init_chat() { } }); - socket.emit("getchat", 0); + send_message("getchat", 0); } function save_chat() { @@ -251,6 +248,38 @@ function toggle_chat() { show_chat(); } +/* REMATCH BUTTON */ + +function remove_resign_menu() { + document.querySelectorAll(".resign").forEach(x => x.remove()); +} + +function goto_rematch() { + window.location = "/rematch/" + params.game_id + "/" + params.role; +} + +function goto_replay() { + window.location = "/" + params.title_id + "/replay:" + params.game_id; +} + +function on_game_over() { + function icon_button(id, img, title, fn) { + if (!document.getElementById(id)) { + let button = document.createElement("div"); + button.id = id; + button.title = title; + button.className = "icon_button"; + button.innerHTML = '<img src="/images/' + img + '.svg">'; + button.addEventListener("click", fn); + document.querySelector("header").appendChild(button); + } + } + icon_button("replay_button", "sherlock-holmes-mirror", "Watch replay", goto_replay); + if (player !== "Observer") + icon_button("rematch_button", "cycle", "Propose a rematch!", goto_rematch); + remove_resign_menu(); +} + /* CONNECT TO GAME SERVER */ function init_player_names(players) { @@ -261,76 +290,102 @@ function init_player_names(players) { } } -function init_play_client() { - const ROLE_SEL = [ - ".role.one", - ".role.two", - ".role.three", - ".role.four", - ".role.five", - ".role.six", - ".role.seven", - ]; +function send_message(cmd, arg) { + let data = JSON.stringify([cmd, arg]); + console.log("SEND %s %s", cmd, arg); + socket.send(data); +} - console.log("JOINING", params.title_id + "/" + params.game_id + "/" + params.role); +let reconnect_count = 0; +let reconnect_max = 10; - socket = io({ - transports: ["websocket"], - query: { title: params.title_id, game: params.game_id, role: params.role }, - }); +function connect_play() { + if (reconnect_count >= reconnect_max) { + document.title = "DISCONNECTED"; + document.getElementById("prompt").textContent = "Disconnected."; + return; + } - socket.on("connect", () => { - console.log("CONNECTED"); - document.querySelector("header").classList.remove("disconnected"); - }); + let protocol = (window.location.protocol === "http:") ? "ws" : "wss"; + let seen = document.getElementById("log").children.length; + let url = `${protocol}://${window.location.host}/play-socket?title=${params.title_id}&game=${params.game_id}&role=${params.role}&seen=${seen}`; - socket.on("disconnect", () => { - console.log("DISCONNECTED"); - document.getElementById("prompt").textContent = "Disconnected from server!"; - document.querySelector("header").classList.add("disconnected"); - }); + console.log("CONNECTING", url); + document.getElementById("prompt").textContent = "Connecting... "; + + socket = new WebSocket(url); - 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); + window.addEventListener('beforeunload', function () { + socket.close(1000); }); - socket.on("presence", (presence) => { - console.log("PRESENCE", JSON.stringify(presence)); - for (let i = 0; i < roles.length; ++i) { - let elt = document.getElementById(roles[i].id); - if (roles[i].role in presence) - elt.classList.add("present"); - else - elt.classList.remove("present"); + socket.onopen = function (evt) { + console.log("OPEN"); + document.querySelector("header").classList.remove("disconnected"); + reconnect_count = 0; + } + + socket.onclose = function (evt) { + console.log("CLOSE %d", evt.code); + if (evt.code === 1000 && evt.reason !== "") { + document.getElementById("prompt").textContent = "Disconnected: " + evt.reason; + document.title = "DISCONNECTED"; } - }); + if (evt.code !== 1000) { + document.querySelector("header").classList.add("disconnected"); + document.getElementById("prompt").textContent = `Reconnecting soon... (${reconnect_count+1}/${reconnect_max})`; + let wait = 1000 * (Math.random() + 0.5) * Math.pow(2, reconnect_count++); + console.log("WAITING %.1f TO RECONNECT", wait/1000); + setTimeout(connect_play, wait); + } + } - socket.on("state", (new_view) => { - console.log("STATE"); - view = new_view; - on_update_header(); - on_update(); - on_update_log(); - }); + socket.onmessage = function (evt) { + let [ cmd, arg ] = JSON.parse(evt.data); + console.log("MESSAGE %s", cmd); + switch (cmd) { + case 'error': + document.getElementById("prompt").textContent = arg; + break; - socket.on("save", (msg) => { - console.log("SAVE"); - window.localStorage[params.title_id + "/save"] = msg; - }); + case 'chat': + update_chat(arg[0], arg[1], arg[2], arg[3]); + break; - socket.on("error", (msg) => { - console.log("ERROR", msg); - document.getElementById("prompt").textContent = msg; - }); + case 'players': + player = arg[0]; + document.querySelector("body").classList.add(player.replace(/ /g, "_")); + if (player !== "Observer") + init_chat(); + else + remove_resign_menu(); + init_player_names(arg[1]); + break; - socket.on("chat", function (item) { - update_chat(item[0], item[1], item[2], item[3]); - }); + case 'presence': + for (let i = 0; i < roles.length; ++i) { + let elt = document.getElementById(roles[i].id); + if (roles[i].role in arg) + elt.classList.add("present"); + else + elt.classList.remove("present"); + } + break; + + case 'state': + view = arg; + on_update_header(); + on_update(); + on_update_log(); + if (view.game_over) + on_game_over(); + break; + + case 'save': + window.localStorage[params.title_id + "/save"] = msg; + break; + } + } } /* HEADER */ @@ -474,17 +529,16 @@ function send_action(verb, noun) { return; // Reset action list here so we don't send more than one action per server prompt! if (noun !== undefined) { - if (view.actions && view.actions[verb] && view.actions[verb].includes(noun)) { + let realnoun = Array.isArray(noun) ? noun[0] : noun; + if (view.actions && view.actions[verb] && view.actions[verb].includes(realnoun)) { view.actions = null; - console.log("ACTION", verb, JSON.stringify(noun)); - socket.emit("action", verb, noun); + send_message("action", [verb, noun]); return true; } } else { if (view.actions && view.actions[verb]) { view.actions = null; - console.log("ACTION", verb); - socket.emit("action", verb); + send_message("action", [verb]); return true; } } @@ -493,21 +547,21 @@ function send_action(verb, noun) { function confirm_resign() { if (window.confirm("Are you sure that you want to resign?")) - socket.emit("resign"); + send_message("resign"); } /* DEBUGGING */ function send_save() { - socket.emit("save"); + send_message("save"); } function send_restore() { - socket.emit("restore", window.localStorage[params.title_id + "/save"]); + send_message("restore", window.localStorage[params.title_id + "/save"]); } function send_restart(scenario) { - socket.emit("restart", scenario); + send_message("restart", scenario); } /* REPLAY */ @@ -546,9 +600,10 @@ async function require(path) { let replay = null; -async function init_replay_client() { +async function init_replay() { + remove_resign_menu(); + document.getElementById("prompt").textContent = "Loading replay..."; - document.querySelector("body").classList.add("replay"); console.log("LOADING RULES"); let rules = await require("rules.js"); @@ -660,7 +715,7 @@ async function init_replay_client() { if (viewpoint === "Active") { player = s.active; - if (player === "All" || player === "Both" || !player) + if (player === "All" || player === "Both" || player === "None" || !player) player = "Observer"; } @@ -679,10 +734,13 @@ async function init_replay_client() { 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 + if (document.querySelector("body").classList.contains("shift")) { + view.prompt = `[${p}/${replay.length}] ${s.active} / ${s.state}`; + if (p < replay.length) + view.prompt += ` / ${replay[p].action} ${replay[p].arguments}`; + } else { view.prompt = "[" + p + "/" + replay.length + "] " + view.prompt; + } } if (log_length < view.log.length) view.log_start = log_length; @@ -696,11 +754,10 @@ async function init_replay_client() { on_update_log(); } - function replay_button(div, label, fn) { + function text_button(div, txt, fn) { let button = document.createElement("button"); button.addEventListener("click", fn); - button.className = "replay_button"; - button.textContent = label; + button.textContent = txt; div.appendChild(button); return button; } @@ -715,20 +772,20 @@ async function init_replay_client() { let div = document.createElement("div"); div.className = "replay"; - replay_button(div, "Active", () => set_viewpoint("Active")); + text_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")); + text_button(div, r.role, () => set_viewpoint(r.role)); + text_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"; + text_button(div, "<<<", () => goto_replay(1)); + text_button(div, "<<", () => goto_replay(prev())); + text_button(div, "<\xa0", () => goto_replay(p-1)); + text_button(div, "\xa0>", () => goto_replay(p+1)); + text_button(div, ">>", () => goto_replay(next())); + text_button(div, "Run", play_pause_replay).style.width = "65px"; document.querySelector("header").appendChild(div); if (window.location.hash === "") @@ -744,7 +801,7 @@ async function init_replay_client() { } } -if (params.mode === "play") - init_play_client(); if (params.mode === "replay") - init_replay_client(); + init_replay(); +if (params.mode === "play") + connect_play(); diff --git a/public/images/sherlock-holmes-mirror.svg b/public/images/sherlock-holmes-mirror.svg new file mode 100644 index 0000000..f2f0f19 --- /dev/null +++ b/public/images/sherlock-holmes-mirror.svg @@ -0,0 +1 @@ +<svg style="height:512px;width:512px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M326.582 36.882s24.67 5.747 35.756 15.557c38.586 34.149 51.256 67.652 53.746 105.53l43.97 47.023c-96.221-17.906-207.672-21.92-340.581-20.912 21.002-14.144 41.37-25.753 59.322-36.814 14.473-35.496 33.701-76.597 65.934-95.373 14.145-8.24 29.144-14.024 43.962-16.83 10.736-20.307 35.4-11.819 37.89 1.82zM407.023 214.8c-20.328 40.62-56.635 79.575-89.761 103.012-18.256 12.63-36.742 21.653-51.035 24.144-11.946 2.242-23.46-1.416-28.825-10.672l-.002-.004v-.002c-8.103-14.299-14.714-28.724-20.359-43.197-15.776 1.737-33.408 2.541-38.04-1.178-7.31-5.871 8.751-56.088 16.056-69.443C192 211.24 192 204.665 192 199.88c39.436-2.194 150.638 6.423 215.023 14.92zm-342.142 6.07c13.335.302 24.897 9.857 33.428 22.668 9.413 14.137 16.138 33.34 18.798 55.055 2.66 21.714.771 41.971-4.95 57.963-4.29 11.99-11.133 22.318-20.655 27.515l10.77 108.676-17.913 1.775-10.615-107.13c-12.015-1.592-22.443-10.62-30.299-22.418-9.413-14.137-16.136-33.339-18.797-55.053-2.66-21.714-.77-41.974 4.952-57.965 5.721-15.991 15.983-29.026 31.087-30.877a29.036 29.036 0 0 1 4.194-.209zm-2.004 18.076c-5.494.673-11.846 6.541-16.33 19.075-4.485 12.533-6.397 30.421-4.033 49.71 2.363 19.29 8.538 36.186 15.916 47.266 7.377 11.08 14.955 15.242 20.449 14.568 5.494-.673 11.844-6.54 16.328-19.074 4.485-12.533 6.396-30.42 4.033-49.709-2.363-19.289-8.536-36.187-15.914-47.267-5.011-6.164-12.69-15.168-20.449-14.569zm349.951 3.584c6.243 8.543 13.975 17.27 23.111 25.744 12.11 11.231 26.664 21.827 43.198 30.211-37.101 7.524-77.514 23.385-115.21 42.594-42.326 21.57-80.776 47.18-106.775 71.145l-2.314-24.686c11.388-12.634 23.526-23.83 35.869-34.252 12.152-5.001 24.557-12.203 36.797-20.672 36.371-25.164 70.878-60.916 85.324-90.084zM68.246 253.806c-.036 21.503-3.015 45.534-9.771 64.632-6.729-19.745-7.02-55.246 9.771-64.632zm137.598 54.43a353.014 353.014 0 0 0 7.74 16.574c-7.33 5.423-11.536 10.592-13.904 15.505-3.119 6.47-3.49 13.077-2.176 21.295 2.24 14.002 10.213 31.472 14.32 52.23 9.049 12.67 1.565 56.042-18.265 60.938-44.677 11.03-69.71-35.7-59.614-46.716 9.817-10.713 37.598-19.736 57.92-19.952-4.048-14.733-9.79-28.985-12.136-43.656-1.666-10.412-1.325-21.456 3.736-31.955 4.255-8.827 11.576-16.856 22.379-24.264zm272.875 8.81c4.751 7.296 9.937 15.484 15.281 24.413V494H238.848c2.921-14.12 5.609-28.7 8.763-42.629.65-5.674 5.269-9.093 9.059-13.314 21.957-24.459 66.328-55.92 115.432-80.942 35.16-17.917 72.94-32.625 106.617-40.07z"/></svg>
\ No newline at end of file |