]> git.cworth.org Git - lmno.games/blob - empires/game.js
Add handling for a spectators list in addition to the players list
[lmno.games] / empires / game.js
1 /* Construction of Game API endpoint paths. Logically all endpoints
2  * are just local resources underneath the current path, but just
3  * referring to them by a local name only works if the current path
4  * has a trailing slash.
5  *
6  * That is, if the current path is:
7  *
8  *      https://lmno.games/empires/WXYZ/
9  *
10  * Then a path of "register" goes to:
11  *
12  *      https://lmno.games/empires/WXYZ/register
13  *
14  * Just as we want. But if the current path happens to be:
15  *
16  *      https://lmno.games/empires/WXYZ
17  *
18  * Then a path of "register" goes to:
19  *
20  *      https://lmno.games/empires/register
21  *
22  * Which cannot work since we have lost the game ID in the path.
23  *
24  * Of course, we would like to have canonical URLs for the game (with
25  * the trailing slash) but since that depends on macehinery outside
26  * the scope of this file, let's construct API paths that will work
27  * either way.
28  */
29 function GAME_API(endpoint) {
30   var path = window.location.pathname;
31   if (! path.endsWith('/'));
32       path += '/';
33   path += endpoint;
34   return path;
35 }
36
37 var state = {
38   spectator_id: undefined,
39   spectators: [],
40   players: []
41 };
42
43 function undisplay(element) {
44   element.style.display="none";
45 }
46
47 function add_message(severity, message) {
48   message = `<div class="message ${severity}" onclick="undisplay(this)">
49 ${message}
50 <span class="hide-button" onclick="undisplay(this.parentElement)">&times</span>
51 </div>`;
52   const message_area = document.getElementById('message-area');
53   message_area.insertAdjacentHTML('beforeend', message);
54 }
55
56 function register(form) {
57   const spectator_req = new XMLHttpRequest();
58
59   /* Before registering as a player, first remove us as a spectator. */
60   spectator_req.open("DELETE", GAME_API("spectator/") + state.spectator_id);
61   spectator_req.send();
62
63   const register_req = new XMLHttpRequest();
64
65   register_req.open("POST", GAME_API("register"));
66   register_req.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
67   var data = {
68     "character": form.character.value
69   };
70   register_req.send(JSON.stringify(data));
71
72   form.reset();
73 }
74
75 function toggle_host_tools() {
76   const host_tools = document.getElementById("host-tools");
77
78   console.log("Toggling, host_tools.style.display is '" + host_tools.style.display + "'");
79
80   if (host_tools.style.display === "block")
81     host_tools.style.display = "none";
82   else
83     host_tools.style.display = "block";
84 }
85
86 function post_reveal() {
87   const request = new XMLHttpRequest();
88
89   request.open("POST", GAME_API("reveal"));
90   request.send();
91 }
92
93 function post_start() {
94   const request = new XMLHttpRequest();
95
96   request.open("POST", GAME_API("start"));
97   request.send();
98 }
99
100 function post_reset() {
101   const request = new XMLHttpRequest();
102
103   request.open("POST", GAME_API("reset"));
104   request.send();
105 }
106
107 const events = new EventSource(GAME_API("events"));
108
109 events.onerror = function(event) {
110   if (event.target.readyState === EventSource.CLOSED) {
111       add_message("danger", "Connection to server lost.");
112   }
113 };
114
115 events.addEventListener("spectators", function(event) {
116   const spectators_div = document.getElementById("spectators-div");
117   const spectators_element = document.getElementById("spectators");
118   const spectators = JSON.parse(event.data);
119
120   spectators_element.innerHTML = '';
121   for (const spectator of spectators) {
122     var li = document.createElement('li');
123     li.id = "spectator-" + spectator.id;
124     li.innerText = spectator.name;
125     spectators_element.appendChild(li);
126   }
127
128   /* Force players list to be visible. */
129   spectators_div.style.display = "block";
130 });
131
132 events.addEventListener("spectator-join", function(event) {
133   const spectators_div = document.getElementById("spectators-div");
134   const spectators = document.getElementById("spectators");
135   const spectator = JSON.parse(event.data);
136
137   const li = document.createElement('li');
138   li.id = "spectator-" + spectator.id;
139   li.innerText = spectator.name;
140   spectators.appendChild(li);
141
142   /* Force spectators list to be visible. */
143   spectators_div.style.display = "block";
144 });
145
146 events.addEventListener("spectator-leave", function(event) {
147   const spectators = document.getElementById("spectators");
148   const id = JSON.parse(event.data);
149   const spectator = document.getElementById("spectator-" + id.id);
150
151   spectators.removeChild(spectator);
152 });
153
154 events.addEventListener("players", function(event) {
155   const players_div = document.getElementById("players-div");
156   const players_element = document.getElementById("players");
157   const players = JSON.parse(event.data);
158
159   players_element.innerHTML = '';
160   for (const player of players) {
161     var li = document.createElement('li');
162     li.id = "player-" + player.id;
163     li.innerText = player.name;
164     players_element.appendChild(li);
165   }
166
167   /* Force players list to be visible. */
168   players_div.style.display = "block";
169 });
170
171 events.addEventListener("player-join", function(event) {
172   const players_div = document.getElementById("players-div");
173   const players = document.getElementById("players");
174   const player = JSON.parse(event.data);
175
176   const li = document.createElement('li');
177   li.id = "player-" + player.id;
178   li.innerText = player.name;
179   players.appendChild(li);
180
181   /* Force players list to be visible. */
182   players_div.style.display = "block";
183 });
184
185 events.addEventListener("player-leave", function(event) {
186   const players = document.getElementById("players");
187   const id = JSON.parse(event.data);
188   const player = document.getElementById("player-" + id.id);
189
190   add_message("info", player.innerText + " has left the game.");
191   players.removeChild(player);
192 });
193
194 function spectator_on_load() {
195   state.spectator_id = JSON.parse(this.response);
196 }
197
198 events.addEventListener("game-state", function(event) {
199   const data = JSON.parse(event.data);
200   const old_state = data.old_state;
201   const new_state = data.new_state;
202
203   const hide_selector = ".show-state-" +old_state+ ",.hide-state-" +new_state;
204   const show_selector = ".hide-state-" +old_state+ ",.show-state-" +new_state;
205
206   /* Hide all elements based on the state transition. */
207   var elts = document.querySelectorAll(hide_selector);
208   for (const elt of elts) {
209     elt.style.display = "none";
210   }
211
212   /* And show all elements based on the same state transition. */
213   elts = document.querySelectorAll(show_selector);
214   for (const elt of elts) {
215     elt.style.display = "block";
216   }
217
218   /* Whenever the game enters the "join" state, add ourselves as a spectator. */
219   if (new_state === "join") {
220     const request = new XMLHttpRequest();
221
222     request.addEventListener("load", spectator_on_load);
223     request.open("POST", GAME_API("spectator"));
224     request.send();
225   }
226 });
227
228 events.addEventListener("character-reveal", function(event) {
229   const data = JSON.parse(event.data);
230   const character_name = data.character;
231
232   const character = document.getElementById("character-reveal");
233
234   character.innerText = character_name;
235 });
236
237 events.addEventListener("capture", function(event_string) {
238   const players = document.getElementById("players");
239   const event = JSON.parse(event_string.data);
240   const player = document.getElementById("player-" + event.captee);
241
242   players.removeChild(player);
243 });