3 Commits

Author SHA1 Message Date
Henry Wang
b45f1bf3c0 Fix start-up issues 2017-02-25 17:56:00 -06:00
Henry Wang
f4ea4318c7 Update gh pages 2017-02-25 00:48:05 -06:00
Henry Wang
4afe0ecb70 Make github pages as a demo 2017-02-23 22:29:16 -06:00
31 changed files with 1400 additions and 12088 deletions

1
.gitignore vendored
View File

@@ -1 +0,0 @@
/node_modules

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 theKidOfArcrania
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,14 +0,0 @@
# Blockly.IO
This is a clone of the original Paper-IO released by Voodoo, except for one aspect. This will attempt to implement a multi-player aspect of the game (like a real IO game). Currently this has a playground at this [link] (https://thekidofarcrania.github.io/PaperIO-Web). It's a demo version of what to come. Hopefully by that time, the necessary server infrastructure could be obtained.
This is just a fun side-project for me. If you would want to use this code, it would be nice to let me know.
## Running
After cloning this repository, run the follow commands to install dependencies and set up server. Enjoy!
```bash
npm install
npm start
```

1373
bundle.js Normal file

File diff suppressed because it is too large Load Diff

100
color.js
View File

@@ -1,100 +0,0 @@
function Color(h, s, l, a)
{
verifyRange(h, s, l);
if (a === undefined) a = 1;
else verifyRange(a);
Object.defineProperties(this, {
"hue": {value: h, enumerable: true},
"sat": {value: s, enumerable: true},
"lum": {value: l, enumerable: true},
"alpha": {value: a, enumerable: true},
});
}
Color.fromData = function(data) {
return new Color(data.hue, data.sat, data.lum, data.alpha);
};
function verifyRange()
{
for (var i = 0; i < arguments.length; i++)
{
if (arguments[i] < 0 || arguments[i] > 1)
throw new RangeError("H, S, L, and A parameters must be between the range [0, 1]");
}
}
Color.prototype.interpolateToString = function(color, amount)
{
var rgbThis = hslToRgb(this.hue, this.sat, this.lum);
var rgbThat = hslToRgb(color.hue, color.sat, color.lum);
var rgb = [];
for (var i = 0; i < 3; i++)
rgb[i] = Math.floor((rgbThat[i] - rgbThis[i]) * amount + rgbThis[i]);
return {rgbString: function() {return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')'}};
}
Color.prototype.deriveLumination = function(amount)
{
var lum = this.lum + amount;
lum = Math.min(Math.max(lum, 0), 1);
return new Color(this.hue, this.sat, lum, this.alpha);
};
Color.prototype.deriveHue = function(amount)
{
var hue = this.hue - amount;
return new Color(hue - Math.floor(hue), this.sat, this.lum, this.alpha);
};
Color.prototype.deriveSaturation = function(amount)
{
var sat = this.sat + amount;
sat = Math.min(Math.max(sat, 0), 1);
return new Color(this.hue, sat, this.lum, this.alpha);
};
Color.prototype.deriveAlpha = function(newAlpha)
{
verifyRange(newAlpha);
return new Color(this.hue, this.sat, this.lum, newAlpha);
};
Color.prototype.rgbString = function() {
var rgb = hslToRgb(this.hue, this.sat, this.lum);
rgb[3] = this.a;
return 'rgba(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ', ' + this.alpha + ')';
};
//http://stackoverflow.com/a/9493060/7344257
function hslToRgb(h, s, l){
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
}else{
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
module.exports = Color;

View File

@@ -1,2 +0,0 @@
#!/bin/sh
browserify game-client.js | uglifyjs -c > public/bundle.min.js

View File

@@ -1,398 +0,0 @@
/* global $ */
var Player = require("./player.js");
var renderer = require("./game-renderer.js");
var consts = require("./game-consts.js");
var core = require("./game-core.js");
var io = require('socket.io-client');
var GRID_SIZE = consts.GRID_SIZE;
var CELL_WIDTH = consts.CELL_WIDTH;
renderer.allowAnimation = true;
/**
* Provides requestAnimationFrame in a cross browser way.
* @author paulirish / http://paulirish.com/
*/
// window.requestAnimationFrame = function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
// window.setTimeout( callback, 1000 / 60 );
// };
if ( !window.requestAnimationFrame ) {
window.requestAnimationFrame = ( function() {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
window.setTimeout( callback, 1000 / 60 );
};
})();
}
var norun = false;
function run() {
if (norun)
return; //Prevent multiple clicks.
norun = true;
user = null;
deadFrames = 0;
//Socket connection.
//, {transports: ['websocket'], upgrade: false}
connectServer();
socket.emit('hello', {
name: $("#name").val(),
type: 0, //Free-for-all
gameid: -1 //Requested game-id, or -1 for anyone.
}, function(success, msg) {
if (success)
{
console.info("Connected to game!");
$("#begin").addClass("hidden");
$("#begin").animate({
opacity: 0
}, 1000);
}
else
{
console.error("Unable to connect to game: " + msg);
var error = $("#error");
error.text(msg);
norun = false;
}
});
}
$(function() {
var error = $("#error");
if (!window.WebSocket)
{
error.text("Your browser does not support WebSockets!");
return;
}
error.text("Loading..."); //TODO: show loading screen.
var success = false;
var socket = io('http://' + window.location.hostname + ':8081', {
'forceNew': true,
upgrade: false,
transports: ['websocket']
});
socket.on('connect_error', function() {
if (!success)
error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)");
})
socket.emit("checkConn", function() {
success = true;
socket.disconnect();
});
setTimeout(function() {
if (!success)
error.text("Cannot connect with server. This probably is due to misconfigured proxy server. (Try using a different browser)");
else
{
error.text("");
$("input").keypress(function(evt) {
if (evt.which === 13)
requestAnimationFrame(run);
});
$("button").click(function(evt) {
requestAnimationFrame(run);
});
}
}, 2000);
});
var user, socket, frame;
//Event listeners
//Used from http://stackoverflow.com/a/23230280/7344257
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchmove', handleTouchMove, false);
var xDown = null;
var yDown = null;
function handleTouchStart(evt) {
xDown = evt.touches[0].clientX;
yDown = evt.touches[0].clientY;
}
function handleTouchMove(evt) {
if ( xDown === null || yDown === null )
return;
var xUp = evt.touches[0].clientX;
var yUp = evt.touches[0].clientY;
var xDiff = xDown - xUp;
var yDiff = yDown - yUp;
//Set new heading.
var newHeading;
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) /*most significant*/
{
if ( xDiff > 0 ) newHeading = 3; /* left swipe */
else newHeading = 1; /* right swipe */
}
else
{
if ( yDiff > 0 ) newHeading = 0; /* up swipe */
else newHeading = 2; /* down swipe */
}
setHeading(newHeading);
/* reset values */
xDown = null;
yDown = null;
};
$(document).keydown(function(e) {
if (!user || user.dead)
return;
var newHeading = -1;
switch (e.which)
{
case 37: newHeading = 3; break; //LEFT
case 38: newHeading = 0; break; //UP
case 39: newHeading = 1; break; //RIGHT
case 40: newHeading = 2; break; //DOWN
default: return; //exit handler for other keys.
}
setHeading(newHeading);
e.preventDefault();
});
function setHeading(newHeading)
{
if (newHeading === user.currentHeading || ((newHeading % 2 === 0) ^
(user.currentHeading % 2 === 0)))
{
//user.heading = newHeading;
if (socket)
socket.emit("frame", {
frame: frame,
heading: newHeading
}, function(success, msg) {
if (!success)
console.error(msg);
});
}
}
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 = [];
socket = io('http://' + window.location.hostname + ':8081', {
'forceNew': true,
upgrade: false,
transports: ['websocket']
});
socket.on('connect', function(){
console.info("Connected to server.");
});
socket.on('game', function(data) {
if (timeout != undefined)
clearTimeout(timeout);
//Initialize game.
//TODO: display data.gameid --- game id #
frame = data.frame;
renderer.reset();
//Load players.
data.players.forEach(function(p) {
var pl = new Player(grid, p);
renderer.addPlayer(pl);
});
user = renderer.getPlayerFromNum(data.num);
if (!user) throw new Error();
renderer.setUser(user);
//Load grid.
var gridData = new Uint8Array(data.grid);
for (var r = 0; r < grid.size; r++)
for (var c = 0; c < grid.size; c++)
{
var ind = gridData[r * grid.size + c] - 1;
grid.set(r, c, ind === -1 ? null : renderer.getPlayer(ind));
}
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 = [];
}
});
socket.on('notifyFrame', processFrame);
socket.on('dead', function() {
socket.disconnect(); //In case we didn't get the disconnect call.
});
socket.on('disconnect', function(){
if (!user)
return;
console.info("Server has disconnected. Creating new game.");
socket.disconnect();
user.die();
dirty = true;
paintLoop();
showStats();
//TODO: Show score stats.
//Show score stats.
// $("#stats").removeClass("hidden");
// $("#stats").animate({
// opacity: .9999
// }, 3000, function() {
// });
//Then fade back into the login screen.
});
}
function showStats() {
var stats =
$("#begin").removeClass("hidden");
$("#begin").animate({
opacity: .9999
}, 1000, function() {
$("#stats").addClass("hidden").css("opacity", 0);
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)
return;
renderer.paint();
dirty = false;
if (user.dead)
{
if (timeout)
clearTimeout(timeout);
if (deadFrames === 60) //One second of frame
{
var before = renderer.allowAnimation;
renderer.allowAnimation = false;
renderer.update();
renderer.paint();
renderer.allowAnimation = before;
user = null;
deadFrames = 0;
return;
}
socket.disconnect();
deadFrames++;
dirty = true;
renderer.update();
requestAnimationFrame(paintLoop);
}
}

View File

@@ -1,17 +0,0 @@
function constant(val)
{
return {
value: val,
enumerable: true
};
}
var consts = {
GRID_SIZE: constant(80),
CELL_WIDTH: constant(40),
SPEED: constant(5),
BORDER_WIDTH: constant(20),
MAX_PLAYERS: constant(81)
};
Object.defineProperties(module.exports, consts);

View File

@@ -1,135 +0,0 @@
var ANIMATE_FRAMES = 24;
var CELL_WIDTH = 40;
//TODO: remove constants.
exports.initPlayer = function(grid, player)
{
for (var dr = -1; dr <= 1; dr++)
for (var dc = -1; dc <= 1; dc++)
if (!grid.isOutOfBounds(dr + player.row, dc + player.col))
grid.set(dr + player.row, dc + player.col, player);
};
exports.updateFrame = function(grid, players, dead, notifyKill)
{
var adead = [];
if (dead instanceof Array)
adead = dead;
var kill;
if (!notifyKill)
kill = function() {};
else
kill = function(killer, other) {if (!removing[other]) notifyKill(killer, other);};
//Move players.
var tmp = players.filter(function(val) {
val.move();
if (val.dead)
adead.push(val);
return !val.dead;
});
//Remove players with collisions.
var removing = new Array(players.length);
for (var i = 0; i < players.length; i++)
{
for (var j = i; j < players.length; j++)
{
//Remove those players when other players have hit their tail.
if (!removing[j] && players[j].tail.hitsTail(players[i]))
{
kill(i, j);
removing[j] = true;
//console.log("TAIL");
}
if (!removing[i] && players[i].tail.hitsTail(players[j]))
{
kill(j, i);
removing[i] = true;
//console.log("TAIL");
}
//Remove players with collisons...
if (i !== j && squaresIntersect(players[i].posX, players[j].posX) &&
squaresIntersect(players[i].posY, players[j].posY))
{
//...if one player is own his own territory, the other is out.
if (grid.get(players[i].row, players[i].col) === players[i])
{
kill(i, j);
removing[j] = true;
}
else if (grid.get(players[j].row, players[j].col) === players[j])
{
kill(j, i);
removing[i] = true;
}
else
{
//...otherwise, the one that sustains most of the collision will be removed.
var areaI = area(players[i]);
var areaJ = area(players[j]);
if (areaI === areaJ)
{
kill(i, j);
kill(j, i);
removing[i] = removing[j] = true;
}
else if (areaI > areaJ)
{
kill(j, i);
removing[i] = true;
}
else
{
kill(i, j);
removing[j] = true;
}
}
}
}
}
tmp = tmp.filter(function(val, i) {
if (removing[i])
{
adead.push(val);
val.die();
}
return !removing[i];
});
players.length = tmp.length;
for (var i = 0; i < tmp.length; i++)
players[i] = tmp[i];
//Remove dead squares.
for (var r = 0; r < grid.size; r++)
{
for (var c = 0; c < grid.size; c++)
{
if (adead.indexOf(grid.get(r, c)) !== -1)
grid.set(r, c, null);
}
}
};
function squaresIntersect(a, b)
{
if (a < b)
return b < a + CELL_WIDTH;
else
return a < b + CELL_WIDTH;
}
function area(player)
{
var xDest = player.col * CELL_WIDTH;
var yDest = player.row * CELL_WIDTH;
if (player.posX === xDest)
return Math.abs(player.posY - yDest);
else
return Math.abs(player.posX - xDest);
}

View File

@@ -1,466 +0,0 @@
/* global $ */
var Rolling = require("./rolling.js");
var Color = require("./color.js");
var Grid = require("./grid.js");
var consts = require("./game-consts.js");
var core = require("./game-core.js");
var GRID_SIZE = consts.GRID_SIZE;
var CELL_WIDTH = consts.CELL_WIDTH;
var SPEED = consts.SPEED;
var BORDER_WIDTH = consts.BORDER_WIDTH;
var SHADOW_OFFSET = 5;
var ANIMATE_FRAMES = 24;
var BOUNCE_FRAMES = [8, 4];
var DROP_HEIGHT = 24;
var DROP_SPEED = 2;
var MIN_BAR_WIDTH = 65;
var BAR_HEIGHT = SHADOW_OFFSET + CELL_WIDTH;
var BAR_WIDTH = 400;
var canvasWidth, canvasHeight, gameWidth, gameHeight, ctx, offctx, offscreenCanvas, initZoom;
$(function () {
var canvas = $("#main-ui")[0];
ctx = canvas.getContext('2d');
offscreenCanvas = document.createElement("canvas");
offctx = offscreenCanvas.getContext('2d');
canvasWidth = offscreenCanvas.width = canvas.width = window.innerWidth;
canvasHeight = offscreenCanvas.height = canvas.height = window.innerHeight - 20;
zoom = initZoom = (canvasWidth / 1280);
canvas.style.marginTop = 20 / 2;
gameWidth = canvasWidth;
gameHeight = canvasHeight - BAR_HEIGHT;
});
var allowAnimation = true;
var animateGrid, players, allPlayers, playerPortion, portionsRolling,
barProportionRolling, grid, animateTo, offset, user, zoom, kills, showedDead;
grid = new Grid(GRID_SIZE, function(row, col, before, after) {
//Keep track of areas.
if (before)
playerPortion[before.num]--;
if (after)
playerPortion[after.num]++;
//Queue animation
if (before === after || !allowAnimation)
return;
animateGrid.set(row, col, {
before: before,
after: after,
frame: 0
});
});
function init() {
animateGrid = new Grid(GRID_SIZE);
grid.reset();
players = [];
allPlayers = [];
playerPortion = [];
portionsRolling = [];
barProportionRolling = [];
animateTo = [0, 0];
offset = [0, 0];
user = null;
zoom = 1;
kills = 0;
showedDead = false;
}
init();
//Paint methods.
function paintGridBorder(ctx)
{
ctx.fillStyle = 'lightgray';
var gridWidth = CELL_WIDTH * GRID_SIZE;
ctx.fillRect(-BORDER_WIDTH, 0, BORDER_WIDTH, gridWidth);
ctx.fillRect(-BORDER_WIDTH, -BORDER_WIDTH, gridWidth + BORDER_WIDTH * 2, BORDER_WIDTH);
ctx.fillRect(gridWidth, 0, BORDER_WIDTH, gridWidth);
ctx.fillRect(-BORDER_WIDTH, gridWidth, gridWidth + BORDER_WIDTH * 2, BORDER_WIDTH);
}
function paintGrid(ctx)
{
//Paint background.
ctx.fillStyle = "#e2ebf3";
ctx.fillRect(0, 0, CELL_WIDTH * GRID_SIZE, CELL_WIDTH * GRID_SIZE);
paintGridBorder(ctx);
//paintGridLines(ctx);
//Get viewing limits
var offsetX = (offset[0] - BORDER_WIDTH);
var offsetY = (offset[1] - BORDER_WIDTH);
var minRow = Math.max(Math.floor(offsetY / CELL_WIDTH), 0);
var minCol = Math.max(Math.floor(offsetX / CELL_WIDTH), 0);
var maxRow = Math.min(Math.ceil((offsetY + gameHeight / zoom) / CELL_WIDTH), grid.size);
var maxCol = Math.min(Math.ceil((offsetX + gameWidth / zoom) / CELL_WIDTH), grid.size);
//Paint occupied areas. (and fading ones).
for (var r = minRow; r < maxRow; r++)
{
for (var c = minCol; c < maxCol; c++)
{
var p = grid.get(r, c);
var x = c * CELL_WIDTH, y = r * CELL_WIDTH, baseColor, shadowColor;
var animateSpec = animateGrid.get(r, c);
if (allowAnimation && animateSpec)
{
if (animateSpec.before) //fading animation
{
var frac = (animateSpec.frame / ANIMATE_FRAMES);
var back = new Color(.58, .41, .92, 1);
baseColor = animateSpec.before.baseColor.interpolateToString(back, frac);
shadowColor = animateSpec.before.shadowColor.interpolateToString(back, frac);
}
else
continue;
}
else if (p)
{
baseColor = p.baseColor;
shadowColor = p.shadowColor;
}
else //No animation nor is this player owned.
continue;
var hasBottom = !grid.isOutOfBounds(r + 1, c);
var bottomAnimate = hasBottom && animateGrid.get(r + 1, c);
var totalStatic = !bottomAnimate && !animateSpec;
var bottomEmpty = totalStatic ? (hasBottom && !grid.get(r + 1, c)) :
(!bottomAnimate || (bottomAnimate.after && bottomAnimate.before));
if (hasBottom && ((!!bottomAnimate ^ !!animateSpec) || bottomEmpty))
{
ctx.fillStyle = shadowColor.rgbString();
ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH + 1 / zoom, SHADOW_OFFSET);
}
ctx.fillStyle = baseColor.rgbString();
ctx.fillRect(x, y, CELL_WIDTH + 1 / zoom, CELL_WIDTH + 1 / zoom);
}
}
if (!allowAnimation)
return;
//Paint squares with drop in animation.
for (var r = 0; r < grid.size; r++)
{
for (var c = 0; c < grid.size; c++)
{
animateSpec = animateGrid.get(r, c);
x = c * CELL_WIDTH, y = r * CELL_WIDTH;
if (animateSpec && allowAnimation)
{
var viewable = r >= minRow && r < maxRow && c >= minCol && c < maxCol;
if (animateSpec.after && viewable)
{
//Bouncing the squares.
var offsetBounce = getBounceOffset(animateSpec.frame);
y -= offsetBounce;
shadowColor = animateSpec.after.shadowColor;
baseColor = animateSpec.after.baseColor.deriveLumination(-(offsetBounce / DROP_HEIGHT) * .1);
ctx.fillStyle = shadowColor.rgbString();
ctx.fillRect(x, y + CELL_WIDTH, CELL_WIDTH, SHADOW_OFFSET);
ctx.fillStyle = baseColor.rgbString();
ctx.fillRect(x, y, CELL_WIDTH + 1 / zoom, CELL_WIDTH + 1 / zoom);
}
animateSpec.frame++;
if (animateSpec.frame >= ANIMATE_FRAMES)
animateGrid.set(r, c, null);
}
}
}
}
//TODO: mobile-friendly UI bar and mobile-friendly ranks
function paintUIBar(ctx)
{
var Z_CELL_WIDTH = CELL_WIDTH * initZoom;
var Z_BAR_HEIGHT = BAR_HEIGHT * initZoom;
var L_PADDING = 20 * initZoom;
var PADDING = 10 * initZoom;
var S_PADDING = 5 * initZoom;
//UI Bar background
ctx.fillStyle = "#24422c";
ctx.fillRect(0, 0, canvasWidth, Z_BAR_HEIGHT);
var barOffset;
ctx.fillStyle = "white";
ctx.font = "24px Changa";
barOffset = (user && user.name) ? (ctx.measureText(user.name).width + L_PADDING) : 0;
ctx.fillText(user ? user.name : "", S_PADDING, Z_CELL_WIDTH - S_PADDING);
//Draw filled bar.
ctx.fillStyle = "rgba(180, 180, 180, .3)";
ctx.fillRect(barOffset, 0, BAR_WIDTH * initZoom, Z_BAR_HEIGHT);
var userPortions = portionsRolling[user.num] ? portionsRolling[user.num].lag : 0;
var barSize = Math.ceil(((BAR_WIDTH - MIN_BAR_WIDTH) * userPortions + MIN_BAR_WIDTH) * initZoom);
ctx.fillStyle = user ? user.baseColor.rgbString() : "";
ctx.fillRect(barOffset, 0, barSize, Z_CELL_WIDTH);
ctx.fillStyle = user ? user.shadowColor.rgbString() : "";
ctx.fillRect(barOffset, Z_CELL_WIDTH, barSize, SHADOW_OFFSET * initZoom);
//TODO: dont reset kill count and zoom when we request frames.
//Percentage
ctx.fillStyle = "white";
ctx.font = "18px Changa";
ctx.fillText((userPortions * 100).toFixed(3) + "%", S_PADDING + barOffset, Z_CELL_WIDTH - S_PADDING);
//Number of kills
var killsText = "Kills: " + kills;
var killsOffset = L_PADDING + BAR_WIDTH + barOffset;
ctx.fillText(killsText, killsOffset, Z_CELL_WIDTH - S_PADDING);
//Calcuate rank
var sorted = [];
players.forEach(function(val) {
sorted.push({player: val, portion: playerPortion[val.num]});
});
sorted.sort(function(a, b) {
if (a.portion === b.portion) return a.player.num - b.player.num;
else return b.portion - a.portion;
});
var rank = sorted.findIndex(function(val) {return val.player === user});
ctx.fillText("Rank: " + (rank === -1 ? "--" : rank + 1) + " of " + sorted.length,
ctx.measureText(killsText).width + killsOffset + L_PADDING, Z_CELL_WIDTH - S_PADDING);
//Rolling the leaderboard bars.
if (sorted.length > 0)
{
var maxPortion = sorted[0].portion;
for (var i = 0; i < players.length; i++)
{
var rolling = barProportionRolling[players[i].num];
rolling.value = playerPortion[players[i].num] / maxPortion;
rolling.update();
}
}
//Show leaderboard.
var leaderboardNum = Math.min(5, sorted.length);
for (var i = 0; i < leaderboardNum; i++)
{
var player = sorted[i].player;
var name = player.name || "Unnamed";
var portion = barProportionRolling[player.num].lag;
var nameWidth = ctx.measureText(name).width;
barSize = Math.ceil(((BAR_WIDTH - MIN_BAR_WIDTH) * portion + MIN_BAR_WIDTH) * initZoom);
var barX = canvasWidth - barSize;
var barY = Z_BAR_HEIGHT * (i + 1) * initZoom;
var offset = i == 0 ? PADDING : 0;
ctx.fillStyle = 'rgba(10, 10, 10, .3)';
ctx.fillRect(barX - PADDING, barY + PADDING - offset, barSize + PADDING, Z_BAR_HEIGHT + offset);
ctx.fillStyle = player.baseColor.rgbString();
ctx.fillRect(barX, barY, barSize, CELL_WIDTH * initZoom);
ctx.fillStyle = player.shadowColor.rgbString();
ctx.fillRect(barX, barY + CELL_WIDTH * initZoom, barSize, SHADOW_OFFSET * initZoom);
ctx.fillStyle = "black";
ctx.fillText(name, barX - nameWidth - 15 * initZoom, barY + 27 * initZoom);
var percentage = (portionsRolling[player.num].lag * 100).toFixed(3) + "%";
ctx.fillStyle = "white";
ctx.fillText(percentage, barX + 5 * initZoom, barY + (CELL_WIDTH - 5) * initZoom);
}
}
function paint(ctx)
{
ctx.fillStyle = 'whitesmoke';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
//Move grid to viewport as said with the offsets, below the stats
ctx.save();
ctx.translate(0, BAR_HEIGHT);
ctx.beginPath();
ctx.rect(0, 0, gameWidth, gameHeight);
ctx.clip();
//Zoom in/out based on player stats.
ctx.scale(zoom, zoom);
ctx.translate(-offset[0] + BORDER_WIDTH, -offset[1] + BORDER_WIDTH);
paintGrid(ctx);
players.forEach(function (p) {
var fr = p.waitLag;
if (fr < ANIMATE_FRAMES)
p.render(ctx, fr / ANIMATE_FRAMES);
else
p.render(ctx);
});
//Reset transform to paint fixed UI elements
ctx.restore();
paintUIBar(ctx);
if ((!user || user.dead) && !showedDead)
{
showedDead = true;
console.log("You died!");
//return;
}
}
function paintDoubleBuff()
{
paint(offctx);
ctx.drawImage(offscreenCanvas, 0, 0);
}
function update() {
//Change grid offsets.
for (var i = 0; i <= 1; i++)
{
if (animateTo[i] !== offset[i])
{
if (allowAnimation)
{
var delta = animateTo[i] - offset[i];
var dir = Math.sign(delta);
var mag = Math.min(SPEED, Math.abs(delta));
offset[i] += dir * mag;
}
else
offset[i] = animateTo[i];
}
}
//Calculate player portions.
for (var i = 0; i < players.length; i++)
{
var roll = portionsRolling[players[i].num];
roll.value = playerPortion[players[i].num] / GRID_SIZE / GRID_SIZE;
roll.update();
}
//Zoom goes from 1 to .5, decreasing as portion goes up. TODO: maybe can modify this?
if (portionsRolling[user.num])
zoom = initZoom / (portionsRolling[user.num].lag + 1);
var dead = [];
core.updateFrame(grid, players, dead, function addKill(killer, other)
{
if (players[killer] === user && killer !== other)
kills++;
});
dead.forEach(function(val) {
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.
if (user) centerOnPlayer(user, animateTo);
}
//Helper methods.
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);
var gridWidth = grid.size * CELL_WIDTH + BORDER_WIDTH * 2;
pos[0] = Math.max(Math.min(xOff, gridWidth + (BAR_WIDTH + 100) / zoom - gameWidth / zoom), 0);
pos[1] = Math.max(Math.min(yOff, gridWidth - gameHeight / zoom), 0);
}
function getBounceOffset(frame)
{
var offsetBounce = ANIMATE_FRAMES;
var bounceNum = BOUNCE_FRAMES.length - 1;
while (bounceNum >= 0 && frame < offsetBounce - BOUNCE_FRAMES[bounceNum])
{
offsetBounce -= BOUNCE_FRAMES[bounceNum];
bounceNum--;
}
if (bounceNum === -1)
{
return (offsetBounce - frame) * DROP_SPEED;
}
else
{
offsetBounce -= BOUNCE_FRAMES[bounceNum];
frame = frame - offsetBounce;
var midFrame = BOUNCE_FRAMES[bounceNum] / 2;
if (frame >= midFrame)
return (BOUNCE_FRAMES[bounceNum] - frame) * DROP_SPEED;
else
return frame * DROP_SPEED;
}
}
module.exports = exports = {
addPlayer: function(player) {
if (allPlayers[player.num])
return; //Already added.
allPlayers[player.num] = players[players.length] = player;
playerPortion[player.num] = 0;
portionsRolling[player.num] = new Rolling(9 / GRID_SIZE / GRID_SIZE, ANIMATE_FRAMES);
barProportionRolling[player.num] = new Rolling(0, ANIMATE_FRAMES);
return players.length - 1;
},
getPlayer: function(ind) {
if (ind < 0 || ind >= players.length)
throw new RangeError("Player index out of bounds (" + ind + ").");
return players[ind];
},
getPlayerFromNum: function(num) {
return allPlayers[num];
},
playerSize: function() {
return players.length;
},
setUser: function(player) {
user = player;
centerOnPlayer(user, offset);
},
incrementKill: function() {
kills++;
},
reset: function() {
init();
},
paint: paintDoubleBuff,
update: update
};
Object.defineProperties(exports, {
allowAnimation: {
get: function() { return allowAnimation; },
set: function(val) { allowAnimation = !!val; },
enumerable: true
},
grid: {
get: function() { return grid; },
enumerable: true
}
});

