diff --git a/game-client.js b/game-client.js index 49554a2..84818a0 100644 --- a/game-client.js +++ b/game-client.js @@ -3,6 +3,7 @@ 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; @@ -32,7 +33,7 @@ var user, socket, frame; //Event listeners $(document).keydown(function(e) { - if (user.dead) + if (!user || user.dead) return; var newHeading = -1; switch (e.which) @@ -63,24 +64,42 @@ $(document).keydown(function(e) { e.preventDefault(); }); - -$(function() { - var grid = renderer.grid; - var timeout = undefined; +window.run = run; +function run() { + $("#begin").css("display: none"); + $("#begin").animate({ + opacity: 0 + }, 1000); + + user = null; + deadFrames = 0; //Socket connection. - socket = require('socket.io-client')('http://paper-io-thekidofarcrania.c9users.io:8081'); + //, {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) { + if (success) console.info("Connected to game!"); + else console.error("Unable to connect to game."); + }); +} + +var grid = renderer.grid; +var timeout = undefined; +var dirty = false; +var deadFrames = 0; + +function connectServer() { + io.j = []; + io.sockets = []; + socket = io('http://paper-io-thekidofarcrania.c9users.io:8081', {'forceNew': true}); socket.on('connect', function(){ console.info("Connected to server."); - socket.emit('hello', { - name: 'Test player', - type: 0, //Free-for-all - gameid: -1 //Requested game-id, or -1 for anyone. - }, function(success) { - if (success) console.info("Connected to game!"); - else console.error("Unable to connect to game."); - }); }); + var colors; socket.on('game', function(data) { if (timeout != undefined) clearTimeout(timeout); @@ -89,9 +108,10 @@ $(function() { frame = data.frame; renderer.reset(); + waiting = false; //Load colors. - var colors = data.colors || []; + colors = data.colors || []; //Load players. data.players.forEach(function(p) { @@ -115,15 +135,19 @@ $(function() { frame = data.frame; }); - var paintFrames = 0; + var waiting = false; socket.on('notifyFrame', function(data, fn) { 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; } @@ -133,68 +157,85 @@ $(function() { 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.getPlayer(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(); + } + } - paintFrames++; + renderer.update(frame); + + dirty = true; requestAnimationFrame(function() { paintLoop(); }); timeout = setTimeout(function() { console.warn("Server has timed-out. Disconnecting."); socket.disconnect(); - }, 5000); + }, 500); fn(); }); socket.on('disconnect', function(){ + if (!user) + return; console.info("Server has disconnected. Creating new game."); socket.disconnect(); user.die(); paintLoop(); - }); - - var deadFrames = 0; - function paintLoop() - { - if (paintFrames === 0) - return; - while (paintFrames > 0) - { - renderer.update(); - paintFrames--; - } - renderer.paint(); - if (user.dead) + $("#begin").css("display: block"); + $("#begin").animate({ + opacity: .9999 + }, 500); + }); +} + +function paintLoop() +{ + if (!dirty) + return; + renderer.paint(); + dirty = false; + + if (user.dead) + { + if (timeout) + clearTimeout(timeout); + if (deadFrames === 60) //One second of frame { - if (timeout) - clearTimeout(timeout); - if (deadFrames === 120) //Two second of frames - { - var before = renderer.allowAnimation; - renderer.allowAnimation = false; - renderer.update(); - renderer.paint(); - renderer.allowAnimation = before; - deadFrames = 0; - return; - } - - socket.disconnect(); - deadFrames++; - paintFrames++; - requestAnimationFrame(paintLoop); + var before = renderer.allowAnimation; + renderer.allowAnimation = false; + renderer.update(); + renderer.paint(); + renderer.allowAnimation = before; + user = null; + deadFrames = 0; + return; } + + socket.disconnect(); + deadFrames++; + dirty = true; + requestAnimationFrame(paintLoop); } -}); \ No newline at end of file +} diff --git a/game-core.js b/game-core.js index 84cdda2..3f5f604 100644 --- a/game-core.js +++ b/game-core.js @@ -1,5 +1,6 @@ var ANIMATE_FRAMES = 24; var CELL_WIDTH = 40; +var NEW_PLAYER_LAG = 60; //wait for a second at least. //TODO: remove constants. exports.initPlayer = function(grid, player) @@ -9,7 +10,7 @@ exports.initPlayer = function(grid, player) if (!grid.isOutOfBounds(dr + player.row, dc + player.col)) grid.set(dr + player.row, dc + player.col, player); }; -exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) +exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill, frame) { var adead = []; if (dead instanceof Array) @@ -26,10 +27,11 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) if (!newPlayerFrames[val.num]) newPlayerFrames[val.num] = 0; - if (newPlayerFrames[val.num] < ANIMATE_FRAMES) + if (newPlayerFrames[val.num] < ANIMATE_FRAMES + NEW_PLAYER_LAG) newPlayerFrames[val.num]++; else - val.move(); + //TODO: remove frame + val.move(frame); if (val.dead) adead.push(val); @@ -48,11 +50,13 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) { 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... @@ -98,12 +102,12 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) } tmp = tmp.filter(function(val, i) { - if (removing[i]) + if (removing[i] && val.num !== 1) /** GHOST PLAYER **/ { adead.push(val); val.die(); } - return !removing[i]; + return !removing[i] || val.num === 1; }); players.length = tmp.length; for (var i = 0; i < tmp.length; i++) diff --git a/game-renderer.js b/game-renderer.js index 5e6e317..6196ae3 100644 --- a/game-renderer.js +++ b/game-renderer.js @@ -255,6 +255,7 @@ function paintUIBar(ctx) ctx.measureText(killsText).width + killsOffset + 20, CELL_WIDTH - 5); } +//TODO: depict leaderboard. function paint(ctx) { ctx.fillStyle = 'whitesmoke'; @@ -298,7 +299,7 @@ function paintDoubleBuff() ctx.drawImage(offscreenCanvas, 0, 0); } -function update() { +function update(frame) { //Change grid offsets. for (var i = 0; i <= 1; i++) @@ -346,7 +347,7 @@ function update() { { if (players[killer] === user && killer !== other) kills++; - }); + }, frame); dead.forEach(function(val) { console.log(val.name + " is dead"); allPlayers[val.num] = undefined; diff --git a/game-server.js b/game-server.js index 2e8ff65..409b48b 100644 --- a/game-server.js +++ b/game-server.js @@ -171,11 +171,12 @@ function Game(id) //}else // return; + //TODO: notify those players that this server automatically drops out. var snews = newPlayers.map(function(val) {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 {left: !!val.disconnected, heading: val.heading}; + return {num: val.num, left: !!val.disconnected, heading: val.heading}; }); var data = {frame: frame + 1, moves: moves}; @@ -185,19 +186,26 @@ function Game(id) newPlayers = []; } + //TODO: send a "good-bye" frame to the dead players. Just in case. players.forEach(function(val) { - // var splayers = players.map(function(val) {return val.serialData();}); - // val.client.emit("game", { - // "num": val.num, - // "gameid": id, - // "frame": frame, - // "players": splayers, - // "grid": gridSerialData(grid, players), - // "colors": colors - // }); - val.client.emit("notifyFrame", data, function() { - //playerReady(val, waitFrame); - }); + if (val.num === 1) //GHOST PLAYER + { + var splayers = players.map(function(val) {return val.serialData();}); + val.client.emit("game", { + "num": val.num, + "gameid": id, + "frame": frame, + "players": splayers, + "grid": gridSerialData(grid, players), + "colors": colors + }); + } + else + { + val.client.emit("notifyFrame", data, function() { + //playerReady(val, waitFrame); + }); + } }); frame++; @@ -212,7 +220,7 @@ function Game(id) function update() { var dead = []; - core.updateFrame(grid, players, newPlayerFrames, dead); + core.updateFrame(grid, players, newPlayerFrames, dead, undefined, frame); dead.forEach(function(val) { console.log(val.name + " died."); //val.client.disconnect(true); diff --git a/package.json b/package.json index b341e3e..c78f300 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "finalhandler": "^1.0.0", "serve-static": "^1.11.2", "socket.io": "^1.7.3", - "socket.io-client": "^1.7.3" + "socket.io-client": "^1.7.3", + "socket.io-proxy": "^1.0.3" } } diff --git a/player.js b/player.js index e0262d5..7c1cbe4 100644 --- a/player.js +++ b/player.js @@ -386,7 +386,7 @@ function Player(isClient, grid, sdata) { //Instance methods. this.move = move.bind(this, data); - this.die = function() {data.dead = true;}; + this.die = function() { if (data.num !== 1) /* GHOST PLAYER */ data.dead = true;}; this.serialData = function() { return { num: data.num, @@ -452,12 +452,13 @@ Player.prototype.render = function(ctx, fade) ctx.fillText(this.name, this.posX + CELL_WIDTH / 2, this.posY + yoff); }; -var moves = 0; -function move(data) + +function move(data, frame) { - moves++; - //console.log(moves + ": " + this.heading); + //console.log("P" + this.num + ": " + frame + ": " + this.heading); + //Move to new position. + var prevX = this.posX, prevY = this.posY; var heading = this.heading; if (this.posX % CELL_WIDTH !== 0 || this.posY % CELL_WIDTH !== 0) heading = data.currentHeading; @@ -475,6 +476,12 @@ function move(data) var row = this.row, col = this.col; if (data.grid.isOutOfBounds(row, col)) { + if (data.num === 1) /* GHOST PLAYER */ + { + prevX = this.posX; + prevY = this.posY; + return; + } data.dead = true; return; } diff --git a/public/bundle.js b/public/bundle.js index 7775826..e8b5db2 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -289,6 +289,7 @@ 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; @@ -318,7 +319,7 @@ var user, socket, frame; //Event listeners $(document).keydown(function(e) { - if (user.dead) + if (!user || user.dead) return; var newHeading = -1; switch (e.which) @@ -349,24 +350,42 @@ $(document).keydown(function(e) { e.preventDefault(); }); - -$(function() { - var grid = renderer.grid; - var timeout = undefined; +window.run = run; +function run() { + $("#begin").css("display: none"); + $("#begin").animate({ + opacity: 0 + }, 1000); + + user = null; + deadFrames = 0; //Socket connection. - socket = require('socket.io-client')('http://paper-io-thekidofarcrania.c9users.io:8081'); + //, {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) { + if (success) console.info("Connected to game!"); + else console.error("Unable to connect to game."); + }); +} + +var grid = renderer.grid; +var timeout = undefined; +var dirty = false; +var deadFrames = 0; + +function connectServer() { + io.j = []; + io.sockets = []; + socket = io('http://paper-io-thekidofarcrania.c9users.io:8081', {'forceNew': true}); socket.on('connect', function(){ console.info("Connected to server."); - socket.emit('hello', { - name: 'Test player', - type: 0, //Free-for-all - gameid: -1 //Requested game-id, or -1 for anyone. - }, function(success) { - if (success) console.info("Connected to game!"); - else console.error("Unable to connect to game."); - }); }); + var colors; socket.on('game', function(data) { if (timeout != undefined) clearTimeout(timeout); @@ -375,9 +394,10 @@ $(function() { frame = data.frame; renderer.reset(); + waiting = false; //Load colors. - var colors = data.colors || []; + colors = data.colors || []; //Load players. data.players.forEach(function(p) { @@ -401,15 +421,19 @@ $(function() { frame = data.frame; }); - var paintFrames = 0; + var waiting = false; socket.on('notifyFrame', function(data, fn) { 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; } @@ -419,71 +443,89 @@ $(function() { 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.getPlayer(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(); + } + } - paintFrames++; + renderer.update(frame); + + dirty = true; requestAnimationFrame(function() { paintLoop(); }); timeout = setTimeout(function() { console.warn("Server has timed-out. Disconnecting."); socket.disconnect(); - }, 5000); + }, 500); fn(); }); socket.on('disconnect', function(){ + if (!user) + return; console.info("Server has disconnected. Creating new game."); socket.disconnect(); user.die(); paintLoop(); - }); - - var deadFrames = 0; - function paintLoop() - { - if (paintFrames === 0) - return; - while (paintFrames > 0) - { - renderer.update(); - paintFrames--; - } - renderer.paint(); - if (user.dead) + $("#begin").css("display: block"); + $("#begin").animate({ + opacity: .9999 + }, 500); + }); +} + +function paintLoop() +{ + if (!dirty) + return; + renderer.paint(); + dirty = false; + + if (user.dead) + { + if (timeout) + clearTimeout(timeout); + if (deadFrames === 60) //One second of frame { - if (timeout) - clearTimeout(timeout); - if (deadFrames === 120) //Two second of frames - { - var before = renderer.allowAnimation; - renderer.allowAnimation = false; - renderer.update(); - renderer.paint(); - renderer.allowAnimation = before; - deadFrames = 0; - return; - } - - socket.disconnect(); - deadFrames++; - paintFrames++; - requestAnimationFrame(paintLoop); + var before = renderer.allowAnimation; + renderer.allowAnimation = false; + renderer.update(); + renderer.paint(); + renderer.allowAnimation = before; + user = null; + deadFrames = 0; + return; } + + socket.disconnect(); + deadFrames++; + dirty = true; + requestAnimationFrame(paintLoop); } -}); +} + },{"./game-consts.js":5,"./game-core.js":6,"./game-renderer.js":7,"./player.js":56,"socket.io-client":9}],5:[function(require,module,exports){ function constant(val) { @@ -505,6 +547,7 @@ Object.defineProperties(module.exports, consts); },{}],6:[function(require,module,exports){ var ANIMATE_FRAMES = 24; var CELL_WIDTH = 40; +var NEW_PLAYER_LAG = 60; //wait for a second at least. //TODO: remove constants. exports.initPlayer = function(grid, player) @@ -514,7 +557,7 @@ exports.initPlayer = function(grid, player) if (!grid.isOutOfBounds(dr + player.row, dc + player.col)) grid.set(dr + player.row, dc + player.col, player); }; -exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) +exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill, frame) { var adead = []; if (dead instanceof Array) @@ -531,10 +574,11 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) if (!newPlayerFrames[val.num]) newPlayerFrames[val.num] = 0; - if (newPlayerFrames[val.num] < ANIMATE_FRAMES) + if (newPlayerFrames[val.num] < ANIMATE_FRAMES + NEW_PLAYER_LAG) newPlayerFrames[val.num]++; else - val.move(); + //TODO: remove frame + val.move(frame); if (val.dead) adead.push(val); @@ -553,11 +597,13 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) { 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... @@ -603,12 +649,12 @@ exports.updateFrame = function(grid, players, newPlayerFrames, dead, notifyKill) } tmp = tmp.filter(function(val, i) { - if (removing[i]) + if (removing[i] && val.num !== 1) /** GHOST PLAYER **/ { adead.push(val); val.die(); } - return !removing[i]; + return !removing[i] || val.num === 1; }); players.length = tmp.length; for (var i = 0; i < tmp.length; i++) @@ -901,6 +947,7 @@ function paintUIBar(ctx) ctx.measureText(killsText).width + killsOffset + 20, CELL_WIDTH - 5); } +//TODO: depict leaderboard. function paint(ctx) { ctx.fillStyle = 'whitesmoke'; @@ -944,7 +991,7 @@ function paintDoubleBuff() ctx.drawImage(offscreenCanvas, 0, 0); } -function update() { +function update(frame) { //Change grid offsets. for (var i = 0; i <= 1; i++) @@ -992,7 +1039,7 @@ function update() { { if (players[killer] === user && killer !== other) kills++; - }); + }, frame); dead.forEach(function(val) { console.log(val.name + " is dead"); allPlayers[val.num] = undefined; @@ -9309,7 +9356,7 @@ function Player(isClient, grid, sdata) { //Instance methods. this.move = move.bind(this, data); - this.die = function() {data.dead = true;}; + this.die = function() { if (data.num !== 1) /* GHOST PLAYER */ data.dead = true;}; this.serialData = function() { return { num: data.num, @@ -9375,12 +9422,13 @@ Player.prototype.render = function(ctx, fade) ctx.fillText(this.name, this.posX + CELL_WIDTH / 2, this.posY + yoff); }; -var moves = 0; -function move(data) + +function move(data, frame) { - moves++; - //console.log(moves + ": " + this.heading); + //console.log("P" + this.num + ": " + frame + ": " + this.heading); + //Move to new position. + var prevX = this.posX, prevY = this.posY; var heading = this.heading; if (this.posX % CELL_WIDTH !== 0 || this.posY % CELL_WIDTH !== 0) heading = data.currentHeading; @@ -9398,6 +9446,12 @@ function move(data) var row = this.row, col = this.col; if (data.grid.isOutOfBounds(row, col)) { + if (data.num === 1) /* GHOST PLAYER */ + { + prevX = this.posX; + prevY = this.posY; + return; + } data.dead = true; return; } diff --git a/public/index.html b/public/index.html index 9b011dd..d35a265 100644 --- a/public/index.html +++ b/public/index.html @@ -1,27 +1,21 @@ + - + Paper.IO +
+
+
+

