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.
This commit is contained in:
theKidOfArcrania 2017-03-02 06:43:38 +00:00
parent 8352433fa5
commit fab29a8f75
7 changed files with 385 additions and 256 deletions

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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)

26
rolling.js Normal file
View File

@ -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;

View File

@ -1,4 +1,13 @@
var socket = io("http://localhost:8081");
socket.on('connect', document.write.bind(document, "<p>Connected!"));
socket.on('message', function(data) {document.write("<p>Message: " + data)});
socket.on('disconnect', document.write.bind(this, "<p>Disconnected!"));
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);