View File

@@ -1,311 +0,0 @@
var Color = require("./color");
var Grid = require("./grid");
var Player = require("./player");
//var Gate = require("./gate");
var core = require("./game-core");
var consts = require("./game-consts");
var GRID_SIZE = consts.GRID_SIZE;
var CELL_WIDTH = consts.CELL_WIDTH;
var MAX_PLAYERS = consts.MAX_PLAYERS;
var HUES = [0, 10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 100, 110, 120, 125, 130, 135, 140, 145, 150, 160, 170, 180, 190, 200, 210, 220].map(function(val) {return val / 240});
var SATS = [192, 150, 100].map(function(val) {return val / 240});
function Game(id)
{
//Shuffle the hues.
for (var i = 0; i < HUES.length * 50; i++)
{
var a = Math.floor(Math.random() * HUES.length);
var b = Math.floor(Math.random() * HUES.length);
var tmp = HUES[a];
HUES[a] = HUES[b];
HUES[b] = tmp;
}
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++)
possColors[i++] = new Color(HUES[h], SATS[s], .5, 1);
var nextInd = 0;
var players = [];
var newPlayers = [];
var frameLocs = [];
var frame = 0;
var filled = 0;
var grid = new Grid(GRID_SIZE, function(row, col, before, after) {
if (!!after ^ !!before)
{
if (after)
filled++;
else
filled--;
if (filled === GRID_SIZE * GRID_SIZE)
console.log("FULL GAME");
}
});
this.id = id;
this.addPlayer = function(client, name) {
if (players.length >= MAX_PLAYERS)
return false;
var start = findEmpty(grid);
if (!start)
return false;
var params = {
posX: start.col * CELL_WIDTH,
posY: start.row * CELL_WIDTH,
currentHeading: Math.floor(Math.random() * 4),
name: name,
num: nextInd,
base: possColors.shift()
};
var p = new Player(grid, params);
p.tmpHeading = params.currentHeading;
p.client = client;
players.push(p);
newPlayers.push(p);
nextInd++;
core.initPlayer(grid, p);
console.log((p.name || "Unnamed") + " (" + p.num + ") joined.");
client.on("requestFrame", function () {
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)
});
});
//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, false, "No frame supplied");
else if (!checkInt(data.frame, 0, frame + 1))
resp(false, false, "Must be a valid frame number");
else
{
verifyPlayerLocations(data.frame, data.locs, resp);
}
});
client.on("frame", function(data, errorHan){
if (typeof data === "function")
{
errorHan(false, "No data supplied.");
return;
}
if (typeof errorHan !== "function")
errorHan = function() {};
if (!data)
errorHan(false, "No data supplied.");
else if (!checkInt(data.frame, 0, Infinity))
errorHan(false, "Requires a valid non-negative frame integer.");
else if (data.frame > frame)
errorHan(false, "Invalid frame received.");
else
{
if (data.heading !== undefined)
{
if (checkInt(data.heading, 0, 4))
{
p.tmpHeading = data.heading;
errorHan(true);
}
else
errorHan(false, "New heading must be an integer of range [0, 4).");
}
}
});
client.on('disconnect', function() {
p.die(); //Die immediately if not already.
p.disconnected = true;
console.log((p.name || "Unnamed") + " (" + p.num + ") left.");
});
return true;
};
function pushPlayerLocations()
{
var locs = [];
for (var p of players)
locs[p.num] = [p.posX, p.posY, p.waitLag];
locs.frame = frame;
if (frameLocs.length >= 300) //Give it 5 seconds of lag.
frameLocs.shift();
frameLocs.push(locs);
}
function verifyPlayerLocations(fr, verify, resp)
{
var minFrame = frame - frameLocs.length + 1;
if (fr < minFrame || fr > frame)
{
resp(false, false, "Frames out of reference");
return;
}
function string(loc)
{
return '(' + loc[0] + ', ' + loc[1] + ') [' + loc[2] + ']';
}
var locs = frameLocs[fr - minFrame];
if (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, true, 'P' + num + ' ' + string(locs[num]) + ' !== ' + string(verify[num]));
return;
}
}
resp(true, false);
}
function tick() {
//TODO: notify those players that this server automatically drops out.
var splayers = players.map(function(val) {return val.serialData();});
var snews = newPlayers.map(function(val) {
//Emit game stats.
val.client.emit("game", {
"num": val.num,
"gameid": id,
"frame": frame,
"players": splayers,
"grid": gridSerialData(grid, players),
});
return val.serialData();
});
var moves = players.map(function(val) {
//Account for race condition (when heading is set after emitting frames, and before updating).
val.heading = val.tmpHeading;
return {num: val.num, left: !!val.disconnected, heading: val.heading};
});
update();
var data = {frame: frame + 1, moves: moves};
if (snews.length > 0)
{
data.newPlayers = snews;
newPlayers = [];
}
for (var pl of players)
pl.client.emit("notifyFrame", data);
frame++;
pushPlayerLocations();
}
this.tickFrame = tick;
function update()
{
var dead = [];
core.updateFrame(grid, players, dead);
for (var pl of dead)
{
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);
}
}
}
function checkInt(value, min, max)
{
if (typeof value !== "number")
return false;
if (value < min || value >= max)
return false;
if (Math.floor(value) !== value)
return false;
return true;
}
function gridSerialData(grid, players)
{
var buff = Buffer.alloc(grid.size * grid.size);
var numToIndex = new Array(players.length > 0 ? players[players.length - 1].num + 1 : 0);
for (var i = 0; i < players.length; i++)
numToIndex[players[i].num] = i + 1;
for (var r = 0; r < grid.size; r++)
for (var c = 0; c < grid.size; c++)
{
var ele = grid.get(r, c);
buff[r * grid.size + c] = ele ? numToIndex[ele.num] : 0;
}
return buff;
}
function findEmpty(grid)
{
var available = [];
for (var r = 1; r < grid.size - 1; r++)
for (var c = 1; c < grid.size - 1; c++)
{
var cluttered = false;
checkclutter: for (var dr = -1; dr <= 1; dr++)
{
for (var dc = -1; dc <= 1; dc++)
{
if (grid.get(r + dr, c + dc))
{
cluttered = true;
break checkclutter;
}
}
}
if (!cluttered)
available.push({row: r, col: c});
}
if (available.length === 0)
return null;
else
return available[Math.floor(available.length * Math.random())];
}
module.exports = Game;

