From fab29a8f75d3bd0149a35abe59e292f38713a09b Mon Sep 17 00:00:00 2001 From: theKidOfArcrania Date: Thu, 2 Mar 2017 06:43:38 +0000 Subject: [PATCH] UI impls and bug fixes. * Implement leaderboard (shows top five players). * Make color be decided on server side (make sure we reuse the correct colors) * Better cache frames during 'requestFrames' mechanism * Make sure we die when we disconnect. * Make sure that new client doesn't consider all players as new clients (remove newPlayerFrames, and use Player.waitLag instead) * Have default player name be empty (or "Unnamed") * Fix refresh issue when new player joins game. --- game-client.js | 192 +++++++++++++++++-------------- game-renderer.js | 36 +++--- game-server.js | 56 ++++----- player.js | 27 ++--- public/bundle.js | 287 ++++++++++++++++++++++++++++------------------- rolling.js | 26 +++++ socket-test.js | 17 ++- 7 files changed, 385 insertions(+), 256 deletions(-) create mode 100644 rolling.js diff --git a/game-client.js b/game-client.js index 932b24b..6f0f51e 100644 --- a/game-client.js +++ b/game-client.js @@ -55,17 +55,18 @@ $(document).keydown(function(e) { heading: newHeading }, function(success, msg) { if (!success) - { - //TODO: restore frames. console.error(msg); - } }); } e.preventDefault(); }); +var norun = false; window.run = run; function run() { + if (norun) + return; //Prevent multiple clicks. + norun = true; $("#begin").css("display: none"); $("#begin").animate({ opacity: 0 @@ -91,7 +92,10 @@ var grid = renderer.grid; var timeout = undefined; var dirty = false; var deadFrames = 0; +var requesting = -1; //frame that we are requesting at. +var frameCache = []; //Frames after our request. +//TODO: check if we can connect to server. function connectServer() { io.j = []; io.sockets = []; @@ -99,24 +103,18 @@ function connectServer() { socket.on('connect', function(){ console.info("Connected to server."); }); - var colors; socket.on('game', function(data) { if (timeout != undefined) clearTimeout(timeout); + //Initialize game. //TODO: display data.gameid --- game id # frame = data.frame; renderer.reset(); - waiting = false; - - //Load colors. - colors = data.colors || []; - //Load players. data.players.forEach(function(p) { - p.base = colors[p.num]; - var pl = new Player(true, grid, p); + var pl = new Player(grid, p); renderer.addPlayer(pl); }); user = renderer.getPlayerFromNum(data.num); @@ -134,79 +132,22 @@ function connectServer() { renderer.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 = []; + } }); - var waiting = false; - socket.on('notifyFrame', function(data) { - if (timeout != undefined) - clearTimeout(timeout); - - if (waiting) - return; - - if (data.frame - 1 !== frame) - { - console.error("Frames don't match up!"); - socket.emit('requestFrame'); //Restore data. - waiting = true; - return; - //TODO: cache frames when this happen. - } - - frame++; - if (data.newPlayers) - { - data.newPlayers.forEach(function(p) { - if (p.num === user.num) - return; - p.base = colors[p.num]; - var pl = new Player(true, grid, p); - renderer.addPlayer(pl); - core.initPlayer(grid, pl); - }); - } - - var found = new Array(renderer.playerSize()); - data.moves.forEach(function(val, i) { - var player = renderer.getPlayerFromNum(val.num); - if (!player) return; - if (val.left) player.die(); - found[i] = true; - player.heading = val.heading; - }); - for (var i = 0; i < renderer.playerSize(); i++) - { - //Implicitly leaving game. - if (!found[i]) - { - var player = renderer.getPlayer(); - player && player.die(); - } - } - - renderer.update(frame); - - var locs = {}; - for (var i = 0; i < renderer.playerSize(); i++) - { - var p = renderer.getPlayer(i); - locs[p.num] = [p.posX, p.posY, p.waitLag]; - } - socket.emit("verify", { - frame: frame, - locs: locs - }, function(frame, success, msg) { - if (!success) console.error(frame + ": " + msg); - }.bind(this, frame)); - - dirty = true; - requestAnimationFrame(function() { - paintLoop(); - }); - timeout = setTimeout(function() { - console.warn("Server has timed-out. Disconnecting."); - socket.disconnect(); - }, 3000); + socket.on('notifyFrame', processFrame); + + socket.on('dead', function() { + socket.disconnect(); //In case we didn't get the disconnect call. }); socket.on('disconnect', function(){ @@ -215,15 +156,99 @@ function connectServer() { console.info("Server has disconnected. Creating new game."); socket.disconnect(); user.die(); + dirty = true; paintLoop(); $("#begin").css("display: block"); $("#begin").animate({ opacity: .9999 - }, 500); + }, 1000, function() { + norun = false; + }); }); } +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); + renderer.addPlayer(pl); + core.initPlayer(grid, pl); + }); + } + + var found = new Array(renderer.playerSize()); + data.moves.forEach(function(val, i) { + var player = renderer.getPlayerFromNum(val.num); + if (!player) return; + if (val.left) player.die(); + found[i] = true; + player.heading = val.heading; + }); + for (var i = 0; i < renderer.playerSize(); i++) + { + //Implicitly leaving game. + if (!found[i]) + { + var player = renderer.getPlayer(); + player && player.die(); + } + } + + renderer.update(); + + var locs = {}; + for (var i = 0; i < renderer.playerSize(); i++) + { + var p = renderer.getPlayer(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) @@ -250,6 +275,7 @@ function paintLoop() socket.disconnect(); deadFrames++; dirty = true; + renderer.update(); requestAnimationFrame(paintLoop); } } diff --git a/game-renderer.js b/game-renderer.js index d0048a9..1979e13 100644 --- a/game-renderer.js +++ b/game-renderer.js @@ -1,4 +1,5 @@ /* global $ */ +var Rolling = require("./rolling.js"); var Color = require("./color.js"); var Grid = require("./grid.js"); var consts = require("./game-consts.js"); @@ -37,7 +38,7 @@ $(function () { var allowAnimation = true; -var animateGrid, players, allPlayers, playerPortion, grid, +var animateGrid, players, allPlayers, playerPortion, portionsRolling, grid, animateTo, offset, user, lagPortion, portionSpeed, zoom, kills, showedDead; grid = new Grid(GRID_SIZE, function(row, col, before, after) { @@ -64,6 +65,7 @@ function init() { players = []; allPlayers = []; playerPortion = []; + portionsRolling = []; animateTo = [0, 0]; offset = [0, 0]; @@ -200,7 +202,7 @@ function paintUIBar(ctx) var barOffset; ctx.fillStyle = "white"; ctx.font = "24px Changa"; - barOffset = ctx.measureText(user ? user.name : "").width + 20; + barOffset = (user && user.name) ? (ctx.measureText(user.name).width + 20) : 0; ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); //Draw filled bar. @@ -213,6 +215,7 @@ function paintUIBar(ctx) 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"; @@ -243,29 +246,33 @@ function paintUIBar(ctx) for (var i = 0; i < leaderboardNum; i++) { var player = sorted[i].player; - var name = player.name; + var name = player.name || "Unnamed"; var portion = sorted[i].portion / maxPortion; 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, barSize + 10, CELL_WIDTH + 10); + 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 - 5, barY + CELL_WIDTH - 10); + ctx.fillText(name, barX - nameWidth - 10, barY + 27); + var percentage = (sorted[i].portion / GRID_SIZE / GRID_SIZE * 100).toFixed(3) + "%"; + var metrics = ctx.measureText(percentage); + ctx.fillStyle = "white"; + ctx.fillText(percentage, barX + 5, barY + CELL_WIDTH - 5); } } -//TODO: depict leaderboard. function paint(ctx) { ctx.fillStyle = 'whitesmoke'; @@ -309,7 +316,7 @@ function paintDoubleBuff() ctx.drawImage(offscreenCanvas, 0, 0); } -function update(frame) { +function update() { //Change grid offsets. for (var i = 0; i <= 1; i++) @@ -359,12 +366,12 @@ function update(frame) { kills++; }); dead.forEach(function(val) { - console.log(val.name + " is dead"); - allPlayers[val.num] = undefined; + console.log(val.name || "Unnamed" + " is dead"); + delete allPlayers[val.num]; + delete portionsRolling[val.num]; }); //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. - //TODO: show when this player is dead if (user) centerOnPlayer(user, animateTo); } @@ -373,8 +380,9 @@ 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); - 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); + var gridWidth = grid.size * CELL_WIDTH + BORDER_WIDTH * 2; + pos[0] = Math.max(Math.min(xOff, gridWidth + BAR_WIDTH + 100 - gameWidth / zoom), 0); + pos[1] = Math.max(Math.min(yOff, gridWidth - gameHeight / zoom), 0); } function getBounceOffset(frame) @@ -411,10 +419,12 @@ module.exports = exports = { return; //Already added. allPlayers[player.num] = players[players.length] = player; playerPortion[player.num] = 0; + portionsRolling = new Rolling(0, .2); return players.length - 1; }, - //TODO: check index. getPlayer: function(ind) { + if (ind < 0 || ind >= players.length) + throw new RangeError("Player index out of bounds (" + ind + ")."); return players[ind]; }, getPlayerFromNum: function(num) { diff --git a/game-server.js b/game-server.js index df41d8c..3f2e0ea 100644 --- a/game-server.js +++ b/game-server.js @@ -14,7 +14,6 @@ var HUES = [0, 10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 100, 110, 120, 125, 130, var SATS = [192, 150, 100].map(function(val) {return val / 240}); - function Game(id) { //Shuffle the hues. @@ -27,11 +26,11 @@ function Game(id) HUES[b] = tmp; } - var colors = new Array(SATS.length * HUES.length); + 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++) - colors[i++] = new Color(HUES[h], SATS[s], .5, 1); + possColors[i++] = new Color(HUES[h], SATS[s], .5, 1); var nextInd = 0; var players = []; @@ -52,9 +51,6 @@ function Game(id) this.id = id; - //var frameGate = new Gate(1); - //var timeout = undefined; - this.addPlayer = function(client, name) { if (players.length >= MAX_PLAYERS) return false; @@ -68,10 +64,11 @@ function Game(id) posY: start.row * CELL_WIDTH, currentHeading: Math.floor(Math.random() * 4), name: name, - num: nextInd + num: nextInd, + base: possColors.shift() }; - var p = new Player(false, grid, params); + var p = new Player(grid, params); p.tmpHeading = params.currentHeading; p.client = client; players.push(p); @@ -79,34 +76,32 @@ function Game(id) nextInd++; core.initPlayer(grid, p); - //playerReady(p, frame); - console.log(p.name + " joined."); + console.log((p.name || "Unnamed") + " (" + p.num + ") joined."); - //TODO: kick off any clients that take too long. - //TODO: limit number of requests per frame. client.on("requestFrame", function () { - //if (p.frame === frame) - // return; + 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), - "colors": colors + "grid": gridSerialData(grid, players) }); - //playerReady(p, frame); }); + //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, "No frame supplied"); + resp(false, false, "No frame supplied"); else if (!checkInt(data.frame, 0, frame + 1)) - resp(false, "Must be a valid frame number"); + resp(false, false, "Must be a valid frame number"); else { verifyPlayerLocations(data.frame, data.locs, resp); @@ -145,8 +140,9 @@ function Game(id) }); client.on('disconnect', function() { + p.die(); //Die immediately if not already. p.disconnected = true; - console.log(p.name + " left."); + console.log((p.name || "Unnamed") + " (" + p.num + ") left."); }); return true; }; @@ -158,7 +154,7 @@ function Game(id) locs[p.num] = [p.posX, p.posY, p.waitLag]; locs.frame = frame; - if (frameLocs.length >= 100) + if (frameLocs.length >= 300) //Give it 5 seconds of lag. frameLocs.shift(); frameLocs.push(locs); } @@ -168,7 +164,7 @@ function Game(id) var minFrame = frame - frameLocs.length + 1; if (fr < minFrame || fr > frame) { - resp(false, "Frames out of reference"); + resp(false, false, "Frames out of reference"); return; } @@ -180,19 +176,19 @@ function Game(id) var locs = frameLocs[fr - minFrame]; if (locs.frame !== fr) { - resp(false, locs.frame + " != " + fr); + resp(false, false, locs.frame + " != " + fr); return; } for (var num in verify) { if (locs[num][0] !== verify[num][0] || locs[num][1] !== verify[num][1] || locs[num][2] !== verify[num][2]) { - resp(false, 'P' + num + ' ' + string(locs[num]) + ' !== ' + string(verify[num])); + resp(false, true, 'P' + num + ' ' + string(locs[num]) + ' !== ' + string(verify[num])); return; } } - resp(true); + resp(true, false); } function tick() { @@ -207,7 +203,6 @@ function Game(id) "frame": frame, "players": splayers, "grid": gridSerialData(grid, players), - "colors": colors }); return val.serialData(); }); @@ -241,8 +236,13 @@ function Game(id) core.updateFrame(grid, players, dead); for (var pl of dead) { - //TODO: send a "good-bye" frame to the dead players. Just in case. - console.log(pl.name + " died."); + if (!pl.handledDead) + { + possColors.unshift(pl.baseColor); + pl.handledDead = true; + } + console.log((pl.name || "Unnamed") + " (" + pl.num + ") died."); + pl.client.emit("dead"); pl.client.disconnect(true); } } diff --git a/player.js b/player.js index 002af31..c56ef51 100644 --- a/player.js +++ b/player.js @@ -295,7 +295,7 @@ 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(r, c) || been.get(row, col) || onTail(start) || grid.get(row, col) === data.player) + 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 = []; @@ -351,13 +351,12 @@ function hitsTail(data, other) var SPEED = 5; var SHADOW_OFFSET = 10; -function Player(isClient, grid, sdata) { +function Player(grid, sdata) { var data = {}; //Parameters data.num = sdata.num; - data.name = sdata.name || "Player " + (data.num + 1); - data.isCient = isClient; + data.name = sdata.name || ""; //|| "Player " + (data.num + 1); data.grid = grid; data.posX = sdata.posX; data.posY = sdata.posY; @@ -366,19 +365,16 @@ function Player(isClient, grid, sdata) { data.dead = false; //Only need colors for client side. - if (isClient) + var base; + if (sdata.base) + base = this.baseColor = sdata.base instanceof Color ? sdata.base : Color.fromData(sdata.base); + else { - var base; - if (sdata.base) - base = this.baseColor = Color.fromData(sdata.base); - else - { - var hue = Math.random(); - this.baseColor = base = new Color(hue, .8, .5); - } - this.shadowColor = base.deriveLumination(-.3); - this.tailColor = base.deriveLumination(.2).deriveAlpha(.5); + var hue = Math.random(); + this.baseColor = base = new Color(hue, .8, .5); } + this.shadowColor = base.deriveLumination(-.3); + this.tailColor = base.deriveLumination(.2).deriveAlpha(.5); //Tail requires special handling. this.grid = grid; //Temporary @@ -395,6 +391,7 @@ function Player(isClient, grid, sdata) { this.die = function() { data.dead = true;}; this.serialData = function() { return { + base: this.baseColor, num: data.num, name: data.name, posX: data.posX, diff --git a/public/bundle.js b/public/bundle.js index 6d2ea9a..ec80918 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -341,17 +341,18 @@ $(document).keydown(function(e) { heading: newHeading }, function(success, msg) { if (!success) - { - //TODO: restore frames. console.error(msg); - } }); } e.preventDefault(); }); +var norun = false; window.run = run; function run() { + if (norun) + return; //Prevent multiple clicks. + norun = true; $("#begin").css("display: none"); $("#begin").animate({ opacity: 0 @@ -377,7 +378,10 @@ var grid = renderer.grid; var timeout = undefined; var dirty = false; var deadFrames = 0; +var requesting = -1; //frame that we are requesting at. +var frameCache = []; //Frames after our request. +//TODO: check if we can connect to server. function connectServer() { io.j = []; io.sockets = []; @@ -385,24 +389,18 @@ function connectServer() { socket.on('connect', function(){ console.info("Connected to server."); }); - var colors; socket.on('game', function(data) { if (timeout != undefined) clearTimeout(timeout); + //Initialize game. //TODO: display data.gameid --- game id # frame = data.frame; renderer.reset(); - waiting = false; - - //Load colors. - colors = data.colors || []; - //Load players. data.players.forEach(function(p) { - p.base = colors[p.num]; - var pl = new Player(true, grid, p); + var pl = new Player(grid, p); renderer.addPlayer(pl); }); user = renderer.getPlayerFromNum(data.num); @@ -420,79 +418,22 @@ function connectServer() { renderer.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 = []; + } }); - var waiting = false; - socket.on('notifyFrame', function(data) { - if (timeout != undefined) - clearTimeout(timeout); - - if (waiting) - return; - - if (data.frame - 1 !== frame) - { - console.error("Frames don't match up!"); - socket.emit('requestFrame'); //Restore data. - waiting = true; - return; - //TODO: cache frames when this happen. - } - - frame++; - if (data.newPlayers) - { - data.newPlayers.forEach(function(p) { - if (p.num === user.num) - return; - p.base = colors[p.num]; - var pl = new Player(true, grid, p); - renderer.addPlayer(pl); - core.initPlayer(grid, pl); - }); - } - - var found = new Array(renderer.playerSize()); - data.moves.forEach(function(val, i) { - var player = renderer.getPlayerFromNum(val.num); - if (!player) return; - if (val.left) player.die(); - found[i] = true; - player.heading = val.heading; - }); - for (var i = 0; i < renderer.playerSize(); i++) - { - //Implicitly leaving game. - if (!found[i]) - { - var player = renderer.getPlayer(); - player && player.die(); - } - } - - renderer.update(frame); - - var locs = {}; - for (var i = 0; i < renderer.playerSize(); i++) - { - var p = renderer.getPlayer(i); - locs[p.num] = [p.posX, p.posY, p.waitLag]; - } - socket.emit("verify", { - frame: frame, - locs: locs - }, function(frame, success, msg) { - if (!success) console.error(frame + ": " + msg); - }.bind(this, frame)); - - dirty = true; - requestAnimationFrame(function() { - paintLoop(); - }); - timeout = setTimeout(function() { - console.warn("Server has timed-out. Disconnecting."); - socket.disconnect(); - }, 3000); + socket.on('notifyFrame', processFrame); + + socket.on('dead', function() { + socket.disconnect(); //In case we didn't get the disconnect call. }); socket.on('disconnect', function(){ @@ -501,15 +442,99 @@ function connectServer() { console.info("Server has disconnected. Creating new game."); socket.disconnect(); user.die(); + dirty = true; paintLoop(); $("#begin").css("display: block"); $("#begin").animate({ opacity: .9999 - }, 500); + }, 1000, function() { + norun = false; + }); }); } +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); + renderer.addPlayer(pl); + core.initPlayer(grid, pl); + }); + } + + var found = new Array(renderer.playerSize()); + data.moves.forEach(function(val, i) { + var player = renderer.getPlayerFromNum(val.num); + if (!player) return; + if (val.left) player.die(); + found[i] = true; + player.heading = val.heading; + }); + for (var i = 0; i < renderer.playerSize(); i++) + { + //Implicitly leaving game. + if (!found[i]) + { + var player = renderer.getPlayer(); + player && player.die(); + } + } + + renderer.update(); + + var locs = {}; + for (var i = 0; i < renderer.playerSize(); i++) + { + var p = renderer.getPlayer(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) @@ -536,6 +561,7 @@ function paintLoop() socket.disconnect(); deadFrames++; dirty = true; + renderer.update(); requestAnimationFrame(paintLoop); } } @@ -696,6 +722,7 @@ function area(player) } },{}],7:[function(require,module,exports){ /* global $ */ +var Rolling = require("./rolling.js"); var Color = require("./color.js"); var Grid = require("./grid.js"); var consts = require("./game-consts.js"); @@ -734,7 +761,7 @@ $(function () { var allowAnimation = true; -var animateGrid, players, allPlayers, playerPortion, grid, +var animateGrid, players, allPlayers, playerPortion, portionsRolling, grid, animateTo, offset, user, lagPortion, portionSpeed, zoom, kills, showedDead; grid = new Grid(GRID_SIZE, function(row, col, before, after) { @@ -761,6 +788,7 @@ function init() { players = []; allPlayers = []; playerPortion = []; + portionsRolling = []; animateTo = [0, 0]; offset = [0, 0]; @@ -897,7 +925,7 @@ function paintUIBar(ctx) var barOffset; ctx.fillStyle = "white"; ctx.font = "24px Changa"; - barOffset = ctx.measureText(user ? user.name : "").width + 20; + barOffset = (user && user.name) ? (ctx.measureText(user.name).width + 20) : 0; ctx.fillText(user ? user.name : "", 5, CELL_WIDTH - 5); //Draw filled bar. @@ -910,6 +938,7 @@ function paintUIBar(ctx) 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"; @@ -940,29 +969,33 @@ function paintUIBar(ctx) for (var i = 0; i < leaderboardNum; i++) { var player = sorted[i].player; - var name = player.name; + var name = player.name || "Unnamed"; var portion = sorted[i].portion / maxPortion; 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, barSize + 10, CELL_WIDTH + 10); + 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 - 5, barY + CELL_WIDTH - 10); + ctx.fillText(name, barX - nameWidth - 10, barY + 27); + var percentage = (sorted[i].portion / GRID_SIZE / GRID_SIZE * 100).toFixed(3) + "%"; + var metrics = ctx.measureText(percentage); + ctx.fillStyle = "white"; + ctx.fillText(percentage, barX + 5, barY + CELL_WIDTH - 5); } } -//TODO: depict leaderboard. function paint(ctx) { ctx.fillStyle = 'whitesmoke'; @@ -1006,7 +1039,7 @@ function paintDoubleBuff() ctx.drawImage(offscreenCanvas, 0, 0); } -function update(frame) { +function update() { //Change grid offsets. for (var i = 0; i <= 1; i++) @@ -1056,12 +1089,12 @@ function update(frame) { kills++; }); dead.forEach(function(val) { - console.log(val.name + " is dead"); - allPlayers[val.num] = undefined; + console.log(val.name || "Unnamed" + " is dead"); + delete allPlayers[val.num]; + delete portionsRolling[val.num]; }); //TODO: animate player is dead. (maybe explosion?), and tail rewinds itself. - //TODO: show when this player is dead if (user) centerOnPlayer(user, animateTo); } @@ -1070,8 +1103,9 @@ 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); - 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); + var gridWidth = grid.size * CELL_WIDTH + BORDER_WIDTH * 2; + pos[0] = Math.max(Math.min(xOff, gridWidth + BAR_WIDTH + 100 - gameWidth / zoom), 0); + pos[1] = Math.max(Math.min(yOff, gridWidth - gameHeight / zoom), 0); } function getBounceOffset(frame) @@ -1108,10 +1142,12 @@ module.exports = exports = { return; //Already added. allPlayers[player.num] = players[players.length] = player; playerPortion[player.num] = 0; + portionsRolling = new Rolling(0, .2); return players.length - 1; }, - //TODO: check index. getPlayer: function(ind) { + if (ind < 0 || ind >= players.length) + throw new RangeError("Player index out of bounds (" + ind + ")."); return players[ind]; }, getPlayerFromNum: function(num) { @@ -1145,7 +1181,7 @@ Object.defineProperties(exports, { enumerable: true } }); -},{"./color.js":3,"./game-consts.js":5,"./game-core.js":6,"./grid.js":8}],8:[function(require,module,exports){ +},{"./color.js":3,"./game-consts.js":5,"./game-core.js":6,"./grid.js":8,"./rolling.js":57}],8:[function(require,module,exports){ function Grid(size, changeCallback) { var grid = new Array(size); @@ -9270,7 +9306,7 @@ 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(r, c) || been.get(row, col) || onTail(start) || grid.get(row, col) === data.player) + 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 = []; @@ -9326,13 +9362,12 @@ function hitsTail(data, other) var SPEED = 5; var SHADOW_OFFSET = 10; -function Player(isClient, grid, sdata) { +function Player(grid, sdata) { var data = {}; //Parameters data.num = sdata.num; - data.name = sdata.name || "Player " + (data.num + 1); - data.isCient = isClient; + data.name = sdata.name || ""; //|| "Player " + (data.num + 1); data.grid = grid; data.posX = sdata.posX; data.posY = sdata.posY; @@ -9341,19 +9376,16 @@ function Player(isClient, grid, sdata) { data.dead = false; //Only need colors for client side. - if (isClient) + var base; + if (sdata.base) + base = this.baseColor = sdata.base instanceof Color ? sdata.base : Color.fromData(sdata.base); + else { - var base; - if (sdata.base) - base = this.baseColor = Color.fromData(sdata.base); - else - { - var hue = Math.random(); - this.baseColor = base = new Color(hue, .8, .5); - } - this.shadowColor = base.deriveLumination(-.3); - this.tailColor = base.deriveLumination(.2).deriveAlpha(.5); + var hue = Math.random(); + this.baseColor = base = new Color(hue, .8, .5); } + this.shadowColor = base.deriveLumination(-.3); + this.tailColor = base.deriveLumination(.2).deriveAlpha(.5); //Tail requires special handling. this.grid = grid; //Temporary @@ -9370,6 +9402,7 @@ function Player(isClient, grid, sdata) { this.die = function() { data.dead = true;}; this.serialData = function() { return { + base: this.baseColor, num: data.num, name: data.name, posX: data.posX, @@ -9478,7 +9511,35 @@ function move(data) } module.exports = Player; -},{"./color.js":3,"./game-consts.js":5,"./grid.js":8,"./stack.js":57}],57:[function(require,module,exports){ +},{"./color.js":3,"./game-consts.js":5,"./grid.js":8,"./stack.js":58}],57:[function(require,module,exports){ + +function Rolling(value, maxSpeed) +{ + var lag = 0; + + if (!maxSpeed) + maxSpeed = 5; + + this.value = value; + + Object.defineProperty(this, "lag", { + get: function() { return lag; }, + enumerable: true + }); + this.update = function() { + var delta = value - lag; + var dir = Math.sign(delta); + var mag = Math.min(Math.abs(maxSpeed), Math.abs(delta)); + + lag += mag * dir; + return lag; + } +} + +module.exports = Rolling; + + +},{}],58:[function(require,module,exports){ function Stack(initSize) diff --git a/rolling.js b/rolling.js new file mode 100644 index 0000000..97c9dd8 --- /dev/null +++ b/rolling.js @@ -0,0 +1,26 @@ + +function Rolling(value, maxSpeed) +{ + var lag = 0; + + if (!maxSpeed) + maxSpeed = 5; + + this.value = value; + + Object.defineProperty(this, "lag", { + get: function() { return lag; }, + enumerable: true + }); + this.update = function() { + var delta = value - lag; + var dir = Math.sign(delta); + var mag = Math.min(Math.abs(maxSpeed), Math.abs(delta)); + + lag += mag * dir; + return lag; + } +} + +module.exports = Rolling; + diff --git a/socket-test.js b/socket-test.js index 98c812e..1598ef7 100644 --- a/socket-test.js +++ b/socket-test.js @@ -1,4 +1,13 @@ -var socket = io("http://localhost:8081"); -socket.on('connect', document.write.bind(document, "

Connected!")); -socket.on('message', function(data) {document.write("

Message: " + data)}); -socket.on('disconnect', document.write.bind(this, "

Disconnected!")); \ No newline at end of file +var ws = require("nodejs-websocket") + +// Scream server example: "hi" -> "HI!!!" +var server = ws.createServer(function (conn) { + console.log("New connection") + conn.on("text", function (str) { + console.log("Received "+str) + conn.sendText(str.toUpperCase()+"!!!") + }) + conn.on("close", function (code, reason) { + console.log("Connection closed") + }) +}).listen(8081); \ No newline at end of file