Commit | Line | Data |
---|---|---|
41534b92 BA |
1 | let $ = document; //shortcut |
2 | ||
3 | /////////////////// | |
4 | // Initialisations | |
5 | ||
6 | // https://stackoverflow.com/a/27747377/12660887 | |
41534b92 | 7 | function generateId (len) { |
f46a68b8 BA |
8 | const dec2hex = (dec) => dec.toString(16).padStart(2, "0"); |
9 | let arr = new Uint8Array(len / 2); //len/2 because 2 chars per hex value | |
10 | window.crypto.getRandomValues(arr); //fill with random integers | |
11 | return Array.from(arr, dec2hex).join(''); | |
41534b92 BA |
12 | } |
13 | ||
14 | // Populate variants dropdown list | |
15 | let dropdown = $.getElementById("selectVariant"); | |
16 | dropdown[0] = new Option("? ? ?", "_random", true, true); | |
17 | dropdown[0].title = "Random variant"; | |
18 | for (let i = 0; i < variants.length; i++) { | |
19 | let newOption = new Option( | |
20 | variants[i].disp || variants[i].name, variants[i].name, false, false); | |
21 | newOption.title = variants[i].desc; | |
22 | dropdown[dropdown.length] = newOption; | |
23 | } | |
24 | ||
25 | // Ensure that I have a socket ID and a name | |
26 | if (!localStorage.getItem("sid")) | |
27 | localStorage.setItem("sid", generateId(8)); | |
28 | if (!localStorage.getItem("name")) | |
29 | localStorage.setItem("name", "@non" + generateId(4)); | |
30 | const sid = localStorage.getItem("sid"); | |
31 | $.getElementById("myName").value = localStorage.getItem("name"); | |
32 | ||
86f3c2cd BA |
33 | // "Material" input field name |
34 | let inputName = document.getElementById("myName"); | |
35 | let formField = document.getElementById("ng-name"); | |
36 | const setActive = (active) => { | |
b4ae3ff6 BA |
37 | if (active) |
38 | formField.classList.add("form-field--is-active"); | |
86f3c2cd BA |
39 | else { |
40 | formField.classList.remove("form-field--is-active"); | |
f46a68b8 BA |
41 | inputName.value == '' |
42 | ? formField.classList.remove("form-field--is-filled") | |
43 | : formField.classList.add("form-field--is-filled"); | |
86f3c2cd BA |
44 | } |
45 | }; | |
f46a68b8 | 46 | setActive(true); |
86f3c2cd BA |
47 | inputName.onblur = () => setActive(false); |
48 | inputName.onfocus = () => setActive(true); | |
86f3c2cd | 49 | |
41534b92 BA |
50 | ///////// |
51 | // Utils | |
52 | ||
53 | function setName() { | |
f46a68b8 | 54 | // 'onChange' event on name input text field [HTML] |
41534b92 BA |
55 | localStorage.setItem("name", $.getElementById("myName").value); |
56 | } | |
57 | ||
58 | // Turn a "tab" on, and "close" all others | |
59 | function toggleVisible(element) { | |
f46a68b8 | 60 | for (elt of document.querySelectorAll("main > div")) { |
b4ae3ff6 BA |
61 | if (elt.id != element) |
62 | elt.style.display = "none"; | |
63 | else | |
64 | elt.style.display = "block"; | |
41534b92 | 65 | } |
a77150a1 | 66 | if (element == "boardContainer") { |
8022d544 BA |
67 | // Avoid smartphone scrolling effects (TODO?) |
68 | document.querySelector("html").style.overflow = "hidden"; | |
69 | document.body.style.overflow = "hidden"; | |
70 | } | |
71 | else { | |
72 | document.querySelector("html").style.overflow = "visible"; | |
73 | document.body.style.overflow = "visible"; | |
f46a68b8 | 74 | // Workaround "superposed texts" effect: |
b4ae3ff6 BA |
75 | if (element == "newGame") |
76 | setActive(false); | |
86f3c2cd | 77 | } |
41534b92 BA |
78 | } |
79 | ||
80 | let seek_vname; | |
81 | function seekGame() { | |
82 | seek_vname = $.getElementById("selectVariant").value; | |
f46a68b8 BA |
83 | if (send("seekgame", |
84 | {vname: seek_vname, name: localStorage.getItem("name")}) | |
85 | ) { | |
86 | toggleVisible("pendingSeek"); | |
87 | } | |
41534b92 BA |
88 | } |
89 | function cancelSeek() { | |
b4ae3ff6 BA |
90 | if (send("cancelseek", {vname: seek_vname})) |
91 | toggleVisible("newGame"); | |
41534b92 BA |
92 | } |
93 | ||
32f57b42 BA |
94 | function sendRematch(random) { |
95 | if (send("rematch", {gid: gid, random: !!random})) | |
96 | toggleVisible("pendingRematch"); | |
41534b92 BA |
97 | } |
98 | function cancelRematch() { | |
b4ae3ff6 BA |
99 | if (send("norematch", {gid: gid})) |
100 | toggleVisible("newGame"); | |
41534b92 BA |
101 | } |
102 | ||
103 | // Play with a friend (or not ^^) | |
104 | function showNewGameForm() { | |
105 | const vname = $.getElementById("selectVariant").value; | |
b4ae3ff6 BA |
106 | if (vname == "_random") |
107 | alert("Select a variant first"); | |
41534b92 BA |
108 | else { |
109 | $.getElementById("gameLink").innerHTML = ""; | |
110 | $.getElementById("selectColor").selectedIndex = 0; | |
111 | toggleVisible("newGameForm"); | |
112 | import(`/variants/${vname}/class.js`).then(module => { | |
cc2c7183 | 113 | window.V = module.default; |
b4ae3ff6 BA |
114 | for (const [k, v] of Object.entries(V.Aliases)) |
115 | window[k] = v; | |
cc2c7183 | 116 | prepareOptions(); |
41534b92 BA |
117 | }); |
118 | } | |
119 | } | |
f46a68b8 BA |
120 | function backToNormalSeek() { |
121 | toggleVisible("newGame"); | |
122 | } | |
41534b92 | 123 | |
f46a68b8 BA |
124 | function toggleStyle(event, obj) { |
125 | const word = obj.innerHTML; | |
41534b92 | 126 | options[word] = !options[word]; |
f46a68b8 | 127 | event.target.classList.toggle("highlight-word"); |
41534b92 BA |
128 | } |
129 | ||
130 | let options; | |
cc2c7183 | 131 | function prepareOptions() { |
41534b92 | 132 | options = {}; |
d621e620 BA |
133 | let optHtml = ""; |
134 | if (V.Options.select) { | |
135 | optHtml += V.Options.select.map(select => { return ` | |
86f3c2cd BA |
136 | <div class="option-select"> |
137 | <label for="var_${select.variable}">${select.label}</label> | |
138 | <div class="select"> | |
535c464b | 139 | <select id="var_${select.variable}">` + |
86f3c2cd BA |
140 | select.options.map(option => { return ` |
141 | <option | |
142 | value="${option.value}" | |
143 | ${option.value == select.defaut ? " selected" : ""} | |
144 | > | |
145 | ${option.label} | |
146 | </option>`; | |
147 | }).join("") + ` | |
148 | </select> | |
149 | <span class="focus"></span> | |
150 | </div> | |
151 | </div>`; | |
d621e620 BA |
152 | }).join(""); |
153 | } | |
d621e620 BA |
154 | if (V.Options.input) { |
155 | optHtml += V.Options.input.map(input => { return ` | |
156 | <div class="option-input"> | |
157 | <label class="input"> | |
158 | <input id="var_${input.variable}" | |
159 | type="${input.type}" | |
535c464b BA |
160 | ${input.type == "checkbox" && input.defaut |
161 | ? "checked" | |
162 | : 'value="' + input.defaut + '"'} | |
163 | /> | |
d621e620 BA |
164 | <span class="spacer"></span> |
165 | <span>${input.label}</span> | |
166 | </label> | |
167 | </div>`; | |
168 | }).join(""); | |
169 | } | |
170 | if (V.Options.styles) { | |
86f3c2cd BA |
171 | optHtml += '<div class="words">'; |
172 | let i = 0; | |
cc2c7183 | 173 | const stylesLength = V.Options.styles.length; |
86f3c2cd BA |
174 | while (i < stylesLength) { |
175 | optHtml += '<div class="row">'; | |
176 | for (let j=i; j<i+4; j++) { | |
b4ae3ff6 BA |
177 | if (j == stylesLength) |
178 | break; | |
cc2c7183 | 179 | const style = V.Options.styles[j]; |
f46a68b8 | 180 | optHtml += `<span onClick="toggleStyle(event, this)">${style}</span>`; |
86f3c2cd BA |
181 | } |
182 | optHtml += "</div>"; | |
183 | i += 4; | |
41534b92 | 184 | } |
86f3c2cd | 185 | optHtml += "</div>"; |
41534b92 | 186 | } |
41534b92 BA |
187 | $.getElementById("gameOptions").innerHTML = optHtml; |
188 | } | |
189 | ||
190 | function getGameLink() { | |
191 | const vname = $.getElementById("selectVariant").value; | |
192 | const color = $.getElementById("selectColor").value; | |
86f3c2cd | 193 | for (const select of $.querySelectorAll("#gameOptions select")) { |
f5435757 BA |
194 | let value = parseInt(select.value, 10); |
195 | if (isNaN(value)) //not an integer | |
196 | value = select.value; | |
535c464b | 197 | options[ select.id.split("_")[1] ] = value; |
cc2c7183 | 198 | } |
535c464b BA |
199 | for (const input of $.querySelectorAll("#gameOptions input")) { |
200 | const variable = input.id.split("_")[1]; | |
201 | if (input.type == "number") | |
202 | options[variable] = parseInt(input.value, 10); //TODO: real numbers? | |
203 | else if (input.type == "checkbox") | |
204 | options[variable] = input.checked; | |
41534b92 | 205 | } |
41534b92 BA |
206 | send("creategame", { |
207 | vname: vname, | |
f46a68b8 | 208 | player: {sid: sid, name: localStorage.getItem("name"), color: color}, |
41534b92 BA |
209 | options: options |
210 | }); | |
211 | } | |
212 | ||
f46a68b8 | 213 | function fillGameInfos(gameInfos, oppIndex) { |
41534b92 BA |
214 | fetch(`/variants/${gameInfos.vname}/rules.html`) |
215 | .then(res => res.text()) | |
216 | .then(txt => { | |
217 | let htmlContent = ` | |
86f3c2cd BA |
218 | <div class="players-info"> |
219 | <p> | |
220 | <span class="bold">${gameInfos.vdisp}</span> | |
221 | <span>vs. ${gameInfos.players[oppIndex].name}</span> | |
222 | </p> | |
223 | </div>`; | |
224 | const options = Object.entries(gameInfos.options); | |
225 | if (options.length > 0) { | |
226 | htmlContent += '<div class="options-info">'; | |
227 | let i = 0; | |
228 | while (i < options.length) { | |
229 | htmlContent += '<div class="row">'; | |
230 | for (let j=i; j<i+4; j++) { | |
b4ae3ff6 BA |
231 | if (j == options.length) |
232 | break; | |
86f3c2cd | 233 | const opt = options[j]; |
f5435757 | 234 | if (!opt[1]) //includes 0 and false (lighter display) |
b4ae3ff6 | 235 | continue; |
86f3c2cd BA |
236 | htmlContent += |
237 | '<span class="option">' + | |
238 | (opt[1] === true ? opt[0] : `${opt[0]}:${opt[1]}`) + " " + | |
f46a68b8 | 239 | "</span>"; |
86f3c2cd BA |
240 | } |
241 | htmlContent += "</div>"; | |
242 | i += 4; | |
243 | } | |
244 | htmlContent += "</div>"; | |
245 | } | |
41534b92 | 246 | htmlContent += ` |
86f3c2cd BA |
247 | <div class="rules">${txt}</div> |
248 | <div class="btn-wrap"> | |
249 | <button onClick="toggleGameInfos()">Back to game</button> | |
250 | </div>`; | |
41534b92 BA |
251 | $.getElementById("gameInfos").innerHTML = htmlContent; |
252 | }); | |
f46a68b8 | 253 | } |
41534b92 BA |
254 | |
255 | //////////////// | |
256 | // Communication | |
257 | ||
f46a68b8 | 258 | let socket, gid, recoAttempt = 0; |
41534b92 | 259 | const autoReconnectDelay = () => { |
f46a68b8 | 260 | return [100, 200, 500, 1000, 3000, 10000, 30000][Math.min(recoAttempt, 6)]; |
41534b92 BA |
261 | }; |
262 | ||
f46a68b8 BA |
263 | function send(code, data, opts) { |
264 | opts = opts || {}; | |
265 | const trySend = () => { | |
266 | if (socket.readyState == 1) { | |
267 | socket.send(JSON.stringify(Object.assign({code: code}, data))); | |
b4ae3ff6 BA |
268 | if (opts.success) |
269 | opts.success(); | |
f46a68b8 BA |
270 | return true; |
271 | } | |
272 | return false; | |
273 | }; | |
274 | const firstTry = trySend(); | |
275 | if (!firstTry) { | |
276 | if (opts.retry) { | |
277 | // Retry for a few seconds (sending move) | |
278 | let sendAttempt = 1; | |
279 | const retryLoop = setInterval( | |
280 | () => { | |
b4ae3ff6 BA |
281 | if (trySend() || ++sendAttempt >= 3) |
282 | clearInterval(retryLoop); | |
283 | if (sendAttempt >= 3 && opts.error) | |
284 | opts.error(); | |
f46a68b8 BA |
285 | }, |
286 | 1000 | |
287 | ); | |
288 | } | |
b4ae3ff6 BA |
289 | else if (opts.error) |
290 | opts.error(); | |
f46a68b8 BA |
291 | } |
292 | return firstTry; | |
293 | } | |
294 | ||
295 | function copyClipboard(msg) { | |
296 | navigator.clipboard.writeText(msg); | |
297 | } | |
41534b92 BA |
298 | function getWhatsApp(msg) { |
299 | return `https://api.whatsapp.com/send?text=${encodeURIComponent(msg)}`; | |
300 | } | |
301 | ||
302 | const tryResumeGame = () => { | |
f46a68b8 | 303 | recoAttempt = 0; |
41534b92 BA |
304 | // If a game is found, resume it: |
305 | if (localStorage.getItem("gid")) { | |
306 | gid = localStorage.getItem("gid"); | |
f46a68b8 BA |
307 | send("getgame", |
308 | {gid: gid}, | |
309 | { | |
310 | retry: true, | |
311 | error: () => alert("Cannot load game: no connection") | |
312 | }); | |
41534b92 BA |
313 | } |
314 | else { | |
315 | // If URL indicates "play with a friend", start game: | |
316 | const hashIdx = document.URL.indexOf('#'); | |
317 | if (hashIdx >= 0) { | |
318 | const urlParts = $.URL.split('#'); | |
319 | gid = urlParts[1]; | |
41534b92 | 320 | localStorage.setItem("gid", gid); |
f46a68b8 BA |
321 | history.replaceState(null, '', urlParts[0]); //hide game ID |
322 | send("joingame", | |
323 | {gid: gid, name: localStorage.getItem("name")}, | |
324 | { | |
325 | retry: true, | |
326 | error: () => alert("Cannot load game: no connection") | |
327 | }); | |
41534b92 BA |
328 | } |
329 | } | |
330 | }; | |
331 | ||
332 | const messageCenter = (msg) => { | |
333 | const obj = JSON.parse(msg.data); | |
334 | switch (obj.code) { | |
335 | // Start new game: | |
336 | case "gamestart": { | |
b4ae3ff6 BA |
337 | if (document.hidden) |
338 | notifyMe("game"); | |
41534b92 BA |
339 | gid = obj.gid; |
340 | initializeGame(obj); | |
341 | break; | |
342 | } | |
343 | // Game vs. friend just created on server: share link now | |
344 | case "gamecreated": { | |
345 | const link = `${Params.http_server}/#${obj.gid}`; | |
346 | $.getElementById("gameLink").innerHTML = ` | |
347 | <p> | |
348 | <a href="${getWhatsApp(link)}">WhatsApp</a> | |
349 | / | |
f46a68b8 | 350 | <span onClick="copyClipboard('${link}')">ToClipboard</span> |
41534b92 BA |
351 | </p> |
352 | <p>${link}</p> | |
353 | `; | |
354 | break; | |
355 | } | |
356 | // Game vs. friend joined after 1 minute (try again!) | |
357 | case "jointoolate": | |
358 | alert("Game no longer available"); | |
359 | break; | |
360 | // Get infos of a running game (already launched) | |
361 | case "gameinfo": | |
362 | initializeGame(obj); | |
363 | break; | |
364 | // Tried to resume a game which is now gone: | |
365 | case "nogame": | |
366 | localStorage.removeItem("gid"); | |
367 | break; | |
368 | // Receive opponent's move: | |
369 | case "newmove": | |
f46a68b8 | 370 | // Basic check: was it really opponent's turn? |
b4ae3ff6 BA |
371 | if (vr.turn == playerColor) |
372 | break; | |
373 | if (document.hidden) | |
374 | notifyMe("move"); | |
41534b92 | 375 | vr.playReceivedMove(obj.moves, () => { |
eceb02f7 | 376 | if (vr.getCurrentScore(obj.moves) != "*") { |
41534b92 BA |
377 | localStorage.removeItem("gid"); |
378 | setTimeout( () => toggleVisible("gameStopped"), 2000 ); | |
379 | } | |
b4ae3ff6 BA |
380 | else |
381 | toggleTurnIndicator(true); | |
41534b92 BA |
382 | }); |
383 | break; | |
384 | // Opponent stopped game (draw, abort, resign...) | |
385 | case "gameover": | |
386 | toggleVisible("gameStopped"); | |
387 | localStorage.removeItem("gid"); | |
388 | break; | |
389 | // Opponent cancelled rematch: | |
390 | case "closerematch": | |
391 | toggleVisible("newGame"); | |
392 | break; | |
0e466aac BA |
393 | case "filechange": |
394 | // TODO?: could be more subtle | |
383c05e2 | 395 | setTimeout(() => location.reload(), 100); |
0e466aac | 396 | break; |
41534b92 BA |
397 | } |
398 | }; | |
399 | ||
400 | const handleError = (err) => { | |
f46a68b8 | 401 | if (err.code === "ECONNREFUSED") { |
41534b92 BA |
402 | removeAllListeners(); |
403 | alert("Server refused connection. Please reload page later"); | |
404 | } | |
405 | socket.close(); | |
406 | }; | |
407 | ||
408 | const handleClose = () => { | |
409 | setTimeout(() => { | |
410 | removeAllListeners(); | |
411 | connectToWSS(); | |
412 | }, autoReconnectDelay()); | |
413 | }; | |
414 | ||
f46a68b8 | 415 | function removeAllListeners() { |
41534b92 BA |
416 | socket.removeEventListener("open", tryResumeGame); |
417 | socket.removeEventListener("message", messageCenter); | |
418 | socket.removeEventListener("error", handleError); | |
419 | socket.removeEventListener("close", handleClose); | |
f46a68b8 | 420 | } |
41534b92 | 421 | |
f46a68b8 | 422 | function connectToWSS() { |
41534b92 BA |
423 | socket = |
424 | new WebSocket(`${Params.socket_server}${Params.socket_path}?sid=${sid}`); | |
425 | socket.addEventListener("open", tryResumeGame); | |
426 | socket.addEventListener("message", messageCenter); | |
427 | socket.addEventListener("error", handleError); | |
428 | socket.addEventListener("close", handleClose); | |
f46a68b8 BA |
429 | recoAttempt++; |
430 | } | |
41534b92 BA |
431 | connectToWSS(); |
432 | ||
41534b92 BA |
433 | /////////// |
434 | // Playing | |
435 | ||
436 | function toggleTurnIndicator(myTurn) { | |
3c61449b BA |
437 | let indicator = |
438 | $.getElementById("boardContainer").querySelector(".chessboard"); | |
b4ae3ff6 BA |
439 | if (myTurn) |
440 | indicator.style.outline = "thick solid green"; | |
441 | else | |
442 | indicator.style.outline = "thick solid lightgrey"; | |
41534b92 BA |
443 | } |
444 | ||
445 | function notifyMe(code) { | |
446 | const doNotify = () => { | |
447 | // NOTE: empty body (TODO?) | |
448 | new Notification("New " + code, { vibrate: [200, 100, 200] }); | |
449 | new Audio("/assets/new_" + code + ".mp3").play(); | |
450 | } | |
b4ae3ff6 BA |
451 | if (Notification.permission === "granted") |
452 | doNotify(); | |
f46a68b8 | 453 | else if (Notification.permission !== "denied") { |
016306e3 | 454 | Notification.requestPermission().then(permission => { |
b4ae3ff6 BA |
455 | if (permission === "granted") |
456 | doNotify(); | |
41534b92 BA |
457 | }); |
458 | } | |
459 | } | |
460 | ||
8a9f61ce | 461 | let curMoves = [], |
f46a68b8 | 462 | lastFen; |
5f08c59b BA |
463 | const afterPlay = (move_s, newTurn, ops) => { |
464 | if (ops.send) { | |
465 | // Pack into one moves array, then send (if turn changed) | |
466 | if (Array.isArray(move_s)) | |
467 | // Array of simple moves (e.g. Chakart) | |
468 | Array.prototype.push.apply(curMoves, move_s); | |
469 | else | |
470 | // Usual case | |
471 | curMoves.push(move_s); | |
472 | if (newTurn != playerColor) { | |
473 | send("newmove", | |
474 | {gid: gid, moves: curMoves, fen: vr.getFen()}, | |
475 | { | |
476 | retry: true, | |
5f08c59b BA |
477 | error: () => alert("Move not sent: reload page") |
478 | } | |
479 | ); | |
480 | } | |
481 | } | |
482 | if (ops.res && newTurn != playerColor) { | |
483 | toggleTurnIndicator(false); //now all moves are sent and animated | |
eceb02f7 BA |
484 | const result = vr.getCurrentScore(curMoves); |
485 | curMoves = []; | |
f46a68b8 BA |
486 | if (result != "*") { |
487 | setTimeout(() => { | |
488 | toggleVisible("gameStopped"); | |
489 | send("gameover", {gid: gid}); | |
490 | }, 2000); | |
491 | } | |
41534b92 BA |
492 | } |
493 | }; | |
494 | ||
a5da6269 | 495 | let vr = null, playerColor, lastVname = undefined; |
41534b92 BA |
496 | function initializeGame(obj) { |
497 | const options = obj.options || {}; | |
498 | import(`/variants/${obj.vname}/class.js`).then(module => { | |
cc2c7183 | 499 | window.V = module.default; |
b4ae3ff6 BA |
500 | for (const [k, v] of Object.entries(V.Aliases)) |
501 | window[k] = v; | |
a5da6269 BA |
502 | if (lastVname != obj.vname) { |
503 | // Load CSS + unload potential previous one. | |
504 | if (lastVname) | |
505 | document.getElementById(lastVname + "_css").remove(); | |
f46a68b8 BA |
506 | $.getElementsByTagName("head")[0].insertAdjacentHTML( |
507 | "beforeend", | |
a5da6269 | 508 | `<link id="${obj.vname + '_css'}" rel="stylesheet" |
f46a68b8 | 509 | href="/variants/${obj.vname}/style.css"/>`); |
a5da6269 | 510 | lastVname = obj.vname; |
f46a68b8 | 511 | } |
21e8e712 | 512 | playerColor = (sid == obj.players[0].sid ? "w" : "b"); |
41534b92 BA |
513 | // Init + remove potential extra DOM elements from a previous game: |
514 | document.getElementById("boardContainer").innerHTML = ` | |
515 | <div id="upLeftInfos" | |
516 | onClick="toggleGameInfos()"> | |
cc2c7183 BA |
517 | <svg version="1.1" |
518 | viewBox="0.5 0.5 100 100"> | |
519 | <g> | |
520 | <path d="M50.5,0.5c-27.614,0-50,22.386-50,50c0,27.614,22.386,50,50,50s50-22.386,50-50C100.5,22.886,78.114,0.5,50.5,0.5z M60.5,85.5h-20v-40h20V85.5z M50.5,35.5c-5.523,0-10-4.477-10-10s4.477-10,10-10c5.522,0,10,4.477,10,10S56.022,35.5,50.5,35.5z"/> | |
521 | </g> | |
522 | </svg> | |
41534b92 BA |
523 | </div> |
524 | <div id="upRightStop" | |
525 | onClick="confirmStopGame()"> | |
cc2c7183 BA |
526 | <svg version="1.1" |
527 | viewBox="0 0 533.333 533.333"> | |
528 | <g> | |
529 | <path d="M528.468,428.468c-0.002-0.002-0.004-0.004-0.006-0.005L366.667,266.666l161.795-161.797 c0.002-0.002,0.004-0.003,0.006-0.005c1.741-1.742,3.001-3.778,3.809-5.946c2.211-5.925,0.95-12.855-3.814-17.62l-76.431-76.43 c-4.765-4.763-11.694-6.024-17.619-3.812c-2.167,0.807-4.203,2.066-5.946,3.807c0,0.002-0.002,0.003-0.005,0.005L266.667,166.666 L104.87,4.869c-0.002-0.002-0.003-0.003-0.005-0.005c-1.743-1.74-3.778-3-5.945-3.807C92.993-1.156,86.065,0.105,81.3,4.869 L4.869,81.3c-4.764,4.765-6.024,11.694-3.813,17.619c0.808,2.167,2.067,4.205,3.808,5.946c0.002,0.001,0.003,0.003,0.005,0.005 l161.797,161.796L4.869,428.464c-0.001,0.002-0.003,0.003-0.004,0.005c-1.741,1.742-3,3.778-3.809,5.945 c-2.212,5.924-0.951,12.854,3.813,17.619L81.3,528.464c4.766,4.765,11.694,6.025,17.62,3.813c2.167-0.809,4.203-2.068,5.946-3.809 c0.001-0.002,0.003-0.003,0.005-0.005l161.796-161.797l161.795,161.797c0.003,0.001,0.005,0.003,0.007,0.004 c1.743,1.741,3.778,3.001,5.944,3.81c5.927,2.212,12.856,0.951,17.619-3.813l76.43-76.432c4.766-4.765,6.026-11.696,3.815-17.62 C531.469,432.246,530.209,430.21,528.468,428.468z"/> | |
530 | </g> | |
531 | </svg> | |
41534b92 | 532 | </div> |
c4e9bb92 | 533 | <div class="chessboard"></div>`; |
a5da6269 BA |
534 | if (vr) |
535 | // Avoid interferences: | |
536 | vr.removeListeners(); | |
cc2c7183 | 537 | vr = new V({ |
41534b92 BA |
538 | seed: obj.seed, //may be null if FEN already exists (running game) |
539 | fen: obj.fen, | |
3c61449b | 540 | element: "boardContainer", |
21e8e712 | 541 | color: playerColor, |
41534b92 BA |
542 | afterPlay: afterPlay, |
543 | options: options | |
544 | }); | |
5f08c59b BA |
545 | const gameCreation = !obj.fen; |
546 | if (gameCreation) { | |
547 | // Both players set FEN, in case of one is offline | |
f46a68b8 | 548 | send("setfen", {gid: obj.gid, fen: vr.getFen()}); |
41534b92 BA |
549 | localStorage.setItem("gid", obj.gid); |
550 | } | |
551 | const select = $.getElementById("selectVariant"); | |
552 | obj.vdisp = ""; | |
553 | for (let i=0; i<select.options.length; i++) { | |
554 | if (select.options[i].value == obj.vname) { | |
555 | obj.vdisp = select.options[i].text; | |
556 | break; | |
557 | } | |
558 | } | |
554e3ad3 BA |
559 | const playerIndex = (playerColor == "w" ? 0 : 1); |
560 | fillGameInfos(obj, 1 - playerIndex); | |
5f08c59b | 561 | if (obj.players[playerIndex].randvar && gameCreation) |
b4ae3ff6 BA |
562 | toggleVisible("gameInfos"); |
563 | else | |
564 | toggleVisible("boardContainer"); | |
21e8e712 | 565 | toggleTurnIndicator(vr.turn == playerColor); |
41534b92 BA |
566 | }); |
567 | } | |
568 | ||
569 | function confirmStopGame() { | |
f46a68b8 | 570 | if (confirm("Stop game?") && send("gameover", {gid: gid, relay: true})) { |
41534b92 BA |
571 | localStorage.removeItem("gid"); |
572 | toggleVisible("gameStopped"); | |
573 | } | |
574 | } | |
575 | ||
576 | function toggleGameInfos() { | |
577 | if ($.getElementById("gameInfos").style.display == "none") | |
578 | toggleVisible("gameInfos"); | |
b4ae3ff6 BA |
579 | else |
580 | toggleVisible("boardContainer"); | |
41534b92 BA |
581 | } |
582 | ||
583 | $.body.addEventListener("keydown", (e) => { | |
b4ae3ff6 BA |
584 | if (!localStorage.getItem("gid")) |
585 | return; | |
586 | if (e.keyCode == 27) | |
587 | confirmStopGame(); | |
41534b92 BA |
588 | else if (e.keyCode == 32) { |
589 | e.preventDefault(); | |
590 | toggleGameInfos(); | |
591 | } | |
592 | }); |