47
gate.js
View File

@@ -1,47 +0,0 @@
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
module.exports = Gate;
function Gate(awaiting)
{
var _this = this;
if (!(this instanceof Gate))
return new Gate(awaiting);
if (typeof awaiting !== "number")
awaiting = 0;
var currentAwaiting = awaiting;
var readyCount = 0;
var ready = new Array(currentAwaiting);
this.setAwaiting = function(count) {
awaiting = count;
};
this.ready = function(ind) {
if (Math.floor(ind) != ind || ind >= readyCount)
return false;
ready[ind] = true;
readyCount++;
_this.emit("ready", ind);
if (readyCount >= currentAwaiting)
{
_this.emit("allReady");
_this.reset();
}
return true;
};
this.reset = function() {
_this.emit("reset");
ready = new Array(currentAwaiting = awaiting);
readyCount = 0;
};
EventEmitter.call(this);
}
inherits(Gate, EventEmitter);

56
grid.js
View File

@@ -1,56 +0,0 @@
function Grid(size, changeCallback)
{
var grid = new Array(size);
var modified = false;
var data = {
grid: grid,
size: size
};
this.get = function(row, col)
{
if (isOutOfBounds(data, row, col))
throw new RangeError("Row or Column value out of bounds");
return grid[row] && grid[row][col];
}
this.set = function(row, col, value)
{
if (isOutOfBounds(data, row, col))
throw new RangeError("Row or Column value out of bounds");
if (!grid[row])
grid[row] = new Array(size);
var before = grid[row][col];
grid[row][col] = value;
if (typeof changeCallback === "function")
changeCallback(row, col, before, value);
modified = true;
return before;
}
this.reset = function() {
if (modified)
{
grid = new Array(size);
modified = false;
}
}
this.isOutOfBounds = isOutOfBounds.bind(this, data);
Object.defineProperty(this, "size", {
get: function() {return size; },
enumerable: true
});
}
function isOutOfBounds(data, row, col)
{
return row < 0 || row >= data.size || col < 0 || col >= data.size;
}
module.exports = Grid;

