From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sun, 18 Nov 2018 23:43:17 +0000 (+0100)
Subject: First draft of game balancing solution after reconnect
X-Git-Url: https://git.auder.net/doc/html/css/scripts/pieces/DESCRIPTION?a=commitdiff_plain;h=a29d9d6b7703d680ddb49cd3fe096f49b1d774f5;p=vchess.git

First draft of game balancing solution after reconnect
---

diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 160c692f..4a077417 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -1,4 +1,4 @@
-// TODO: use indexedDB instead of localStorage? (more flexible...)
+// TODO: use indexedDB instead of localStorage? (more flexible: allow several games)
 
 Vue.component('my-game', {
 	data: function() {
@@ -365,14 +365,13 @@ Vue.component('my-game', {
 		const socketOpenListener = () => {
 			if (continuation)
 			{
-				// TODO: check FEN integrity with opponent
 				const fen = localStorage.getItem("fen");
 				const mycolor = localStorage.getItem("mycolor");
 				const oppid = localStorage.getItem("oppid");
 				const moves = JSON.parse(localStorage.getItem("moves"));
 				this.newGame("human", fen, mycolor, oppid, moves, true);
-				// Send ping to server, which answers pong if opponent is connected
-				this.conn.send(JSON.stringify({code:"ping", oppid:this.oppId}));
+				// Send ping to server (answer pong if opponent is connected)
+				this.conn.send(JSON.stringify({code:"ping",oppid:this.oppId}));
 			}
 			else if (localStorage.getItem("newgame") === variant)
 			{
@@ -391,9 +390,46 @@ Vue.component('my-game', {
 				case "newmove": //..he played!
 					this.play(data.move, "animate");
 					break;
-				case "pong": //sent when opponent stayed online after we disconnected
+				case "pong": //received if opponent sent a ping
 					this.oppConnected = true;
+					const L = this.vr.moves.length;
+					// Send our "last state" informations to opponent (we are still playing)
+					this.conn.send(JSON.stringify({
+						code:"lastate",
+						oppid:this.oppId,
+						lastMove:L>0?this.vr.moves[L-1]:undefined,
+						movesCount:L,
+					}));
 					break;
+				case "lastate": //got opponent infos about last move (we might have resigned)
+					if (this.mode!="human" || this.oppid!=data.oppid)
+					{
+						// OK, we resigned
+						this.conn.send(JSON.stringify({
+							code:"lastate",
+							oppid:this.oppId,
+							lastMove:undefined,
+							movesCount:-1,
+						}));
+					}
+					else if (data.movesCount < 0)
+					{
+						// OK, he resigned
+						this.endGame(this.mycolor=="w"?"1-0":"0-1");
+					}
+					else if (data.movesCount < this.vr.moves.length)
+					{
+						// We must tell last move to opponent
+						const L = this.vr.moves.length;
+						this.conn.send(JSON.stringify({
+							code:"lastate",
+							oppid:this.oppId,
+							lastMove:this.vr.moves[L-1],
+							movesCount:L,
+						}));
+					}
+					else if (data.movesCount > this.vr.moves.length) //just got last move from him
+						this.play(data.lastMove, "animate");
 				case "resign": //..you won!
 					this.endGame(this.mycolor=="w"?"1-0":"0-1");
 					break;
@@ -433,7 +469,7 @@ Vue.component('my-game', {
 				try {
 					this.conn.send(JSON.stringify({code: "resign", oppid: this.oppid}));
 				} catch (INVALID_STATE_ERR) {
-					return; //resign failed for some reason...
+					return; //socket is not ready (and not yet reconnected)
 				}
 			}
 			this.endGame(this.mycolor=="w"?"0-1":"1-0");
@@ -646,15 +682,7 @@ Vue.component('my-game', {
 			this.incheck = this.vr.getCheckSquares(move, oppCol); //is opponent in check?
 			// Not programmatic, or animation is over
 			if (this.mode == "human" && this.vr.turn == this.mycolor)
-			{
-				if (!this.oppConnected)
-					return; //abort move if opponent is gone
-				try {
-					this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
-				} catch(INVALID_STATE_ERR) {
-					return; //abort also if sending failed
-				}
-			}
+				this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid}));
 			new Audio("/sounds/chessmove1.mp3").play();
 			this.vr.play(move, "ingame");
 			if (this.mode == "human")
diff --git a/sockets.js b/sockets.js
index f3d479d6..8548e2fe 100644
--- a/sockets.js
+++ b/sockets.js
@@ -1,16 +1,6 @@
-//const url = require('url');
+const url = require('url');
 const Variants = require("./variants");
 
-function getJsonFromUrl(url) {
-	var query = url.substr(2); //starts with "/?"
-	var result = {};
-	query.split("&").forEach(function(part) {
-		var item = part.split("=");
-		result[item[0]] = decodeURIComponent(item[1]);
-	});
-	return result;
-}
-
 module.exports = function(wss) {
 
 	let clients = { "index": {} };
@@ -18,21 +8,10 @@ module.exports = function(wss) {
 	for (const v of Variants)
 		clients[v.name] = {};
 
-	// (resign, newgame, newmove). See https://github.com/websockets/ws/blob/master/lib/websocket.js around line 313
-	// TODO: awaiting newmove, resign, (+newgame?) :: in memory structure (use Redis ?)
-	let newmoves = {};
-	let newresign = {};
-	for (const v of Variants)
-	{
-		newmoves[v.name] = {};
-		newresign[v.name] = {};
-	}
-
 	wss.on("connection", (socket, req) => {
-		//const params = new URL("http://localhost" + req.url).searchParams;
-		var query = getJsonFromUrl(req.url);
-		const sid = query["sid"]; //params.get("sid");
-		const page = query["page"]; //params.get("page");
+		const params = new URL("http://localhost" + req.url).searchParams;
+		const sid = params.get("sid");
+		const page = params.get("page");
 		clients[page][sid] = socket;
 		if (page == "index")
 		{
@@ -52,30 +31,22 @@ module.exports = function(wss) {
 			Object.keys(clients[page]).forEach( k => {
 				clients[page][k].send(JSON.stringify({code:"connect",id:sid}));
 			});
-			if (!!newmoves[page][sid])
-			{
-				socket.send(JSON.stringify({code:"newmove",move:newmoves[page][sid]}));
-				delete newmoves[page][sid];
-			}
-			if (!!newresign[page][sid])
-			{
-				socket.send(JSON.stringify({code:"resign"}));
-				delete newresign[page][sid];
-			}
 			socket.on("message", objtxt => {
 				let obj = JSON.parse(objtxt);
 				switch (obj.code)
 				{
 					case "newmove":
-						if (!!clients[page][obj.oppid]) // && clients[page][obj.oppid].readyState == WebSocket.OPEN)
+						if (!!clients[page][obj.oppid])
 							clients[page][obj.oppid].send(JSON.stringify({code:"newmove",move:obj.move}));
-						else
-							newmoves[page][obj.oppid] = obj.move;
 						break;
 					case "ping":
-						if (!!clients[page][obj.oppid]) // && clients[page][obj.oppid].readyState == WebSocket.OPEN)
+						if (!!clients[page][obj.oppid])
 							socket.send(JSON.stringify({code:"pong"}));
 						break;
+					case "lastate":
+						if (!!clients[page][obj.oppid])
+							clients[page][obj.oppid].send(objtxt);
+						break;
 					case "newgame":
 						if (!!games[page])
 						{
@@ -85,16 +56,15 @@ module.exports = function(wss) {
 							delete games[page];
 							const mycolor = Math.random() < 0.5 ? 'w' : 'b';
 							socket.send(JSON.stringify({code:"newgame",fen:fen,oppid:oppId,color:mycolor}));
-							clients[page][oppId].send(JSON.stringify({code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}));
+							if (!!clients[page][oppId])
+								clients[page][oppId].send(JSON.stringify({code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}));
 						}
 						else
 							games[page] = {id:sid, fen:obj.fen}; //wait for opponent
 						break;
 					case "resign":
-						if (!!clients[page][obj.oppid]) // && clients[page][obj.oppid].readyState == WebSocket.OPEN)
+						if (!!clients[page][obj.oppid])
 							clients[page][obj.oppid].send(JSON.stringify({code:"resign"}));
-						else
-							newresign[page][obj.oppid] = true;
 						break;
 				}
 			});