From 3ce7ba97cfb4cc63052be900f73ce56605e7b67e Mon Sep 17 00:00:00 2001 From: theKidOfArcrania Date: Thu, 23 Feb 2017 00:51:41 +0000 Subject: [PATCH] Animations work! (albeit laggy) --- Grid.js | 49 +++++++++++++ color.js | 87 ++++++++++++++++++++++ game.js | 216 ++++++++++++++++++++++++++++++++++++++++-------------- player.js | 93 ++++++++--------------- test.html | 2 + 5 files changed, 332 insertions(+), 115 deletions(-) create mode 100644 Grid.js create mode 100644 color.js diff --git a/Grid.js b/Grid.js new file mode 100644 index 0000000..f2fe38d --- /dev/null +++ b/Grid.js @@ -0,0 +1,49 @@ +this.Grid = (function() +{ + function Grid(size, changeCallback) + { + var grid = new Array(size); + + 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); + + return before; + } + + 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; + } + + return Grid; +})() \ No newline at end of file diff --git a/color.js b/color.js new file mode 100644 index 0000000..2ba343c --- /dev/null +++ b/color.js @@ -0,0 +1,87 @@ +this.Color = function() +{ + + 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 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.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)]; + } + + return Color; +}() \ No newline at end of file diff --git a/game.js b/game.js index e9ca7d8..55bfc6d 100644 --- a/game.js +++ b/game.js @@ -1,14 +1,12 @@ var Player; - if (!Player) throw new Error("Requires player.js"); -//Thanks to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random -function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min)) + min; -} +var Grid; +if (!Grid) + throw new Error("Requires grid.js"); + + /** * Provides requestAnimationFrame in a cross browser way. @@ -35,36 +33,47 @@ $(function() { var CELL_WIDTH = 30; var SPEED = 5; var SHADOW_OFFSET = 5; - + var MAX_FRAMES = 16; + var DROP_HEIGHT = 16; + var BOUNCE_FRAMES = 8; + var DROP_SPEED = 2; var canvas = $("#main-ui")[0]; var ctx = canvas.getContext('2d'); var width = canvas.width = window.innerWidth - 20; var height = canvas.height = window.innerHeight - 20; - + var players = []; - var grid = []; + var animateOff = true; + var animateGrid = new Grid(GRID_SIZE); + var grid = new Grid(GRID_SIZE, function(row, col, before, after) { + if (before === after || animateOff) + return; + animateGrid.set(row, col, { + before: before, + after: after, + frame: 0 + }); + }); //Load players. - for (var p = 0; p < 1; p++) + for (var p = 0; p < 9; p++) { //TODO: socket loading. - players[p] = new Player(null, grid, p); + players[p] = new Player(null, grid, p, getRandomInt(0, GRID_SIZE), getRandomInt(0, GRID_SIZE)); } //Load grid. - for (var r = 0; r < GRID_SIZE; r++) + for (var r = 0; r < grid.size; r++) { - grid[r] = []; - for (var c = 0; c < GRID_SIZE; c++) + for (var c = 0; c < grid.size; c++) { //TODO: load data. - if (Math.random() < .9) - grid[r][c] = -1; - else - grid[r][c] = players[getRandomInt(0, players.length)]; + if (Math.random() > .9) + grid.set(r, c, players[getRandomInt(0, players.length)]); } } + animateOff = false; var frameCount = 0; var animateTo = [0, 0]; @@ -72,6 +81,7 @@ $(function() { //TODO: current player index var user = players[0]; + centerOnPlayer(user, offset); function update() { @@ -115,9 +125,9 @@ $(function() { squaresIntersect(players[i].startY, players[j].startY)) { //...if one player is own his own territory, the other is out. - if (grid[players[i].row][players[i].col] === players[i]) + if (grid.get(players[i].row, players[i].col) === players[i]) removing[j] = true; - else if (grid[players[j].row][players[j].col] === players[j]) + else if (grid.get(players[j].row, players[j].col) === players[j]) removing[i] = true; else { @@ -148,13 +158,25 @@ $(function() { dead.forEach(function(val) { console.log(val.name + " is dead"); }); + for (var r = 0; r < grid.size; r++) + { + for (var c = 0; c < grid.size; c++) + { + if (dead.indexOf(grid.get(r, c)) !== -1) + grid.set(r, c, null); + } + } //TODO: animate dead, and if this player is dead. - var xOff = Math.floor(user.posX - (width - CELL_WIDTH) / 2); - var yOff = Math.floor(user.posY - (height - CELL_WIDTH) / 2); - - animateTo[0] = Math.min(Math.max(xOff, 0), GRID_SIZE * CELL_WIDTH); - animateTo[1] = Math.min(Math.max(yOff, 0), GRID_SIZE * CELL_WIDTH); + centerOnPlayer(user, animateTo); + } + + function centerOnPlayer(player, pos) + { + var xOff = Math.floor(player.posX - (width - CELL_WIDTH) / 2); + var yOff = Math.floor(player.posY - (height - CELL_WIDTH) / 2); + pos[0] = Math.min(Math.max(xOff, 0), grid.size * CELL_WIDTH - width / 2); + pos[1] = Math.min(Math.max(yOff, 0), grid.size * CELL_WIDTH - width / 2); } function area(player) @@ -168,6 +190,13 @@ $(function() { return Math.abs(player.startX - xDest); } + //Thanks to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random + function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; + } + function squaresIntersect(a, b) { if (a < b) @@ -176,44 +205,122 @@ $(function() { return a < b + CELL_WIDTH; } + function paintGridLines() + { + ctx.fillStyle = 'lightgray'; + ctx.beginPath(); + for (var x = modRotate(-offset[0], CELL_WIDTH); x < width; x += CELL_WIDTH) + { + ctx.moveTo(x, 0); + ctx.lineTo(x, height); + } + for (var y = modRotate(-offset[1], CELL_WIDTH); y < height; y+= CELL_WIDTH) + { + ctx.moveTo(0, y); + ctx.lineTo(width, y); + } + ctx.stroke(); + } + + function modRotate(val, mod) + { + var res = val % mod; + if (res >= 0) + return res; + else + return mod + res; + } + function paintGrid() { //Paint bottom grid lines. - //ctx.fillStyle = 'lightgray'; - //ctx.beginPath(); - //for (var x = modRotate(-offset[0], CELL_WIDTH); x < width; x += CELL_WIDTH) - //{ - // ctx.moveTo(x, 0); - // ctx.lineTo(x, height); - //} - //for (var y = modRotate(-offset[1], CELL_WIDTH); y < height; y+= CELL_WIDTH) - //{ - // ctx.moveTo(0, y); - // ctx.lineTo(width, y); - //} - //ctx.stroke(); + //paintGridLines(); - //Paint occupied areas. - for (var r = Math.floor(offset[1] / CELL_WIDTH); r * CELL_WIDTH - offset[1] < height; r++) + //Paint occupied areas. (and fading ones). + for (var r = Math.floor(offset[1] / CELL_WIDTH); r < grid.size && r * CELL_WIDTH - offset[1] < height; r++) { - for (var c = Math.floor(offset[0] / CELL_WIDTH); c * CELL_WIDTH - offset[0] < width; c++) + for (var c = Math.floor(offset[0] / CELL_WIDTH); c < grid.size && c * CELL_WIDTH - offset[0] < width; c++) { - if (grid[r][c] !== -1) + var p = grid.get(r, c); + var x = c * CELL_WIDTH, y = r * CELL_WIDTH, baseColor, shadowColor; + + var animateSpec = animateGrid.get(r, c); + if (!animateOff && animateSpec) { - var x = c * CELL_WIDTH, - y = r * CELL_WIDTH, - p = grid[r][c]; - ctx.fillStyle = p.shadowColor; - ctx.fillRect(x, y + SHADOW_OFFSET, CELL_WIDTH, CELL_WIDTH); - ctx.fillStyle = p.baseColor; - ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + if (animateSpec.before) //fading animation + { + var alpha = 1 - (animateSpec.frame / MAX_FRAMES); + baseColor = animateSpec.before.baseColor.deriveAlpha(alpha); + shadowColor = animateSpec.before.shadowColor.deriveAlpha(alpha); + } + else + continue; + } + else if (p) + { + baseColor = p.baseColor; + shadowColor = p.shadowColor; + } + else //No animation nor is this player owned. + continue; + + ctx.fillStyle = shadowColor.rgbString(); + ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET); + ctx.fillStyle = baseColor.rgbString(); + ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + } + } + + if (animateOff) + 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 && !animateOff) + { + if (animateSpec.after) + { + + var offsetBounce = 0; + if (animateSpec.frame >= MAX_FRAMES - BOUNCE_FRAMES) + { + var bounce = animateSpec.frame - MAX_FRAMES + BOUNCE_FRAMES; + var bounceHeight = BOUNCE_FRAMES / 2 * DROP_SPEED; + if (bounce >= BOUNCE_FRAMES / 2) + offsetBounce = bounceHeight - (bounce - BOUNCE_FRAMES / 2) * DROP_SPEED; + else + offsetBounce = bounce * DROP_SPEED; + } + else + offsetBounce = DROP_HEIGHT - DROP_SPEED * animateSpec.frame; + + y -= offsetBounce; + + shadowColor = animateSpec.after.shadowColor; + baseColor = animateSpec.after.baseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1); + + ctx.fillStyle = shadowColor.rgbString(); + ctx.fillRect(x, y + SHADOW_OFFSET, CELL_WIDTH, CELL_WIDTH); + ctx.fillStyle = baseColor.rgbString(); + ctx.fillRect(x, y, CELL_WIDTH, CELL_WIDTH); + } + + animateSpec.frame++; + if (animateSpec.frame >= MAX_FRAMES) + animateGrid.set(r, c, null); } } } } - + var showedDead = false; function paintLoop() { ctx.fillStyle = 'whitesmoke'; @@ -226,10 +333,11 @@ $(function() { }); ctx.setTransform(1, 0, 0, 1, 0, 0); //Reset transform. - if (user.dead) + if (user.dead && !showedDead) { + showedDead = true; console.log("You died!"); - return; + //return; } //TODO: sync each loop with server. (server will give frame count.) @@ -242,6 +350,8 @@ $(function() { //Event listeners $(document).keydown(function(e) { + if (user.dead) + return; var newHeading = -1; switch (e.which) { diff --git a/player.js b/player.js index 237d80c..7472ad7 100644 --- a/player.js +++ b/player.js @@ -2,6 +2,10 @@ var Stack; if (!Stack) throw new Error("Require stack.js"); +var Color; +if (!Color) + throw new Error("Requre color.js"); + var Tail = (function() { var CELL_WIDTH = 30; var GRID_SIZE = 200; @@ -44,7 +48,6 @@ var Tail = (function() { if (!tailGrid[r]) tailGrid[r] = []; tailGrid[r][c] = true; - } function addTail(data, orientation) @@ -87,7 +90,7 @@ var Tail = (function() { function render2(data, ctx) { - ctx.fillStyle = data.player.tailColor; + ctx.fillStyle = data.player.tailColor.rgbString(); for (var r = 0; r < data.tailGrid.length; r++) { if (!data.tailGrid[r]) @@ -100,7 +103,10 @@ var Tail = (function() { function render(data, ctx) { - ctx.fillStyle = data.player.tailColor; + if (data.tail.length === 0) + return; + + ctx.fillStyle = data.player.tailColor.rgbString(); var prevOrient = -1; var start = [data.startRow, data.startCol]; @@ -203,7 +209,7 @@ var Tail = (function() { function onTail(c) { return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; } var start = [data.startRow, data.startCol]; - var been = new Array(grid.length); + var been = new Grid(grid.size); var coords = []; coords.push(start); @@ -213,18 +219,16 @@ var Tail = (function() { var r = coord[0]; var c = coord[1]; - if (r < 0 || c < 0 || r >= grid.length || c >= grid[r].length) - continue; //Out of bounds! + if (grid.isOutOfBounds(r, c)) + continue; - if (been[r] && been[r][c]) + if (been.get(r, c)) continue; if (onTail(coord)) //on the tail. { - if (!been[r]) - been[r] = new Array(grid[r].length); - been[r][c] = true; - grid[r][c] = data.player; + 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); @@ -255,19 +259,17 @@ var Tail = (function() { var r = coord[0]; var c = coord[1]; - if (r < 0 || c < 0 || r >= grid.length || c >= grid[r].length) + if (grid.isOutOfBounds(r, c)) { surrounded = false; - continue; //Out of bounds! + continue; } //End this traverse on boundaries (where we been, on the tail, and when we enter our territory) - if ((been[r] && been[r][c]) || onTail(coord) || grid[r][c] === data.player) + if (been.get(r, c) || onTail(coord) || grid.get(r, c) === data.player) continue; - if (!been[r]) - been[r] = new Array(grid[r].length); - been[r][c] = true; + been.set(r, c, true); if (surrounded) filled.push(coord); @@ -282,7 +284,7 @@ var Tail = (function() { while (!filled.isEmpty()) { coord = filled.pop(); - grid[coord[0]][coord[1]] = data.player; + grid.set(coord[0], coord[1], data.player); } } @@ -302,24 +304,23 @@ this.Player = (function() { var SPEED = 5; var SHADOW_OFFSET = 10; - function Player(socket, grid, num) { + function Player(socket, grid, num, row, col) { var data = {}; //TODO: load player data and color. var hue = Math.random(); - var base = hslToRgb(hue, .8, .5); - this.baseColor = rgbString(base); - this.shadowColor = rgbString(hslToRgb(hue, .8, .2)); - base[3] = .5; - this.tailColor = rgbaString(base); + var base = new Color(hue, .8, .5); + this.baseColor = base; + this.shadowColor = base.deriveLumination(-.3); + this.tailColor = base.deriveLumination(.2).deriveAlpha(.5); this.name = 'Player ' + (num + 1); data.grid = grid; data.curHeading = 2; - data.row = Math.floor(Math.random() * 10) + 10; - data.col = 10;//num; + data.row = row || 0; + data.col = col || 0; data.dead = false; data.tail = new Tail(this); @@ -350,13 +351,13 @@ this.Player = (function() { this.tail.render(ctx); //Render player. - ctx.fillStyle = this.shadowColor; + ctx.fillStyle = this.shadowColor.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); + grd.addColorStop(0, this.baseColor.rgbString()); grd.addColorStop(1, "white"); ctx.fillStyle = grd; ctx.fillRect(this.posX, this.posY - SHADOW_OFFSET, CELL_WIDTH, CELL_WIDTH); @@ -394,13 +395,13 @@ this.Player = (function() { data.row = row; data.col = col; - if (row < 0 || row > data.grid.length || col < 0 || col > data.grid[row].length) + if (data.grid.isOutOfBounds(row, col)) { data.dead = true; return; } - if (data.grid[row][col] === this) + if (data.grid.get(row, col) === this) { //Safe zone! this.tail.fillTail(data.grid); @@ -423,39 +424,7 @@ this.Player = (function() { }; } - //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 rgbString(rgb) { - return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')'; - } - function rgbaString(rgb) { - return 'rgba(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ', ' + rgb[3] + ')'; - } return Player; })(); \ No newline at end of file diff --git a/test.html b/test.html index ef013e1..4a35a7e 100644 --- a/test.html +++ b/test.html @@ -1,4 +1,6 @@ + +