27
index.html Normal file
View File

@@ -0,0 +1,27 @@
<head>
<link href="https://fonts.googleapis.com/css?family=Changa:600" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="bundle.js"></script>
<style>
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background: black;
}
canvas {
image-rendering: optimizeSpeed; /* Older versions of FF */
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
</style>
</head>
<body>
<canvas id="main-ui"></canvas>
</body>

View File

@@ -1,34 +0,0 @@
{
"name": "blockly-io",
"version": "0.9.0",
"description": "An multiplayer-IO type game (cloned from Paper-IO)",
"main": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/theKidOfArcrania/Blockly-IO.git"
},
"keywords": [
"Blockly-IO",
"Paper-IO",
"IO",
"Game"
],
"author": "theKidOfArcrania",
"license": "MIT",
"bugs": {
"url": "https://github.com/theKidOfArcrania/Blockly-IO/issues"
},
"homepage": "https://github.com/theKidOfArcrania/Blockly-IO#readme",
"dependencies": {
"compression": "^1.6.2",
"express": "^4.15.0",
"finalhandler": "^1.0.0",
"serve-static": "^1.11.2",
"socket.io": "^1.7.3",
"socket.io-client": "^1.7.3",
"socket.io-proxy": "^1.0.3"
}
}

502
player.js
View File

