diff --git a/color.js b/color.js index bdaf25a..54386d5 100644 --- a/color.js +++ b/color.js @@ -23,6 +23,17 @@ function verifyRange() } } +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; diff --git a/game-client.js b/game-client.js index 10bd585..39cc532 100644 --- a/game-client.js +++ b/game-client.js @@ -1,5 +1,6 @@ var Player = require("./player.js"); var Grid = require("./grid.js"); +var Color = require("./color.js"); /** * Provides requestAnimationFrame in a cross browser way. @@ -109,6 +110,7 @@ $(function() { var userPortion = 0; var lagPortion = 0; var portionSpeed = 0; + var zoom = 1; //TODO: current player index var user = players[0]; @@ -226,10 +228,10 @@ $(function() { function centerOnPlayer(player, pos) { - var xOff = Math.floor(player.posX - (gameWidth - CELL_WIDTH) / 2); - var yOff = Math.floor(player.posY - (gameHeight - CELL_WIDTH) / 2); - pos[0] = Math.max(Math.min(xOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameWidth), 0); - pos[1] = Math.max(Math.min(yOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameHeight), 0); + var xOff = Math.floor(player.posX - (gameWidth / zoom - CELL_WIDTH) / 2); + var yOff = Math.floor(player.posY - (gameHeight / zoom - CELL_WIDTH) / 2); + pos[0] = Math.max(Math.min(xOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameWidth / zoom), 0); + pos[1] = Math.max(Math.min(yOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameHeight / zoom), 0); } function area(player) @@ -305,10 +307,13 @@ $(function() { //paintGridLines(); //Get viewing limits - var minRow = Math.max(Math.floor((offset[1] - BORDER_WIDTH) / CELL_WIDTH), 0); - var minCol = Math.max(Math.floor((offset[0] - BORDER_WIDTH) / CELL_WIDTH), 0); - var maxRow = Math.min(Math.ceil((offset[1] + gameHeight) / CELL_WIDTH), grid.size); - var maxCol = Math.min(Math.ceil((offset[0] + gameWidth) / CELL_WIDTH), grid.size); + 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++) @@ -319,13 +324,16 @@ $(function() { var x = c * CELL_WIDTH, y = r * CELL_WIDTH, baseColor, shadowColor; var animateSpec = animateGrid.get(r, c); + var adjust = 1; if (!animateOff && animateSpec) { if (animateSpec.before) //fading animation { - var alpha = 1 - (animateSpec.frame / ANIMATE_FRAMES); - baseColor = animateSpec.before.baseColor.deriveAlpha(alpha); - shadowColor = animateSpec.before.shadowColor.deriveAlpha(alpha); + var frac = (animateSpec.frame / ANIMATE_FRAMES); + var back = new Color(.58, .41, .92, 1); + baseColor = animateSpec.before.baseColor.interpolateToString(back, frac); + shadowColor = animateSpec.before.shadowColor.interpolateToString(back, frac); + adjust = 0; } else continue; @@ -343,11 +351,12 @@ $(function() { var bottomEmpty = !bottomAnimate || (bottomAnimate.after && bottomAnimate.before); if (hasBottom && ((!!bottomAnimate ^ !!animateSpec) || bottomEmpty)) { + ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); + ctx.fillRect(x, y + CELL_WIDTH + 1, CELL_WIDTH + 1, SHADOW_OFFSET); } ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); } } @@ -375,9 +384,9 @@ $(function() { baseColor = animateSpec.after.baseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1); ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + SHADOW_OFFSET, CELL_WIDTH, CELL_WIDTH); + ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); } animateSpec.frame++; @@ -420,12 +429,17 @@ $(function() { ctx.fillStyle = 'whitesmoke'; ctx.fillRect(0, 0, canvasWidth, canvasHeight); - //Draw the grid items. + //Move grid to viewport as said with the offsets, below the stats ctx.save(); + ctx.translate(0, BAR_HEIGHT); ctx.beginPath(); - ctx.translate(-offset[0] + BORDER_WIDTH, -offset[1] + BORDER_WIDTH + BAR_HEIGHT); - ctx.rect(offset[0] - BORDER_WIDTH, offset[1] - BORDER_WIDTH, canvasWidth, canvasHeight); + 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(); players.forEach(function (p) { var fr = newPlayerFrames[p.num] || 0; @@ -435,8 +449,6 @@ $(function() { p.render(ctx); }); - - //Reset transform to paint fixed UI elements ctx.restore(); @@ -450,6 +462,8 @@ $(function() { barOffset = ctx.measureText(user.name).width + 10; ctx.fillText(user.name, 5, CELL_WIDTH - 5); + zoom = .0014 / lagPortion; + //Draw filled bar. ctx.fillStyle = "rgba(180, 180, 180, .3)"; ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); diff --git a/game-server.js b/game-server.js index 1abf844..b46c854 100644 --- a/game-server.js +++ b/game-server.js @@ -1,20 +1,79 @@ -var Player = require("./player-server.js"); +var GRID_SIZE = 80; +var CELL_WIDTH = 40; +var Player = require("./player.js"); + function Game(id) { + var players = []; var newPlayers = []; var frame = 0; + var filled = 0; + var grid = new Grid(GRID_SIZE, function(row, col, before, after) { + if (!!after ^ !!before) + { + if (after) + filled++; + else + filled--; + } + }); + this.id = id; - this.addPlayer = function(client) { - var p = {num: players.length, client: client}; - players.push(p); - newPlayers.push(p); + this.addPlayer = function(client, name) { + var start = findEmpty(grid); + if (!start) + return false; + + var params = { + posX: start.col * CELL_WIDTH, + posY: start.row * CELL_WIDTH, + currentHeading: getRandomInt(0, 4), + name: name, + num: players.length + } + + var p = new Player(false, grid, params); + p.client = client; + player.push(p); + newPlayer.push(p); + client.emit("game", {players, }) + return true; } } +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())]; +} + module.exports = Game; \ No newline at end of file diff --git a/player.js b/player.js index 8eac3e5..d6a3d89 100644 --- a/player.js +++ b/player.js @@ -386,7 +386,16 @@ function Player(isClient, grid, sdata) { //Instance methods. this.move = move.bind(this, data); this.die = function() {data.dead = true;}; - + this.serialData = function() { + return { + num: data.num, + name: data.name, + posX: data.posX, + posY: data.posY, + currentHeading: data.currentHeading, + tail: data.tail.serialData() + }; + } //Read-only Properties. defineAccessorProperties(this, data, "currentHeading", "dead", "name", "num", "posX", "posY", "tail"); Object.defineProperties(this, { diff --git a/public/bundle.js b/public/bundle.js index 0556c11..c83c4b8 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -24,6 +24,17 @@ function verifyRange() } } +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; @@ -87,6 +98,7 @@ module.exports = Color; },{}],2:[function(require,module,exports){ var Player = require("./player.js"); var Grid = require("./grid.js"); +var Color = require("./color.js"); /** * Provides requestAnimationFrame in a cross browser way. @@ -196,6 +208,7 @@ $(function() { var userPortion = 0; var lagPortion = 0; var portionSpeed = 0; + var zoom = 1; //TODO: current player index var user = players[0]; @@ -313,10 +326,10 @@ $(function() { function centerOnPlayer(player, pos) { - var xOff = Math.floor(player.posX - (gameWidth - CELL_WIDTH) / 2); - var yOff = Math.floor(player.posY - (gameHeight - CELL_WIDTH) / 2); - pos[0] = Math.max(Math.min(xOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameWidth), 0); - pos[1] = Math.max(Math.min(yOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameHeight), 0); + var xOff = Math.floor(player.posX - (gameWidth / zoom - CELL_WIDTH) / 2); + var yOff = Math.floor(player.posY - (gameHeight / zoom - CELL_WIDTH) / 2); + pos[0] = Math.max(Math.min(xOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameWidth / zoom), 0); + pos[1] = Math.max(Math.min(yOff, grid.size * CELL_WIDTH + BORDER_WIDTH * 2 - gameHeight / zoom), 0); } function area(player) @@ -392,10 +405,13 @@ $(function() { //paintGridLines(); //Get viewing limits - var minRow = Math.max(Math.floor((offset[1] - BORDER_WIDTH) / CELL_WIDTH), 0); - var minCol = Math.max(Math.floor((offset[0] - BORDER_WIDTH) / CELL_WIDTH), 0); - var maxRow = Math.min(Math.ceil((offset[1] + gameHeight) / CELL_WIDTH), grid.size); - var maxCol = Math.min(Math.ceil((offset[0] + gameWidth) / CELL_WIDTH), grid.size); + 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++) @@ -406,13 +422,16 @@ $(function() { var x = c * CELL_WIDTH, y = r * CELL_WIDTH, baseColor, shadowColor; var animateSpec = animateGrid.get(r, c); + var adjust = 1; if (!animateOff && animateSpec) { if (animateSpec.before) //fading animation { - var alpha = 1 - (animateSpec.frame / ANIMATE_FRAMES); - baseColor = animateSpec.before.baseColor.deriveAlpha(alpha); - shadowColor = animateSpec.before.shadowColor.deriveAlpha(alpha); + var frac = (animateSpec.frame / ANIMATE_FRAMES); + var back = new Color(.58, .41, .92, 1); + baseColor = animateSpec.before.baseColor.interpolateToString(back, frac); + shadowColor = animateSpec.before.shadowColor.interpolateToString(back, frac); + adjust = 0; } else continue; @@ -430,11 +449,12 @@ $(function() { var bottomEmpty = !bottomAnimate || (bottomAnimate.after && bottomAnimate.before); if (hasBottom && ((!!bottomAnimate ^ !!animateSpec) || bottomEmpty)) { + ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); + ctx.fillRect(x, y + CELL_WIDTH + 1, CELL_WIDTH + 1, SHADOW_OFFSET); } ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); } } @@ -462,9 +482,9 @@ $(function() { baseColor = animateSpec.after.baseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1); ctx.fillStyle = shadowColor.rgbString(); - ctx.fillRect(x, y + SHADOW_OFFSET, CELL_WIDTH, CELL_WIDTH); + ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); ctx.fillStyle = baseColor.rgbString(); - ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + ctx.fillRect(x, y, CELL_WIDTH + 1, CELL_WIDTH + 1); } animateSpec.frame++; @@ -507,12 +527,17 @@ $(function() { ctx.fillStyle = 'whitesmoke'; ctx.fillRect(0, 0, canvasWidth, canvasHeight); - //Draw the grid items. + //Move grid to viewport as said with the offsets, below the stats ctx.save(); + ctx.translate(0, BAR_HEIGHT); ctx.beginPath(); - ctx.translate(-offset[0] + BORDER_WIDTH, -offset[1] + BORDER_WIDTH + BAR_HEIGHT); - ctx.rect(offset[0] - BORDER_WIDTH, offset[1] - BORDER_WIDTH, canvasWidth, canvasHeight); + 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(); players.forEach(function (p) { var fr = newPlayerFrames[p.num] || 0; @@ -522,8 +547,6 @@ $(function() { p.render(ctx); }); - - //Reset transform to paint fixed UI elements ctx.restore(); @@ -537,6 +560,8 @@ $(function() { barOffset = ctx.measureText(user.name).width + 10; ctx.fillText(user.name, 5, CELL_WIDTH - 5); + zoom = .0014 / lagPortion; + //Draw filled bar. ctx.fillStyle = "rgba(180, 180, 180, .3)"; ctx.fillRect(barOffset, 0, BAR_WIDTH, BAR_HEIGHT); @@ -591,7 +616,7 @@ $(function() { }); }); -},{"./grid.js":3,"./player.js":4}],3:[function(require,module,exports){ +},{"./color.js":1,"./grid.js":3,"./player.js":4}],3:[function(require,module,exports){ function Grid(size, changeCallback) { var grid = new Array(size); @@ -1027,7 +1052,16 @@ function Player(isClient, grid, sdata) { //Instance methods. this.move = move.bind(this, data); this.die = function() {data.dead = true;}; - + this.serialData = function() { + return { + num: data.num, + name: data.name, + posX: data.posX, + posY: data.posY, + currentHeading: data.currentHeading, + tail: data.tail.serialData() + }; + } //Read-only Properties. defineAccessorProperties(this, data, "currentHeading", "dead", "name", "num", "posX", "posY", "tail"); Object.defineProperties(this, { diff --git a/public/index.html b/public/index.html index 36a83e3..9b011dd 100644 --- a/public/index.html +++ b/public/index.html @@ -11,6 +11,15 @@ overflow: hidden; background: black; } + + canvas { + image-rendering: optimizeSpeed; /* Older versions of FF */ + image-rendering: -moz-crisp-edges; /* FF 6.0+ */ + image-rendering: -webkit-optimize-contrast; /* Safari */ + image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */ + image-rendering: pixelated; /* Awesome future-browsers */ + -ms-interpolation-mode: nearest-neighbor; /* IE */ + }