diff --git a/.gitignore b/.gitignore index 2c7a78b..8507aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -/node_modules +node_modules npm-debug.log* log.txt +package-lock.json diff --git a/README.md b/README.md index c72eddd..1e0e242 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,29 @@ This is a clone of the original Paper-IO released by Voodoo, except for one aspe This is just a fun side-project for me. If you would want to use this code, it would be nice to let me know. +## Install + +```bash +git clone https://github.com/stevenjoezhang/paper.io.git +cd paper.io +npm install +``` + ## Running After cloning this repository, run the follow commands to install dependencies and set up server. Enjoy! ```bash -npm install npm start ``` + +## Build + +```bash +sudo npm install -g browserify uglify-es +browserify game-client.js | uglifyjs > public/bundle.js +``` + ## License This is licensed under MIT. As such, please provide due credit and link back to this repository if possible. diff --git a/client-modes/bot-mode.js b/client-modes/bot-mode.js index 1c74808..4b49376 100644 --- a/client-modes/bot-mode.js +++ b/client-modes/bot-mode.js @@ -1,11 +1,11 @@ if (process.argv.length < 3) { - console.log("Usage: node game-client-bot.js []") - process.exit(1); + console.log("Usage: node game-client-bot.js []") + process.exit(1); } var oldlog = console.log; console.log = function(msg) { - return oldlog('[' + new Date() + '] ' + msg); + return oldlog(`[${new Date()}] ${msg}`); } //TODO: add a land claiming algo (with coefficient parameters) @@ -25,265 +25,257 @@ var THRESHOLD = 10; var startFrame = -1; var endFrame = -1; -var coeffs = [0.6164220147940495,-2.519369747858328,0.9198978109542851,-1.2158956330674564,-3.072901620397528, 5, 4]; +var coeffs = [0.6164220147940495, -2.519369747858328, 0.9198978109542851, -1.2158956330674564, -3.072901620397528, 5, 4]; var grid, others, user, playerPortion; var DIST_TYPES = { - land: { - check: function(loc) { return grid.get(loc.row, loc.col) === user; }, - coeff: function() {return coeffs[0];} - }, tail: { - check: function(loc) {return tail(user, loc)}, - coeff: function() {return coeffs[1];} - }, oTail: { - check: foundProto(tail), - coeff: function() {return AGGRESSIVE * coeffs[2];} - }, other: { - check: foundProto(function(other, loc) { return other.row === this.row && other.col === this.col; }), - coeff: function() {return (1 - AGGRESSIVE) * coeffs[3];} - }, edge: { - check: function(loc) {return loc.row <= 1 || loc.col <= 1 || loc.row >= GRID_SIZE - 1 || loc.col >= GRID_SIZE - 1}, - coeff: function() {return coeffs[4];} - } + land: { + check: function(loc) { + return grid.get(loc.row, loc.col) === user; + }, + coeff: function() { + return coeffs[0]; + } + }, tail: { + check: function(loc) { + return tail(user, loc) + }, + coeff: function() { + return coeffs[1]; + } + }, oTail: { + check: foundProto(tail), + coeff: function() { + return AGGRESSIVE * coeffs[2]; + } + }, other: { + check: foundProto(function(other, loc) { + return other.row === this.row && other.col === this.col; + }), + coeff: function() { + return (1 - AGGRESSIVE) * coeffs[3]; + } + }, edge: { + check: function(loc) { + return loc.row <= 1 || loc.col <= 1 || loc.row >= GRID_SIZE - 1 || loc.col >= GRID_SIZE - 1 + }, + coeff: function() { + return coeffs[4]; + } + } }; function generateLandDirections() { - function mod(x) { - x %= 4; - if (x < 0) { - x += 4; - } - return x; - } + function mod(x) { + x %= 4; + if (x < 0) x += 4; + return x; + } - var breadth = Math.floor(Math.random() * coeffs[5]) + 1; - var spread = Math.floor(Math.random() * coeffs[6]) + 1; - var extra = Math.floor(Math.random() * 2) + 1; + var breadth = Math.floor(Math.random() * coeffs[5]) + 1; + var spread = Math.floor(Math.random() * coeffs[6]) + 1; + var extra = Math.floor(Math.random() * 2) + 1; + var ccw = Math.floor(Math.random() * 2) * 2 - 1; - var ccw = Math.floor(Math.random() * 2) * 2 - 1; + var dir = user.currentHeading; + var turns = [dir, mod(dir + ccw), mod(dir + ccw * 2), mod(dir + ccw * 3)]; + var lengths = [breadth, spread, breadth + extra, spread]; - var dir = user.currentHeading; - var turns = [dir, mod(dir + ccw), mod(dir + ccw * 2), mod(dir + ccw * 3)]; - var lengths = [breadth, spread, breadth + extra, spread]; - - var moves = []; - for (var i = 0; i < turns.length; i++) { - for (var j = 0; j < lengths[i]; j++) { - moves.push(turns[i]); - } - } + var moves = []; + for (var i = 0; i < turns.length; i++) { + for (var j = 0; j < lengths[i]; j++) { + moves.push(turns[i]); + } + } } var LAND_CLAIMS = { - rectDims: function() { - var - }, rectSpread: function() { - - } + rectDims: function() {}, + rectSpread: function() {} } function foundProto(func) { - return function(loc) { - return others.some(function(other) { - return func(other, loc); - }); - } + return function(loc) { + return others.some(function(other) { + return func(other, loc); + }); + } } function connect() { - client.connectGame(process.argv[2], process.argv[3] || '[BOT]', function(success, msg) { - if (!success) { - setTimeout(connect, 1000); - } - }); + client.connectGame(process.argv[2], process.argv[3] || "[BOT]", function(success, msg) { + if (!success) setTimeout(connect, 1000); + }); } function Loc(row, col, step) { - if (this.constructor != Loc) { - return new Loc(row, col, step); - } - - this.row = row; - this.col = col; - this.step = step; + if (this.constructor != Loc) return new Loc(row, col, step); + this.row = row; + this.col = col; + this.step = step; } //Projects vector b onto vector a function project(a, b) { - var factor = (b[0] * a[0] + b[1] * a[1]) / (a[0] * a[0] + a[1] * a[1]); - return [factor * a[0], factor * a[1]]; + var factor = (b[0] * a[0] + b[1] * a[1]) / (a[0] * a[0] + a[1] * a[1]); + return [factor * a[0], factor * a[1]]; } function tail(player, loc) { - return player.tail.hitsTail(loc); + return player.tail.hitsTail(loc); } function traverseGrid(dir) { - steps = new Array(GRID_SIZE * GRID_SIZE); - for (var i in steps) { - steps[i] = -1; - } + steps = new Array(GRID_SIZE * GRID_SIZE); + for (var i in steps) { + steps[i] = -1; + } - distWeights = {}; - for (var type in DIST_TYPES) { - distWeights[type] = 0; - } + distWeights = {}; + for (var type in DIST_TYPES) { + distWeights[type] = 0; + } - var row = user.row, col = user.col; - var minRow = Math.max(0, row - 10), maxRow = Math.min(GRID_SIZE, row + 10); - var minCol = Math.max(0, col - 10), maxCol = Math.min(GRID_SIZE, col + 10); + var row = user.row, col = user.col; + var minRow = Math.max(0, row - 10), maxRow = Math.min(GRID_SIZE, row + 10); + var minCol = Math.max(0, col - 10), maxCol = Math.min(GRID_SIZE, col + 10); - var proj = 0; - for (var i = 1; i >= -1; i-=2) { - proj = (1 + THRESHOLD) * i; - while (proj != 0) { - proj -= i; - var normRange = Math.abs(proj); - for (var norm = -normRange; norm <= normRange; norm++) { - for (var distType in distWeights) { - var move = MOVES[dir]; - var delta = THRESHOLD - Math.abs(proj); - var dist = Math.sign(proj) * delta * delta / (Math.abs(norm) + 1) - var loc = {row: proj * move[0] + norm * move[1], col: proj * move[1] + norm * move[0]}; - - loc.row += user.row; - loc.col += user.col; + var proj = 0; + for (var i = 1; i >= -1; i-=2) { + proj = (1 + THRESHOLD) * i; + while (proj != 0) { + proj -= i; + var normRange = Math.abs(proj); + for (var norm = -normRange; norm <= normRange; norm++) { + for (var distType in distWeights) { + var move = MOVES[dir]; + var delta = THRESHOLD - Math.abs(proj); + var dist = Math.sign(proj) * delta * delta / (Math.abs(norm) + 1) + var loc = {row: proj * move[0] + norm * move[1], col: proj * move[1] + norm * move[0]}; - if (loc.row < 0 || loc.row >= GRID_SIZE || loc.col < 0 || loc.col >= GRID_SIZE) { - continue; - } - if (DIST_TYPES[distType].check(loc)) { - distWeights[distType] += dist; - } - } - } - } - } + loc.row += user.row; + loc.col += user.col; - return distWeights; + if (loc.row < 0 || loc.row >= GRID_SIZE || loc.col < 0 || loc.col >= GRID_SIZE) continue; + if (DIST_TYPES[distType].check(loc)) distWeights[distType] += dist; + } + } + } + } + return distWeights; } function printGrid() { - var chars = new core.Grid(GRID_SIZE); - for (var r = 0; r < GRID_SIZE; r++) { - for (var c = 0; c < GRID_SIZE; c++) { - if (tail(user, {row: r, col: c})) { - chars.set(r, c, 't'); - } else { - var owner = grid.get(r, c); - chars.set(r, c, owner ? '' + owner.num % 10 : '.'); - } - } - } + var chars = new core.Grid(GRID_SIZE); + for (var r = 0; r < GRID_SIZE; r++) { + for (var c = 0; c < GRID_SIZE; c++) { + if (tail(user, {row: r, col: c})) chars.set(r, c, "t"); + else { + var owner = grid.get(r, c); + chars.set(r, c, owner ? "" + owner.num % 10 : "."); + } + } + } - for (var p of others) { - chars.set(p.row, p.col, 'x'); - } - chars.set(user.row, user.col, '^>V<'[user.currentHeading]); + for (var p of others) { + chars.set(p.row, p.col, "x"); + } + chars.set(user.row, user.col, "^>V<"[user.currentHeading]); - var str = ''; - for (var r = 0; r < GRID_SIZE; r++) { - str += '\n'; - for (var c = 0; c < GRID_SIZE; c++) { - str += chars.get(r, c); - } - } - console.log(str); + var str = ""; + for (var r = 0; r < GRID_SIZE; r++) { + str += "\n"; + for (var c = 0; c < GRID_SIZE; c++) { + str += chars.get(r, c); + } + } + console.log(str); } function update(frame) { - if (startFrame == -1) { - startFrame = frame; - } - endFrame = frame; + if (startFrame == -1) startFrame = frame; + endFrame = frame; + if (frame % 6 == 1) { + grid = client.grid; + others = client.getOthers(); + //printGrid(); + var weights = [0, 0, 0, 0]; + for (var d of [3, 0, 1]) { + var weight = 0; - if (frame % 6 == 1) { - grid = client.grid; - others = client.getOthers(); + d = (d + user.currentHeading) % 4; + distWeights = traverseGrid(d); - //printGrid(); + var str = d + ": " + for (var distType in DIST_TYPES) { + var point = distWeights[distType] * DIST_TYPES[distType].coeff(); + weight += point; + str += distType + ": " + point + ", "; + } + //console.log(str); + weights[d] = weight; + } - var weights = [0, 0, 0, 0]; - for (var d of [3, 0, 1]) { - var weight = 0; + var low = Math.min(0, Math.min.apply(this, weights)); + var total = 0; - d = (d + user.currentHeading) % 4; - distWeights = traverseGrid(d); + weights[(user.currentHeading + 2) % 4] = low; + for (var i = 0; i < weights.length; i++) { + weights[i] -= low * (1 + Math.random()); + total += weights[i]; + } - var str = d + ": " - for (var distType in DIST_TYPES) { - var point = distWeights[distType] * DIST_TYPES[distType].coeff(); - weight += point; - str += distType + ": " + point + ", "; - } - //console.log(str); - weights[d] = weight; - } - - var low = Math.min(0, Math.min.apply(this, weights)); - var total = 0; - - weights[(user.currentHeading + 2) % 4] = low; - for (var i = 0; i < weights.length; i++) { - weights[i] -= low * (1 + Math.random()); - total += weights[i]; - } - - if (total == 0) { - for (var d of [-1, 0, 1]) { - d = (d + user.currentHeading) % 4; - while (d < 0) d += 4; - weights[d] = 1; - total++; - } - } - - //console.log(weights) - - //Choose a random direction from the weighted list - var choice = Math.random() * total; - var d = 0; - while (choice > weights[d]) { - choice -= weights[d++]; - } - client.changeHeading(d); - } + if (total == 0) { + for (var d of [-1, 0, 1]) { + d = (d + user.currentHeading) % 4; + while (d < 0) d += 4; + weights[d] = 1; + total++; + } + } + //console.log(weights) + //Choose a random direction from the weighted list + var choice = Math.random() * total; + var d = 0; + while (choice > weights[d]) { + choice -= weights[d++]; + } + client.changeHeading(d); + } } function calcFavorability(params) { - return params.portion + params.kills * 50 + params.survival / 100; + return params.portion + params.kills * 50 + params.survival / 100; } client.allowAnimation = false; client.renderer = { - addPlayer: function(player) { - playerPortion[player.num] = 0; - }, - disconnect: function() { - var dt = (endFrame - startFrame); - startFrame = -1; - - console.log("I died... (survived for " + dt + " frames.)"); - console.log("I killed " + client.kills + " player(s)."); - console.log("Coefficients: " + coeffs) - - var mutation = Math.min(10, Math.pow(2, calcFavorability(params))); - for (var i = 0; i < coeffs.length; i++) { - coeffs[i] += Math.random() * mutation * 2 - mutation; - } + addPlayer: function(player) { + playerPortion[player.num] = 0; + }, + disconnect: function() { + var dt = (endFrame - startFrame); + startFrame = -1; + console.log("I died... (survived for " + dt + " frames.)"); + console.log("I killed " + client.kills + " player(s)."); + console.log("Coefficients: " + coeffs); - connect(); - }, - removePlayer: function(player) { - delete playerPortion[player.num]; - }, - setUser: function(u) { - user = u; - }, - update: update, - updateGrid: function(row, col, before, after) { - before && playerPortion[before.num]--; - after && playerPortion[after.num]++; - } + var mutation = Math.min(10, Math.pow(2, calcFavorability(params))); + for (var i = 0; i < coeffs.length; i++) { + coeffs[i] += Math.random() * mutation * 2 - mutation; + } + connect(); + }, + removePlayer: function(player) { + delete playerPortion[player.num]; + }, + setUser: function(u) { + user = u; + }, + update: update, + updateGrid: function(row, col, before, after) { + before && playerPortion[before.num]--; + after && playerPortion[after.num]++; + } }; connect(); diff --git a/client-modes/paper-io-bot-mode.js b/client-modes/paper-io-bot-mode.js index 442cbe0..f9e34b6 100644 --- a/client-modes/paper-io-bot-mode.js +++ b/client-modes/paper-io-bot-mode.js @@ -1,14 +1,13 @@ if (process.argv.length < 3) { - console.log("Usage: node game-client-bot.js []") - process.exit(1); + console.log("Usage: node game-client-bot.js []") + process.exit(1); } var oldlog = console.log; console.log = function(msg) { - return oldlog('[' + new Date() + '] ' + msg); + return oldlog(`[${new Date()}] ${msg}`); } - var core = require("../game-core"); var client = require("../client"); @@ -21,223 +20,192 @@ var endFrame = -1; var grid, others, user, playerPortion = {}, claim = []; function mod(x) { - x %= 4; - if (x < 0) { - x += 4; - } - return x; + x %= 4; + if (x < 0) x += 4; + return x; } function connect() { - client.connectGame(process.argv[2], process.argv[3] || '[PAPER-IO-BOT]', function(success, msg) { - if (!success) { - setTimeout(connect, 1000); - } - }); + client.connectGame(process.argv[2], process.argv[3] || "[PAPER-IO-BOT]", function(success, msg) { + if (!success) setTimeout(connect, 1000); + }); } function Loc(row, col) { - if (this.constructor != Loc) { - return new Loc(row, col); - } + if (this.constructor != Loc) return new Loc(row, col); - this.row = row; - this.col = col; + this.row = row; + this.col = col; } function update(frame) { - if (startFrame == -1) { - startFrame = frame; - } - endFrame = frame; + if (startFrame == -1) { + startFrame = frame; + } + endFrame = frame; - if (frame % 6 == (startFrame + 1) % 6) { - grid = client.grid; - others = client.getOthers(); + if (frame % 6 == (startFrame + 1) % 6) { + grid = client.grid; + others = client.getOthers(); - //Note: the code below isn't really my own code. This code is in fact the - //approximate algorithm used by the paper.io game. It has been modified from - //the original code (i.e. deobfuscating) and made more efficient in some - //areas (and some tweaks), otherwise, the original logic is about the same. - var row = user.row, col = user.col, dir = user.currentHeading; - var thres = (.05 + .1 * Math.random()) * GRID_SIZE * GRID_SIZE; + //Note: the code below isn't really my own code. This code is in fact the + //approximate algorithm used by the paper.io game. It has been modified from + //the original code (i.e. deobfuscating) and made more efficient in some + //areas (and some tweaks), otherwise, the original logic is about the same. + var row = user.row, col = user.col, dir = user.currentHeading; + var thres = (.05 + .1 * Math.random()) * GRID_SIZE * GRID_SIZE; - if (row < 0 || col < 0 || row >= GRID_SIZE || col >= GRID_SIZE) { - return; - } + if (row < 0 || col < 0 || row >= GRID_SIZE || col >= GRID_SIZE) return; - if (grid.get(row, col) === user) { - //When we are inside our territory - claim = []; - weights = [25, 25, 25, 25]; - weights[dir] = 100; - weights[mod(dir + 2)] = -9999; + if (grid.get(row, col) === user) { + //When we are inside our territory + claim = []; + weights = [25, 25, 25, 25]; + weights[dir] = 100; + weights[mod(dir + 2)] = -9999; - for (var nd = 0; nd < 4; nd++) { - for (var S = 1; S < 20; S++) { - var nr = MOVES[nd][0] * S + row; - var nc = MOVES[nd][1] * S + col; + for (var nd = 0; nd < 4; nd++) { + for (var S = 1; S < 20; S++) { + var nr = MOVES[nd][0] * S + row; + var nc = MOVES[nd][1] * S + col; - if (nr < 0 || nc < 0 || nr >= GRID_SIZE || nc >= GRID_SIZE) { - if (S > 1) { - weights[nd]--; - } else { - weights[nd] = -9999; - } - } else { - if (grid.get(nr, nc) !== user) { - weights[nd]--; - } + if (nr < 0 || nc < 0 || nr >= GRID_SIZE || nc >= GRID_SIZE) { + if (S > 1) weights[nd]--; + else weights[nd] = -9999; + } + else { + if (grid.get(nr, nc) !== user) weights[nd]--; - var tailed = undefined; - for (var o of others) { - if (o.tail.hitsTail(new Loc(nr, nc))) { - tailed = o; - break; - } - } + var tailed = undefined; + for (var o of others) { + if (o.tail.hitsTail(new Loc(nr, nc))) { + tailed = o; + break; + } + } - if (tailed) { - if (o.name.indexOf("PAPER") != -1) { //Don't really try to kill our own kind - weights[nd] += 3 * (30 - S); - } else { - weights[nd] += 30 * (30 - S); - } - } - } - } - } + if (tailed) { + if (o.name.indexOf("PAPER") != -1) weights[nd] += 3 * (30 - S); //Don't really try to kill our own kind + else weights[nd] += 30 * (30 - S); + } + } + } + } - //View a selection of choices based on the weights we computed - var choices = []; - for (var d = 0; d < 4; d++) { - for (var S = 1; S < weights[d]; S++) { - choices.push(d); - } - } + //View a selection of choices based on the weights we computed + var choices = []; + for (var d = 0; d < 4; d++) { + for (var S = 1; S < weights[d]; S++) { + choices.push(d); + } + } - if (choices.length === 0) { - choices.push(dir); - } - - dir = choices[Math.floor(Math.random() * choices.length)]; - } else if (playerPortion[user.num] < thres) { - //Claim some land if we are relatively tiny and have little to risk. - if (claim.length === 0) { - var breadth = 4 * Math.random() + 2; - var length = 4 * Math.random() + 2; - var ccw = 2 * Math.floor(2 * Math.random()) - 1; + if (choices.length === 0) choices.push(dir); + dir = choices[Math.floor(Math.random() * choices.length)]; + } + else if (playerPortion[user.num] < thres) { + //Claim some land if we are relatively tiny and have little to risk. + if (claim.length === 0) { + var breadth = 4 * Math.random() + 2; + var length = 4 * Math.random() + 2; + var ccw = 2 * Math.floor(2 * Math.random()) - 1; - turns = [dir, mod(dir + ccw), mod(dir + ccw * 2), mod(dir + ccw * 3)]; - lengths = [breadth, length, breadth + 2 * Math.random() + 1, length]; + turns = [dir, mod(dir + ccw), mod(dir + ccw * 2), mod(dir + ccw * 3)]; + lengths = [breadth, length, breadth + 2 * Math.random() + 1, length]; - for (var i = 0; i < turns.length; i++) { - for (var j = 0; j < lengths[i]; j++) { - claim.push(turns[i]); - } - } - } + for (var i = 0; i < turns.length; i++) { + for (var j = 0; j < lengths[i]; j++) { + claim.push(turns[i]); + } + } + } - if (claim.length !== 0) { - dir = claim.shift(); - } - } else { - claim = []; - //We are playing a little bit more cautious when we are outside and have a - //lot of land - weights = [5, 5, 5, 5]; - weights[dir] = 50; - weights[mod(dir + 2)] = -9999; + if (claim.length !== 0) dir = claim.shift(); + } + else { + claim = []; + //We are playing a little bit more cautious when we are outside and have a + //lot of land + weights = [5, 5, 5, 5]; + weights[dir] = 50; + weights[mod(dir + 2)] = -9999; - for (var nd = 0; nd < 4; nd++) { - for (var S = 1; S < 20; S++) { - var nr = MOVES[nd][0] * S + row; - var nc = MOVES[nd][1] * S + col; + for (var nd = 0; nd < 4; nd++) { + for (var S = 1; S < 20; S++) { + var nr = MOVES[nd][0] * S + row; + var nc = MOVES[nd][1] * S + col; - if (nr < 0 || nc < 0 || nr >= GRID_SIZE || nc >= GRID_SIZE) { - if (S > 1) { - weights[nd]--; - } else { - weights[nd] = -9999; - } - } else { - if (user.tail.hitsTail(new Loc(nr, nc))) { - if (S > 1) { - weights[nd] -= 50 - S; - } else { - weights[nd] = -9999; - } - } + if (nr < 0 || nc < 0 || nr >= GRID_SIZE || nc >= GRID_SIZE) { + if (S > 1) weights[nd]--; + else weights[nd] = -9999; + } + else { + if (user.tail.hitsTail(new Loc(nr, nc))) { + if (S > 1) weights[nd] -= 50 - S; + else weights[nd] = -9999; + } - if (grid.get(nr, nc) === user) { - weights[nd] += 10 + S; - } + if (grid.get(nr, nc) === user) weights[nd] += 10 + S; - var tailed = undefined; - for (var o of others) { - if (o.tail.hitsTail(new Loc(nr, nc))) { - tailed = o; - break; - } - } + var tailed = undefined; + for (var o of others) { + if (o.tail.hitsTail(new Loc(nr, nc))) { + tailed = o; + break; + } + } - if (tailed) { - if (o.name.indexOf("PAPER") != -1) { //Don't really try to kill our own kind - weights[nd] += 3 * (30 - S); - } else { - weights[nd] += 30 * (30 - S); - } - } - } - } - } + if (tailed) { + if (o.name.indexOf("PAPER") != -1) weights[nd] += 3 * (30 - S); //Don't really try to kill our own kind + else weights[nd] += 30 * (30 - S); + } + } + } + } - //View a selection of choices based on the weights we computed - var choices = []; - for (var d = 0; d < 4; d++) { - for (var S = 1; S < weights[d]; S++) { - choices.push(d); - } - } + //View a selection of choices based on the weights we computed + var choices = []; + for (var d = 0; d < 4; d++) { + for (var S = 1; S < weights[d]; S++) { + choices.push(d); + } + } - if (choices.length === 0) { - choices.push(dir); - } - - dir = choices[Math.floor(Math.random() * choices.length)]; - } - client.changeHeading(dir); - } + if (choices.length === 0) choices.push(dir); + dir = choices[Math.floor(Math.random() * choices.length)]; + } + client.changeHeading(dir); + } } function calcFavorability(params) { - return params.portion + params.kills * 50 + params.survival / 100; + return params.portion + params.kills * 50 + params.survival / 100; } client.allowAnimation = false; client.renderer = { - addPlayer: function(player) { - playerPortion[player.num] = 0; - }, - disconnect: function() { - var dt = (endFrame - startFrame); - startFrame = -1; - - console.log("I died... (survived for " + dt + " frames.)"); - console.log("I killed " + client.kills + " player(s)."); - connect(); - }, - removePlayer: function(player) { - delete playerPortion[player.num]; - }, - setUser: function(u) { - user = u; - }, - update: update, - updateGrid: function(row, col, before, after) { - before && playerPortion[before.num]--; - after && playerPortion[after.num]++; - } + addPlayer: function(player) { + playerPortion[player.num] = 0; + }, + disconnect: function() { + var dt = (endFrame - startFrame); + startFrame = -1; + + console.log("I died... (survived for " + dt + " frames.)"); + console.log("I killed " + client.kills + " player(s)."); + connect(); + }, + removePlayer: function(player) { + delete playerPortion[player.num]; + }, + setUser: function(u) { + user = u; + }, + update: update, + updateGrid: function(row, col, before, after) { + before && playerPortion[before.num]--; + after && playerPortion[after.num]++; + } }; connect(); diff --git a/client-modes/rolling.js b/client-modes/rolling.js index 67fcbb4..84f3cf9 100644 --- a/client-modes/rolling.js +++ b/client-modes/rolling.js @@ -1,27 +1,22 @@ +function Rolling(value, frames) { + var lag = 0; + if (!frames) frames = 24; + this.value = value; + Object.defineProperty(this, "lag", { + get: function() { + return lag; + }, + enumerable: true + }); + this.update = function() { + var delta = this.value - lag; + var dir = Math.sign(delta); + var speed = Math.abs(delta) / frames; + var mag = Math.min(Math.abs(speed), Math.abs(delta)); -function Rolling(value, frames) -{ - var lag = 0; - - if (!frames) - frames = 24; - - this.value = value; - - Object.defineProperty(this, "lag", { - get: function() { return lag; }, - enumerable: true - }); - this.update = function() { - var delta = this.value - lag; - var dir = Math.sign(delta); - var speed = Math.abs(delta) / frames; - var mag = Math.min(Math.abs(speed), Math.abs(delta)); - - lag += mag * dir; - return lag; - } + lag += mag * dir; + return lag; + } } module.exports = Rolling; - diff --git a/client-modes/user-mode.js b/client-modes/user-mode.js index 367c152..ae887bd 100644 --- a/client-modes/user-mode.js +++ b/client-modes/user-mode.js @@ -19,432 +19,339 @@ var BAR_WIDTH = 400; var canvas, canvasWidth, canvasHeight, gameWidth, gameHeight, ctx, offctx, offscreenCanvas; -$(function () { - canvas = $("#main-ui")[0]; - ctx = canvas.getContext('2d'); - - offscreenCanvas = document.createElement("canvas"); - offctx = offscreenCanvas.getContext('2d'); - - canvas.style.marginTop = 10; - updateSize(); +$(function() { + canvas = $("#main-ui")[0]; + ctx = canvas.getContext("2d"); + offscreenCanvas = document.createElement("canvas"); + offctx = offscreenCanvas.getContext("2d"); + canvas.style.marginTop = 10; + updateSize(); }); - -var animateGrid, playerPortion, portionsRolling, - barProportionRolling, animateTo, offset, user, zoom, showedDead; +var animateGrid, playerPortion, portionsRolling, barProportionRolling, animateTo, offset, user, zoom, showedDead; var grid = client.grid; -function updateSize() -{ - var changed = false; - if (canvasWidth != window.innerWidth) - { - gameWidth = canvasWidth = offscreenCanvas.width = canvas.width = window.innerWidth; - changed = true; - } - - if (canvasHeight != window.innerHeight - 20) - { - canvasHeight = offscreenCanvas.height = canvas.height = window.innerHeight - 20; - gameHeight = canvasHeight - BAR_HEIGHT; - changed = true; - } - - if (changed && user) - centerOnPlayer(user, offset); +function updateSize() { + var changed = false; + if (canvasWidth != window.innerWidth) { + gameWidth = canvasWidth = offscreenCanvas.width = canvas.width = window.innerWidth; + changed = true; + } + if (canvasHeight != window.innerHeight - 20) { + canvasHeight = offscreenCanvas.height = canvas.height = window.innerHeight - 20; + gameHeight = canvasHeight - BAR_HEIGHT; + changed = true; + } + if (changed && user) centerOnPlayer(user, offset); } function reset() { - animateGrid = new core.Grid(GRID_SIZE); - - playerPortion = []; - portionsRolling = []; - barProportionRolling = []; - - animateTo = [0, 0]; - offset = [0, 0]; - - user = null; - zoom = 1; - - showedDead = false; + animateGrid = new core.Grid(GRID_SIZE); + playerPortion = []; + portionsRolling = []; + barProportionRolling = []; + animateTo = [0, 0]; + offset = [0, 0]; + user = null; + zoom = 1; + showedDead = false; } reset(); -//Paint methods. -function paintGridBorder(ctx) -{ - ctx.fillStyle = 'lightgray'; - var gridWidth = CELL_WIDTH * GRID_SIZE; +//Paint methods +function paintGridBorder(ctx) { + ctx.fillStyle = "lightgray"; + var gridWidth = CELL_WIDTH * GRID_SIZE; - ctx.fillRect(-BORDER_WIDTH, 0, BORDER_WIDTH, gridWidth); - ctx.fillRect(-BORDER_WIDTH, -BORDER_WIDTH, gridWidth + BORDER_WIDTH * 2, BORDER_WIDTH); - ctx.fillRect(gridWidth, 0, BORDER_WIDTH, gridWidth); - ctx.fillRect(-BORDER_WIDTH, gridWidth, gridWidth + BORDER_WIDTH * 2, BORDER_WIDTH); + ctx.fillRect(-BORDER_WIDTH, 0, BORDER_WIDTH, gridWidth); + ctx.fillRect(-BORDER_WIDTH, -BORDER_WIDTH, gridWidth + BORDER_WIDTH * 2, BORDER_WIDTH); + ctx.fillRect(gridWidth, 0, BORDER_WIDTH, gridWidth); + ctx.fillRect(-BORDER_WIDTH, gridWidth, gridWidth + BORDER_WIDTH * 2, BORDER_WIDTH); } -function paintGrid(ctx) -{ - //Paint background. - ctx.fillStyle = "rgb(211, 225, 237)"; - ctx.fillRect(0, 0, CELL_WIDTH * GRID_SIZE, CELL_WIDTH * GRID_SIZE); - - paintGridBorder(ctx); - //paintGridLines(ctx); - - //Get viewing limits - var offsetX = (offset[0] - BORDER_WIDTH); - var offsetY = (offset[1] - BORDER_WIDTH); - - var minRow = Math.max(Math.floor(offsetY / CELL_WIDTH), 0); - var minCol = Math.max(Math.floor(offsetX / CELL_WIDTH), 0); - var maxRow = Math.min(Math.ceil((offsetY + gameHeight / zoom) / CELL_WIDTH), grid.size); - var maxCol = Math.min(Math.ceil((offsetX + gameWidth / zoom) / CELL_WIDTH), grid.size); - - //Paint occupied areas. (and fading ones). - for (var r = minRow; r < maxRow; r++) - { - for (var c = minCol; c < maxCol; c++) - { - var p = grid.get(r, c); - var x = c * CELL_WIDTH, y = r * CELL_WIDTH, baseColor, shadowColor; - - var animateSpec = animateGrid.get(r, c); - if (client.allowAnimation && animateSpec) - { - if (animateSpec.before) //fading animation - { - var frac = (animateSpec.frame / ANIMATE_FRAMES); - var back = new core.Color(.58, .41, .92, 1); - baseColor = animateSpec.before.lightBaseColor.interpolateToString(back, frac); - shadowColor = animateSpec.before.shadowColor.interpolateToString(back, frac); - } - else - continue; - } - else if (p) - { - baseColor = p.lightBaseColor; - shadowColor = p.shadowColor; - } - else //No animation nor is this player owned. - continue; - - var hasBottom = !grid.isOutOfBounds(r + 1, c); - var bottomAnimate = hasBottom && animateGrid.get(r + 1, c); - var totalStatic = !bottomAnimate && !animateSpec; - var bottomEmpty = totalStatic ? (hasBottom && !grid.get(r + 1, c)) : - (!bottomAnimate || (bottomAnimate.after && bottomAnimate.before)); - if (hasBottom && ((!!bottomAnimate ^ !!animateSpec) || bottomEmpty)) - { - - ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH + 1, SHADOW_OFFSET); - } - ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); - } - } - - if (!client.allowAnimation) - return; - - //Paint squares with drop in animation. - for (var r = 0; r < grid.size; r++) - { - for (var c = 0; c < grid.size; c++) - { - animateSpec = animateGrid.get(r, c); - x = c * CELL_WIDTH, y = r * CELL_WIDTH; - - if (animateSpec && client.allowAnimation) - { - var viewable = r >= minRow && r < maxRow && c >= minCol && c < maxCol; - if (animateSpec.after && viewable) - { - //Bouncing the squares. - var offsetBounce = getBounceOffset(animateSpec.frame); - y -= offsetBounce; - - shadowColor = animateSpec.after.shadowColor; - baseColor = animateSpec.after.lightBaseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1); - - ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); - ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); - } - - animateSpec.frame++; - if (animateSpec.frame >= ANIMATE_FRAMES) - animateGrid.set(r, c, null); - } - } - } +function paintGrid(ctx) { + //Paint background + ctx.fillStyle = "rgb(211, 225, 237)"; + ctx.fillRect(0, 0, CELL_WIDTH * GRID_SIZE, CELL_WIDTH * GRID_SIZE); + paintGridBorder(ctx); + + //paintGridLines(ctx); + //Get viewing limits + var offsetX = (offset[0] - BORDER_WIDTH); + var offsetY = (offset[1] - BORDER_WIDTH); + var minRow = Math.max(Math.floor(offsetY / CELL_WIDTH), 0); + var minCol = Math.max(Math.floor(offsetX / CELL_WIDTH), 0); + var maxRow = Math.min(Math.ceil((offsetY + gameHeight / zoom) / CELL_WIDTH), grid.size); + var maxCol = Math.min(Math.ceil((offsetX + gameWidth / zoom) / CELL_WIDTH), grid.size); + + //Paint occupied areas. (and fading ones) + for (var r = minRow; r < maxRow; r++) { + for (var c = minCol; c < maxCol; c++) { + var p = grid.get(r, c); + var x = c * CELL_WIDTH, y = r * CELL_WIDTH, baseColor, shadowColor; + var animateSpec = animateGrid.get(r, c); + if (client.allowAnimation && animateSpec) { + if (animateSpec.before) { //fading animation + var frac = (animateSpec.frame / ANIMATE_FRAMES); + var back = new core.Color(.58, .41, .92, 1); + baseColor = animateSpec.before.lightBaseColor.interpolateToString(back, frac); + shadowColor = animateSpec.before.shadowColor.interpolateToString(back, frac); + } + else continue; + } + else if (p) { + baseColor = p.lightBaseColor; + shadowColor = p.shadowColor; + } + else continue; //No animation nor is this player owned + var hasBottom = !grid.isOutOfBounds(r + 1, c); + var bottomAnimate = hasBottom && animateGrid.get(r + 1, c); + var totalStatic = !bottomAnimate && !animateSpec; + var bottomEmpty = totalStatic ? (hasBottom && !grid.get(r + 1, c)) : + (!bottomAnimate || (bottomAnimate.after && bottomAnimate.before)); + if (hasBottom && ((!!bottomAnimate ^ !!animateSpec) || bottomEmpty)) { + ctx.fillStyle = shadowColor.rgbString(); + ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH + 1, SHADOW_OFFSET); + } + ctx.fillStyle = baseColor.rgbString(); + ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); + } + } + if (!client.allowAnimation) return; + + //Paint squares with drop in animation + for (var r = 0; r < grid.size; r++) { + for (var c = 0; c < grid.size; c++) { + animateSpec = animateGrid.get(r, c); + x = c * CELL_WIDTH, y = r * CELL_WIDTH; + if (animateSpec && client.allowAnimation) { + var viewable = r >= minRow && r < maxRow && c >= minCol && c < maxCol; + if (animateSpec.after && viewable) { + //Bouncing the squares. + var offsetBounce = getBounceOffset(animateSpec.frame); + y -= offsetBounce; + shadowColor = animateSpec.after.shadowColor; + baseColor = animateSpec.after.lightBaseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1); + ctx.fillStyle = shadowColor.rgbString(); + ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); + ctx.fillStyle = baseColor.rgbString(); + ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); + } + animateSpec.frame++; + if (animateSpec.frame >= ANIMATE_FRAMES) animateGrid.set(r, c, null); + } + } + } } +function paintUIBar(ctx) { + //UI Bar background + ctx.fillStyle = "#24422c"; + ctx.fillRect(0, 0, canvasWidth, BAR_HEIGHT); -function paintUIBar(ctx) -{ - - //UI Bar background - ctx.fillStyle = "#24422c"; - ctx.fillRect(0, 0, canvasWidth, BAR_HEIGHT); - - var barOffset; - ctx.fillStyle = "white"; - ctx.font = "24px Changa"; - barOffset = (user && user.name) ? (ctx.measureText(user.name).width + 20) : 0; - ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); - - //Draw filled bar. - ctx.fillStyle = "rgba(180, 180, 180, .3)"; - ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); - - var userPortions = portionsRolling[user.num] ? portionsRolling[user.num].lag : 0; - var barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * userPortions + MIN_BAR_WIDTH); - ctx.fillStyle = user ? user.baseColor.rgbString() : ""; - ctx.fillRect(barOffset, 0, barSize, CELL_WIDTH); - ctx.fillStyle = user ? user.shadowColor.rgbString() : ""; - ctx.fillRect(barOffset, CELL_WIDTH, barSize, SHADOW_OFFSET); - - //TODO: dont reset kill count and zoom when we request frames. - //Percentage - ctx.fillStyle = "white"; - ctx.font = "18px Changa"; - ctx.fillText((userPortions * 100).toFixed(3) + "%", 5 + barOffset, CELL_WIDTH - 5); - - //Number of kills - var killsText = "Kills: " + client.kills; - var killsOffset = 20 + BAR_WIDTH + barOffset; - ctx.fillText(killsText, killsOffset, CELL_WIDTH - 5); - - //Calcuate rank - var sorted = []; - client.getPlayers().forEach(function(val) { - sorted.push({player: val, portion: playerPortion[val.num]}); - }); - sorted.sort(function(a, b) { - if (a.portion === b.portion) return a.player.num - b.player.num; - else return b.portion - a.portion; - }); - - var rank = sorted.findIndex(function(val) {return val.player === user}); - ctx.fillText("Rank: " + (rank === -1 ? "--" : rank + 1) + " of " + sorted.length, - ctx.measureText(killsText).width + killsOffset + 20, CELL_WIDTH - 5); - - //Rolling the leaderboard bars. - if (sorted.length > 0) - { - var maxPortion = sorted[0].portion; - client.getPlayers().forEach(function(player) { - var rolling = barProportionRolling[player.num]; - rolling.value = playerPortion[player.num] / maxPortion; - rolling.update(); - }); - } - - //Show leaderboard. - var leaderboardNum = Math.min(5, sorted.length); - for (var i = 0; i < leaderboardNum; i++) - { - var player = sorted[i].player; - var name = player.name || "Unnamed"; - var portion = barProportionRolling[player.num].lag; - - var nameWidth = ctx.measureText(name).width; - barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * portion + MIN_BAR_WIDTH); - var barX = canvasWidth - barSize; - var barY = BAR_HEIGHT * (i + 1); - var offset = i == 0 ? 10 : 0; - - ctx.fillStyle = 'rgba(10, 10, 10, .3)'; - ctx.fillRect(barX - 10, barY + 10 - offset, barSize + 10, BAR_HEIGHT + offset); - ctx.fillStyle = player.baseColor.rgbString(); - ctx.fillRect(barX, barY, barSize, CELL_WIDTH); - ctx.fillStyle = player.shadowColor.rgbString(); - ctx.fillRect(barX, barY + CELL_WIDTH, barSize, SHADOW_OFFSET); - - ctx.fillStyle = "black"; - ctx.fillText(name, barX - nameWidth - 15, barY + 27); - - var percentage = (portionsRolling[player.num].lag * 100).toFixed(3) + "%"; - ctx.fillStyle = "white"; - ctx.fillText(percentage, barX + 5, barY + CELL_WIDTH - 5); - } - + var barOffset; + ctx.fillStyle = "white"; + ctx.font = "24px Changa"; + barOffset = (user && user.name) ? (ctx.measureText(user.name).width + 20) : 0; + ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); + + //Draw filled bar + ctx.fillStyle = "rgba(180, 180, 180, .3)"; + ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); + + var userPortions = portionsRolling[user.num] ? portionsRolling[user.num].lag : 0; + var barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * userPortions + MIN_BAR_WIDTH); + ctx.fillStyle = user ? user.baseColor.rgbString() : ""; + ctx.fillRect(barOffset, 0, barSize, CELL_WIDTH); + ctx.fillStyle = user ? user.shadowColor.rgbString() : ""; + ctx.fillRect(barOffset, CELL_WIDTH, barSize, SHADOW_OFFSET); + + //TODO: dont reset kill count and zoom when we request frames. + //Percentage + ctx.fillStyle = "white"; + ctx.font = "18px Changa"; + ctx.fillText((userPortions * 100).toFixed(3) + "%", 5 + barOffset, CELL_WIDTH - 5); + + //Number of kills + var killsText = "Kills: " + client.kills; + var killsOffset = 20 + BAR_WIDTH + barOffset; + ctx.fillText(killsText, killsOffset, CELL_WIDTH - 5); + + //Calcuate rank + var sorted = []; + client.getPlayers().forEach(function(val) { + sorted.push({player: val, portion: playerPortion[val.num]}); + }); + sorted.sort(function(a, b) { + return (a.portion === b.portion) ? a.player.num - b.player.num : b.portion - a.portion; + }); + + var rank = sorted.findIndex(function(val) { + return val.player === user + }); + ctx.fillText("Rank: " + (rank === -1 ? "--" : rank + 1) + " of " + sorted.length, + ctx.measureText(killsText).width + killsOffset + 20, CELL_WIDTH - 5); + + //Rolling the leaderboard bars + if (sorted.length > 0) { + var maxPortion = sorted[0].portion; + client.getPlayers().forEach(function(player) { + var rolling = barProportionRolling[player.num]; + rolling.value = playerPortion[player.num] / maxPortion; + rolling.update(); + }); + } + + //Show leaderboard + var leaderboardNum = Math.min(5, sorted.length); + for (var i = 0; i < leaderboardNum; i++) { + var player = sorted[i].player; + var name = player.name || "Unnamed"; + var portion = barProportionRolling[player.num].lag; + var nameWidth = ctx.measureText(name).width; + barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * portion + MIN_BAR_WIDTH); + var barX = canvasWidth - barSize; + var barY = BAR_HEIGHT * (i + 1); + var offset = i == 0 ? 10 : 0; + ctx.fillStyle = "rgba(10, 10, 10, .3)"; + ctx.fillRect(barX - 10, barY + 10 - offset, barSize + 10, BAR_HEIGHT + offset); + ctx.fillStyle = player.baseColor.rgbString(); + ctx.fillRect(barX, barY, barSize, CELL_WIDTH); + ctx.fillStyle = player.shadowColor.rgbString(); + ctx.fillRect(barX, barY + CELL_WIDTH, barSize, SHADOW_OFFSET); + ctx.fillStyle = "black"; + ctx.fillText(name, barX - nameWidth - 15, barY + 27); + var percentage = (portionsRolling[player.num].lag * 100).toFixed(3) + "%"; + ctx.fillStyle = "white"; + ctx.fillText(percentage, barX + 5, barY + CELL_WIDTH - 5); + } } -function paint(ctx) -{ - - ctx.fillStyle = '#e2ebf3'; //'whitesmoke'; - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - - //Move grid to viewport as said with the offsets, below the stats - ctx.save(); - ctx.translate(0, BAR_HEIGHT); - ctx.beginPath(); - ctx.rect(0, 0, gameWidth, gameHeight); - ctx.clip(); - - //Zoom in/out based on player stats. - ctx.scale(zoom, zoom); - ctx.translate(-offset[0] + BORDER_WIDTH, -offset[1] + BORDER_WIDTH); - - paintGrid(ctx); - client.getPlayers().forEach(function (p) { - var fr = p.waitLag; - if (fr < ANIMATE_FRAMES) - p.render(ctx, fr / ANIMATE_FRAMES); - else - p.render(ctx); - }); - - //Reset transform to paint fixed UI elements - ctx.restore(); - paintUIBar(ctx); - - if ((!user || user.dead) && !showedDead) - { - showedDead = true; - console.log("You died!"); - //return; - } +function paint(ctx) { + ctx.fillStyle = "#e2ebf3"; //"whitesmoke"; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + //Move grid to viewport as said with the offsets, below the stats + ctx.save(); + ctx.translate(0, BAR_HEIGHT); + ctx.beginPath(); + ctx.rect(0, 0, gameWidth, gameHeight); + ctx.clip(); + + //Zoom in/out based on player stats + ctx.scale(zoom, zoom); + ctx.translate(-offset[0] + BORDER_WIDTH, -offset[1] + BORDER_WIDTH); + + paintGrid(ctx); + client.getPlayers().forEach(function (p) { + var fr = p.waitLag; + if (fr < ANIMATE_FRAMES) p.render(ctx, fr / ANIMATE_FRAMES); + else p.render(ctx); + }); + + //Reset transform to paint fixed UI elements + ctx.restore(); + paintUIBar(ctx); + + if ((!user || user.dead) && !showedDead) { + showedDead = true; + console.log("You died!"); + //return; + } } -function paintDoubleBuff() -{ - paint(offctx); - ctx.drawImage(offscreenCanvas, 0, 0); +function paintDoubleBuff() { + paint(offctx); + ctx.drawImage(offscreenCanvas, 0, 0); } function update() { - updateSize(); - - //Change grid offsets. - for (var i = 0; i <= 1; i++) - { - if (animateTo[i] !== offset[i]) - { - if (client.allowAnimation) - { - var delta = animateTo[i] - offset[i]; - var dir = Math.sign(delta); - var mag = Math.min(SPEED, Math.abs(delta)); - offset[i] += dir * mag; - } - else - offset[i] = animateTo[i]; - } - } - - //Calculate player portions. - client.getPlayers().forEach(function(player) { - var roll = portionsRolling[player.num]; - roll.value = playerPortion[player.num] / GRID_SIZE / GRID_SIZE; - roll.update(); - }); - - //Zoom goes from 1 to .5, decreasing as portion goes up. TODO: maybe can modify this? - if (portionsRolling[user.num]) - zoom = 1 / (portionsRolling[user.num].lag + 1); - - //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. - if (user) centerOnPlayer(user, animateTo); + updateSize(); + + //Change grid offsets + for (var i = 0; i <= 1; i++) { + if (animateTo[i] !== offset[i]) { + if (client.allowAnimation) { + var delta = animateTo[i] - offset[i]; + var dir = Math.sign(delta); + var mag = Math.min(SPEED, Math.abs(delta)); + offset[i] += dir * mag; + } + else offset[i] = animateTo[i]; + } + } + + //Calculate player portions + client.getPlayers().forEach(function(player) { + var roll = portionsRolling[player.num]; + roll.value = playerPortion[player.num] / GRID_SIZE / GRID_SIZE; + roll.update(); + }); + + //Zoom goes from 1 to .5, decreasing as portion goes up. TODO: maybe can modify this? + if (portionsRolling[user.num]) zoom = 1 / (portionsRolling[user.num].lag + 1); + //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. + if (user) centerOnPlayer(user, animateTo); } -//Helper methods. -function centerOnPlayer(player, pos) -{ - var xOff = Math.floor(player.posX - (gameWidth / zoom - CELL_WIDTH) / 2); - var yOff = Math.floor(player.posY - (gameHeight / zoom - CELL_WIDTH) / 2); - var gridWidth = grid.size * CELL_WIDTH + BORDER_WIDTH * 2; - pos[0] = xOff; //Math.max(Math.min(xOff, gridWidth + (BAR_WIDTH + 100) / zoom - gameWidth / zoom), 0); - pos[1] = yOff; //Math.max(Math.min(yOff, gridWidth - gameHeight / zoom), 0); +//Helper methods +function centerOnPlayer(player, pos) { + var xOff = Math.floor(player.posX - (gameWidth / zoom - CELL_WIDTH) / 2); + var yOff = Math.floor(player.posY - (gameHeight / zoom - CELL_WIDTH) / 2); + var gridWidth = grid.size * CELL_WIDTH + BORDER_WIDTH * 2; + pos[0] = xOff; //Math.max(Math.min(xOff, gridWidth + (BAR_WIDTH + 100) / zoom - gameWidth / zoom), 0); + pos[1] = yOff; //Math.max(Math.min(yOff, gridWidth - gameHeight / zoom), 0); } -function getBounceOffset(frame) -{ - var offsetBounce = ANIMATE_FRAMES; - var bounceNum = BOUNCE_FRAMES.length - 1; - while (bounceNum >= 0 && frame < offsetBounce - BOUNCE_FRAMES[bounceNum]) - { - offsetBounce -= BOUNCE_FRAMES[bounceNum]; - bounceNum--; - } - - if (bounceNum === -1) - { - return (offsetBounce - frame) * DROP_SPEED; - } - else - { - offsetBounce -= BOUNCE_FRAMES[bounceNum]; - frame = frame - offsetBounce; - var midFrame = BOUNCE_FRAMES[bounceNum] / 2; - if (frame >= midFrame) - return (BOUNCE_FRAMES[bounceNum] - frame) * DROP_SPEED; - else - return frame * DROP_SPEED; - } -} - -function showStats() { - //TODO: Show score stats. - $("#begin").removeClass("hidden"); - $("#begin").animate({ - opacity: .9999 - }, 1000, function() { - $("#stats").addClass("hidden").css("opacity", 0); - }); +function getBounceOffset(frame) { + var offsetBounce = ANIMATE_FRAMES; + var bounceNum = BOUNCE_FRAMES.length - 1; + while (bounceNum >= 0 && frame < offsetBounce - BOUNCE_FRAMES[bounceNum]) { + offsetBounce -= BOUNCE_FRAMES[bounceNum]; + bounceNum--; + } + if (bounceNum === -1) return (offsetBounce - frame) * DROP_SPEED; + else { + offsetBounce -= BOUNCE_FRAMES[bounceNum]; + frame = frame - offsetBounce; + var midFrame = BOUNCE_FRAMES[bounceNum] / 2; + return (frame >= midFrame) ? (BOUNCE_FRAMES[bounceNum] - frame) * DROP_SPEED : frame * DROP_SPEED; + } } module.exports = exports = { - addPlayer: function(player) { - playerPortion[player.num] = 0; - portionsRolling[player.num] = new Rolling(9 / GRID_SIZE / GRID_SIZE, ANIMATE_FRAMES); - barProportionRolling[player.num] = new Rolling(0, ANIMATE_FRAMES); - }, - disconnect: function() { - //Show score stats. - $("#stats").removeClass("hidden"); - $("#stats").animate({ - opacity: .9999 - }, 3000, function() { - showStats(); - //Then fade back into the login screen. - }); - }, - removePlayer: function(player) { - delete playerPortion[player.num]; - delete portionsRolling[player.num]; - delete barProportionRolling[player.num]; - }, - - setUser: function(player) { - user = player; - centerOnPlayer(user, offset); - }, - reset: reset, - updateGrid: function(row, col, before, after) { - //Keep track of areas. - if (before) - playerPortion[before.num]--; - if (after) - playerPortion[after.num]++; - - //Queue animation - if (before === after || !client.allowAnimation) - return; - animateGrid.set(row, col, { - before: before, - after: after, - frame: 0 - }); - }, - paint: paintDoubleBuff, - update: update + addPlayer: function(player) { + playerPortion[player.num] = 0; + portionsRolling[player.num] = new Rolling(9 / GRID_SIZE / GRID_SIZE, ANIMATE_FRAMES); + barProportionRolling[player.num] = new Rolling(0, ANIMATE_FRAMES); + }, + disconnect: function() { + $("#begin").fadeIn(1000); + $("#main-ui").fadeOut(1000); + }, + removePlayer: function(player) { + delete playerPortion[player.num]; + delete portionsRolling[player.num]; + delete barProportionRolling[player.num]; + }, + setUser: function(player) { + user = player; + centerOnPlayer(user, offset); + }, + reset: reset, + updateGrid: function(row, col, before, after) { + //Keep track of areas + if (before) playerPortion[before.num]--; + if (after) playerPortion[after.num]++; + //Queue animation + if (before === after || !client.allowAnimation) return; + animateGrid.set(row, col, { + before: before, + after: after, + frame: 0 + }); + }, + paint: paintDoubleBuff, + update: update }; diff --git a/client.js b/client.js index bbb0df1..7c3476f 100644 --- a/client.js +++ b/client.js @@ -1,382 +1,291 @@ -var core = require('./game-core'); +var core = require("./game-core"); var Player = core.Player; - -var io = require('socket.io-client'); - +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 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]); -}); + invokeRenderer("updateGrid", [row, col, before, after]); +}); -/** - * Provides requestAnimationFrame in a cross browser way. (edited so that this is also compatible with node.) - * @author paulirish / http://paulirish.com/ - */ -// window.requestAnimationFrame = function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { -// window.setTimeout( callback, 1000 / 60 ); -// }; - -var hasWindow; -try { - window.document; - hasWindow = true; -} catch (e) { - hasWindow = false; -} - -var requestAnimationFrame; -if ( !requestAnimationFrame ) { - requestAnimationFrame = ( function() { - if (hasWindow) { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } else { - return function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } - })(); -} +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); - }); - - + 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); - }); - } - } + 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; + return user; } function getOthers() { - var ret = []; - for (var p of players) { - if (p !== user) { - ret.push(p); - } - } - return ret; + var ret = []; + for (var p of players) { + if (p !== user) ret.push(p); + } + return ret; } function getPlayers() { - return players.slice(); + 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; + 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); + 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; - requestAnimationFrame(function() { - paintLoop(); - }); - timeout = setTimeout(function() { - console.warn('Server has timed-out. Disconnecting.'); - socket.disconnect(); - }, 3000); +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(); - requestAnimationFrame(paintLoop); - } +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'); + user = null; + grid.reset(); + players = []; + allPlayers = []; + kills = 0; + invokeRenderer("reset"); } function setUser(player) { - user = player; - invokeRenderer('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]); + 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; +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 - } + 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 + } }); diff --git a/game-client.js b/game-client.js index 9be4399..4dc64ff 100644 --- a/game-client.js +++ b/game-client.js @@ -1,128 +1,77 @@ /* global $ */ + var client = require("./client"); var core = require("./game-core"); -var io = require('socket.io-client'); - +var io = require("socket.io-client"); var GRID_SIZE = core.GRID_SIZE; var CELL_WIDTH = core.CELL_WIDTH; - client.allowAnimation = true; client.renderer = require("./client-modes/user-mode"); - -/** - * Provides requestAnimationFrame in a cross browser way. (edited so that this is also compatible with node.) - * @author paulirish / http://paulirish.com/ - */ -// window.requestAnimationFrame = function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { -// window.setTimeout( callback, 1000 / 60 ); -// }; -var hasWindow; -try { - window.document; - hasWindow = true; -} catch (e) { - hasWindow = false; -} -var requestAnimationFrame; -if ( !requestAnimationFrame ) { - requestAnimationFrame = ( function() { - if (hasWindow) { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } else { - return function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } - })(); -} +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) }; function run() { - client.connectGame('//' + window.location.hostname + ':8081', $('#name').val(), function(success, msg) { - if (success) - { - $("#begin").addClass("hidden"); - $("#begin").animate({ - opacity: 0 - }, 1000); - } - else - { - var error = $("#error"); - error.text(msg); - } - }); + client.connectGame("//" + window.location.hostname + ":8081", $("#name").val(), function(success, msg) { + if (success) { + $("#begin").fadeOut(1000); + $("#main-ui").fadeIn(1000); + } + else { + var error = $("#error"); + error.text(msg); + } + }); } - $(function() { - var error = $("#error"); - - if (!window.WebSocket) - { - error.text("Your browser does not support WebSockets!"); - return; - } - - error.text("Loading..."); //TODO: show loading screen. - var success = false; - var socket = io('http://' + window.location.hostname + ':8081', { - 'forceNew': true, - upgrade: false, - transports: ['websocket'] - }); - - socket.on('connect_error', function() { - if (!success) - error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)"); - }); - socket.emit("checkConn", function() { - success = true; - socket.disconnect(); - }); - setTimeout(function() { - if (!success) - error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)"); - else - { - error.text(""); - $("input").keypress(function(evt) { - if (evt.which === 13) - requestAnimationFrame(run); - }); - $("button").click(function(evt) { - requestAnimationFrame(run); - }); - } - }, 2000); + var error = $("#error"); + if (!window.WebSocket) { + error.text("Your browser does not support WebSockets!"); + return; + } + error.text("Loading... Please wait"); //TODO: show loading screen. + var socket = io("//" + window.location.hostname + ":8081", { + forceNew: true, + upgrade: false, + transports: ["websocket"] + }); + socket.on("connect", function() { + socket.emit("pings"); + }); + socket.on("pongs", function() { + socket.disconnect(); + error.text("All done, have fun!"); + $("#name").keypress(function(evt) { + if (evt.which === 13) mimiRequestAnimationFrame(run); + }); + $("#start").removeAttr("disabled").click(function(evt) { + mimiRequestAnimationFrame(run); + }); + }); + socket.on("connect_error", function() { + error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)"); + }); }); - - //Event listeners $(document).keydown(function(e) { - var newHeading = -1; - switch (e.which) - { - case 37: newHeading = 3; break; //LEFT - case 65: newHeading = 3; break; //LEFT (A) - - case 38: newHeading = 0; break; //UP - case 87: newHeading = 0; break; //UP (W) - - case 39: newHeading = 1; break; //RIGHT - case 68: newHeading = 1; break; //RIGHT (D) - - case 40: newHeading = 2; break; //DOWN - case 83: newHeading = 2; break; //DOWN (S) - default: return; //exit handler for other keys. - } - - client.changeHeading(newHeading); - e.preventDefault(); + var newHeading = -1; + switch (e.which) { + case 37: newHeading = 3; break; //LEFT + case 65: newHeading = 3; break; //LEFT (A) + case 38: newHeading = 0; break; //UP + case 87: newHeading = 0; break; //UP (W) + case 39: newHeading = 1; break; //RIGHT + case 68: newHeading = 1; break; //RIGHT (D) + case 40: newHeading = 2; break; //DOWN + case 83: newHeading = 2; break; //DOWN (S) + default: return; //exit handler for other keys. + } + client.changeHeading(newHeading); + e.preventDefault(); }); diff --git a/game-core/color.js b/game-core/color.js index ef574f9..9c6a079 100644 --- a/game-core/color.js +++ b/game-core/color.js @@ -1,100 +1,88 @@ - - -function Color(h, s, l, a) -{ - verifyRange(h, s, l); - if (a === undefined) a = 1; - else verifyRange(a); - - Object.defineProperties(this, { - "hue": {value: h, enumerable: true}, - "sat": {value: s, enumerable: true}, - "lum": {value: l, enumerable: true}, - "alpha": {value: a, enumerable: true}, - }); +function Color(h, s, l, a) { + verifyRange(h, s, l); + if (a === undefined) a = 1; + else verifyRange(a); + Object.defineProperties(this, { + "hue": { + value: h, + enumerable: true + }, + "sat": { + value: s, + enumerable: true + }, + "lum": { + value: l, + enumerable: true + }, + "alpha": { + value: a, + enumerable: true + }, + }); } - Color.fromData = function(data) { - return new Color(data.hue, data.sat, data.lum, data.alpha); + return new Color(data.hue, data.sat, data.lum, data.alpha); }; -function verifyRange() -{ - for (var i = 0; i < arguments.length; i++) - { - if (arguments[i] < 0 || arguments[i] > 1) - throw new RangeError("H, S, L, and A parameters must be between the range [0, 1]"); - } +function verifyRange() { + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] < 0 || arguments[i] > 1) throw new RangeError("H, S, L, and A parameters must be between the range [0, 1]"); + } } - -Color.prototype.interpolateToString = function(color, amount) -{ - var rgbThis = hslToRgb(this.hue, this.sat, this.lum); - var rgbThat = hslToRgb(color.hue, color.sat, color.lum); - var rgb = []; - - for (var i = 0; i < 3; i++) - rgb[i] = Math.floor((rgbThat[i] - rgbThis[i]) * amount + rgbThis[i]); - return {rgbString: function() {return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')'}}; +Color.prototype.interpolateToString = function(color, amount) { + var rgbThis = hslToRgb(this.hue, this.sat, this.lum); + var rgbThat = hslToRgb(color.hue, color.sat, color.lum); + var rgb = []; + for (var i = 0; i < 3; i++) rgb[i] = Math.floor((rgbThat[i] - rgbThis[i]) * amount + rgbThis[i]); + return { + rgbString: function() { + return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})` + } + }; } - -Color.prototype.deriveLumination = function(amount) -{ - var lum = this.lum + amount; - lum = Math.min(Math.max(lum, 0), 1); - return new Color(this.hue, this.sat, lum, this.alpha); +Color.prototype.deriveLumination = function(amount) { + var lum = this.lum + amount; + lum = Math.min(Math.max(lum, 0), 1); + return new Color(this.hue, this.sat, lum, this.alpha); }; - -Color.prototype.deriveHue = function(amount) -{ - var hue = this.hue - amount; - return new Color(hue - Math.floor(hue), this.sat, this.lum, this.alpha); +Color.prototype.deriveHue = function(amount) { + var hue = this.hue - amount; + return new Color(hue - Math.floor(hue), this.sat, this.lum, this.alpha); }; - -Color.prototype.deriveSaturation = function(amount) -{ - var sat = this.sat + amount; - sat = Math.min(Math.max(sat, 0), 1); - return new Color(this.hue, sat, this.lum, this.alpha); +Color.prototype.deriveSaturation = function(amount) { + var sat = this.sat + amount; + sat = Math.min(Math.max(sat, 0), 1); + return new Color(this.hue, sat, this.lum, this.alpha); }; - -Color.prototype.deriveAlpha = function(newAlpha) -{ - verifyRange(newAlpha); - return new Color(this.hue, this.sat, this.lum, newAlpha); +Color.prototype.deriveAlpha = function(newAlpha) { + verifyRange(newAlpha); + return new Color(this.hue, this.sat, this.lum, newAlpha); }; - Color.prototype.rgbString = function() { - var rgb = hslToRgb(this.hue, this.sat, this.lum); - rgb[3] = this.a; - return 'rgba(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ', ' + this.alpha + ')'; + var rgb = hslToRgb(this.hue, this.sat, this.lum); + rgb[3] = this.a; + return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${this.alpha})`; }; - - //http://stackoverflow.com/a/9493060/7344257 -function hslToRgb(h, s, l){ - var r, g, b; - - if(s == 0){ - r = g = b = l; // achromatic - }else{ - var hue2rgb = function hue2rgb(p, q, t){ - if(t < 0) t += 1; - if(t > 1) t -= 1; - if(t < 1/6) return p + (q - p) * 6 * t; - if(t < 1/2) return q; - if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - }; - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; +function hslToRgb(h, s, l) { + var r, g, b; + if (s == 0) r = g = b = l; // achromatic + else { + var hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; } - -module.exports = Color; \ No newline at end of file +module.exports = Color; diff --git a/game-core/game-consts.js b/game-core/game-consts.js index 8aaea13..47b203d 100644 --- a/game-core/game-consts.js +++ b/game-core/game-consts.js @@ -1,16 +1,16 @@ function constant(val) { - return { - value: val, - enumerable: true - }; + return { + value: val, + enumerable: true + }; } var consts = { - GRID_SIZE: constant(80), - CELL_WIDTH: constant(40), - SPEED: constant(5), - BORDER_WIDTH: constant(20), - MAX_PLAYERS: constant(81) + GRID_SIZE: constant(80), + CELL_WIDTH: constant(40), + SPEED: constant(5), + BORDER_WIDTH: constant(20), + MAX_PLAYERS: constant(81) }; Object.defineProperties(module.exports, consts); diff --git a/game-core/game-core.js b/game-core/game-core.js index df77a39..650b797 100644 --- a/game-core/game-core.js +++ b/game-core/game-core.js @@ -1,135 +1,107 @@ var ANIMATE_FRAMES = 24; var CELL_WIDTH = 40; -//TODO: remove constants. -exports.initPlayer = function(grid, player) -{ - for (var dr = -1; dr <= 1; dr++) - for (var dc = -1; dc <= 1; dc++) - if (!grid.isOutOfBounds(dr + player.row, dc + player.col)) - grid.set(dr + player.row, dc + player.col, player); +//TODO: remove constants +exports.initPlayer = function(grid, player) { + for (var dr = -1; dr <= 1; dr++) { + for (var dc = -1; dc <= 1; dc++) { + if (!grid.isOutOfBounds(dr + player.row, dc + player.col)) grid.set(dr + player.row, dc + player.col, player); + } + } }; -exports.updateFrame = function(grid, players, dead, notifyKill) -{ - var adead = []; - if (dead instanceof Array) - adead = dead; - - var kill; - if (!notifyKill) - kill = function() {}; - else - kill = function(killer, other) {if (!removing[other]) notifyKill(killer, other);}; - - //Move players. - var tmp = players.filter(function(val) { - val.move(); - if (val.dead) - adead.push(val); - return !val.dead; - }); - - //Remove players with collisions. - var removing = new Array(players.length); - for (var i = 0; i < players.length; i++) - { - for (var j = i; j < players.length; j++) - { - - //Remove those players when other players have hit their tail. - if (!removing[j] && players[j].tail.hitsTail(players[i])) - { - kill(i, j); - removing[j] = true; - //console.log("TAIL"); - } - if (!removing[i] && players[i].tail.hitsTail(players[j])) - { - kill(j, i); - removing[i] = true; - //console.log("TAIL"); - } - - //Remove players with collisons... - if (i !== j && squaresIntersect(players[i].posX, players[j].posX) && - squaresIntersect(players[i].posY, players[j].posY)) - { - //...if one player is own his own territory, the other is out. - if (grid.get(players[i].row, players[i].col) === players[i]) - { - kill(i, j); - removing[j] = true; - } - else if (grid.get(players[j].row, players[j].col) === players[j]) - { - kill(j, i); - removing[i] = true; - } - else - { - //...otherwise, the one that sustains most of the collision will be removed. - var areaI = area(players[i]); - var areaJ = area(players[j]); - - if (areaI === areaJ) - { - kill(i, j); - kill(j, i); - removing[i] = removing[j] = true; - } - else if (areaI > areaJ) - { - kill(j, i); - removing[i] = true; - } - else - { - kill(i, j); - removing[j] = true; - } - } - } - } - } - - tmp = tmp.filter(function(val, i) { - if (removing[i]) - { - adead.push(val); - val.die(); - } - return !removing[i]; - }); - players.length = tmp.length; - for (var i = 0; i < tmp.length; i++) - players[i] = tmp[i]; - - //Remove dead squares. - for (var r = 0; r < grid.size; r++) - { - for (var c = 0; c < grid.size; c++) - { - if (adead.indexOf(grid.get(r, c)) !== -1) - grid.set(r, c, null); - } - } +exports.updateFrame = function(grid, players, dead, notifyKill) { + var adead = []; + if (dead instanceof Array) adead = dead; + + var kill = (!notifyKill) ? function() {} : function(killer, other) { + if (!removing[other]) notifyKill(killer, other); + }; + + //Move players + var tmp = players.filter(function(val) { + val.move(); + if (val.dead) adead.push(val); + return !val.dead; + }); + + //Remove players with collisions + var removing = new Array(players.length); + for (var i = 0; i < players.length; i++) { + for (var j = i; j < players.length; j++) { + + //Remove those players when other players have hit their tail + if (!removing[j] && players[j].tail.hitsTail(players[i])) { + kill(i, j); + removing[j] = true; + //console.log("TAIL"); + } + if (!removing[i] && players[i].tail.hitsTail(players[j])) { + kill(j, i); + removing[i] = true; + //console.log("TAIL"); + } + + //Remove players with collisons... + if (i !== j && squaresIntersect(players[i].posX, players[j].posX) && + squaresIntersect(players[i].posY, players[j].posY)) { + //...if one player is own his own territory, the other is out + if (grid.get(players[i].row, players[i].col) === players[i]) { + kill(i, j); + removing[j] = true; + } + else if (grid.get(players[j].row, players[j].col) === players[j]) { + kill(j, i); + removing[i] = true; + } + else { + //...otherwise, the one that sustains most of the collision will be removed + var areaI = area(players[i]); + var areaJ = area(players[j]); + + if (areaI === areaJ) { + kill(i, j); + kill(j, i); + removing[i] = removing[j] = true; + } + else if (areaI > areaJ) { + kill(j, i); + removing[i] = true; + } + else { + kill(i, j); + removing[j] = true; + } + } + } + } + } + + tmp = tmp.filter(function(val, i) { + if (removing[i]) { + adead.push(val); + val.die(); + } + return !removing[i]; + }); + players.length = tmp.length; + for (var i = 0; i < tmp.length; i++) { + players[i] = tmp[i]; + } + + //Remove dead squares + for (var r = 0; r < grid.size; r++) { + for (var c = 0; c < grid.size; c++) { + if (adead.indexOf(grid.get(r, c)) !== -1) grid.set(r, c, null); + } + } }; -function squaresIntersect(a, b) -{ - if (a < b) - return b < a + CELL_WIDTH; - else - return a < b + CELL_WIDTH; +function squaresIntersect(a, b) { + return (a < b) ? (b < a + CELL_WIDTH) : (a < b + CELL_WIDTH); } -function area(player) -{ - var xDest = player.col * CELL_WIDTH; - var yDest = player.row * CELL_WIDTH; - - if (player.posX === xDest) - return Math.abs(player.posY - yDest); - else - return Math.abs(player.posX - xDest); -} \ No newline at end of file +function area(player) { + var xDest = player.col * CELL_WIDTH; + var yDest = player.row * CELL_WIDTH; + return (player.posX === xDest) ? Math.abs(player.posY - yDest) : Math.abs(player.posX - xDest); +} diff --git a/game-core/grid.js b/game-core/grid.js index dc36e24..9ee1259 100644 --- a/game-core/grid.js +++ b/game-core/grid.js @@ -1,56 +1,42 @@ -function Grid(size, changeCallback) -{ - var grid = new Array(size); - var modified = false; - - var data = { - grid: grid, - size: size - }; - - this.get = function(row, col) - { - if (isOutOfBounds(data, row, col)) - throw new RangeError("Row or Column value out of bounds"); - return grid[row] && grid[row][col]; - } - this.set = function(row, col, value) - { - if (isOutOfBounds(data, row, col)) - throw new RangeError("Row or Column value out of bounds"); - - if (!grid[row]) - grid[row] = new Array(size); - var before = grid[row][col]; - grid[row][col] = value; - - if (typeof changeCallback === "function") - changeCallback(row, col, before, value); - - modified = true; - - return before; - } - this.reset = function() { - if (modified) - { - grid = new Array(size); - modified = false; - } - } - - this.isOutOfBounds = isOutOfBounds.bind(this, data); - - Object.defineProperty(this, "size", { - get: function() {return size; }, - enumerable: true - }); - +function Grid(size, changeCallback) { + var grid = new Array(size); + var modified = false; + var data = { + grid: grid, + size: size + }; + + this.get = function(row, col) { + if (isOutOfBounds(data, row, col)) throw new RangeError("Row or Column value out of bounds"); + return grid[row] && grid[row][col]; + } + this.set = function(row, col, value) { + if (isOutOfBounds(data, row, col)) throw new RangeError("Row or Column value out of bounds"); + if (!grid[row]) grid[row] = new Array(size); + var before = grid[row][col]; + grid[row][col] = value; + if (typeof changeCallback === "function") changeCallback(row, col, before, value); + modified = true; + return before; + } + this.reset = function() { + if (modified) { + grid = new Array(size); + modified = false; + } + } + this.isOutOfBounds = isOutOfBounds.bind(this, data); + + Object.defineProperty(this, "size", { + get: function() { + return size; + }, + enumerable: true + }); } -function isOutOfBounds(data, row, col) -{ - return row < 0 || row >= data.size || col < 0 || col >= data.size; +function isOutOfBounds(data, row, col) { + return row < 0 || row >= data.size || col < 0 || col >= data.size; } -module.exports = Grid; \ No newline at end of file +module.exports = Grid; diff --git a/game-core/index.js b/game-core/index.js index daab7e4..2b1336f 100644 --- a/game-core/index.js +++ b/game-core/index.js @@ -1,17 +1,16 @@ -var core = require('./game-core'); -var consts = require('./game-consts'); +var core = require("./game-core"); +var consts = require("./game-consts"); -exports.Color = require('./color'); -exports.Grid = require('./grid'); -exports.Player = require('./player'); +exports.Color = require("./color"); +exports.Grid = require("./grid"); +exports.Player = require("./player"); exports.initPlayer = core.initPlayer; exports.updateFrame = core.updateFrame; for (var prop in consts) { - Object.defineProperty(exports, prop, { - enumerable: true, - value: consts[prop] - }); + Object.defineProperty(exports, prop, { + enumerable: true, + value: consts[prop] + }); } - diff --git a/game-core/player.js b/game-core/player.js index b6bdc2c..2c43fa8 100644 --- a/game-core/player.js +++ b/game-core/player.js @@ -5,499 +5,445 @@ var consts = require("./game-consts.js"); var GRID_SIZE = consts.GRID_SIZE; var CELL_WIDTH = consts.CELL_WIDTH; -var NEW_PLAYER_LAG = 60; //wait for a second at least. +var NEW_PLAYER_LAG = 60; //Wait for a second at least function defineGetter(getter) { - return { - get: getter, - enumerable: true - }; + return { + get: getter, + enumerable: true + }; } -function defineInstanceMethods(thisobj, data /*, methods...*/) -{ - for (var i = 2; i < arguments.length; i++) - thisobj[arguments[i].name] = arguments[i].bind(this, data); +function defineInstanceMethods(thisobj, data /*, methods...*/) { + for (var i = 2; i < arguments.length; i++) { + thisobj[arguments[i].name] = arguments[i].bind(this, data); + } } -function defineAccessorProperties(thisobj, data /*, names...*/) -{ - var descript = {}; - function getAt(name) { return function() {return data[name] } } - for (var i = 2; i < arguments.length; i++) - descript[arguments[i]] = defineGetter(getAt(arguments[i])); - Object.defineProperties(thisobj, descript); +function defineAccessorProperties(thisobj, data /*, names...*/) { + var descript = {}; + function getAt(name) { + return function() { + return data[name]; + } + } + for (var i = 2; i < arguments.length; i++) { + descript[arguments[i]] = defineGetter(getAt(arguments[i])); + } + Object.defineProperties(thisobj, descript); } -function TailMove(orientation) -{ - this.move = 1; - Object.defineProperty(this, "orientation", { - value: orientation, - enumerable: true - }); +function TailMove(orientation) { + this.move = 1; + Object.defineProperty(this, "orientation", { + value: orientation, + enumerable: true + }); } -function Tail(player, sdata) -{ - var data = { - tail: [], - tailGrid: [], - prev: null, - startRow: 0, - startCol: 0, - prevRow: 0, - prevCol: 0, - player: player - }; - - if (sdata) - { - data.startRow = data.prevRow = sdata.startRow || 0; - data.startCol = data.prevCol = sdata.startCol || 0; - sdata.tail.forEach(function(val) { - addTail(data, val.orientation, val.move); - }); - } - data.grid = player.grid; - - defineInstanceMethods(this, data, addTail, hitsTail, fillTail, renderTail, reposition, serialData); - Object.defineProperty(this, "moves", { - get: function() {return data.tail.slice(0);}, - enumerable: true - }); +function Tail(player, sdata) { + var data = { + tail: [], + tailGrid: [], + prev: null, + startRow: 0, + startCol: 0, + prevRow: 0, + prevCol: 0, + player: player + }; + + if (sdata) { + data.startRow = data.prevRow = sdata.startRow || 0; + data.startCol = data.prevCol = sdata.startCol || 0; + sdata.tail.forEach(function(val) { + addTail(data, val.orientation, val.move); + }); + } + data.grid = player.grid; + + defineInstanceMethods(this, data, addTail, hitsTail, fillTail, renderTail, reposition, serialData); + Object.defineProperty(this, "moves", { + get: function() { + return data.tail.slice(0); + }, + enumerable: true + }); } -//Instance methods. +//Instance methods function serialData(data) { - return { - tail: data.tail, - startRow: data.startRow, - startCol: data.startCol - }; + return { + tail: data.tail, + startRow: data.startRow, + startCol: data.startCol + }; } -function setTailGrid(data, tailGrid, r, c) -{ - if (!tailGrid[r]) - tailGrid[r] = []; - tailGrid[r][c] = true; +function setTailGrid(data, tailGrid, r, c) { + if (!tailGrid[r]) tailGrid[r] = []; + tailGrid[r][c] = true; } -function addTail(data, orientation, count) -{ - if (count === undefined) - count = 1; - if (!count || count < 0) - return; - - var prev = data.prev; - var r = data.prevRow, c = data.prevCol; - if (data.tail.length === 0) - setTailGrid(data, data.tailGrid, r, c); - - if (!prev || prev.orientation !== orientation) - { - prev = data.prev = new TailMove(orientation); - data.tail.push(prev); - prev.move += count - 1; - } - else - prev.move += count; +function addTail(data, orientation, count) { + if (count === undefined) count = 1; + if (!count || count < 0) return; - for (var i = 0; i < count; i++) - { - var pos = walk([data.prevRow, data.prevCol], null, orientation, 1); - data.prevRow = pos[0]; - data.prevCol = pos[1]; - setTailGrid(data, data.tailGrid, pos[0], pos[1]); - } - + var prev = data.prev; + var r = data.prevRow, c = data.prevCol; + if (data.tail.length === 0) setTailGrid(data, data.tailGrid, r, c); + + if (!prev || prev.orientation !== orientation) { + prev = data.prev = new TailMove(orientation); + data.tail.push(prev); + prev.move += count - 1; + } + else prev.move += count; + + for (var i = 0; i < count; i++) { + var pos = walk([data.prevRow, data.prevCol], null, orientation, 1); + data.prevRow = pos[0]; + data.prevCol = pos[1]; + setTailGrid(data, data.tailGrid, pos[0], pos[1]); + } } -function reposition(data, row, col) -{ - data.prevRow = data.startRow = row; - data.prevCol = data.startCol = col; - data.prev = null; - if (data.tail.length === 0) - return; - else - { - var ret = data.tail; - data.tail = []; - data.tailGrid = []; - return ret; - } +function reposition(data, row, col) { + data.prevRow = data.startRow = row; + data.prevCol = data.startCol = col; + data.prev = null; + if (data.tail.length === 0) return; + else { + var ret = data.tail; + data.tail = []; + data.tailGrid = []; + return ret; + } } /* -function render2(data, ctx) -{ - ctx.fillStyle = data.player.tailColor.rgbString(); - for (var r = 0; r < data.tailGrid.length; r++) - { - if (!data.tailGrid[r]) - continue; - for (var c = 0; c < data.tailGrid[r].length; c++) - if (data.tailGrid[r][c]) - ctx.fillRect(c * CELL_WIDTH, r * CELL_WIDTH, CELL_WIDTH, CELL_WIDTH); - } +function render2(data, ctx) { + ctx.fillStyle = data.player.tailColor.rgbString(); + for (var r = 0; r < data.tailGrid.length; r++) { + if (!data.tailGrid[r]) continue; + for (var c = 0; c < data.tailGrid[r].length; c++) { + if (data.tailGrid[r][c]) ctx.fillRect(c * CELL_WIDTH, r * CELL_WIDTH, CELL_WIDTH, CELL_WIDTH); + } + } } */ -//Helper methods. -function renderTail(data, ctx) -{ - if (data.tail.length === 0) - return; - - ctx.fillStyle = data.player.tailColor.rgbString(); - - var prevOrient = -1; - var start = [data.startRow, data.startCol]; - - //fillTailRect(ctx, start, start); - data.tail.forEach(function(tail) { - var negDir = tail.orientation === 0 || tail.orientation === 3; +//Helper methods +function renderTail(data, ctx) { + if (data.tail.length === 0) return; - var back = start; - if (!negDir) - start = walk(start, null, tail.orientation, 1); - var finish = walk(start, null, tail.orientation, tail.move - 1); - - if (tail.move > 1) - fillTailRect(ctx, start, finish); - if (prevOrient !== -1) - //Draw folding triangle. - renderCorner(ctx, back, prevOrient, tail.orientation); - - start = finish; - if (negDir) - walk(start, start, tail.orientation, 1); - prevOrient = tail.orientation; - }); - - var curOrient = data.player.currentHeading; - if (prevOrient === curOrient) - { - fillTailRect(ctx, start, start); - } - else - renderCorner(ctx, start, prevOrient, curOrient); + ctx.fillStyle = data.player.tailColor.rgbString(); + + var prevOrient = -1; + var start = [data.startRow, data.startCol]; + + //fillTailRect(ctx, start, start); + data.tail.forEach(function(tail) { + var negDir = tail.orientation === 0 || tail.orientation === 3; + + var back = start; + if (!negDir) start = walk(start, null, tail.orientation, 1); + var finish = walk(start, null, tail.orientation, tail.move - 1); + + if (tail.move > 1) fillTailRect(ctx, start, finish); + if (prevOrient !== -1) renderCorner(ctx, back, prevOrient, tail.orientation); + //Draw folding triangle. + start = finish; + if (negDir) walk(start, start, tail.orientation, 1); + prevOrient = tail.orientation; + }); + + var curOrient = data.player.currentHeading; + if (prevOrient === curOrient) fillTailRect(ctx, start, start); + else renderCorner(ctx, start, prevOrient, curOrient); } -function renderCorner(ctx, cornerStart, dir1, dir2) -{ - if (dir1 === 0 || dir2 === 0) - walk(cornerStart, cornerStart, 2, 1); - if (dir1 === 3 || dir2 === 3) - walk(cornerStart, cornerStart, 1, 1); - - var a = walk(cornerStart, null, dir2, 1); - var b = walk(a, null, dir1, 1); - - var triangle = new Path2D(); - triangle.moveTo(cornerStart[1] * CELL_WIDTH, cornerStart[0] * CELL_WIDTH); - triangle.lineTo(a[1] * CELL_WIDTH, a[0] * CELL_WIDTH); - triangle.lineTo(b[1] * CELL_WIDTH, b[0] * CELL_WIDTH); - triangle.closePath(); - for (var i = 0; i < 2; i++) - ctx.fill(triangle); +function renderCorner(ctx, cornerStart, dir1, dir2) { + if (dir1 === 0 || dir2 === 0) walk(cornerStart, cornerStart, 2, 1); + if (dir1 === 3 || dir2 === 3) walk(cornerStart, cornerStart, 1, 1); + + var a = walk(cornerStart, null, dir2, 1); + var b = walk(a, null, dir1, 1); + + var triangle = new Path2D(); + triangle.moveTo(cornerStart[1] * CELL_WIDTH, cornerStart[0] * CELL_WIDTH); + triangle.lineTo(a[1] * CELL_WIDTH, a[0] * CELL_WIDTH); + triangle.lineTo(b[1] * CELL_WIDTH, b[0] * CELL_WIDTH); + triangle.closePath(); + for (var i = 0; i < 2; i++) { + ctx.fill(triangle); + } } -function walk(from, ret, orient, dist) -{ - ret = ret || []; - ret[0] = from[0]; - ret[1] = from[1]; - switch (orient) - { - case 0: ret[0] -= dist; break; //UP - case 1: ret[1] += dist; break; //RIGHT - case 2: ret[0] += dist; break; //DOWN - case 3: ret[1] -= dist; break; //LEFT - } - return ret; +function walk(from, ret, orient, dist) { + ret = ret || []; + ret[0] = from[0]; + ret[1] = from[1]; + switch (orient) { + case 0: ret[0] -= dist; break; //UP + case 1: ret[1] += dist; break; //RIGHT + case 2: ret[0] += dist; break; //DOWN + case 3: ret[1] -= dist; break; //LEFT + } + return ret; } -function fillTailRect(ctx, start, end) -{ - var x = start[1] * CELL_WIDTH; - var y = start[0] * CELL_WIDTH; - var width = (end[1] - start[1]) * CELL_WIDTH; - var height = (end[0] - start[0]) * CELL_WIDTH; - - if (width === 0) - width += CELL_WIDTH; - if (height === 0) - height += CELL_WIDTH; - - if (width < 0) - { - x += width; - width = -width; - } - if (height < 0) - { - y += height; - height = -height; - } - ctx.fillRect(x, y, width, height); +function fillTailRect(ctx, start, end) { + var x = start[1] * CELL_WIDTH; + var y = start[0] * CELL_WIDTH; + var width = (end[1] - start[1]) * CELL_WIDTH; + var height = (end[0] - start[0]) * CELL_WIDTH; + + if (width === 0) width += CELL_WIDTH; + if (height === 0) height += CELL_WIDTH; + + if (width < 0) { + x += width; + width = -width; + } + if (height < 0) { + y += height; + height = -height; + } + ctx.fillRect(x, y, width, height); } -function fillTail(data) -{ - if (data.tail.length === 0) - return; - - function onTail(c) { return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; } - - var grid = data.grid; - var start = [data.startRow, data.startCol]; - var been = new Grid(grid.size); - var coords = []; - - coords.push(start); - while (coords.length > 0) //BFS for all tail spaces. - { - var coord = coords.shift(); - var r = coord[0]; - var c = coord[1]; - - if (grid.isOutOfBounds(r, c)) - continue; - - if (been.get(r, c)) - continue; - - if (onTail(coord)) //on the tail. - { - been.set(r, c, true); - grid.set(r, c, data.player); - - //Find all spots that this tail encloses. - floodFill(data, grid, r + 1, c, been); - floodFill(data, grid, r - 1, c, been); - floodFill(data, grid, r, c + 1, been); - floodFill(data, grid, r, c - 1, been); - - coords.push([r + 1, c]); - coords.push([r - 1, c]); - coords.push([r, c + 1]); - coords.push([r, c - 1]); - } - } +function fillTail(data) { + if (data.tail.length === 0) return; + + function onTail(c) { + return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; + } + + var grid = data.grid; + var start = [data.startRow, data.startCol]; + var been = new Grid(grid.size); + var coords = []; + + coords.push(start); + while (coords.length > 0) { //BFS for all tail spaces + var coord = coords.shift(); + var r = coord[0]; + var c = coord[1]; + + if (grid.isOutOfBounds(r, c)) { + continue; + } + + if (been.get(r, c)) { + continue; + } + + if (onTail(coord)) {//On the tail + been.set(r, c, true); + grid.set(r, c, data.player); + + //Find all spots that this tail encloses + floodFill(data, grid, r + 1, c, been); + floodFill(data, grid, r - 1, c, been); + floodFill(data, grid, r, c + 1, been); + floodFill(data, grid, r, c - 1, been); + + coords.push([r + 1, c]); + coords.push([r - 1, c]); + coords.push([r, c + 1]); + coords.push([r, c - 1]); + } + } } -function floodFill(data, grid, row, col, been) -{ - function onTail(c) { return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; } - - var start = [row, col]; - if (grid.isOutOfBounds(row, col) || been.get(row, col) || onTail(start) || grid.get(row, col) === data.player) - return; //Avoid allocating too many resources. - - var coords = []; - var filled = new Stack(GRID_SIZE * GRID_SIZE + 1); - var surrounded = true; - - coords.push(start); - while (coords.length > 0) - { - var coord = coords.shift(); - var r = coord[0]; - var c = coord[1]; - - if (grid.isOutOfBounds(r, c)) - { - surrounded = false; - continue; - } - - //End this traverse on boundaries (where we been, on the tail, and when we enter our territory) - if (been.get(r, c) || onTail(coord) || grid.get(r, c) === data.player) - continue; - - been.set(r, c, true); - - if (surrounded) - filled.push(coord); - - coords.push([r + 1, c]); - coords.push([r - 1, c]); - coords.push([r, c + 1]); - coords.push([r, c - 1]); - } - if (surrounded) - { - while (!filled.isEmpty()) - { - coord = filled.pop(); - grid.set(coord[0], coord[1], data.player); - } - } - - return surrounded; +function floodFill(data, grid, row, col, been) { + function onTail(c) { + return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; + } + + var start = [row, col]; + if (grid.isOutOfBounds(row, col) || been.get(row, col) || onTail(start) || grid.get(row, col) === data.player) return; + //Avoid allocating too many resources + var coords = []; + var filled = new Stack(GRID_SIZE * GRID_SIZE + 1); + var surrounded = true; + + coords.push(start); + while (coords.length > 0) { + var coord = coords.shift(); + var r = coord[0]; + var c = coord[1]; + + if (grid.isOutOfBounds(r, c)) { + surrounded = false; + continue; + } + + //End this traverse on boundaries (where we been, on the tail, and when we enter our territory) + if (been.get(r, c) || onTail(coord) || grid.get(r, c) === data.player) continue; + + been.set(r, c, true); + + if (surrounded) filled.push(coord); + + coords.push([r + 1, c]); + coords.push([r - 1, c]); + coords.push([r, c + 1]); + coords.push([r, c - 1]); + } + if (surrounded) { + while (!filled.isEmpty()) { + coord = filled.pop(); + grid.set(coord[0], coord[1], data.player); + } + } + return surrounded; } -function hitsTail(data, other) -{ - return (data.prevRow !== other.row || data.prevCol !== other.col) && - (data.startRow !== other.row || data.startCol !== other.col) && - !!(data.tailGrid[other.row] && data.tailGrid[other.row][other.col]); +function hitsTail(data, other) { + return (data.prevRow !== other.row || data.prevCol !== other.col) && + (data.startRow !== other.row || data.startCol !== other.col) && + !!(data.tailGrid[other.row] && data.tailGrid[other.row][other.col]); } var SPEED = 5; var SHADOW_OFFSET = 10; function Player(grid, sdata) { - var data = {}; - - //Parameters - data.num = sdata.num; - data.name = sdata.name || ""; //|| "Player " + (data.num + 1); - data.grid = grid; - data.posX = sdata.posX; - data.posY = sdata.posY; - this.heading = data.currentHeading = sdata.currentHeading; //0 is up, 1 is right, 2 is down, 3 is left. - data.waitLag = sdata.waitLag || 0; - data.dead = false; - - //Only need colors for client side. - var base; - if (sdata.base) - base = this.baseColor = sdata.base instanceof Color ? sdata.base : Color.fromData(sdata.base); - else - { - var hue = Math.random(); - this.baseColor = base = new Color(hue, .8, .5); - } - this.lightBaseColor = base.deriveLumination(.1); - this.shadowColor = base.deriveLumination(-.3); - this.tailColor = base.deriveLumination(.3).deriveAlpha(.5); - - //Tail requires special handling. - this.grid = grid; //Temporary - if (sdata.tail) - data.tail = new Tail(this, sdata.tail); - else - { - data.tail = new Tail(this); - data.tail.reposition(calcRow(data), calcCol(data)); - } - - //Instance methods. - this.move = move.bind(this, data); - this.die = function() { data.dead = true;}; - this.serialData = function() { - return { - base: this.baseColor, - num: data.num, - name: data.name, - posX: data.posX, - posY: data.posY, - currentHeading: data.currentHeading, - tail: data.tail.serialData(), - waitLag: data.waitLag - }; - }; - - //Read-only Properties. - defineAccessorProperties(this, data, "currentHeading", "dead", "name", "num", "posX", "posY", "grid", "tail", "waitLag"); - Object.defineProperties(this, { - row: defineGetter(function() { return calcRow(data); }), - col: defineGetter(function() { return calcCol(data); }) - }); + var data = {}; + + //Parameters + data.num = sdata.num; + data.name = sdata.name || ""; //|| "Player " + (data.num + 1); + data.grid = grid; + data.posX = sdata.posX; + data.posY = sdata.posY; + this.heading = data.currentHeading = sdata.currentHeading; //0 is up, 1 is right, 2 is down, 3 is left + data.waitLag = sdata.waitLag || 0; + data.dead = false; + + //Only need colors for client side + var base; + if (sdata.base) { + base = this.baseColor = sdata.base instanceof Color ? sdata.base : Color.fromData(sdata.base); + } + else { + var hue = Math.random(); + this.baseColor = base = new Color(hue, .8, .5); + } + this.lightBaseColor = base.deriveLumination(.1); + this.shadowColor = base.deriveLumination(-.3); + this.tailColor = base.deriveLumination(.2).deriveAlpha(0.98); + + //Tail requires special handling + this.grid = grid; //Temporary + if (sdata.tail) { + data.tail = new Tail(this, sdata.tail); + } + else { + data.tail = new Tail(this); + data.tail.reposition(calcRow(data), calcCol(data)); + } + + //Instance methods + this.move = move.bind(this, data); + this.die = function() { data.dead = true;}; + this.serialData = function() { + return { + base: this.baseColor, + num: data.num, + name: data.name, + posX: data.posX, + posY: data.posY, + currentHeading: data.currentHeading, + tail: data.tail.serialData(), + waitLag: data.waitLag + }; + }; + + //Read-only Properties + defineAccessorProperties(this, data, "currentHeading", "dead", "name", "num", "posX", "posY", "grid", "tail", "waitLag"); + Object.defineProperties(this, { + row: defineGetter(function() { + return calcRow(data); + }), + col: defineGetter(function() { + return calcCol(data); + }) + }); } -//Gets the next integer in positive or negative direction. -function nearestInteger(positive, val) -{ - return positive ? Math.ceil(val) : Math.floor(val); +//Gets the next integer in positive or negative direction +function nearestInteger(positive, val) { + return positive ? Math.ceil(val) : Math.floor(val); } -function calcRow(data) -{ - return nearestInteger(data.currentHeading === 2 /*DOWN*/, data.posY / CELL_WIDTH); +function calcRow(data) { + return nearestInteger(data.currentHeading === 2 /*DOWN*/, data.posY / CELL_WIDTH); } -function calcCol(data) -{ - return nearestInteger(data.currentHeading === 1 /*RIGHT*/, data.posX / CELL_WIDTH); +function calcCol(data) { + return nearestInteger(data.currentHeading === 1 /*RIGHT*/, data.posX / CELL_WIDTH); } //Instance methods -Player.prototype.render = function(ctx, fade) -{ - //Render tail. - this.tail.renderTail(ctx); - - //Render player. - fade = fade || 1; - ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString(); - ctx.fillRect(this.posX, this.posY, CELL_WIDTH, CELL_WIDTH); - - var mid = CELL_WIDTH / 2; - var grd = ctx.createRadialGradient(this.posX + mid, this.posY + mid - SHADOW_OFFSET, 1, - this.posX + mid, this.posY + mid - SHADOW_OFFSET, CELL_WIDTH); - grd.addColorStop(0, this.baseColor.deriveAlpha(fade).rgbString()); - grd.addColorStop(1, new Color(0, 0, 1, fade).rgbString()); - ctx.fillStyle = grd; - ctx.fillRect(this.posX - 1, this.posY - SHADOW_OFFSET, CELL_WIDTH + 2, CELL_WIDTH); - - //Render name - ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString(); - ctx.textAlign = "center"; - - var yoff = -SHADOW_OFFSET * 2; - if (this.row === 0) - yoff = SHADOW_OFFSET * 2 + CELL_WIDTH; - ctx.font = "18px Changa"; - ctx.fillText(this.name, this.posX + CELL_WIDTH / 2, this.posY + yoff); +Player.prototype.render = function(ctx, fade) { + //Render tail. + this.tail.renderTail(ctx); + //Render player. + fade = fade || 1; + ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString(); + ctx.fillRect(this.posX, this.posY, CELL_WIDTH, CELL_WIDTH); + + var mid = CELL_WIDTH / 2; + var grd = ctx.createRadialGradient(this.posX + mid, this.posY + mid - SHADOW_OFFSET, 1, this.posX + mid, this.posY + mid - SHADOW_OFFSET, CELL_WIDTH); + //grd.addColorStop(0, this.baseColor.deriveAlpha(fade).rgbString()); + //grd.addColorStop(1, new Color(0, 0, 1, fade).rgbString()); + //ctx.fillStyle = grd; + ctx.fillStyle = this.shadowColor.deriveLumination(.2).rgbString(); + ctx.fillRect(this.posX - 1, this.posY - SHADOW_OFFSET, CELL_WIDTH + 2, CELL_WIDTH); + + //Render name + ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString(); + ctx.textAlign = "center"; + + var yoff = -SHADOW_OFFSET * 2; + if (this.row === 0) + yoff = SHADOW_OFFSET * 2 + CELL_WIDTH; + ctx.font = "18px Changa"; + ctx.fillText(this.name, this.posX + CELL_WIDTH / 2, this.posY + yoff); }; - -function move(data) -{ - if (data.waitLag < NEW_PLAYER_LAG) - { - data.waitLag++; - return; - } - - //Move to new position. - var heading = this.heading; - if (this.posX % CELL_WIDTH !== 0 || this.posY % CELL_WIDTH !== 0) - heading = data.currentHeading; - else - data.currentHeading = heading; - switch (heading) - { - case 0: data.posY -= SPEED; break; //UP - case 1: data.posX += SPEED; break; //RIGHT - case 2: data.posY += SPEED; break; //DOWN - case 3: data.posX -= SPEED; break; //LEFT - } - - //Check for out of bounds. - var row = this.row, col = this.col; - if (data.grid.isOutOfBounds(row, col)) - { - data.dead = true; - return; - } - - //Update tail position. - if (data.grid.get(row, col) === this) - { - //Safe zone! - this.tail.fillTail(); - this.tail.reposition(row, col); - } - //If we are completely in a new cell (not in our safe zone), we add to the tail. - else if (this.posX % CELL_WIDTH === 0 && this.posY % CELL_WIDTH === 0) - this.tail.addTail(heading); +function move(data) { + if (data.waitLag < NEW_PLAYER_LAG) { + data.waitLag++; + return; + } + //Move to new position. + var heading = this.heading; + if (this.posX % CELL_WIDTH !== 0 || this.posY % CELL_WIDTH !== 0) heading = data.currentHeading; + else data.currentHeading = heading; + switch (heading) { + case 0: data.posY -= SPEED; break; //UP + case 1: data.posX += SPEED; break; //RIGHT + case 2: data.posY += SPEED; break; //DOWN + case 3: data.posX -= SPEED; break; //LEFT + } + //Check for out of bounds. + var row = this.row, col = this.col; + if (data.grid.isOutOfBounds(row, col)) { + data.dead = true; + return; + } + //Update tail position. + if (data.grid.get(row, col) === this) { + //Safe zone! + this.tail.fillTail(); + this.tail.reposition(row, col); + } + //If we are completely in a new cell (not in our safe zone), we add to the tail. + else if (this.posX % CELL_WIDTH === 0 && this.posY % CELL_WIDTH === 0) this.tail.addTail(heading); } module.exports = Player; diff --git a/game-core/stack.js b/game-core/stack.js index 3445db2..f15e663 100644 --- a/game-core/stack.js +++ b/game-core/stack.js @@ -1,41 +1,28 @@ - - -function Stack(initSize) -{ - var len = 0; - var arr = []; - - this.ensureCapacity = function(size) - { - arr.length = Math.max(arr.length, size || 0); - }; - - this.push = function(ele) - { - this[len] = ele; - len++; - }; - - this.pop = function() - { - if (len === 0) - return; - len--; - var tmp = this[len]; - this[len] = undefined; - return tmp; - }; - - this.isEmpty = function() { - return len === 0; - } - - this.ensureCapacity(initSize); - - - Object.defineProperty(this, "length", { - get: function() {return len;} - }); +function Stack(initSize) { + var len = 0; + var arr = []; + this.ensureCapacity = function(size) { + arr.length = Math.max(arr.length, size || 0); + }; + this.push = function(ele) { + this[len] = ele; + len++; + }; + this.pop = function() { + if (len === 0) return; + len--; + var tmp = this[len]; + this[len] = undefined; + return tmp; + }; + this.isEmpty = function() { + return len === 0; + } + this.ensureCapacity(initSize); + Object.defineProperty(this, "length", { + get: function() { + return len; + } + }); } - -module.exports = Stack; \ No newline at end of file +module.exports = Stack; diff --git a/game-server.js b/game-server.js index 464c48e..9039486 100644 --- a/game-server.js +++ b/game-server.js @@ -1,319 +1,247 @@ - var core = require("./game-core"); - var GRID_SIZE = core.GRID_SIZE; var CELL_WIDTH = core.CELL_WIDTH; var MAX_PLAYERS = core.MAX_PLAYERS; - -var HUES = [0, 10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 100, 110, 120, 125, 130, 135, 140, 145, 150, 160, 170, 180, 190, 200, 210, 220].map(function(val) {return val / 240}); -var SATS = [192, 150, 100].map(function(val) {return val / 240}); +var HUES = [0, 10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 100, 110, 120, 125, 130, 135, 140, 145, 150, 160, 170, 180, 190, 200, 210, 220].map(function(val) { + return val / 240; +}); +var SATS = [192, 150, 100].map(function(val) { + return val / 240; +}); function log(msg) { - console.log('[' + new Date() + '] ' + msg); + console.log("[" + new Date() + "] " + msg); } -function Game(id) -{ - var possColors = new Array(SATS.length * HUES.length); - i = 0; - for (var s = 0; s < SATS.length; s++) - for (var h = 0; h < HUES.length; h++) - possColors[i++] = new core.Color(HUES[h], SATS[s], .5, 1); - - //Shuffle the colors. - for (var i = 0; i < possColors.length * 50; i++) - { - var a = Math.floor(Math.random() * possColors.length); - var b = Math.floor(Math.random() * possColors.length); - var tmp = possColors[a]; - possColors[a] = possColors[b]; - possColors[b] = tmp; - } - - var nextInd = 0; - var players = []; - var newPlayers = []; - var frameLocs = []; - var frame = 0; - - var filled = 0; - var grid = new core.Grid(GRID_SIZE, function(row, col, before, after) { - if (!!after ^ !!before) - { - if (after) - filled++; - else - filled--; - if (filled === GRID_SIZE * GRID_SIZE) - log("FULL GAME"); - } - }); - - this.id = id; - - this.addPlayer = function(client, name) { - if (players.length >= MAX_PLAYERS) - return false; - - var start = findEmpty(grid); - if (!start) - return false; - - var params = { - posX: start.col * CELL_WIDTH, - posY: start.row * CELL_WIDTH, - currentHeading: Math.floor(Math.random() * 4), - name: name, - num: nextInd, - base: possColors.shift() - }; - - var p = new core.Player(grid, params); - p.tmpHeading = params.currentHeading; - p.client = client; - players.push(p); - newPlayers.push(p); - nextInd++; - core.initPlayer(grid, p); - - if (p.name.indexOf("BOT") == -1) { - log((p.name || "Unnamed") + " (" + p.num + ") joined."); - } - - client.on("requestFrame", function () { - if (p.frame === frame) - return; - p.frame = frame; //Limit number of requests per frame. (One per frame); - - var splayers = players.map(function(val) {return val.serialData();}); - client.emit("game", { - "num": p.num, - "gameid": id, - "frame": frame, - "players": splayers, - "grid": gridSerialData(grid, players) - }); - }); - - //Verifies that this client has executed this frame properly. - client.on("verify", function(data, resp) { - if (typeof resp !== "function") - return; - - if (!data.frame) - resp(false, false, "No frame supplied"); - else if (!checkInt(data.frame, 0, frame + 1)) - resp(false, false, "Must be a valid frame number"); - else - { - verifyPlayerLocations(data.frame, data.locs, resp); - } - }); - - client.on("frame", function(data, errorHan){ - if (typeof data === "function") - { - errorHan(false, "No data supplied."); - return; - } - - if (typeof errorHan !== "function") - errorHan = function() {}; - - if (!data) - errorHan(false, "No data supplied."); - else if (!checkInt(data.frame, 0, Infinity)) - errorHan(false, "Requires a valid non-negative frame integer."); - else if (data.frame > frame) - errorHan(false, "Invalid frame received."); - else - { - if (data.heading !== undefined) - { - if (checkInt(data.heading, 0, 4)) - { - p.tmpHeading = data.heading; - errorHan(true); - } - else - errorHan(false, "New heading must be an integer of range [0, 4)."); - } - } - }); - - client.on('disconnect', function() { - p.die(); //Die immediately if not already. - p.disconnected = true; - if (p.name.indexOf("BOT") == -1) { - log((p.name || "Unnamed") + " (" + p.num + ") left."); - } - }); - return true; - }; - - function pushPlayerLocations() - { - var locs = []; - for (var p of players) - locs[p.num] = [p.posX, p.posY, p.waitLag]; - locs.frame = frame; - - if (frameLocs.length >= 300) //Give it 5 seconds of lag. - frameLocs.shift(); - frameLocs.push(locs); - } - - function verifyPlayerLocations(fr, verify, resp) - { - var minFrame = frame - frameLocs.length + 1; - if (fr < minFrame || fr > frame) - { - resp(false, false, "Frames out of reference"); - return; - } - - function string(loc) - { - return '(' + loc[0] + ', ' + loc[1] + ') [' + loc[2] + ']'; - } - - var locs = frameLocs[fr - minFrame]; - if (locs.frame !== fr) - { - resp(false, false, locs.frame + " != " + fr); - return; - } - - - for (var num in verify) - { - if (!locs[num]) - continue; - if (locs[num][0] !== verify[num][0] || locs[num][1] !== verify[num][1] || locs[num][2] !== verify[num][2]) - { - resp(false, true, 'P' + num + ' ' + string(locs[num]) + ' !== ' + string(verify[num])); - return; - } - } - - resp(true, false); - } - - function tick() { - - //TODO: notify those players that this server automatically drops out. - var splayers = players.map(function(val) {return val.serialData();}); - var snews = newPlayers.map(function(val) { - //Emit game stats. - val.client.emit("game", { - "num": val.num, - "gameid": id, - "frame": frame, - "players": splayers, - "grid": gridSerialData(grid, players), - }); - return val.serialData(); - }); - var moves = players.map(function(val) { - //Account for race condition (when heading is set after emitting frames, and before updating). - val.heading = val.tmpHeading; - return {num: val.num, left: !!val.disconnected, heading: val.heading}; - }); - - update(); - - var data = {frame: frame + 1, moves: moves}; - if (snews.length > 0) - { - data.newPlayers = snews; - newPlayers = []; - } - - for (var pl of players) - pl.client.emit("notifyFrame", data); - - frame++; - pushPlayerLocations(); - } - - this.tickFrame = tick; - - function update() - { - var dead = []; - core.updateFrame(grid, players, dead); - for (var pl of dead) - { - if (!pl.handledDead) - { - possColors.push(pl.baseColor); - pl.handledDead = true; - } - if (pl.name.indexOf("BOT") == -1) { - log((pl.name || "Unnamed") + " (" + pl.num + ") died."); - } - pl.client.emit("dead"); - pl.client.disconnect(true); - } - } +function Game(id) { + var possColors = new Array(SATS.length * HUES.length); + i = 0; + for (var s = 0; s < SATS.length; s++) { + for (var h = 0; h < HUES.length; h++) { + possColors[i++] = new core.Color(HUES[h], SATS[s], .5, 1); + } + } + //Shuffle the colors. + for (var i = 0; i < possColors.length * 50; i++) { + var a = Math.floor(Math.random() * possColors.length); + var b = Math.floor(Math.random() * possColors.length); + var tmp = possColors[a]; + possColors[a] = possColors[b]; + possColors[b] = tmp; + } + var nextInd = 0; + var players = []; + var newPlayers = []; + var frameLocs = []; + var frame = 0; + var filled = 0; + var grid = new core.Grid(GRID_SIZE, function(row, col, before, after) { + if (!!after ^ !!before) { + if (after) filled++; + else filled--; + if (filled === GRID_SIZE * GRID_SIZE) log("FULL GAME"); + } + }); + this.id = id; + this.addPlayer = function(client, name) { + if (players.length >= MAX_PLAYERS) return false; + var start = findEmpty(grid); + if (!start) return false; + var params = { + posX: start.col * CELL_WIDTH, + posY: start.row * CELL_WIDTH, + currentHeading: Math.floor(Math.random() * 4), + name: name, + num: nextInd, + base: possColors.shift() + }; + var p = new core.Player(grid, params); + p.tmpHeading = params.currentHeading; + p.client = client; + players.push(p); + newPlayers.push(p); + nextInd++; + core.initPlayer(grid, p); + if (p.name.indexOf("BOT") == -1) log((p.name || "Unnamed") + " (" + p.num + ") joined."); + client.on("requestFrame", function() { + if (p.frame === frame) return; + p.frame = frame; //Limit number of requests per frame. (One per frame); + var splayers = players.map(function(val) { + return val.serialData(); + }); + client.emit("game", { + "num": p.num, + "gameid": id, + "frame": frame, + "players": splayers, + "grid": gridSerialData(grid, players) + }); + }); + //Verifies that this client has executed this frame properly. + client.on("verify", function(data, resp) { + if (typeof resp !== "function") return; + if (!data.frame) resp(false, false, "No frame supplied"); + else if (!checkInt(data.frame, 0, frame + 1)) resp(false, false, "Must be a valid frame number"); + else { + verifyPlayerLocations(data.frame, data.locs, resp); + } + }); + client.on("frame", function(data, errorHan) { + if (typeof data === "function") { + errorHan(false, "No data supplied."); + return; + } + if (typeof errorHan !== "function") errorHan = function() {}; + if (!data) errorHan(false, "No data supplied."); + else if (!checkInt(data.frame, 0, Infinity)) errorHan(false, "Requires a valid non-negative frame integer."); + else if (data.frame > frame) errorHan(false, "Invalid frame received."); + else { + if (data.heading !== undefined) { + if (checkInt(data.heading, 0, 4)) { + p.tmpHeading = data.heading; + errorHan(true); + } + else errorHan(false, "New heading must be an integer of range [0, 4)."); + } + } + }); + client.on("disconnect", function() { + p.die(); //Die immediately if not already. + p.disconnected = true; + if (p.name.indexOf("BOT") == -1) log((p.name || "Unnamed") + " (" + p.num + ") left."); + }); + return true; + }; + + function pushPlayerLocations() { + var locs = []; + for (var p of players) { + locs[p.num] = [p.posX, p.posY, p.waitLag]; + } + locs.frame = frame; + if (frameLocs.length >= 300) frameLocs.shift(); //Give it 5 seconds of lag. + frameLocs.push(locs); + } + + function verifyPlayerLocations(fr, verify, resp) { + var minFrame = frame - frameLocs.length + 1; + if (fr < minFrame || fr > frame) { + resp(false, false, "Frames out of reference"); + return; + } + + function string(loc) { + return `(${loc[0]}, ${loc[1]}) [${loc[2]}]`; + } + var locs = frameLocs[fr - minFrame]; + if (locs.frame !== fr) { + resp(false, false, locs.frame + " != " + fr); + return; + } + for (var num in verify) { + if (!locs[num]) continue; + if (locs[num][0] !== verify[num][0] || locs[num][1] !== verify[num][1] || locs[num][2] !== verify[num][2]) { + resp(false, true, "P" + num + " " + string(locs[num]) + " !== " + string(verify[num])); + return; + } + } + resp(true, false); + } + + function tick() { + //TODO: notify those players that this server automatically drops out. + var splayers = players.map(function(val) { + return val.serialData(); + }); + var snews = newPlayers.map(function(val) { + //Emit game stats. + val.client.emit("game", { + "num": val.num, + "gameid": id, + "frame": frame, + "players": splayers, + "grid": gridSerialData(grid, players), + }); + return val.serialData(); + }); + var moves = players.map(function(val) { + //Account for race condition (when heading is set after emitting frames, and before updating). + val.heading = val.tmpHeading; + return { + num: val.num, + left: !!val.disconnected, + heading: val.heading + }; + }); + update(); + var data = { + frame: frame + 1, + moves: moves + }; + if (snews.length > 0) { + data.newPlayers = snews; + newPlayers = []; + } + for (var pl of players) { + pl.client.emit("notifyFrame", data); + } + frame++; + pushPlayerLocations(); + } + this.tickFrame = tick; + + function update() { + var dead = []; + core.updateFrame(grid, players, dead); + for (var pl of dead) { + if (!pl.handledDead) { + possColors.push(pl.baseColor); + pl.handledDead = true; + } + if (pl.name.indexOf("BOT") == -1) log((pl.name || "Unnamed") + " (" + pl.num + ") died."); + pl.client.emit("dead"); + pl.client.disconnect(true); + } + } } -function checkInt(value, min, max) -{ - if (typeof value !== "number") - return false; - if (value < min || value >= max) - return false; - if (Math.floor(value) !== value) - return false; - - return true; +function checkInt(value, min, max) { + return !(typeof value !== "number" || value < min || value >= max || Math.floor(value) !== value); } -function gridSerialData(grid, players) -{ - var buff = Buffer.alloc(grid.size * grid.size); - - var numToIndex = new Array(players.length > 0 ? players[players.length - 1].num + 1 : 0); - for (var i = 0; i < players.length; i++) - numToIndex[players[i].num] = i + 1; - - for (var r = 0; r < grid.size; r++) - for (var c = 0; c < grid.size; c++) - { - var ele = grid.get(r, c); - buff[r * grid.size + c] = ele ? numToIndex[ele.num] : 0; - } - return buff; +function gridSerialData(grid, players) { + var buff = Buffer.alloc(grid.size * grid.size); + var numToIndex = new Array(players.length > 0 ? players[players.length - 1].num + 1 : 0); + for (var i = 0; i < players.length; i++) { + numToIndex[players[i].num] = i + 1; + } + for (var r = 0; r < grid.size; r++) { + for (var c = 0; c < grid.size; c++) { + var ele = grid.get(r, c); + buff[r * grid.size + c] = ele ? numToIndex[ele.num] : 0; + } + } + return buff; } -function findEmpty(grid) -{ - var available = []; - - for (var r = 1; r < grid.size - 1; r++) - for (var c = 1; c < grid.size - 1; c++) - { - var cluttered = false; - checkclutter: for (var dr = -1; dr <= 1; dr++) - { - for (var dc = -1; dc <= 1; dc++) - { - if (grid.get(r + dr, c + dc)) - { - cluttered = true; - break checkclutter; - } - } - } - if (!cluttered) - available.push({row: r, col: c}); - } - - if (available.length === 0) - return null; - else - return available[Math.floor(available.length * Math.random())]; +function findEmpty(grid) { + var available = []; + for (var r = 1; r < grid.size - 1; r++) + for (var c = 1; c < grid.size - 1; c++) { + var cluttered = false; + checkclutter: for (var dr = -1; dr <= 1; dr++) { + for (var dc = -1; dc <= 1; dc++) { + if (grid.get(r + dr, c + dc)) { + cluttered = true; + break checkclutter; + } + } + } + if (!cluttered) available.push({ + row: r, + col: c + }); + } + return (available.length === 0) ? null : available[Math.floor(available.length * Math.random())]; } - module.exports = Game; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 8c5bfaf..0000000 --- a/package-lock.json +++ /dev/null @@ -1,486 +0,0 @@ -{ - "name": "blockly-io", - "version": "0.9.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", - "requires": { - "mime-types": "2.1.17", - "negotiator": "0.6.1" - } - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - }, - "arraybuffer.slice": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", - "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=" - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, - "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "has-binary2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", - "integrity": "sha1-6D26SfC5vk0CbSc2U1DZ8D9Uvpg=", - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - } - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "requires": { - "better-assert": "1.0.2" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "requires": { - "better-assert": "1.0.2" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "requires": { - "debug": "2.6.9", - "depd": "1.1.1", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - }, - "socket.io": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz", - "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", - "requires": { - "debug": "2.6.9", - "engine.io": "3.1.4", - "socket.io-adapter": "1.1.1", - "socket.io-client": "2.0.4", - "socket.io-parser": "3.1.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "engine.io": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz", - "integrity": "sha1-PQIRtwpVLOhB/8fahiezAamkFi4=", - "requires": { - "accepts": "1.3.3", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "2.6.9", - "engine.io-parser": "2.1.1", - "uws": "0.14.5", - "ws": "3.3.1" - } - }, - "engine.io-parser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.1.tgz", - "integrity": "sha1-4Ps/DgRi9/WLt3waUun1p+JuRmg=", - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "0.0.6", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary2": "1.0.2" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - }, - "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" - }, - "socket.io-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", - "integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=", - "requires": { - "component-emitter": "1.2.1", - "debug": "2.6.9", - "has-binary2": "1.0.2", - "isarray": "2.0.1" - } - }, - "ultron": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", - "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" - }, - "ws": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.1.tgz", - "integrity": "sha512-8A/uRMnQy8KCQsmep1m7Bk+z/+LIkeF7w+TDMLtX1iZm5Hq9HsUDmgFGaW1ACW5Cj0b2Qo7wCvRhYN2ErUVp/A==", - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.0" - } - } - } - }, - "socket.io-client": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", - "integrity": "sha1-CRilUkBtxeVAs4Dc2Xr8SmQzL44=", - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "2.6.9", - "engine.io-client": "3.1.4", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "3.1.2", - "to-array": "0.1.4" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "engine.io-client": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.4.tgz", - "integrity": "sha1-T88TcLRxY70s6b4nM5ckMDUNTqE=", - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "2.6.9", - "engine.io-parser": "2.1.1", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "3.3.1", - "xmlhttprequest-ssl": "1.5.4", - "yeast": "0.1.2" - } - }, - "engine.io-parser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.1.tgz", - "integrity": "sha1-4Ps/DgRi9/WLt3waUun1p+JuRmg=", - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "0.0.6", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary2": "1.0.2" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - }, - "socket.io-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", - "integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=", - "requires": { - "component-emitter": "1.2.1", - "debug": "2.6.9", - "has-binary2": "1.0.2", - "isarray": "2.0.1" - } - }, - "ultron": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz", - "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ=" - }, - "ws": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.1.tgz", - "integrity": "sha512-8A/uRMnQy8KCQsmep1m7Bk+z/+LIkeF7w+TDMLtX1iZm5Hq9HsUDmgFGaW1ACW5Cj0b2Qo7wCvRhYN2ErUVp/A==", - "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.0" - } - }, - "xmlhttprequest-ssl": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.4.tgz", - "integrity": "sha1-BPVgkVcks4kIhxXMDteBPpZ3v1c=" - } - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "uws": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz", - "integrity": "sha1-Z6rzPEaypYel9mZtAPdpEyjxSdw=", - "optional": true - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" - } - } -} diff --git a/package.json b/package.json index 8808537..7684a85 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,30 @@ { - "name": "blockly-io", - "version": "0.9.0", - "description": "An multiplayer-IO type game (cloned from Paper-IO)", - "main": "", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/theKidOfArcrania/Blockly-IO.git" - }, - "keywords": [ - "Blockly-IO", - "Paper-IO", - "IO", - "Game" - ], - "author": "theKidOfArcrania", - "license": "MIT", - "bugs": { - "url": "https://github.com/theKidOfArcrania/Blockly-IO/issues" - }, - "homepage": "https://github.com/theKidOfArcrania/Blockly-IO#readme", - "dependencies": { - "finalhandler": "^1.0.0", - "serve-static": "^1.11.2", - "socket.io": "^2.0.4", - "socket.io-client": "^2.0.4" - } + "name": "paper-io", + "version": "0.0.1", + "description": "An multiplayer-IO type game (cloned from Paper-IO)", + "main": "", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/stevenjoezhang/paper.io.git" + }, + "keywords": [ + "Paper-IO", + "IO", + "Game" + ], + "author": "theKidOfArcrania", + "license": "MIT", + "bugs": { + "url": "https://github.com/stevenjoezhang/paper.io/issues" + }, + "homepage": "https://github.com/stevenjoezhang/paper.io", + "dependencies": { + "finalhandler": "^1.1.1", + "serve-static": "^1.13.2", + "socket.io": "^2.2.0", + "socket.io-client": "^2.2.0" + } } diff --git a/public/Queue.js b/public/Queue.js deleted file mode 100644 index 075a103..0000000 --- a/public/Queue.js +++ /dev/null @@ -1,2 +0,0 @@ -//code.stephenmorley.org -function Queue(){var a=[],b=0;this.getLength=function(){return a.length-b};this.isEmpty=function(){return 0==a.length};this.enqueue=function(b){a.push(b)};this.dequeue=function(){if(0!=a.length){var c=a[b];2*++b>=a.length&&(a=a.slice(b),b=0);return c}};this.peek=function(){return 0= minRow && r < maxRow && c >= minCol && c < maxCol; - if (animateSpec.after && viewable) - { - //Bouncing the squares. - var offsetBounce = getBounceOffset(animateSpec.frame); - y -= offsetBounce; - - shadowColor = animateSpec.after.shadowColor; - baseColor = animateSpec.after.lightBaseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1); - - ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); - ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); - } - - animateSpec.frame++; - if (animateSpec.frame >= ANIMATE_FRAMES) - animateGrid.set(r, c, null); - } - } - } -} - - -function paintUIBar(ctx) -{ - - //UI Bar background - ctx.fillStyle = "#24422c"; - ctx.fillRect(0, 0, canvasWidth, BAR_HEIGHT); - - var barOffset; - ctx.fillStyle = "white"; - ctx.font = "24px Changa"; - barOffset = (user && user.name) ? (ctx.measureText(user.name).width + 20) : 0; - ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); - - //Draw filled bar. - ctx.fillStyle = "rgba(180, 180, 180, .3)"; - ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); - - var userPortions = portionsRolling[user.num] ? portionsRolling[user.num].lag : 0; - var barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * userPortions + MIN_BAR_WIDTH); - ctx.fillStyle = user ? user.baseColor.rgbString() : ""; - ctx.fillRect(barOffset, 0, barSize, CELL_WIDTH); - ctx.fillStyle = user ? user.shadowColor.rgbString() : ""; - ctx.fillRect(barOffset, CELL_WIDTH, barSize, SHADOW_OFFSET); - - //TODO: dont reset kill count and zoom when we request frames. - //Percentage - ctx.fillStyle = "white"; - ctx.font = "18px Changa"; - ctx.fillText((userPortions * 100).toFixed(3) + "%", 5 + barOffset, CELL_WIDTH - 5); - - //Number of kills - var killsText = "Kills: " + client.kills; - var killsOffset = 20 + BAR_WIDTH + barOffset; - ctx.fillText(killsText, killsOffset, CELL_WIDTH - 5); - - //Calcuate rank - var sorted = []; - client.getPlayers().forEach(function(val) { - sorted.push({player: val, portion: playerPortion[val.num]}); - }); - sorted.sort(function(a, b) { - if (a.portion === b.portion) return a.player.num - b.player.num; - else return b.portion - a.portion; - }); - - var rank = sorted.findIndex(function(val) {return val.player === user}); - ctx.fillText("Rank: " + (rank === -1 ? "--" : rank + 1) + " of " + sorted.length, - ctx.measureText(killsText).width + killsOffset + 20, CELL_WIDTH - 5); - - //Rolling the leaderboard bars. - if (sorted.length > 0) - { - var maxPortion = sorted[0].portion; - client.getPlayers().forEach(function(player) { - var rolling = barProportionRolling[player.num]; - rolling.value = playerPortion[player.num] / maxPortion; - rolling.update(); - }); - } - - //Show leaderboard. - var leaderboardNum = Math.min(5, sorted.length); - for (var i = 0; i < leaderboardNum; i++) - { - var player = sorted[i].player; - var name = player.name || "Unnamed"; - var portion = barProportionRolling[player.num].lag; - - var nameWidth = ctx.measureText(name).width; - barSize = Math.ceil((BAR_WIDTH - MIN_BAR_WIDTH) * portion + MIN_BAR_WIDTH); - var barX = canvasWidth - barSize; - var barY = BAR_HEIGHT * (i + 1); - var offset = i == 0 ? 10 : 0; - - ctx.fillStyle = 'rgba(10, 10, 10, .3)'; - ctx.fillRect(barX - 10, barY + 10 - offset, barSize + 10, BAR_HEIGHT + offset); - ctx.fillStyle = player.baseColor.rgbString(); - ctx.fillRect(barX, barY, barSize, CELL_WIDTH); - ctx.fillStyle = player.shadowColor.rgbString(); - ctx.fillRect(barX, barY + CELL_WIDTH, barSize, SHADOW_OFFSET); - - ctx.fillStyle = "black"; - ctx.fillText(name, barX - nameWidth - 15, barY + 27); - - var percentage = (portionsRolling[player.num].lag * 100).toFixed(3) + "%"; - ctx.fillStyle = "white"; - ctx.fillText(percentage, barX + 5, barY + CELL_WIDTH - 5); - } - -} - -function paint(ctx) -{ - - ctx.fillStyle = '#e2ebf3'; //'whitesmoke'; - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - - //Move grid to viewport as said with the offsets, below the stats - ctx.save(); - ctx.translate(0, BAR_HEIGHT); - ctx.beginPath(); - ctx.rect(0, 0, gameWidth, gameHeight); - ctx.clip(); - - //Zoom in/out based on player stats. - ctx.scale(zoom, zoom); - ctx.translate(-offset[0] + BORDER_WIDTH, -offset[1] + BORDER_WIDTH); - - paintGrid(ctx); - client.getPlayers().forEach(function (p) { - var fr = p.waitLag; - if (fr < ANIMATE_FRAMES) - p.render(ctx, fr / ANIMATE_FRAMES); - else - p.render(ctx); - }); - - //Reset transform to paint fixed UI elements - ctx.restore(); - paintUIBar(ctx); - - if ((!user || user.dead) && !showedDead) - { - showedDead = true; - console.log("You died!"); - //return; - } -} - -function paintDoubleBuff() -{ - paint(offctx); - ctx.drawImage(offscreenCanvas, 0, 0); -} - -function update() { - updateSize(); - - //Change grid offsets. - for (var i = 0; i <= 1; i++) - { - if (animateTo[i] !== offset[i]) - { - if (client.allowAnimation) - { - var delta = animateTo[i] - offset[i]; - var dir = Math.sign(delta); - var mag = Math.min(SPEED, Math.abs(delta)); - offset[i] += dir * mag; - } - else - offset[i] = animateTo[i]; - } - } - - //Calculate player portions. - client.getPlayers().forEach(function(player) { - var roll = portionsRolling[player.num]; - roll.value = playerPortion[player.num] / GRID_SIZE / GRID_SIZE; - roll.update(); - }); - - //Zoom goes from 1 to .5, decreasing as portion goes up. TODO: maybe can modify this? - if (portionsRolling[user.num]) - zoom = 1 / (portionsRolling[user.num].lag + 1); - - //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. - if (user) centerOnPlayer(user, animateTo); -} - -//Helper methods. -function centerOnPlayer(player, pos) -{ - var xOff = Math.floor(player.posX - (gameWidth / zoom - CELL_WIDTH) / 2); - var yOff = Math.floor(player.posY - (gameHeight / zoom - CELL_WIDTH) / 2); - var gridWidth = grid.size * CELL_WIDTH + BORDER_WIDTH * 2; - pos[0] = xOff; //Math.max(Math.min(xOff, gridWidth + (BAR_WIDTH + 100) / zoom - gameWidth / zoom), 0); - pos[1] = yOff; //Math.max(Math.min(yOff, gridWidth - gameHeight / zoom), 0); -} - -function getBounceOffset(frame) -{ - var offsetBounce = ANIMATE_FRAMES; - var bounceNum = BOUNCE_FRAMES.length - 1; - while (bounceNum >= 0 && frame < offsetBounce - BOUNCE_FRAMES[bounceNum]) - { - offsetBounce -= BOUNCE_FRAMES[bounceNum]; - bounceNum--; - } - - if (bounceNum === -1) - { - return (offsetBounce - frame) * DROP_SPEED; - } - else - { - offsetBounce -= BOUNCE_FRAMES[bounceNum]; - frame = frame - offsetBounce; - var midFrame = BOUNCE_FRAMES[bounceNum] / 2; - if (frame >= midFrame) - return (BOUNCE_FRAMES[bounceNum] - frame) * DROP_SPEED; - else - return frame * DROP_SPEED; - } -} - -function showStats() { - //TODO: Show score stats. - $("#begin").removeClass("hidden"); - $("#begin").animate({ - opacity: .9999 - }, 1000, function() { - $("#stats").addClass("hidden").css("opacity", 0); - }); -} - -module.exports = exports = { - addPlayer: function(player) { - playerPortion[player.num] = 0; - portionsRolling[player.num] = new Rolling(9 / GRID_SIZE / GRID_SIZE, ANIMATE_FRAMES); - barProportionRolling[player.num] = new Rolling(0, ANIMATE_FRAMES); - }, - disconnect: function() { - //Show score stats. - $("#stats").removeClass("hidden"); - $("#stats").animate({ - opacity: .9999 - }, 3000, function() { - showStats(); - //Then fade back into the login screen. - }); - }, - removePlayer: function(player) { - delete playerPortion[player.num]; - delete portionsRolling[player.num]; - delete barProportionRolling[player.num]; - }, - - setUser: function(player) { - user = player; - centerOnPlayer(user, offset); - }, - reset: reset, - updateGrid: function(row, col, before, after) { - //Keep track of areas. - if (before) - playerPortion[before.num]--; - if (after) - playerPortion[after.num]++; - - //Queue animation - if (before === after || !client.allowAnimation) - return; - animateGrid.set(row, col, { - before: before, - after: after, - frame: 0 - }); - }, - paint: paintDoubleBuff, - update: update -}; - -},{"../client":3,"../game-core":9,"./rolling":1}],3:[function(require,module,exports){ -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]); -}); - -/** - * Provides requestAnimationFrame in a cross browser way. (edited so that this is also compatible with node.) - * @author paulirish / http://paulirish.com/ - */ -// window.requestAnimationFrame = function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { -// window.setTimeout( callback, 1000 / 60 ); -// }; - -var hasWindow; -try { - window.document; - hasWindow = true; -} catch (e) { - hasWindow = false; -} - -var requestAnimationFrame; -if ( !requestAnimationFrame ) { - requestAnimationFrame = ( function() { - if (hasWindow) { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } else { - return function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } - })(); -} - -//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; - requestAnimationFrame(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(); - requestAnimationFrame(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 - } -}); - -},{"./game-core":9,"socket.io-client":44}],4:[function(require,module,exports){ -/* global $ */ -var client = require("./client"); -var core = require("./game-core"); -var io = require('socket.io-client'); - -var GRID_SIZE = core.GRID_SIZE; -var CELL_WIDTH = core.CELL_WIDTH; - -client.allowAnimation = true; -client.renderer = require("./client-modes/user-mode"); - -/** - * Provides requestAnimationFrame in a cross browser way. (edited so that this is also compatible with node.) - * @author paulirish / http://paulirish.com/ - */ -// window.requestAnimationFrame = function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { -// window.setTimeout( callback, 1000 / 60 ); -// }; -var hasWindow; -try { - window.document; - hasWindow = true; -} catch (e) { - hasWindow = false; -} - -var requestAnimationFrame; -if ( !requestAnimationFrame ) { - requestAnimationFrame = ( function() { - if (hasWindow) { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } else { - return function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { - setTimeout( callback, 1000 / 60 ); - }; - } - })(); -} - -function run() { - client.connectGame('//' + window.location.hostname + ':8081', $('#name').val(), function(success, msg) { - if (success) - { - $("#begin").addClass("hidden"); - $("#begin").animate({ - opacity: 0 - }, 1000); - } - else - { - var error = $("#error"); - error.text(msg); - } - }); -} - -$(function() { - var error = $("#error"); - - if (!window.WebSocket) - { - error.text("Your browser does not support WebSockets!"); - return; - } - - error.text("Loading..."); //TODO: show loading screen. - var success = false; - var socket = io('http://' + window.location.hostname + ':8081', { - 'forceNew': true, - upgrade: false, - transports: ['websocket'] - }); - - socket.on('connect_error', function() { - if (!success) - error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)"); - }); - socket.emit("checkConn", function() { - success = true; - socket.disconnect(); - }); - setTimeout(function() { - if (!success) - error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)"); - else - { - error.text(""); - $("input").keypress(function(evt) { - if (evt.which === 13) - requestAnimationFrame(run); - }); - $("button").click(function(evt) { - requestAnimationFrame(run); - }); - } - }, 2000); -}); - - -//Event listeners -$(document).keydown(function(e) { - var newHeading = -1; - switch (e.which) - { - case 37: newHeading = 3; break; //LEFT - case 65: newHeading = 3; break; //LEFT (A) - - case 38: newHeading = 0; break; //UP - case 87: newHeading = 0; break; //UP (W) - - case 39: newHeading = 1; break; //RIGHT - case 68: newHeading = 1; break; //RIGHT (D) - - case 40: newHeading = 2; break; //DOWN - case 83: newHeading = 2; break; //DOWN (S) - default: return; //exit handler for other keys. - } - - client.changeHeading(newHeading); - e.preventDefault(); -}); - -},{"./client":3,"./client-modes/user-mode":2,"./game-core":9,"socket.io-client":44}],5:[function(require,module,exports){ - - -function Color(h, s, l, a) -{ - verifyRange(h, s, l); - if (a === undefined) a = 1; - else verifyRange(a); - - Object.defineProperties(this, { - "hue": {value: h, enumerable: true}, - "sat": {value: s, enumerable: true}, - "lum": {value: l, enumerable: true}, - "alpha": {value: a, enumerable: true}, - }); -} - -Color.fromData = function(data) { - return new Color(data.hue, data.sat, data.lum, data.alpha); -}; - -function verifyRange() -{ - for (var i = 0; i < arguments.length; i++) - { - if (arguments[i] < 0 || arguments[i] > 1) - throw new RangeError("H, S, L, and A parameters must be between the range [0, 1]"); - } -} - -Color.prototype.interpolateToString = function(color, amount) -{ - var rgbThis = hslToRgb(this.hue, this.sat, this.lum); - var rgbThat = hslToRgb(color.hue, color.sat, color.lum); - var rgb = []; - - for (var i = 0; i < 3; i++) - rgb[i] = Math.floor((rgbThat[i] - rgbThis[i]) * amount + rgbThis[i]); - return {rgbString: function() {return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')'}}; -} - -Color.prototype.deriveLumination = function(amount) -{ - var lum = this.lum + amount; - lum = Math.min(Math.max(lum, 0), 1); - return new Color(this.hue, this.sat, lum, this.alpha); -}; - -Color.prototype.deriveHue = function(amount) -{ - var hue = this.hue - amount; - return new Color(hue - Math.floor(hue), this.sat, this.lum, this.alpha); -}; - -Color.prototype.deriveSaturation = function(amount) -{ - var sat = this.sat + amount; - sat = Math.min(Math.max(sat, 0), 1); - return new Color(this.hue, sat, this.lum, this.alpha); -}; - -Color.prototype.deriveAlpha = function(newAlpha) -{ - verifyRange(newAlpha); - return new Color(this.hue, this.sat, this.lum, newAlpha); -}; - -Color.prototype.rgbString = function() { - var rgb = hslToRgb(this.hue, this.sat, this.lum); - rgb[3] = this.a; - return 'rgba(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ', ' + this.alpha + ')'; -}; - - -//http://stackoverflow.com/a/9493060/7344257 -function hslToRgb(h, s, l){ - var r, g, b; - - if(s == 0){ - r = g = b = l; // achromatic - }else{ - var hue2rgb = function hue2rgb(p, q, t){ - if(t < 0) t += 1; - if(t > 1) t -= 1; - if(t < 1/6) return p + (q - p) * 6 * t; - if(t < 1/2) return q; - if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; - return p; - }; - - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); - } - - return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; -} - -module.exports = Color; -},{}],6:[function(require,module,exports){ -function constant(val) { - return { - value: val, - enumerable: true - }; -} - -var consts = { - GRID_SIZE: constant(80), - CELL_WIDTH: constant(40), - SPEED: constant(5), - BORDER_WIDTH: constant(20), - MAX_PLAYERS: constant(81) -}; - -Object.defineProperties(module.exports, consts); - -},{}],7:[function(require,module,exports){ -var ANIMATE_FRAMES = 24; -var CELL_WIDTH = 40; - -//TODO: remove constants. -exports.initPlayer = function(grid, player) -{ - for (var dr = -1; dr <= 1; dr++) - for (var dc = -1; dc <= 1; dc++) - if (!grid.isOutOfBounds(dr + player.row, dc + player.col)) - grid.set(dr + player.row, dc + player.col, player); -}; -exports.updateFrame = function(grid, players, dead, notifyKill) -{ - var adead = []; - if (dead instanceof Array) - adead = dead; - - var kill; - if (!notifyKill) - kill = function() {}; - else - kill = function(killer, other) {if (!removing[other]) notifyKill(killer, other);}; - - //Move players. - var tmp = players.filter(function(val) { - val.move(); - if (val.dead) - adead.push(val); - return !val.dead; - }); - - //Remove players with collisions. - var removing = new Array(players.length); - for (var i = 0; i < players.length; i++) - { - for (var j = i; j < players.length; j++) - { - - //Remove those players when other players have hit their tail. - if (!removing[j] && players[j].tail.hitsTail(players[i])) - { - kill(i, j); - removing[j] = true; - //console.log("TAIL"); - } - if (!removing[i] && players[i].tail.hitsTail(players[j])) - { - kill(j, i); - removing[i] = true; - //console.log("TAIL"); - } - - //Remove players with collisons... - if (i !== j && squaresIntersect(players[i].posX, players[j].posX) && - squaresIntersect(players[i].posY, players[j].posY)) - { - //...if one player is own his own territory, the other is out. - if (grid.get(players[i].row, players[i].col) === players[i]) - { - kill(i, j); - removing[j] = true; - } - else if (grid.get(players[j].row, players[j].col) === players[j]) - { - kill(j, i); - removing[i] = true; - } - else - { - //...otherwise, the one that sustains most of the collision will be removed. - var areaI = area(players[i]); - var areaJ = area(players[j]); - - if (areaI === areaJ) - { - kill(i, j); - kill(j, i); - removing[i] = removing[j] = true; - } - else if (areaI > areaJ) - { - kill(j, i); - removing[i] = true; - } - else - { - kill(i, j); - removing[j] = true; - } - } - } - } - } - - tmp = tmp.filter(function(val, i) { - if (removing[i]) - { - adead.push(val); - val.die(); - } - return !removing[i]; - }); - players.length = tmp.length; - for (var i = 0; i < tmp.length; i++) - players[i] = tmp[i]; - - //Remove dead squares. - for (var r = 0; r < grid.size; r++) - { - for (var c = 0; c < grid.size; c++) - { - if (adead.indexOf(grid.get(r, c)) !== -1) - grid.set(r, c, null); - } - } -}; - -function squaresIntersect(a, b) -{ - if (a < b) - return b < a + CELL_WIDTH; - else - return a < b + CELL_WIDTH; -} - -function area(player) -{ - var xDest = player.col * CELL_WIDTH; - var yDest = player.row * CELL_WIDTH; - - if (player.posX === xDest) - return Math.abs(player.posY - yDest); - else - return Math.abs(player.posX - xDest); -} -},{}],8:[function(require,module,exports){ -function Grid(size, changeCallback) -{ - var grid = new Array(size); - var modified = false; - - var data = { - grid: grid, - size: size - }; - - this.get = function(row, col) - { - if (isOutOfBounds(data, row, col)) - throw new RangeError("Row or Column value out of bounds"); - return grid[row] && grid[row][col]; - } - this.set = function(row, col, value) - { - if (isOutOfBounds(data, row, col)) - throw new RangeError("Row or Column value out of bounds"); - - if (!grid[row]) - grid[row] = new Array(size); - var before = grid[row][col]; - grid[row][col] = value; - - if (typeof changeCallback === "function") - changeCallback(row, col, before, value); - - modified = true; - - return before; - } - this.reset = function() { - if (modified) - { - grid = new Array(size); - modified = false; - } - } - - this.isOutOfBounds = isOutOfBounds.bind(this, data); - - Object.defineProperty(this, "size", { - get: function() {return size; }, - enumerable: true - }); - -} - -function isOutOfBounds(data, row, col) -{ - return row < 0 || row >= data.size || col < 0 || col >= data.size; -} - -module.exports = Grid; -},{}],9:[function(require,module,exports){ -var core = require('./game-core'); -var consts = require('./game-consts'); - -exports.Color = require('./color'); -exports.Grid = require('./grid'); -exports.Player = require('./player'); - -exports.initPlayer = core.initPlayer; -exports.updateFrame = core.updateFrame; - -for (var prop in consts) { - Object.defineProperty(exports, prop, { - enumerable: true, - value: consts[prop] - }); -} - - -},{"./color":5,"./game-consts":6,"./game-core":7,"./grid":8,"./player":10}],10:[function(require,module,exports){ -var Stack = require("./stack"); -var Color = require("./color"); -var Grid = require("./grid.js"); -var consts = require("./game-consts.js"); - -var GRID_SIZE = consts.GRID_SIZE; -var CELL_WIDTH = consts.CELL_WIDTH; -var NEW_PLAYER_LAG = 60; //wait for a second at least. - -function defineGetter(getter) { - return { - get: getter, - enumerable: true - }; -} - -function defineInstanceMethods(thisobj, data /*, methods...*/) -{ - for (var i = 2; i < arguments.length; i++) - thisobj[arguments[i].name] = arguments[i].bind(this, data); -} - -function defineAccessorProperties(thisobj, data /*, names...*/) -{ - var descript = {}; - function getAt(name) { return function() {return data[name] } } - for (var i = 2; i < arguments.length; i++) - descript[arguments[i]] = defineGetter(getAt(arguments[i])); - Object.defineProperties(thisobj, descript); -} - -function TailMove(orientation) -{ - this.move = 1; - Object.defineProperty(this, "orientation", { - value: orientation, - enumerable: true - }); -} - -function Tail(player, sdata) -{ - var data = { - tail: [], - tailGrid: [], - prev: null, - startRow: 0, - startCol: 0, - prevRow: 0, - prevCol: 0, - player: player - }; - - if (sdata) - { - data.startRow = data.prevRow = sdata.startRow || 0; - data.startCol = data.prevCol = sdata.startCol || 0; - sdata.tail.forEach(function(val) { - addTail(data, val.orientation, val.move); - }); - } - data.grid = player.grid; - - defineInstanceMethods(this, data, addTail, hitsTail, fillTail, renderTail, reposition, serialData); - Object.defineProperty(this, "moves", { - get: function() {return data.tail.slice(0);}, - enumerable: true - }); -} - -//Instance methods. -function serialData(data) { - return { - tail: data.tail, - startRow: data.startRow, - startCol: data.startCol - }; -} - -function setTailGrid(data, tailGrid, r, c) -{ - if (!tailGrid[r]) - tailGrid[r] = []; - tailGrid[r][c] = true; -} - -function addTail(data, orientation, count) -{ - if (count === undefined) - count = 1; - if (!count || count < 0) - return; - - var prev = data.prev; - var r = data.prevRow, c = data.prevCol; - if (data.tail.length === 0) - setTailGrid(data, data.tailGrid, r, c); - - if (!prev || prev.orientation !== orientation) - { - prev = data.prev = new TailMove(orientation); - data.tail.push(prev); - prev.move += count - 1; - } - else - prev.move += count; - - for (var i = 0; i < count; i++) - { - var pos = walk([data.prevRow, data.prevCol], null, orientation, 1); - data.prevRow = pos[0]; - data.prevCol = pos[1]; - setTailGrid(data, data.tailGrid, pos[0], pos[1]); - } - -} - -function reposition(data, row, col) -{ - data.prevRow = data.startRow = row; - data.prevCol = data.startCol = col; - data.prev = null; - if (data.tail.length === 0) - return; - else - { - var ret = data.tail; - data.tail = []; - data.tailGrid = []; - return ret; - } -} - -/* -function render2(data, ctx) -{ - ctx.fillStyle = data.player.tailColor.rgbString(); - for (var r = 0; r < data.tailGrid.length; r++) - { - if (!data.tailGrid[r]) - continue; - for (var c = 0; c < data.tailGrid[r].length; c++) - if (data.tailGrid[r][c]) - ctx.fillRect(c * CELL_WIDTH, r * CELL_WIDTH, CELL_WIDTH, CELL_WIDTH); - } -} -*/ - -//Helper methods. -function renderTail(data, ctx) -{ - if (data.tail.length === 0) - return; - - ctx.fillStyle = data.player.tailColor.rgbString(); - - var prevOrient = -1; - var start = [data.startRow, data.startCol]; - - //fillTailRect(ctx, start, start); - data.tail.forEach(function(tail) { - var negDir = tail.orientation === 0 || tail.orientation === 3; - - var back = start; - if (!negDir) - start = walk(start, null, tail.orientation, 1); - var finish = walk(start, null, tail.orientation, tail.move - 1); - - if (tail.move > 1) - fillTailRect(ctx, start, finish); - if (prevOrient !== -1) - //Draw folding triangle. - renderCorner(ctx, back, prevOrient, tail.orientation); - - start = finish; - if (negDir) - walk(start, start, tail.orientation, 1); - prevOrient = tail.orientation; - }); - - var curOrient = data.player.currentHeading; - if (prevOrient === curOrient) - { - fillTailRect(ctx, start, start); - } - else - renderCorner(ctx, start, prevOrient, curOrient); -} - -function renderCorner(ctx, cornerStart, dir1, dir2) -{ - if (dir1 === 0 || dir2 === 0) - walk(cornerStart, cornerStart, 2, 1); - if (dir1 === 3 || dir2 === 3) - walk(cornerStart, cornerStart, 1, 1); - - var a = walk(cornerStart, null, dir2, 1); - var b = walk(a, null, dir1, 1); - - var triangle = new Path2D(); - triangle.moveTo(cornerStart[1] * CELL_WIDTH, cornerStart[0] * CELL_WIDTH); - triangle.lineTo(a[1] * CELL_WIDTH, a[0] * CELL_WIDTH); - triangle.lineTo(b[1] * CELL_WIDTH, b[0] * CELL_WIDTH); - triangle.closePath(); - for (var i = 0; i < 2; i++) - ctx.fill(triangle); -} - -function walk(from, ret, orient, dist) -{ - ret = ret || []; - ret[0] = from[0]; - ret[1] = from[1]; - switch (orient) - { - case 0: ret[0] -= dist; break; //UP - case 1: ret[1] += dist; break; //RIGHT - case 2: ret[0] += dist; break; //DOWN - case 3: ret[1] -= dist; break; //LEFT - } - return ret; -} - -function fillTailRect(ctx, start, end) -{ - var x = start[1] * CELL_WIDTH; - var y = start[0] * CELL_WIDTH; - var width = (end[1] - start[1]) * CELL_WIDTH; - var height = (end[0] - start[0]) * CELL_WIDTH; - - if (width === 0) - width += CELL_WIDTH; - if (height === 0) - height += CELL_WIDTH; - - if (width < 0) - { - x += width; - width = -width; - } - if (height < 0) - { - y += height; - height = -height; - } - ctx.fillRect(x, y, width, height); -} - -function fillTail(data) -{ - if (data.tail.length === 0) - return; - - function onTail(c) { return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; } - - var grid = data.grid; - var start = [data.startRow, data.startCol]; - var been = new Grid(grid.size); - var coords = []; - - coords.push(start); - while (coords.length > 0) //BFS for all tail spaces. - { - var coord = coords.shift(); - var r = coord[0]; - var c = coord[1]; - - if (grid.isOutOfBounds(r, c)) - continue; - - if (been.get(r, c)) - continue; - - if (onTail(coord)) //on the tail. - { - been.set(r, c, true); - grid.set(r, c, data.player); - - //Find all spots that this tail encloses. - floodFill(data, grid, r + 1, c, been); - floodFill(data, grid, r - 1, c, been); - floodFill(data, grid, r, c + 1, been); - floodFill(data, grid, r, c - 1, been); - - coords.push([r + 1, c]); - coords.push([r - 1, c]); - coords.push([r, c + 1]); - coords.push([r, c - 1]); - } - } -} - -function floodFill(data, grid, row, col, been) -{ - function onTail(c) { return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; } - - var start = [row, col]; - if (grid.isOutOfBounds(row, col) || been.get(row, col) || onTail(start) || grid.get(row, col) === data.player) - return; //Avoid allocating too many resources. - - var coords = []; - var filled = new Stack(GRID_SIZE * GRID_SIZE + 1); - var surrounded = true; - - coords.push(start); - while (coords.length > 0) - { - var coord = coords.shift(); - var r = coord[0]; - var c = coord[1]; - - if (grid.isOutOfBounds(r, c)) - { - surrounded = false; - continue; - } - - //End this traverse on boundaries (where we been, on the tail, and when we enter our territory) - if (been.get(r, c) || onTail(coord) || grid.get(r, c) === data.player) - continue; - - been.set(r, c, true); - - if (surrounded) - filled.push(coord); - - coords.push([r + 1, c]); - coords.push([r - 1, c]); - coords.push([r, c + 1]); - coords.push([r, c - 1]); - } - if (surrounded) - { - while (!filled.isEmpty()) - { - coord = filled.pop(); - grid.set(coord[0], coord[1], data.player); - } - } - - return surrounded; -} - -function hitsTail(data, other) -{ - return (data.prevRow !== other.row || data.prevCol !== other.col) && - (data.startRow !== other.row || data.startCol !== other.col) && - !!(data.tailGrid[other.row] && data.tailGrid[other.row][other.col]); -} - -var SPEED = 5; -var SHADOW_OFFSET = 10; - -function Player(grid, sdata) { - var data = {}; - - //Parameters - data.num = sdata.num; - data.name = sdata.name || ""; //|| "Player " + (data.num + 1); - data.grid = grid; - data.posX = sdata.posX; - data.posY = sdata.posY; - this.heading = data.currentHeading = sdata.currentHeading; //0 is up, 1 is right, 2 is down, 3 is left. - data.waitLag = sdata.waitLag || 0; - data.dead = false; - - //Only need colors for client side. - var base; - if (sdata.base) - base = this.baseColor = sdata.base instanceof Color ? sdata.base : Color.fromData(sdata.base); - else - { - var hue = Math.random(); - this.baseColor = base = new Color(hue, .8, .5); - } - this.lightBaseColor = base.deriveLumination(.1); - this.shadowColor = base.deriveLumination(-.3); - this.tailColor = base.deriveLumination(.3).deriveAlpha(.5); - - //Tail requires special handling. - this.grid = grid; //Temporary - if (sdata.tail) - data.tail = new Tail(this, sdata.tail); - else - { - data.tail = new Tail(this); - data.tail.reposition(calcRow(data), calcCol(data)); - } - - //Instance methods. - this.move = move.bind(this, data); - this.die = function() { data.dead = true;}; - this.serialData = function() { - return { - base: this.baseColor, - num: data.num, - name: data.name, - posX: data.posX, - posY: data.posY, - currentHeading: data.currentHeading, - tail: data.tail.serialData(), - waitLag: data.waitLag - }; - }; - - //Read-only Properties. - defineAccessorProperties(this, data, "currentHeading", "dead", "name", "num", "posX", "posY", "grid", "tail", "waitLag"); - Object.defineProperties(this, { - row: defineGetter(function() { return calcRow(data); }), - col: defineGetter(function() { return calcCol(data); }) - }); -} - -//Gets the next integer in positive or negative direction. -function nearestInteger(positive, val) -{ - return positive ? Math.ceil(val) : Math.floor(val); -} - -function calcRow(data) -{ - return nearestInteger(data.currentHeading === 2 /*DOWN*/, data.posY / CELL_WIDTH); -} - -function calcCol(data) -{ - return nearestInteger(data.currentHeading === 1 /*RIGHT*/, data.posX / CELL_WIDTH); -} - -//Instance methods -Player.prototype.render = function(ctx, fade) -{ - //Render tail. - this.tail.renderTail(ctx); - - //Render player. - fade = fade || 1; - ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString(); - ctx.fillRect(this.posX, this.posY, CELL_WIDTH, CELL_WIDTH); - - var mid = CELL_WIDTH / 2; - var grd = ctx.createRadialGradient(this.posX + mid, this.posY + mid - SHADOW_OFFSET, 1, - this.posX + mid, this.posY + mid - SHADOW_OFFSET, CELL_WIDTH); - grd.addColorStop(0, this.baseColor.deriveAlpha(fade).rgbString()); - grd.addColorStop(1, new Color(0, 0, 1, fade).rgbString()); - ctx.fillStyle = grd; - ctx.fillRect(this.posX - 1, this.posY - SHADOW_OFFSET, CELL_WIDTH + 2, CELL_WIDTH); - - //Render name - ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString(); - ctx.textAlign = "center"; - - var yoff = -SHADOW_OFFSET * 2; - if (this.row === 0) - yoff = SHADOW_OFFSET * 2 + CELL_WIDTH; - ctx.font = "18px Changa"; - ctx.fillText(this.name, this.posX + CELL_WIDTH / 2, this.posY + yoff); -}; - - -function move(data) -{ - if (data.waitLag < NEW_PLAYER_LAG) - { - data.waitLag++; - return; - } - - //Move to new position. - var heading = this.heading; - if (this.posX % CELL_WIDTH !== 0 || this.posY % CELL_WIDTH !== 0) - heading = data.currentHeading; - else - data.currentHeading = heading; - switch (heading) - { - case 0: data.posY -= SPEED; break; //UP - case 1: data.posX += SPEED; break; //RIGHT - case 2: data.posY += SPEED; break; //DOWN - case 3: data.posX -= SPEED; break; //LEFT - } - - //Check for out of bounds. - var row = this.row, col = this.col; - if (data.grid.isOutOfBounds(row, col)) - { - data.dead = true; - return; - } - - //Update tail position. - if (data.grid.get(row, col) === this) - { - //Safe zone! - this.tail.fillTail(); - this.tail.reposition(row, col); - } - //If we are completely in a new cell (not in our safe zone), we add to the tail. - else if (this.posX % CELL_WIDTH === 0 && this.posY % CELL_WIDTH === 0) - this.tail.addTail(heading); -} - -module.exports = Player; - -},{"./color":5,"./game-consts.js":6,"./grid.js":8,"./stack":11}],11:[function(require,module,exports){ - - -function Stack(initSize) -{ - var len = 0; - var arr = []; - - this.ensureCapacity = function(size) - { - arr.length = Math.max(arr.length, size || 0); - }; - - this.push = function(ele) - { - this[len] = ele; - len++; - }; - - this.pop = function() - { - if (len === 0) - return; - len--; - var tmp = this[len]; - this[len] = undefined; - return tmp; - }; - - this.isEmpty = function() { - return len === 0; - } - - this.ensureCapacity(initSize); - - - Object.defineProperty(this, "length", { - get: function() {return len;} - }); -} - -module.exports = Stack; -},{}],12:[function(require,module,exports){ -module.exports = after - -function after(count, callback, err_cb) { - var bail = false - err_cb = err_cb || noop - proxy.count = count - - return (count === 0) ? callback() : proxy - - function proxy(err, result) { - if (proxy.count <= 0) { - throw new Error('after called too many times') - } - --proxy.count - - // after first error, rest are passed to err_cb - if (err) { - bail = true - callback(err) - // future error callbacks will go to error handler - callback = err_cb - } else if (proxy.count === 0 && !bail) { - callback(null, result) - } - } -} - -function noop() {} - -},{}],13:[function(require,module,exports){ -/** - * An abstraction for slicing an arraybuffer even when - * ArrayBuffer.prototype.slice is not supported - * - * @api public - */ - -module.exports = function(arraybuffer, start, end) { - var bytes = arraybuffer.byteLength; - start = start || 0; - end = end || bytes; - - if (arraybuffer.slice) { return arraybuffer.slice(start, end); } - - if (start < 0) { start += bytes; } - if (end < 0) { end += bytes; } - if (end > bytes) { end = bytes; } - - if (start >= bytes || start >= end || bytes === 0) { - return new ArrayBuffer(0); - } - - var abv = new Uint8Array(arraybuffer); - var result = new Uint8Array(end - start); - for (var i = start, ii = 0; i < end; i++, ii++) { - result[ii] = abv[i]; - } - return result.buffer; -}; - -},{}],14:[function(require,module,exports){ - -/** - * Expose `Backoff`. - */ - -module.exports = Backoff; - -/** - * Initialize backoff timer with `opts`. - * - * - `min` initial timeout in milliseconds [100] - * - `max` max timeout [10000] - * - `jitter` [0] - * - `factor` [2] - * - * @param {Object} opts - * @api public - */ - -function Backoff(opts) { - opts = opts || {}; - this.ms = opts.min || 100; - this.max = opts.max || 10000; - this.factor = opts.factor || 2; - this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; - this.attempts = 0; -} - -/** - * Return the backoff duration. - * - * @return {Number} - * @api public - */ - -Backoff.prototype.duration = function(){ - var ms = this.ms * Math.pow(this.factor, this.attempts++); - if (this.jitter) { - var rand = Math.random(); - var deviation = Math.floor(rand * this.jitter * ms); - ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; - } - return Math.min(ms, this.max) | 0; -}; - -/** - * Reset the number of attempts. - * - * @api public - */ - -Backoff.prototype.reset = function(){ - this.attempts = 0; -}; - -/** - * Set the minimum duration - * - * @api public - */ - -Backoff.prototype.setMin = function(min){ - this.ms = min; -}; - -/** - * Set the maximum duration - * - * @api public - */ - -Backoff.prototype.setMax = function(max){ - this.max = max; -}; - -/** - * Set the jitter - * - * @api public - */ - -Backoff.prototype.setJitter = function(jitter){ - this.jitter = jitter; -}; - - -},{}],15:[function(require,module,exports){ -/* - * base64-arraybuffer - * https://github.com/niklasvh/base64-arraybuffer - * - * Copyright (c) 2012 Niklas von Hertzen - * Licensed under the MIT license. - */ -(function(){ - "use strict"; - - var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - // Use a lookup table to find the index. - var lookup = new Uint8Array(256); - for (var i = 0; i < chars.length; i++) { - lookup[chars.charCodeAt(i)] = i; - } - - exports.encode = function(arraybuffer) { - var bytes = new Uint8Array(arraybuffer), - i, len = bytes.length, base64 = ""; - - for (i = 0; i < len; i+=3) { - base64 += chars[bytes[i] >> 2]; - base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; - base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; - base64 += chars[bytes[i + 2] & 63]; - } - - if ((len % 3) === 2) { - base64 = base64.substring(0, base64.length - 1) + "="; - } else if (len % 3 === 1) { - base64 = base64.substring(0, base64.length - 2) + "=="; - } - - return base64; - }; - - exports.decode = function(base64) { - var bufferLength = base64.length * 0.75, - len = base64.length, i, p = 0, - encoded1, encoded2, encoded3, encoded4; - - if (base64[base64.length - 1] === "=") { - bufferLength--; - if (base64[base64.length - 2] === "=") { - bufferLength--; - } - } - - var arraybuffer = new ArrayBuffer(bufferLength), - bytes = new Uint8Array(arraybuffer); - - for (i = 0; i < len; i+=4) { - encoded1 = lookup[base64.charCodeAt(i)]; - encoded2 = lookup[base64.charCodeAt(i+1)]; - encoded3 = lookup[base64.charCodeAt(i+2)]; - encoded4 = lookup[base64.charCodeAt(i+3)]; - - bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); - bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); - bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); - } - - return arraybuffer; - }; -})(); - -},{}],16:[function(require,module,exports){ -(function (global){ -/** - * Create a blob builder even when vendor prefixes exist - */ - -var BlobBuilder = global.BlobBuilder - || global.WebKitBlobBuilder - || global.MSBlobBuilder - || global.MozBlobBuilder; - -/** - * Check if Blob constructor is supported - */ - -var blobSupported = (function() { - try { - var a = new Blob(['hi']); - return a.size === 2; - } catch(e) { - return false; - } -})(); - -/** - * Check if Blob constructor supports ArrayBufferViews - * Fails in Safari 6, so we need to map to ArrayBuffers there. - */ - -var blobSupportsArrayBufferView = blobSupported && (function() { - try { - var b = new Blob([new Uint8Array([1,2])]); - return b.size === 2; - } catch(e) { - return false; - } -})(); - -/** - * Check if BlobBuilder is supported - */ - -var blobBuilderSupported = BlobBuilder - && BlobBuilder.prototype.append - && BlobBuilder.prototype.getBlob; - -/** - * Helper function that maps ArrayBufferViews to ArrayBuffers - * Used by BlobBuilder constructor and old browsers that didn't - * support it in the Blob constructor. - */ - -function mapArrayBufferViews(ary) { - for (var i = 0; i < ary.length; i++) { - var chunk = ary[i]; - if (chunk.buffer instanceof ArrayBuffer) { - var buf = chunk.buffer; - - // if this is a subarray, make a copy so we only - // include the subarray region from the underlying buffer - if (chunk.byteLength !== buf.byteLength) { - var copy = new Uint8Array(chunk.byteLength); - copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); - buf = copy.buffer; - } - - ary[i] = buf; - } - } -} - -function BlobBuilderConstructor(ary, options) { - options = options || {}; - - var bb = new BlobBuilder(); - mapArrayBufferViews(ary); - - for (var i = 0; i < ary.length; i++) { - bb.append(ary[i]); - } - - return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); -}; - -function BlobConstructor(ary, options) { - mapArrayBufferViews(ary); - return new Blob(ary, options || {}); -}; - -module.exports = (function() { - if (blobSupported) { - return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; - } else if (blobBuilderSupported) { - return BlobBuilderConstructor; - } else { - return undefined; - } -})(); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],17:[function(require,module,exports){ -/** - * Slice reference. - */ - -var slice = [].slice; - -/** - * Bind `obj` to `fn`. - * - * @param {Object} obj - * @param {Function|String} fn or string - * @return {Function} - * @api public - */ - -module.exports = function(obj, fn){ - if ('string' == typeof fn) fn = obj[fn]; - if ('function' != typeof fn) throw new Error('bind() requires a function'); - var args = slice.call(arguments, 2); - return function(){ - return fn.apply(obj, args.concat(slice.call(arguments))); - } -}; - -},{}],18:[function(require,module,exports){ - -/** - * Expose `Emitter`. - */ - -module.exports = Emitter; - -/** - * Initialize a new `Emitter`. - * - * @api public - */ - -function Emitter(obj) { - if (obj) return mixin(obj); -}; - -/** - * Mixin the emitter properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - -function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; -} - -/** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.on = -Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks[event] = this._callbacks[event] || []) - .push(fn); - return this; -}; - -/** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.once = function(event, fn){ - var self = this; - this._callbacks = this._callbacks || {}; - - function on() { - self.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; -}; - -/** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.off = -Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = -Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks[event]; - if (!callbacks) return this; - - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks[event]; - return this; - } - - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - return this; -}; - -/** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ - -Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks[event]; - - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; -}; - -/** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - -Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks[event] || []; -}; - -/** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ - -Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; -}; - -},{}],19:[function(require,module,exports){ - -module.exports = function(a, b){ - var fn = function(){}; - fn.prototype = b.prototype; - a.prototype = new fn; - a.prototype.constructor = a; -}; -},{}],20:[function(require,module,exports){ - -module.exports = require('./lib/index'); - -},{"./lib/index":21}],21:[function(require,module,exports){ - -module.exports = require('./socket'); - -/** - * Exports parser - * - * @api public - * - */ -module.exports.parser = require('engine.io-parser'); - -},{"./socket":22,"engine.io-parser":34}],22:[function(require,module,exports){ -(function (global){ -/** - * Module dependencies. - */ - -var transports = require('./transports/index'); -var Emitter = require('component-emitter'); -var debug = require('debug')('engine.io-client:socket'); -var index = require('indexof'); -var parser = require('engine.io-parser'); -var parseuri = require('parseuri'); -var parsejson = require('parsejson'); -var parseqs = require('parseqs'); - -/** - * Module exports. - */ - -module.exports = Socket; - -/** - * Socket constructor. - * - * @param {String|Object} uri or options - * @param {Object} options - * @api public - */ - -function Socket (uri, opts) { - if (!(this instanceof Socket)) return new Socket(uri, opts); - - opts = opts || {}; - - if (uri && 'object' === typeof uri) { - opts = uri; - uri = null; - } - - if (uri) { - uri = parseuri(uri); - opts.hostname = uri.host; - opts.secure = uri.protocol === 'https' || uri.protocol === 'wss'; - opts.port = uri.port; - if (uri.query) opts.query = uri.query; - } else if (opts.host) { - opts.hostname = parseuri(opts.host).host; - } - - this.secure = null != opts.secure ? opts.secure - : (global.location && 'https:' === location.protocol); - - if (opts.hostname && !opts.port) { - // if no port is specified manually, use the protocol default - opts.port = this.secure ? '443' : '80'; - } - - this.agent = opts.agent || false; - this.hostname = opts.hostname || - (global.location ? location.hostname : 'localhost'); - this.port = opts.port || (global.location && location.port - ? location.port - : (this.secure ? 443 : 80)); - this.query = opts.query || {}; - if ('string' === typeof this.query) this.query = parseqs.decode(this.query); - this.upgrade = false !== opts.upgrade; - this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; - this.forceJSONP = !!opts.forceJSONP; - this.jsonp = false !== opts.jsonp; - this.forceBase64 = !!opts.forceBase64; - this.enablesXDR = !!opts.enablesXDR; - this.timestampParam = opts.timestampParam || 't'; - this.timestampRequests = opts.timestampRequests; - this.transports = opts.transports || ['polling', 'websocket']; - this.readyState = ''; - this.writeBuffer = []; - this.prevBufferLen = 0; - this.policyPort = opts.policyPort || 843; - this.rememberUpgrade = opts.rememberUpgrade || false; - this.binaryType = null; - this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; - this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; - - if (true === this.perMessageDeflate) this.perMessageDeflate = {}; - if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { - this.perMessageDeflate.threshold = 1024; - } - - // SSL options for Node.js client - this.pfx = opts.pfx || null; - this.key = opts.key || null; - this.passphrase = opts.passphrase || null; - this.cert = opts.cert || null; - this.ca = opts.ca || null; - this.ciphers = opts.ciphers || null; - this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized; - this.forceNode = !!opts.forceNode; - - // other options for Node.js client - var freeGlobal = typeof global === 'object' && global; - if (freeGlobal.global === freeGlobal) { - if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { - this.extraHeaders = opts.extraHeaders; - } - - if (opts.localAddress) { - this.localAddress = opts.localAddress; - } - } - - // set on handshake - this.id = null; - this.upgrades = null; - this.pingInterval = null; - this.pingTimeout = null; - - // set on heartbeat - this.pingIntervalTimer = null; - this.pingTimeoutTimer = null; - - this.open(); -} - -Socket.priorWebsocketSuccess = false; - -/** - * Mix in `Emitter`. - */ - -Emitter(Socket.prototype); - -/** - * Protocol version. - * - * @api public - */ - -Socket.protocol = parser.protocol; // this is an int - -/** - * Expose deps for legacy compatibility - * and standalone browser access. - */ - -Socket.Socket = Socket; -Socket.Transport = require('./transport'); -Socket.transports = require('./transports/index'); -Socket.parser = require('engine.io-parser'); - -/** - * Creates transport of the given type. - * - * @param {String} transport name - * @return {Transport} - * @api private - */ - -Socket.prototype.createTransport = function (name) { - debug('creating transport "%s"', name); - var query = clone(this.query); - - // append engine.io protocol identifier - query.EIO = parser.protocol; - - // transport name - query.transport = name; - - // session id if we already have one - if (this.id) query.sid = this.id; - - var transport = new transports[name]({ - agent: this.agent, - hostname: this.hostname, - port: this.port, - secure: this.secure, - path: this.path, - query: query, - forceJSONP: this.forceJSONP, - jsonp: this.jsonp, - forceBase64: this.forceBase64, - enablesXDR: this.enablesXDR, - timestampRequests: this.timestampRequests, - timestampParam: this.timestampParam, - policyPort: this.policyPort, - socket: this, - pfx: this.pfx, - key: this.key, - passphrase: this.passphrase, - cert: this.cert, - ca: this.ca, - ciphers: this.ciphers, - rejectUnauthorized: this.rejectUnauthorized, - perMessageDeflate: this.perMessageDeflate, - extraHeaders: this.extraHeaders, - forceNode: this.forceNode, - localAddress: this.localAddress - }); - - return transport; -}; - -function clone (obj) { - var o = {}; - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - o[i] = obj[i]; - } - } - return o; -} - -/** - * Initializes transport to use and starts probe. - * - * @api private - */ -Socket.prototype.open = function () { - var transport; - if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) { - transport = 'websocket'; - } else if (0 === this.transports.length) { - // Emit error on next tick so it can be listened to - var self = this; - setTimeout(function () { - self.emit('error', 'No transports available'); - }, 0); - return; - } else { - transport = this.transports[0]; - } - this.readyState = 'opening'; - - // Retry with the next transport if the transport is disabled (jsonp: false) - try { - transport = this.createTransport(transport); - } catch (e) { - this.transports.shift(); - this.open(); - return; - } - - transport.open(); - this.setTransport(transport); -}; - -/** - * Sets the current transport. Disables the existing one (if any). - * - * @api private - */ - -Socket.prototype.setTransport = function (transport) { - debug('setting transport %s', transport.name); - var self = this; - - if (this.transport) { - debug('clearing existing transport %s', this.transport.name); - this.transport.removeAllListeners(); - } - - // set up transport - this.transport = transport; - - // set up transport listeners - transport - .on('drain', function () { - self.onDrain(); - }) - .on('packet', function (packet) { - self.onPacket(packet); - }) - .on('error', function (e) { - self.onError(e); - }) - .on('close', function () { - self.onClose('transport close'); - }); -}; - -/** - * Probes a transport. - * - * @param {String} transport name - * @api private - */ - -Socket.prototype.probe = function (name) { - debug('probing transport "%s"', name); - var transport = this.createTransport(name, { probe: 1 }); - var failed = false; - var self = this; - - Socket.priorWebsocketSuccess = false; - - function onTransportOpen () { - if (self.onlyBinaryUpgrades) { - var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; - failed = failed || upgradeLosesBinary; - } - if (failed) return; - - debug('probe transport "%s" opened', name); - transport.send([{ type: 'ping', data: 'probe' }]); - transport.once('packet', function (msg) { - if (failed) return; - if ('pong' === msg.type && 'probe' === msg.data) { - debug('probe transport "%s" pong', name); - self.upgrading = true; - self.emit('upgrading', transport); - if (!transport) return; - Socket.priorWebsocketSuccess = 'websocket' === transport.name; - - debug('pausing current transport "%s"', self.transport.name); - self.transport.pause(function () { - if (failed) return; - if ('closed' === self.readyState) return; - debug('changing transport and sending upgrade packet'); - - cleanup(); - - self.setTransport(transport); - transport.send([{ type: 'upgrade' }]); - self.emit('upgrade', transport); - transport = null; - self.upgrading = false; - self.flush(); - }); - } else { - debug('probe transport "%s" failed', name); - var err = new Error('probe error'); - err.transport = transport.name; - self.emit('upgradeError', err); - } - }); - } - - function freezeTransport () { - if (failed) return; - - // Any callback called by transport should be ignored since now - failed = true; - - cleanup(); - - transport.close(); - transport = null; - } - - // Handle any error that happens while probing - function onerror (err) { - var error = new Error('probe error: ' + err); - error.transport = transport.name; - - freezeTransport(); - - debug('probe transport "%s" failed because of error: %s', name, err); - - self.emit('upgradeError', error); - } - - function onTransportClose () { - onerror('transport closed'); - } - - // When the socket is closed while we're probing - function onclose () { - onerror('socket closed'); - } - - // When the socket is upgraded while we're probing - function onupgrade (to) { - if (transport && to.name !== transport.name) { - debug('"%s" works - aborting "%s"', to.name, transport.name); - freezeTransport(); - } - } - - // Remove all listeners on the transport and on self - function cleanup () { - transport.removeListener('open', onTransportOpen); - transport.removeListener('error', onerror); - transport.removeListener('close', onTransportClose); - self.removeListener('close', onclose); - self.removeListener('upgrading', onupgrade); - } - - transport.once('open', onTransportOpen); - transport.once('error', onerror); - transport.once('close', onTransportClose); - - this.once('close', onclose); - this.once('upgrading', onupgrade); - - transport.open(); -}; - -/** - * Called when connection is deemed open. - * - * @api public - */ - -Socket.prototype.onOpen = function () { - debug('socket open'); - this.readyState = 'open'; - Socket.priorWebsocketSuccess = 'websocket' === this.transport.name; - this.emit('open'); - this.flush(); - - // we check for `readyState` in case an `open` - // listener already closed the socket - if ('open' === this.readyState && this.upgrade && this.transport.pause) { - debug('starting upgrade probes'); - for (var i = 0, l = this.upgrades.length; i < l; i++) { - this.probe(this.upgrades[i]); - } - } -}; - -/** - * Handles a packet. - * - * @api private - */ - -Socket.prototype.onPacket = function (packet) { - if ('opening' === this.readyState || 'open' === this.readyState || - 'closing' === this.readyState) { - debug('socket receive: type "%s", data "%s"', packet.type, packet.data); - - this.emit('packet', packet); - - // Socket is live - any packet counts - this.emit('heartbeat'); - - switch (packet.type) { - case 'open': - this.onHandshake(parsejson(packet.data)); - break; - - case 'pong': - this.setPing(); - this.emit('pong'); - break; - - case 'error': - var err = new Error('server error'); - err.code = packet.data; - this.onError(err); - break; - - case 'message': - this.emit('data', packet.data); - this.emit('message', packet.data); - break; - } - } else { - debug('packet received with socket readyState "%s"', this.readyState); - } -}; - -/** - * Called upon handshake completion. - * - * @param {Object} handshake obj - * @api private - */ - -Socket.prototype.onHandshake = function (data) { - this.emit('handshake', data); - this.id = data.sid; - this.transport.query.sid = data.sid; - this.upgrades = this.filterUpgrades(data.upgrades); - this.pingInterval = data.pingInterval; - this.pingTimeout = data.pingTimeout; - this.onOpen(); - // In case open handler closes socket - if ('closed' === this.readyState) return; - this.setPing(); - - // Prolong liveness of socket on heartbeat - this.removeListener('heartbeat', this.onHeartbeat); - this.on('heartbeat', this.onHeartbeat); -}; - -/** - * Resets ping timeout. - * - * @api private - */ - -Socket.prototype.onHeartbeat = function (timeout) { - clearTimeout(this.pingTimeoutTimer); - var self = this; - self.pingTimeoutTimer = setTimeout(function () { - if ('closed' === self.readyState) return; - self.onClose('ping timeout'); - }, timeout || (self.pingInterval + self.pingTimeout)); -}; - -/** - * Pings server every `this.pingInterval` and expects response - * within `this.pingTimeout` or closes connection. - * - * @api private - */ - -Socket.prototype.setPing = function () { - var self = this; - clearTimeout(self.pingIntervalTimer); - self.pingIntervalTimer = setTimeout(function () { - debug('writing ping packet - expecting pong within %sms', self.pingTimeout); - self.ping(); - self.onHeartbeat(self.pingTimeout); - }, self.pingInterval); -}; - -/** -* Sends a ping packet. -* -* @api private -*/ - -Socket.prototype.ping = function () { - var self = this; - this.sendPacket('ping', function () { - self.emit('ping'); - }); -}; - -/** - * Called on `drain` event - * - * @api private - */ - -Socket.prototype.onDrain = function () { - this.writeBuffer.splice(0, this.prevBufferLen); - - // setting prevBufferLen = 0 is very important - // for example, when upgrading, upgrade packet is sent over, - // and a nonzero prevBufferLen could cause problems on `drain` - this.prevBufferLen = 0; - - if (0 === this.writeBuffer.length) { - this.emit('drain'); - } else { - this.flush(); - } -}; - -/** - * Flush write buffers. - * - * @api private - */ - -Socket.prototype.flush = function () { - if ('closed' !== this.readyState && this.transport.writable && - !this.upgrading && this.writeBuffer.length) { - debug('flushing %d packets in socket', this.writeBuffer.length); - this.transport.send(this.writeBuffer); - // keep track of current length of writeBuffer - // splice writeBuffer and callbackBuffer on `drain` - this.prevBufferLen = this.writeBuffer.length; - this.emit('flush'); - } -}; - -/** - * Sends a message. - * - * @param {String} message. - * @param {Function} callback function. - * @param {Object} options. - * @return {Socket} for chaining. - * @api public - */ - -Socket.prototype.write = -Socket.prototype.send = function (msg, options, fn) { - this.sendPacket('message', msg, options, fn); - return this; -}; - -/** - * Sends a packet. - * - * @param {String} packet type. - * @param {String} data. - * @param {Object} options. - * @param {Function} callback function. - * @api private - */ - -Socket.prototype.sendPacket = function (type, data, options, fn) { - if ('function' === typeof data) { - fn = data; - data = undefined; - } - - if ('function' === typeof options) { - fn = options; - options = null; - } - - if ('closing' === this.readyState || 'closed' === this.readyState) { - return; - } - - options = options || {}; - options.compress = false !== options.compress; - - var packet = { - type: type, - data: data, - options: options - }; - this.emit('packetCreate', packet); - this.writeBuffer.push(packet); - if (fn) this.once('flush', fn); - this.flush(); -}; - -/** - * Closes the connection. - * - * @api private - */ - -Socket.prototype.close = function () { - if ('opening' === this.readyState || 'open' === this.readyState) { - this.readyState = 'closing'; - - var self = this; - - if (this.writeBuffer.length) { - this.once('drain', function () { - if (this.upgrading) { - waitForUpgrade(); - } else { - close(); - } - }); - } else if (this.upgrading) { - waitForUpgrade(); - } else { - close(); - } - } - - function close () { - self.onClose('forced close'); - debug('socket closing - telling transport to close'); - self.transport.close(); - } - - function cleanupAndClose () { - self.removeListener('upgrade', cleanupAndClose); - self.removeListener('upgradeError', cleanupAndClose); - close(); - } - - function waitForUpgrade () { - // wait for upgrade to finish since we can't send packets while pausing a transport - self.once('upgrade', cleanupAndClose); - self.once('upgradeError', cleanupAndClose); - } - - return this; -}; - -/** - * Called upon transport error - * - * @api private - */ - -Socket.prototype.onError = function (err) { - debug('socket error %j', err); - Socket.priorWebsocketSuccess = false; - this.emit('error', err); - this.onClose('transport error', err); -}; - -/** - * Called upon transport close. - * - * @api private - */ - -Socket.prototype.onClose = function (reason, desc) { - if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) { - debug('socket close with reason: "%s"', reason); - var self = this; - - // clear timers - clearTimeout(this.pingIntervalTimer); - clearTimeout(this.pingTimeoutTimer); - - // stop event from firing again for transport - this.transport.removeAllListeners('close'); - - // ensure transport won't stay open - this.transport.close(); - - // ignore further transport communication - this.transport.removeAllListeners(); - - // set ready state - this.readyState = 'closed'; - - // clear session id - this.id = null; - - // emit close event - this.emit('close', reason, desc); - - // clean buffers after, so users can still - // grab the buffers on `close` event - self.writeBuffer = []; - self.prevBufferLen = 0; - } -}; - -/** - * Filters upgrades, returning only those matching client transports. - * - * @param {Array} server upgrades - * @api private - * - */ - -Socket.prototype.filterUpgrades = function (upgrades) { - var filteredUpgrades = []; - for (var i = 0, j = upgrades.length; i < j; i++) { - if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); - } - return filteredUpgrades; -}; - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./transport":23,"./transports/index":24,"component-emitter":30,"debug":31,"engine.io-parser":34,"indexof":38,"parsejson":41,"parseqs":42,"parseuri":43}],23:[function(require,module,exports){ -/** - * Module dependencies. - */ - -var parser = require('engine.io-parser'); -var Emitter = require('component-emitter'); - -/** - * Module exports. - */ - -module.exports = Transport; - -/** - * Transport abstract constructor. - * - * @param {Object} options. - * @api private - */ - -function Transport (opts) { - this.path = opts.path; - this.hostname = opts.hostname; - this.port = opts.port; - this.secure = opts.secure; - this.query = opts.query; - this.timestampParam = opts.timestampParam; - this.timestampRequests = opts.timestampRequests; - this.readyState = ''; - this.agent = opts.agent || false; - this.socket = opts.socket; - this.enablesXDR = opts.enablesXDR; - - // SSL options for Node.js client - this.pfx = opts.pfx; - this.key = opts.key; - this.passphrase = opts.passphrase; - this.cert = opts.cert; - this.ca = opts.ca; - this.ciphers = opts.ciphers; - this.rejectUnauthorized = opts.rejectUnauthorized; - this.forceNode = opts.forceNode; - - // other options for Node.js client - this.extraHeaders = opts.extraHeaders; - this.localAddress = opts.localAddress; -} - -/** - * Mix in `Emitter`. - */ - -Emitter(Transport.prototype); - -/** - * Emits an error. - * - * @param {String} str - * @return {Transport} for chaining - * @api public - */ - -Transport.prototype.onError = function (msg, desc) { - var err = new Error(msg); - err.type = 'TransportError'; - err.description = desc; - this.emit('error', err); - return this; -}; - -/** - * Opens the transport. - * - * @api public - */ - -Transport.prototype.open = function () { - if ('closed' === this.readyState || '' === this.readyState) { - this.readyState = 'opening'; - this.doOpen(); - } - - return this; -}; - -/** - * Closes the transport. - * - * @api private - */ - -Transport.prototype.close = function () { - if ('opening' === this.readyState || 'open' === this.readyState) { - this.doClose(); - this.onClose(); - } - - return this; -}; - -/** - * Sends multiple packets. - * - * @param {Array} packets - * @api private - */ - -Transport.prototype.send = function (packets) { - if ('open' === this.readyState) { - this.write(packets); - } else { - throw new Error('Transport not open'); - } -}; - -/** - * Called upon open - * - * @api private - */ - -Transport.prototype.onOpen = function () { - this.readyState = 'open'; - this.writable = true; - this.emit('open'); -}; - -/** - * Called with data. - * - * @param {String} data - * @api private - */ - -Transport.prototype.onData = function (data) { - var packet = parser.decodePacket(data, this.socket.binaryType); - this.onPacket(packet); -}; - -/** - * Called with a decoded packet. - */ - -Transport.prototype.onPacket = function (packet) { - this.emit('packet', packet); -}; - -/** - * Called upon close. - * - * @api private - */ - -Transport.prototype.onClose = function () { - this.readyState = 'closed'; - this.emit('close'); -}; - -},{"component-emitter":30,"engine.io-parser":34}],24:[function(require,module,exports){ -(function (global){ -/** - * Module dependencies - */ - -var XMLHttpRequest = require('xmlhttprequest-ssl'); -var XHR = require('./polling-xhr'); -var JSONP = require('./polling-jsonp'); -var websocket = require('./websocket'); - -/** - * Export transports. - */ - -exports.polling = polling; -exports.websocket = websocket; - -/** - * Polling transport polymorphic constructor. - * Decides on xhr vs jsonp based on feature detection. - * - * @api private - */ - -function polling (opts) { - var xhr; - var xd = false; - var xs = false; - var jsonp = false !== opts.jsonp; - - if (global.location) { - var isSSL = 'https:' === location.protocol; - var port = location.port; - - // some user agents have empty `location.port` - if (!port) { - port = isSSL ? 443 : 80; - } - - xd = opts.hostname !== location.hostname || port !== opts.port; - xs = opts.secure !== isSSL; - } - - opts.xdomain = xd; - opts.xscheme = xs; - xhr = new XMLHttpRequest(opts); - - if ('open' in xhr && !opts.forceJSONP) { - return new XHR(opts); - } else { - if (!jsonp) throw new Error('JSONP disabled'); - return new JSONP(opts); - } -} - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./polling-jsonp":25,"./polling-xhr":26,"./websocket":28,"xmlhttprequest-ssl":29}],25:[function(require,module,exports){ -(function (global){ - -/** - * Module requirements. - */ - -var Polling = require('./polling'); -var inherit = require('component-inherit'); - -/** - * Module exports. - */ - -module.exports = JSONPPolling; - -/** - * Cached regular expressions. - */ - -var rNewline = /\n/g; -var rEscapedNewline = /\\n/g; - -/** - * Global JSONP callbacks. - */ - -var callbacks; - -/** - * Noop. - */ - -function empty () { } - -/** - * JSONP Polling constructor. - * - * @param {Object} opts. - * @api public - */ - -function JSONPPolling (opts) { - Polling.call(this, opts); - - this.query = this.query || {}; - - // define global callbacks array if not present - // we do this here (lazily) to avoid unneeded global pollution - if (!callbacks) { - // we need to consider multiple engines in the same page - if (!global.___eio) global.___eio = []; - callbacks = global.___eio; - } - - // callback identifier - this.index = callbacks.length; - - // add callback to jsonp global - var self = this; - callbacks.push(function (msg) { - self.onData(msg); - }); - - // append to query string - this.query.j = this.index; - - // prevent spurious errors from being emitted when the window is unloaded - if (global.document && global.addEventListener) { - global.addEventListener('beforeunload', function () { - if (self.script) self.script.onerror = empty; - }, false); - } -} - -/** - * Inherits from Polling. - */ - -inherit(JSONPPolling, Polling); - -/* - * JSONP only supports binary as base64 encoded strings - */ - -JSONPPolling.prototype.supportsBinary = false; - -/** - * Closes the socket. - * - * @api private - */ - -JSONPPolling.prototype.doClose = function () { - if (this.script) { - this.script.parentNode.removeChild(this.script); - this.script = null; - } - - if (this.form) { - this.form.parentNode.removeChild(this.form); - this.form = null; - this.iframe = null; - } - - Polling.prototype.doClose.call(this); -}; - -/** - * Starts a poll cycle. - * - * @api private - */ - -JSONPPolling.prototype.doPoll = function () { - var self = this; - var script = document.createElement('script'); - - if (this.script) { - this.script.parentNode.removeChild(this.script); - this.script = null; - } - - script.async = true; - script.src = this.uri(); - script.onerror = function (e) { - self.onError('jsonp poll error', e); - }; - - var insertAt = document.getElementsByTagName('script')[0]; - if (insertAt) { - insertAt.parentNode.insertBefore(script, insertAt); - } else { - (document.head || document.body).appendChild(script); - } - this.script = script; - - var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); - - if (isUAgecko) { - setTimeout(function () { - var iframe = document.createElement('iframe'); - document.body.appendChild(iframe); - document.body.removeChild(iframe); - }, 100); - } -}; - -/** - * Writes with a hidden iframe. - * - * @param {String} data to send - * @param {Function} called upon flush. - * @api private - */ - -JSONPPolling.prototype.doWrite = function (data, fn) { - var self = this; - - if (!this.form) { - var form = document.createElement('form'); - var area = document.createElement('textarea'); - var id = this.iframeId = 'eio_iframe_' + this.index; - var iframe; - - form.className = 'socketio'; - form.style.position = 'absolute'; - form.style.top = '-1000px'; - form.style.left = '-1000px'; - form.target = id; - form.method = 'POST'; - form.setAttribute('accept-charset', 'utf-8'); - area.name = 'd'; - form.appendChild(area); - document.body.appendChild(form); - - this.form = form; - this.area = area; - } - - this.form.action = this.uri(); - - function complete () { - initIframe(); - fn(); - } - - function initIframe () { - if (self.iframe) { - try { - self.form.removeChild(self.iframe); - } catch (e) { - self.onError('jsonp polling iframe removal error', e); - } - } - - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - var html = '