]> git.cworth.org Git - lmno.games/commitdiff
Merge in empires-html client
authorCarl Worth <cworth@cworth.org>
Sun, 17 May 2020 14:53:51 +0000 (07:53 -0700)
committerCarl Worth <cworth@cworth.org>
Sun, 17 May 2020 14:53:51 +0000 (07:53 -0700)
This HTML client for the empires game was originally developed in a
separate repository (and at the top-level).

In order to create this commit, I first rewrote the history of the
empires-html repository with:

git-filter-repo --path-rename '':empires/

to move all of its content down into the empires sub-directory.

I then fetched things over to thi repository with:

git fetch ../empires-html-filtered
        git merge --allow-unrelated-histories FETCH_HEAD

Going forward, we can develop the empires client within a
sub-directory of the lmno.games content (so they can share common
resources such as reset.css and style.css, etc.).

empires/Makefile [new file with mode: 0644]
empires/empires-client.js [new file with mode: 0644]
empires/index.html [new file with mode: 0644]
empires/reset.css [new file with mode: 0644]
empires/style.css [new file with mode: 0644]

diff --git a/empires/Makefile b/empires/Makefile
new file mode 100644 (file)
index 0000000..f490fec
--- /dev/null
@@ -0,0 +1,11 @@
+PUBLISH_HOST=cworth.org
+PUBLISH_DIR=/srv/families.cworth.org/www
+
+DO_NOT_PUBLISH="Makefile"
+
+deploy:
+       rm -rf .deploy-source
+       git clone . .deploy-source
+       rm -rf .deploy-source/.git
+       (cd .deploy-source; rm $(DO_NOT_PUBLISH); rsync -avz ./ --delete --delete-after $(PUBLISH_HOST):$(PUBLISH_DIR) )
+       rm -rf .deploy-source
diff --git a/empires/empires-client.js b/empires/empires-client.js
new file mode 100644 (file)
index 0000000..a0df913
--- /dev/null
@@ -0,0 +1,146 @@
+const API = "https://families.cworth.org/api/";
+
+function undisplay(element) {
+  element.style.display="none";
+}
+
+function add_message(severity, message) {
+  message = `<div class="message ${severity}" onclick="undisplay(this)">
+${message}
+<span class="hide-button" onclick="undisplay(this.parentElement)">&times</span>
+</div>`;
+  const message_area = document.getElementById('message-area');
+  message_area.insertAdjacentHTML('beforeend', message);
+}
+
+function register(form) {
+  var request = new XMLHttpRequest();
+
+  request.open("POST", API + "register");
+  request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
+  var data = {
+    "name": form.name.value,
+    "character": form.character.value
+  };
+  request.send(JSON.stringify(data));
+
+  form.reset();
+}
+
+function toggle_host_tools() {
+  const host_tools = document.getElementById("host-tools");
+
+  console.log("Toggling, host_tools.style.display is '" + host_tools.style.display + "'");
+
+  if (host_tools.style.display === "block")
+    host_tools.style.display = "none";
+  else
+    host_tools.style.display = "block";
+}
+
+function post_reveal() {
+  const request = new XMLHttpRequest();
+
+  request.open("POST", API + "reveal");
+  request.send();
+}
+
+function post_start() {
+  const request = new XMLHttpRequest();
+
+  request.open("POST", API + "start");
+  request.send();
+}
+
+function post_reset() {
+  const request = new XMLHttpRequest();
+
+  request.open("POST", API + "reset");
+  request.send();
+}
+
+const events = new EventSource(API + "events");
+
+events.onerror = function(event) {
+  if (event.target.readyState === EventSource.CLOSED) {
+      add_message("danger", "Connection to server lost.");
+  }
+};
+
+events.addEventListener("players", function(event) {
+  const players_div = document.getElementById("players-div");
+  const players_element = document.getElementById("players");
+  const players = JSON.parse(event.data);
+
+  players_element.innerHTML = '';
+  for (const player of players) {
+    var li = document.createElement('li');
+    li.id = "player-" + player.id;
+    li.innerText = player.name;
+    players_element.appendChild(li);
+  }
+
+  /* Force players list to be visible. */
+  players_div.style.display = "block";
+});
+
+events.addEventListener("player-join", function(event) {
+  const players_div = document.getElementById("players-div");
+  const players = document.getElementById("players");
+  const player = JSON.parse(event.data);
+
+  const li = document.createElement('li');
+  li.id = "player-" + player.id;
+  li.innerText = player.name;
+  players.appendChild(li);
+
+  /* Force players list to be visible. */
+  players_div.style.display = "block";
+});
+
+events.addEventListener("player-leave", function(event) {
+  const players = document.getElementById("players");
+  const id = JSON.parse(event.data);
+  const player = document.getElementById("player-" + id.id);
+
+  add_message("info", player.innerText + " has left the game.");
+  players.removeChild(player);
+});
+
+events.addEventListener("game-state", function(event) {
+  const data = JSON.parse(event.data);
+  const old_state = data.old_state;
+  const new_state = data.new_state;
+
+  const hide_selector = ".show-state-" +old_state+ ",.hide-state-" +new_state;
+  const show_selector = ".hide-state-" +old_state+ ",.show-state-" +new_state;
+
+  /* Hide all elements based on the state transition. */
+  var elts = document.querySelectorAll(hide_selector);
+  for (const elt of elts) {
+    elt.style.display = "none";
+  }
+
+  /* And show all elements based on the same state transition. */
+  elts = document.querySelectorAll(show_selector);
+  for (const elt of elts) {
+    elt.style.display = "block";
+  }
+});
+
+events.addEventListener("character-reveal", function(event) {
+  const data = JSON.parse(event.data);
+  const character_name = data.character;
+
+  const character = document.getElementById("character-reveal");
+
+  character.innerText = character_name;
+});
+
+events.addEventListener("capture", function(event_string) {
+  const players = document.getElementById("players");
+  const event = JSON.parse(event_string.data);
+  const player = document.getElementById("player-" + event.captee);
+
+  players.removeChild(player);
+});
diff --git a/empires/index.html b/empires/index.html
new file mode 100644 (file)
index 0000000..b599404
--- /dev/null
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
+
+    <title>The Game of Empires</title>
+
+    <link rel="stylesheet" href="reset.css" type="text/css" />
+    <link rel="stylesheet" href="style.css" type="text/css" />
+  </head>
+<body>
+
+  <script src="empires-client.js"></script>
+
+<div id="page">
+
+  <span style="float:right;cursor:pointer" onclick="toggle_host_tools()">
+    ⚙
+  </span>
+
+  <div id="host-tools">
+    <button onclick="post_reveal()">
+      Reveal Characters
+    </button>
+    <button onclick="post_start()">
+      Start Game
+    </button>
+    <button onclick="post_reset()">
+      Reset Game
+    </button>
+  </div>
+
+  <div id="message-area">
+  </div>
+
+  <div id="loading" class="show-state-none">
+    <h1>The Game of Empires</h1>
+
+    <p>
+      Contacting server. Please wait...
+    </p>
+  </div>
+
+  <div id="pre-game" class="show-state-join">
+
+    <h1>The Game of Empires</h1>
+
+    <p>
+      To join the game, type your own name below. Also, choose the name
+      of a character that you want to play as. This can be anyone (real
+      or fictional) that everyone playing the game would be likely to
+      know, (for example "Albert Einstein" or "Harry Potter").
+    </p>
+
+    <p>
+      Note: After you have joined the game, another player can use this
+      same device to join the game as well.
+    </p>
+
+    <!-- The return false prevents the page from being reloaded -->
+    <form id="register-form" onsubmit="register(this); return false">
+      <div class="form-row medium left">
+        <label for="name">Your name</label>
+        <input type="text" id="name" required>
+      </div>
+
+      <div class="form-row medium right">
+        <label for="character">Character name</label>
+        <input type="text" id="character" autocomplete="off" required>
+      </div>
+
+      <div class="form-row large">
+        <button type="submit">
+          Join game
+        </button>
+      </div>
+    </form>
+
+  </div>
+
+  <div class="show-state-reveal">
+    <h1>Watch and memorize each character!</h1>
+
+    <span id="character-reveal"></span>
+  </div>
+
+  <div class="hide-state-reveal" id="players-div">
+    <h1>Players in the game</h1>
+
+    <ul id="players">
+    </ul>
+
+  </div>
+
+</div>
+</body>
+</html>
diff --git a/empires/reset.css b/empires/reset.css
new file mode 100644 (file)
index 0000000..e29c0f5
--- /dev/null
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/ 
+   v2.0 | 20110126
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+       margin: 0;
+       padding: 0;
+       border: 0;
+       font-size: 100%;
+       font: inherit;
+       vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section {
+       display: block;
+}
+body {
+       line-height: 1;
+}
+ol, ul {
+       list-style: none;
+}
+blockquote, q {
+       quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+       content: '';
+       content: none;
+}
+table {
+       border-collapse: collapse;
+       border-spacing: 0;
+}
diff --git a/empires/style.css b/empires/style.css
new file mode 100644 (file)
index 0000000..adc5a7d
--- /dev/null
@@ -0,0 +1,209 @@
+body {
+    background-color: #333738;
+    font-family: sans-serif;
+    line-height: 1.5;
+}
+
+#page {
+    margin-top: 36px;
+    margin-bottom: 36px;
+    padding-top: 30px;
+    padding-bottom: 30px;
+    padding-left: 10px;
+    padding-right: 10px;
+    background-color: white;
+    margin-left: auto;
+    margin-right: auto;
+}
+
+/* For a wide screen, just use the center of the window for content. */
+@media screen and (min-width: 500px) {
+  #page {
+      max-width: 720px;
+      padding-left: 50px;
+      padding-right: 50px;
+  }
+}
+
+/* And when the screen is narrow, let's kill the wasted vertical space
+   above it too. */
+@media screen and (max-width: 499px) {
+    body {
+        background-color: white;
+    }
+    #page {
+        margin-top: 0;
+        margin-bottom: 0;
+        padding-top: 0;
+        padding-bottom: 0;
+    }
+}
+
+h1 {
+    color: #333738;
+    font-size: 150%;
+    font-weight: bold;
+}
+
+h2 {
+    color: #333738;
+    font-size: 110%;
+    font-weight: bold;
+}
+
+p,dl,dd {
+    margin-bottom: 1em;
+}
+
+/* Default message severity is "info" but can be overriden. */
+.message {
+    padding: 1em;
+    background-color: #44c7ef;
+    color: white;
+    transition: 0.3s;
+    margin-bottom: 0.5em;
+    font-weight: bold;
+}
+
+.success {
+    background-color: #44c7ef;
+}
+
+.warning {
+    background-color: #ffa92a;
+}
+
+.danger {
+    background-color: #f56257
+}
+
+.hide-button {
+    color: white;
+    font-weight: bold;
+    float: right;
+    font-size: 150%;
+    cursor: pointer;
+}
+
+.hide-button:hover {
+    color: black;
+}
+
+.show-state-join {
+    display:none;
+}
+
+.show-state-reveal {
+    display:none;
+}
+
+.show-state-capture {
+    display:none;
+}
+
+.hide-state-join {
+    display:block;
+}
+
+.hide-state-reveal {
+    display:block;
+}
+
+.hide-state-capture {
+    display:block;
+}
+
+/* Players list starts out hidden (until a player is added). */
+#players-div {
+    display:none;
+}
+
+/* Host tools start out hidden. */
+#host-tools {
+    display: none;
+}
+
+#register-form {
+    max-width: 100%;
+    display: grid;
+    grid-template-columns: 49% 49%;
+    grid-column-gap: 2%;
+}
+
+.form-row {
+    max-width: 100%;
+    padding-bottom: 0.25em;
+}
+
+.form-row.medium.left {
+    grid-column-start: 1;
+}
+
+.form-row.medium.right {
+    grid-column-start: 2;
+}
+
+.form-row.large {
+    grid-column-start: 1;
+    grid-column-end: span 2;
+}
+
+/* For a narrow screen, use a single-column for medium form rows. */
+@media screen and (max-width: 600px) {
+    .form-row.medium.left,.form-row.medium.right {
+       grid-column-start: 1;
+        grid-column-end: span 2
+    }
+}
+
+input {
+    width: 100%;
+}
+
+input {
+    box-sizing: border-box;
+
+    height: 40px;
+
+    padding: 10px 12px;
+
+    border: 1px solid #287789;
+    border-radius: 4px;
+}
+
+input:focus {
+    border: 2px solid #44c7ef;
+}
+
+button {
+    display: inline-block;
+    border-radius: 4px;
+    background-color: #287789;
+    border: none;
+    color: white;
+    text-align: center;
+    font-size: 125%;
+    margin-top: .25em;
+    padding-top: 0.25em;
+    padding-bottom: 0.25em;
+    width: 200px;
+    transition: all 150ms ease;
+}
+
+button:hover {
+    transform: translateY(-1px);
+    background-color: #44c7ef;
+}
+
+:focus {
+    outline: none;
+}
+::-moz-focus-inner {
+    border: 0;
+}
+
+#character-reveal {
+    color: #333738;
+    font-size: 300%;
+    font-weight: bold;
+}