From: Benjamin Auder Date: Mon, 15 Nov 2021 01:38:47 +0000 (+0100) Subject: Attempt to prevent 'lost moves'... X-Git-Url: https://git.auder.net/?p=xogo.git;a=commitdiff_plain;h=8a9f61cec20509fd4398169d4ce1da73157d32ab Attempt to prevent 'lost moves'... --- diff --git a/app.js b/app.js index 592d2a9..a46ceb3 100644 --- a/app.js +++ b/app.js @@ -295,6 +295,9 @@ const messageCenter = (msg) => { break; // Receive opponent's move: case "newmove": + send("gotmove", {fen: obj.fen, gid: gid}); + if (obj.fen == lastFen) break; //got this move already + lastFen = obj.fen; if (document.hidden) notifyMe("move"); vr.playReceivedMove(obj.moves, () => { if (vr.getCurrentScore(obj.moves[obj.moves.length-1]) != "*") { @@ -304,6 +307,16 @@ const messageCenter = (msg) => { else toggleTurnIndicator(true); }); break; + // The server notifies that it got our move: + case "gotmove": + if (obj.fen == lastFen) { + curMoves = []; + clearTimeout(timeout1); + clearTimeout(timeout2); + clearTimeout(timeout3); + callbackAfterConfirmation(); + } + break; // Opponent stopped game (draw, abort, resign...) case "gameover": toggleVisible("gameStopped"); @@ -376,25 +389,39 @@ function notifyMe(code) { } } -let curMoves = []; -const afterPlay = (move) => { //pack into one moves array, then send +let curMoves = [], + lastFen, lastMove, + timeout1, timeout2, timeout3; +const callbackAfterConfirmation = () => { + const result = vr.getCurrentScore(lastMove); + if (result != "*") { + setTimeout( () => { + toggleVisible("gameStopped"); + send("gameover", { gid: gid }); + }, 2000); + } +}; +const afterPlay = (move) => { + // Pack into one moves array, then send curMoves.push({ appear: move.appear, vanish: move.vanish, start: move.start, end: move.end }); + lastMove = move; if (vr.turn != playerColor) { toggleTurnIndicator(false); - send("newmove", { gid: gid, moves: curMoves, fen: vr.getFen() }); - curMoves = []; - const result = vr.getCurrentScore(move); - if (result != "*") { - setTimeout( () => { - toggleVisible("gameStopped"); - send("gameover", { gid: gid }); - }, 2000); - } + lastFen = vr.getFen(); + const sendMove = + () => send("newmove", {gid: gid, moves: curMoves, fen: lastFen}); + // Send move until we obtain confirmation or timeout, then callback + sendMove(); + timeout1 = setTimeout(sendMove, 500); + timeout2 = setTimeout(sendMove, 1500); + timeout3 = setTimeout( + () => alert("The move may be lost :( Please reload"), + 3000); } }; diff --git a/server.js b/server.js index c21d8bf..d8a2efa 100644 --- a/server.js +++ b/server.js @@ -6,8 +6,19 @@ const wss = new WebSocket.Server( let challenges = {}; //variantName --> socketId, name let games = {}; //gameId --> gameInfo (vname, fen, players, options) let sockets = {}; //socketId --> socket +let sendmoveTimeout1 = {}, + sendmoveTimeout2 = {}, + sendmoveRetry = {}, + stopRetry = {}; const variants = require("./variants.js"); +const clearTrySendMove = (gid) => { + clearTimeout(sendmoveTimeout1[gid]); + clearTimeout(sendmoveTimeout2[gid]); + clearInterval(sendmoveRetry[gid]); + clearTimeout(stopRetry[gid]); +}; + const send = (sid, code, data) => { const socket = sockets[sid]; // If a player delete local infos and then try to resume a game, @@ -119,7 +130,7 @@ wss.on('connection', function connection(socket, req) { delete challenges[obj.vname]; break; // Receive rematch - case "rematch": { + case "rematch": if (!games[obj.gid]) send(sid, "closerematch"); else { const myIndex = (games[obj.gid].players[0].sid == sid ? 0 : 1); @@ -133,17 +144,15 @@ wss.on('connection', function connection(socket, req) { } } break; - } - // Rematch cancellation - case "norematch": { + // Rematch cancellation + case "norematch": if (games[obj.gid]) { const myIndex = (games[obj.gid].players[0].sid == sid ? 0 : 1); send(games[obj.gid].players[1-myIndex].sid, "closerematch"); } break; - } // Create game vs. friend - case "creategame": + case "creategame": { let players = [ { sid: obj.player.sid, name: obj.player.name }, undefined @@ -156,8 +165,9 @@ wss.on('connection', function connection(socket, req) { } launchGame(obj.vname, players, obj.options); break; + } // Join game vs. friend - case "joingame": { + case "joingame": if (!games[obj.gid]) send(sid, "jointoolate"); else { // Join a game (started by some other player) @@ -172,16 +182,27 @@ wss.on('connection', function connection(socket, req) { send(games[obj.gid].players[i].sid, "gamestart", gameInfo); } break; - } // Relay a move + update games object - case "newmove": { - // TODO?: "pingback" strategy to ensure that move was transmitted + case "newmove": + // If already received this move: skip + if (games[obj.gid].fen == obj.fen) break; + // Notify sender that the move is received: + send(sid, "gotmove", {fen: obj.fen}); games[obj.gid].fen = obj.fen; const playingWhite = (games[obj.gid].players[0].sid == sid); const oppSid = games[obj.gid].players[playingWhite ? 1 : 0].sid; - send(oppSid, "newmove", { moves: obj.moves }); + const sendMove = + // NOTE: sending FEN also, to check it in "gotmove" below + () => send(oppSid, "newmove", {moves: obj.moves, fen: obj.fen}); + sendMove(); + sendmoveTimeout1[obj.gid] = setTimeout(sendMove, 500); + sendmoveTimeout2[obj.gid] = setTimeout(sendMove, 1500); + sendmoveRetry[obj.gid] = setInterval(sendMove, 5000); + stopRetry[obj.gid] = setTimeout(clearTrySendMove, 31000); + break; + case "gotmove": + if (games[obj.gid].fen == obj.fen) clearTrySendMove(obj.gid); break; - } // Relay "game ends" message case "gameover": { const playingWhite = (games[obj.gid].players[0].sid == sid);