Paper.IO!

+ Todo: replace ^^^ with a picture. Work in progress. +

Enter your name

+ + +
+
+
\ No newline at end of file diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 0000000..81c044b --- /dev/null +++ b/public/styles.css @@ -0,0 +1,60 @@ +body, html { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; + background: black; + + color: white; + font-family: "Changa", "Sans Serif"; +} + +canvas { + position: absolute; + top: 0; + left: 0; +} + +.fullscreen { + width: 100%; + height: 100%; + background: black; +} + +.center { + vertical-align: middle; + text-align: center; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +input { + font-family: "Changa", "Sans Serif"; + font-size: 24px; + padding: .5rem 1rem; + border: 4px solid lightgray; + border-radius: 2px; +} + +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; +} + +button:active { + border-color: #73730d #cccc7d #cccc7d #73730d; + background: #e8e830; +} + +.hidden { + display: none; +} \ No newline at end of file diff --git a/server.js b/server.js index fc1c35c..1f3b668 100644 --- a/server.js +++ b/server.js @@ -3,7 +3,11 @@ var http = require('http'); var serveStatic = require('serve-static'); // Serve up public/ftp folder -var serve = serveStatic('public/', {'cacheControl': false}); +var serve = serveStatic('public/', {'cacheControl': false, 'setHeaders': setHeaders}); + +function setHeaders(res, path) { + res.setHeader("Access-Control-Allow-Origin", "http://paper-io-thekidofarcrania.c9users.io:8081"); +} // Create server var server = http.createServer(function onRequest (req, res) { @@ -15,6 +19,8 @@ server.listen(8080); 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){