├── .gitignore ├── README.md ├── game-server ├── app.js ├── app │ ├── components │ │ ├── botComponent.js │ │ ├── stateComponent.js │ │ └── tableComponent.js │ ├── game │ │ ├── game.js │ │ ├── hand.js │ │ ├── player.js │ │ └── table.js │ ├── module │ │ └── timeReport.js │ ├── persistence │ │ ├── tables.js │ │ └── users.js │ ├── servers │ │ ├── chat │ │ │ ├── handler │ │ │ │ └── chatHandler.js │ │ │ └── remote │ │ │ │ └── chatRemote.js │ │ ├── connector │ │ │ └── handler │ │ │ │ └── entryHandler.js │ │ ├── game │ │ │ ├── filter │ │ │ │ └── abuseFilter.js │ │ │ ├── handler │ │ │ │ ├── tableHandler.js │ │ │ │ └── userHandler.js │ │ │ └── remote │ │ │ │ ├── authRemote.js │ │ │ │ └── tableRemote.js │ │ └── gate │ │ │ └── handler │ │ │ └── gateHandler.js │ ├── services │ │ ├── botService.js │ │ ├── chatService.js │ │ ├── stateService.js │ │ └── tableService.js │ └── util │ │ ├── dispatcher.js │ │ └── routeUtil.js ├── config │ ├── adminServer.json │ ├── adminUser.json │ ├── bots.json │ ├── gameSettings.json │ ├── log4js.json │ ├── master.json │ ├── redis.json │ └── servers.json ├── localstore │ ├── tables.json │ └── users.json ├── logs │ └── .gitignore └── package.json ├── npm-install.bat ├── npm-install.sh ├── shared ├── config │ ├── authCodes.json │ └── session.json ├── server.crt ├── server.key └── token.js └── web-server ├── app.js ├── bin ├── component.bat └── component.sh ├── package.json └── public ├── css ├── bootstrap.min.css └── styles.css ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff ├── images └── cards │ ├── 10_of_clubs.png │ ├── 10_of_diamonds.png │ ├── 10_of_hearts.png │ ├── 10_of_spades.png │ ├── 2_of_clubs.png │ ├── 2_of_diamonds.png │ ├── 2_of_hearts.png │ ├── 2_of_spades.png │ ├── 3_of_clubs.png │ ├── 3_of_diamonds.png │ ├── 3_of_hearts.png │ ├── 3_of_spades.png │ ├── 4_of_clubs.png │ ├── 4_of_diamonds.png │ ├── 4_of_hearts.png │ ├── 4_of_spades.png │ ├── 5_of_clubs.png │ ├── 5_of_diamonds.png │ ├── 5_of_hearts.png │ ├── 5_of_spades.png │ ├── 6_of_clubs.png │ ├── 6_of_diamonds.png │ ├── 6_of_hearts.png │ ├── 6_of_spades.png │ ├── 7_of_clubs.png │ ├── 7_of_diamonds.png │ ├── 7_of_hearts.png │ ├── 7_of_spades.png │ ├── 8_of_clubs.png │ ├── 8_of_diamonds.png │ ├── 8_of_hearts.png │ ├── 8_of_spades.png │ ├── 9_of_clubs.png │ ├── 9_of_diamonds.png │ ├── 9_of_hearts.png │ ├── 9_of_spades.png │ ├── ace_of_clubs.png │ ├── ace_of_diamonds.png │ ├── ace_of_hearts.png │ ├── ace_of_spades.png │ ├── ace_of_spades2.png │ ├── black_joker.png │ ├── jack_of_clubs.png │ ├── jack_of_clubs2.png │ ├── jack_of_diamonds.png │ ├── jack_of_diamonds2.png │ ├── jack_of_hearts.png │ ├── jack_of_hearts2.png │ ├── jack_of_spades.png │ ├── jack_of_spades2.png │ ├── king_of_clubs.png │ ├── king_of_clubs2.png │ ├── king_of_diamonds.png │ ├── king_of_diamonds2.png │ ├── king_of_hearts.png │ ├── king_of_hearts2.png │ ├── king_of_spades.png │ ├── king_of_spades2.png │ ├── queen_of_clubs.png │ ├── queen_of_clubs2.png │ ├── queen_of_diamonds.png │ ├── queen_of_diamonds2.png │ ├── queen_of_hearts.png │ ├── queen_of_hearts2.png │ ├── queen_of_spades.png │ ├── queen_of_spades2.png │ └── red_joker.png ├── index.html └── js ├── collections ├── MessageCollection.js └── UserCollection.js ├── desktop.js ├── libs ├── backbone-min.js ├── bootstrap.min.js ├── jquery.js ├── pomeloclient.js ├── require.js ├── resources.js ├── socket.io.js └── underscore.js ├── models ├── MessageModel.js └── UserModel.js ├── routers └── desktopRouter.js └── views ├── AlertConfirmView.js ├── AlertErrorView.js ├── AlertGeneralView.js ├── GlobalView.js ├── LoginView.js ├── ProfileView.js ├── RegisterView.js ├── TableListView.js └── TableView.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .idea 3 | node_modules 4 | game-server/logs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## node-poker-stack -- a node.js Texas Holdem game server and web client 2 | 3 | node-poker-stack is a [node.js](http://nodejs.org) Texas Holdem game server and web client. Some notable features 4 | are real-time game-play and chat, multiple game rooms, support up to 10 players per room with a combination 5 | of human and bot players. 6 | 7 | ## Features 8 | 9 | ### Game Features 10 | 11 | * Texas Holdem game engine for up to 10 players per room based on [node-poker](https://github.com/mjhbell/node-poker). 12 | * Configure bots to join at intervals and play against humans or other bots. They will make use of a hand evaluator, and were very useful for debugging game logic. 13 | * Multiple simultaneous game rooms with individual game rules (blinds, buyins, # of players, etc). 14 | * Real-time game and chat interaction between clients via web sockets. 15 | * Robust game records are stored which include each player action and along with game results. 16 | * Rudimentary friend system to check whether friends are online, chat, and join their games. 17 | * A basic web client server is available (node.js + backbone.js + websocket web browser client). 18 | 19 | ### Built Using Pomelo 20 | 21 | * Real-time communication between server and client. 22 | * Distributed architecture to scale painlessly. 23 | * Pluggable architecture to easily add new features. 24 | * SDKs for a variety of clients (javascript, flash, android, iOS, cocos2d-x, C). 25 | * See [Pomelo Framework](http://github.com/NetEase/pomelo) for more information. 26 | 27 | ### Whats Missing? 28 | 29 | * User and table data is persisted to file store rather than database. 30 | * Web browser client could be improved (uses vanilla bootstrap.js). 31 | * Add more client platforms (android, ios, phonegap, etc). 32 | 33 | ## Instructions 34 | 35 | 1. git clone https://github.com/vampserv/node-poker-stack.git 36 | 2. ./npm-install.sh 37 | 3. node game-server/app 38 | 4. open another terminal window 39 | 5. node web-server/app 40 | 6. go to http://localhost:3002 to access web client 41 | 7. register a new user and login 42 | 8. create a game room, join the game, and wait 43 | 9. bots will join the game to play 44 | 45 | 46 | ## License 47 | 48 | (The MIT License) 49 | 50 | Copyright (c) 2012-2014 Edward Yang 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining 53 | a copy of this software and associated documentation files (the 54 | 'Software'), to deal in the Software without restriction, including 55 | without limitation the rights to use, copy, modify, merge, publish, 56 | distribute, sublicense, and/or sell copies of the Software, and to 57 | permit persons to whom the Software is furnished to do so, subject to 58 | the following conditions: 59 | 60 | The above copyright notice and this permission notice shall be 61 | included in all copies or substantial portions of the Software. 62 | 63 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 64 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 65 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 66 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 67 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 68 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 69 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 70 | -------------------------------------------------------------------------------- /game-server/app.js: -------------------------------------------------------------------------------- 1 | var pomelo = require('pomelo'); 2 | var routeUtil = require('./app/util/routeUtil'); 3 | var abuseFilter = require('./app/servers/game/filter/abuseFilter'); 4 | var tableComponent = require('./app/components/tableComponent'); 5 | var botComponent = require('./app/components/botComponent'); 6 | var stateComponent = require('./app/components/stateComponent'); 7 | var ChatService = require('./app/services/chatService'); 8 | 9 | var app = pomelo.createApp(); 10 | app.set('name', 'poker-game-stack'); 11 | 12 | app.configure('production|development', function(){ 13 | app.route('game', routeUtil.game); 14 | app.filter(pomelo.timeout()); 15 | app.set('session', require('../shared/config/session.json')); 16 | }); 17 | 18 | app.configure('production|development', 'game', function(){ 19 | app.filter(abuseFilter()); 20 | app.load(tableComponent); 21 | app.load(botComponent); 22 | // app.load(stateComponent); 23 | }); 24 | 25 | app.configure('production|development', 'chat', function(){ 26 | app.set('chatService', new ChatService(app)); 27 | }); 28 | 29 | //var timeReport = require('./app/module/timeReport'); 30 | //app.registerAdmin(timeReport, {app: app}); 31 | 32 | app.start(); 33 | 34 | process.on('uncaughtException', function(err){ 35 | console.error('Caught exception: ' + err.stack); 36 | }); -------------------------------------------------------------------------------- /game-server/app/components/botComponent.js: -------------------------------------------------------------------------------- 1 | var BotService = require('../services/botService'); 2 | 3 | module.exports = function(app, opts){ 4 | var service = new BotService(app, opts); 5 | app.set('botService', service, true); 6 | service.name = '__bot__'; 7 | return service; 8 | }; -------------------------------------------------------------------------------- /game-server/app/components/stateComponent.js: -------------------------------------------------------------------------------- 1 | var BotService = require('../services/stateService'); 2 | 3 | module.exports = function(app, opts){ 4 | var service = new StateService(app, opts); 5 | app.set('stateService', service, true); 6 | service.name = '__state__'; 7 | return service; 8 | }; -------------------------------------------------------------------------------- /game-server/app/components/tableComponent.js: -------------------------------------------------------------------------------- 1 | var TableService = require('../services/tableService'); 2 | 3 | module.exports = function(app, opts){ 4 | var service = new TableService(app, opts); 5 | app.set('tableService', service, true); 6 | service.name = '__table__'; 7 | return service; 8 | }; -------------------------------------------------------------------------------- /game-server/app/game/game.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('game-log', __filename); 2 | var uuid = require('node-uuid'); 3 | 4 | 5 | 6 | module.exports = Game = function(smallBlind, bigBlind){ 7 | this.id = uuid.v1(); 8 | this.smallBlind = smallBlind; 9 | this.bigBlind = bigBlind; 10 | this.pot = 0; 11 | this.roundName = 'Deal'; //Start the first round 12 | this.betName = 'bet'; //bet,raise,re-raise,cap 13 | this.bets = []; 14 | this.roundBets = []; 15 | this.deck = []; 16 | this.board = []; 17 | fillDeck(this.deck); 18 | } 19 | 20 | function fillDeck(deck){ 21 | deck.push('AS'); 22 | deck.push('KS'); 23 | deck.push('QS'); 24 | deck.push('JS'); 25 | deck.push('TS'); 26 | deck.push('9S'); 27 | deck.push('8S'); 28 | deck.push('7S'); 29 | deck.push('6S'); 30 | deck.push('5S'); 31 | deck.push('4S'); 32 | deck.push('3S'); 33 | deck.push('2S'); 34 | deck.push('AH'); 35 | deck.push('KH'); 36 | deck.push('QH'); 37 | deck.push('JH'); 38 | deck.push('TH'); 39 | deck.push('9H'); 40 | deck.push('8H'); 41 | deck.push('7H'); 42 | deck.push('6H'); 43 | deck.push('5H'); 44 | deck.push('4H'); 45 | deck.push('3H'); 46 | deck.push('2H'); 47 | deck.push('AD'); 48 | deck.push('KD'); 49 | deck.push('QD'); 50 | deck.push('JD'); 51 | deck.push('TD'); 52 | deck.push('9D'); 53 | deck.push('8D'); 54 | deck.push('7D'); 55 | deck.push('6D'); 56 | deck.push('5D'); 57 | deck.push('4D'); 58 | deck.push('3D'); 59 | deck.push('2D'); 60 | deck.push('AC'); 61 | deck.push('KC'); 62 | deck.push('QC'); 63 | deck.push('JC'); 64 | deck.push('TC'); 65 | deck.push('9C'); 66 | deck.push('8C'); 67 | deck.push('7C'); 68 | deck.push('6C'); 69 | deck.push('5C'); 70 | deck.push('4C'); 71 | deck.push('3C'); 72 | deck.push('2C'); 73 | 74 | //Shuffle the deck array with Fisher-Yates 75 | var i, j, tempi, tempj; 76 | for(i=0;i< deck.length;i+=1){ 77 | j = Math.floor(Math.random() * (i + 1)); 78 | tempi = deck[i]; 79 | tempj = deck[j]; 80 | deck[i] = tempj; 81 | deck[j] = tempi; 82 | } 83 | } -------------------------------------------------------------------------------- /game-server/app/game/player.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('game-log', __filename); 2 | var Hand = require('./hand'); 3 | 4 | 5 | module.exports = Player = function(playerName, chips, uid, table){ 6 | this.playerName = playerName; 7 | this.id = uid; 8 | this.chips = chips; 9 | this.folded = false; 10 | this.allIn = false; 11 | this.talked = false; 12 | this.table = table; //Circular reference to allow reference back to parent object. 13 | this.cards = []; 14 | } 15 | 16 | Player.prototype.GetChips = function(cash){ 17 | this.chips += cash; 18 | }; 19 | 20 | Player.prototype.Check = function(){ 21 | var checkAllow, v, i; 22 | checkAllow = true; 23 | for(v=0;v totalBet){ 70 | this.table.players[i].chips -= (totalBet - currentBet); 71 | this.table.game.bets[i] = totalBet; // this.table.game.bets[i] += totalBet; 72 | this.talked = true; 73 | // attempt to progress the game 74 | this.turnBet = {action: "bet", playerName: this.playerName, amount: totalBet}; 75 | this.table.actions.push(this.turnBet); 76 | progress(this.table); 77 | }else{ 78 | logger.debug('forced-all-in: '+this.id); 79 | this.AllIn(); 80 | } 81 | }; 82 | 83 | Player.prototype.getIndex = function(){ 84 | var index; 85 | for(i=0;i< this.table.players.length;i+=1){ 86 | if(this === this.table.players[i]){ 87 | index = i; 88 | } 89 | } 90 | return index; 91 | } 92 | 93 | Player.prototype.Call = function(){ 94 | var maxBet, i; 95 | // console.log('bets', this.table.game.bets); 96 | maxBet = getMaxBet(this.table.game.bets); 97 | if(this.chips > maxBet){ 98 | // console.log('higher chips', this.chips, maxBet); 99 | //Match the highest bet 100 | for(i=0;i< this.table.players.length;i+=1){ 101 | if(this === this.table.players[i]){ 102 | if(this.table.game.bets[i] >= 0){ 103 | this.chips += this.table.game.bets[i]; 104 | } 105 | this.chips -= maxBet; 106 | this.table.game.bets[i] = maxBet; 107 | this.talked = true; 108 | } 109 | } 110 | // attempt to progress the game 111 | this.turnBet = {action: "call", playerName: this.playerName, amount: maxBet}; 112 | this.table.actions.push(this.turnBet); 113 | progress(this.table); 114 | }else{ 115 | logger.debug('forced-all-in: '+this.id); 116 | this.AllIn(); 117 | } 118 | }; 119 | 120 | Player.prototype.AllIn = function(){ 121 | var i, allInValue=0; 122 | for(i=0;i< this.table.players.length;i+=1){ 123 | if(this === this.table.players[i]){ 124 | if(this.table.players[i].chips !== 0){ 125 | allInValue = this.table.players[i].chips; 126 | this.table.game.bets[i] += this.table.players[i].chips; 127 | this.table.players[i].chips = 0; 128 | 129 | this.allIn = true; 130 | this.talked = true; 131 | } 132 | } 133 | } 134 | 135 | // attempt to progress the game 136 | this.turnBet = {action: "allin", playerName: this.playerName, amount: allInValue}; 137 | this.table.actions.push(this.turnBet); 138 | 139 | progress(this.table); 140 | }; 141 | 142 | function progress(table){ 143 | var i, j, cards, hand; 144 | if(table.game){ 145 | table.eventEmitter.emit("turnEnd"); 146 | table.stopTimer(); 147 | if(checkForEndOfGame(table)){ 148 | //Move all bets to the pot 149 | for(i=0;i< table.game.bets.length;i+=1){ 150 | table.game.pot += parseInt(table.game.bets[i], 10); 151 | table.game.roundBets[i] += parseInt(table.game.bets[i], 10); 152 | } 153 | completeGame(table); 154 | return; 155 | } 156 | if(checkForEndOfRound(table) === true){ 157 | //Move all bets to the pot 158 | for(i=0;i< table.game.bets.length;i+=1){ 159 | table.game.pot += parseInt(table.game.bets[i], 10); 160 | table.game.roundBets[i] += parseInt(table.game.bets[i], 10); 161 | } 162 | if(table.game.roundName === 'River'){ 163 | completeGame(table); 164 | return; 165 | }else if(table.game.roundName === 'Turn'){ 166 | table.game.roundName = 'River'; 167 | table.game.deck.pop(); //Burn a card 168 | table.game.board.push(table.game.deck.pop()); //Turn a card 169 | //table.game.bets.splice(0,table.game.bets.length-1); 170 | for(i=0;i< table.game.bets.length;i+=1){ 171 | table.game.bets[i] = 0; 172 | } 173 | for(i=0;i< table.players.length;i+=1){ 174 | table.players[i].talked = false; 175 | } 176 | table.eventEmitter.emit( "deal" ); 177 | }else if(table.game.roundName === 'Flop'){ 178 | table.game.roundName = 'Turn'; 179 | table.game.deck.pop(); //Burn a card 180 | table.game.board.push(table.game.deck.pop()); //Turn a card 181 | for(i=0;i< table.game.bets.length;i+=1){ 182 | table.game.bets[i] = 0; 183 | } 184 | for(i=0;i< table.players.length;i+=1){ 185 | table.players[i].talked = false; 186 | } 187 | table.eventEmitter.emit( "deal" ); 188 | }else if(table.game.roundName === 'Deal'){ 189 | table.game.roundName = 'Flop'; 190 | table.game.deck.pop(); //Burn a card 191 | for(i=0;i< 3;i+=1){ //Turn three cards 192 | table.game.board.push(table.game.deck.pop()); 193 | } 194 | //table.game.bets.splice(0,table.game.bets.length-1); 195 | for(i=0;i< table.game.bets.length;i+=1){ 196 | table.game.bets[i] = 0; 197 | } 198 | for(i=0;i< table.players.length;i+=1){ 199 | table.players[i].talked = false; 200 | } 201 | table.eventEmitter.emit( "deal" ); 202 | } 203 | table.currentPlayer = getNextAvailablePlayer(table, table.startIndex, table.players.length); 204 | if(typeof table.currentPlayer !== 'number' && table.game.roundName !== 'GameEnd'){ 205 | console.log('ALL IN GAME'); 206 | completeBoard(table); 207 | return progress(table); 208 | } 209 | } 210 | table.eventEmitter.emit("turnStart"); 211 | } 212 | } 213 | 214 | function completeGame(table){ 215 | table.game.roundName = 'GameEnd'; 216 | table.game.bets.splice(0, table.game.bets.length); 217 | //Evaluate each hand 218 | for(j = 0; j < table.players.length; j += 1){ 219 | cards = table.players[j].cards.concat(table.game.board); 220 | table.players[j].hand = new Hand(cards); 221 | } 222 | console.log('checkForWinner', table); 223 | checkForWinner(table); 224 | table.eventEmitter.emit('gameOver'); 225 | } 226 | 227 | function completeBoard(table){ 228 | var i; 229 | if(table.game.board.length == 0){ 230 | table.game.deck.pop(); 231 | for(i=0;i<3;i+=1){ 232 | table.game.board.push(table.game.deck.pop()); 233 | } 234 | } 235 | if(table.game.board.length == 3){ 236 | table.game.deck.pop(); 237 | table.game.board.push(table.game.deck.pop()); 238 | } 239 | if(table.game.board.length == 4){ 240 | table.game.deck.pop(); 241 | table.game.board.push(table.game.deck.pop()); 242 | } 243 | } 244 | 245 | function getMaxBet(bets){ 246 | var maxBet, i; 247 | maxBet = 0; 248 | for(i=0;i< bets.length;i+=1){ 249 | if(bets[i] > maxBet){ 250 | maxBet = bets[i]; 251 | } 252 | } 253 | return maxBet; 254 | } 255 | 256 | function checkForEndOfGame(table){ 257 | var notFolded = [], allInPlayer = [], actionablePlayer = [], i; 258 | for(i = 0; i < table.players.length;i+=1){ 259 | if(table.players[i].folded === false){ 260 | notFolded.push(i); 261 | } 262 | if(table.players[i].allIn === true){ 263 | allInPlayer.push(i); 264 | } 265 | if(table.players[i].folded === false && table.players[i].allIn === false){ 266 | actionablePlayer.push(i); 267 | } 268 | } 269 | if(allInPlayer.length > 1){ 270 | completeBoard(table); 271 | } 272 | return (notFolded.length === 1 || actionablePlayer.length === 0); 273 | } 274 | 275 | function checkForEndOfRound(table){ 276 | var endOfRound = true; 277 | var nextPlayer = getNextAvailablePlayer(table, (table.currentPlayer + 1), table.players.length); 278 | if(typeof nextPlayer === 'number'){ 279 | table.currentPlayer = nextPlayer; 280 | endOfRound = false; 281 | } 282 | return endOfRound; 283 | } 284 | 285 | function getNextAvailablePlayer(table, playerIndex, len, ctr){ 286 | ctr = ctr || 0; 287 | var maxBet = getMaxBet(table.game.bets); 288 | var nextPlayer; 289 | if(playerIndex === len){ 290 | playerIndex = 0; 291 | } 292 | if(table.players[playerIndex].folded === false && (table.players[playerIndex].talked === false || table.game.bets[playerIndex] !== maxBet) && table.players[playerIndex].allIn === false){ 293 | nextPlayer = playerIndex; 294 | } 295 | ctr += 1; 296 | playerIndex += 1; 297 | if(typeof nextPlayer !== 'number' && ctr !== len){ 298 | nextPlayer = getNextAvailablePlayer(table, playerIndex, len, ctr); 299 | } 300 | return nextPlayer; 301 | } 302 | 303 | function checkForAllInPlayer(table, winners){ 304 | var i, allInPlayer; 305 | allInPlayer = []; 306 | for(i=0;i< winners.length;i+=1){ 307 | if(table.players[winners[i]].allIn === true){ 308 | allInPlayer.push(winners[i]); 309 | } 310 | } 311 | return allInPlayer; 312 | } 313 | 314 | function checkForWinner(table){ 315 | var i, j, k, l, maxRank, notFolded, winners, part, prize, allInPlayer, minBets, roundEnd; 316 | //Identify winner(s) 317 | winners = []; 318 | notFolded = []; 319 | maxRank = 0.000; 320 | for(k = 0; k < table.players.length; k += 1){ 321 | if(table.players[k].folded === false){ 322 | notFolded.push(k); 323 | } 324 | if(table.players[k].hand.rank === maxRank && table.players[k].folded === false){ 325 | winners.push(k); 326 | } 327 | if(table.players[k].hand.rank > maxRank && table.players[k].folded === false){ 328 | maxRank = table.players[k].hand.rank; 329 | winners.splice(0, winners.length); 330 | winners.push(k); 331 | } 332 | } 333 | // handle mid-round fold 334 | if(winners.length === 0 && notFolded.length == 1){ 335 | console.log('mid round fold'); 336 | winners.push(notFolded[0]); 337 | } 338 | part = 0; 339 | prize = 0; 340 | console.log('roundBets', table.game.roundBets); 341 | allInPlayer = checkForAllInPlayer(table, winners); 342 | if(allInPlayer.length > 0){ 343 | minBets = table.game.roundBets[winners[0]]; 344 | for(j = 1; j < allInPlayer.length; j += 1){ 345 | if(table.game.roundBets[winners[j]] !== 0 && table.game.roundBets[winners[j]] < minBets){ 346 | minBets = table.game.roundBets[winners[j]]; 347 | } 348 | } 349 | part = parseInt(minBets, 10); 350 | }else{ 351 | part = parseInt(table.game.roundBets[winners[0]], 10); 352 | } 353 | console.log('part', part); 354 | for(l = 0; l < table.game.roundBets.length; l += 1){ 355 | // handle user leave 356 | // console.log('more than 1 player', table.players.length > 1 && (table.players.length - table.playersToRemove.length > 1)); 357 | if(table.game.roundBets[l] > part){ 358 | prize += part; 359 | table.game.roundBets[l] -= part; 360 | }else{ 361 | prize += table.game.roundBets[l]; 362 | table.game.roundBets[l] = 0; 363 | } 364 | } 365 | console.log('prize', prize); 366 | console.log('winners', winners); 367 | 368 | if(prize > 0){ 369 | var remainder = prize % winners.length; 370 | var winnerHands = []; 371 | var highestIndex; 372 | var winnerPrize = Math.floor(prize / winners.length); 373 | if(remainder !== 0){ 374 | console.log('chip remainder of '+remainder); 375 | for(i=0;i 10){ //hard limit of 10 players at a table. 46 | err = new Error(102, 'Parameter [maxPlayers] must be a positive integer less than or equal to 10.'); 47 | }else if(minPlayers > maxPlayers){ //Without this we can never start a game! 48 | err = new Error(103, 'Parameter [minPlayers] must be less than or equal to [maxPlayers].'); 49 | } 50 | if(err){ 51 | return err; 52 | } 53 | } 54 | 55 | Table.prototype.initNewGame = function(){ 56 | var i; 57 | this.instance.state = 'JOIN'; 58 | this.dealer += 1; 59 | if(this.dealer >= this.players.length){ 60 | this.dealer = 0; 61 | } 62 | delete this.game; 63 | this.previousPlayers = []; 64 | // add existing players and remove players who left or are bankrupt 65 | for(i=0;i= this.minBuyIn && chips <= this.maxBuyIn){ 89 | var player = new Player(playerName, chips, uid, this); 90 | this.playersToAdd.push(player); 91 | } 92 | }; 93 | Table.prototype.removePlayer = function(pid){ 94 | for(var i in this.players ){ 95 | if(this.players[i].id === pid){ 96 | this.playersToRemove.push( parseInt(i) ); 97 | this.players[i].Fold(); 98 | } 99 | } 100 | for(var i in this.playersToAdd ){ 101 | if(this.playersToAdd[i].id === pid){ 102 | this.playersToAdd.splice(i, 1); 103 | } 104 | } 105 | for(var i in this.members ){ 106 | if(this.members[i].id === pid){ 107 | this.members.splice(i, 1); 108 | } 109 | } 110 | for(var i in this.previousPlayers){ 111 | if(this.previousPlayers[i].id === pid){ 112 | this.previousPlayers.splice(i, 1); 113 | } 114 | } 115 | this.eventEmitter.emit("playerLeft"); 116 | } 117 | Table.prototype.NewRound = function(){ 118 | var removeIndex = 0; 119 | for(var i in this.playersToAdd){ 120 | // if(removeIndex < this.playersToRemove.length){ 121 | // var index = this.playersToRemove[removeIndex]; 122 | // this.players[index] = this.playersToAdd[i]; 123 | // removeIndex += 1; 124 | // }else{ 125 | // this.players.push(this.playersToAdd[i]); 126 | // } 127 | this.players.push(this.playersToAdd[i]); 128 | } 129 | this.playersToRemove = []; 130 | this.playersToAdd = []; 131 | this.gameWinners = []; 132 | this.gameLosers = []; 133 | 134 | 135 | var i, smallBlind, bigBlind; 136 | //Deal 2 cards to each player 137 | for(i=0;i< this.players.length;i+=1){ 138 | this.players[i].cards.push(this.game.deck.pop()); 139 | this.players[i].cards.push(this.game.deck.pop()); 140 | this.game.bets[i] = 0; 141 | this.game.roundBets[i] = 0; 142 | } 143 | //Identify Small and Big Blind player indexes 144 | smallBlind = this.dealer + 1; 145 | if(smallBlind >= this.players.length){ 146 | smallBlind = 0; 147 | } 148 | bigBlind = smallBlind + 1; 149 | if(bigBlind >= this.players.length){ 150 | bigBlind = 0; 151 | } 152 | this.currentPlayer = bigBlind + 1; 153 | if(this.currentPlayer >= this.players.length){ 154 | this.currentPlayer = 0; 155 | } 156 | this.startIndex = this.currentPlayer; 157 | //Force Blind Bets 158 | this.players[smallBlind].chips -= this.smallBlind; 159 | this.players[bigBlind].chips -= this.bigBlind; 160 | this.game.bets[smallBlind] = this.smallBlind; 161 | this.game.bets[bigBlind] = this.bigBlind; 162 | this.game.blinds = [smallBlind, bigBlind]; 163 | 164 | this.eventEmitter.emit("newRound"); 165 | }; 166 | 167 | Table.prototype.startTimer = function(){ 168 | var me = this; 169 | me.stopTimer(); 170 | me._countdown = setTimeout(function(){ 171 | if(!me.active){ 172 | return; 173 | } 174 | console.log('timer ended. executing move.'); 175 | if(me.game.bets[me.currentPlayer] < getMaxBet(me.game.bets)){ 176 | me.players[me.currentPlayer].Fold(); 177 | }else{ 178 | me.players[me.currentPlayer].Call(); 179 | } 180 | me.instance.tableService.handleGameState(me.instance.id, function(e){ 181 | if(e){ 182 | console.error(e); 183 | } 184 | }); 185 | }, (GAME_SETTINGS.gameMode[this.gameMode].timeout * 1000)); 186 | }; 187 | 188 | Table.prototype.stopTimer = function(){ 189 | if(this._countdown){ 190 | clearTimeout(this._countdown); 191 | } 192 | }; 193 | 194 | function getMaxBet(bets){ 195 | var maxBet, i; 196 | maxBet = 0; 197 | for(i=0;i< bets.length;i+=1){ 198 | if(bets[i] > maxBet){ 199 | maxBet = bets[i]; 200 | } 201 | } 202 | return maxBet; 203 | } 204 | -------------------------------------------------------------------------------- /game-server/app/module/timeReport.js: -------------------------------------------------------------------------------- 1 | module.exports = function(opts) { 2 | return new Module(opts); 3 | } 4 | 5 | var testMsg = 'a default message'; 6 | 7 | var moduleId = "timeReport"; 8 | module.exports.moduleId = moduleId; 9 | 10 | var Module = function(opts) { 11 | this.app = opts.app; 12 | this.type = opts.type || 'pull'; 13 | this.interval = opts.interval || 5; 14 | } 15 | 16 | Module.prototype.monitorHandler = function(agent, msg, cb) { 17 | console.log(this.app.getServerId() + '' + msg); 18 | var serverId = agent.id; 19 | var time = new Date(). toString(); 20 | 21 | agent.notify(moduleId, {serverId: serverId, time: time}); 22 | } 23 | 24 | Module.prototype.masterHandler = function(agent, msg) { 25 | if(! msg) { 26 | agent.notifyAll(moduleId, testMsg); 27 | return; 28 | } 29 | 30 | console.log(msg); 31 | var timeData = agent.get(moduleId); 32 | if(! timeData) { 33 | timeData = {}; 34 | agent.set(moduleId, timeData); 35 | } 36 | timeData[msg.serverId] = msg.time; 37 | } 38 | 39 | 40 | Module.prototype.clientHandler = function(agent, msg, cb) { 41 | cb(null, agent.get(moduleId)); 42 | } 43 | -------------------------------------------------------------------------------- /game-server/app/persistence/tables.js: -------------------------------------------------------------------------------- 1 | var uuid = require('node-uuid'); 2 | var util = require('util'); 3 | var fs = require('fs'); 4 | 5 | var TableStore = module.exports = { 6 | store : './localstore/tables.json', 7 | entity : 'tables', 8 | create : function(obj, cb){ 9 | var me = this; 10 | me.persist({ 11 | id : obj.id || uuid.v1(), 12 | tid : obj.tid, 13 | smallBlind : obj.smallBlind, 14 | bigBlind : obj.bigBlind, 15 | minBuyIn : obj.minBuyIn, 16 | maxBuyIn : obj.maxBuyIn, 17 | minPlayers : obj.minPlayers, 18 | maxPlayers : obj.maxPlayers, 19 | gameMode : obj.gameMode, 20 | board : obj.board, 21 | creator : obj.creator, 22 | players : obj.players || [], 23 | actions : obj.actions || [], 24 | gameWinners : obj.gameWinners || [], 25 | created : Date.now() 26 | }, function(e, table){ 27 | if(e){ 28 | cb(e); 29 | }else{ 30 | // console.log('TableStore: table created - ', table); 31 | cb(null, table); 32 | } 33 | }); 34 | }, 35 | set : function(obj, cb){ 36 | var me = this; 37 | me.persist(obj, function(e, table){ 38 | if(e){ 39 | cb(e); 40 | }else{ 41 | // console.log('TableStore: table modified - ', table); 42 | cb(null, table); 43 | } 44 | }); 45 | }, 46 | getByAttr : function(key, val, cb){ 47 | var matches = [], matched; 48 | this.retrieve(function(e, entities){ 49 | if(e){ 50 | return cb(e); 51 | } 52 | if(key == '*' && val == '*'){ 53 | cb(null, entities); 54 | }else{ 55 | for(var entity in entities){ 56 | if(util.isArray(key)){ 57 | matched = true; 58 | for(var i=key.length;i--;){ 59 | if(entities[entity][key[i]] != val[i]){ 60 | matched = false; 61 | } 62 | } 63 | if(matched){ 64 | matches.push(entities[entity]); 65 | } 66 | }else if(entities[entity][key] == val){ 67 | matches.push(entities[entity]); 68 | } 69 | } 70 | cb(null, matches.length == 0 ? null : (matches.length == 1 ? matches[0] : matches)); 71 | } 72 | }); 73 | }, 74 | persist : function(row, cb){ 75 | var me = this; 76 | me.retrieve(function(e, entities){ 77 | entities[row.id] = entities[row.id] || {}; 78 | for(var key in row){ 79 | entities[row.id][key] = row[key]; 80 | } 81 | fs.writeFile(me.store, JSON.stringify(entities, undefined, 4), function(e2){ 82 | if(e2){ 83 | return cb('error writing file: ' + e2); 84 | }else{ 85 | cb(null, entities[row.id]); 86 | } 87 | }); 88 | }); 89 | }, 90 | retrieve : function(cb){ 91 | fs.readFile(this.store, 'utf8', function(e, content){ 92 | if(e){ 93 | return cb('error loading file: ' + e); 94 | } 95 | if(content.trim().length == 0){ 96 | content = {}; 97 | }else{ 98 | content = JSON.parse(content); 99 | } 100 | cb(null, content); 101 | }); 102 | } 103 | }; -------------------------------------------------------------------------------- /game-server/app/persistence/users.js: -------------------------------------------------------------------------------- 1 | var uuid = require('node-uuid'); 2 | var util = require('util'); 3 | var fs = require('fs'); 4 | 5 | var UserStore = module.exports = { 6 | store : './localstore/users.json', 7 | entity : 'users', 8 | create : function(obj, callback){ 9 | var me = this; 10 | me.getByAttr('username', obj.username, false, function(e, existingUser){ 11 | if(existingUser){ 12 | callback('user-exists', existingUser); 13 | }else{ 14 | var id = uuid.v1(); 15 | me.persist({ 16 | id : id, 17 | username : obj.username, 18 | password : obj.password, 19 | email : obj.email, 20 | chips : obj.chips, 21 | wins : 0, 22 | largestWin : 0, 23 | friends : [], 24 | created : Date.now() 25 | }, function(e, user){ 26 | if(e){ 27 | callback(e); 28 | }else{ 29 | callback(null, user); 30 | } 31 | }) 32 | } 33 | }); 34 | }, 35 | set : function(obj, cb){ 36 | var me = this; 37 | me.persist(obj, function(e, entity){ 38 | if(e){ 39 | cb(e); 40 | }else{ 41 | cb(null, safetyFilter(entity)); 42 | } 43 | }); 44 | }, 45 | getByAttr : function(key, val, opts, cb){ 46 | var matches = [], matched; 47 | opts = opts || {}; 48 | this.retrieve(function(e, entities){ 49 | if(e){ 50 | return cb(e, []); 51 | } 52 | for(var entity in entities){ 53 | if(util.isArray(key)){ 54 | matched = true; 55 | for(var i=key.length;i--;){ 56 | if(entities[entity][key[i]] != val[i]){ 57 | matched = false; 58 | } 59 | } 60 | if(matched){ 61 | matches.push(opts.getFullEntity ? entities[entity] : safetyFilter(entities[entity])); 62 | } 63 | }else if(entities[entity][key] == val || (key == '*' && val == '*')){ 64 | matches.push(opts.getFullEntity ? entities[entity] : safetyFilter(entities[entity])); 65 | } 66 | } 67 | matches = opts.getArray ? matches : (matches.length == 0 ? null : (matches.length == 1 ? matches[0] : matches)); 68 | cb(null, matches); 69 | }); 70 | }, 71 | getByIds : function(ary, cb){ 72 | var matches = []; 73 | this.retrieve(function(e, entities){ 74 | if(e){ 75 | return cb(e, []); 76 | } 77 | for(var entity in entities){ 78 | if(ary.indexOf(entities[entity].id) != -1){ 79 | matches.push(safetyFilter(entities[entity])); 80 | } 81 | } 82 | cb(null, matches); 83 | }); 84 | }, 85 | persist : function(row, cb){ 86 | var me = this; 87 | me.retrieve(function(e, entities){ 88 | entities[row.id] = entities[row.id] || {}; 89 | for(var key in row){ 90 | entities[row.id][key] = row[key]; 91 | } 92 | fs.writeFile(me.store, JSON.stringify(entities, undefined, 4), function(e2){ 93 | if(e2){ 94 | return cb('error writing file: ' + e2); 95 | }else{ 96 | cb(null, entities[row.id]); 97 | } 98 | }); 99 | }); 100 | }, 101 | retrieve : function(cb){ 102 | fs.readFile(this.store, 'utf8', function(e, content){ 103 | if(e){ 104 | return cb('error loading file: ' + e); 105 | } 106 | if(content.trim().length == 0){ 107 | content = {}; 108 | }else{ 109 | try{ 110 | content = JSON.parse(content); 111 | }catch(e){ 112 | console.log('parseerror'); 113 | content = content.slice(0, - 1); 114 | content = JSON.parse(content); 115 | } 116 | } 117 | cb(null, content); 118 | }); 119 | } 120 | }; 121 | 122 | function safetyFilter(obj){ 123 | return { 124 | id : obj.id, 125 | username : obj.username, 126 | email : obj.email, 127 | chips : obj.chips, 128 | wins : obj.wins, 129 | largestWin : obj.largestWin, 130 | created : obj.created 131 | } 132 | } -------------------------------------------------------------------------------- /game-server/app/servers/chat/handler/chatHandler.js: -------------------------------------------------------------------------------- 1 | var UserStore = require('../../../persistence/users'); 2 | var dispatcher = require('../../../util/dispatcher'); 3 | 4 | module.exports = function(app){ 5 | return new Handler(app, app.get('chatService')); 6 | }; 7 | var Handler = function(app, chatService){ 8 | this.app = app; 9 | this.chatService = chatService; 10 | }; 11 | var handler = Handler.prototype; 12 | 13 | /** 14 | * Send messages to users in the channel 15 | * 16 | * @param {Object} msg message from client 17 | * @param {Object} session 18 | * @param {Function} next next stemp callback 19 | * 20 | */ 21 | handler.sendMessage = function(msg, session, next){ 22 | var me = this; 23 | var tid = session.get('tid'); 24 | var channelService = this.app.get('channelService'); 25 | UserStore.getByAttr('id', session.uid, false, function(e, user){ 26 | if(!user){ 27 | next(null, { 28 | code : 500, 29 | error : 'user-not-exist' 30 | }); 31 | return; 32 | } 33 | // target is all users 34 | if(msg.target == 'table'){ 35 | var channel = channelService.getChannel(tid, true); 36 | msg.target = '*'; 37 | channel.pushMessage({ 38 | route : 'onChat', 39 | msg : msg.content, 40 | username : user.username, 41 | target : msg.target 42 | }); 43 | next(null, { 44 | code : 200, 45 | route : msg.route 46 | }); 47 | }else{ 48 | // target is specific user 49 | me.chatService.pushByPlayerName(msg.target, { 50 | username : user.username, 51 | msg : msg.content 52 | }, function(e){ 53 | if(e){ 54 | return next(null, { 55 | code : 500, 56 | error : e 57 | }); 58 | } 59 | next(null, { 60 | code : 200, 61 | route : msg.route 62 | }); 63 | }); 64 | } 65 | }); 66 | }; 67 | 68 | /** 69 | * Get friend list 70 | * 71 | * @param {Object} msg game parameters from client 72 | * @param {Object} session 73 | * @param {Function} next next step callback 74 | * 75 | */ 76 | handler.getFriends = function(msg, session, next){ 77 | this.chatService.getFriendList(session.uid, function(e, friends){ 78 | if(e){ 79 | return next(null, { 80 | code : 500, 81 | error : e 82 | }); 83 | } 84 | next(null, { 85 | code : 200, 86 | route : msg.route, 87 | friends : friends 88 | }); 89 | }); 90 | }; -------------------------------------------------------------------------------- /game-server/app/servers/chat/remote/chatRemote.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app){ 2 | return new ChatRemote(app, app.get('chatService')); 3 | }; 4 | 5 | var ChatRemote = function(app, chatService){ 6 | this.app = app; 7 | this.chatService = chatService; 8 | }; 9 | 10 | /** 11 | * Add player into channel 12 | */ 13 | ChatRemote.prototype.addToChannel = function(uid, cid, cb){ 14 | this.chatService.addToChannel(uid, cid, cb); 15 | }; 16 | 17 | /** 18 | * Add player record 19 | */ 20 | ChatRemote.prototype.add = function(uid, cid, cb){ 21 | this.chatService.add(uid, cid, cb); 22 | }; 23 | 24 | /** 25 | * Get members in a channel 26 | */ 27 | ChatRemote.prototype.getMembers = function(cid, cb){ 28 | this.chatService.getMembers(cid, cb); 29 | }; 30 | 31 | /** 32 | * leave Channel 33 | * uid 34 | * cid 35 | */ 36 | ChatRemote.prototype.leave = function(uid, cid, cb){ 37 | this.chatService.leave(uid, cid); 38 | cb(); 39 | }; 40 | 41 | /** 42 | * kick out user 43 | * 44 | */ 45 | ChatRemote.prototype.disconnect = function(uid, cb){ 46 | this.chatService.disconnect(uid); 47 | cb(); 48 | }; 49 | -------------------------------------------------------------------------------- /game-server/app/servers/connector/handler/entryHandler.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('con-log', __filename); 2 | 3 | module.exports = function(app){ 4 | return new Handler(app); 5 | }; 6 | var Handler = function(app){ 7 | this.app = app; 8 | }; 9 | var handler = Handler.prototype; 10 | 11 | /** 12 | * Register user. 13 | * 14 | * @param {Object} msg request message 15 | * @param {Object} session current session object 16 | * @param {Function} next next step callback 17 | */ 18 | handler.register = function(msg, session, next){ 19 | this.app.rpc.game.authRemote.register(session, msg, function(e, user){ 20 | if(e){ 21 | next(null, { 22 | code : 500, 23 | error : e 24 | }); 25 | }else{ 26 | next(null, { 27 | code : 201 28 | }); 29 | } 30 | }); 31 | }; 32 | 33 | /** 34 | * Connect to the server 35 | * 36 | * @param {Object} msg request message 37 | * @param {Object} session current session object 38 | * @param {Function} next next step callback 39 | */ 40 | handler.connect = function(msg, session, next){ 41 | var me = this; 42 | var sessionService = me.app.get('sessionService'); 43 | me.app.rpc.game.authRemote.auth(session, msg, function(e, user, token){ 44 | if(!user){ 45 | next(null, { 46 | code : 401, 47 | error : e 48 | }); 49 | return; 50 | } 51 | // duplicate log in 52 | if(!! sessionService.getByUid(user.id)){ 53 | return next(null, { 54 | code : 500, 55 | error : 'duplicate-session' 56 | }); 57 | } 58 | session.bind(user.id, function(e){ 59 | if(e){ 60 | console.error('error-binding-user', e); 61 | } 62 | session.set('username', user.username); 63 | session.on('closed', onUserLeave.bind(null, me.app)); 64 | session.pushAll(function(e){ 65 | if(e){ 66 | console.error('set username for session service failed! error is : %j', e.stack); 67 | } 68 | }); 69 | // add user to chat service 70 | me.app.rpc.chat.chatRemote.add(session, session.uid, function(e){ 71 | if(e){ 72 | return next(null, { 73 | code : 500, 74 | error : e 75 | }); 76 | } 77 | next(null, { 78 | code : 200, 79 | token : token, 80 | user : user 81 | }); 82 | }); 83 | }); 84 | }); 85 | }; 86 | /** 87 | * User log out handler 88 | * 89 | * @param {Object} app current application 90 | * @param {Object} session current session object 91 | * 92 | */ 93 | var onUserLeave = function(app, session){ 94 | if(!session || !session.uid || !session.get('tid')){ 95 | return; 96 | } 97 | if(session.get('tid')){ 98 | app.rpc.chat.chatRemote.disconnect(session, session.uid, function(){}); 99 | app.rpc.game.tableRemote.removeMember(session, session.uid, app.get('serverId'), session.get('tid'), function(){}); 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /game-server/app/servers/game/filter/abuseFilter.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(){ 3 | return new Filter(); 4 | }; 5 | 6 | var Filter = function(){}; 7 | 8 | Filter.prototype.before = function (msg, session, next){ 9 | if(msg.content && msg.content.indexOf ('fuck') !== -1){ 10 | session.__abuse__ = true; 11 | msg.content = msg.content.replace ('fuck', '****'); 12 | } 13 | next(); 14 | }; 15 | 16 | Filter.prototype.after = function (err, msg, session, resp, next){ 17 | if(session.__abuse__){ 18 | var user_info = session.uid.split ('*'); 19 | console.log ('abuse:' + user_info[0] + "at room" + user_info[1]); 20 | } 21 | next(err); 22 | }; -------------------------------------------------------------------------------- /game-server/app/servers/game/handler/tableHandler.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('game-log', __filename); 2 | 3 | module.exports = function(app){ 4 | return new Handler(app); 5 | }; 6 | var Handler = function(app){ 7 | this.app = app; 8 | }; 9 | var handler = Handler.prototype; 10 | 11 | /** 12 | * Get tables 13 | * 14 | * @param {Object} msg game parameters from client 15 | * @param {Object} session 16 | * @param {Function} next next step callback 17 | * 18 | */ 19 | handler.getTables = function(msg, session, next){ 20 | var tableService = this.app.get('tableService'); 21 | next(null, { 22 | code : 200, 23 | route : msg.route, 24 | tables : tableService.getTables() 25 | }); 26 | }; 27 | 28 | /** 29 | * Create table 30 | * 31 | * @param {Object} msg game parameters from client 32 | * @param {Object} session 33 | * @param {Function} next next step callback 34 | * 35 | */ 36 | handler.createTable = function(msg, session, next){ 37 | if(session.get('tid')){ 38 | next(null, { 39 | code : 500, 40 | error : 'already-in-table' 41 | }); 42 | return; 43 | } 44 | this.app.get('tableService').createTable(session.uid, msg, function(e){ 45 | if(e){ 46 | next(null, { 47 | code : 500, 48 | error : e 49 | }); 50 | return; 51 | } 52 | next(null, { 53 | code : 200, 54 | route : msg.route 55 | }); 56 | }); 57 | }; 58 | 59 | /** 60 | * Join table 61 | * 62 | * @param {Object} msg table parameters from client 63 | * @param {Object} session 64 | * @param {Function} next next step callback 65 | * 66 | */ 67 | handler.joinTable = function(msg, session, next){ 68 | var me = this; 69 | var tableService = this.app.get('tableService'); 70 | var table = tableService.getTable(msg.tid); 71 | if(!msg.tid || !table){ 72 | next(null, { 73 | code : 500, 74 | error : 'invalid-table' 75 | }); 76 | return; 77 | } 78 | session.set('tid', msg.tid); 79 | session.pushAll(function(err){ 80 | if(err){ 81 | logger.error('set tid for session service failed! error is : %j', err.stack); 82 | next(null, { 83 | code : 500, 84 | error : 'server-error' 85 | }); 86 | return; 87 | } 88 | var tid = session.get('tid'); 89 | me.app.rpc.chat.chatRemote.addToChannel(session, session.uid, tid, function(e){ 90 | if(e){ 91 | next(null, { 92 | code : 500, 93 | error : e 94 | }); 95 | return; 96 | } 97 | tableService.addMember(tid, session.uid, function(e){ 98 | if(e){ 99 | next(null, { 100 | code : 500, 101 | error : e 102 | }); 103 | return; 104 | } 105 | next(null, { 106 | code : 200, 107 | route : msg.route 108 | }); 109 | }); 110 | }); 111 | }); 112 | }; 113 | 114 | /** 115 | * Leave table 116 | * 117 | * @param {Object} msg table parameters from client 118 | * @param {Object} session 119 | * @param {Function} next next step callback 120 | * 121 | */ 122 | handler.leaveTable = function(msg, session, next){ 123 | var me = this; 124 | var tid = session.get('tid'); 125 | if(!tid){ 126 | return next(null, { 127 | code : 500, 128 | error : 'not-table-member' 129 | }); 130 | } 131 | me.app.rpc.chat.chatRemote.leave(session, session.uid, tid, function(e){}); 132 | session.set('tid', undefined); 133 | session.pushAll(function(e){ 134 | if(e){ 135 | logger.error('unset tid for session service failed! error is : %j', e.stack); 136 | return next(null, { 137 | code : 500, 138 | error : 'server-error' 139 | }); 140 | } 141 | me.app.get('tableService').removeMember(tid, session.uid, function(e){ 142 | if(e){ 143 | next(null, { 144 | code : 500, 145 | error : e 146 | }); 147 | return; 148 | } 149 | next(null, { 150 | code : 200, 151 | route : msg.route 152 | }); 153 | }); 154 | }); 155 | }; 156 | 157 | /** 158 | * Join game 159 | * 160 | * @param {Object} msg game parameters from client 161 | * @param {Object} session 162 | * @param {Function} next next step callback 163 | * 164 | */ 165 | handler.joinGame = function(msg, session, next){ 166 | this.app.get('tableService').addPlayer(session.get('tid'), session.uid, msg.buyIn, function(e){ 167 | if(e){ 168 | next(null, { 169 | code : 500, 170 | error : e 171 | }); 172 | return; 173 | } 174 | next(null, { 175 | code : 200, 176 | route : msg.route 177 | }); 178 | }); 179 | }; 180 | 181 | /** 182 | * Start game 183 | * 184 | * @param {Object} msg game parameters from client 185 | * @param {Object} session 186 | * @param {Function} next next step callback 187 | * 188 | */ 189 | handler.startGame = function(msg, session, next){ 190 | this.app.get('tableService').startGame(session.get('tid'), function(e){ 191 | if(e){ 192 | next(null, { 193 | code : 500, 194 | error : e 195 | }); 196 | } 197 | next(null, { 198 | code : 200, 199 | route : msg.route 200 | }); 201 | }); 202 | }; 203 | 204 | /** 205 | * Perform an action on your turn 206 | * 207 | * @param {Object} msg game parameters from client 208 | * @param {Object} session 209 | * @param {Function} next next step callback 210 | * 211 | */ 212 | handler.execute = function(msg, session, next){ 213 | this.app.get('tableService').performAction(session.get('tid'), session.uid, msg, function(e){ 214 | if(e){ 215 | next(null, { 216 | code : 500, 217 | error : e 218 | }); 219 | } 220 | next(null, { 221 | code : 200, 222 | route : msg.route 223 | }); 224 | }); 225 | }; 226 | 227 | /** 228 | * Perform an action on your turn 229 | * 230 | * @param {Object} msg game parameters from client 231 | * @param {Object} session 232 | * @param {Function} next next step callback 233 | * 234 | */ 235 | handler.removeBots = function(msg, session, next){ 236 | var botService = this.app.get('botService'); 237 | botService.removeAllBots(session.get('tid'), true); 238 | next(null, { 239 | code : 200, 240 | route : msg.route 241 | }); 242 | }; 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /game-server/app/servers/game/handler/userHandler.js: -------------------------------------------------------------------------------- 1 | var logger = require('pomelo-logger').getLogger('game-log', __filename); 2 | var UserStore = require('../../../persistence/users'); 3 | 4 | module.exports = function(app){ 5 | return new Handler(app); 6 | }; 7 | var Handler = function(app){ 8 | this.app = app; 9 | }; 10 | var handler = Handler.prototype; 11 | 12 | /** 13 | * Get users matching the criteria 14 | * 15 | * @param {Object} msg game parameters from client 16 | * @param {Object} session 17 | * @param {Function} next next step callback 18 | * 19 | */ 20 | handler.getUsers = function(msg, session, next){ 21 | if(!msg.name && !msg.val && !session.uid){ 22 | return next(null, { 23 | code : 500, 24 | error : 'invalid-input' 25 | }); 26 | } 27 | var searchId = (msg.name == 'id' || msg.name == 'username' || msg.name == 'email') ? msg.name : 'id'; 28 | var searchVal = typeof msg.val === 'string' ? msg.val : session.uid; 29 | UserStore.getByAttr(searchId, searchVal, { 30 | getArray : true 31 | }, function(e, matches){ 32 | if(e){ 33 | return next(null, { 34 | code : 500, 35 | error : e 36 | }); 37 | } 38 | next(null, { 39 | code : 200, 40 | route : msg.route, 41 | matches : matches 42 | }); 43 | }); 44 | }; 45 | 46 | /** 47 | * Update user profile 48 | * 49 | * @param {Object} msg game parameters from client 50 | * @param {Object} session 51 | * @param {Function} next next step callback 52 | * 53 | */ 54 | handler.setProfile = function(msg, session, next){ 55 | if(!session.uid){ 56 | return next(null, { 57 | code : 500, 58 | error : 'invalid-session' 59 | }); 60 | } 61 | UserStore.getByAttr('id', session.uid, false, function(e, user){ 62 | if(e){ 63 | return next(null, { 64 | code : 500, 65 | error : e 66 | }); 67 | } 68 | var userObj = { 69 | id : user.id 70 | }; 71 | if(msg.email){ 72 | userObj.email = msg.email.trim(); 73 | } 74 | UserStore.set(userObj, function(e, updatedUser){ 75 | if(e){ 76 | return next(null, { 77 | code : 500, 78 | error : e 79 | }); 80 | } 81 | next(null, { 82 | code : 200, 83 | route : msg.route 84 | }); 85 | }); 86 | }); 87 | }; 88 | 89 | /** 90 | * Update user password 91 | * 92 | * @param {Object} msg game parameters from client 93 | * @param {Object} session 94 | * @param {Function} next next step callback 95 | * 96 | */ 97 | handler.setPassword = function(msg, session, next){ 98 | if(!session.uid || !msg.oldpassword || !msg.password){ 99 | return next(null, { 100 | code : 500, 101 | error : 'invalid-input' 102 | }); 103 | } 104 | UserStore.getByAttr(['id', 'password'], [session.uid, msg.oldpassword], false, function(e, user){ 105 | if(e){ 106 | return next(null, { 107 | code : 500, 108 | error : e 109 | }); 110 | } 111 | var userObj = { 112 | id : user.id, 113 | password : msg.password.trim() 114 | }; 115 | UserStore.set(userObj, function(e, updatedUser){ 116 | if(e){ 117 | return next(null, { 118 | code : 500, 119 | error : e 120 | }); 121 | } 122 | next(null, { 123 | code : 200, 124 | route : msg.route 125 | }); 126 | }); 127 | }); 128 | }; 129 | 130 | /** 131 | * Add a friend to friend list 132 | * 133 | * @param {Object} msg game parameters from client 134 | * @param {Object} session 135 | * @param {Function} next next step callback 136 | * 137 | */ 138 | handler.addFriend = function(msg, session, next){ 139 | if(!session.uid){ 140 | return next(null, { 141 | code : 500, 142 | error : 'invalid-session' 143 | }); 144 | } 145 | if(!msg.friend){ 146 | return next(null, { 147 | code : 200, 148 | route : msg.route 149 | }); 150 | } 151 | UserStore.getByAttr('id', session.uid, { 152 | getFullEntity : true 153 | }, function(e, user){ 154 | if(e){ 155 | return next(null, { 156 | code : 500, 157 | error : e 158 | }); 159 | } 160 | UserStore.getByAttr('id', msg.friend, false, function(e, friend){ 161 | if(e){ 162 | return next(null, { 163 | code : 500, 164 | error : e 165 | }); 166 | } 167 | user.friends.push({ 168 | id : friend.id, 169 | username : friend.username 170 | }); 171 | UserStore.set({ 172 | id : user.id, 173 | friends : user.friends 174 | }, function(e, updatedUser){ 175 | if(e){ 176 | return next(null, { 177 | code : 500, 178 | error : e 179 | }); 180 | } 181 | next(null, { 182 | code : 200, 183 | route : msg.route 184 | }); 185 | }); 186 | }); 187 | }); 188 | }; -------------------------------------------------------------------------------- /game-server/app/servers/game/remote/authRemote.js: -------------------------------------------------------------------------------- 1 | var UserStore = require('../../../persistence/users'); 2 | var tokenService = require('../../../../../shared/token'); 3 | var SESSION_CONFIG = require('../../../../../shared/config/session.json'); 4 | 5 | module.exports = function(app){ 6 | return new Remote(app); 7 | }; 8 | var Remote = function(app){ 9 | this.app = app; 10 | }; 11 | var remote = Remote.prototype; 12 | 13 | /** 14 | * Register user. 15 | * 16 | * @param {object} userObj object containing userObj.user and userObj.pass 17 | * @param {Function} cb 18 | * @return {Void} 19 | */ 20 | remote.register = function(userObj, cb){ 21 | UserStore.create({ 22 | username : userObj.username, 23 | password : userObj.password, 24 | email : userObj.email, 25 | chips : 100000 26 | }, function(e, user){ 27 | if(e){ 28 | cb(e); 29 | }else{ 30 | cb(null, user); 31 | } 32 | }); 33 | }; 34 | /** 35 | * Auth via user/pass or token, and check for expiry. 36 | * 37 | * @param {object|string} input token or object containing username and password 38 | * @param {Function} cb 39 | * @return {Void} 40 | */ 41 | remote.auth = function(input, cb){ 42 | if(typeof input === 'string'){ 43 | var res = tokenService.parse(input, SESSION_CONFIG.secret); 44 | if(!res){ 45 | cb('invalid-token'); 46 | return; 47 | } 48 | if(!checkExpire(res, SESSION_CONFIG.expire)){ 49 | cb('token-expired'); 50 | return; 51 | } 52 | UserStore.getByAttr('id', res.uid, false, function(e, user){ 53 | if(e){ 54 | cb('invalid-user'); 55 | return; 56 | } 57 | cb(null, user); 58 | }); 59 | }else{ 60 | UserStore.getByAttr(['username', 'password'], [input.username, input.password], false, function(e, user){ 61 | if(!user){ 62 | cb('invalid-user'); 63 | }else{ 64 | cb(null, user, tokenService.create(user.id, Date.now(), SESSION_CONFIG.secret)); 65 | } 66 | }); 67 | } 68 | }; 69 | /** 70 | * Check the token whether expire. 71 | * 72 | * @param {Object} token token info 73 | * @param {Number} expire expire time 74 | * @return {Boolean} true for not expire and false for expire 75 | */ 76 | var checkExpire = function(token, expire){ 77 | if(expire < 0){ 78 | // negative expire means never expire 79 | return true; 80 | } 81 | return (Date.now() - token.timestamp) < expire; 82 | }; 83 | -------------------------------------------------------------------------------- /game-server/app/servers/game/remote/tableRemote.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app){ 2 | return new Remote(app); 3 | }; 4 | var Remote = function(app){ 5 | this.app = app; 6 | this.tableService = app.get('tableService'); 7 | }; 8 | var remote = Remote.prototype; 9 | 10 | /** 11 | * Remove member/player from table 12 | * 13 | * @param {string} uid user id 14 | * @param {string} sid server id 15 | * @param {string} tid channel id 16 | * @param {function} cb callback 17 | * 18 | */ 19 | remote.removeMember = function(uid, sid, tid, cb){ 20 | this.tableService.removeMember(tid, uid, cb); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /game-server/app/servers/gate/handler/gateHandler.js: -------------------------------------------------------------------------------- 1 | var dispatcher = require('../../../util/dispatcher'); 2 | 3 | module.exports = function(app){ 4 | return new Handler(app); 5 | }; 6 | 7 | var Handler = function(app){ 8 | this.app = app; 9 | }; 10 | 11 | var handler = Handler.prototype; 12 | 13 | /** 14 | * Gate handler that dispatch user to connectors. 15 | * 16 | * @param {Object} msg message from client 17 | * @param {Object} session 18 | * @param {Function} next next stemp callback 19 | * 20 | */ 21 | handler.queryEntry = function(msg, session, next){ 22 | // get all connectors 23 | var connectors = this.app.getServersByType('connector'); 24 | if(!connectors || connectors.length === 0){ 25 | next(null, { 26 | code : 500 27 | }); 28 | return; 29 | } 30 | // select connector 31 | var res = dispatcher.dispatch(1, connectors); 32 | next(null, { 33 | code : 200, 34 | host : res.host, 35 | port : res.clientPort 36 | }); 37 | }; -------------------------------------------------------------------------------- /game-server/app/services/botService.js: -------------------------------------------------------------------------------- 1 | var BOT_CONFIG = require('../../config/bots.json'); 2 | var logger = require('pomelo-logger').getLogger('game-log', __filename); 3 | var UserStore = require('../../app/persistence/users'); 4 | Hand = require('hoyle').Hand; 5 | 6 | 7 | var BotService = function(app){ 8 | this.app = app; 9 | this.channelService = app.get('channelService'); 10 | this.tableService = app.get('tableService'); 11 | this.tableInstance = {}; 12 | this.tableInstanceActive = {}; 13 | this.bots = {}; 14 | this.config = BOT_CONFIG.config; 15 | }; 16 | 17 | module.exports = BotService; 18 | 19 | BotService.prototype.start = function(cb){ 20 | var me = this; 21 | if(!me.config.enabled){ 22 | return cb(); 23 | } 24 | me.registerBots(BOT_CONFIG.bots, function(){ 25 | logger.info('all bots registered'); 26 | me.checkAvailability(); 27 | cb(); 28 | }); 29 | }; 30 | 31 | BotService.prototype.registerBots = function(bots, cb){ 32 | var me = this, i = 0; 33 | if(!bots.length){ 34 | cb(); 35 | } 36 | function createIfNotExist(){ 37 | bots[i].chips = 100000; 38 | UserStore.create(bots[i], function(e, user){ 39 | me.bots[user.id] = user; 40 | me.bots[user.id].available = true; 41 | if(++i == bots.length){ 42 | return cb(); 43 | } 44 | createIfNotExist(); 45 | }); 46 | } 47 | createIfNotExist(); 48 | }; 49 | 50 | BotService.prototype.checkAvailability = function(){ 51 | var me = this; 52 | setInterval(function(){ 53 | var bot = me.getAvailableBot(); 54 | var table = me.getAvailableTable(); 55 | if(bot && table && (!me.config.minBots || me.config.minBots > me.getActiveBots()) && !me.config.banBots){ 56 | me.joinGame(bot, table); 57 | } 58 | }, (1000 * getRandomInt(me.config.joinInterval.min, (me.config.joinInterval.max)))); 59 | }; 60 | 61 | BotService.prototype.getById = function(id){ 62 | return this.bots[id]; 63 | }; 64 | 65 | BotService.prototype.getAvailableBot = function(){ 66 | var bot; 67 | for(var i in this.bots){ 68 | if(this.bots[i].available){ 69 | bot = this.bots[i]; 70 | } 71 | } 72 | return bot; 73 | }; 74 | 75 | BotService.prototype.getActiveBots = function(){ 76 | var ctr = 0; 77 | for(var i in this.bots){ 78 | if(!this.bots[i].available){ 79 | ctr += 1; 80 | } 81 | } 82 | return ctr; 83 | }; 84 | 85 | BotService.prototype.getAvailableTable = function(){ 86 | var table; 87 | for(var i in this.tableService.tables){ 88 | if(!this.tableInstanceActive[i] && (this.tableService.tables[i].state == 'JOIN' && this.tableService.tables[i].table.playersToAdd.length < this.tableService.tables[i].table.maxPlayers) || 89 | (this.tableService.tables[i].state == 'IN_PROGRESS' && (this.tableService.tables[i].table.playersToAdd + this.tableService.tables[i].table.players.length) < this.tableService.tables[i].table.maxPlayers)){ 90 | table = this.tableService.tables[i]; 91 | break; 92 | } 93 | } 94 | return table; 95 | }; 96 | 97 | BotService.prototype.joinGame = function(bot, table){ 98 | var me = this; 99 | me.tableService.addPlayer(table.id, bot.id, me.config.buyIn || 1000, function(e){ 100 | if(e){ 101 | return logger.error('bot error joining game', e); 102 | } 103 | bot.available = false; 104 | bot.games = getRandomInt(me.config.gamesToPlay.min, me.config.gamesToPlay.max); 105 | bot.tid = table.id; 106 | logger.debug('bot '+bot.username+' joining table '+table.id+' for '+bot.games+' games'); 107 | me.tableInstance[table.id] = me.tableInstance[table.id] || 0; 108 | me.tableInstance[table.id] += 1; 109 | me.listen(table.id); 110 | }); 111 | }; 112 | 113 | BotService.prototype.startGame = function(table, tid){ 114 | var me = this; 115 | var interval = setInterval(function(){ 116 | if(table.state == 'IN_PROGRESS'){ 117 | clearInterval(interval); 118 | } 119 | if(table.table.playersToAdd.length >= (me.config.minPlayers ? me.config.minPlayers : 0) && !me.config.banBots){ 120 | table.tableService.startGame(tid, function(e){ 121 | if(e){ 122 | return logger.debug('cant start game yet', e); 123 | } 124 | clearInterval(interval); 125 | }); 126 | } 127 | }, (1000 * getRandomInt(7, 20))); 128 | } 129 | 130 | BotService.prototype.leaveGame = function(tid, uid, cb){ 131 | var me = this; 132 | me.tableService.removeMember(tid, uid, function(){ 133 | cb(); 134 | }); 135 | }; 136 | 137 | BotService.prototype.listen = function(tid){ 138 | var me = this; 139 | if(me.tableInstance[tid] > 1){ 140 | return false; 141 | } 142 | logger.debug('initializing listeners for table '+tid); 143 | var table = me.tableService.getTable(tid); 144 | var playerJoinedListener = function(){ 145 | logger.debug('playerJoined'); 146 | me.startGame(table, tid); 147 | }; 148 | var newRoundListener = function(){ 149 | logger.debug('newRound'); 150 | me.moveIfTurn(table); 151 | }; 152 | var turnStartListener = function(){ 153 | logger.debug('turnStart'); 154 | me.moveIfTurn(table); 155 | }; 156 | var gameInitListener = function(){ 157 | logger.debug('gameInit'); 158 | setTimeout(function(){ 159 | me.removeAllBots(tid); 160 | }, 300); 161 | }; 162 | table.table.eventEmitter.on('playerJoined', playerJoinedListener); 163 | table.table.eventEmitter.on('newRound', newRoundListener); 164 | table.table.eventEmitter.on('turnStart', turnStartListener); 165 | table.table.eventEmitter.on('gameInit', gameInitListener); 166 | }; 167 | 168 | BotService.prototype.moveIfTurn = function(table){ 169 | var me = this, pid; 170 | if(typeof table.table.currentPlayer === 'number' && table.table.players[table.table.currentPlayer]) 171 | pid = table.table.players[table.table.currentPlayer].id; 172 | if(this.bots[pid]){ 173 | logger.debug('starting move: '+this.bots[pid].username); 174 | table.table.stopTimer(); 175 | setTimeout(function(){ 176 | if(!table.table || !table.table.game) return false; 177 | var board = table.table.game.board || []; 178 | if(board.length < 3){ 179 | table.table.players[table.table.currentPlayer].Call(); 180 | }else{ 181 | performMove(table); 182 | } 183 | logger.debug('completed move.'); 184 | table.tableService.handleGameState(table.id, function(e){ 185 | if(e){ 186 | logger.error(e); 187 | } 188 | }); 189 | }, (getRandomInt(me.config.actionInterval.min, me.config.actionInterval.max) * 1000)); 190 | } 191 | }; 192 | 193 | function performMove(table){ 194 | var currentPlayer = table.table.players[table.table.currentPlayer]; 195 | var myBet = table.table.game.bets[table.table.currentPlayer]; 196 | var myHand = Hand.make(getHand(table.table.game.board.concat(table.table.players[table.table.currentPlayer].cards))); 197 | var maxBet = myBet; 198 | var maxRank = myHand.rank; 199 | var winningPlayer = currentPlayer.playerName; 200 | var hands = []; 201 | for(var i=0;i maxBet) maxBet = bet; 205 | if(hand.rank > maxRank){ 206 | maxRank = hand.rank; 207 | winningPlayer = table.table.players[i].playerName; 208 | } 209 | hands.push(hand); 210 | } 211 | logger.debug('has best hand: '+ winningPlayer); 212 | var isWinner = false; 213 | var winners = Hand.pickWinners(hands); 214 | var diff = maxBet - myBet; 215 | for(var i=0;i= maxBet){ 220 | if(getRandomInt(1, 51) > 47){ 221 | currentPlayer.AllIn(); 222 | }else if(getRandomInt(1, 10) > 7){ 223 | currentPlayer.Bet(getRandomInt(2, 53)); 224 | }else{ 225 | currentPlayer.Call(); 226 | } 227 | }else if(myBet < maxBet){ 228 | if(diff > getRandomInt(4, 61)){ 229 | if(getRandomInt(1, 73) > 71){ 230 | currentPlayer.AllIn(); 231 | }else if(getRandomInt(1, 10) > 8){ 232 | currentPlayer.Bet(getRandomInt(2, 36)); 233 | }else if(getRandomInt(1, 10) > 2){ 234 | currentPlayer.Fold(); 235 | }else{ 236 | currentPlayer.Call(); 237 | } 238 | }else{ 239 | if(getRandomInt(1, 73) > 70){ 240 | currentPlayer.AllIn(); 241 | }else if(getRandomInt(1, 10) > 7){ 242 | currentPlayer.Bet(getRandomInt(2, 43)); 243 | }else if(diff > getRandomInt(1, 7) && getRandomInt(1, 10) > 6){ 244 | currentPlayer.Fold(); 245 | }else{ 246 | currentPlayer.Call(); 247 | } 248 | } 249 | }else{ 250 | currentPlayer.Call(); 251 | } 252 | }else{ 253 | if(myBet >= maxBet){ 254 | if(getRandomInt(1, 10) == 10){ 255 | currentPlayer.AllIn(); 256 | }else if(getRandomInt(1, 10) > 4){ 257 | currentPlayer.Bet(getRandomInt(1, 271)); 258 | }else{ 259 | currentPlayer.Call(); 260 | } 261 | }else if(myBet < maxBet){ 262 | if(getRandomInt(1, 10) == 10){ 263 | currentPlayer.AllIn(); 264 | }else if(getRandomInt(1, 10) > 4){ 265 | currentPlayer.Bet(getRandomInt(2, 189)); 266 | }else if(diff > getRandomInt(71, 104) && getRandomInt(1, 10) > 8){ 267 | currentPlayer.Fold(); 268 | }else{ 269 | currentPlayer.Call(); 270 | } 271 | }else{ 272 | currentPlayer.Call(); 273 | } 274 | } 275 | } 276 | 277 | function getHand(hand){ 278 | for(var j=0;j 0 && this._events[type].length > m) { 145 | this._events[type].warned = true; 146 | console.error('(node) warning: possible EventEmitter memory ' + 147 | 'leak detected. %d listeners added. ' + 148 | 'Use emitter.setMaxListeners() to increase limit.', 149 | this._events[type].length); 150 | console.trace(); 151 | } 152 | } 153 | 154 | return this; 155 | }; 156 | 157 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 158 | 159 | EventEmitter.prototype.once = function(type, listener) { 160 | if ('function' !== typeof listener) { 161 | throw new Error('.once only takes instances of Function'); 162 | } 163 | 164 | var self = this; 165 | function g() { 166 | self.removeListener(type, g); 167 | listener.apply(this, arguments); 168 | }; 169 | 170 | g.listener = listener; 171 | self.on(type, g); 172 | 173 | return this; 174 | }; 175 | 176 | EventEmitter.prototype.removeListener = function(type, listener) { 177 | if ('function' !== typeof listener) { 178 | throw new Error('removeListener only takes instances of Function'); 179 | } 180 | 181 | // does not use listeners(), so no side effect of creating _events[type] 182 | if (!this._events || !this._events[type]) return this; 183 | 184 | var list = this._events[type]; 185 | 186 | if (isArray(list)) { 187 | var position = -1; 188 | for (var i = 0, length = list.length; i < length; i++) { 189 | if (list[i] === listener || 190 | (list[i].listener && list[i].listener === listener)) 191 | { 192 | position = i; 193 | break; 194 | } 195 | } 196 | 197 | if (position < 0) return this; 198 | list.splice(position, 1); 199 | } else if (list === listener || 200 | (list.listener && list.listener === listener)) 201 | { 202 | delete this._events[type]; 203 | } 204 | 205 | return this; 206 | }; 207 | 208 | EventEmitter.prototype.removeAllListeners = function(type) { 209 | if (arguments.length === 0) { 210 | this._events = {}; 211 | return this; 212 | } 213 | 214 | var events = this._events && this._events[type]; 215 | if (!events) return this; 216 | 217 | if (isArray(events)) { 218 | events.splice(0); 219 | } else { 220 | this._events[type] = null; 221 | } 222 | 223 | return this; 224 | }; 225 | 226 | EventEmitter.prototype.listeners = function(type) { 227 | if (!this._events) this._events = {}; 228 | if (!this._events[type]) this._events[type] = []; 229 | if (!isArray(this._events[type])) { 230 | this._events[type] = [this._events[type]]; 231 | } 232 | return this._events[type]; 233 | } 234 | })(); 235 | 236 | (function (exports, global) { 237 | 238 | var Protocol = exports; 239 | 240 | var HEADER = 5; 241 | 242 | var Message = function(id,route,body){ 243 | this.id = id; 244 | this.route = route; 245 | this.body = body; 246 | }; 247 | 248 | /** 249 | * 250 | *pomele client encode 251 | * id message id; 252 | * route message route 253 | * msg message body 254 | * socketio current support string 255 | * 256 | */ 257 | Protocol.encode = function(id,route,msg){ 258 | var msgStr = JSON.stringify(msg); 259 | if (route.length>255) { throw new Error('route maxlength is overflow'); } 260 | var byteArray = new Uint16Array(HEADER + route.length + msgStr.length); 261 | var index = 0; 262 | byteArray[index++] = (id>>24) & 0xFF; 263 | byteArray[index++] = (id>>16) & 0xFF; 264 | byteArray[index++] = (id>>8) & 0xFF; 265 | byteArray[index++] = id & 0xFF; 266 | byteArray[index++] = route.length & 0xFF; 267 | for(var i = 0;i>>0; 293 | var routeLen = buf[HEADER-1]; 294 | var route = bt2Str(buf,HEADER, routeLen+HEADER); 295 | var body = bt2Str(buf,routeLen+HEADER,buf.length); 296 | return new Message(id,route,body); 297 | }; 298 | 299 | var bt2Str = function(byteArray,start,end) { 300 | var result = ""; 301 | for(var i = start; i < byteArray.length && ithis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||g.onError!==ca)try{f=i.execCb(c,l,b,f)}catch(d){a=d}else f=i.execCb(c,l,b,f);this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:this.usingExports&& 19 | (f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,g.onResourceLoad))g.onResourceLoad(i,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a= 20 | this.map,b=a.id,d=p(a.prefix);this.depMaps.push(d);q(d,"defined",u(this,function(f){var l,d;d=m(aa,this.map.id);var e=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,n=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(e=f.normalize(e,function(a){return c(a,P,!0)})||""),f=p(a.prefix+"!"+e,this.map.parentMap),q(f,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(h,f.id)){this.depMaps.push(f); 21 | if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=i.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(h,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,e=p(d),P=M;c&&(f=c);P&&(M=!1);s(e);t(j.config,b)&&(j.config[d]=j.config[b]);try{g.exec(f)}catch(h){return w(C("fromtexteval", 22 | "fromText eval for "+b+" failed: "+h,h,[b]))}P&&(M=!0);this.depMaps.push(e);i.completeLoad(d);n([d],l)}),f.load(a.name,n,l,j))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=p(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",u(this,function(a){this.defineDep(b, 23 | a);this.check()}));this.errback&&q(a,"error",u(this,this.errback))}c=a.id;f=h[c];!t(L,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,u(this,function(a){var b=m(h,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:j,contextName:b,registry:h,defined:r,urlFetched:S,defQueue:A,Module:Z,makeModuleMap:p, 24 | nextTick:g.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(j[b]||(j[b]={}),U(j[b],a,!0,!0)):j[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(aa[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),j.shim=b);a.packages&&v(a.packages,function(a){var b, 25 | a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(j.paths[b]=a.location);j.pkgs[b]=a.name+"/"+(a.main||"main").replace(ia,"").replace(Q,"")});B(h,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=p(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,e){function j(c,d,m){var n,q;e.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild= 26 | !0);if("string"===typeof c){if(G(d))return w(C("requireargs","Invalid require call"),m);if(a&&t(L,c))return L[c](h[a.id]);if(g.get)return g.get(i,c,a,j);n=p(c,a,!1,!0);n=n.id;return!t(r,n)?w(C("notloaded",'Module name "'+n+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[n]}J();i.nextTick(function(){J();q=s(p(null,a));q.skipMap=e.skipMap;q.init(c,d,m,{enabled:!0});D()});return j}e=e||{};U(j,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),k=b.split("/")[0];if(-1!== 27 | e&&(!("."===k||".."===k)||1e.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,e.attachEvent("onreadystatechange",b.onScriptLoad)): 34 | (e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)),e.src=d,J=e,D?y.insertBefore(e,D):y.appendChild(e),J=null,e;if(ea)try{importScripts(d),b.completeLoad(c)}catch(m){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,m,[c]))}};z&&!q.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return s=I,q.baseUrl||(E=s.split("/"),s=E.pop(),O=E.length?E.join("/")+"/":"./",q.baseUrl= 35 | O),s=s.replace(Q,""),g.jsExtRegExp.test(s)&&(s=I),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ka,"").replace(la,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b|| 36 | (b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}(g?g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(q)}})(this); -------------------------------------------------------------------------------- /web-server/public/js/libs/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.6.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /web-server/public/js/models/MessageModel.js: -------------------------------------------------------------------------------- 1 | define(["jquery", "backbone"], function($, Backbone){ 2 | var View = Backbone.Model.extend({ 3 | urlRoot : '/rest/messages' 4 | }); 5 | return View; 6 | }); -------------------------------------------------------------------------------- /web-server/public/js/models/UserModel.js: -------------------------------------------------------------------------------- 1 | define(["jquery", "backbone"], function($, Backbone){ 2 | var View = Backbone.Model.extend({ 3 | urlRoot : '/rest/users' 4 | }); 5 | return View; 6 | }); -------------------------------------------------------------------------------- /web-server/public/js/routers/desktopRouter.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'backbone', 4 | 'views/AlertGeneralView', 5 | 'views/AlertConfirmView', 6 | 'views/AlertErrorView', 7 | 'views/GlobalView', 8 | 'views/LoginView', 9 | 'views/RegisterView', 10 | 'views/TableListView', 11 | 'views/TableView', 12 | 'views/ProfileView' 13 | ], function($, Backbone, AlertGeneralView, AlertConfirmView, AlertErrorView, GlobalView, LoginView, RegisterView, TableListView, TableView, ProfileView){ 14 | // bind alerts 15 | Alerts.General = new AlertGeneralView(); 16 | Alerts.Confirm = new AlertConfirmView(); 17 | Alerts.Error = new AlertErrorView(); 18 | // main router 19 | var Router = Backbone.Router.extend({ 20 | initialize: function(){ 21 | // establish event pub/sub 22 | this.eventPubSub = _.extend({}, Backbone.Events); 23 | this.game = new GameClient(); 24 | var gv = new GlobalView({game:this.game, eventPubSub:this.eventPubSub}); 25 | var lv = new LoginView({game:this.game, eventPubSub:this.eventPubSub}); 26 | var rv = new RegisterView({game:this.game, eventPubSub:this.eventPubSub}); 27 | var tlv = new TableListView({game:this.game, eventPubSub:this.eventPubSub}); 28 | var tv = new TableView({game:this.game, eventPubSub:this.eventPubSub}); 29 | var pv = new ProfileView({game:this.game, eventPubSub:this.eventPubSub}); 30 | Backbone.history.start(); 31 | }, 32 | routes: { 33 | '' : 'tables', 34 | 'login' : 'login', 35 | 'register' : 'register', 36 | 'profile' : 'profile', 37 | 'tables' : 'tables', 38 | 'table/:id' : 'table' 39 | }, 40 | login: function(){ 41 | var me = this; 42 | me.connect(function(){ 43 | me.eventPubSub.trigger('initLoginView'); 44 | }); 45 | }, 46 | register: function(){ 47 | var me = this; 48 | me.connect(function(){ 49 | me.eventPubSub.trigger('initRegisterView'); 50 | }); 51 | }, 52 | profile: function(){ 53 | var me = this; 54 | me.connect(function(){ 55 | me.auth(function(){ 56 | me.eventPubSub.trigger('initProfileView'); 57 | }); 58 | }); 59 | }, 60 | tables: function(){ 61 | var me = this; 62 | me.connect(function(){ 63 | me.auth(function(){ 64 | me.eventPubSub.trigger('initTableListView'); 65 | }); 66 | }); 67 | }, 68 | table: function(id){ 69 | var me = this; 70 | me.connect(function(){ 71 | me.auth(function(){ 72 | me.eventPubSub.trigger('initTableView', { 73 | tid : id 74 | }); 75 | }); 76 | }); 77 | }, 78 | connect: function(callback){ 79 | if(!this.game.connection.host){ 80 | this.game.getEntry(callback); 81 | }else{ 82 | callback(); 83 | } 84 | }, 85 | auth: function(callback){ 86 | if(!this.game.session.user){ 87 | $('.username-placeholder').html(''); 88 | $('.chips-placeholder').html(''); 89 | $('.authed-section').hide(); 90 | $('.unauthed-section').show(); 91 | Backbone.history.navigate('#/login'); 92 | }else{ 93 | $('.username-placeholder').html(this.game.session.user.username); 94 | $('.chips-placeholder').html(this.game.session.user.chips); 95 | $('.unauthed-section').hide(); 96 | $('.authed-section').show(); 97 | callback(); 98 | } 99 | } 100 | }); 101 | return Router; 102 | }); 103 | var Alerts = {}; -------------------------------------------------------------------------------- /web-server/public/js/views/AlertConfirmView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | initialize: function(){ 4 | this.setElement('#confirm-alert'); 5 | $(this.el).modal({ 6 | show : false, 7 | keyboard : true, 8 | backdrop : true 9 | }); 10 | }, 11 | events: { 12 | "click button.submit": "doConfirm" 13 | }, 14 | display: function(vars, callback){ 15 | if(vars && typeof callback === typeof Function){ 16 | $(this.el).modal('show'); 17 | $(this.el).find('.modal-title').html(vars.title); 18 | $(this.el).find('.modal-body').html(vars.content); 19 | this.callback = callback; 20 | } 21 | }, 22 | doConfirm: function(vars){ 23 | $(this.el).modal('hide'); 24 | this.callback(); 25 | } 26 | }); 27 | return View; 28 | }); -------------------------------------------------------------------------------- /web-server/public/js/views/AlertErrorView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | initialize: function(){ 4 | this.setElement('#error-alert'); 5 | $(this.el).modal({ 6 | show : false, 7 | keyboard : true, 8 | backdrop : true 9 | }); 10 | }, 11 | display: function(vars, failback, onClose){ 12 | if(vars){ 13 | $(this.el).modal('show'); 14 | $(this.el).find('.modal-title').html(vars.title); 15 | $(this.el).find('.modal-body').html(vars.content); 16 | } 17 | if(typeof failback == typeof Function){ 18 | failback(); 19 | } 20 | if(typeof onClose == typeof Function){ 21 | $(this.el).unbind('hidden').on('hidden', onClose); 22 | } 23 | } 24 | }); 25 | return View; 26 | }); -------------------------------------------------------------------------------- /web-server/public/js/views/AlertGeneralView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | initialize: function(){ 4 | this.setElement('#general-alert'); 5 | $(this.el).modal({ 6 | show : false, 7 | keyboard : true, 8 | backdrop : true 9 | }); 10 | }, 11 | display: function(vars, url, timeout){ 12 | var me = this; 13 | me.redirected = false; 14 | if(vars){ 15 | $(me.el).modal('show'); 16 | $(me.el).find('.modal-title').html(vars.title); 17 | $(me.el).find('.modal-body').html(vars.content); 18 | if(url){ 19 | $('.modal-alert button').click(function(){ 20 | $(this).unbind('click'); 21 | me.redirected = true; 22 | Backbone.history.navigate(url); 23 | }); 24 | me.autoRedirect(url, timeout); 25 | } 26 | } 27 | }, 28 | autoRedirect: function(url, timeout){ 29 | var me = this; 30 | setTimeout(function(){ 31 | if(!me.redirected){ 32 | Backbone.history.navigate(url); 33 | } 34 | }, timeout || 3000); 35 | } 36 | }); 37 | return View; 38 | }); -------------------------------------------------------------------------------- /web-server/public/js/views/GlobalView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | el: 'body', 4 | initialize: function(options){ 5 | var me = this; 6 | me.options = options; 7 | me.bindGlobalEvents(); 8 | me.selectedFriend = undefined; 9 | me.messageContainer = $('#messenger-input-container'); 10 | me.friendModal = $('#add-friend-alert'); 11 | me.friendList = $('#friend-list'); 12 | me.messageArea = $('#messenger-area'); 13 | me.msgs = {}; 14 | me.options.eventPubSub.bind('getFriendList', function(){ 15 | me.messageArea.html(''); 16 | me.getFriends(); 17 | }); 18 | }, 19 | events: { 20 | 'click .goBack': 'goBack', 21 | 'click #nav-logout-btn': 'logout', 22 | 'click #friend-list li': 'selectChatFriend', 23 | 'keypress #messenger-chatbox': 'submitFriendMessage', 24 | 'click #show-add-friend-modal-btn': 'showAddFriendModal', 25 | 'click #search-friends-btn': 'findFriend', 26 | 'click #add-friend-btn': 'addFriend', 27 | 'click #sync-friend-list': 'getFriends' 28 | }, 29 | goBack: function(e){ 30 | e.preventDefault(); 31 | window.history.back(); 32 | }, 33 | logout: function(){ 34 | this.options.game.disconnect(); 35 | $('.username-placeholder').html(''); 36 | $('.chips-placeholder').html(''); 37 | $('.authed-section').hide(); 38 | $('.unauthed-section').show(); 39 | Backbone.history.navigate('#/login'); 40 | }, 41 | bindGlobalEvents: function(){ 42 | var me = this; 43 | pomelo.on('onUpdateMyself', function(data){ 44 | console.log('onUpdateMyself', data.user); 45 | me.options.game.session.user.chips = data.user.chips || me.options.game.session.user.chips; 46 | me.options.game.session.user.username = data.user.username || me.options.game.session.user.username; 47 | $('.username-placeholder').text(me.options.game.session.user.username); 48 | $('.chips-placeholder').text(me.options.game.session.user.chips); 49 | }); 50 | pomelo.on('onUserChat', function(res){ 51 | console.log('onUserChat', res); 52 | me.addMessage(res.username, res.username, res.msg); 53 | }); 54 | }, 55 | selectChatFriend: function(e){ 56 | var item = $(e.currentTarget); 57 | if(!item.attr('did')){ 58 | return false; 59 | } 60 | $('#friend-list li').removeClass('active'); 61 | item.addClass('active'); 62 | this.selectedFriend = $.trim(item.text()); 63 | this.messageContainer.show(); 64 | this.renderMessages(); 65 | }, 66 | renderMessages: function(){ 67 | this.msgs[this.selectedFriend] = this.msgs[this.selectedFriend] || []; 68 | this.messageArea.html(_.template($('#ChatMessageList').html(), { 69 | items : this.msgs[this.selectedFriend] 70 | })); 71 | }, 72 | submitFriendMessage: function(e){ 73 | if(e.keyCode != 13) return; 74 | var me = this; 75 | var msg = $(e.currentTarget); 76 | if($.trim(msg.val()).length > 0 && me.selectedFriend){ 77 | me.addMessage(me.selectedFriend, 'Me', msg.val()); 78 | me.options.game.sendMessage(msg.val(), me.selectedFriend, function(e){ 79 | if(e){ 80 | me.addMessage(me.selectedFriend, 'System', (e == 'user-not-online' ? 'user is not online.' : e), null, 'error'); 81 | } 82 | msg.scrollTop(msg[0].scrollHeight); 83 | msg.val(''); 84 | }); 85 | } 86 | }, 87 | addMessage: function(context, username, text, time, type){ 88 | if(time == null){ 89 | time = new Date(); 90 | }else if((time instanceof Date) === false){ 91 | time = new Date(time); 92 | } 93 | var message = { 94 | date : utils.timeString(time), 95 | user : username, 96 | msg : utils.toStaticHTML(text), 97 | type : type || '' 98 | }; 99 | this.msgs[context] = this.msgs[context] || []; 100 | this.msgs[context].push(message); 101 | this.messageArea.append(_.template($('#ChatMessageItem').html(), message)); 102 | this.messageArea.scrollTop(this.messageArea[0].scrollHeight); 103 | }, 104 | showAddFriendModal: function(){ 105 | this.friendModal.find('input').val(''); 106 | this.friendModal.find('#friend-results-list').html(''); 107 | this.friendModal.modal('show'); 108 | }, 109 | findFriend: function(){ 110 | var me = this; 111 | var search = this.friendModal.find('input'); 112 | var friendList = this.friendModal.find('#friend-results-list'); 113 | friendList.html(''); 114 | pomelo.request('game.userHandler.getUsers', { 115 | name : 'username', 116 | val : search.val() 117 | }, function(res){ 118 | if(res.code != 200){ 119 | console.log('error', res.error); 120 | }else{ 121 | console.log('userHandler.getUsers', res); 122 | me.renderUserList(friendList, res.matches, true); 123 | } 124 | }); 125 | }, 126 | renderUserList: function(dom, friends, showCheckbox){ 127 | dom.html(_.template($('#FriendListTmpl').html(), { 128 | friends : friends, 129 | showCheckbox : showCheckbox 130 | })); 131 | }, 132 | addFriend: function(){ 133 | var me = this; 134 | var friendList = this.friendModal.find('#friend-results-list'); 135 | var fid = friendList.find('input:checked').closest('li').attr('did'); 136 | pomelo.request('game.userHandler.addFriend', { 137 | friend : fid 138 | }, function(res){ 139 | if(res.code != 200){ 140 | console.log('error', res.error); 141 | }else{ 142 | console.log('chatHandler.addFriend', res); 143 | me.friendModal.modal('hide'); 144 | } 145 | }); 146 | }, 147 | getFriends: function(){ 148 | var me = this; 149 | pomelo.request('chat.chatHandler.getFriends', '', function(res){ 150 | if(res.code != 200){ 151 | console.log('error', res.error); 152 | }else{ 153 | console.log('getFriends', res); 154 | me.renderUserList(me.friendList, res.friends); 155 | } 156 | }); 157 | } 158 | }); 159 | return View; 160 | }); 161 | 162 | function GameClient(){ 163 | this.connection = {}; 164 | this.session = {}; 165 | this.table = {}; 166 | } 167 | /* gate handling */ 168 | GameClient.prototype.getEntry = function(cb){ 169 | var me = this; 170 | pomelo.init({ 171 | host : window.location.hostname, 172 | port : 3014, 173 | log : true 174 | }, function(){ 175 | pomelo.request('gate.gateHandler.queryEntry', {}, function(data){ 176 | console.log('queryEntry', data); 177 | me.connection = data; 178 | pomelo.disconnect(); 179 | cb(data); 180 | }); 181 | }); 182 | }; 183 | /* user management */ 184 | GameClient.prototype.register = function(userObj, cb, fb){ 185 | var me = this; 186 | me.init(function(){ 187 | pomelo.request('connector.entryHandler.register', userObj, function(res){ 188 | if(res.code == 500){ 189 | fb(res.error); 190 | }else{ 191 | console.log('register', res); 192 | cb(); 193 | } 194 | }); 195 | }); 196 | }; 197 | GameClient.prototype.connect = function(userObj, cb){ 198 | var me = this; 199 | me.init(function(){ 200 | pomelo.request('connector.entryHandler.connect', userObj, function(res){ 201 | if(res.code != 200){ 202 | cb(res.error); 203 | }else{ 204 | console.log('connect', res); 205 | me.session.token = res.token; 206 | me.session.user = res.user; 207 | cb(); 208 | } 209 | }); 210 | }); 211 | }; 212 | GameClient.prototype.disconnect = function(){ 213 | this.session = {}; 214 | pomelo.disconnect(); 215 | }; 216 | GameClient.prototype.init = function(cb){ 217 | pomelo.init({ 218 | host : this.connection.host, 219 | port : this.connection.port, 220 | log : true 221 | }, function(socket){ 222 | console.log('init', socket); 223 | cb(socket); 224 | }); 225 | }; 226 | GameClient.prototype.sendMessage = function(msg, target, cb, fb){ 227 | pomelo.request('chat.chatHandler.sendMessage', { 228 | content : msg, 229 | target : target 230 | }, function(res){ 231 | cb(res.error); 232 | }); 233 | }; -------------------------------------------------------------------------------- /web-server/public/js/views/LoginView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | el: '#login', 4 | initialize: function(options){ 5 | var me = this; 6 | me.options = options; 7 | options.eventPubSub.bind('initLoginView', function(){ 8 | $('.app-view').hide(); 9 | me.$el.show('fast'); 10 | me.formDom = me.$el.find('form'); 11 | me.formDom.val(''); 12 | }); 13 | }, 14 | events: { 15 | 'click #login-btn': 'login' 16 | }, 17 | login: function(e){ 18 | e.preventDefault(); 19 | var me = this; 20 | var obj = utils.collect(me.formDom); 21 | me.options.game.connect(obj, function(e){ 22 | if(e){ 23 | var content = e; 24 | if(e == 'invalid-user') 25 | content = 'The credentials you specified were invalid. Please try again.'; 26 | else if(e == 'duplicate-session') 27 | content = 'You are already logged in from another machine.'; 28 | Alerts.Error.display({ 29 | title : 'Could Not Login', 30 | content : content 31 | }); 32 | return; 33 | } 34 | me.formDom.find('input').val(''); 35 | Backbone.history.navigate('#/tables'); 36 | }); 37 | } 38 | }); 39 | return View; 40 | }); 41 | -------------------------------------------------------------------------------- /web-server/public/js/views/ProfileView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | el: '#profile', 4 | initialize: function(options){ 5 | var me = this; 6 | me.options = options; 7 | options.eventPubSub.bind('initProfileView', function(){ 8 | $('.app-view').hide(); 9 | me.$el.show('fast'); 10 | me.getPlayerInfo(function(user){ 11 | me.renderProfile(user); 12 | me.renderStats(user); 13 | }) 14 | }); 15 | }, 16 | events: { 17 | 'click #save-profile-btn': 'saveProfile', 18 | 'click #update-password-btn': 'updatePassword' 19 | }, 20 | getPlayerInfo: function(cb){ 21 | pomelo.request('game.userHandler.getUsers', '', function(res){ 22 | if(res.code != 200){ 23 | console.log('error', res.error); 24 | }else{ 25 | console.log('userHandler.getUsers', res); 26 | cb(res.matches[0]); 27 | } 28 | }); 29 | }, 30 | renderProfile: function(user){ 31 | $('#profile-container').html(_.template($('#ProfileUpdateTmpl').html(), { 32 | user : user 33 | })); 34 | }, 35 | renderStats: function(user){ 36 | $('#user-stats-container').html(_.template($('#UserStatsTmpl').html(), { 37 | user : user 38 | })); 39 | }, 40 | saveProfile: function(e){ 41 | e.preventDefault(); 42 | var form = $('#profile-form'); 43 | var obj = utils.collect(form); 44 | pomelo.request('game.userHandler.setProfile', obj, function(res){ 45 | if(res.code != 200){ 46 | console.log('error', res.error); 47 | }else{ 48 | console.log('userHandler.setProfile', res); 49 | Alerts.General.display({ 50 | title : 'Profile Updated', 51 | content : 'Your profile information has been updated.' 52 | }); 53 | } 54 | }); 55 | }, 56 | updatePassword: function(e){ 57 | e.preventDefault(); 58 | var form = $('#password-form'); 59 | var obj = utils.collect(form); 60 | if(obj.password !== obj.password2){ 61 | Alerts.General.display({ 62 | title : 'Password Update Failed', 63 | content : 'The confirmation password did not match the password you specified.' 64 | }); 65 | return; 66 | } 67 | pomelo.request('game.userHandler.setPassword', obj, function(res){ 68 | if(res.code != 200){ 69 | console.log('error', res.error); 70 | }else{ 71 | console.log('userHandler.setPassword', res); 72 | form.find('input').val(''); 73 | Alerts.General.display({ 74 | title : 'Profile Updated', 75 | content : 'Your profile information has been updated.' 76 | }); 77 | } 78 | }); 79 | } 80 | }); 81 | return View; 82 | }); 83 | -------------------------------------------------------------------------------- /web-server/public/js/views/RegisterView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | el: '#register', 4 | initialize: function(options){ 5 | var me = this; 6 | me.options = options; 7 | options.eventPubSub.bind('initRegisterView', function(){ 8 | $('.app-view').hide(); 9 | me.$el.show('fast'); 10 | }); 11 | }, 12 | events: { 13 | 'click #register-btn': 'register' 14 | }, 15 | register: function(e){ 16 | e.preventDefault(); 17 | var me = this; 18 | var form = this.$el.find('form'); 19 | var obj = utils.collect(form); 20 | me.options.game.register(obj, function(){ 21 | Alerts.General.display({ 22 | title : 'User Created', 23 | content : 'Your username "'+obj.username+'" has been created. You will now be redirected to the login page.' 24 | }); 25 | form.find('input').val(''); 26 | Backbone.history.navigate('#/login'); 27 | }, function(err){ 28 | var content = ''; 29 | if(err == 'user-exists') 30 | content = 'The username "'+obj.username+'" you specified has already been taken. Please try another username.'; 31 | Alerts.Error.display({ 32 | title : 'Could Not Register', 33 | content : content 34 | }); 35 | }); 36 | } 37 | }); 38 | return View; 39 | }); 40 | -------------------------------------------------------------------------------- /web-server/public/js/views/TableListView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone'], function($, Backbone){ 2 | var View = Backbone.View.extend({ 3 | el: '#tables', 4 | initialize: function(options){ 5 | var me = this; 6 | me.options = options; 7 | options.eventPubSub.bind('initTableListView', function(){ 8 | $('.app-view').hide(); 9 | me.$el.show('fast'); 10 | me.tableDom = $('#table-list'); 11 | me.getTables(); 12 | me.options.eventPubSub.trigger('getFriendList'); 13 | me.newTableContainer = $('#create-table-container'); 14 | $('#messenger-area').html(''); 15 | me.renderNewTable(); 16 | }); 17 | }, 18 | events: { 19 | 'click #register-btn': 'register', 20 | 'click #create-table-btn': 'createTable', 21 | 'click .join-table-btn': 'joinTable', 22 | 'click #sync-table-list': 'getTables' 23 | }, 24 | getTables: function(){ 25 | var me = this; 26 | pomelo.request('game.tableHandler.getTables', '', function(res){ 27 | if(res.code != 200){ 28 | console.log('error', res.error); 29 | }else{ 30 | console.log('getTables', res); 31 | me.renderTables(res.tables); 32 | } 33 | }); 34 | }, 35 | renderTables: function(tables){ 36 | this.tableDom.html(_.template($('#TableListTmpl').html(), tables)); 37 | }, 38 | renderNewTable: function(){ 39 | this.newTableContainer.html(_.template($('#newTableTmpl').html())); 40 | }, 41 | createTable: function(e){ 42 | e.preventDefault(); 43 | var me = this; 44 | pomelo.request('game.tableHandler.createTable', utils.collect(this.newTableContainer), function(res){ 45 | if(res.code != 200){ 46 | console.log('error', res.error); 47 | }else{ 48 | console.log('createTable', res); 49 | me.getTables(); 50 | // Backbone.history.navigate('#/table'); 51 | } 52 | }); 53 | }, 54 | joinTable: function(e){ 55 | e.preventDefault(); 56 | var tid = $(e.currentTarget).closest('tr').attr('did'); 57 | if(tid){ 58 | Backbone.history.navigate('#/table/'+tid); 59 | } 60 | } 61 | }); 62 | return View; 63 | }); 64 | -------------------------------------------------------------------------------- /web-server/public/js/views/TableView.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'backbone', 'collections/UserCollection', 'models/UserModel'], function($, Backbone, UserCollection, UserModel){ 2 | var View = Backbone.View.extend({ 3 | el: '#dashboard', 4 | initialize: function(options){ 5 | var me = this; 6 | me.options = options; 7 | me.userlist = me.$el.find('.list-group'); 8 | me.chatarea = me.$el.find('#chat-area'); 9 | me.historyarea = me.$el.find('#history-area'); 10 | me.countdownDom = $('#countdown-placeholder'); 11 | me.timeout = {}; 12 | me.bindEvents(); 13 | me.channel = new UserCollection(); 14 | options.eventPubSub.bind('initTableView', function(params){ 15 | if(!params || !params.tid) 16 | Backbone.history.navigate('#/tables'); 17 | $('.app-view').hide(); 18 | me.$el.show('fast'); 19 | me.$el.find('textarea').val(''); 20 | me.gameSession = undefined; 21 | me.resetCountDown(); 22 | me.historyarea.html(''); 23 | me.actionIndex = 0; 24 | me.joinTable(params.tid, function(){ 25 | me.renderGame(); 26 | }); 27 | }); 28 | }, 29 | events: { 30 | 'click #login-btn': 'login', 31 | 'click .list-group-item': 'showMemberProfile', 32 | 'keypress #chat-container textarea': 'submitMessage', 33 | 'click #join-game-btn': 'joinGame', 34 | 'click #start-game-btn': 'startGame', 35 | 'click .game-action': 'doAction', 36 | 'click #create-new-game-btn': 'resetGame', 37 | 'click #leave-table-btn': 'leaveTable', 38 | 'click #remove-bots-btn': 'removeBots' 39 | }, 40 | showMemberProfile: function(e){ 41 | e.preventDefault(); 42 | var dom = $(e.currentTarget); 43 | if(dom.attr('data-original-title')){ 44 | return; 45 | } 46 | pomelo.request('game.userHandler.getUsers', { 47 | name : 'id', 48 | val : dom.attr('did') 49 | }, function(res){ 50 | if(res.code != 200){ 51 | console.log('error', res.error); 52 | }else{ 53 | console.log('userHandler.getUsers', res); 54 | dom.popover({ 55 | html : true, 56 | trigger : 'click', 57 | content : function(){ 58 | return _.template($('#UserStatsTmpl').html(), { 59 | user : res.matches[0] || {} 60 | }); 61 | } 62 | }); 63 | dom.popover('toggle'); 64 | } 65 | }); 66 | }, 67 | goBack: function(e){ 68 | e.preventDefault(); 69 | window.history.back(); 70 | }, 71 | buildUserList: function(){ 72 | this.userlist.html(_.template($('#UserListItem').html(), { 73 | items : this.channel.models, 74 | userId : this.options.game.session.user.id 75 | })); 76 | }, 77 | bindEvents: function(){ 78 | var me = this; 79 | pomelo.on('onUpdateUsers', function(data){ 80 | console.log('onUpdateUsers', data.members); 81 | me.channel = new UserCollection(data.members); 82 | me.buildUserList(); 83 | }); 84 | pomelo.on('onChat', function(data){ 85 | console.log('onChat', data); 86 | me.addMessage(data.username, data.msg); 87 | }); 88 | pomelo.on('onAdd', function(data){ 89 | console.log('onAdd', data); 90 | me.channel.add(data.user); 91 | me.buildUserList(); 92 | me.updateHistory(''+data.user.username+' joined table'); 93 | }); 94 | pomelo.on('onLeave', function(data){ 95 | console.log('onLeave', data); 96 | me.channel.remove(data.user.id); 97 | me.removePlayerFromGame(data.user.id); 98 | me.buildUserList(); 99 | me.renderGame(); 100 | me.updateHistory(''+data.user.username+' left table'); 101 | }); 102 | pomelo.on('disconnect', function(reason){ 103 | console.log('disconnect', reason); 104 | }); 105 | pomelo.on('onTableJoin', function(data){ 106 | console.log('onTableJoin', data); 107 | me.gameSession.playersToAdd.push(data.msg); 108 | me.renderGame(); 109 | me.updateHistory(''+data.msg.playerName+' sat down'); 110 | }); 111 | pomelo.on('onTableEvent', function(data){ 112 | console.log('onTableEvent', data); 113 | me.gameSession = data.msg; 114 | me.handleTimeout(); 115 | me.renderGame(); 116 | if(me.gameSession.actions.length == 0) 117 | me.actionIndex = 0; 118 | while(me.actionIndex < me.gameSession.actions.length){ 119 | var action = me.gameSession.actions[me.actionIndex]; 120 | me.updateHistory(''+action.playerName+' performed '+action.action 121 | +''+(action.amount ? (' for '+action.amount+' chips') : '')); 122 | ++me.actionIndex; 123 | } 124 | if(me.gameSession.gameWinners.length){ 125 | for(var i=0;i'+winner.playerName+' wins '+winner.amount+' chips'); 128 | } 129 | } 130 | }); 131 | }, 132 | handleTimeout: function(){ 133 | var me = this; 134 | if(me.gameSession.state == 'JOIN') 135 | me.resetCountDown(); 136 | if(me.gameSession.state == 'IN_PROGRESS' && (me.timeout.gid !== me.gameSession.id || me.timeout.tid !== me.gameSession.tid || me.timeout.player !== me.gameSession.currentPlayer)){ 137 | me.resetCountDown(); 138 | me.timeout = { 139 | player : me.gameSession.currentPlayer, 140 | gid : me.gameSession.id, 141 | tid : me.gameSession.tid, 142 | count : me.gameSession.gameMode == 'normal' ? 30 : 15, 143 | ref : me.initCountdown() 144 | }; 145 | } 146 | }, 147 | initCountdown: function(){ 148 | var me = this; 149 | me.resetCountDown(); 150 | return setInterval(function(){ 151 | if(typeof me.gameSession.currentPlayer == 'number'){ 152 | me.countdownDom.html(--me.timeout.count+' seconds left for '+me.gameSession.players[me.gameSession.currentPlayer].playerName+''); 153 | } 154 | if(me.timeout.count <= 0 || typeof me.gameSession.currentPlayer != 'number') 155 | me.resetCountDown(); 156 | }, 1000); 157 | }, 158 | resetCountDown: function(){ 159 | this.countdownDom.html(''); 160 | clearInterval(this.timeout.ref); 161 | }, 162 | removePlayerFromGame: function(uid){ 163 | var i; 164 | if(this.gameSession){ 165 | for(i in this.gameSession.players) 166 | if(this.gameSession.players[i].id === uid) 167 | this.gameSession.playersToRemove.push(i); 168 | for(i in this.gameSession.playersToAdd) 169 | if(this.gameSession.playersToAdd[i].id === uid) 170 | this.gameSession.playersToAdd.splice(i, 1); 171 | } 172 | }, 173 | submitMessage: function(e){ 174 | var me = this; 175 | if(e.keyCode != 13) return; 176 | var msg = $(e.currentTarget); 177 | if($.trim(msg.val()).length > 0){ 178 | this.options.game.sendMessage(msg.val(), 'table', function(e){ 179 | if(e){ 180 | me.addMessage('System', (e == 'user-not-online' ? 'user is not online.' : e), null, 'error'); 181 | } 182 | msg.scrollTop(msg[0].scrollHeight); 183 | msg.val(''); 184 | }); 185 | } 186 | }, 187 | addMessage: function(username, text, time){ 188 | if(time == null){ 189 | time = new Date(); 190 | }else if((time instanceof Date) === false){ 191 | time = new Date(time); 192 | } 193 | this.chatarea.append(_.template($('#ChatMessageItem').html(), { 194 | date : utils.timeString(time), 195 | user : username, 196 | msg : utils.toStaticHTML(text) 197 | })); 198 | this.chatarea.scrollTop(this.chatarea[0].scrollHeight); 199 | }, 200 | renderGame: function(){ 201 | $('#poker-container .panel-body').html(_.template($('#CurrentGameView').html(), { 202 | gameSession : this.gameSession, 203 | user : this.options.game.session.user, 204 | convertCard : this.convertCard, 205 | timeout : this.timeout 206 | })); 207 | }, 208 | joinTable: function(tid){ 209 | var me = this; 210 | pomelo.request('game.tableHandler.joinTable', { 211 | tid : tid 212 | }, function(res){ 213 | if(res.code != 200){ 214 | console.log('error', res.error); 215 | }else{ 216 | console.log('joinTable', res); 217 | me.options.game.session.tid = res.tid; 218 | } 219 | }); 220 | }, 221 | leaveTable: function(e){ 222 | e.preventDefault(); 223 | var me = this; 224 | pomelo.request('game.tableHandler.leaveTable', '', function(res){ 225 | if(res.code != 200){ 226 | console.log('error', res.error); 227 | }else{ 228 | console.log('leaveTable', res); 229 | delete me.options.game.session.tid; 230 | me.channel.reset(); 231 | Backbone.history.navigate('#/tables'); 232 | } 233 | }); 234 | }, 235 | resetGame: function(){ 236 | delete this.gameSession; 237 | this.renderGame(); 238 | }, 239 | joinGame: function(e){ 240 | e.preventDefault(); 241 | pomelo.request('game.tableHandler.joinGame', { 242 | buyIn : this.$el.find('input[name="user-buyin-input"]').val() 243 | }, function(res){ 244 | if(res.code != 200){ 245 | console.log('error', res.error); 246 | }else{ 247 | console.log('joinGame', res); 248 | } 249 | }); 250 | }, 251 | startGame: function(e){ 252 | e.preventDefault(); 253 | pomelo.request('game.tableHandler.startGame', '', function(res){ 254 | if(res.code != 200){ 255 | console.log('error', res.error); 256 | }else{ 257 | console.log('startGame', res); 258 | } 259 | }); 260 | }, 261 | doAction: function(e){ 262 | var me = this; 263 | var params = { 264 | action : $(e.currentTarget).attr('did') 265 | }; 266 | if(params.action == 'bet') 267 | params.amt = me.$el.find('input[name="betAmount"]').val(); 268 | pomelo.request('game.tableHandler.execute', params, function(res){ 269 | if(res.code != 200){ 270 | console.log('error', res.error); 271 | }else{ 272 | console.log('execute', res); 273 | if(params.action == 'bet') 274 | me.$el.find('input[name="betAmount"]').val(''); 275 | } 276 | }); 277 | }, 278 | convertCard: function(card){ 279 | var str = ''; 280 | switch(card[0]){ 281 | case 'J': str += 'jack'; break; 282 | case 'Q': str += 'queen'; break; 283 | case 'K': str += 'king'; break; 284 | case 'A': str += 'ace'; break; 285 | case 'T': str += '10'; break; 286 | default : str += card[0]; break; 287 | } 288 | str += '_of_'; 289 | switch(card[1]){ 290 | case 'D': str += 'diamonds'; break; 291 | case 'S': str += 'spades'; break; 292 | case 'C': str += 'clubs'; break; 293 | case 'H': str += 'hearts'; break; 294 | } 295 | return str += '.png'; 296 | }, 297 | updateHistory: function(msg){ 298 | this.historyarea.append('
'+msg+'
'); 299 | this.historyarea.scrollTop(this.historyarea[0].scrollHeight); 300 | }, 301 | removeBots: function(){ 302 | pomelo.request('game.tableHandler.removeBots', '', function(res){ 303 | if(res.code != 200){ 304 | console.log('error', res.error); 305 | }else{ 306 | console.log('removed'); 307 | } 308 | }); 309 | } 310 | }); 311 | return View; 312 | }); 313 | --------------------------------------------------------------------------------