Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // INLINED: server.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // CORE MODULE: http
- // CORE MODULE: socket.io
- // INLINED: config.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- /*globals XSS*/
- var config = {
- SERVER_PORT : 80,
- SERVER_ENDPOINT : 'http://localhost:80',
- SOCKET_IO_JS : 'http://localhost:80/socket.io/socket.io.js',
- GAME_TICK : 50,
- ROOM_CAPACITY : 4,
- TIME_GLOAT : 5,
- TIME_COUNTDOWN_FROM: 3,
- TIME_RESPAWN_APPLE : 30,
- TIME_SPAWN_POWERUP : [5, 30],
- SNAKE_SPEED : 150,
- SNAKE_SIZE : 4
- };
- if (typeof XSS !== 'undefined') {
- }
- // INLINED: event_handler.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // INLINED: events.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- /*globals XSS*/
- // Event keys are hard-coded and unique.
- var events = {
- CLIENT_CONNECT : 'CON',
- CLIENT_PING : 'PING',
- CLIENT_APPLE_HIT : 'CA1',
- CLIENT_APPLE_SPAWN : 'CA2',
- CLIENT_CHAT_NOTICE : 'CC1',
- CLIENT_CHAT_MESSAGE : 'CC2',
- CLIENT_GAME_COUNTDOWN: 'CG1',
- CLIENT_GAME_START : 'CG2',
- CLIENT_GAME_SNAKES : 'CG3',
- CLIENT_GAME_SPAWNS : 'CG4',
- CLIENT_POWERUP_HIT : 'CP1',
- CLIENT_POWERUP_SPAWN : 'CP2',
- CLIENT_ROOM_JOIN : 'CR1',
- CLIENT_ROOM_INDEX : 'CR2',
- CLIENT_ROOM_SCORE : 'CR3',
- CLIENT_SNAKE_ACTION : 'CS1',
- CLIENT_SNAKE_CRASH : 'CS2',
- CLIENT_SNAKE_SPEED : 'CS3',
- CLIENT_SNAKE_UPDATE : 'CS4',
- SERVER_PONG : 'SP1',
- SERVER_ROOM_MATCH : 'SR1',
- SERVER_CHAT_MESSAGE : 'SC1',
- SERVER_SNAKE_UPDATE : 'SS1',
- SERVER_GAME_STATE : 'SG1'
- };
- if (typeof XSS !== 'undefined') {
- }
- /**
- * @param {Object} server
- * @param {Client} client
- * @param {Object} socket
- * @constructor
- */
- function EventHandler(server, client, socket) {
- this.server = server;
- this.client = client;
- this.socket = socket;
- this._pingInterval = setInterval(this._ping.bind(this), 5000);
- client.emit(events.CLIENT_CONNECT, client.id);
- socket.on('disconnect', this._disconnect.bind(this));
- socket.on(events.SERVER_ROOM_MATCH, this._matchRoom.bind(this));
- socket.on(events.SERVER_CHAT_MESSAGE, this._chat.bind(this));
- socket.on(events.SERVER_SNAKE_UPDATE, this._snakeUpdate.bind(this));
- socket.on(events.SERVER_GAME_STATE, this._gameState.bind(this));
- socket.on(events.SERVER_PONG, this._pong.bind(this));
- }
- // module.exports = EventHandler;
- EventHandler.prototype = {
- destruct: function() {
- // Other event listeners will remove themselves.
- clearInterval(this._pingInterval);
- this.server = null;
- this.client = null;
- this.socket = null;
- },
- /**
- * @private
- */
- _ping: function() {
- this.client.emit(events.CLIENT_PING, +new Date());
- },
- /**
- * @param {number} sendTime
- * @private
- */
- _pong: function(sendTime) {
- var roundtrip = (+new Date()) - sendTime;
- this.client.latency = Math.round(roundtrip / 2);
- },
- /**
- * @private
- */
- _disconnect: function() {
- var room, client = this.client;
- room = this.server.roomManager.rooms[client.roomid];
- if (room) {
- // If client is in a room, we cannot clean up immediately
- // because we need data to remove the client from the room
- // gracefully.
- room.disconnect(client);
- } else {
- this.server.removeClient(client);
- }
- },
- /**
- * @param {Object} data Object with keys name, pub, friendly
- * @private
- */
- _matchRoom: function(data) {
- var room, client = this.client, server = this.server;
- client.name = data.name;
- room = server.roomManager.getPreferredRoom(data);
- room.join(client);
- },
- /**
- * @param {string} message
- * @private
- */
- _chat: function(message) {
- var room, data, index;
- room = this._clientRoom(this.client);
- if (room) {
- index = room.clients.indexOf(this.client);
- data = [index, message.substr(0, 30)];
- room.broadcast(events.CLIENT_CHAT_MESSAGE, data, this.client);
- room.emit(events.CLIENT_SNAKE_ACTION, [index, 'Blah']);
- }
- },
- /**
- * @param data [<Array>,<number>] 0: parts, 1: direction
- */
- _snakeUpdate: function(data) {
- var game = this._clientGame(this.client);
- if (game && game.room.inProgress) {
- game.updateSnake(this.client, data[0], data[1]);
- }
- },
- /**
- * @private
- */
- _gameState: function() {
- var game = this._clientGame(this.client);
- if (game && game.room.inProgress) {
- game.emitState(this.client);
- }
- },
- /**
- * @param {Client} client
- * @return {Room}
- * @private
- */
- _clientRoom: function(client) {
- return this.server.roomManager.room(client.roomid);
- },
- /**
- * @param {Client} client
- * @return {Game}
- * @private
- */
- _clientGame: function(client) {
- return (client.roomid) ? this._clientRoom(client).game : null;
- }
- };
- // INLINED: room_manager.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // INLINED: room.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // INLINED: game.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // ALREADY INLINED: util
- // INLINED: spawner.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // ALREADY INLINED: util.js
- // ALREADY INLINED: events.js
- /**
- * Spawnable
- * @param {Game} game
- * @constructor
- */
- function Spawner(game) {
- this.game = game;
- this.spawns = [];
- this.locations = []; // Keep separate list for speed
- }
- // module.exports = Spawner;
- Spawner.prototype = {
- APPLE : 0,
- POWERUP: 1,
- EVENTS: [
- events.CLIENT_APPLE_SPAWN,
- events.CLIENT_POWERUP_SPAWN
- ],
- destruct: function() {
- for (var i = 0, m = this.spawns.length; i < m; i++) {
- this._destructSpawn(i);
- }
- },
- /**
- * @param {number} type
- * @param {number|null=} index
- * @param {boolean=} respawn
- * @param {number=} respawnAfter
- * @return {Object}
- */
- spawn: function(type, index, respawn, respawnAfter) {
- var spawn = {
- location : this.game.getEmptyLocation(),
- type : type,
- respawn : !!respawn,
- respawnAfter: respawnAfter
- };
- index = (typeof index === 'number') ? index : this.spawns.length;
- if (respawnAfter) {
- spawn.timer = setTimeout(function() {
- this._destructSpawn(index);
- this.spawn(type, index, respawn, respawnAfter);
- }.bind(this), respawnAfter);
- }
- this.spawns[index] = spawn;
- this.locations[index] = spawn.location;
- this.game.room.emit(this.EVENTS[type], [index, spawn.location]);
- return spawn;
- },
- /**
- * @param {Client} client
- * @param {number} index
- */
- hit: function(client, index) {
- var spawn = this.spawns[index];
- switch (spawn.type) {
- case this.APPLE:
- this.game.hitApple(client, index);
- break;
- case this.POWERUP:
- this.game.hitPowerup(client, index);
- break;
- }
- if (spawn.respawn) {
- this._destructSpawn(index);
- this.spawn(spawn.type, index, true, spawn.respawnAfter);
- } else {
- this._destructSpawn(index);
- }
- },
- /**
- * @param {Client} client
- * @param {Array.<number>} location
- * @return {Array}
- */
- handleHits: function(client, location) {
- var hits = [];
- for (var i = 0, m = this.spawns.length; i < m; i++) {
- if (null !== this.spawns[i] && Util.eq(this.spawns[i].location, location)) {
- hits.push(i);
- this.hit(client, i);
- }
- }
- return hits;
- },
- /**
- * @param {number} index
- * @private
- */
- _destructSpawn: function(index) {
- var spawn = this.spawns[index];
- if (spawn && spawn.timer) {
- clearTimeout(spawn.timer);
- }
- this.spawns[index] = null;
- this.locations[index] = null;
- }
- };
- // INLINED: levels.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- /*globals XSS*/
- // Generated on Wed, 10 Oct 2012 17:11:27 GMT
- // Generate file: `node build/levels.js`
- // Template file: source/templates/levels.js.tpl
- var levels = [
- {width: 63, height: 33, spawns: [192, 1886, 248, 1830], directions: [2, 0, 0, 2], walls: []},
- {width: 63, height: 33, spawns: [192, 1886, 248, 1830], directions: [2, 0, 0, 2], walls: [94, 157, 220, 283, 346, 409, 472, 535, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1480, 1543, 1606, 1669, 1732, 1795, 1858, 1921, 1984]},
- {width: 63, height: 33, spawns: [192, 1886, 248, 1830], directions: [2, 0, 0, 2], walls: [7, 31, 55, 70, 94, 118, 133, 157, 181, 220, 259, 283, 307, 322, 346, 370, 385, 409, 433, 441, 442, 443, 444, 445, 446, 447, 448, 472, 496, 497, 498, 499, 500, 501, 502, 503, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1533, 1534, 1535, 1536, 1537, 1538, 1539, 1540, 1541, 1542, 1543, 1544, 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1552, 1553, 1575, 1576, 1577, 1578, 1579, 1580, 1581, 1582, 1606, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1645, 1669, 1693, 1708, 1732, 1756, 1771, 1795, 1819, 1858, 1897, 1921, 1945, 1960, 1984, 2008, 2023, 2047, 2071]}
- ];
- if (typeof XSS !== 'undefined') {
- }
- // ALREADY INLINED: config.js
- // ALREADY INLINED: events.js
- // INLINED: util.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- var Util = {
- /**
- * @param {*} destination
- * @param {*} source
- * @return {*}
- */
- extend: function(destination, source) {
- for (var property in source) {
- if (source.hasOwnProperty(property)) {
- destination[property] = source[property];
- }
- }
- return destination;
- },
- /**
- * @param {number} min
- * @param {number} max
- * @return {number}
- */
- randomBetween: function(min, max) {
- return min + Math.floor(Math.random() * (max - min + 1));
- },
- /**
- * @param {Array} arr
- * @return {*}
- */
- randomItem: function(arr) {
- return arr[Util.randomBetween(0, arr.length - 1)];
- },
- /**
- * @return {string}
- */
- randomStr: function() {
- return Math.random().toString(36).substring(2, 5);
- },
- /**
- * @param {Array.<number>} a
- * @param {Array.<number>} b
- * @return {number}
- */
- delta: function(a, b) {
- return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
- },
- /**
- * @param {Array.<number>} a
- * @param {Array.<number>} b
- * @return {boolean}
- */
- eq: function(a, b) {
- return a[0] === b[0] && a[1] === b[1];
- },
- /**
- * @param {*} obj
- * @param {*} val
- * @return {?string}
- */
- getKey: function(obj, val) {
- for (var k in obj) {
- if (obj.hasOwnProperty(k) && val === obj[k]) {
- return k;
- }
- }
- return null;
- }
- };
- // module.exports = Util;
- // INLINED: level.js
- /*jshint globalstrict:true, es5:true, node:true */
- /**
- * Collisions and levels
- * @constructor
- * @param {number} levelID
- * @param {Object} levelData
- */
- function Level(levelID, levelData) {
- this.level = levelData[levelID];
- }
- // module.exports = Level;
- Level.prototype = {
- /**
- * @param {number} x
- * @param {number} y
- * @return {boolean}
- */
- isWall: function(x, y) {
- if (this.outOfBounds(x, y)) {
- return true;
- } else if (this.innerWall(x, y)) {
- return true;
- }
- return false;
- },
- /**
- * @param {number} playerID
- * @return {Array.<number>}
- */
- getSpawn: function(playerID) {
- var pos = this.level.spawns[playerID];
- return this.seqToXY(pos);
- },
- /**
- * @param {number} playerID
- * @return {number}
- */
- getSpawnDirection: function(playerID) {
- return this.level.directions[playerID];
- },
- /**
- * @param {Array.<Array>} locations
- * @return {Array.<number>}
- */
- getEmptyLocation: function(locations) {
- var m = this.level.width * this.level.height;
- while (true) {
- var location = this.seqToXY(Math.floor(Math.random() * m));
- if (this.isEmpty(locations, location)) {
- return location;
- }
- }
- },
- /**
- * @param {Array.<Array>} locations
- * @param {Array.<number>} location
- * @return {boolean}
- */
- isEmpty: function(locations, location) {
- if (this.isWall(location[0], location[1])) {
- return false;
- }
- for (var i = 0, m = locations.length; i < m; i++) {
- var iloc = locations[i];
- if (iloc && iloc[0] === location[0] && iloc[1] === location[1]) {
- return false;
- }
- }
- return true;
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {boolean}
- */
- outOfBounds: function(x, y) {
- if (x < 0 || y < 0) {
- return true;
- } else {
- return x >= this.level.width || y >= this.level.height;
- }
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {boolean}
- */
- innerWall: function(x, y) {
- var seq = this.xyToSeq(x, y);
- return this.innerWallSeq(seq);
- },
- /**
- * @param {number} seq
- * @return {boolean}
- */
- innerWallSeq: function(seq) {
- var wall = this.level.walls;
- for (var i = 0, m = wall.length; i < m; i++) {
- if (seq === wall[i]) {
- return true;
- }
- }
- return false;
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {number}
- */
- xyToSeq: function(x, y) {
- return x + this.level.width * y;
- },
- /**
- * @param {number} seq
- * @return {Array.<number>}
- */
- seqToXY: function(seq) {
- return [
- seq % this.level.width,
- Math.floor(seq / this.level.width)
- ];
- }
- };
- // INLINED: snake.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- /**
- * Snake
- * @param {Array.<number>} location
- * @param {number} direction
- * @param {number} size
- * @param {number} speed
- * @constructor
- */
- function Snake(location, direction, size, speed) {
- this.parts = [location];
- this.direction = direction;
- this.size = size;
- this.speed = speed;
- this.crashed = false;
- }
- // module.exports = Snake;
- Snake.prototype = {
- /**
- * @param {Array.<number>} position
- */
- move: function(position) {
- this.parts.push(position);
- this.trim();
- },
- /**
- * @return {Array.<number>}
- */
- head: function() {
- return this.parts[this.parts.length - 1];
- },
- /**
- * @param {Array.<number>} part
- * @return {boolean}
- */
- hasPartPredict: function(part) {
- var treshold = this.crashed ? -1 : 0;
- return (this.partIndex(part) > treshold);
- },
- /**
- * @return {Array.<number>}
- */
- getNextPosition: function() {
- var shift, head = this.head();
- shift = this.directionToShift(this.direction);
- return [head[0] + shift[0], head[1] + shift[1], 'x'];
- },
- trim: function() {
- while (this.parts.length > this.size) {
- this.parts.shift();
- }
- },
- /**
- * @param {Array.<number>} part
- * @return {boolean}
- */
- hasPart: function(part) {
- return (-1 !== this.partIndex(part));
- },
- /**
- * @param {Array.<number>} part
- * @return {number}
- */
- partIndex: function(part) {
- var parts = this.parts;
- for (var i = 0, m = parts.length; i < m; i++) {
- if (parts[i][0] === part[0] && parts[i][1] === part[1]) {
- return i;
- }
- }
- return -1;
- },
- /**
- * @param {number} direction
- * @return {Array.<number>}
- */
- directionToShift: function(direction) {
- return [[-1, 0], [0, -1], [1, 0], [0, 1]][direction];
- }
- };
- // INLINED: powerup.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // ALREADY INLINED: events.js
- // ALREADY INLINED: util.js
- /**
- * Powerup
- * @param {Client} client
- * @param {Game} game
- * @constructor
- */
- function Powerup(game, client) {
- var powerup = this._getPowerUp().bind(this);
- powerup(client, game);
- }
- // module.exports = Powerup;
- /** @const */ Powerup.APPLY_SELF = 0;
- /** @const */ Powerup.APPLY_OTHERS = 1;
- /** @const */ Powerup.APPLY_EITHER = 2;
- /** @const */ Powerup.APPLY_ALL = 3;
- Powerup.prototype = {
- /**
- * @return {Array}
- * @private
- */
- _getPowerups: function() {
- return [
- // [Weight, Powerup]
- [1, this._speed],
- [1, this._apples],
- [1, this._powerups],
- [1, this._reverse]
- ];
- },
- /**
- * @return {Function}
- * @private
- */
- _getPowerUp: function() {
- var i, m, random, powerups, cumulative = 0;
- powerups = this._getPowerups();
- for (i = 0, m = powerups.length; i < m; i++) {
- cumulative += powerups[i][0];
- powerups[i][0] = cumulative;
- }
- random = cumulative * Math.random();
- for (i = 0, m = powerups.length; i < m; i++) {
- if (powerups[i][0] > random) {
- return powerups[i][1];
- }
- }
- return powerups[0][1];
- },
- /**
- * Change snake speed
- * @param {Client} client
- * @param {Game} game
- * @private
- */
- _speed: function(client, game) {
- var index = game.room.clients.indexOf(client);
- client.snake.speed -= 5;
- game.room.emit(events.CLIENT_SNAKE_SPEED, [index, client.snake.speed]);
- game.room.emit(events.CLIENT_SNAKE_ACTION, [index, 'Speed+']);
- },
- /**
- * Spawn multiple apples
- * @param {Client} client
- * @param {Game} game
- * @private
- */
- _apples: function(client, game) {
- var r = Util.randomBetween(2, 6);
- this._spawn(client, game, game.spawner.APPLE, r, 'Apples+');
- },
- /**
- * Spawn multiple powerups
- * @param {Client} client
- * @param {Game} game
- * @private
- */
- _powerups: function(client, game) {
- var r = Util.randomBetween(2, 4);
- this._spawn(client, game, game.spawner.POWERUP, r, 'Power-ups+');
- },
- /**
- * @param {Client} client
- * @param {Game} game
- * @param {number} type
- * @param {number} amount
- * @param {string} message
- * @private
- */
- _spawn: function(client, game, type, amount, message) {
- var index, spawn;
- index = game.room.clients.indexOf(client);
- spawn = function() {
- game.spawner.spawn(type);
- };
- game.room.emit(events.CLIENT_SNAKE_ACTION, [index, message]);
- for (var i = 0; i < amount; i++) {
- setTimeout(spawn, i * 100);
- }
- },
- /**
- * Reverse Snake direction
- * @param {Client} client
- * @param {Game} game
- * @private
- */
- _reverse: function(client, game) {
- var snakes = game.snakes, index = game.room.clients.indexOf(client);
- for (var i = 0, m = snakes.length; i < m; i++) {
- if (i !== index) {
- game.room.emit(events.CLIENT_SNAKE_ACTION, [i, 'Reverse']);
- game.reverseSnake(i);
- }
- }
- }
- };
- /**
- * @param {Room} room
- * @param {number} level
- * @constructor
- */
- function Game(room, level) {
- this.room = room;
- this.server = room.server;
- this.level = new Level(level, levels);
- this.spawner = new Spawner(this);
- this.snakes = [];
- this._roundEnded = false;
- this._tickBound = this._tick.bind(this);
- }
- // module.exports = Game;
- Game.prototype = {
- // Max allowed client-server delta
- MAX_DELTA_ALLOWED: 4,
- CRASH_OBJECTS: {
- WALL : 0,
- SELF : 1,
- OPPONENT: 2
- },
- countdown: function() {
- var delay = config.TIME_COUNTDOWN_FROM * 1000;
- this._gameStartTimer = setTimeout(this.start.bind(this), delay);
- this.room.emit(events.CLIENT_GAME_COUNTDOWN, null);
- this._setupClients();
- },
- start: function() {
- console.log('___ NEW ROUND IN ROOM ' + this.room.id + ' ___');
- this.room.emit(events.CLIENT_GAME_START, []);
- this.room.inProgress = true;
- this.server.ticker.addListener('tick', this._tickBound);
- var respawnAfter = config.TIME_RESPAWN_APPLE * 1000;
- this.spawner.spawn(this.spawner.APPLE, null, true, respawnAfter);
- this._delaySpawnPowerup();
- },
- destruct: function() {
- var ticker = this.server.ticker;
- if (ticker.listeners('tick')) {
- ticker.removeListener('tick', this._tickBound);
- }
- clearTimeout(this._gameStartTimer);
- clearTimeout(this._powerUpTimer);
- if (this.spawner) {
- this.spawner.destruct();
- this.spawner = null;
- }
- this.snakes = null;
- this.level = null;
- },
- /**
- * @param {Client} client
- * @param {Array.<Array>} parts
- * @param {number} direction
- */
- updateSnake: function(client, parts, direction) {
- var head = parts[parts.length - 1],
- allowedDelta = this._getAllowedDelta(client);
- client.snake.direction = direction;
- // Check if server-client delta is similar enough,
- // We tolerate a small difference because of lag.
- if (Util.delta(head, client.snake.head()) <= allowedDelta) {
- client.snake.parts = parts;
- this._broadCastSnake(client);
- } else {
- head = client.snake.head();
- parts = client.snake.parts;
- this._sendServerSnakeState(client);
- }
- if (this._isCrash(client, parts)) {
- this._setSnakeCrashed(client, parts);
- } else {
- client.snake.limbo = false;
- }
- this.spawner.handleHits(client, head);
- },
- /**
- * Reverse Snake (powerup)
- * @param {number} i
- */
- reverseSnake: function(i) {
- var data, dx, dy, snake = this.snakes[i];
- dx = snake.parts[0][0] - snake.parts[1][0];
- dy = snake.parts[0][1] - snake.parts[1][1];
- if (dx !== 0) {
- snake.direction = (dx === -1) ? 0 : 2;
- } else if (dy !== 0) {
- snake.direction = (dy === -1) ? 1 : 3;
- }
- snake.parts.reverse();
- data = [i, snake.parts, snake.direction];
- this.room.emit(events.CLIENT_SNAKE_UPDATE, data);
- },
- /**
- * @param client
- */
- clientDisconnect: function(client) {
- this._setSnakeCrashed(client, client.snake.parts);
- },
- /**
- * @param client
- */
- emitState: function(client) {
- this.emitSnakes(client);
- this.emitSpawns(client);
- },
- /**
- * @param client
- */
- emitSnakes: function(client) {
- var data = [];
- for (var i = 0, m = this.snakes.length; i < m; i++) {
- data.push([i, this.snakes[i].parts, this.snakes[i].direction]);
- }
- client.emit(events.CLIENT_GAME_SNAKES, data);
- },
- /**
- * Spawns a sent as separate messages.
- * @param client
- */
- emitSpawns: function(client) {
- var spawner = this.spawner,
- spawns = spawner.spawns,
- data = [];
- for (var i = 0, m = spawns.length; i < m; i++) {
- var spawn = spawns[i];
- if (null !== spawn) {
- data.push([spawner.EVENTS[spawn.type], [i, spawn.location]]);
- }
- }
- client.emit(events.CLIENT_GAME_SPAWNS, data);
- },
- /**
- * @param {Client} client
- * @param {number} index
- */
- hitApple: function(client, index) {
- var clientIndex = this.room.clients.indexOf(client),
- size = client.snake.size += 3,
- score = ++this.room.points[clientIndex];
- this.room.emit(events.CLIENT_APPLE_HIT, [clientIndex, size, index]);
- this.room.emit(events.CLIENT_SNAKE_ACTION, [clientIndex, 'Nom']);
- this.room.emit(events.CLIENT_ROOM_SCORE, [clientIndex, score]);
- },
- /**
- * @param {Client} client
- * @param {number} index
- */
- hitPowerup: function(client, index) {
- var clientIndex = this.room.clients.indexOf(client);
- this.room.emit(events.CLIENT_POWERUP_HIT, [clientIndex, index]);
- return new Powerup(this, client);
- },
- /**
- * @return {Array.<number>}
- */
- getEmptyLocation: function() {
- var locations = this.spawner.locations.slice();
- for (var i = 0, m = this.snakes.length; i < m; i++) {
- var parts = this.snakes[i].parts;
- for (var ii = 0, mm = parts.length; ii < mm; ii++) {
- locations.push(parts[ii]);
- }
- }
- return this.level.getEmptyLocation(locations);
- },
- /**
- * @param {Client} client
- * @return {number}
- * @private
- */
- _getAllowedDelta: function(client) {
- var allowedDelta = Math.ceil(client.latency / client.snake.speed) + 1;
- return Math.min(allowedDelta, this.MAX_DELTA_ALLOWED);
- },
- /**
- * @private
- */
- _delaySpawnPowerup: function() {
- var i = config.TIME_SPAWN_POWERUP;
- clearTimeout(this._powerUpTimer);
- this._powerUpTimer = setTimeout(function() {
- this.spawner.spawn(this.spawner.POWERUP);
- this._delaySpawnPowerup();
- }.bind(this), Util.randomBetween(i[0] * 1000, i[1]* 1000));
- },
- /**
- * @param {Client} client
- * @private
- */
- _sendServerSnakeState: function(client) {
- var data = [
- this.room.clients.indexOf(client),
- client.snake.parts,
- client.snake.direction
- ];
- this.room.emit(events.CLIENT_SNAKE_UPDATE, data);
- },
- /**
- * @param {Client} client
- * @private
- */
- _broadCastSnake: function(client) {
- var send = [
- this.room.clients.indexOf(client),
- client.snake.parts,
- client.snake.direction
- ];
- this.room.broadcast(events.CLIENT_SNAKE_UPDATE, send, client);
- },
- /**
- * @param {Client} client
- * @param {Array.<Array>} parts
- * @return {Array.<number>}
- * @private
- */
- _isCrash: function(client, parts) {
- var eq = Util.eq,
- limbo = client.snake.limbo,
- clients = this.room.clients,
- level = this.level;
- for (var i = 0, m = parts.length; i < m; i++) {
- var part = parts[i];
- // Wall
- if (level.isWall(part[0], part[1])) {
- return [this.CRASH_OBJECTS.WALL, clients.indexOf(client)];
- }
- // Self
- if (m >= 5 && m - 1 !== i && eq(part, parts[m - 1])) {
- return [this.CRASH_OBJECTS.SELF, clients.indexOf(client)];
- }
- // Self (limbo)
- else if (limbo && m >= 5 && m - 2 !== i && eq(part, parts[m - 2])) {
- return [this.CRASH_OBJECTS.SELF, clients.indexOf(client)];
- }
- // Opponent
- for (var ii = 0, mm = clients.length; ii < mm; ii++) {
- if (client !== clients[ii]) {
- if (clients[ii].snake.hasPart(part)) {
- return [
- this.CRASH_OBJECTS.OPPONENT,
- clients.indexOf(client),
- ii
- ];
- }
- }
- }
- }
- return null;
- },
- /**
- * @param {Client} client
- * @param {Array.<Array>} parts
- * @private
- */
- _setSnakeCrashed: function(client, parts) {
- client.snake.crashed = true;
- var clientIndex = this.room.clients.indexOf(client);
- this.room.emit(events.CLIENT_SNAKE_CRASH, [clientIndex, parts]);
- this._checkRoundEnded();
- },
- /**
- * @private
- */
- _checkRoundEnded: function() {
- var clients, numcrashed, alive;
- clients = this.room.clients;
- numcrashed = 0;
- for (var i = 0, m = clients.length; i < m; i++) {
- if (clients[i].snake.crashed) {
- numcrashed++;
- } else {
- alive = clients[i];
- // Knockout system points
- this.room.emit(
- events.CLIENT_ROOM_SCORE,
- [i, this.room.points[i] += 2]
- );
- }
- }
- if (numcrashed >= clients.length -1 && !this._roundEnded) {
- this._endRound();
- }
- },
- /**
- * @private
- */
- _endRound: function() {
- this._roundEnded = true;
- this.room.emit(
- events.CLIENT_CHAT_NOTICE,
- 'New round starting in ' + config.TIME_GLOAT + ' seconds'
- );
- setTimeout(this._startNewRound.bind(this), config.TIME_GLOAT * 1000);
- },
- /**
- * @private
- */
- _startNewRound: function() {
- this.server.ticker.removeListener('tick', this._tickBound);
- this.room.newRound();
- },
- /**
- * @param {number} delta
- * @private
- */
- _tick: function(delta) {
- var clients = this.room.clients;
- for (var i = 0, m = clients.length; i < m; i++) {
- this._tickClient(clients[i], delta);
- }
- },
- /**
- * @param {Client} client
- * @param {number} delta
- * @private
- */
- _tickClient: function(client, delta) {
- var snake = client.snake;
- if (!snake.crashed) {
- if (snake.elapsed >= snake.speed) {
- snake.elapsed -= snake.speed;
- this._applyPredictedPosition(client);
- snake.trim();
- }
- snake.elapsed += delta;
- }
- },
- /**
- * @param {Snake} snake
- * @return {Array.<number>}
- * @private
- */
- _getPredictPosition: function(snake) {
- var head, shift;
- head = snake.head();
- shift = [[-1, 0], [0, -1], [1, 0], [0, 1]][snake.direction];
- return [head[0] + shift[0], head[1] + shift[1]];
- },
- /**
- * @param {Client} client
- * @private
- */
- _applyPredictedPosition: function(client) {
- var predict, predictParts, snake, crash;
- snake = client.snake;
- predict = this._getPredictPosition(snake);
- predictParts = snake.parts.slice(1);
- predictParts.push(predict);
- crash = this._isCrash(client, predictParts);
- if (crash) {
- // A snake is in limbo when the server predicts that a snake has
- // crashed. The prediction is wrong when the client made a turn
- // just in time but that message was received too late by the server
- // because of network delay. When the turn message is received by
- // the server, and it seems like the server made a wrong prediction,
- // the snake returns from limbo.
- if (snake.limbo) {
- this._emitCrashMessage(crash);
- this._setSnakeCrashed(client, snake.limbo);
- } else {
- snake.limbo = snake.parts.slice();
- }
- }
- // Apply move
- if (!snake.crashed) {
- snake.move(predict);
- }
- // Cannot hit apples and powerups when in limbo;
- // Snake is either dead or made a turn to prevent death.
- if (!snake.limbo) {
- this.spawner.handleHits(client, predict);
- }
- },
- /**
- * @param {Array.<number>} crash
- * @private
- */
- _emitCrashMessage: function(crash) {
- var message, object = crash[0];
- if (object === this.CRASH_OBJECTS.WALL) {
- message = util.format('{%d} crashed into a wall', crash[1]);
- } else if (object === this.CRASH_OBJECTS.SELF) {
- message = util.format('{%d} crashed into own tail', crash[1]);
- } else if (object === this.CRASH_OBJECTS.OPPONENT) {
- message = util.format('{%d} crashed into {%d}', crash[1], crash[2]);
- }
- this.room.emit(events.CLIENT_CHAT_NOTICE, message);
- },
- /**
- * @private
- */
- _setupClients: function() {
- var clients = this.room.clients;
- for (var i = 0, m = clients.length; i < m; i++) {
- var snake = this._spawnSnake(i);
- this.snakes[i] = snake;
- clients[i].snake = snake;
- }
- },
- /**
- * @param {number} index
- * @return {Snake}
- * @private
- */
- _spawnSnake: function(index) {
- var spawn, direction, snake, size, speed;
- spawn = this.level.getSpawn(index);
- direction = this.level.getSpawnDirection(index);
- size = config.SNAKE_SIZE;
- speed = config.SNAKE_SPEED;
- snake = new Snake(spawn, direction, size, speed);
- snake.elapsed = 0;
- return snake;
- }
- };
- // ALREADY INLINED: events.js
- // ALREADY INLINED: config.js
- /**
- * @param {Server} server
- * @param {number} id
- * @param {Object} filter
- * @constructor
- */
- function Room(server, id, filter) {
- this.server = server;
- this.id = id;
- this.clients = [];
- this.points = [];
- this.inProgress = false;
- this.pub = !!filter['public'];
- this.friendly = !!filter['friendly'];
- this.capacity = config.ROOM_CAPACITY;
- this.level = 0;
- this.game = new Game(this, this.level);
- this._disconnected = [];
- }
- // module.exports = Room;
- Room.prototype = {
- destruct: function() {
- this.game.destruct();
- this.game = null;
- this.clients = null;
- },
- emitState: function() {
- var names = this.names();
- for (var i = 0, m = this.clients.length; i < m; i++) {
- var data = [i, this.level, names, this.points];
- this.clients[i].emit(events.CLIENT_ROOM_INDEX, data);
- }
- },
- /**
- * @param {Client} client
- * @return {Room}
- */
- join: function(client) {
- var index = this.clients.push(client) - 1;
- client.socket.join(this.id);
- client.roomid = this.id;
- this.points[index] = 0;
- this.emitState();
- this.broadcast(events.CLIENT_CHAT_NOTICE, '{' + index + '} joined', client);
- if (this.isFull()) {
- this.game.countdown();
- }
- return this;
- },
- /**
- * @param {Client} client
- */
- disconnect: function(client) {
- var index = this.clients.indexOf(client);
- // Leave during game, clean up after round ends
- if (this.inProgress) {
- this.game.clientDisconnect(client);
- this._disconnected.push(client);
- }
- // Leave before game, clean up immediately
- else {
- this.clients.splice(index, 1);
- this.emitState();
- if (!this.clients.length) {
- this.server.roomManager.remove(this);
- }
- }
- this.emit(events.CLIENT_CHAT_NOTICE, '{' + index + '} left');
- },
- /**
- * @return {Game}
- */
- newRound: function() {
- // Before round starts
- this.game.destruct();
- this._removeDisconnectedClients(this._disconnected);
- // Check if Room was destructed
- if (!this.clients.length) {
- this.server.roomManager.remove(this);
- return null;
- }
- // Round start
- this.game = new Game(this, this.level);
- this.emitState();
- this.game.countdown();
- return this.game;
- },
- /**
- * @return {boolean}
- */
- isFull: function() {
- return (this.clients.length === this.capacity);
- },
- /**
- * @return {Array.<string>}
- */
- names: function() {
- var names = [];
- for (var i = 0, m = this.clients.length; i < m; i++) {
- names.push(this.clients[i].name);
- }
- return names;
- },
- /**
- * Send data to everyone in the room.
- * @param {string} name
- * @param {*} data
- */
- emit: function(name, data) {
- this.server.io.sockets.in(this.id).emit(name, data);
- },
- /**
- * Send data to everyone else in the room.
- * @param {string} name
- * @param {*} data
- * @param {Client} exclude
- */
- broadcast: function(name, data, exclude) {
- exclude.socket.broadcast.to(this.id).emit(name, data);
- },
- /**
- * @param {Array.<Client>} clients
- * @private
- */
- _removeDisconnectedClients: function(clients) {
- if (clients) {
- for (var i = 0, m = clients.length; i < m; i++) {
- this.clients.splice(this.clients.indexOf(clients[i]), 1);
- this.server.removeClient(clients[i]);
- }
- }
- this._disconnected = [];
- }
- };
- /**
- * @constructor
- */
- function RoomManager(server) {
- this.server = server;
- this.inc = 0;
- this.rooms = {};
- }
- // module.exports = RoomManager;
- RoomManager.prototype = {
- /**
- * @param {number} id
- * @return {Room}
- */
- room: function(id) {
- return this.rooms[id];
- },
- /**
- * @param {Room} room
- */
- remove: function(room) {
- delete this.rooms[room.id];
- room.destruct();
- },
- /**
- * @param {Object.<string, boolean>} filter
- * @return {Room}
- */
- getPreferredRoom: function(filter) {
- var room = this._findRoom(filter);
- if (!room) {
- room = this.createRoom(filter);
- }
- return room;
- },
- /**
- * @param {Object.<string, boolean>} filter
- * @return {Room}
- */
- createRoom: function(filter) {
- var id, room;
- id = ++this.inc;
- room = new Room(this.server, id, filter);
- this.rooms[room.id] = room;
- return room;
- },
- /**
- * @param {Object.<string, boolean>} filter
- * @return {?Room}
- * @private
- */
- _findRoom: function(filter) {
- for (var k in this.rooms) {
- if (this.rooms.hasOwnProperty(k)) {
- var room = this.rooms[k];
- if (this._isFilterMatch(filter, room)) {
- return room;
- }
- }
- }
- return null;
- },
- /**
- * @param {Object.<string, boolean>} filter
- * @param {Room} room
- * @return {boolean}
- * @private
- */
- _isFilterMatch: function(filter, room) {
- var eqFriendly = room.friendly === filter.friendly;
- return (room.pub && eqFriendly && !room.isFull() && !room.inProgress);
- }
- };
- // INLINED: client.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- /**
- * @param {number} id
- * @param {Server} server
- * @param {EventEmitter} socket
- * @constructor
- */
- function Client(id, server, socket) {
- this.server = server;
- this.id = id;
- this.socket = socket;
- this.latency = 0;
- /** @type {?string} */
- this.name = null;
- /** @type {Snake} */
- this.snake = null;
- /** @type {?number} */
- this.roomid = null;
- /** @type {EventHandler} */
- this.eventHandler = null;
- /** @type {boolean} */
- this.limbo = false;
- }
- // module.exports = Client;
- Client.prototype = {
- /**
- * Send data to client
- * @param {string} name
- * @param {*} data
- */
- emit: function(name, data) {
- this.socket.emit(name, data);
- },
- destruct: function() {
- this.eventHandler.destruct();
- this.eventHandler = null;
- this.snake = null;
- this.socket = null;
- }
- };
- // INLINED: ticker.js
- /*jshint globalstrict:true, es5:true, node:true, sub:true*/
- // CORE MODULE: util
- // Prevent name collision with shared/events.js
- // CORE MODULE: events
- /**
- * @param {number} tick
- * @extends {EventEmitter}
- * @constructor
- */
- function Ticker(tick) {
- this._time = +new Date();
- setInterval(this.tick.bind(this), tick);
- }
- util.inherits(Ticker, nodeEvents.EventEmitter);
- // module.exports = Ticker;
- Ticker.prototype.tick = function() {
- var now = +new Date(),
- elapsed = now - this._time;
- this.emit('tick', elapsed);
- this._time = new Date();
- };
- /**
- * @constructor
- */
- function Server() {
- /** @typedef {number} */
- this.inc = 0;
- /** @typedef {Object.<number, {Client}>} */
- this.clients = {};
- this.ticker = new Ticker(50);
- this.ticker.setMaxListeners(0);
- this.roomManager = new RoomManager(this);
- this.listen(config.SERVER_PORT);
- }
- // module.exports = Server;
- Server.prototype = {
- /**
- * @param {number} port
- */
- listen: function(port) {
- var server = http.createServer();
- /** @type {Manager} */
- this.io = socketio.listen(server, {log: false});
- this.io.set('browser client etag', true);
- this.io.set('browser client gzip', true);
- this.io.set('browser client minification', true);
- this.io.set('transports', ['websocket']);
- this.io.set('close timeout', 10);
- this.io.set('heartbeat timeout ', 10);
- this.io.sockets.on('connection', this.addClient.bind(this));
- server.listen(port);
- },
- /**
- * @param {EventEmitter} socket
- */
- addClient: function(socket) {
- var client, id = ++this.inc;
- client = new Client(id, this, socket);
- client.eventHandler = new EventHandler(this, client, socket);
- this.clients[id] = client;
- },
- /**
- * @param {Client} client
- */
- removeClient: function(client) {
- client.destruct();
- delete this.clients[client.id];
- }
- };
- var server = new Server();
- console.log('XSSNAKE server is running...');
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement