Commit | Line | Data |
---|---|---|
41534b92 BA |
1 | let $ = document; //shortcut |
2 | ||
3 | /////////////////// | |
4 | // Initialisations | |
5 | ||
6 | // https://stackoverflow.com/a/27747377/12660887 | |
7 | function dec2hex (dec) { return dec.toString(16).padStart(2, "0") } | |
8 | function generateId (len) { | |
9 | var arr = new Uint8Array((len || 40) / 2) | |
10 | window.crypto.getRandomValues(arr) | |
11 | return Array.from(arr, dec2hex).join('') | |
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) => { | |
37 | if (active) formField.classList.add("form-field--is-active"); | |
38 | else { | |
39 | formField.classList.remove("form-field--is-active"); | |
40 | inputName.value === "" ? | |
41 | formField.classList.remove("form-field--is-filled") : | |
42 | formField.classList.add("form-field--is-filled"); | |
43 | } | |
44 | }; | |
45 | inputName.onblur = () => setActive(false); | |
46 | inputName.onfocus = () => setActive(true); | |
47 | inputName.focus(); | |
48 | ||
41534b92 BA |
49 | ///////// |
50 | // Utils | |
51 | ||
52 | function setName() { | |
53 | localStorage.setItem("name", $.getElementById("myName").value); | |
54 | } | |
55 | ||
56 | // Turn a "tab" on, and "close" all others | |
57 | function toggleVisible(element) { | |
86f3c2cd | 58 | for (elt of document.querySelectorAll('main > div')) { |
41534b92 BA |
59 | if (elt.id != element) elt.style.display = "none"; |
60 | else elt.style.display = "block"; | |
61 | } | |
a77150a1 | 62 | if (element == "boardContainer") { |
8022d544 BA |
63 | // Avoid smartphone scrolling effects (TODO?) |
64 | document.querySelector("html").style.overflow = "hidden"; | |
65 | document.body.style.overflow = "hidden"; | |
66 | } | |
67 | else { | |
68 | document.querySelector("html").style.overflow = "visible"; | |
69 | document.body.style.overflow = "visible"; | |
a77150a1 | 70 | if (element == "newGame") { |
8022d544 BA |
71 | // Workaround "superposed texts" effect |
72 | inputName.focus(); | |
73 | inputName.blur(); | |
74 | } | |
86f3c2cd | 75 | } |
41534b92 BA |
76 | } |
77 | ||
78 | let seek_vname; | |
79 | function seekGame() { | |
80 | seek_vname = $.getElementById("selectVariant").value; | |
81 | send("seekgame", {vname: seek_vname, name: localStorage.getItem("name")}); | |
82 | toggleVisible("pendingSeek"); | |
83 | } | |
84 | function cancelSeek() { | |
85 | send("cancelseek", {vname: seek_vname}); | |
86 | toggleVisible("newGame"); | |
87 | } | |
88 | ||
89 | function sendRematch() { | |
90 | send("rematch", { gid: gid }); | |
91 | toggleVisible("pendingRematch"); | |
92 | } | |
93 | function cancelRematch() { | |
94 | send("norematch", { gid: gid }); | |
95 | toggleVisible("newGame"); | |
96 | } | |
97 | ||
98 | // Play with a friend (or not ^^) | |
99 | function showNewGameForm() { | |
100 | const vname = $.getElementById("selectVariant").value; | |
101 | if (vname == "_random") alert("Select a variant first"); | |
102 | else { | |
103 | $.getElementById("gameLink").innerHTML = ""; | |
104 | $.getElementById("selectColor").selectedIndex = 0; | |
105 | toggleVisible("newGameForm"); | |
106 | import(`/variants/${vname}/class.js`).then(module => { | |
cc2c7183 BA |
107 | window.V = module.default; |
108 | prepareOptions(); | |
41534b92 BA |
109 | }); |
110 | } | |
111 | } | |
112 | function backToNormalSeek() { toggleVisible("newGame"); } | |
113 | ||
114 | function toggleStyle(e, word) { | |
115 | options[word] = !options[word]; | |
116 | e.target.classList.toggle("highlight-word"); | |
117 | } | |
118 | ||
119 | let options; | |
cc2c7183 | 120 | function prepareOptions() { |
41534b92 | 121 | options = {}; |
cc2c7183 | 122 | let optHtml = V.Options.select.map(select => { return ` |
86f3c2cd BA |
123 | <div class="option-select"> |
124 | <label for="var_${select.variable}">${select.label}</label> | |
125 | <div class="select"> | |
126 | <select id="var_${select.variable}" data-numeric="1">` + | |
127 | select.options.map(option => { return ` | |
128 | <option | |
129 | value="${option.value}" | |
130 | ${option.value == select.defaut ? " selected" : ""} | |
131 | > | |
132 | ${option.label} | |
133 | </option>`; | |
134 | }).join("") + ` | |
135 | </select> | |
136 | <span class="focus"></span> | |
137 | </div> | |
138 | </div>`; | |
139 | }).join(""); | |
cc2c7183 | 140 | optHtml += V.Options.check.map(check => { |
86f3c2cd BA |
141 | return ` |
142 | <div class="option-check"> | |
143 | <label class="checkbox"> | |
144 | <input id="var_${check.variable}" | |
145 | type="checkbox"${check.defaut ? " checked" : ""}/> | |
146 | <span class="spacer"></span> | |
147 | <span>${check.label}</span> | |
148 | </label> | |
149 | </div>`; | |
150 | }).join(""); | |
cc2c7183 | 151 | if (V.Options.styles.length >= 1) { |
86f3c2cd BA |
152 | optHtml += '<div class="words">'; |
153 | let i = 0; | |
cc2c7183 | 154 | const stylesLength = V.Options.styles.length; |
86f3c2cd BA |
155 | while (i < stylesLength) { |
156 | optHtml += '<div class="row">'; | |
157 | for (let j=i; j<i+4; j++) { | |
158 | if (j == stylesLength) break; | |
cc2c7183 | 159 | const style = V.Options.styles[j]; |
86f3c2cd BA |
160 | optHtml += |
161 | `<span onClick="toggleStyle(event, '${style}')">${style}</span>`; | |
162 | } | |
163 | optHtml += "</div>"; | |
164 | i += 4; | |
41534b92 | 165 | } |
86f3c2cd | 166 | optHtml += "</div>"; |
41534b92 | 167 | } |
41534b92 BA |
168 | $.getElementById("gameOptions").innerHTML = optHtml; |
169 | } | |
170 | ||
171 | function getGameLink() { | |
172 | const vname = $.getElementById("selectVariant").value; | |
173 | const color = $.getElementById("selectColor").value; | |
86f3c2cd | 174 | for (const select of $.querySelectorAll("#gameOptions select")) { |
41534b92 BA |
175 | let value = select.value; |
176 | if (select.attributes["data-numeric"]) value = parseInt(value, 10); | |
cc2c7183 BA |
177 | if (value) options[ select.id.split("_")[1] ] = value; |
178 | } | |
179 | for (const check of $.querySelectorAll("#gameOptions input")) { | |
180 | if (check.checked) options[ check.id.split("_")[1] ] = check.checked; | |
41534b92 | 181 | } |
41534b92 BA |
182 | send("creategame", { |
183 | vname: vname, | |
184 | player: { sid: sid, name: localStorage.getItem("name"), color: color }, | |
185 | options: options | |
186 | }); | |
187 | } | |
188 | ||
189 | const fillGameInfos = (gameInfos, oppIndex) => { | |
190 | fetch(`/variants/${gameInfos.vname}/rules.html`) | |
191 | .then(res => res.text()) | |
192 | .then(txt => { | |
193 | let htmlContent = ` | |
86f3c2cd BA |
194 | <div class="players-info"> |
195 | <p> | |
196 | <span class="bold">${gameInfos.vdisp}</span> | |
197 | <span>vs. ${gameInfos.players[oppIndex].name}</span> | |
198 | </p> | |
199 | </div>`; | |
200 | const options = Object.entries(gameInfos.options); | |
201 | if (options.length > 0) { | |
202 | htmlContent += '<div class="options-info">'; | |
203 | let i = 0; | |
204 | while (i < options.length) { | |
205 | htmlContent += '<div class="row">'; | |
206 | for (let j=i; j<i+4; j++) { | |
207 | if (j == options.length) break; | |
208 | const opt = options[j]; | |
209 | htmlContent += | |
210 | '<span class="option">' + | |
211 | (opt[1] === true ? opt[0] : `${opt[0]}:${opt[1]}`) + " " + | |
212 | '</span>'; | |
213 | } | |
214 | htmlContent += "</div>"; | |
215 | i += 4; | |
216 | } | |
217 | htmlContent += "</div>"; | |
218 | } | |
41534b92 | 219 | htmlContent += ` |
86f3c2cd BA |
220 | <div class="rules">${txt}</div> |
221 | <div class="btn-wrap"> | |
222 | <button onClick="toggleGameInfos()">Back to game</button> | |
223 | </div>`; | |
41534b92 BA |
224 | $.getElementById("gameInfos").innerHTML = htmlContent; |
225 | }); | |
226 | }; | |
227 | ||
228 | //////////////// | |
229 | // Communication | |
230 | ||
231 | let socket, gid, attempt = 0; | |
232 | const autoReconnectDelay = () => { | |
233 | return [100, 200, 500, 1000, 3000, 10000, 30000][Math.min(attempt, 6)]; | |
234 | }; | |
235 | ||
236 | function copyClipboard(msg) { navigator.clipboard.writeText(msg); } | |
237 | function getWhatsApp(msg) { | |
238 | return `https://api.whatsapp.com/send?text=${encodeURIComponent(msg)}`; | |
239 | } | |
240 | ||
241 | const tryResumeGame = () => { | |
242 | attempt = 0; | |
243 | // If a game is found, resume it: | |
244 | if (localStorage.getItem("gid")) { | |
245 | gid = localStorage.getItem("gid"); | |
246 | send("getgame", { gid: gid }); | |
247 | } | |
248 | else { | |
249 | // If URL indicates "play with a friend", start game: | |
250 | const hashIdx = document.URL.indexOf('#'); | |
251 | if (hashIdx >= 0) { | |
252 | const urlParts = $.URL.split('#'); | |
253 | gid = urlParts[1]; | |
254 | send("joingame", { gid: gid, name: localStorage.getItem("name") }); | |
255 | localStorage.setItem("gid", gid); | |
256 | history.replaceState(null, '', urlParts[0]); | |
257 | } | |
258 | } | |
259 | }; | |
260 | ||
261 | const messageCenter = (msg) => { | |
262 | const obj = JSON.parse(msg.data); | |
263 | switch (obj.code) { | |
264 | // Start new game: | |
265 | case "gamestart": { | |
016306e3 | 266 | if (document.hidden) notifyMe("game"); |
41534b92 BA |
267 | gid = obj.gid; |
268 | initializeGame(obj); | |
269 | break; | |
270 | } | |
271 | // Game vs. friend just created on server: share link now | |
272 | case "gamecreated": { | |
273 | const link = `${Params.http_server}/#${obj.gid}`; | |
274 | $.getElementById("gameLink").innerHTML = ` | |
275 | <p> | |
276 | <a href="${getWhatsApp(link)}">WhatsApp</a> | |
277 | / | |
278 | <span onClick='copyClipboard("${link}")'>ToClipboard</span> | |
279 | </p> | |
280 | <p>${link}</p> | |
281 | `; | |
282 | break; | |
283 | } | |
284 | // Game vs. friend joined after 1 minute (try again!) | |
285 | case "jointoolate": | |
286 | alert("Game no longer available"); | |
287 | break; | |
288 | // Get infos of a running game (already launched) | |
289 | case "gameinfo": | |
290 | initializeGame(obj); | |
291 | break; | |
292 | // Tried to resume a game which is now gone: | |
293 | case "nogame": | |
294 | localStorage.removeItem("gid"); | |
295 | break; | |
296 | // Receive opponent's move: | |
297 | case "newmove": | |
8a9f61ce BA |
298 | send("gotmove", {fen: obj.fen, gid: gid}); |
299 | if (obj.fen == lastFen) break; //got this move already | |
300 | lastFen = obj.fen; | |
016306e3 | 301 | if (document.hidden) notifyMe("move"); |
41534b92 BA |
302 | vr.playReceivedMove(obj.moves, () => { |
303 | if (vr.getCurrentScore(obj.moves[obj.moves.length-1]) != "*") { | |
304 | localStorage.removeItem("gid"); | |
305 | setTimeout( () => toggleVisible("gameStopped"), 2000 ); | |
306 | } | |
307 | else toggleTurnIndicator(true); | |
308 | }); | |
309 | break; | |
8a9f61ce BA |
310 | // The server notifies that it got our move: |
311 | case "gotmove": | |
312 | if (obj.fen == lastFen) { | |
313 | curMoves = []; | |
314 | clearTimeout(timeout1); | |
315 | clearTimeout(timeout2); | |
316 | clearTimeout(timeout3); | |
317 | callbackAfterConfirmation(); | |
318 | } | |
319 | break; | |
41534b92 BA |
320 | // Opponent stopped game (draw, abort, resign...) |
321 | case "gameover": | |
322 | toggleVisible("gameStopped"); | |
323 | localStorage.removeItem("gid"); | |
324 | break; | |
325 | // Opponent cancelled rematch: | |
326 | case "closerematch": | |
327 | toggleVisible("newGame"); | |
328 | break; | |
329 | } | |
330 | }; | |
331 | ||
332 | const handleError = (err) => { | |
333 | if (err.code === 'ECONNREFUSED') { | |
334 | removeAllListeners(); | |
335 | alert("Server refused connection. Please reload page later"); | |
336 | } | |
337 | socket.close(); | |
338 | }; | |
339 | ||
340 | const handleClose = () => { | |
341 | setTimeout(() => { | |
342 | removeAllListeners(); | |
343 | connectToWSS(); | |
344 | }, autoReconnectDelay()); | |
345 | }; | |
346 | ||
347 | const removeAllListeners = () => { | |
348 | socket.removeEventListener("open", tryResumeGame); | |
349 | socket.removeEventListener("message", messageCenter); | |
350 | socket.removeEventListener("error", handleError); | |
351 | socket.removeEventListener("close", handleClose); | |
352 | }; | |
353 | ||
354 | const connectToWSS = () => { | |
355 | socket = | |
356 | new WebSocket(`${Params.socket_server}${Params.socket_path}?sid=${sid}`); | |
357 | socket.addEventListener("open", tryResumeGame); | |
358 | socket.addEventListener("message", messageCenter); | |
359 | socket.addEventListener("error", handleError); | |
360 | socket.addEventListener("close", handleClose); | |
361 | attempt++; | |
362 | }; | |
363 | connectToWSS(); | |
364 | ||
365 | const send = (code, data) => { | |
366 | socket.send(JSON.stringify(Object.assign({code: code}, data))); | |
367 | }; | |
368 | ||
369 | /////////// | |
370 | // Playing | |
371 | ||
372 | function toggleTurnIndicator(myTurn) { | |
373 | let indicator = $.getElementById("chessboard"); | |
374 | if (myTurn) indicator.style.outline = "thick solid green"; | |
375 | else indicator.style.outline = "thick solid lightgrey"; | |
376 | } | |
377 | ||
378 | function notifyMe(code) { | |
379 | const doNotify = () => { | |
380 | // NOTE: empty body (TODO?) | |
381 | new Notification("New " + code, { vibrate: [200, 100, 200] }); | |
382 | new Audio("/assets/new_" + code + ".mp3").play(); | |
383 | } | |
384 | if (Notification.permission === 'granted') doNotify(); | |
385 | else if (Notification.permission !== 'denied') { | |
016306e3 | 386 | Notification.requestPermission().then(permission => { |
41534b92 BA |
387 | if (permission === 'granted') doNotify(); |
388 | }); | |
389 | } | |
390 | } | |
391 | ||
8a9f61ce BA |
392 | let curMoves = [], |
393 | lastFen, lastMove, | |
394 | timeout1, timeout2, timeout3; | |
395 | const callbackAfterConfirmation = () => { | |
396 | const result = vr.getCurrentScore(lastMove); | |
397 | if (result != "*") { | |
398 | setTimeout( () => { | |
399 | toggleVisible("gameStopped"); | |
400 | send("gameover", { gid: gid }); | |
401 | }, 2000); | |
402 | } | |
403 | }; | |
404 | const afterPlay = (move) => { | |
405 | // Pack into one moves array, then send | |
41534b92 BA |
406 | curMoves.push({ |
407 | appear: move.appear, | |
408 | vanish: move.vanish, | |
409 | start: move.start, | |
410 | end: move.end | |
411 | }); | |
8a9f61ce | 412 | lastMove = move; |
21e8e712 | 413 | if (vr.turn != playerColor) { |
41534b92 | 414 | toggleTurnIndicator(false); |
8a9f61ce BA |
415 | lastFen = vr.getFen(); |
416 | const sendMove = | |
417 | () => send("newmove", {gid: gid, moves: curMoves, fen: lastFen}); | |
418 | // Send move until we obtain confirmation or timeout, then callback | |
419 | sendMove(); | |
420 | timeout1 = setTimeout(sendMove, 500); | |
421 | timeout2 = setTimeout(sendMove, 1500); | |
422 | timeout3 = setTimeout( | |
423 | () => alert("The move may be lost :( Please reload"), | |
424 | 3000); | |
41534b92 BA |
425 | } |
426 | }; | |
427 | ||
428 | // Avoid loading twice the same stylesheet: | |
429 | const conditionalLoadCSS = (vname) => { | |
430 | const allIds = [].slice.call($.styleSheets).map(s => s.id); | |
431 | const newId = vname + "_css"; | |
432 | if (!allIds.includes(newId)) { | |
433 | $.getElementsByTagName("head")[0].insertAdjacentHTML( | |
434 | "beforeend", | |
435 | `<link id="${newId}" rel="stylesheet" | |
436 | href="/variants/${vname}/style.css"/>`); | |
437 | } | |
438 | }; | |
439 | ||
21e8e712 | 440 | let vr, playerColor; |
41534b92 BA |
441 | function initializeGame(obj) { |
442 | const options = obj.options || {}; | |
443 | import(`/variants/${obj.vname}/class.js`).then(module => { | |
cc2c7183 | 444 | window.V = module.default; |
41534b92 | 445 | conditionalLoadCSS(obj.vname); |
21e8e712 | 446 | playerColor = (sid == obj.players[0].sid ? "w" : "b"); |
41534b92 BA |
447 | // Init + remove potential extra DOM elements from a previous game: |
448 | document.getElementById("boardContainer").innerHTML = ` | |
449 | <div id="upLeftInfos" | |
450 | onClick="toggleGameInfos()"> | |
cc2c7183 BA |
451 | <svg version="1.1" |
452 | viewBox="0.5 0.5 100 100"> | |
453 | <g> | |
454 | <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"/> | |
455 | </g> | |
456 | </svg> | |
41534b92 BA |
457 | </div> |
458 | <div id="upRightStop" | |
459 | onClick="confirmStopGame()"> | |
cc2c7183 BA |
460 | <svg version="1.1" |
461 | viewBox="0 0 533.333 533.333"> | |
462 | <g> | |
463 | <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"/> | |
464 | </g> | |
465 | </svg> | |
41534b92 BA |
466 | </div> |
467 | <div class="resizeable" id="chessboard"></div>`; | |
cc2c7183 | 468 | vr = new V({ |
41534b92 BA |
469 | seed: obj.seed, //may be null if FEN already exists (running game) |
470 | fen: obj.fen, | |
471 | element: "chessboard", | |
21e8e712 | 472 | color: playerColor, |
41534b92 BA |
473 | afterPlay: afterPlay, |
474 | options: options | |
475 | }); | |
476 | if (!obj.fen) { | |
477 | // Game creation | |
21e8e712 | 478 | if (playerColor == "w") send("setfen", {gid: obj.gid, fen: vr.getFen()}); |
41534b92 BA |
479 | localStorage.setItem("gid", obj.gid); |
480 | } | |
481 | const select = $.getElementById("selectVariant"); | |
482 | obj.vdisp = ""; | |
483 | for (let i=0; i<select.options.length; i++) { | |
484 | if (select.options[i].value == obj.vname) { | |
485 | obj.vdisp = select.options[i].text; | |
486 | break; | |
487 | } | |
488 | } | |
21e8e712 | 489 | fillGameInfos(obj, playerColor == "w" ? 1 : 0); |
41534b92 BA |
490 | if (obj.randvar) toggleVisible("gameInfos"); |
491 | else toggleVisible("boardContainer"); | |
21e8e712 | 492 | toggleTurnIndicator(vr.turn == playerColor); |
41534b92 BA |
493 | }); |
494 | } | |
495 | ||
496 | function confirmStopGame() { | |
497 | if (confirm("Stop game?")) { | |
498 | send("gameover", { gid: gid, relay: true }); | |
499 | localStorage.removeItem("gid"); | |
500 | toggleVisible("gameStopped"); | |
501 | } | |
502 | } | |
503 | ||
504 | function toggleGameInfos() { | |
505 | if ($.getElementById("gameInfos").style.display == "none") | |
506 | toggleVisible("gameInfos"); | |
21e8e712 BA |
507 | else { |
508 | toggleVisible("boardContainer"); | |
509 | // Quickfix for the "vanished piece" bug (move played while on game infos) | |
510 | vr.setupPieces(); //TODO: understand better | |
511 | } | |
41534b92 BA |
512 | } |
513 | ||
514 | $.body.addEventListener("keydown", (e) => { | |
515 | if (!localStorage.getItem("gid")) return; | |
516 | if (e.keyCode == 27) confirmStopGame(); | |
517 | else if (e.keyCode == 32) { | |
518 | e.preventDefault(); | |
519 | toggleGameInfos(); | |
520 | } | |
521 | }); |