@@ -1,502 +0,0 @@
var Stack = require("./stack.js");
var Color = require("./color.js");
var Grid = require("./grid.js");
var consts = require("./game-consts.js");
var GRID_SIZE = consts.GRID_SIZE;
var CELL_WIDTH = consts.CELL_WIDTH;
var NEW_PLAYER_LAG = 60; //wait for a second at least.
function defineGetter(getter) {
return {
get: getter,
enumerable: true
};
}
function defineInstanceMethods(thisobj, data /*, methods...*/)
{
for (var i = 2; i < arguments.length; i++)
thisobj[arguments[i].name] = arguments[i].bind(this, data);
}
function defineAccessorProperties(thisobj, data /*, names...*/)
{
var descript = {};
function getAt(name) { return function() {return data[name] } }
for (var i = 2; i < arguments.length; i++)
descript[arguments[i]] = defineGetter(getAt(arguments[i]));
Object.defineProperties(thisobj, descript);
}
function TailMove(orientation)
{
this.move = 1;
Object.defineProperty(this, "orientation", {
value: orientation,
enumerable: true
});
}
function Tail(player, sdata)
{
var data = {
tail: [],
tailGrid: [],
prev: null,
startRow: 0,
startCol: 0,
prevRow: 0,
prevCol: 0,
player: player
};
if (sdata)
{
data.startRow = data.prevRow = sdata.startRow || 0;
data.startCol = data.prevCol = sdata.startCol || 0;
sdata.tail.forEach(function(val) {
addTail(data, val.orientation, val.move);
});
}
data.grid = player.grid;
defineInstanceMethods(this, data, addTail, hitsTail, fillTail, renderTail, reposition, serialData);
Object.defineProperty(this, "moves", {
get: function() {return data.tail.slice(0);},
enumerable: true
});
}
//Instance methods.
function serialData(data) {
return {
tail: data.tail,
startRow: data.startRow,
startCol: data.startCol
};
}
function setTailGrid(data, tailGrid, r, c)
{
if (!tailGrid[r])
tailGrid[r] = [];
tailGrid[r][c] = true;
}
function addTail(data, orientation, count)
{
if (count === undefined)
count = 1;
if (!count || count < 0)
return;
var prev = data.prev;
var r = data.prevRow, c = data.prevCol;
if (data.tail.length === 0)
setTailGrid(data, data.tailGrid, r, c);
if (!prev || prev.orientation !== orientation)
{
prev = data.prev = new TailMove(orientation);
data.tail.push(prev);
prev.move += count - 1;
}
else
prev.move += count;
for (var i = 0; i < count; i++)
{
var pos = walk([data.prevRow, data.prevCol], null, orientation, 1);
data.prevRow = pos[0];
data.prevCol = pos[1];
setTailGrid(data, data.tailGrid, pos[0], pos[1]);
}
}
function reposition(data, row, col)
{
data.prevRow = data.startRow = row;
data.prevCol = data.startCol = col;
data.prev = null;
if (data.tail.length === 0)
return;
else
{
var ret = data.tail;
data.tail = [];
data.tailGrid = [];
return ret;
}
}
/*
function render2(data, ctx)
{
ctx.fillStyle = data.player.tailColor.rgbString();
for (var r = 0; r < data.tailGrid.length; r++)
{
if (!data.tailGrid[r])
continue;
for (var c = 0; c < data.tailGrid[r].length; c++)
if (data.tailGrid[r][c])
ctx.fillRect(c * CELL_WIDTH, r * CELL_WIDTH, CELL_WIDTH, CELL_WIDTH);
}
}
*/
//Helper methods.
function renderTail(data, ctx)
{
if (data.tail.length === 0)
return;
ctx.fillStyle = data.player.tailColor.rgbString();
var prevOrient = -1;
var start = [data.startRow, data.startCol];
//fillTailRect(ctx, start, start);
data.tail.forEach(function(tail) {
var negDir = tail.orientation === 0 || tail.orientation === 3;
var back = start;
if (!negDir)
start = walk(start, null, tail.orientation, 1);
var finish = walk(start, null, tail.orientation, tail.move - 1);
if (tail.move > 1)
fillTailRect(ctx, start, finish);
if (prevOrient !== -1)
//Draw folding triangle.
renderCorner(ctx, back, prevOrient, tail.orientation);
start = finish;
if (negDir)
walk(start, start, tail.orientation, 1);
prevOrient = tail.orientation;
});
var curOrient = data.player.currentHeading;
if (prevOrient === curOrient)
{
fillTailRect(ctx, start, start);
}
else
renderCorner(ctx, start, prevOrient, curOrient);
}
function renderCorner(ctx, cornerStart, dir1, dir2)
{
if (dir1 === 0 || dir2 === 0)
walk(cornerStart, cornerStart, 2, 1);
if (dir1 === 3 || dir2 === 3)
walk(cornerStart, cornerStart, 1, 1);
var a = walk(cornerStart, null, dir2, 1);
var b = walk(a, null, dir1, 1);
var triangle = new Path2D();
triangle.moveTo(cornerStart[1] * CELL_WIDTH, cornerStart[0] * CELL_WIDTH);
triangle.lineTo(a[1] * CELL_WIDTH, a[0] * CELL_WIDTH);
triangle.lineTo(b[1] * CELL_WIDTH, b[0] * CELL_WIDTH);
triangle.closePath();
for (var i = 0; i < 2; i++)
ctx.fill(triangle);
}
function walk(from, ret, orient, dist)
{
ret = ret || [];
ret[0] = from[0];
ret[1] = from[1];
switch (orient)
{
case 0: ret[0] -= dist; break; //UP
case 1: ret[1] += dist; break; //RIGHT
case 2: ret[0] += dist; break; //DOWN
case 3: ret[1] -= dist; break; //LEFT
}
return ret;
}
function fillTailRect(ctx, start, end)
{
var x = start[1] * CELL_WIDTH;
var y = start[0] * CELL_WIDTH;
var width = (end[1] - start[1]) * CELL_WIDTH;
var height = (end[0] - start[0]) * CELL_WIDTH;
if (width === 0)
width += CELL_WIDTH;
if (height === 0)
height += CELL_WIDTH;
if (width < 0)
{
x += width;
width = -width;
}
if (height < 0)
{
y += height;
height = -height;
}
ctx.fillRect(x, y, width, height);
}
function fillTail(data)
{
if (data.tail.length === 0)
return;
function onTail(c) { return data.tailGrid[c[0]] && data.tailGrid[c[0]][c[1]]; }
var grid = data.grid;
var start = [data.startRow, data.startCol];
var been = new Grid(grid.size);
var coords = [];
coords.push(start);
while (coords.length > 0) //BFS for all tail spaces.
{
var coord = coords.shift();
var r = coord[0];
var c = coord[1];
if (grid.isOutOfBounds(r, c))
continue;
if (been.get(r, c))
continue;
if (onTail(coord)) //on the tail.
{
been.set(r, c, true);
grid.set(r, c, data.player);
//Find all spots that this tail encloses.
floodFill(data, grid, r + 1, c, been);
floodFill(data, grid, r - 1, c, been);
floodFill(data, grid, r, c + 1, been);
floodFill(data, grid, r, c - 1, been);
coords.push([r + 1, c]);
coords.push([r - 1, c]);
coords.push([r, c + 1]);
coords.push([r, c - 1]);
}
}
}
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(row, col) || been.get(row, col) || onTail(start) || grid.get(row, col) === data.player)
return; //Avoid allocating too many resources.
var coords = [];
var filled = new Stack(GRID_SIZE * GRID_SIZE + 1);
var surrounded = true;
coords.push(start);
while (coords.length > 0)
{
var coord = coords.shift();
var r = coord[0];
var c = coord[1];
if (grid.isOutOfBounds(r, c))
{
surrounded = false;
continue;
}
//End this traverse on boundaries (where we been, on the tail, and when we enter our territory)
if (been.get(r, c) || onTail(coord) || grid.get(r, c) === data.player)
continue;
been.set(r, c, true);
if (surrounded)
filled.push(coord);
coords.push([r + 1, c]);
coords.push([r - 1, c]);
coords.push([r, c + 1]);
coords.push([r, c - 1]);
}
if (surrounded)
{
while (!filled.isEmpty())
{
coord = filled.pop();
grid.set(coord[0], coord[1], data.player);
}
}
return surrounded;
}
function hitsTail(data, other)
{
return (data.prevRow !== other.row || data.prevCol !== other.col) &&
(data.startRow !== other.row || data.startCol !== other.col) &&
!!(data.tailGrid[other.row] && data.tailGrid[other.row][other.col]);
}
var SPEED = 5;
var SHADOW_OFFSET = 10;
function Player(grid, sdata) {
var data = {};
//Parameters
data.num = sdata.num;
data.name = sdata.name || ""; //|| "Player " + (data.num + 1);
data.grid = grid;
data.posX = sdata.posX;
data.posY = sdata.posY;
this.heading = data.currentHeading = sdata.currentHeading; //0 is up, 1 is right, 2 is down, 3 is left.
data.waitLag = sdata.waitLag || 0;
data.dead = false;
//Only need colors for client side.
var base;
if (sdata.base)
base = this.baseColor = sdata.base instanceof Color ? sdata.base : 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);
//Tail requires special handling.
this.grid = grid; //Temporary
if (sdata.tail)
data.tail = new Tail(this, sdata.tail);
else
{
data.tail = new Tail(this);
data.tail.reposition(calcRow(data), calcCol(data));
}
//Instance methods.
this.move = move.bind(this, data);
this.die = function() { data.dead = true;};
this.serialData = function() {
return {
base: this.baseColor,
num: data.num,
name: data.name,
posX: data.posX,
posY: data.posY,
currentHeading: data.currentHeading,
tail: data.tail.serialData(),
waitLag: data.waitLag
};
};
//Read-only Properties.
defineAccessorProperties(this, data, "currentHeading", "dead", "name", "num", "posX", "posY", "grid", "tail", "waitLag");
Object.defineProperties(this, {
row: defineGetter(function() { return calcRow(data); }),
col: defineGetter(function() { return calcCol(data); })
});
}
//Gets the next integer in positive or negative direction.
function nearestInteger(positive, val)
{
return positive ? Math.ceil(val) : Math.floor(val);
}
function calcRow(data)
{
return nearestInteger(data.currentHeading === 2 /*DOWN*/, data.posY / CELL_WIDTH);
}
function calcCol(data)
{
return nearestInteger(data.currentHeading === 1 /*RIGHT*/, data.posX / CELL_WIDTH);
}
//Instance methods
Player.prototype.render = function(ctx, fade)
{
//Render tail.
this.tail.renderTail(ctx);
//Render player.
fade = fade || 1;
ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString();
ctx.fillRect(this.posX, this.posY, CELL_WIDTH, CELL_WIDTH);
var mid = CELL_WIDTH / 2;
var grd = ctx.createRadialGradient(this.posX + mid, this.posY + mid - SHADOW_OFFSET, 1,
this.posX + mid, this.posY + mid - SHADOW_OFFSET, CELL_WIDTH);
grd.addColorStop(0, this.baseColor.deriveAlpha(fade).rgbString());
grd.addColorStop(1, new Color(0, 0, 1, fade).rgbString());
ctx.fillStyle = grd;
ctx.fillRect(this.posX - 1, this.posY - SHADOW_OFFSET, CELL_WIDTH + 2, CELL_WIDTH);
//Render name
ctx.fillStyle = this.shadowColor.deriveAlpha(fade).rgbString();
ctx.textAlign = "center";
var yoff = -SHADOW_OFFSET * 2;
if (this.row === 0)
yoff = SHADOW_OFFSET * 2 + CELL_WIDTH;
ctx.font = "18px Changa";
ctx.fillText(this.name, this.posX + CELL_WIDTH / 2, this.posY + yoff);
};
function move(data)
{
if (data.waitLag < NEW_PLAYER_LAG)
{
data.waitLag++;
return;
}
//Move to new position.
var heading = this.heading;
if (this.posX % CELL_WIDTH !== 0 || this.posY % CELL_WIDTH !== 0)
heading = data.currentHeading;
else
data.currentHeading = heading;
switch (heading)
{
case 0: data.posY -= SPEED; break; //UP
case 1: data.posX += SPEED; break; //RIGHT
case 2: data.posY += SPEED; break; //DOWN
case 3: data.posX -= SPEED; break; //LEFT
}
//Check for out of bounds.
var row = this.row, col = this.col;
if (data.grid.isOutOfBounds(row, col))
{
data.dead = true;
return;
}
//Update tail position.
if (data.grid.get(row, col) === this)
{
//Safe zone!
this.tail.fillTail();
this.tail.reposition(row, col);
}
//If we are completely in a new cell (not in our safe zone), we add to the tail.
else if (this.posX % CELL_WIDTH === 0 && this.posY % CELL_WIDTH === 0)
this.tail.addTail(heading);
}
module.exports = Player;

