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:
parent
8352433fa5
commit
fab29a8f75
192
game-client.js
192
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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
27
player.js
27
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,
|
||||
|
287
public/bundle.js
287
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)
|
||||
|
26
rolling.js
Normal file
26
rolling.js
Normal 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;
|
||||
|
@ -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);
|
Loading…
Reference in New Issue
Block a user