var core = require("./game-core"); var Player = core.Player; var io = require("socket.io-client"); var GRID_SIZE = core.GRID_SIZE; var CELL_WIDTH = core.CELL_WIDTH; var running = false; var user, socket, frame; var players, allPlayers; var kills; var timeout = undefined; var dirty = false; var deadFrames = 0; var requesting = -1; //frame that we are requesting at var frameCache = []; //Frames after our request var allowAnimation = true; var grid = new core.Grid(core.GRID_SIZE, function(row, col, before, after) { invokeRenderer("updateGrid", [row, col, before, after]); }); var mimiRequestAnimationFrame = window && window.document ? window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 30) } : function(callback) { window.setTimeout(callback, 1000 / 30) }; //Public API function connectGame(url, name, callback) { if (running) return; //Prevent multiple runs running = true; user = null; deadFrames = 0; //Socket connection io.j = []; io.sockets = []; socket = io(url, { "forceNew": true, upgrade: false, transports: ["websocket"] }); socket.on("connect", function() { console.info("Connected to server."); }); socket.on("game", function(data) { if (timeout != undefined) clearTimeout(timeout); //Initialize game. //TODO: display data.gameid --- game id # frame = data.frame; reset(); //Load players. data.players.forEach(function(p) { var pl = new Player(grid, p); addPlayer(pl); }); user = allPlayers[data.num]; if (!user) throw new Error(); setUser(user); //Load grid. var gridData = new Uint8Array(data.grid); for (var r = 0; r < grid.size; r++) { for (var c = 0; c < grid.size; c++) { var ind = gridData[r * grid.size + c] - 1; grid.set(r, c, ind === -1 ? null : players[ind]); } } invokeRenderer("paint", []); frame = data.frame; if (requesting !== -1) { //Update those cache frames after we updated game. var minFrame = requesting; requesting = -1; while (frameCache.length > frame - minFrame) processFrame(frameCache[frame - minFrame]); frameCache = []; } }); socket.on("notifyFrame", processFrame); socket.on("dead", function() { socket.disconnect(); //In case we didn"t get the disconnect call }); socket.on("disconnect", function() { if (!user) return; console.info("Server has disconnected. Creating new game."); socket.disconnect(); user.die(); dirty = true; paintLoop(); running = false; invokeRenderer("disconnect", []); }); socket.emit("hello", { name: name, type: 0, //Free-for-all gameid: -1 //Requested game-id, or -1 for anyone }, function(success, msg) { if (success) console.info("Connected to game!"); else { console.error("Unable to connect to game: " + msg); running = false; } if (callback) callback(success, msg); }); } function changeHeading(newHeading) { if (!user || user.dead) return; if (newHeading === user.currentHeading || ((newHeading % 2 === 0) ^ (user.currentHeading % 2 === 0))) { //user.heading = newHeading; if (socket) { socket.emit("frame", { frame: frame, heading: newHeading }, function(success, msg) { if (!success) console.error(msg); }); } } } function getUser() { return user; } function getOthers() { var ret = []; for (var p of players) { if (p !== user) ret.push(p); } return ret; } function getPlayers() { return players.slice(); } //Private API function addPlayer(player) { if (allPlayers[player.num]) return; //Already added allPlayers[player.num] = players[players.length] = player; invokeRenderer("addPlayer", [player]); return players.length - 1; } function invokeRenderer(name, args) { var renderer = exports.renderer; if (renderer && typeof renderer[name] === "function") renderer[name].apply(exports, args); } function processFrame(data) { if (timeout != undefined) clearTimeout(timeout); if (requesting !== -1 && requesting < data.frame) { frameCache.push(data); return; } if (data.frame - 1 !== frame) { console.error("Frames don\"t match up!"); socket.emit("requestFrame"); //Restore data requesting = data.frame; frameCache.push(data); return; } frame++; if (data.newPlayers) { data.newPlayers.forEach(function(p) { if (p.num === user.num) return; var pl = new Player(grid, p); addPlayer(pl); core.initPlayer(grid, pl); }); } var found = new Array(players.length); data.moves.forEach(function(val, i) { var player = allPlayers[val.num]; if (!player) return; if (val.left) player.die(); found[i] = true; player.heading = val.heading; }); for (var i = 0; i < players.length; i++) { //Implicitly leaving game if (!found[i]) { var player = players[i]; player && player.die(); } } update(); var locs = {}; for (var i = 0; i < players.length; i++) { var p = players[i]; locs[p.num] = [p.posX, p.posY, p.waitLag]; } /* socket.emit("verify", { frame: frame, locs: locs }, function(frame, success, adviceFix, msg) { if (!success && requesting === -1) { console.error(frame + ": " + msg); if (adviceFix) socket.emit("requestFrame"); } }.bind(this, frame)); */ dirty = true; mimiRequestAnimationFrame(function() { paintLoop(); }); timeout = setTimeout(function() { console.warn("Server has timed-out. Disconnecting."); socket.disconnect(); }, 3000); } function paintLoop() { if (!dirty) return; invokeRenderer("paint", []); dirty = false; if (user && user.dead) { if (timeout) clearTimeout(timeout); if (deadFrames === 60) { //One second of frame var before = allowAnimation; allowAnimation = false; update(); invokeRenderer("paint", []); allowAnimation = before; user = null; deadFrames = 0; return; } socket.disconnect(); deadFrames++; dirty = true; update(); mimiRequestAnimationFrame(paintLoop); } } function reset() { user = null; grid.reset(); players = []; allPlayers = []; kills = 0; invokeRenderer("reset"); } function setUser(player) { user = player; invokeRenderer("setUser", [player]); } function update() { var dead = []; core.updateFrame(grid, players, dead, function addKill(killer, other) { if (players[killer] === user && killer !== other) kills++; }); dead.forEach(function(val) { console.log((val.name || "Unnamed") + " is dead"); delete allPlayers[val.num]; invokeRenderer("removePlayer", [val]); }); invokeRenderer("update", [frame]); } //Export stuff var funcs = [connectGame, changeHeading, getOthers, getPlayers, getUser]; funcs.forEach(function(f) { exports[f.name] = f; }); exports.renderer = null; Object.defineProperties(exports, { allowAnimation: { get: function() { return allowAnimation; }, set: function(val) { allowAnimation = !!val; }, enumerable: true }, grid: { get: function() { return grid; }, enumerable: true }, kills: { get: function() { return kills; }, enumerable: true } });