View File

@@ -1,2 +0,0 @@
//code.stephenmorley.org
function Queue(){var a=[],b=0;this.getLength=function(){return a.length-b};this.isEmpty=function(){return 0==a.length};this.enqueue=function(b){a.push(b)};this.dequeue=function(){if(0!=a.length){var c=a[b];2*++b>=a.length&&(a=a.slice(b),b=0);return c}};this.peek=function(){return 0<a.length?a[b]:void 0}};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,24 +0,0 @@
/* arabic */
@font-face {
font-family: 'Changa';
font-style: normal;
font-weight: 600;
src: local('Changa SemiBold'), local('Changa-SemiBold'), url(8-mw6umTgtMSI5PXqVIDMRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+FB50-FDFF, U+FE80-FEFC;
}
/* latin-ext */
@font-face {
font-family: 'Changa';
font-style: normal;
font-weight: 600;
src: local('Changa SemiBold'), local('Changa-SemiBold'), url(l9R1mHXzJ6oxjfw8BkQIThJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Changa';
font-style: normal;
font-weight: 600;
src: local('Changa SemiBold'), local('Changa-SemiBold'), url(aXdbZDB08TCIBRKa7Qgu_FtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}

View File

@@ -1,40 +0,0 @@
<head>
<meta name=viewport content="width=device-width, initial-scale=1">
<link href="changa.css" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
<script src="jquery.min.js"></script>
<script src="bundle.min.js"></script>
<title>Blockly.IO</title>
</head>
<body>
<canvas id="main-ui"></canvas>
<div id="stats" style="opacity: 0" class="fullscreen hidden">
<div style="padding-top: 100px"></div>
<div class="card">
<h1 id="score">Score: <span id="score-num">0</span></h1>
<h1 id="high-score">High Score: <span id="high-score-num">0</span></h1>
<h1 id="time">Time Alive: <span id="time-num">0</span></h1>
<h1 id="killed">Killed: <span id="killed-num">0</span></h1>
</div>
</div>
<div id="begin" style="opacity: .99999" class="fullscreen">
<div class="center">
<h1>Blockly.IO!</h1>
<small>Todo: replace ^^^ with a picture. Work in progress.</small><br>
<h1>Enter your name</h1>
<div>
<input autocomplete="off" id="name" placeholder="An awesome name!">
</div>
<div class="blockless">
<button type="submit">Play!</button>
</div>
<div class="blockless">
<small id="error">Your browser does not support JavaScript!</small>
</div>
<br>
<small>Visit the open-source code on <a href="https://github.com/theKidOfArcrania/PaperIO-Web">GitHub</a>!</small>
</div>
</div>
</body>

File diff suppressed because one or more lines are too long

View File

@@ -1,79 +0,0 @@
body, html {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background: black;
color: white;
font-family: "Changa", "Sans Serif";
}
#error {
color: red;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
.fullscreen {
width: 100%;
height: 100%;
background: black;
}
.center {
vertical-align: middle;
text-align: center;
margin: 1rem;
margin-top: 0rem;
padding-top: 3rem;
}
input {
font-family: "Changa", "Sans Serif";
font-size: 24px;
padding: .5rem 1rem;
border: 4px solid lightgray;
border-radius: 2px;
width: 100%;
}
button {
border-style: solid;
border-color: #e6e699 #b8b814 #b8b814 #e6e699;
border-width: 4px 4px 4px 4px;
border-radius: 4px;
background: #eded5e;
font-family: "Changa", "Sans-serif";
font-size: 24px;
padding: .5rem 1rem;
width: 100%;
margin-top: .5rem;
}
@media screen and (min-width: 769px){
button, input {
width: 50%;
}
}
button:active {
border-color: #73730d #cccc7d #cccc7d #73730d;
background: #e8e830;
}
.hidden {
display: none;
}
#stats
{
background: rgb(80, 80, 80);
position: absolute;
top: 0;
left: 0;
}

View File

@@ -1,2 +0,0 @@
<script src="socket.io.min.js"></script>
<script src="socket-test.js"></script>

View File

@@ -1,27 +0,0 @@
function Rolling(value, frames)
{
var lag = 0;
if (!frames)
frames = 24;
this.value = value;
Object.defineProperty(this, "lag", {
get: function() { return lag; },
enumerable: true
});
this.update = function() {
var delta = this.value - lag;
var dir = Math.sign(delta);
var speed = Math.abs(delta) / frames;
var mag = Math.min(Math.abs(speed), Math.abs(delta));
lag += mag * dir;
return lag;
}
}
module.exports = Rolling;

View File

@@ -1,40 +0,0 @@
//TODO: rename as "blockly.io".
var hostname = process.argv[2] || "0.0.0.0";
var port = parseInt(process.argv[3]) || 80;
var express = require('express');
var compression = require('compression')
var app = express();
//Create static server
app.use(compression());
app.use(express.static('public'));
app.listen(port, hostname);
var http = require('http');
var server = http.createServer();
var io = require('socket.io')(server);
io.set('transports', ['websocket']);
var Game = require('./game-server.js');
var games = [new Game()];
io.on('connection', function(socket){
socket.on("hello", function(data, fn) {
//TODO: error checking.
if (data.name && data.name.length > 32)
fn(false, "Your name is too long!");
else if (!games[0].addPlayer(socket, data.name))
fn(false, "Game is too full!");
else
fn(true);
});
socket.on("checkConn", function(fn) { fn(); });
});
server.listen(8081);
function tick() {
games[0].tickFrame();
setTimeout(tick, 1000 / 60);
}
tick();
//setTimeout(tick, 1000 / 60);

View File

@@ -1,13 +0,0 @@
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);

View File

@@ -1,41 +0,0 @@
function Stack(initSize)
{
var len = 0;
var arr = [];
this.ensureCapacity = function(size)
{
arr.length = Math.max(arr.length, size || 0);
};
this.push = function(ele)
{
this[len] = ele;
len++;
};
this.pop = function()
{
if (len === 0)
return;
len--;
var tmp = this[len];
this[len] = undefined;
return tmp;
};
this.isEmpty = function() {
return len === 0;
}
this.ensureCapacity(initSize);
Object.defineProperty(this, "length", {
get: function() {return len;}
});
}
module.exports = Stack;