X-Git-Url: https://git.auder.net/?p=xogo.git;a=blobdiff_plain;f=server.js;h=e08d13b41d3d44a3d59e6e682e104fa037499371;hp=7dbbaac337dae96eff50c2bee9ccb9ae68df2501;hb=HEAD;hpb=f46a68b8ed74b54b6a26645b88d2a4ae48c1227a diff --git a/server.js b/server.js index 7dbbaac..06d9866 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,9 @@ const params = require("./parameters.js"); const WebSocket = require("ws"); -const wss = new WebSocket.Server( - {port: params.socket_port, path: params.socket_path}); +const wss = new WebSocket.Server({ + port: params.socket_port, + path: params.socket_path +}); let challenges = {}; //variantName --> socketId, name let games = {}; //gameId --> gameInfo (vname, fen, players, options, time) @@ -10,63 +12,64 @@ const variants = require("./variants.js"); const Crypto = require("crypto"); const randstrSize = 8; -const send = (sid, code, data) => { +function send(sid, code, data) { const socket = sockets[sid]; // If a player deletes local infos and then tries to resume a game, // sockets[oppSid] will probably not exist anymore: - if (socket) socket.send(JSON.stringify(Object.assign({ code: code }, data))); -}; + if (socket) + socket.send(JSON.stringify(Object.assign({code: code}, data))); +} + +function initializeGame(vname, players, options) { + const gid = + Crypto.randomBytes(randstrSize).toString("hex").slice(0, randstrSize); + games[gid] = { + vname: vname, + players: players, + options: options, + time: Date.now(), + moveHash: {} //set of moves hashes seen so far + }; + return gid; +} + +// Provide seed in case of, so that both players initialize with same FEN +function launchGame(gid) { + const gameInfo = Object.assign( + {seed: Math.floor(Math.random() * 19840), gid: gid}, + games[gid] + ); + // players array is supposed to be full: + for (const p of games[gid].players) + send(p.sid, "gamestart", gameInfo); +} -wss.on("connection", function connection(socket, req) { +function getRandomVariant() { + // Pick a variant at random in the list + const index = Math.floor(Math.random() * variants.length); + return variants[index].name; +} + +wss.on("connection", (socket, req) => { const sid = req.url.split("=")[1]; //...?sid=... sockets[sid] = socket; socket.isAlive = true; socket.on("pong", () => socket.isAlive = true); - - function launchGame(vname, players, options) { - const gid = - Crypto.randomBytes(randstrSize).toString("hex").slice(0, randstrSize); - games[gid] = { - vname: vname, - players: players.map(p => { - return (!p ? null : {sid: p.sid, name: p.name}); - }), - options: options, - time: Date.now() - }; - if (players.every(p => p)) { - const gameInfo = Object.assign( - // Provide seed so that both players initialize with same FEN - {seed: Math.floor(Math.random() * 1984), gid: gid}, - games[gid]); - for (const p of players) { - send(p.sid, - "gamestart", - Object.assign({randvar: p.randvar}, gameInfo)); - } - } - else { - // Incomplete players array: do not start game yet - send(sid, "gamecreated", {gid: gid}); - // If nobody joins within 5 minutes, delete game - setTimeout( - () => { - if (games[gid] && games[gid].players.some(p => !p)) - delete games[gid]; - }, - 5 * 60000 - ); - } + if (params.dev == true) { + const chokidar = require("chokidar"); + const watcher = chokidar.watch( + ["*.js", "*.css", "utils/", "variants/"], + {persistent: true}); + watcher.on("change", path => send(sid, "filechange", {path: path})); } - socket.on("message", (msg) => { const obj = JSON.parse(msg); switch (obj.code) { // Send challenge (may trigger game creation) case "seekgame": { - let opponent = undefined, - choice = undefined; - const vname = obj.vname, + let oppIndex = undefined, //variant name + choice = undefined; //variant finally played + const vname = obj.vname, //variant requested randvar = (obj.vname == "_random"); if (vname == "_random") { // Pick any current challenge if possible @@ -74,27 +77,32 @@ wss.on("connection", function connection(socket, req) { if (currentChalls.length >= 1) { choice = currentChalls[Math.floor(Math.random() * currentChalls.length)]; - opponent = challenges[choice]; + oppIndex = choice; } } else if (challenges[vname]) { - opponent = challenges[vname]; + // Anyone wanting to play the same variant ? choice = vname; + oppIndex = vname; } - if (opponent) { - delete challenges[choice]; - if (choice == "_random") { - // Pick a variant at random in the list - const index = Math.floor(Math.random() * variants.length); - choice = variants[index].name; - } + else if (challenges["_random"]) { + // Anyone accepting any variant (including vname) ? + choice = vname; + oppIndex = "_random"; + } + if (oppIndex) { + if (choice == "_random") + choice = getRandomVariant(); // Launch game let players = [ {sid: sid, name: obj.name, randvar: randvar}, - opponent + Object.assign({}, challenges[oppIndex]) ]; - if (Math.random() < 0.5) players = players.reverse(); - launchGame(choice, players, {}); //empty options => default + delete challenges[oppIndex]; + if (Math.random() < 0.5) + players = players.reverse(); + // Empty options = default + launchGame( initializeGame(choice, players, {}) ); } else // Place challenge and wait. 'randvar' indicate if we play anything @@ -106,27 +114,36 @@ wss.on("connection", function connection(socket, req) { games[obj.gid].fen = obj.fen; break; // Send back game informations - case "getgame": { - if (!games[obj.gid]) send(sid, "nogame"); - else send(sid, "gameinfo", games[obj.gid]); + case "getgame": + if (!games[obj.gid]) + send(sid, "nogame"); + else + send(sid, "gameinfo", games[obj.gid]); break; - } // Cancel challenge case "cancelseek": delete challenges[obj.vname]; break; // Receive rematch case "rematch": - if (!games[obj.gid]) send(sid, "closerematch"); + if (!games[obj.gid]) + send(sid, "closerematch"); else { const myIndex = (games[obj.gid].players[0].sid == sid ? 0 : 1); - if (!games[obj.gid].rematch) games[obj.gid].rematch = [false, false]; - games[obj.gid].rematch[myIndex] = true; + if (!games[obj.gid].rematch) + games[obj.gid].rematch = [0, 0]; + games[obj.gid].rematch[myIndex] = !obj.random ? 1 : 2; if (games[obj.gid].rematch[1-myIndex]) { // Launch new game, colors reversed - launchGame(games[obj.gid].vname, - games[obj.gid].players.reverse(), - games[obj.gid].options); + let vname = games[obj.gid].vname; + const allrand = games[obj.gid].rematch.every(r => r == 2); + if (allrand) + vname = getRandomVariant(); + games[obj.gid].players.forEach(p => p.randvar = allrand); + const gid = initializeGame(vname, + games[obj.gid].players.reverse(), + games[obj.gid].options); + launchGame(gid); } } break; @@ -149,29 +166,45 @@ wss.on("connection", function connection(socket, req) { ) { players = players.reverse(); } - launchGame(obj.vname, players, obj.options); + // Incomplete players array: do not start game yet + const gid = initializeGame(obj.vname, players, obj.options); + send(sid, "gamecreated", {gid: gid}); + // If nobody joins within 3 minutes, delete game + setTimeout( + () => { + if (games[gid] && games[gid].players.some(p => !p)) + delete games[gid]; + }, + 3 * 60000 + ); break; } // Join game vs. friend case "joingame": - if (!games[obj.gid]) send(sid, "jointoolate"); + if (!games[obj.gid]) + send(sid, "jointoolate"); else { - // Join a game (started by some other player) const emptySlot = games[obj.gid].players.findIndex(p => !p); - if (emptySlot < 0) send(sid, "jointoolate"); - games[obj.gid].players[emptySlot] = {sid: sid, name: obj.name}; - const gameInfo = Object.assign( - // Provide seed so that both players initialize with same FEN - {seed: Math.floor(Math.random()*1984), gid: obj.gid}, - games[obj.gid]); - for (const p of games[obj.gid].players) - send(p.sid, "gamestart", gameInfo); + if (emptySlot < 0) + send(sid, "jointoolate"); + else { + // Join a game (started by some other player) + games[obj.gid].players[emptySlot] = {sid: sid, name: obj.name}; + launchGame(obj.gid); + } } break; // Relay a move + update games object case "newmove": + // NOTE: still potential racing issues, but... fingers crossed + const hash = Crypto.createHash("md5") + .update(JSON.stringify(obj.fen)) + .digest("hex"); + if (games[obj.gid].moveHash[hash]) + break; + games[obj.gid].moveHash[hash] = true; games[obj.gid].fen = obj.fen; - games[obj.gid].time = Date.now(); //update timestamp in case of + games[obj.gid].time = Date.now(); //update useful if verrry slow game 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}); @@ -196,12 +229,20 @@ wss.on("connection", function connection(socket, req) { break; //only one challenge per player } } + for (let g of Object.values(games)) { + const myIndex = g.players.findIndex(p => p && p.sid == sid); + if (myIndex >= 0) { + if (g.rematch && g.rematch[myIndex] > 0) g.rematch[myIndex] = 0; + break; //only one game per player + } + } }); }); const heartbeat = setInterval(() => { wss.clients.forEach((ws) => { - if (ws.isAlive === false) return ws.terminate(); + if (ws.isAlive === false) + return ws.terminate(); ws.isAlive = false; ws.ping(); }); @@ -212,7 +253,8 @@ const dayInMillisecs = 24 * 60 * 60 * 1000; const killOldGames = setInterval(() => { const now = Date.now(); Object.keys(games).forEach(gid => { - if (now - games[gid].time >= dayInMillisecs) delete games[gid]; + if (now - games[gid].time >= dayInMillisecs) + delete games[gid]; }); }, dayInMillisecs);