commit cd7f17778fa46ab5f6f001cb8bd046341437ca73 Author: Edward Yang Date: Fri Mar 20 14:21:44 2015 -0700 initial commit of poker game diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a1106f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +npm-debug.log +.idea +node_modules +game-server/logs \ No newline at end of file diff --git a/game-server/app.js b/game-server/app.js new file mode 100644 index 0000000..fbfc744 --- /dev/null +++ b/game-server/app.js @@ -0,0 +1,36 @@ +var pomelo = require('pomelo'); +var routeUtil = require('./app/util/routeUtil'); +var abuseFilter = require('./app/servers/game/filter/abuseFilter'); +var tableComponent = require('./app/components/tableComponent'); +var botComponent = require('./app/components/botComponent'); +var stateComponent = require('./app/components/stateComponent'); +var ChatService = require('./app/services/chatService'); + +var app = pomelo.createApp(); +app.set('name', 'poker-game-stack'); + +app.configure('production|development', function(){ + app.route('game', routeUtil.game); + app.filter(pomelo.timeout()); + app.set('session', require('../shared/config/session.json')); +}); + +app.configure('production|development', 'game', function(){ + app.filter(abuseFilter()); + app.load(tableComponent); + app.load(botComponent); +// app.load(stateComponent); +}); + +app.configure('production|development', 'chat', function(){ + app.set('chatService', new ChatService(app)); +}); + +//var timeReport = require('./app/module/timeReport'); +//app.registerAdmin(timeReport, {app: app}); + +app.start(); + +process.on('uncaughtException', function(err){ + console.error('Caught exception: ' + err.stack); +}); \ No newline at end of file diff --git a/game-server/app/components/botComponent.js b/game-server/app/components/botComponent.js new file mode 100644 index 0000000..06c18ae --- /dev/null +++ b/game-server/app/components/botComponent.js @@ -0,0 +1,8 @@ +var BotService = require('../services/botService'); + +module.exports = function(app, opts){ + var service = new BotService(app, opts); + app.set('botService', service, true); + service.name = '__bot__'; + return service; +}; \ No newline at end of file diff --git a/game-server/app/components/stateComponent.js b/game-server/app/components/stateComponent.js new file mode 100644 index 0000000..f393b7c --- /dev/null +++ b/game-server/app/components/stateComponent.js @@ -0,0 +1,8 @@ +var BotService = require('../services/stateService'); + +module.exports = function(app, opts){ + var service = new StateService(app, opts); + app.set('stateService', service, true); + service.name = '__state__'; + return service; +}; \ No newline at end of file diff --git a/game-server/app/components/tableComponent.js b/game-server/app/components/tableComponent.js new file mode 100644 index 0000000..b5103f3 --- /dev/null +++ b/game-server/app/components/tableComponent.js @@ -0,0 +1,8 @@ +var TableService = require('../services/tableService'); + +module.exports = function(app, opts){ + var service = new TableService(app, opts); + app.set('tableService', service, true); + service.name = '__table__'; + return service; +}; \ No newline at end of file diff --git a/game-server/app/game/game.js b/game-server/app/game/game.js new file mode 100644 index 0000000..10c0519 --- /dev/null +++ b/game-server/app/game/game.js @@ -0,0 +1,83 @@ +var logger = require('pomelo-logger').getLogger('game-log', __filename); +var uuid = require('node-uuid'); + + + +module.exports = Game = function(smallBlind, bigBlind){ + this.id = uuid.v1(); + this.smallBlind = smallBlind; + this.bigBlind = bigBlind; + this.pot = 0; + this.roundName = 'Deal'; //Start the first round + this.betName = 'bet'; //bet,raise,re-raise,cap + this.bets = []; + this.roundBets = []; + this.deck = []; + this.board = []; + fillDeck(this.deck); +} + +function fillDeck(deck){ + deck.push('AS'); + deck.push('KS'); + deck.push('QS'); + deck.push('JS'); + deck.push('TS'); + deck.push('9S'); + deck.push('8S'); + deck.push('7S'); + deck.push('6S'); + deck.push('5S'); + deck.push('4S'); + deck.push('3S'); + deck.push('2S'); + deck.push('AH'); + deck.push('KH'); + deck.push('QH'); + deck.push('JH'); + deck.push('TH'); + deck.push('9H'); + deck.push('8H'); + deck.push('7H'); + deck.push('6H'); + deck.push('5H'); + deck.push('4H'); + deck.push('3H'); + deck.push('2H'); + deck.push('AD'); + deck.push('KD'); + deck.push('QD'); + deck.push('JD'); + deck.push('TD'); + deck.push('9D'); + deck.push('8D'); + deck.push('7D'); + deck.push('6D'); + deck.push('5D'); + deck.push('4D'); + deck.push('3D'); + deck.push('2D'); + deck.push('AC'); + deck.push('KC'); + deck.push('QC'); + deck.push('JC'); + deck.push('TC'); + deck.push('9C'); + deck.push('8C'); + deck.push('7C'); + deck.push('6C'); + deck.push('5C'); + deck.push('4C'); + deck.push('3C'); + deck.push('2C'); + + //Shuffle the deck array with Fisher-Yates + var i, j, tempi, tempj; + for(i=0;i< deck.length;i+=1){ + j = Math.floor(Math.random() * (i + 1)); + tempi = deck[i]; + tempj = deck[j]; + deck[i] = tempj; + deck[j] = tempi; + } +} \ No newline at end of file diff --git a/game-server/app/game/hand.js b/game-server/app/game/hand.js new file mode 100644 index 0000000..dd1fc42 --- /dev/null +++ b/game-server/app/game/hand.js @@ -0,0 +1,446 @@ +module.exports = Hand = function(cards){ + this.cards = cards; + var result = rankHandInt({ + cards : cards + }); + this.rank = result.rank; + this.message = result.message; + return this; +} + +function rankHandInt(hand){ + var rank, message, handRanks, handSuits, ranks, suits, cards, result, i; + rank = 0.0000; + message = ''; + handRanks = []; + handSuits = []; + + for(i=0;i< hand.cards.length;i+=1){ + handRanks[i] = hand.cards[i].substr(0, 1); + handSuits[i] = hand.cards[i].substr(1, 1); + } + + ranks = handRanks.sort().toString().replace(/\W/g, ""); + suits = handSuits.sort().toString().replace(/\W/g, ""); + cards = hand.cards.toString(); + + //Four of a kind + if(rank === 0){ + if(ranks.indexOf('AAAA') > -1){rank = 292 + rankKickers(ranks.replace('AAAA', ''), 1); } + if(ranks.indexOf('KKKK') > -1 && rank === 0){rank = 291 + rankKickers(ranks.replace('KKKK', ''), 1); } + if(ranks.indexOf('QQQQ') > -1 && rank === 0){rank = 290 + rankKickers(ranks.replace('QQQQ', ''), 1); } + if(ranks.indexOf('JJJJ') > -1 && rank === 0){rank = 289 + rankKickers(ranks.replace('JJJJ', ''), 1); } + if(ranks.indexOf('TTTT') > -1 && rank === 0){rank = 288 + rankKickers(ranks.replace('TTTT', ''), 1); } + if(ranks.indexOf('9999') > -1 && rank === 0){rank = 287 + rankKickers(ranks.replace('9999', ''), 1); } + if(ranks.indexOf('8888') > -1 && rank === 0){rank = 286 + rankKickers(ranks.replace('8888', ''), 1); } + if(ranks.indexOf('7777') > -1 && rank === 0){rank = 285 + rankKickers(ranks.replace('7777', ''), 1); } + if(ranks.indexOf('6666') > -1 && rank === 0){rank = 284 + rankKickers(ranks.replace('6666', ''), 1); } + if(ranks.indexOf('5555') > -1 && rank === 0){rank = 283 + rankKickers(ranks.replace('5555', ''), 1); } + if(ranks.indexOf('4444') > -1 && rank === 0){rank = 282 + rankKickers(ranks.replace('4444', ''), 1); } + if(ranks.indexOf('3333') > -1 && rank === 0){rank = 281 + rankKickers(ranks.replace('3333', ''), 1); } + if(ranks.indexOf('2222') > -1 && rank === 0){rank = 280 + rankKickers(ranks.replace('2222', ''), 1); } + if(rank !== 0){message = 'Four of a kind'; } + } + + //Full House + if(rank === 0){ + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('KK') > -1){rank = 279; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 278; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 277; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 276; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 275; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 274; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 273; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 272; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 271; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 270; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 269; } + if(ranks.indexOf('AAA') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 268; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 267; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 266; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 265; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 264; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 263; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 262; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 261; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 260; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 259; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 258; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 257; } + if(ranks.indexOf('KKK') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 256; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 255; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 254; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 253; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 252; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 251; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 250; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 249; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 248; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 247; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 246; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 245; } + if(ranks.indexOf('QQQ') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 244; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 243; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 242; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 241; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 240; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 239; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 238; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 237; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 236; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 235; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 234; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 233; } + if(ranks.indexOf('JJJ') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 232; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 231; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 230; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 229; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 228; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 227; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 226; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 225; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 224; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 223; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 222; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 221; } + if(ranks.indexOf('TTT') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 220; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 219; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 218; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 217; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 216; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 215; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 214; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 213; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 212; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 211; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 210; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 209; } + if(ranks.indexOf('999') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 208; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 207; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 206; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 205; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 204; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 203; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 202; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 201; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 200; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 199; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 198; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 197; } + if(ranks.indexOf('888') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 196; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 195; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 194; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 193; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 192; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 191; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 190; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 189; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 188; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 187; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 186; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 185; } + if(ranks.indexOf('777') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 184; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 183; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 182; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 181; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 180; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 179; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 178; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 177; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 176; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 175; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 174; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 173; } + if(ranks.indexOf('666') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 172; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 171; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 170; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 169; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 168; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 167; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 166; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 165; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 164; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 163; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 162; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 161; } + if(ranks.indexOf('555') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 160; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 159; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 158; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 157; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 156; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 155; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 154; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 153; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 152; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 151; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 150; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 149; } + if(ranks.indexOf('444') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 148; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 147; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 146; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 145; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 144; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 143; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 142; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 141; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 140; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 139; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 138; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 137; } + if(ranks.indexOf('333') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 136; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('AA') > -1 && rank === 0){rank = 135; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('KK') > -1 && rank === 0){rank = 134; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 133; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 132; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 131; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 130; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 129; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 128; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 127; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 126; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 125; } + if(ranks.indexOf('222') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 124; } + if(rank !== 0){message = 'Full House'; } + } + + //Flush + if(rank === 0){ + if(suits.indexOf('CCCCC') > -1 || suits.indexOf('DDDDD') > -1 || suits.indexOf('HHHHH') > -1 || suits.indexOf('SSSSS') > -1){rank = 123; message = 'Flush';} + + //Straight flush + if(cards.indexOf('TC') > -1 && cards.indexOf('JC') > -1 && cards.indexOf('QC') > -1 && cards.indexOf('KC') > -1 && cards.indexOf('AC') > -1 && rank === 123){rank = 302; message = 'Straight Flush';} + if(cards.indexOf('TD') > -1 && cards.indexOf('JD') > -1 && cards.indexOf('QD') > -1 && cards.indexOf('KD') > -1 && cards.indexOf('AD') > -1 && rank === 123){rank = 302; message = 'Straight Flush';} + if(cards.indexOf('TH') > -1 && cards.indexOf('JH') > -1 && cards.indexOf('QH') > -1 && cards.indexOf('KH') > -1 && cards.indexOf('AH') > -1 && rank === 123){rank = 302; message = 'Straight Flush';} + if(cards.indexOf('TS') > -1 && cards.indexOf('JS') > -1 && cards.indexOf('QS') > -1 && cards.indexOf('KS') > -1 && cards.indexOf('AS') > -1 && rank === 123){rank = 302; message = 'Straight Flush';} + if(cards.indexOf('9C') > -1 && cards.indexOf('TC') > -1 && cards.indexOf('JC') > -1 && cards.indexOf('QC') > -1 && cards.indexOf('KC') > -1 && rank === 123){rank = 301; message = 'Straight Flush';} + if(cards.indexOf('9D') > -1 && cards.indexOf('TD') > -1 && cards.indexOf('JD') > -1 && cards.indexOf('QD') > -1 && cards.indexOf('KD') > -1 && rank === 123){rank = 301; message = 'Straight Flush';} + if(cards.indexOf('9H') > -1 && cards.indexOf('TH') > -1 && cards.indexOf('JH') > -1 && cards.indexOf('QH') > -1 && cards.indexOf('KH') > -1 && rank === 123){rank = 301; message = 'Straight Flush';} + if(cards.indexOf('9S') > -1 && cards.indexOf('TS') > -1 && cards.indexOf('JS') > -1 && cards.indexOf('QS') > -1 && cards.indexOf('KS') > -1 && rank === 123){rank = 301; message = 'Straight Flush';} + if(cards.indexOf('8C') > -1 && cards.indexOf('9C') > -1 && cards.indexOf('TC') > -1 && cards.indexOf('JC') > -1 && cards.indexOf('QC') > -1 && rank === 123){rank = 300; message = 'Straight Flush';} + if(cards.indexOf('8D') > -1 && cards.indexOf('9D') > -1 && cards.indexOf('TD') > -1 && cards.indexOf('JD') > -1 && cards.indexOf('QD') > -1 && rank === 123){rank = 300; message = 'Straight Flush';} + if(cards.indexOf('8H') > -1 && cards.indexOf('9H') > -1 && cards.indexOf('TH') > -1 && cards.indexOf('JH') > -1 && cards.indexOf('QH') > -1 && rank === 123){rank = 300; message = 'Straight Flush';} + if(cards.indexOf('8S') > -1 && cards.indexOf('9S') > -1 && cards.indexOf('TS') > -1 && cards.indexOf('JS') > -1 && cards.indexOf('QS') > -1 && rank === 123){rank = 300; message = 'Straight Flush';} + if(cards.indexOf('7C') > -1 && cards.indexOf('8C') > -1 && cards.indexOf('9C') > -1 && cards.indexOf('TC') > -1 && cards.indexOf('JC') > -1 && rank === 123){rank = 299; message = 'Straight Flush';} + if(cards.indexOf('7D') > -1 && cards.indexOf('8D') > -1 && cards.indexOf('9D') > -1 && cards.indexOf('TD') > -1 && cards.indexOf('JD') > -1 && rank === 123){rank = 299; message = 'Straight Flush';} + if(cards.indexOf('7H') > -1 && cards.indexOf('8H') > -1 && cards.indexOf('9H') > -1 && cards.indexOf('TH') > -1 && cards.indexOf('JH') > -1 && rank === 123){rank = 299; message = 'Straight Flush';} + if(cards.indexOf('7S') > -1 && cards.indexOf('8S') > -1 && cards.indexOf('9S') > -1 && cards.indexOf('TS') > -1 && cards.indexOf('JS') > -1 && rank === 123){rank = 299; message = 'Straight Flush';} + if(cards.indexOf('6C') > -1 && cards.indexOf('7C') > -1 && cards.indexOf('8C') > -1 && cards.indexOf('9C') > -1 && cards.indexOf('TC') > -1 && rank === 123){rank = 298; message = 'Straight Flush';} + if(cards.indexOf('6D') > -1 && cards.indexOf('7D') > -1 && cards.indexOf('8D') > -1 && cards.indexOf('9D') > -1 && cards.indexOf('TD') > -1 && rank === 123){rank = 298; message = 'Straight Flush';} + if(cards.indexOf('6H') > -1 && cards.indexOf('7H') > -1 && cards.indexOf('8H') > -1 && cards.indexOf('9H') > -1 && cards.indexOf('TH') > -1 && rank === 123){rank = 298; message = 'Straight Flush';} + if(cards.indexOf('6S') > -1 && cards.indexOf('7S') > -1 && cards.indexOf('8S') > -1 && cards.indexOf('9S') > -1 && cards.indexOf('TS') > -1 && rank === 123){rank = 298; message = 'Straight Flush';} + if(cards.indexOf('5C') > -1 && cards.indexOf('6C') > -1 && cards.indexOf('7C') > -1 && cards.indexOf('8C') > -1 && cards.indexOf('9C') > -1 && rank === 123){rank = 297; message = 'Straight Flush';} + if(cards.indexOf('5D') > -1 && cards.indexOf('6D') > -1 && cards.indexOf('7D') > -1 && cards.indexOf('8D') > -1 && cards.indexOf('9D') > -1 && rank === 123){rank = 297; message = 'Straight Flush';} + if(cards.indexOf('5H') > -1 && cards.indexOf('6H') > -1 && cards.indexOf('7H') > -1 && cards.indexOf('8H') > -1 && cards.indexOf('9H') > -1 && rank === 123){rank = 297; message = 'Straight Flush';} + if(cards.indexOf('5S') > -1 && cards.indexOf('6S') > -1 && cards.indexOf('7S') > -1 && cards.indexOf('8S') > -1 && cards.indexOf('9S') > -1 && rank === 123){rank = 297; message = 'Straight Flush';} + if(cards.indexOf('4C') > -1 && cards.indexOf('5C') > -1 && cards.indexOf('6C') > -1 && cards.indexOf('7C') > -1 && cards.indexOf('8C') > -1 && rank === 123){rank = 296; message = 'Straight Flush';} + if(cards.indexOf('4D') > -1 && cards.indexOf('5D') > -1 && cards.indexOf('6D') > -1 && cards.indexOf('7D') > -1 && cards.indexOf('8D') > -1 && rank === 123){rank = 296; message = 'Straight Flush';} + if(cards.indexOf('4H') > -1 && cards.indexOf('5H') > -1 && cards.indexOf('6H') > -1 && cards.indexOf('7H') > -1 && cards.indexOf('8H') > -1 && rank === 123){rank = 296; message = 'Straight Flush';} + if(cards.indexOf('4S') > -1 && cards.indexOf('5S') > -1 && cards.indexOf('6S') > -1 && cards.indexOf('7S') > -1 && cards.indexOf('8S') > -1 && rank === 123){rank = 296; message = 'Straight Flush';} + if(cards.indexOf('3C') > -1 && cards.indexOf('4C') > -1 && cards.indexOf('5C') > -1 && cards.indexOf('6C') > -1 && cards.indexOf('7C') > -1 && rank === 123){rank = 295; message = 'Straight Flush';} + if(cards.indexOf('3D') > -1 && cards.indexOf('4D') > -1 && cards.indexOf('5D') > -1 && cards.indexOf('6D') > -1 && cards.indexOf('7D') > -1 && rank === 123){rank = 295; message = 'Straight Flush';} + if(cards.indexOf('3H') > -1 && cards.indexOf('4H') > -1 && cards.indexOf('5H') > -1 && cards.indexOf('6H') > -1 && cards.indexOf('7H') > -1 && rank === 123){rank = 295; message = 'Straight Flush';} + if(cards.indexOf('3S') > -1 && cards.indexOf('4S') > -1 && cards.indexOf('5S') > -1 && cards.indexOf('6S') > -1 && cards.indexOf('7S') > -1 && rank === 123){rank = 295; message = 'Straight Flush';} + if(cards.indexOf('2C') > -1 && cards.indexOf('3C') > -1 && cards.indexOf('4C') > -1 && cards.indexOf('5C') > -1 && cards.indexOf('6C') > -1 && rank === 123){rank = 294; message = 'Straight Flush';} + if(cards.indexOf('2D') > -1 && cards.indexOf('3D') > -1 && cards.indexOf('4D') > -1 && cards.indexOf('5D') > -1 && cards.indexOf('6D') > -1 && rank === 123){rank = 294; message = 'Straight Flush';} + if(cards.indexOf('2H') > -1 && cards.indexOf('3H') > -1 && cards.indexOf('4H') > -1 && cards.indexOf('5H') > -1 && cards.indexOf('6H') > -1 && rank === 123){rank = 294; message = 'Straight Flush';} + if(cards.indexOf('2S') > -1 && cards.indexOf('3S') > -1 && cards.indexOf('4S') > -1 && cards.indexOf('5S') > -1 && cards.indexOf('6S') > -1 && rank === 123){rank = 294; message = 'Straight Flush';} + if(cards.indexOf('AC') > -1 && cards.indexOf('2C') > -1 && cards.indexOf('3C') > -1 && cards.indexOf('4C') > -1 && cards.indexOf('5C') > -1 && rank === 123){rank = 293; message = 'Straight Flush';} + if(cards.indexOf('AS') > -1 && cards.indexOf('2S') > -1 && cards.indexOf('3S') > -1 && cards.indexOf('4S') > -1 && cards.indexOf('5S') > -1 && rank === 123){rank = 293; message = 'Straight Flush';} + if(cards.indexOf('AH') > -1 && cards.indexOf('2H') > -1 && cards.indexOf('3H') > -1 && cards.indexOf('4H') > -1 && cards.indexOf('5H') > -1 && rank === 123){rank = 293; message = 'Straight Flush';} + if(cards.indexOf('AD') > -1 && cards.indexOf('2D') > -1 && cards.indexOf('3D') > -1 && cards.indexOf('4D') > -1 && cards.indexOf('5D') > -1 && rank === 123){rank = 293; message = 'Straight Flush';} + if(rank === 123){rank = rank + rankKickers(ranks, 5);} + + } + + //Straight + if(rank === 0){ + if(cards.indexOf('T') > -1 && cards.indexOf('J') > -1 && cards.indexOf('Q') > -1 && cards.indexOf('K') > -1 && cards.indexOf('A') > -1){rank = 122; } + if(cards.indexOf('9') > -1 && cards.indexOf('T') > -1 && cards.indexOf('J') > -1 && cards.indexOf('Q') > -1 && cards.indexOf('K') > -1 && rank === 0){rank = 121; } + if(cards.indexOf('8') > -1 && cards.indexOf('9') > -1 && cards.indexOf('T') > -1 && cards.indexOf('J') > -1 && cards.indexOf('Q') > -1 && rank === 0){rank = 120; } + if(cards.indexOf('7') > -1 && cards.indexOf('8') > -1 && cards.indexOf('9') > -1 && cards.indexOf('T') > -1 && cards.indexOf('J') > -1 && rank === 0){rank = 119; } + if(cards.indexOf('6') > -1 && cards.indexOf('7') > -1 && cards.indexOf('8') > -1 && cards.indexOf('9') > -1 && cards.indexOf('T') > -1 && rank === 0){rank = 118; } + if(cards.indexOf('5') > -1 && cards.indexOf('6') > -1 && cards.indexOf('7') > -1 && cards.indexOf('8') > -1 && cards.indexOf('9') > -1 && rank === 0){rank = 117; } + if(cards.indexOf('4') > -1 && cards.indexOf('5') > -1 && cards.indexOf('6') > -1 && cards.indexOf('7') > -1 && cards.indexOf('8') > -1 && rank === 0){rank = 116; } + if(cards.indexOf('3') > -1 && cards.indexOf('4') > -1 && cards.indexOf('5') > -1 && cards.indexOf('6') > -1 && cards.indexOf('7') > -1 && rank === 0){rank = 115; } + if(cards.indexOf('2') > -1 && cards.indexOf('3') > -1 && cards.indexOf('4') > -1 && cards.indexOf('5') > -1 && cards.indexOf('6') > -1 && rank === 0){rank = 114; } + if(cards.indexOf('A') > -1 && cards.indexOf('2') > -1 && cards.indexOf('3') > -1 && cards.indexOf('4') > -1 && cards.indexOf('5') > -1 && rank === 0){rank = 113; } + if(rank !== 0){message = 'Straight'; } + } + + //Three of a kind + if(rank === 0){ + if(ranks.indexOf('AAA') > -1){rank = 112 + rankKickers(ranks.replace('AAA', ''), 2); } + if(ranks.indexOf('KKK') > -1 && rank === 0){rank = 111 + rankKickers(ranks.replace('KKK', ''), 2); } + if(ranks.indexOf('QQQ') > -1 && rank === 0){rank = 110 + rankKickers(ranks.replace('QQQ', ''), 2); } + if(ranks.indexOf('JJJ') > -1 && rank === 0){rank = 109 + rankKickers(ranks.replace('JJJ', ''), 2); } + if(ranks.indexOf('TTT') > -1 && rank === 0){rank = 108 + rankKickers(ranks.replace('TTT', ''), 2); } + if(ranks.indexOf('999') > -1 && rank === 0){rank = 107 + rankKickers(ranks.replace('999', ''), 2); } + if(ranks.indexOf('888') > -1 && rank === 0){rank = 106 + rankKickers(ranks.replace('888', ''), 2); } + if(ranks.indexOf('777') > -1 && rank === 0){rank = 105 + rankKickers(ranks.replace('777', ''), 2); } + if(ranks.indexOf('666') > -1 && rank === 0){rank = 104 + rankKickers(ranks.replace('666', ''), 2); } + if(ranks.indexOf('555') > -1 && rank === 0){rank = 103 + rankKickers(ranks.replace('555', ''), 2); } + if(ranks.indexOf('444') > -1 && rank === 0){rank = 102 + rankKickers(ranks.replace('444', ''), 2); } + if(ranks.indexOf('333') > -1 && rank === 0){rank = 101 + rankKickers(ranks.replace('333', ''), 2); } + if(ranks.indexOf('222') > -1 && rank === 0){rank = 100 + rankKickers(ranks.replace('222', ''), 2); } + if(rank !== 0){message = 'Three of a Kind'; } + } + + //Two pair + if(rank === 0){ + if(ranks.indexOf('AA') > -1 && ranks.indexOf('KK') > -1){rank = 99 + rankKickers(ranks.replace('AA', '').replace('KK', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 98 + rankKickers(ranks.replace('AA', '').replace('QQ', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 97 + rankKickers(ranks.replace('AA', '').replace('JJ', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 96 + rankKickers(ranks.replace('AA', '').replace('TT', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 95 + rankKickers(ranks.replace('AA', '').replace('99', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 94 + rankKickers(ranks.replace('AA', '').replace('88', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 93 + rankKickers(ranks.replace('AA', '').replace('77', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 92 + rankKickers(ranks.replace('AA', '').replace('66', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 91 + rankKickers(ranks.replace('AA', '').replace('55', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 90 + rankKickers(ranks.replace('AA', '').replace('44', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 89 + rankKickers(ranks.replace('AA', '').replace('33', ''), 1); } + if(ranks.indexOf('AA') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 88 + rankKickers(ranks.replace('AA', '').replace('22', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('QQ') > -1 && rank === 0){rank = 87 + rankKickers(ranks.replace('KK', '').replace('QQ', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 86 + rankKickers(ranks.replace('KK', '').replace('JJ', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 85 + rankKickers(ranks.replace('KK', '').replace('TT', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 84 + rankKickers(ranks.replace('KK', '').replace('99', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 83 + rankKickers(ranks.replace('KK', '').replace('88', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 82 + rankKickers(ranks.replace('KK', '').replace('77', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 81 + rankKickers(ranks.replace('KK', '').replace('66', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 80 + rankKickers(ranks.replace('KK', '').replace('55', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 79 + rankKickers(ranks.replace('KK', '').replace('44', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 78 + rankKickers(ranks.replace('KK', '').replace('33', ''), 1); } + if(ranks.indexOf('KK') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 77 + rankKickers(ranks.replace('KK', '').replace('22', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('JJ') > -1 && rank === 0){rank = 76 + rankKickers(ranks.replace('QQ', '').replace('JJ', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 75 + rankKickers(ranks.replace('QQ', '').replace('TT', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 74 + rankKickers(ranks.replace('QQ', '').replace('99', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 73 + rankKickers(ranks.replace('QQ', '').replace('88', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 72 + rankKickers(ranks.replace('QQ', '').replace('77', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 71 + rankKickers(ranks.replace('QQ', '').replace('66', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 70 + rankKickers(ranks.replace('QQ', '').replace('55', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 69 + rankKickers(ranks.replace('QQ', '').replace('44', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 68 + rankKickers(ranks.replace('QQ', '').replace('33', ''), 1); } + if(ranks.indexOf('QQ') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 67 + rankKickers(ranks.replace('QQ', '').replace('22', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('TT') > -1 && rank === 0){rank = 66 + rankKickers(ranks.replace('JJ', '').replace('TT', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 65 + rankKickers(ranks.replace('JJ', '').replace('99', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 64 + rankKickers(ranks.replace('JJ', '').replace('88', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 63 + rankKickers(ranks.replace('JJ', '').replace('77', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 62 + rankKickers(ranks.replace('JJ', '').replace('66', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 61 + rankKickers(ranks.replace('JJ', '').replace('55', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 60 + rankKickers(ranks.replace('JJ', '').replace('44', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 59 + rankKickers(ranks.replace('JJ', '').replace('33', ''), 1); } + if(ranks.indexOf('JJ') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 58 + rankKickers(ranks.replace('JJ', '').replace('22', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('99') > -1 && rank === 0){rank = 57 + rankKickers(ranks.replace('TT', '').replace('99', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 56 + rankKickers(ranks.replace('TT', '').replace('88', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 55 + rankKickers(ranks.replace('TT', '').replace('77', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 54 + rankKickers(ranks.replace('TT', '').replace('66', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 53 + rankKickers(ranks.replace('TT', '').replace('55', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 52 + rankKickers(ranks.replace('TT', '').replace('44', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 51 + rankKickers(ranks.replace('TT', '').replace('33', ''), 1); } + if(ranks.indexOf('TT') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 50 + rankKickers(ranks.replace('TT', '').replace('22', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('88') > -1 && rank === 0){rank = 49 + rankKickers(ranks.replace('99', '').replace('88', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 48 + rankKickers(ranks.replace('99', '').replace('77', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 47 + rankKickers(ranks.replace('99', '').replace('66', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 46 + rankKickers(ranks.replace('99', '').replace('55', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 45 + rankKickers(ranks.replace('99', '').replace('44', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 44 + rankKickers(ranks.replace('99', '').replace('33', ''), 1); } + if(ranks.indexOf('99') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 43 + rankKickers(ranks.replace('99', '').replace('22', ''), 1); } + if(ranks.indexOf('88') > -1 && ranks.indexOf('77') > -1 && rank === 0){rank = 42 + rankKickers(ranks.replace('88', '').replace('77', ''), 1); } + if(ranks.indexOf('88') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 41 + rankKickers(ranks.replace('88', '').replace('66', ''), 1); } + if(ranks.indexOf('88') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 40 + rankKickers(ranks.replace('88', '').replace('55', ''), 1); } + if(ranks.indexOf('88') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 39 + rankKickers(ranks.replace('88', '').replace('44', ''), 1); } + if(ranks.indexOf('88') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 38 + rankKickers(ranks.replace('88', '').replace('33', ''), 1); } + if(ranks.indexOf('88') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 37 + rankKickers(ranks.replace('88', '').replace('22', ''), 1); } + if(ranks.indexOf('77') > -1 && ranks.indexOf('66') > -1 && rank === 0){rank = 36 + rankKickers(ranks.replace('77', '').replace('66', ''), 1); } + if(ranks.indexOf('77') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 35 + rankKickers(ranks.replace('77', '').replace('55', ''), 1); } + if(ranks.indexOf('77') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 34 + rankKickers(ranks.replace('77', '').replace('44', ''), 1); } + if(ranks.indexOf('77') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 33 + rankKickers(ranks.replace('77', '').replace('33', ''), 1); } + if(ranks.indexOf('77') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 32 + rankKickers(ranks.replace('77', '').replace('22', ''), 1); } + if(ranks.indexOf('66') > -1 && ranks.indexOf('55') > -1 && rank === 0){rank = 31 + rankKickers(ranks.replace('66', '').replace('55', ''), 1); } + if(ranks.indexOf('66') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 30 + rankKickers(ranks.replace('66', '').replace('44', ''), 1); } + if(ranks.indexOf('66') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 29 + rankKickers(ranks.replace('66', '').replace('33', ''), 1); } + if(ranks.indexOf('66') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 28 + rankKickers(ranks.replace('66', '').replace('22', ''), 1); } + if(ranks.indexOf('55') > -1 && ranks.indexOf('44') > -1 && rank === 0){rank = 27 + rankKickers(ranks.replace('55', '').replace('44', ''), 1); } + if(ranks.indexOf('55') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 26 + rankKickers(ranks.replace('55', '').replace('33', ''), 1); } + if(ranks.indexOf('55') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 25 + rankKickers(ranks.replace('55', '').replace('22', ''), 1); } + if(ranks.indexOf('44') > -1 && ranks.indexOf('33') > -1 && rank === 0){rank = 24 + rankKickers(ranks.replace('44', '').replace('33', ''), 1); } + if(ranks.indexOf('44') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 23 + rankKickers(ranks.replace('44', '').replace('22', ''), 1); } + if(ranks.indexOf('33') > -1 && ranks.indexOf('22') > -1 && rank === 0){rank = 22 + rankKickers(ranks.replace('33', '').replace('22', ''), 1); } + if(rank !== 0){message = 'Two Pair'; } + } + + //One Pair + if(rank === 0){ + if(ranks.indexOf('AA') > -1){rank = 21 + rankKickers(ranks.replace('AA', ''), 3); } + if(ranks.indexOf('KK') > -1 && rank === 0){rank = 20 + rankKickers(ranks.replace('KK', ''), 3); } + if(ranks.indexOf('QQ') > -1 && rank === 0){rank = 19 + rankKickers(ranks.replace('QQ', ''), 3); } + if(ranks.indexOf('JJ') > -1 && rank === 0){rank = 18 + rankKickers(ranks.replace('JJ', ''), 3); } + if(ranks.indexOf('TT') > -1 && rank === 0){rank = 17 + rankKickers(ranks.replace('TT', ''), 3); } + if(ranks.indexOf('99') > -1 && rank === 0){rank = 16 + rankKickers(ranks.replace('99', ''), 3); } + if(ranks.indexOf('88') > -1 && rank === 0){rank = 15 + rankKickers(ranks.replace('88', ''), 3); } + if(ranks.indexOf('77') > -1 && rank === 0){rank = 14 + rankKickers(ranks.replace('77', ''), 3); } + if(ranks.indexOf('66') > -1 && rank === 0){rank = 13 + rankKickers(ranks.replace('66', ''), 3); } + if(ranks.indexOf('55') > -1 && rank === 0){rank = 12 + rankKickers(ranks.replace('55', ''), 3); } + if(ranks.indexOf('44') > -1 && rank === 0){rank = 11 + rankKickers(ranks.replace('44', ''), 3); } + if(ranks.indexOf('33') > -1 && rank === 0){rank = 10 + rankKickers(ranks.replace('33', ''), 3); } + if(ranks.indexOf('22') > -1 && rank === 0){rank = 9 + rankKickers(ranks.replace('22', ''), 3); } + if(rank !== 0){message = 'Pair'; } + } + + //High Card + if(rank === 0){ + if(ranks.indexOf('A') > -1){rank = 8 + rankKickers(ranks.replace('A', ''), 4); } + if(ranks.indexOf('K') > -1 && rank === 0){rank = 7 + rankKickers(ranks.replace('K', ''), 4); } + if(ranks.indexOf('Q') > -1 && rank === 0){rank = 6 + rankKickers(ranks.replace('Q', ''), 4); } + if(ranks.indexOf('J') > -1 && rank === 0){rank = 5 + rankKickers(ranks.replace('J', ''), 4); } + if(ranks.indexOf('T') > -1 && rank === 0){rank = 4 + rankKickers(ranks.replace('T', ''), 4); } + if(ranks.indexOf('9') > -1 && rank === 0){rank = 3 + rankKickers(ranks.replace('9', ''), 4); } + if(ranks.indexOf('8') > -1 && rank === 0){rank = 2 + rankKickers(ranks.replace('8', ''), 4); } + if(ranks.indexOf('7') > -1 && rank === 0){rank = 1 + rankKickers(ranks.replace('7', ''), 4); } + if(rank !== 0){message = 'High Card'; } + } + + return { + rank : rank, + message : message + } +} + +function rankKickers(ranks, noOfCards){ + var i, kickerRank, myRanks, rank; + kickerRank = 0.0000; + myRanks = []; + rank = ''; + for(i=0;i<= ranks.length;i+=1){ + rank = ranks.substr(i, 1); + if(rank === 'A'){myRanks.push(0.2048); } + if(rank === 'K'){myRanks.push(0.1024); } + if(rank === 'Q'){myRanks.push(0.0512); } + if(rank === 'J'){myRanks.push(0.0256); } + if(rank === 'T'){myRanks.push(0.0128); } + if(rank === '9'){myRanks.push(0.0064); } + if(rank === '8'){myRanks.push(0.0032); } + if(rank === '7'){myRanks.push(0.0016); } + if(rank === '6'){myRanks.push(0.0008); } + if(rank === '5'){myRanks.push(0.0004); } + if(rank === '4'){myRanks.push(0.0002); } + if(rank === '3'){myRanks.push(0.0001); } + if(rank === '2'){myRanks.push(0.0000); } + } + myRanks.sort(function(a, b){ + return b - a; + }); + for(i=0;i< noOfCards;i+=1){ + kickerRank += myRanks[i]; + } + return kickerRank; +} + +function rankHands(hands){ + var x, myResult; + for(x = 0; x < hands.length; x += 1){ + myResult = rankHandInt(hands[x]); + hands[x].rank = myResult.rank; + hands[x].message = myResult.message; + } + return hands; +} diff --git a/game-server/app/game/player.js b/game-server/app/game/player.js new file mode 100644 index 0000000..ef2b01f --- /dev/null +++ b/game-server/app/game/player.js @@ -0,0 +1,470 @@ +var logger = require('pomelo-logger').getLogger('game-log', __filename); +var Hand = require('./hand'); + + +module.exports = Player = function(playerName, chips, uid, table){ + this.playerName = playerName; + this.id = uid; + this.chips = chips; + this.folded = false; + this.allIn = false; + this.talked = false; + this.table = table; //Circular reference to allow reference back to parent object. + this.cards = []; +} + +Player.prototype.GetChips = function(cash){ + this.chips += cash; +}; + +Player.prototype.Check = function(){ + var checkAllow, v, i; + checkAllow = true; + for(v=0;v totalBet){ + this.table.players[i].chips -= (totalBet - currentBet); + this.table.game.bets[i] = totalBet; // this.table.game.bets[i] += totalBet; + this.talked = true; + // attempt to progress the game + this.turnBet = {action: "bet", playerName: this.playerName, amount: totalBet}; + this.table.actions.push(this.turnBet); + progress(this.table); + }else{ + logger.debug('forced-all-in: '+this.id); + this.AllIn(); + } +}; + +Player.prototype.getIndex = function(){ + var index; + for(i=0;i< this.table.players.length;i+=1){ + if(this === this.table.players[i]){ + index = i; + } + } + return index; +} + +Player.prototype.Call = function(){ + var maxBet, i; +// console.log('bets', this.table.game.bets); + maxBet = getMaxBet(this.table.game.bets); + if(this.chips > maxBet){ +// console.log('higher chips', this.chips, maxBet); + //Match the highest bet + for(i=0;i< this.table.players.length;i+=1){ + if(this === this.table.players[i]){ + if(this.table.game.bets[i] >= 0){ + this.chips += this.table.game.bets[i]; + } + this.chips -= maxBet; + this.table.game.bets[i] = maxBet; + this.talked = true; + } + } + // attempt to progress the game + this.turnBet = {action: "call", playerName: this.playerName, amount: maxBet}; + this.table.actions.push(this.turnBet); + progress(this.table); + }else{ + logger.debug('forced-all-in: '+this.id); + this.AllIn(); + } +}; + +Player.prototype.AllIn = function(){ + var i, allInValue=0; + for(i=0;i< this.table.players.length;i+=1){ + if(this === this.table.players[i]){ + if(this.table.players[i].chips !== 0){ + allInValue = this.table.players[i].chips; + this.table.game.bets[i] += this.table.players[i].chips; + this.table.players[i].chips = 0; + + this.allIn = true; + this.talked = true; + } + } + } + + // attempt to progress the game + this.turnBet = {action: "allin", playerName: this.playerName, amount: allInValue}; + this.table.actions.push(this.turnBet); + + progress(this.table); +}; + +function progress(table){ + var i, j, cards, hand; + if(table.game){ + table.eventEmitter.emit("turnEnd"); + table.stopTimer(); + if(checkForEndOfGame(table)){ + //Move all bets to the pot + for(i=0;i< table.game.bets.length;i+=1){ + table.game.pot += parseInt(table.game.bets[i], 10); + table.game.roundBets[i] += parseInt(table.game.bets[i], 10); + } + completeGame(table); + return; + } + if(checkForEndOfRound(table) === true){ + //Move all bets to the pot + for(i=0;i< table.game.bets.length;i+=1){ + table.game.pot += parseInt(table.game.bets[i], 10); + table.game.roundBets[i] += parseInt(table.game.bets[i], 10); + } + if(table.game.roundName === 'River'){ + completeGame(table); + return; + }else if(table.game.roundName === 'Turn'){ + table.game.roundName = 'River'; + table.game.deck.pop(); //Burn a card + table.game.board.push(table.game.deck.pop()); //Turn a card + //table.game.bets.splice(0,table.game.bets.length-1); + for(i=0;i< table.game.bets.length;i+=1){ + table.game.bets[i] = 0; + } + for(i=0;i< table.players.length;i+=1){ + table.players[i].talked = false; + } + table.eventEmitter.emit( "deal" ); + }else if(table.game.roundName === 'Flop'){ + table.game.roundName = 'Turn'; + table.game.deck.pop(); //Burn a card + table.game.board.push(table.game.deck.pop()); //Turn a card + for(i=0;i< table.game.bets.length;i+=1){ + table.game.bets[i] = 0; + } + for(i=0;i< table.players.length;i+=1){ + table.players[i].talked = false; + } + table.eventEmitter.emit( "deal" ); + }else if(table.game.roundName === 'Deal'){ + table.game.roundName = 'Flop'; + table.game.deck.pop(); //Burn a card + for(i=0;i< 3;i+=1){ //Turn three cards + table.game.board.push(table.game.deck.pop()); + } + //table.game.bets.splice(0,table.game.bets.length-1); + for(i=0;i< table.game.bets.length;i+=1){ + table.game.bets[i] = 0; + } + for(i=0;i< table.players.length;i+=1){ + table.players[i].talked = false; + } + table.eventEmitter.emit( "deal" ); + } + table.currentPlayer = getNextAvailablePlayer(table, table.startIndex, table.players.length); + if(typeof table.currentPlayer !== 'number' && table.game.roundName !== 'GameEnd'){ + console.log('ALL IN GAME'); + completeBoard(table); + return progress(table); + } + } + table.eventEmitter.emit("turnStart"); + } +} + +function completeGame(table){ + table.game.roundName = 'GameEnd'; + table.game.bets.splice(0, table.game.bets.length); + //Evaluate each hand + for(j = 0; j < table.players.length; j += 1){ + cards = table.players[j].cards.concat(table.game.board); + table.players[j].hand = new Hand(cards); + } + console.log('checkForWinner', table); + checkForWinner(table); + table.eventEmitter.emit('gameOver'); +} + +function completeBoard(table){ + var i; + if(table.game.board.length == 0){ + table.game.deck.pop(); + for(i=0;i<3;i+=1){ + table.game.board.push(table.game.deck.pop()); + } + } + if(table.game.board.length == 3){ + table.game.deck.pop(); + table.game.board.push(table.game.deck.pop()); + } + if(table.game.board.length == 4){ + table.game.deck.pop(); + table.game.board.push(table.game.deck.pop()); + } +} + +function getMaxBet(bets){ + var maxBet, i; + maxBet = 0; + for(i=0;i< bets.length;i+=1){ + if(bets[i] > maxBet){ + maxBet = bets[i]; + } + } + return maxBet; +} + +function checkForEndOfGame(table){ + var notFolded = [], allInPlayer = [], actionablePlayer = [], i; + for(i = 0; i < table.players.length;i+=1){ + if(table.players[i].folded === false){ + notFolded.push(i); + } + if(table.players[i].allIn === true){ + allInPlayer.push(i); + } + if(table.players[i].folded === false && table.players[i].allIn === false){ + actionablePlayer.push(i); + } + } + if(allInPlayer.length > 1){ + completeBoard(table); + } + return (notFolded.length === 1 || actionablePlayer.length === 0); +} + +function checkForEndOfRound(table){ + var endOfRound = true; + var nextPlayer = getNextAvailablePlayer(table, (table.currentPlayer + 1), table.players.length); + if(typeof nextPlayer === 'number'){ + table.currentPlayer = nextPlayer; + endOfRound = false; + } + return endOfRound; +} + +function getNextAvailablePlayer(table, playerIndex, len, ctr){ + ctr = ctr || 0; + var maxBet = getMaxBet(table.game.bets); + var nextPlayer; + if(playerIndex === len){ + playerIndex = 0; + } + if(table.players[playerIndex].folded === false && (table.players[playerIndex].talked === false || table.game.bets[playerIndex] !== maxBet) && table.players[playerIndex].allIn === false){ + nextPlayer = playerIndex; + } + ctr += 1; + playerIndex += 1; + if(typeof nextPlayer !== 'number' && ctr !== len){ + nextPlayer = getNextAvailablePlayer(table, playerIndex, len, ctr); + } + return nextPlayer; +} + +function checkForAllInPlayer(table, winners){ + var i, allInPlayer; + allInPlayer = []; + for(i=0;i< winners.length;i+=1){ + if(table.players[winners[i]].allIn === true){ + allInPlayer.push(winners[i]); + } + } + return allInPlayer; +} + +function checkForWinner(table){ + var i, j, k, l, maxRank, notFolded, winners, part, prize, allInPlayer, minBets, roundEnd; + //Identify winner(s) + winners = []; + notFolded = []; + maxRank = 0.000; + for(k = 0; k < table.players.length; k += 1){ + if(table.players[k].folded === false){ + notFolded.push(k); + } + if(table.players[k].hand.rank === maxRank && table.players[k].folded === false){ + winners.push(k); + } + if(table.players[k].hand.rank > maxRank && table.players[k].folded === false){ + maxRank = table.players[k].hand.rank; + winners.splice(0, winners.length); + winners.push(k); + } + } + // handle mid-round fold + if(winners.length === 0 && notFolded.length == 1){ + console.log('mid round fold'); + winners.push(notFolded[0]); + } + part = 0; + prize = 0; + console.log('roundBets', table.game.roundBets); + allInPlayer = checkForAllInPlayer(table, winners); + if(allInPlayer.length > 0){ + minBets = table.game.roundBets[winners[0]]; + for(j = 1; j < allInPlayer.length; j += 1){ + if(table.game.roundBets[winners[j]] !== 0 && table.game.roundBets[winners[j]] < minBets){ + minBets = table.game.roundBets[winners[j]]; + } + } + part = parseInt(minBets, 10); + }else{ + part = parseInt(table.game.roundBets[winners[0]], 10); + } + console.log('part', part); + for(l = 0; l < table.game.roundBets.length; l += 1){ + // handle user leave +// console.log('more than 1 player', table.players.length > 1 && (table.players.length - table.playersToRemove.length > 1)); + if(table.game.roundBets[l] > part){ + prize += part; + table.game.roundBets[l] -= part; + }else{ + prize += table.game.roundBets[l]; + table.game.roundBets[l] = 0; + } + } + console.log('prize', prize); + console.log('winners', winners); + + if(prize > 0){ + var remainder = prize % winners.length; + var winnerHands = []; + var highestIndex; + var winnerPrize = Math.floor(prize / winners.length); + if(remainder !== 0){ + console.log('chip remainder of '+remainder); + for(i=0;i 10){ //hard limit of 10 players at a table. + err = new Error(102, 'Parameter [maxPlayers] must be a positive integer less than or equal to 10.'); + }else if(minPlayers > maxPlayers){ //Without this we can never start a game! + err = new Error(103, 'Parameter [minPlayers] must be less than or equal to [maxPlayers].'); + } + if(err){ + return err; + } +} + +Table.prototype.initNewGame = function(){ + var i; + this.instance.state = 'JOIN'; + this.dealer += 1; + if(this.dealer >= this.players.length){ + this.dealer = 0; + } + delete this.game; + this.previousPlayers = []; + // add existing players and remove players who left or are bankrupt + for(i=0;i= this.minBuyIn && chips <= this.maxBuyIn){ + var player = new Player(playerName, chips, uid, this); + this.playersToAdd.push(player); + } +}; +Table.prototype.removePlayer = function(pid){ + for(var i in this.players ){ + if(this.players[i].id === pid){ + this.playersToRemove.push( parseInt(i) ); + this.players[i].Fold(); + } + } + for(var i in this.playersToAdd ){ + if(this.playersToAdd[i].id === pid){ + this.playersToAdd.splice(i, 1); + } + } + for(var i in this.members ){ + if(this.members[i].id === pid){ + this.members.splice(i, 1); + } + } + for(var i in this.previousPlayers){ + if(this.previousPlayers[i].id === pid){ + this.previousPlayers.splice(i, 1); + } + } + this.eventEmitter.emit("playerLeft"); +} +Table.prototype.NewRound = function(){ + var removeIndex = 0; + for(var i in this.playersToAdd){ +// if(removeIndex < this.playersToRemove.length){ +// var index = this.playersToRemove[removeIndex]; +// this.players[index] = this.playersToAdd[i]; +// removeIndex += 1; +// }else{ +// this.players.push(this.playersToAdd[i]); +// } + this.players.push(this.playersToAdd[i]); + } + this.playersToRemove = []; + this.playersToAdd = []; + this.gameWinners = []; + this.gameLosers = []; + + + var i, smallBlind, bigBlind; + //Deal 2 cards to each player + for(i=0;i< this.players.length;i+=1){ + this.players[i].cards.push(this.game.deck.pop()); + this.players[i].cards.push(this.game.deck.pop()); + this.game.bets[i] = 0; + this.game.roundBets[i] = 0; + } + //Identify Small and Big Blind player indexes + smallBlind = this.dealer + 1; + if(smallBlind >= this.players.length){ + smallBlind = 0; + } + bigBlind = smallBlind + 1; + if(bigBlind >= this.players.length){ + bigBlind = 0; + } + this.currentPlayer = bigBlind + 1; + if(this.currentPlayer >= this.players.length){ + this.currentPlayer = 0; + } + this.startIndex = this.currentPlayer; + //Force Blind Bets + this.players[smallBlind].chips -= this.smallBlind; + this.players[bigBlind].chips -= this.bigBlind; + this.game.bets[smallBlind] = this.smallBlind; + this.game.bets[bigBlind] = this.bigBlind; + this.game.blinds = [smallBlind, bigBlind]; + + this.eventEmitter.emit("newRound"); +}; + +Table.prototype.startTimer = function(){ + var me = this; + me.stopTimer(); + me._countdown = setTimeout(function(){ + if(!me.active){ + return; + } + console.log('timer ended. executing move.'); + if(me.game.bets[me.currentPlayer] < getMaxBet(me.game.bets)){ + me.players[me.currentPlayer].Fold(); + }else{ + me.players[me.currentPlayer].Call(); + } + me.instance.tableService.handleGameState(me.instance.id, function(e){ + if(e){ + console.error(e); + } + }); + }, (GAME_SETTINGS.gameMode[this.gameMode].timeout * 1000)); +}; + +Table.prototype.stopTimer = function(){ + if(this._countdown){ + clearTimeout(this._countdown); + } +}; + +function getMaxBet(bets){ + var maxBet, i; + maxBet = 0; + for(i=0;i< bets.length;i+=1){ + if(bets[i] > maxBet){ + maxBet = bets[i]; + } + } + return maxBet; +} diff --git a/game-server/app/module/timeReport.js b/game-server/app/module/timeReport.js new file mode 100644 index 0000000..8c019e1 --- /dev/null +++ b/game-server/app/module/timeReport.js @@ -0,0 +1,42 @@ +module.exports = function(opts) { + return new Module(opts); +} + +var testMsg = 'a default message'; + +var moduleId = "timeReport"; +module.exports.moduleId = moduleId; + +var Module = function(opts) { + this.app = opts.app; + this.type = opts.type || 'pull'; + this.interval = opts.interval || 5; +} + +Module.prototype.monitorHandler = function(agent, msg, cb) { + console.log(this.app.getServerId() + '' + msg); + var serverId = agent.id; + var time = new Date(). toString(); + + agent.notify(moduleId, {serverId: serverId, time: time}); +} + +Module.prototype.masterHandler = function(agent, msg) { + if(! msg) { + agent.notifyAll(moduleId, testMsg); + return; + } + + console.log(msg); + var timeData = agent.get(moduleId); + if(! timeData) { + timeData = {}; + agent.set(moduleId, timeData); + } + timeData[msg.serverId] = msg.time; +} + + +Module.prototype.clientHandler = function(agent, msg, cb) { + cb(null, agent.get(moduleId)); +} diff --git a/game-server/app/persistence/tables.js b/game-server/app/persistence/tables.js new file mode 100644 index 0000000..b28cdb2 --- /dev/null +++ b/game-server/app/persistence/tables.js @@ -0,0 +1,103 @@ +var uuid = require('node-uuid'); +var util = require('util'); +var fs = require('fs'); + +var TableStore = module.exports = { + store : './localstore/tables.json', + entity : 'tables', + create : function(obj, cb){ + var me = this; + me.persist({ + id : obj.id || uuid.v1(), + tid : obj.tid, + smallBlind : obj.smallBlind, + bigBlind : obj.bigBlind, + minBuyIn : obj.minBuyIn, + maxBuyIn : obj.maxBuyIn, + minPlayers : obj.minPlayers, + maxPlayers : obj.maxPlayers, + gameMode : obj.gameMode, + board : obj.board, + creator : obj.creator, + players : obj.players || [], + actions : obj.actions || [], + gameWinners : obj.gameWinners || [], + created : Date.now() + }, function(e, table){ + if(e){ + cb(e); + }else{ +// console.log('TableStore: table created - ', table); + cb(null, table); + } + }); + }, + set : function(obj, cb){ + var me = this; + me.persist(obj, function(e, table){ + if(e){ + cb(e); + }else{ +// console.log('TableStore: table modified - ', table); + cb(null, table); + } + }); + }, + getByAttr : function(key, val, cb){ + var matches = [], matched; + this.retrieve(function(e, entities){ + if(e){ + return cb(e); + } + if(key == '*' && val == '*'){ + cb(null, entities); + }else{ + for(var entity in entities){ + if(util.isArray(key)){ + matched = true; + for(var i=key.length;i--;){ + if(entities[entity][key[i]] != val[i]){ + matched = false; + } + } + if(matched){ + matches.push(entities[entity]); + } + }else if(entities[entity][key] == val){ + matches.push(entities[entity]); + } + } + cb(null, matches.length == 0 ? null : (matches.length == 1 ? matches[0] : matches)); + } + }); + }, + persist : function(row, cb){ + var me = this; + me.retrieve(function(e, entities){ + entities[row.id] = entities[row.id] || {}; + for(var key in row){ + entities[row.id][key] = row[key]; + } + fs.writeFile(me.store, JSON.stringify(entities, undefined, 4), function(e2){ + if(e2){ + return cb('error writing file: ' + e2); + }else{ + cb(null, entities[row.id]); + } + }); + }); + }, + retrieve : function(cb){ + fs.readFile(this.store, 'utf8', function(e, content){ + if(e){ + return cb('error loading file: ' + e); + } + if(content.trim().length == 0){ + content = {}; + }else{ + content = JSON.parse(content); + } + cb(null, content); + }); + } +}; \ No newline at end of file diff --git a/game-server/app/persistence/users.js b/game-server/app/persistence/users.js new file mode 100644 index 0000000..e889b33 --- /dev/null +++ b/game-server/app/persistence/users.js @@ -0,0 +1,132 @@ +var uuid = require('node-uuid'); +var util = require('util'); +var fs = require('fs'); + +var UserStore = module.exports = { + store : './localstore/users.json', + entity : 'users', + create : function(obj, callback){ + var me = this; + me.getByAttr('username', obj.username, false, function(e, existingUser){ + if(existingUser){ + callback('user-exists', existingUser); + }else{ + var id = uuid.v1(); + me.persist({ + id : id, + username : obj.username, + password : obj.password, + email : obj.email, + chips : obj.chips, + wins : 0, + largestWin : 0, + friends : [], + created : Date.now() + }, function(e, user){ + if(e){ + callback(e); + }else{ + callback(null, user); + } + }) + } + }); + }, + set : function(obj, cb){ + var me = this; + me.persist(obj, function(e, entity){ + if(e){ + cb(e); + }else{ + cb(null, safetyFilter(entity)); + } + }); + }, + getByAttr : function(key, val, opts, cb){ + var matches = [], matched; + opts = opts || {}; + this.retrieve(function(e, entities){ + if(e){ + return cb(e, []); + } + for(var entity in entities){ + if(util.isArray(key)){ + matched = true; + for(var i=key.length;i--;){ + if(entities[entity][key[i]] != val[i]){ + matched = false; + } + } + if(matched){ + matches.push(opts.getFullEntity ? entities[entity] : safetyFilter(entities[entity])); + } + }else if(entities[entity][key] == val || (key == '*' && val == '*')){ + matches.push(opts.getFullEntity ? entities[entity] : safetyFilter(entities[entity])); + } + } + matches = opts.getArray ? matches : (matches.length == 0 ? null : (matches.length == 1 ? matches[0] : matches)); + cb(null, matches); + }); + }, + getByIds : function(ary, cb){ + var matches = []; + this.retrieve(function(e, entities){ + if(e){ + return cb(e, []); + } + for(var entity in entities){ + if(ary.indexOf(entities[entity].id) != -1){ + matches.push(safetyFilter(entities[entity])); + } + } + cb(null, matches); + }); + }, + persist : function(row, cb){ + var me = this; + me.retrieve(function(e, entities){ + entities[row.id] = entities[row.id] || {}; + for(var key in row){ + entities[row.id][key] = row[key]; + } + fs.writeFile(me.store, JSON.stringify(entities, undefined, 4), function(e2){ + if(e2){ + return cb('error writing file: ' + e2); + }else{ + cb(null, entities[row.id]); + } + }); + }); + }, + retrieve : function(cb){ + fs.readFile(this.store, 'utf8', function(e, content){ + if(e){ + return cb('error loading file: ' + e); + } + if(content.trim().length == 0){ + content = {}; + }else{ + try{ + content = JSON.parse(content); + }catch(e){ + console.log('parseerror'); + content = content.slice(0, - 1); + content = JSON.parse(content); + } + } + cb(null, content); + }); + } +}; + +function safetyFilter(obj){ + return { + id : obj.id, + username : obj.username, + email : obj.email, + chips : obj.chips, + wins : obj.wins, + largestWin : obj.largestWin, + created : obj.created + } +} \ No newline at end of file diff --git a/game-server/app/servers/chat/handler/chatHandler.js b/game-server/app/servers/chat/handler/chatHandler.js new file mode 100644 index 0000000..64aad78 --- /dev/null +++ b/game-server/app/servers/chat/handler/chatHandler.js @@ -0,0 +1,90 @@ +var UserStore = require('../../../persistence/users'); +var dispatcher = require('../../../util/dispatcher'); + +module.exports = function(app){ + return new Handler(app, app.get('chatService')); +}; +var Handler = function(app, chatService){ + this.app = app; + this.chatService = chatService; +}; +var handler = Handler.prototype; + +/** + * Send messages to users in the channel + * + * @param {Object} msg message from client + * @param {Object} session + * @param {Function} next next stemp callback + * + */ +handler.sendMessage = function(msg, session, next){ + var me = this; + var tid = session.get('tid'); + var channelService = this.app.get('channelService'); + UserStore.getByAttr('id', session.uid, false, function(e, user){ + if(!user){ + next(null, { + code : 500, + error : 'user-not-exist' + }); + return; + } + // target is all users + if(msg.target == 'table'){ + var channel = channelService.getChannel(tid, true); + msg.target = '*'; + channel.pushMessage({ + route : 'onChat', + msg : msg.content, + username : user.username, + target : msg.target + }); + next(null, { + code : 200, + route : msg.route + }); + }else{ + // target is specific user + me.chatService.pushByPlayerName(msg.target, { + username : user.username, + msg : msg.content + }, function(e){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route + }); + }); + } + }); +}; + +/** + * Get friend list + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.getFriends = function(msg, session, next){ + this.chatService.getFriendList(session.uid, function(e, friends){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route, + friends : friends + }); + }); +}; \ No newline at end of file diff --git a/game-server/app/servers/chat/remote/chatRemote.js b/game-server/app/servers/chat/remote/chatRemote.js new file mode 100644 index 0000000..c2129b6 --- /dev/null +++ b/game-server/app/servers/chat/remote/chatRemote.js @@ -0,0 +1,48 @@ +module.exports = function(app){ + return new ChatRemote(app, app.get('chatService')); +}; + +var ChatRemote = function(app, chatService){ + this.app = app; + this.chatService = chatService; +}; + +/** + * Add player into channel + */ +ChatRemote.prototype.addToChannel = function(uid, cid, cb){ + this.chatService.addToChannel(uid, cid, cb); +}; + +/** + * Add player record + */ +ChatRemote.prototype.add = function(uid, cid, cb){ + this.chatService.add(uid, cid, cb); +}; + +/** + * Get members in a channel + */ +ChatRemote.prototype.getMembers = function(cid, cb){ + this.chatService.getMembers(cid, cb); +}; + +/** + * leave Channel + * uid + * cid + */ +ChatRemote.prototype.leave = function(uid, cid, cb){ + this.chatService.leave(uid, cid); + cb(); +}; + +/** + * kick out user + * + */ +ChatRemote.prototype.disconnect = function(uid, cb){ + this.chatService.disconnect(uid); + cb(); +}; diff --git a/game-server/app/servers/connector/handler/entryHandler.js b/game-server/app/servers/connector/handler/entryHandler.js new file mode 100644 index 0000000..0744c83 --- /dev/null +++ b/game-server/app/servers/connector/handler/entryHandler.js @@ -0,0 +1,101 @@ +var logger = require('pomelo-logger').getLogger('con-log', __filename); + +module.exports = function(app){ + return new Handler(app); +}; +var Handler = function(app){ + this.app = app; +}; +var handler = Handler.prototype; + +/** + * Register user. + * + * @param {Object} msg request message + * @param {Object} session current session object + * @param {Function} next next step callback + */ +handler.register = function(msg, session, next){ + this.app.rpc.game.authRemote.register(session, msg, function(e, user){ + if(e){ + next(null, { + code : 500, + error : e + }); + }else{ + next(null, { + code : 201 + }); + } + }); +}; + +/** + * Connect to the server + * + * @param {Object} msg request message + * @param {Object} session current session object + * @param {Function} next next step callback + */ +handler.connect = function(msg, session, next){ + var me = this; + var sessionService = me.app.get('sessionService'); + me.app.rpc.game.authRemote.auth(session, msg, function(e, user, token){ + if(!user){ + next(null, { + code : 401, + error : e + }); + return; + } + // duplicate log in + if(!! sessionService.getByUid(user.id)){ + return next(null, { + code : 500, + error : 'duplicate-session' + }); + } + session.bind(user.id, function(e){ + if(e){ + console.error('error-binding-user', e); + } + session.set('username', user.username); + session.on('closed', onUserLeave.bind(null, me.app)); + session.pushAll(function(e){ + if(e){ + console.error('set username for session service failed! error is : %j', e.stack); + } + }); + // add user to chat service + me.app.rpc.chat.chatRemote.add(session, session.uid, function(e){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + token : token, + user : user + }); + }); + }); + }); +}; +/** + * User log out handler + * + * @param {Object} app current application + * @param {Object} session current session object + * + */ +var onUserLeave = function(app, session){ + if(!session || !session.uid || !session.get('tid')){ + return; + } + if(session.get('tid')){ + app.rpc.chat.chatRemote.disconnect(session, session.uid, function(){}); + app.rpc.game.tableRemote.removeMember(session, session.uid, app.get('serverId'), session.get('tid'), function(){}); + } +}; diff --git a/game-server/app/servers/game/filter/abuseFilter.js b/game-server/app/servers/game/filter/abuseFilter.js new file mode 100644 index 0000000..a4e80ee --- /dev/null +++ b/game-server/app/servers/game/filter/abuseFilter.js @@ -0,0 +1,22 @@ + +module.exports = function(){ + return new Filter(); +}; + +var Filter = function(){}; + +Filter.prototype.before = function (msg, session, next){ + if(msg.content && msg.content.indexOf ('fuck') !== -1){ + session.__abuse__ = true; + msg.content = msg.content.replace ('fuck', '****'); + } + next(); +}; + +Filter.prototype.after = function (err, msg, session, resp, next){ + if(session.__abuse__){ + var user_info = session.uid.split ('*'); + console.log ('abuse:' + user_info[0] + "at room" + user_info[1]); + } + next(err); +}; \ No newline at end of file diff --git a/game-server/app/servers/game/handler/tableHandler.js b/game-server/app/servers/game/handler/tableHandler.js new file mode 100644 index 0000000..cac2ed2 --- /dev/null +++ b/game-server/app/servers/game/handler/tableHandler.js @@ -0,0 +1,245 @@ +var logger = require('pomelo-logger').getLogger('game-log', __filename); + +module.exports = function(app){ + return new Handler(app); +}; +var Handler = function(app){ + this.app = app; +}; +var handler = Handler.prototype; + +/** + * Get tables + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.getTables = function(msg, session, next){ + var tableService = this.app.get('tableService'); + next(null, { + code : 200, + route : msg.route, + tables : tableService.getTables() + }); +}; + +/** + * Create table + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.createTable = function(msg, session, next){ + if(session.get('tid')){ + next(null, { + code : 500, + error : 'already-in-table' + }); + return; + } + this.app.get('tableService').createTable(session.uid, msg, function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + return; + } + next(null, { + code : 200, + route : msg.route + }); + }); +}; + +/** + * Join table + * + * @param {Object} msg table parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.joinTable = function(msg, session, next){ + var me = this; + var tableService = this.app.get('tableService'); + var table = tableService.getTable(msg.tid); + if(!msg.tid || !table){ + next(null, { + code : 500, + error : 'invalid-table' + }); + return; + } + session.set('tid', msg.tid); + session.pushAll(function(err){ + if(err){ + logger.error('set tid for session service failed! error is : %j', err.stack); + next(null, { + code : 500, + error : 'server-error' + }); + return; + } + var tid = session.get('tid'); + me.app.rpc.chat.chatRemote.addToChannel(session, session.uid, tid, function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + return; + } + tableService.addMember(tid, session.uid, function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + return; + } + next(null, { + code : 200, + route : msg.route + }); + }); + }); + }); +}; + +/** + * Leave table + * + * @param {Object} msg table parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.leaveTable = function(msg, session, next){ + var me = this; + var tid = session.get('tid'); + if(!tid){ + return next(null, { + code : 500, + error : 'not-table-member' + }); + } + me.app.rpc.chat.chatRemote.leave(session, session.uid, tid, function(e){}); + session.set('tid', undefined); + session.pushAll(function(e){ + if(e){ + logger.error('unset tid for session service failed! error is : %j', e.stack); + return next(null, { + code : 500, + error : 'server-error' + }); + } + me.app.get('tableService').removeMember(tid, session.uid, function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + return; + } + next(null, { + code : 200, + route : msg.route + }); + }); + }); +}; + +/** + * Join game + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.joinGame = function(msg, session, next){ + this.app.get('tableService').addPlayer(session.get('tid'), session.uid, msg.buyIn, function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + return; + } + next(null, { + code : 200, + route : msg.route + }); + }); +}; + +/** + * Start game + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.startGame = function(msg, session, next){ + this.app.get('tableService').startGame(session.get('tid'), function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route + }); + }); +}; + +/** + * Perform an action on your turn + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.execute = function(msg, session, next){ + this.app.get('tableService').performAction(session.get('tid'), session.uid, msg, function(e){ + if(e){ + next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route + }); + }); +}; + +/** + * Perform an action on your turn + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.removeBots = function(msg, session, next){ + var botService = this.app.get('botService'); + botService.removeAllBots(session.get('tid'), true); + next(null, { + code : 200, + route : msg.route + }); +}; + + + diff --git a/game-server/app/servers/game/handler/userHandler.js b/game-server/app/servers/game/handler/userHandler.js new file mode 100644 index 0000000..56ff254 --- /dev/null +++ b/game-server/app/servers/game/handler/userHandler.js @@ -0,0 +1,188 @@ +var logger = require('pomelo-logger').getLogger('game-log', __filename); +var UserStore = require('../../../persistence/users'); + +module.exports = function(app){ + return new Handler(app); +}; +var Handler = function(app){ + this.app = app; +}; +var handler = Handler.prototype; + +/** + * Get users matching the criteria + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.getUsers = function(msg, session, next){ + if(!msg.name && !msg.val && !session.uid){ + return next(null, { + code : 500, + error : 'invalid-input' + }); + } + var searchId = (msg.name == 'id' || msg.name == 'username' || msg.name == 'email') ? msg.name : 'id'; + var searchVal = typeof msg.val === 'string' ? msg.val : session.uid; + UserStore.getByAttr(searchId, searchVal, { + getArray : true + }, function(e, matches){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route, + matches : matches + }); + }); +}; + +/** + * Update user profile + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.setProfile = function(msg, session, next){ + if(!session.uid){ + return next(null, { + code : 500, + error : 'invalid-session' + }); + } + UserStore.getByAttr('id', session.uid, false, function(e, user){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + var userObj = { + id : user.id + }; + if(msg.email){ + userObj.email = msg.email.trim(); + } + UserStore.set(userObj, function(e, updatedUser){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route + }); + }); + }); +}; + +/** + * Update user password + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.setPassword = function(msg, session, next){ + if(!session.uid || !msg.oldpassword || !msg.password){ + return next(null, { + code : 500, + error : 'invalid-input' + }); + } + UserStore.getByAttr(['id', 'password'], [session.uid, msg.oldpassword], false, function(e, user){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + var userObj = { + id : user.id, + password : msg.password.trim() + }; + UserStore.set(userObj, function(e, updatedUser){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route + }); + }); + }); +}; + +/** + * Add a friend to friend list + * + * @param {Object} msg game parameters from client + * @param {Object} session + * @param {Function} next next step callback + * + */ +handler.addFriend = function(msg, session, next){ + if(!session.uid){ + return next(null, { + code : 500, + error : 'invalid-session' + }); + } + if(!msg.friend){ + return next(null, { + code : 200, + route : msg.route + }); + } + UserStore.getByAttr('id', session.uid, { + getFullEntity : true + }, function(e, user){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + UserStore.getByAttr('id', msg.friend, false, function(e, friend){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + user.friends.push({ + id : friend.id, + username : friend.username + }); + UserStore.set({ + id : user.id, + friends : user.friends + }, function(e, updatedUser){ + if(e){ + return next(null, { + code : 500, + error : e + }); + } + next(null, { + code : 200, + route : msg.route + }); + }); + }); + }); +}; \ No newline at end of file diff --git a/game-server/app/servers/game/remote/authRemote.js b/game-server/app/servers/game/remote/authRemote.js new file mode 100644 index 0000000..29ae519 --- /dev/null +++ b/game-server/app/servers/game/remote/authRemote.js @@ -0,0 +1,82 @@ +var UserStore = require('../../../persistence/users'); +var tokenService = require('../../../../../shared/token'); +var SESSION_CONFIG = require('../../../../../shared/config/session.json'); + +module.exports = function(app){ + return new Remote(app); +}; +var Remote = function(app){ + this.app = app; +}; +var remote = Remote.prototype; + +/** + * Register user. + * + * @param {object} userObj object containing userObj.user and userObj.pass + * @param {Function} cb + * @return {Void} + */ +remote.register = function(userObj, cb){ + UserStore.create({ + username : userObj.username, + password : userObj.password, + email : userObj.email, + chips : 100000 + }, function(e, user){ + if(e){ + cb(e); + }else{ + cb(null, user); + } + }); +}; +/** + * Auth via user/pass or token, and check for expiry. + * + * @param {object|string} input token or object containing username and password + * @param {Function} cb + * @return {Void} + */ +remote.auth = function(input, cb){ + if(typeof input === 'string'){ + var res = tokenService.parse(input, SESSION_CONFIG.secret); + if(!res){ + cb('invalid-token'); + return; + } + if(!checkExpire(res, SESSION_CONFIG.expire)){ + cb('token-expired'); + return; + } + UserStore.getByAttr('id', res.uid, false, function(e, user){ + if(e){ + cb('invalid-user'); + return; + } + cb(null, user); + }); + }else{ + UserStore.getByAttr(['username', 'password'], [input.username, input.password], false, function(e, user){ + if(!user){ + cb('invalid-user'); + }else{ + cb(null, user, tokenService.create(user.id, Date.now(), SESSION_CONFIG.secret)); + } + }); + } +}; +/** + * Check the token whether expire. + * + * @param {Object} token token info + * @param {Number} expire expire time + * @return {Boolean} true for not expire and false for expire + */ +var checkExpire = function(token, expire){ + if(expire < 0){ + // negative expire means never expire + return true; + } + return (Date.now() - token.timestamp) < expire; +}; diff --git a/game-server/app/servers/game/remote/tableRemote.js b/game-server/app/servers/game/remote/tableRemote.js new file mode 100644 index 0000000..8e4941f --- /dev/null +++ b/game-server/app/servers/game/remote/tableRemote.js @@ -0,0 +1,22 @@ +module.exports = function(app){ + return new Remote(app); +}; +var Remote = function(app){ + this.app = app; + this.tableService = app.get('tableService'); +}; +var remote = Remote.prototype; + +/** + * Remove member/player from table + * + * @param {string} uid user id + * @param {string} sid server id + * @param {string} tid channel id + * @param {function} cb callback + * + */ +remote.removeMember = function(uid, sid, tid, cb){ + this.tableService.removeMember(tid, uid, cb); +}; + diff --git a/game-server/app/servers/gate/handler/gateHandler.js b/game-server/app/servers/gate/handler/gateHandler.js new file mode 100644 index 0000000..dfa1e6d --- /dev/null +++ b/game-server/app/servers/gate/handler/gateHandler.js @@ -0,0 +1,37 @@ +var dispatcher = require('../../../util/dispatcher'); + +module.exports = function(app){ + return new Handler(app); +}; + +var Handler = function(app){ + this.app = app; +}; + +var handler = Handler.prototype; + +/** + * Gate handler that dispatch user to connectors. + * + * @param {Object} msg message from client + * @param {Object} session + * @param {Function} next next stemp callback + * + */ +handler.queryEntry = function(msg, session, next){ + // get all connectors + var connectors = this.app.getServersByType('connector'); + if(!connectors || connectors.length === 0){ + next(null, { + code : 500 + }); + return; + } + // select connector + var res = dispatcher.dispatch(1, connectors); + next(null, { + code : 200, + host : res.host, + port : res.clientPort + }); +}; \ No newline at end of file diff --git a/game-server/app/services/botService.js b/game-server/app/services/botService.js new file mode 100644 index 0000000..ed7e6f6 --- /dev/null +++ b/game-server/app/services/botService.js @@ -0,0 +1,388 @@ +var BOT_CONFIG = require('../../config/bots.json'); +var logger = require('pomelo-logger').getLogger('game-log', __filename); +var UserStore = require('../../app/persistence/users'); +Hand = require('hoyle').Hand; + + +var BotService = function(app){ + this.app = app; + this.channelService = app.get('channelService'); + this.tableService = app.get('tableService'); + this.tableInstance = {}; + this.tableInstanceActive = {}; + this.bots = {}; + this.config = BOT_CONFIG.config; +}; + +module.exports = BotService; + +BotService.prototype.start = function(cb){ + var me = this; + if(!me.config.enabled){ + return cb(); + } + me.registerBots(BOT_CONFIG.bots, function(){ + logger.info('all bots registered'); + me.checkAvailability(); + cb(); + }); +}; + +BotService.prototype.registerBots = function(bots, cb){ + var me = this, i = 0; + if(!bots.length){ + cb(); + } + function createIfNotExist(){ + bots[i].chips = 100000; + UserStore.create(bots[i], function(e, user){ + me.bots[user.id] = user; + me.bots[user.id].available = true; + if(++i == bots.length){ + return cb(); + } + createIfNotExist(); + }); + } + createIfNotExist(); +}; + +BotService.prototype.checkAvailability = function(){ + var me = this; + setInterval(function(){ + var bot = me.getAvailableBot(); + var table = me.getAvailableTable(); + if(bot && table && (!me.config.minBots || me.config.minBots > me.getActiveBots()) && !me.config.banBots){ + me.joinGame(bot, table); + } + }, (1000 * getRandomInt(me.config.joinInterval.min, (me.config.joinInterval.max)))); +}; + +BotService.prototype.getById = function(id){ + return this.bots[id]; +}; + +BotService.prototype.getAvailableBot = function(){ + var bot; + for(var i in this.bots){ + if(this.bots[i].available){ + bot = this.bots[i]; + } + } + return bot; +}; + +BotService.prototype.getActiveBots = function(){ + var ctr = 0; + for(var i in this.bots){ + if(!this.bots[i].available){ + ctr += 1; + } + } + return ctr; +}; + +BotService.prototype.getAvailableTable = function(){ + var table; + for(var i in this.tableService.tables){ + if(!this.tableInstanceActive[i] && (this.tableService.tables[i].state == 'JOIN' && this.tableService.tables[i].table.playersToAdd.length < this.tableService.tables[i].table.maxPlayers) || + (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)){ + table = this.tableService.tables[i]; + break; + } + } + return table; +}; + +BotService.prototype.joinGame = function(bot, table){ + var me = this; + me.tableService.addPlayer(table.id, bot.id, me.config.buyIn || 1000, function(e){ + if(e){ + return logger.error('bot error joining game', e); + } + bot.available = false; + bot.games = getRandomInt(me.config.gamesToPlay.min, me.config.gamesToPlay.max); + bot.tid = table.id; + logger.debug('bot '+bot.username+' joining table '+table.id+' for '+bot.games+' games'); + me.tableInstance[table.id] = me.tableInstance[table.id] || 0; + me.tableInstance[table.id] += 1; + me.listen(table.id); + }); +}; + +BotService.prototype.startGame = function(table, tid){ + var me = this; + var interval = setInterval(function(){ + if(table.state == 'IN_PROGRESS'){ + clearInterval(interval); + } + if(table.table.playersToAdd.length >= (me.config.minPlayers ? me.config.minPlayers : 0) && !me.config.banBots){ + table.tableService.startGame(tid, function(e){ + if(e){ + return logger.debug('cant start game yet', e); + } + clearInterval(interval); + }); + } + }, (1000 * getRandomInt(7, 20))); +} + +BotService.prototype.leaveGame = function(tid, uid, cb){ + var me = this; + me.tableService.removeMember(tid, uid, function(){ + cb(); + }); +}; + +BotService.prototype.listen = function(tid){ + var me = this; + if(me.tableInstance[tid] > 1){ + return false; + } + logger.debug('initializing listeners for table '+tid); + var table = me.tableService.getTable(tid); + var playerJoinedListener = function(){ + logger.debug('playerJoined'); + me.startGame(table, tid); + }; + var newRoundListener = function(){ + logger.debug('newRound'); + me.moveIfTurn(table); + }; + var turnStartListener = function(){ + logger.debug('turnStart'); + me.moveIfTurn(table); + }; + var gameInitListener = function(){ + logger.debug('gameInit'); + setTimeout(function(){ + me.removeAllBots(tid); + }, 300); + }; + table.table.eventEmitter.on('playerJoined', playerJoinedListener); + table.table.eventEmitter.on('newRound', newRoundListener); + table.table.eventEmitter.on('turnStart', turnStartListener); + table.table.eventEmitter.on('gameInit', gameInitListener); +}; + +BotService.prototype.moveIfTurn = function(table){ + var me = this, pid; + if(typeof table.table.currentPlayer === 'number' && table.table.players[table.table.currentPlayer]) + pid = table.table.players[table.table.currentPlayer].id; + if(this.bots[pid]){ + logger.debug('starting move: '+this.bots[pid].username); + table.table.stopTimer(); + setTimeout(function(){ + if(!table.table || !table.table.game) return false; + var board = table.table.game.board || []; + if(board.length < 3){ + table.table.players[table.table.currentPlayer].Call(); + }else{ + performMove(table); + } + logger.debug('completed move.'); + table.tableService.handleGameState(table.id, function(e){ + if(e){ + logger.error(e); + } + }); + }, (getRandomInt(me.config.actionInterval.min, me.config.actionInterval.max) * 1000)); + } +}; + +function performMove(table){ + var currentPlayer = table.table.players[table.table.currentPlayer]; + var myBet = table.table.game.bets[table.table.currentPlayer]; + var myHand = Hand.make(getHand(table.table.game.board.concat(table.table.players[table.table.currentPlayer].cards))); + var maxBet = myBet; + var maxRank = myHand.rank; + var winningPlayer = currentPlayer.playerName; + var hands = []; + for(var i=0;i maxBet) maxBet = bet; + if(hand.rank > maxRank){ + maxRank = hand.rank; + winningPlayer = table.table.players[i].playerName; + } + hands.push(hand); + } + logger.debug('has best hand: '+ winningPlayer); + var isWinner = false; + var winners = Hand.pickWinners(hands); + var diff = maxBet - myBet; + for(var i=0;i= maxBet){ + if(getRandomInt(1, 51) > 47){ + currentPlayer.AllIn(); + }else if(getRandomInt(1, 10) > 7){ + currentPlayer.Bet(getRandomInt(2, 53)); + }else{ + currentPlayer.Call(); + } + }else if(myBet < maxBet){ + if(diff > getRandomInt(4, 61)){ + if(getRandomInt(1, 73) > 71){ + currentPlayer.AllIn(); + }else if(getRandomInt(1, 10) > 8){ + currentPlayer.Bet(getRandomInt(2, 36)); + }else if(getRandomInt(1, 10) > 2){ + currentPlayer.Fold(); + }else{ + currentPlayer.Call(); + } + }else{ + if(getRandomInt(1, 73) > 70){ + currentPlayer.AllIn(); + }else if(getRandomInt(1, 10) > 7){ + currentPlayer.Bet(getRandomInt(2, 43)); + }else if(diff > getRandomInt(1, 7) && getRandomInt(1, 10) > 6){ + currentPlayer.Fold(); + }else{ + currentPlayer.Call(); + } + } + }else{ + currentPlayer.Call(); + } + }else{ + if(myBet >= maxBet){ + if(getRandomInt(1, 10) == 10){ + currentPlayer.AllIn(); + }else if(getRandomInt(1, 10) > 4){ + currentPlayer.Bet(getRandomInt(1, 271)); + }else{ + currentPlayer.Call(); + } + }else if(myBet < maxBet){ + if(getRandomInt(1, 10) == 10){ + currentPlayer.AllIn(); + }else if(getRandomInt(1, 10) > 4){ + currentPlayer.Bet(getRandomInt(2, 189)); + }else if(diff > getRandomInt(71, 104) && getRandomInt(1, 10) > 8){ + currentPlayer.Fold(); + }else{ + currentPlayer.Call(); + } + }else{ + currentPlayer.Call(); + } + } +} + +function getHand(hand){ + for(var j=0;j 10 || + obj.maxPlayers < 2 || + obj.maxPlayers > 10 + ))){ + return cb('invalid-table-rules'); + } + var tid = uuid.v1(); + this.tables[tid] = {}; + this.tables[tid].id = tid; + this.tables[tid].creator = uid; + this.tables[tid].state = 'JOIN'; + this.tables[tid].tableService = this; + obj.smallBlind = Math.round(parseInt(obj.smallBlind)); + obj.bigBlind = Math.round(parseInt(obj.bigBlind)); + obj.minBuyIn = Math.round(parseInt(obj.minBuyIn)); + obj.maxBuyIn = Math.round(parseInt(obj.maxBuyIn)); + obj.minPlayers = Math.round(parseInt(obj.minPlayers)); + obj.maxPlayers = Math.round(parseInt(obj.maxPlayers)); + obj.gameMode = (obj.gameMode == 'normal' || obj.gameMode == 'fast') ? obj.gameMode : 'normal'; + this.tables[tid].table = new Table(obj.smallBlind, obj.bigBlind, obj.minPlayers, obj.maxPlayers, obj.minBuyIn, obj.maxBuyIn, obj.gameMode, this.tables[tid]); + // automatically join created table +// session.set('tid', table.id); +// var tid = session.get('tid'); +// me.app.rpc.chat.chatRemote.add(session, session.uid, tid, function(e, users){ +// if(e){ +// next(500, { +// code : 200, +// error : e +// }); +// return; +// } +// var channelService = me.app.get('channelService'); +// var channel = channelService.getChannel(tid, true); +// channel.pushMessage({ +// route : 'onTableEvent', +// msg : tableService.getTableJSON(tid, session.uid) +// }); +// channel.pushMessage({ +// route : 'onUpdateUsers', +// users : users +// }); +// tableService.broadcastGameState(tid); +// next(null, { +// code : 200, +// route : msg.route +// }); +// }); + cb(null, this.tables[tid]); +}; + +/** + * Add member to the table + * + * @param {Object} tid id of an existing table + * @param {function} cb callback + * + */ +TableService.prototype.addMember = function(tid, uid, cb){ + var me = this; + var channelService = me.app.get('channelService'); + var table = this.tables[tid]; + if(!table){ + cb('table-not-found'); + return; + } + UserStore.getByAttr('id', uid, false, function(e, user){ + if(!user){ + cb(e); + } + var sid = getSidByUid(uid, me.app); + if(!sid){ + return cb('invalid-connector-server'); + } + // TODO: reduce payload by handling based on game state + var channel = channelService.getChannel(tid, true); + channel.add(uid, sid); + channelService.pushMessageByUids({ + route : 'onTableEvent', + msg : me.getTableJSON(tid, uid) + }, [{ + uid : uid, + sid : channel.getMember(uid)['sid'] + }], function(){ + logger.debug('initiated player '+uid+' into table '+tid+' with state '+table.state); + table.table.members.push(user); + channel.pushMessage({ + route : 'onUpdateUsers', + members : table.table.members + }); + cb(); + }); + }); +}; + +/** + * Get the connector server id associated with the uid + */ +var getSidByUid = function(uid, app){ + var connector = dispatcher.dispatch(uid, app.getServersByType('connector')); + if(connector){ + return connector.id; + } + return null; +}; + +/** + * Remove member from the table + * + * @param {Object} tid id of an existing table + * @param {string} uid userId to remove from the table + * @param {function} cb callback + * + */ +TableService.prototype.removeMember = function(tid, uid, cb){ + var me = this; + if(!me.tables[tid]){ + var e = 'table-not-found'; + logger.error('error removing player '+uid+' from table '+tid, e); + cb(e); + return; + } + var channelService = me.app.get('channelService'); + var channel = channelService.getChannel(tid, false); + if(channel && channel.getMember(uid)){ + channel.leave(uid, channel.getMember(uid)['sid']); + } + var user = me.getPlayerJSON(tid, uid, 'players') || me.getPlayerJSON(tid, uid, 'playersToAdd') || me.getPlayerJSON(tid, uid, 'previousPlayers'); + if(user){ + console.log('adding '+user.chips+' to player '+user.id); + me.updatePlayerInfo(uid, { + chips : user.chips + }, function(e, updatedUser){ + if(e){ + logger.error('error removing player '+uid+' from table ', e); + }else{ + logger.debug('removed player '+uid+' from table '+tid); + } + me.tables[tid].table.removePlayer(uid); + me.pushPlayerInfo(tid, uid, updatedUser); + me.handleGameState(tid, cb); + }); + }else{ + me.tables[tid].table.removePlayer(uid); + cb(); + } +}; + +/** + * Update player information + * + * @param {string} uid id of a user to update + * @param {object} obj updated player information + * @param {function} cb callback + * + */ +TableService.prototype.updatePlayerInfo = function(uid, obj, cb){ + UserStore.getByAttr('id', uid, false, function(e, user){ + if(e){ + return cb(e); + } + if(!user){ + return cb('user-not-found'); + } + var userObj = { + id : user.id + }; + if(obj.chips && typeof obj.chips === 'number' && obj.chips != 0){ + userObj.chips = Math.round(user.chips + Math.round(obj.chips)) + } + if(obj.wins){ + userObj.wins = Math.round(user.wins + Math.round(obj.wins)) + } + if(obj.wonAmount && obj.wonAmount > user.largestWin){ + userObj.largestWin = obj.wonAmount; + } + UserStore.set(userObj, function(e, updatedUser){ + if(e){ + cb(e); + return; + } + cb(null, updatedUser); + }); + }); +}; + +TableService.prototype.getTableJSON = function(tid, uid){ + if(!this.tables[tid]){ + return; + } + var table = this.tables[tid]; + return { + state : table.state, + id : (table.table && table.table.game && table.table.game.id ? table.table.game.id : undefined), + tid : tid, + creator : table.creator, + smallBlind : table.table.smallBlind, + bigBlind : table.table.bigBlind, + minPlayers : table.table.minPlayers, + maxPlayers : table.table.maxPlayers, + minBuyIn : table.table.minBuyIn, + maxBuyIn : table.table.maxBuyIn, + gameMode : table.table.gameMode, + players : this.getPlayersJSON(tid, 'players', uid), + playersToRemove : this.getPlayersJSON(tid, 'playersToRemove', uid), + playersToAdd : this.getPlayersJSON(tid, 'playersToAdd', uid), + gameWinners : this.getPlayersJSON(tid, 'gameWinners', uid), + actions : table.table.actions, + game : stripDeck(table.table.game, ['deck', 'id']), + board : (table.table.game && table.table.game.board) ? table.table.game.board : [], + currentPlayer : table.table.currentPlayer + }; +}; + +function stripDeck(obj, props){ + var out = {}; + for(var key in obj){ + if(props.indexOf(key) == -1){ + out[key] = obj[key]; + } + } + return out; +} + +TableService.prototype.getPlayerIndex = function(tid, uid, type){ + var match; + if(!this.tables[tid]){ + return; + } + for(var i=0;i table.maxBuyIn){ + cb('invalid-buyin'); + return; + } + buyIn = Math.round(buyIn); + UserStore.getByAttr('id', uid, false, function(e, user){ + if(e){ + cb(e); + return; + } + if(Math.round(user.chips) < table.minBuyIn){ + cb('below-minimum-buyin'); + return; + } + if(Math.round(user.chips) < buyIn){ + cb('not-enough-chips'); + return; + } + var chips = Math.round(user.chips - buyIn); + UserStore.set({ + id : user.id, + chips : chips + }, function(e, updatedUser){ + if(e){ + cb(e); + return; + } + table.eventEmitter.emit('playerJoined'); + var mIndex = me.getPlayerIndex(tid, updatedUser.id, 'members'); + if(typeof mIndex === 'number'){ + table.members[mIndex].chips = chips; + } + table.AddPlayer(updatedUser.username, buyIn, uid); + me.pushPlayerInfo(tid, user.id, updatedUser); + me.app.get('channelService').getChannel(tid, true).pushMessage({ + route : 'onUpdateUsers', + members : table.members + }); + me.app.get('channelService').getChannel(tid, true).pushMessage({ + route : 'onTableJoin', + msg : me.getPlayerJSON(tid, uid, 'playersToAdd') || me.getPlayerJSON(tid, uid) + }); + cb(); + }); + }); +}; + +/** + * Push detailed user information to a user + * + * @param {Object} tid id of an existing table + * @param {string} uid userId to add to the table + * @param {object} info player information + * @param {function} cb callback + * + */ +TableService.prototype.pushPlayerInfo = function(tid, uid, info){ + var channelService = this.app.get('channelService'); + var channel = channelService.getChannel(tid, false); + if(!channel || !channel.getMember(uid)) return; + channelService.pushMessageByUids({ + route : 'onUpdateMyself', + user : info + }, [{ + uid : uid, + sid : channel.getMember(uid)['sid'] + }], function(e){ + if(e){ + logger.error('unable to push player info ', e); + } + }); +}; + +/** + * Start the game + * + * @param {Object} tid id of an existing table + * @param {function} cb callback + * + */ +TableService.prototype.startGame = function(tid, cb){ + var table = this.tables[tid]; + if(!table){ + return cb('table-not-found'); + } + if(table.state != 'JOIN'){ + return cb('table-not-ready'); + } + if(table.table.active){ + return cb('table-still-active'); + } + if(table.table.playersToAdd.length < table.table.minPlayers){ + return cb('not-enough-players'); + } + if(table.table.playersToAdd.length > table.table.maxPlayers){ + return cb('too-many-players'); + } + // remove chips from user for buy in + table.table.StartGame(); + this.app.get('channelService').getChannel(tid, true).pushMessage({ + route : 'onUpdateUsers', + members : table.table.members + }); + this.broadcastGameState(tid); + cb(); +}; + +/** + * Perform a game action + * + * @param {string} tid table id + * @param {string} uid userId to add to the table + * @param {object} action an object containing the action type and optionally the amount of chips + * @param {function} cb callback + * + */ +TableService.prototype.performAction = function(tid, uid, action, cb){ + var me = this; + var table = this.tables[tid]; + if(!table){ + return cb('table-not-found'); + } + if(table.state != 'IN_PROGRESS'){ + return cb('game-not-ready'); + } + if(me.getPlayerIndex(tid, uid) != table.table.currentPlayer){ + return cb('not-your-turn'); + } + if(me.getPlayerJSON(tid, uid).folded == true){ + return cb('already-folded'); + } + if(action.action == 'bet' && isNaN(action.amt)){ + return cb('invalid-bet-amt'); + } + // perform action + if(action.action == 'call'){ + table.table.players[table.table.currentPlayer].Call(); + }else if(action.action == 'bet'){ + table.table.players[table.table.currentPlayer].Bet(parseInt(action.amt)); + }else if(action.action == 'check'){ + table.table.players[table.table.currentPlayer].Check(); + }else if(action.action == 'allin'){ + table.table.players[table.table.currentPlayer].AllIn(); + }else if(action.action == 'fold'){ + table.table.players[table.table.currentPlayer].Fold(); + }else{ + return cb('invalid-action'); + } + table.table.stopTimer(); + logger.debug('player '+uid+' executed action '+action.action+' on table '+tid+' with state '+table.state); + me.handleGameState(tid, function(e){ + if(e){ + return cb(e); + } + cb(); + }); +} + +/** + * End game and broadcast result to clients + * + * @param {string} tid table id + * @param {function} cb callback + * + */ +TableService.prototype.endGame = function(tid, cb){ + var me = this; + if(!me.tables[tid]){ + cb('table-not-found'); + return; + } + var table = me.tables[tid]; + if(table.table.game.roundName != 'GameEnd'){ + cb('not-game-end'); + return; + } + table.table.active = false; + table.table.stopTimer(); + me.saveResults(tid, function(e){ + if(e){ + cb(e); + return; + } + var channelService = me.app.get('channelService'); + channelService.getChannel(tid, false).pushMessage({ + route : 'onUpdateUsers', + members : table.table.members + }); + table.table.initNewGame(); + me.broadcastGameState(tid); + cb(); + }); +}; + +/** + * Store table results to persistence + * + * @param {string} tid id of the table + * @param {string} cb callback + * + */ +TableService.prototype.saveResults = function(tid, cb){ + var me = this; + if(!this.tables[tid]){ + cb('table-not-found'); + } + var table = this.tables[tid]; + TableStore.getByAttr('id', table.table.game.id, function(e, foundTable){ + if(foundTable){ + cb('game-already-exists'); + return; + } + TableStore.create(me.getTableJSON(tid), function(e, newTable){ + if(e){ + cb(e); + return; + } + var i = 0; + function saveWinner(){ + me.updatePlayerInfo(table.table.gameWinners[i].id, { + wins : 1, + wonAmount : table.table.gameWinners[i].amount + }, function(){ + if(++i === table.table.gameWinners.length){ + cb(); + }else{ + saveWinner(); + } + }); + } + if(table.table.gameWinners.length){ + saveWinner(); + }else{ + return cb(); + } + }); + }); +}; + +/** + * Handle end of game or broadcast game state to users + * + * @param {string} tid id of the table + * @param {function} cb callback + * + */ +TableService.prototype.handleGameState = function(tid, cb){ + var me = this; + var table = me.tables[tid]; + if(table.table && table.table.game && table.table.game.roundName == 'GameEnd' && table.state == 'IN_PROGRESS' && table.table.active){ + me.endGame(tid, cb); + }else{ + me.app.get('channelService').getChannel(tid, true).pushMessage({ + route : 'onUpdateUsers', + members : table.table.members + }); + me.broadcastGameState(tid); + cb(); + } +}; + +/** + * Broadcast game state by iteratively pushing game details to clients + * + * @param {string} tid id + * + */ +TableService.prototype.broadcastGameState = function(tid){ + var i = 0; + var me = this; + var channelService = me.app.get('channelService'); + var channel = channelService.getChannel(tid, false); + function broadcast(){ + if(i == me.tables[tid].table.members.length){ + if(me.tables[tid].state == 'IN_PROGRESS' && me.tables[tid].table.active){ + me.tables[tid].table.startTimer(); + } + return; + } + var uid = me.tables[tid].table.members[i].id; + if(channel.getMember(uid)){ + channelService.pushMessageByUids({ + route : 'onTableEvent', + msg : me.getTableJSON(tid, uid) + }, [{ + uid : uid, + sid : channel.getMember(uid)['sid'] + }], function(){ + ++i; + broadcast(); + }); + }else{ + ++i; + broadcast(); + } + } + broadcast(); +} + +/** + * Shuffles an array + * + * @param {array} ary an array + * + */ +TableService.prototype.shuffle = function(ary){ + var currentIndex = ary.length, temporaryValue, randomIndex; + while(0 !== currentIndex){ + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + temporaryValue = ary[currentIndex]; + ary[currentIndex] = ary[randomIndex]; + ary[randomIndex] = temporaryValue; + } + return ary; +}; + diff --git a/game-server/app/util/dispatcher.js b/game-server/app/util/dispatcher.js new file mode 100644 index 0000000..7dd957e --- /dev/null +++ b/game-server/app/util/dispatcher.js @@ -0,0 +1,6 @@ +var crc = require('crc'); + +module.exports.dispatch = function(uid, connectors){ + var index = Math.abs(crc.crc32(uid)) % connectors.length; + return connectors[index]; +}; \ No newline at end of file diff --git a/game-server/app/util/routeUtil.js b/game-server/app/util/routeUtil.js new file mode 100644 index 0000000..eafb14d --- /dev/null +++ b/game-server/app/util/routeUtil.js @@ -0,0 +1,12 @@ +var exp = module.exports; +var dispatcher = require('./dispatcher'); + +exp.game = function(session, msg, app, cb){ + var gameServers = app.getServersByType('game'); + if(!gameServers || gameServers.length === 0){ + cb(new Error('can not find game servers.')); + return; + } + var res = dispatcher.dispatch(1, gameServers); + cb(null, res.id); +}; \ No newline at end of file diff --git a/game-server/config/adminServer.json b/game-server/config/adminServer.json new file mode 100644 index 0000000..65da91e --- /dev/null +++ b/game-server/config/adminServer.json @@ -0,0 +1,13 @@ +[{ + "type": "connector", + "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" +}, { + "type": "chat", + "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" +},{ + "type": "game", + "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" +},{ + "type": "gate", + "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" +}] \ No newline at end of file diff --git a/game-server/config/adminUser.json b/game-server/config/adminUser.json new file mode 100644 index 0000000..9cedc79 --- /dev/null +++ b/game-server/config/adminUser.json @@ -0,0 +1,16 @@ +[{ + "id": "user-1", + "username": "admin", + "password": "admin", + "level": 1 +}, { + "id": "user-2", + "username": "monitor", + "password": "monitor", + "level": 2 +},{ + "id": "user-3", + "username": "test", + "password": "test", + "level": 2 +}] \ No newline at end of file diff --git a/game-server/config/bots.json b/game-server/config/bots.json new file mode 100644 index 0000000..1deff22 --- /dev/null +++ b/game-server/config/bots.json @@ -0,0 +1,63 @@ +{ + "config" : { + "enabled" : true, + "joinInterval" : { + "min" : 3, + "max" : 3 + }, + "actionInterval" : { + "min" : 1, + "max" : 3 + }, + "gamesToPlay" : { + "min" : 1, + "max" : 1 + }, + "buyIn" : 1000, + "minBots" : 7, + "minPlayers" : 3, + "validateChips" : false + }, + "bots" : [ + { + "username": "bot1", + "password": "bot1", + "email": "bot1@poker-stack.com" + }, + { + "username": "bot2", + "password": "bot2", + "email": "bot2@poker-stack.com" + }, + { + "username": "bot3", + "password": "bot3", + "email": "bot3@poker-stack.com" + }, + { + "username": "bot4", + "password": "bot4", + "email": "bot4@poker-stack.com" + }, + { + "username": "bot5", + "password": "bot5", + "email": "bot5@poker-stack.com" + }, + { + "username": "bot6", + "password": "bot6", + "email": "bot6@poker-stack.com" + }, + { + "username": "bot7", + "password": "bot7", + "email": "bot7@poker-stack.com" + }, + { + "username": "bot8", + "password": "bot8", + "email": "bot8@poker-stack.com" + } + ] +} \ No newline at end of file diff --git a/game-server/config/gameSettings.json b/game-server/config/gameSettings.json new file mode 100644 index 0000000..22af34d --- /dev/null +++ b/game-server/config/gameSettings.json @@ -0,0 +1,10 @@ +{ + "gameMode" : { + "normal" : { + "timeout" : 30 + }, + "fast" : { + "timeout" : 15 + } + } +} \ No newline at end of file diff --git a/game-server/config/log4js.json b/game-server/config/log4js.json new file mode 100644 index 0000000..eb0bd86 --- /dev/null +++ b/game-server/config/log4js.json @@ -0,0 +1,125 @@ +{ + "appenders": [ + { + "type": "console" + }, + { + "type": "file", + "filename": "${opts:base}/logs/con-log-${opts:serverId}.log", + "pattern": "connector", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + }, + "backups": 5, + "category": "con-log" + }, + { + "type": "file", + "filename": "${opts:base}/logs/rpc-log-${opts:serverId}.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + }, + "backups": 5, + "category": "rpc-log" + }, + { + "type": "file", + "filename": "${opts:base}/logs/forward-log-${opts:serverId}.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + }, + "backups": 5, + "category": "forward-log" + }, + { + "type": "file", + "filename": "${opts:base}/logs/rpc-debug-${opts:serverId}.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + }, + "backups": 5, + "category": "rpc-debug" + }, + { + "type": "file", + "level": "DEBUG", + "filename": "${opts:base}/logs/game-log-${opts:serverId}.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + }, + "backups": 5, + "category": "game-log" + }, + { + "type": "file", + "filename": "${opts:base}/logs/crash.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + }, + "backups": 5, + "category":"crash-log" + }, + { + "type": "file", + "filename": "${opts:base}/logs/admin.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + } + ,"backups": 5, + "category":"admin-log" + }, + { + "type": "file", + "filename": "${opts:base}/logs/pomelo.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + } + ,"backups": 5, + "category":"pomelo" + }, + { + "type": "file", + "filename": "${opts:base}/logs/pomelo-admin.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + } + ,"backups": 5, + "category":"pomelo-admin" + }, + { + "type": "file", + "filename": "${opts:base}/logs/pomelo-rpc.log", + "maxLogSize": 1048576, + "layout": { + "type": "basic" + } + ,"backups": 5, + "category":"pomelo-rpc" + } + ], + + "levels": { + "rpc-log" : "ERROR", + "forward-log": "ERROR", + "con-log" : "ERROR", + "rpc-debug": "ERROR", + "crash-log" : "ERROR", + "admin-log": "ERROR", + "pomelo" : "ERROR", + "pomelo-admin": "ERROR", + "pomelo-rpc": "ERROR" + }, + + "replaceConsole": true, + + "lineDebug": false +} diff --git a/game-server/config/master.json b/game-server/config/master.json new file mode 100644 index 0000000..49b3ee8 --- /dev/null +++ b/game-server/config/master.json @@ -0,0 +1,14 @@ +{ + "development":{ + "id":"master-server-1", + "host":"127.0.0.1", + "port":3005 + }, + + "production":{ + "id":"master-server-1", + "host":"127.0.0.1", + "port":3005 + } + +} diff --git a/game-server/config/redis.json b/game-server/config/redis.json new file mode 100644 index 0000000..f5b66cf --- /dev/null +++ b/game-server/config/redis.json @@ -0,0 +1,7 @@ +{ + "host" : "localhost", + "port" : 6379, + "opts" : { + "auth_pass" : "secretredispass" + } +} diff --git a/game-server/config/servers.json b/game-server/config/servers.json new file mode 100644 index 0000000..1a1a725 --- /dev/null +++ b/game-server/config/servers.json @@ -0,0 +1,30 @@ +{ + "development":{ + "connector":[ + {"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true} + ], + "game":[ + {"id":"game-server-1", "host":"127.0.0.1", "port":6050} + ], + "chat": [ + {"id":"chat-server-1","host":"127.0.0.1","port":3450} + ], + "gate":[ + {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true} + ] + }, + "production":{ + "connector":[ + {"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true} + ], + "game":[ + {"id":"game-server-1", "host":"127.0.0.1", "port":6050} + ], + "chat": [ + {"id":"chat-server-1","host":"127.0.0.1","port":3450} + ], + "gate":[ + {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true} + ] + } +} diff --git a/game-server/localstore/tables.json b/game-server/localstore/tables.json new file mode 100644 index 0000000..e69de29 diff --git a/game-server/localstore/users.json b/game-server/localstore/users.json new file mode 100644 index 0000000..e69de29 diff --git a/game-server/logs/.gitignore b/game-server/logs/.gitignore new file mode 100644 index 0000000..5e7d273 --- /dev/null +++ b/game-server/logs/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/game-server/package.json b/game-server/package.json new file mode 100644 index 0000000..b429e33 --- /dev/null +++ b/game-server/package.json @@ -0,0 +1,15 @@ +{ + "author": "Edward Yang", + "name": "poker-game-stack-server", + "description": "Texas Holdem poker game server", + "version": "0.0.1", + "private": false, + "dependencies": { + "pomelo": "~1.0.0", + "crc": "0.2.0", + "hoyle": "^0.2.2", + "node-uuid": "^1.4.1", + "pomelo-logger": "^0.1.6", + "redis": "^0.12.1" + } +} diff --git a/npm-install.bat b/npm-install.bat new file mode 100644 index 0000000..70e1051 --- /dev/null +++ b/npm-install.bat @@ -0,0 +1,4 @@ +::npm-install.bat +@echo off +::install web server dependencies && game server dependencies +cd web-server && npm install -d && cd .. && cd game-server && npm install -d \ No newline at end of file diff --git a/npm-install.sh b/npm-install.sh new file mode 100644 index 0000000..0858be4 --- /dev/null +++ b/npm-install.sh @@ -0,0 +1,5 @@ +cd ./game-server && npm install -d +echo '============ game-server npm installed ============' +cd .. +cd ./web-server && npm install -d +echo '============ web-server npm installed ============' diff --git a/shared/config/authCodes.json b/shared/config/authCodes.json new file mode 100644 index 0000000..57323f6 --- /dev/null +++ b/shared/config/authCodes.json @@ -0,0 +1,23 @@ +{ + "OK" : 200, + "FAILED" : 500, + "BAD_REQUEST" : 400, + "UNAUTHORIZED" : 401, + "FORBIDDEN" : 403, + "ENTRY" : { + "TOKEN_INVALID" : 1001, + "TOKEN_EXPIRE" : 1002, + "USER_NOT_EXIST" : 1003 + }, + + "GATE" : { + "NO_SERVER_AVAILABLE" : 2001 + }, + + "CHAT" : { + "CHANNEL_CREATE" : 3001, + "CHANNEL_NOT_EXIST" : 3002, + "UNKNOWN_CONNECTOR" : 3003, + "USER_NOT_ONLINE" : 3004 + } +} \ No newline at end of file diff --git a/shared/config/session.json b/shared/config/session.json new file mode 100644 index 0000000..9a7fa74 --- /dev/null +++ b/shared/config/session.json @@ -0,0 +1,4 @@ +{ + "secret": "poker-game-session-secret", + "expire": 21600000 +} \ No newline at end of file diff --git a/shared/server.crt b/shared/server.crt new file mode 100644 index 0000000..a084842 --- /dev/null +++ b/shared/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSzCCAbQCCQCQVN8rD6MylDANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJD +TjERMA8GA1UECAwIemhlamlhbmcxETAPBgNVBAcMCGhhbmd6aG91MRAwDgYDVQQK +DAdOZXRFYXNlMQ8wDQYDVQQLDAZwb21lbG8xEjAQBgNVBAMMCWxvY2FsaG9zdDAe +Fw0xNDA0MjIwNjEwMDJaFw0xNDA1MjIwNjEwMDJaMGoxCzAJBgNVBAYTAkNOMREw +DwYDVQQIDAh6aGVqaWFuZzERMA8GA1UEBwwIaGFuZ3pob3UxEDAOBgNVBAoMB05l +dEVhc2UxDzANBgNVBAsMBnBvbWVsbzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50 +l5+ptOVPMK3wgEj+YRyGWhBjugj9teVmLXY9ImWdZkBlvdAiQj7/S/1MxRbRtwEF +GRE5ul/X1M6I+F0UyTGYA1Mo0jIlQaBDXAAyDujCWi+qlyZ28efNDUlO2KBY1H4r +Xobm9hoEFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAMIuL8KqEEtjbfL/tR2+5dQ5 +958gtDtA62L7bMosl4hmuzdyWADu3IcKSaXAESLhIuIClt2Pwc14iFf9qRyB/cjY +4kLgwDGhK5EJw1kQS+Hs9NNSGxJTXUkoms3kEdRGy4hrZpTheJJNaKuv3oXrdvYQ +85yoc/P5OnJapB3huYL9 +-----END CERTIFICATE----- \ No newline at end of file diff --git a/shared/server.key b/shared/server.key new file mode 100644 index 0000000..1fcb9a2 --- /dev/null +++ b/shared/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50l5+ptOVPMK3wgEj+ +YRyGWhBjugj9teVmLXY9ImWdZkBlvdAiQj7/S/1MxRbRtwEFGRE5ul/X1M6I+F0U +yTGYA1Mo0jIlQaBDXAAyDujCWi+qlyZ28efNDUlO2KBY1H4rXobm9hoEFQIDAQAB +AoGAXhaeCUIyqeoynLWh+yzzOHFqzjpnrr0iIwYCgJycEqobRzLh7YXxLRdqe3al +U7Oq9TI2SR2CcEs9mWEi89VOzVvfu+4zRlvJLMzNjG8ncdvzmzWR288ORq6qmYVU +3KAEz/tbNaQMLrD43hkIb9BrSIb/cnwekl3pANo9dwytU5UCQQD4V6vTyzs/ob21 ++fO98tFkPtoHbt43S/1kDBSUyh6WWbS1KIQgtUSr2P5Ddtl6/vD3DW+XHCAhxyfV +vuDvaP/fAkEA0oomFfmlpvzYejYNKPOz2PR+M0oRFVwn7lYyNwbRtUK1JYOMHwJ/ +3gwQEgAcYEkvgRlsxX0T5vHNmoR3U3OqiwJAIWkiG9devDvVWxMqoKZ3V0ZBbPiU +etoFWB1r82yR2uZssmamCAR7HaeO5aKqtapw3rv3BFxrUkAJ8u7AMlVs/wJAVnpm +MGqNjyyWIoSnHSYUvk2WtKx8neBvimcfUxja9HAFBfaljGszaFpeE3a2MRp+h7GQ +ywGYNikmAYzdkoqVBwJAcOm/6u863pD2xA1mSFnmm3TulAMBfCULLdcY40w9m38b +D89R1ISEy//N1fWa4KTsM0GpVOowEyluc53XNRUghw== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/shared/token.js b/shared/token.js new file mode 100644 index 0000000..88a9123 --- /dev/null +++ b/shared/token.js @@ -0,0 +1,42 @@ +var crypto = require('crypto'); + +/** + * Create token by uid. Encrypt uid and timestamp to get a token. + * + * @param {String} uid user id + * @param {String|Number} timestamp + * @param {String} pwd encrypt password + * @return {String} token string + */ +module.exports.create = function(uid, timestamp, pwd){ + var msg = uid + '|' + timestamp; + var cipher = crypto.createCipher('aes256', pwd); + var enc = cipher.update(msg, 'utf8', 'hex'); + enc += cipher.final('hex'); + return enc; +}; + +/** + * Parse token to validate it and get the uid and timestamp. + * + * @param {String} token token string + * @param {String} pwd decrypt password + * @return {Object} uid and timestamp that exported from token. null for illegal token. + */ +module.exports.parse = function(token, pwd){ + var decipher = crypto.createDecipher('aes256', pwd); + var dec; + try{ + dec = decipher.update(token, 'hex', 'utf8'); + dec += decipher.final('utf8'); + } catch(err){ + console.error('[token] fail to decrypt token. %j', token); + return null; + } + var ts = dec.split('|'); + if(ts.length !== 2){ + // illegal token + return null; + } + return {uid: ts[0], timestamp: Number(ts[1])}; +}; diff --git a/web-server/app.js b/web-server/app.js new file mode 100644 index 0000000..f92addd --- /dev/null +++ b/web-server/app.js @@ -0,0 +1,26 @@ +var express = require('express'); +var app = express(); + +app.configure(function(){ + app.use(express.methodOverride()); + app.use(express.bodyParser()); + app.use(app.router); + app.set('view engine', 'jade'); + app.set('views', __dirname + '/public'); + app.set('view options', {layout: false}); + app.set('basepath',__dirname + '/public'); +}); + +app.configure('development', function(){ + app.use(express.static(__dirname + '/public')); + app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); +}); + +app.configure('production', function(){ + var oneYear = 31557600000; + app.use(express.static(__dirname + '/public', { maxAge: oneYear })); + app.use(express.errorHandler()); +}); + +console.log("Web server has started.\nPlease log on http://127.0.0.1:3002/index.html"); +app.listen(3002); diff --git a/web-server/bin/component.bat b/web-server/bin/component.bat new file mode 100644 index 0000000..6191909 --- /dev/null +++ b/web-server/bin/component.bat @@ -0,0 +1 @@ +cd public/js/lib && component install -f && component build -v \ No newline at end of file diff --git a/web-server/bin/component.sh b/web-server/bin/component.sh new file mode 100644 index 0000000..6191909 --- /dev/null +++ b/web-server/bin/component.sh @@ -0,0 +1 @@ +cd public/js/lib && component install -f && component build -v \ No newline at end of file diff --git a/web-server/package.json b/web-server/package.json new file mode 100644 index 0000000..c630289 --- /dev/null +++ b/web-server/package.json @@ -0,0 +1,10 @@ +{ + "author": "Edward Yang", + "name": "poker-game-stack-webclient", + "description": "Texas Holdem poker game web client", + "version": "0.0.1", + "private": false, + "dependencies": { + "express": "3.4.0" + } +} diff --git a/web-server/public/css/bootstrap.min.css b/web-server/public/css/bootstrap.min.css new file mode 100644 index 0000000..a9f35ce --- /dev/null +++ b/web-server/public/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.2.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/web-server/public/css/styles.css b/web-server/public/css/styles.css new file mode 100644 index 0000000..6095034 --- /dev/null +++ b/web-server/public/css/styles.css @@ -0,0 +1,31 @@ +.navbar-default .btn{margin-right:5px} +.panel-heading .btn-default.pull-right{margin:-5px;margin-left:15px} +.msg-text.error{color:#FF0000} + +#user-list-container .list-group{margin:0} +#user-list-container .panel-body{padding:0} +#user-list-container .list-group-item.is-me{background:#d9edf7;border-radius:0} + +#chat-container .panel-body{padding:0 2px} +#chat-container textarea{border:none;background:none;width:100%;padding:3px;height:58px;margin-top:10px;border-top:solid 1px #ccc} +#chat-area{height:130px;overflow-y:auto} +#chat-area .nick{padding:0 4px;font-weight:bold} +#chat-area .msg-text{margin-left:5px} + +#history-container .panel-body{padding:0 2px} +#history-area{min-height:100px;max-height:160px;overflow-y:auto} + +#poker-container h4{font-size:24px;color:#888;text-align:center;margin:20px auto} +#poker-container .panel-body .panel img{height:100px} +#poker-container .panel-body .panel .btn-primary{margin:10px 0} +#poker-container .panel-body .panel .btn-primary.input-group-addon{background-color:#428bca;border-color:#357ebd;padding:6px 12px} +#poker-container .panel-body .panel .btn-primary.input-group-addon, .btn-primary.input-group-addon:hover{color:#fff} + +#friend-list-container .list-group{margin:0} +#friend-list li{padding:4px 15px} +#friend-results-list{margin:15px 0 0 0;padding:0} + +#messenger-area{height:200px;overflow-y:auto;border: 1px solid #ccc} +#messenger-area .nick{padding:0 4px;font-weight:bold} +#messenger-area .msg-text{margin-left:5px} +#messenger-chatbox{background:none;width:100%;padding:10px;margin-top:10px;border:solid 1px #ccc} diff --git a/web-server/public/fonts/glyphicons-halflings-regular.eot b/web-server/public/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..4a4ca86 Binary files /dev/null and b/web-server/public/fonts/glyphicons-halflings-regular.eot differ diff --git a/web-server/public/fonts/glyphicons-halflings-regular.svg b/web-server/public/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..e3e2dc7 --- /dev/null +++ b/web-server/public/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web-server/public/fonts/glyphicons-halflings-regular.ttf b/web-server/public/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..67fa00b Binary files /dev/null and b/web-server/public/fonts/glyphicons-halflings-regular.ttf differ diff --git a/web-server/public/fonts/glyphicons-halflings-regular.woff b/web-server/public/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..8c54182 Binary files /dev/null and b/web-server/public/fonts/glyphicons-halflings-regular.woff differ diff --git a/web-server/public/images/cards/10_of_clubs.png b/web-server/public/images/cards/10_of_clubs.png new file mode 100644 index 0000000..18af741 Binary files /dev/null and b/web-server/public/images/cards/10_of_clubs.png differ diff --git a/web-server/public/images/cards/10_of_diamonds.png b/web-server/public/images/cards/10_of_diamonds.png new file mode 100644 index 0000000..3bbc4e0 Binary files /dev/null and b/web-server/public/images/cards/10_of_diamonds.png differ diff --git a/web-server/public/images/cards/10_of_hearts.png b/web-server/public/images/cards/10_of_hearts.png new file mode 100644 index 0000000..3eb83d7 Binary files /dev/null and b/web-server/public/images/cards/10_of_hearts.png differ diff --git a/web-server/public/images/cards/10_of_spades.png b/web-server/public/images/cards/10_of_spades.png new file mode 100644 index 0000000..0b3d294 Binary files /dev/null and b/web-server/public/images/cards/10_of_spades.png differ diff --git a/web-server/public/images/cards/2_of_clubs.png b/web-server/public/images/cards/2_of_clubs.png new file mode 100644 index 0000000..291ed97 Binary files /dev/null and b/web-server/public/images/cards/2_of_clubs.png differ diff --git a/web-server/public/images/cards/2_of_diamonds.png b/web-server/public/images/cards/2_of_diamonds.png new file mode 100644 index 0000000..4deee7c Binary files /dev/null and b/web-server/public/images/cards/2_of_diamonds.png differ diff --git a/web-server/public/images/cards/2_of_hearts.png b/web-server/public/images/cards/2_of_hearts.png new file mode 100644 index 0000000..75a014f Binary files /dev/null and b/web-server/public/images/cards/2_of_hearts.png differ diff --git a/web-server/public/images/cards/2_of_spades.png b/web-server/public/images/cards/2_of_spades.png new file mode 100644 index 0000000..1ce0ffe Binary files /dev/null and b/web-server/public/images/cards/2_of_spades.png differ diff --git a/web-server/public/images/cards/3_of_clubs.png b/web-server/public/images/cards/3_of_clubs.png new file mode 100644 index 0000000..076ab31 Binary files /dev/null and b/web-server/public/images/cards/3_of_clubs.png differ diff --git a/web-server/public/images/cards/3_of_diamonds.png b/web-server/public/images/cards/3_of_diamonds.png new file mode 100644 index 0000000..8ee0b4b Binary files /dev/null and b/web-server/public/images/cards/3_of_diamonds.png differ diff --git a/web-server/public/images/cards/3_of_hearts.png b/web-server/public/images/cards/3_of_hearts.png new file mode 100644 index 0000000..8e74673 Binary files /dev/null and b/web-server/public/images/cards/3_of_hearts.png differ diff --git a/web-server/public/images/cards/3_of_spades.png b/web-server/public/images/cards/3_of_spades.png new file mode 100644 index 0000000..f9e06b4 Binary files /dev/null and b/web-server/public/images/cards/3_of_spades.png differ diff --git a/web-server/public/images/cards/4_of_clubs.png b/web-server/public/images/cards/4_of_clubs.png new file mode 100644 index 0000000..8be9e08 Binary files /dev/null and b/web-server/public/images/cards/4_of_clubs.png differ diff --git a/web-server/public/images/cards/4_of_diamonds.png b/web-server/public/images/cards/4_of_diamonds.png new file mode 100644 index 0000000..70e82e8 Binary files /dev/null and b/web-server/public/images/cards/4_of_diamonds.png differ diff --git a/web-server/public/images/cards/4_of_hearts.png b/web-server/public/images/cards/4_of_hearts.png new file mode 100644 index 0000000..ceecbfe Binary files /dev/null and b/web-server/public/images/cards/4_of_hearts.png differ diff --git a/web-server/public/images/cards/4_of_spades.png b/web-server/public/images/cards/4_of_spades.png new file mode 100644 index 0000000..95abe3e Binary files /dev/null and b/web-server/public/images/cards/4_of_spades.png differ diff --git a/web-server/public/images/cards/5_of_clubs.png b/web-server/public/images/cards/5_of_clubs.png new file mode 100644 index 0000000..bde9777 Binary files /dev/null and b/web-server/public/images/cards/5_of_clubs.png differ diff --git a/web-server/public/images/cards/5_of_diamonds.png b/web-server/public/images/cards/5_of_diamonds.png new file mode 100644 index 0000000..bb92525 Binary files /dev/null and b/web-server/public/images/cards/5_of_diamonds.png differ diff --git a/web-server/public/images/cards/5_of_hearts.png b/web-server/public/images/cards/5_of_hearts.png new file mode 100644 index 0000000..d923456 Binary files /dev/null and b/web-server/public/images/cards/5_of_hearts.png differ diff --git a/web-server/public/images/cards/5_of_spades.png b/web-server/public/images/cards/5_of_spades.png new file mode 100644 index 0000000..53a1aad Binary files /dev/null and b/web-server/public/images/cards/5_of_spades.png differ diff --git a/web-server/public/images/cards/6_of_clubs.png b/web-server/public/images/cards/6_of_clubs.png new file mode 100644 index 0000000..a9660a0 Binary files /dev/null and b/web-server/public/images/cards/6_of_clubs.png differ diff --git a/web-server/public/images/cards/6_of_diamonds.png b/web-server/public/images/cards/6_of_diamonds.png new file mode 100644 index 0000000..78a80ad Binary files /dev/null and b/web-server/public/images/cards/6_of_diamonds.png differ diff --git a/web-server/public/images/cards/6_of_hearts.png b/web-server/public/images/cards/6_of_hearts.png new file mode 100644 index 0000000..361643e Binary files /dev/null and b/web-server/public/images/cards/6_of_hearts.png differ diff --git a/web-server/public/images/cards/6_of_spades.png b/web-server/public/images/cards/6_of_spades.png new file mode 100644 index 0000000..40242a7 Binary files /dev/null and b/web-server/public/images/cards/6_of_spades.png differ diff --git a/web-server/public/images/cards/7_of_clubs.png b/web-server/public/images/cards/7_of_clubs.png new file mode 100644 index 0000000..9d6b545 Binary files /dev/null and b/web-server/public/images/cards/7_of_clubs.png differ diff --git a/web-server/public/images/cards/7_of_diamonds.png b/web-server/public/images/cards/7_of_diamonds.png new file mode 100644 index 0000000..6ad5f15 Binary files /dev/null and b/web-server/public/images/cards/7_of_diamonds.png differ diff --git a/web-server/public/images/cards/7_of_hearts.png b/web-server/public/images/cards/7_of_hearts.png new file mode 100644 index 0000000..19b89a2 Binary files /dev/null and b/web-server/public/images/cards/7_of_hearts.png differ diff --git a/web-server/public/images/cards/7_of_spades.png b/web-server/public/images/cards/7_of_spades.png new file mode 100644 index 0000000..b9f1b93 Binary files /dev/null and b/web-server/public/images/cards/7_of_spades.png differ diff --git a/web-server/public/images/cards/8_of_clubs.png b/web-server/public/images/cards/8_of_clubs.png new file mode 100644 index 0000000..cec743c Binary files /dev/null and b/web-server/public/images/cards/8_of_clubs.png differ diff --git a/web-server/public/images/cards/8_of_diamonds.png b/web-server/public/images/cards/8_of_diamonds.png new file mode 100644 index 0000000..ed12951 Binary files /dev/null and b/web-server/public/images/cards/8_of_diamonds.png differ diff --git a/web-server/public/images/cards/8_of_hearts.png b/web-server/public/images/cards/8_of_hearts.png new file mode 100644 index 0000000..fb39723 Binary files /dev/null and b/web-server/public/images/cards/8_of_hearts.png differ diff --git a/web-server/public/images/cards/8_of_spades.png b/web-server/public/images/cards/8_of_spades.png new file mode 100644 index 0000000..b6b3b38 Binary files /dev/null and b/web-server/public/images/cards/8_of_spades.png differ diff --git a/web-server/public/images/cards/9_of_clubs.png b/web-server/public/images/cards/9_of_clubs.png new file mode 100644 index 0000000..2174db5 Binary files /dev/null and b/web-server/public/images/cards/9_of_clubs.png differ diff --git a/web-server/public/images/cards/9_of_diamonds.png b/web-server/public/images/cards/9_of_diamonds.png new file mode 100644 index 0000000..0b933fb Binary files /dev/null and b/web-server/public/images/cards/9_of_diamonds.png differ diff --git a/web-server/public/images/cards/9_of_hearts.png b/web-server/public/images/cards/9_of_hearts.png new file mode 100644 index 0000000..7b196d6 Binary files /dev/null and b/web-server/public/images/cards/9_of_hearts.png differ diff --git a/web-server/public/images/cards/9_of_spades.png b/web-server/public/images/cards/9_of_spades.png new file mode 100644 index 0000000..3c3b5ff Binary files /dev/null and b/web-server/public/images/cards/9_of_spades.png differ diff --git a/web-server/public/images/cards/ace_of_clubs.png b/web-server/public/images/cards/ace_of_clubs.png new file mode 100644 index 0000000..42bf5ec Binary files /dev/null and b/web-server/public/images/cards/ace_of_clubs.png differ diff --git a/web-server/public/images/cards/ace_of_diamonds.png b/web-server/public/images/cards/ace_of_diamonds.png new file mode 100644 index 0000000..79cd3b8 Binary files /dev/null and b/web-server/public/images/cards/ace_of_diamonds.png differ diff --git a/web-server/public/images/cards/ace_of_hearts.png b/web-server/public/images/cards/ace_of_hearts.png new file mode 100644 index 0000000..b422124 Binary files /dev/null and b/web-server/public/images/cards/ace_of_hearts.png differ diff --git a/web-server/public/images/cards/ace_of_spades.png b/web-server/public/images/cards/ace_of_spades.png new file mode 100644 index 0000000..103f56d Binary files /dev/null and b/web-server/public/images/cards/ace_of_spades.png differ diff --git a/web-server/public/images/cards/ace_of_spades2.png b/web-server/public/images/cards/ace_of_spades2.png new file mode 100644 index 0000000..fbc3a2d Binary files /dev/null and b/web-server/public/images/cards/ace_of_spades2.png differ diff --git a/web-server/public/images/cards/black_joker.png b/web-server/public/images/cards/black_joker.png new file mode 100644 index 0000000..000b640 Binary files /dev/null and b/web-server/public/images/cards/black_joker.png differ diff --git a/web-server/public/images/cards/jack_of_clubs.png b/web-server/public/images/cards/jack_of_clubs.png new file mode 100644 index 0000000..8bad610 Binary files /dev/null and b/web-server/public/images/cards/jack_of_clubs.png differ diff --git a/web-server/public/images/cards/jack_of_clubs2.png b/web-server/public/images/cards/jack_of_clubs2.png new file mode 100644 index 0000000..5e003be Binary files /dev/null and b/web-server/public/images/cards/jack_of_clubs2.png differ diff --git a/web-server/public/images/cards/jack_of_diamonds.png b/web-server/public/images/cards/jack_of_diamonds.png new file mode 100644 index 0000000..0494785 Binary files /dev/null and b/web-server/public/images/cards/jack_of_diamonds.png differ diff --git a/web-server/public/images/cards/jack_of_diamonds2.png b/web-server/public/images/cards/jack_of_diamonds2.png new file mode 100644 index 0000000..131a977 Binary files /dev/null and b/web-server/public/images/cards/jack_of_diamonds2.png differ diff --git a/web-server/public/images/cards/jack_of_hearts.png b/web-server/public/images/cards/jack_of_hearts.png new file mode 100644 index 0000000..03cdfd4 Binary files /dev/null and b/web-server/public/images/cards/jack_of_hearts.png differ diff --git a/web-server/public/images/cards/jack_of_hearts2.png b/web-server/public/images/cards/jack_of_hearts2.png new file mode 100644 index 0000000..bf342bc Binary files /dev/null and b/web-server/public/images/cards/jack_of_hearts2.png differ diff --git a/web-server/public/images/cards/jack_of_spades.png b/web-server/public/images/cards/jack_of_spades.png new file mode 100644 index 0000000..734e4b1 Binary files /dev/null and b/web-server/public/images/cards/jack_of_spades.png differ diff --git a/web-server/public/images/cards/jack_of_spades2.png b/web-server/public/images/cards/jack_of_spades2.png new file mode 100644 index 0000000..f539c19 Binary files /dev/null and b/web-server/public/images/cards/jack_of_spades2.png differ diff --git a/web-server/public/images/cards/king_of_clubs.png b/web-server/public/images/cards/king_of_clubs.png new file mode 100644 index 0000000..ebde974 Binary files /dev/null and b/web-server/public/images/cards/king_of_clubs.png differ diff --git a/web-server/public/images/cards/king_of_clubs2.png b/web-server/public/images/cards/king_of_clubs2.png new file mode 100644 index 0000000..68e5774 Binary files /dev/null and b/web-server/public/images/cards/king_of_clubs2.png differ diff --git a/web-server/public/images/cards/king_of_diamonds.png b/web-server/public/images/cards/king_of_diamonds.png new file mode 100644 index 0000000..4c9aaf2 Binary files /dev/null and b/web-server/public/images/cards/king_of_diamonds.png differ diff --git a/web-server/public/images/cards/king_of_diamonds2.png b/web-server/public/images/cards/king_of_diamonds2.png new file mode 100644 index 0000000..e21d6a0 Binary files /dev/null and b/web-server/public/images/cards/king_of_diamonds2.png differ diff --git a/web-server/public/images/cards/king_of_hearts.png b/web-server/public/images/cards/king_of_hearts.png new file mode 100644 index 0000000..27d235a Binary files /dev/null and b/web-server/public/images/cards/king_of_hearts.png differ diff --git a/web-server/public/images/cards/king_of_hearts2.png b/web-server/public/images/cards/king_of_hearts2.png new file mode 100644 index 0000000..1d3c468 Binary files /dev/null and b/web-server/public/images/cards/king_of_hearts2.png differ diff --git a/web-server/public/images/cards/king_of_spades.png b/web-server/public/images/cards/king_of_spades.png new file mode 100644 index 0000000..b7abc52 Binary files /dev/null and b/web-server/public/images/cards/king_of_spades.png differ diff --git a/web-server/public/images/cards/king_of_spades2.png b/web-server/public/images/cards/king_of_spades2.png new file mode 100644 index 0000000..2edbbc1 Binary files /dev/null and b/web-server/public/images/cards/king_of_spades2.png differ diff --git a/web-server/public/images/cards/queen_of_clubs.png b/web-server/public/images/cards/queen_of_clubs.png new file mode 100644 index 0000000..949839c Binary files /dev/null and b/web-server/public/images/cards/queen_of_clubs.png differ diff --git a/web-server/public/images/cards/queen_of_clubs2.png b/web-server/public/images/cards/queen_of_clubs2.png new file mode 100644 index 0000000..7be5f9a Binary files /dev/null and b/web-server/public/images/cards/queen_of_clubs2.png differ diff --git a/web-server/public/images/cards/queen_of_diamonds.png b/web-server/public/images/cards/queen_of_diamonds.png new file mode 100644 index 0000000..791b273 Binary files /dev/null and b/web-server/public/images/cards/queen_of_diamonds.png differ diff --git a/web-server/public/images/cards/queen_of_diamonds2.png b/web-server/public/images/cards/queen_of_diamonds2.png new file mode 100644 index 0000000..928f650 Binary files /dev/null and b/web-server/public/images/cards/queen_of_diamonds2.png differ diff --git a/web-server/public/images/cards/queen_of_hearts.png b/web-server/public/images/cards/queen_of_hearts.png new file mode 100644 index 0000000..c95e2df Binary files /dev/null and b/web-server/public/images/cards/queen_of_hearts.png differ diff --git a/web-server/public/images/cards/queen_of_hearts2.png b/web-server/public/images/cards/queen_of_hearts2.png new file mode 100644 index 0000000..21839e6 Binary files /dev/null and b/web-server/public/images/cards/queen_of_hearts2.png differ diff --git a/web-server/public/images/cards/queen_of_spades.png b/web-server/public/images/cards/queen_of_spades.png new file mode 100644 index 0000000..c6c69ca Binary files /dev/null and b/web-server/public/images/cards/queen_of_spades.png differ diff --git a/web-server/public/images/cards/queen_of_spades2.png b/web-server/public/images/cards/queen_of_spades2.png new file mode 100644 index 0000000..7983d03 Binary files /dev/null and b/web-server/public/images/cards/queen_of_spades2.png differ diff --git a/web-server/public/images/cards/red_joker.png b/web-server/public/images/cards/red_joker.png new file mode 100644 index 0000000..55b3ef9 Binary files /dev/null and b/web-server/public/images/cards/red_joker.png differ diff --git a/web-server/public/index.html b/web-server/public/index.html new file mode 100644 index 0000000..43544cb --- /dev/null +++ b/web-server/public/index.html @@ -0,0 +1,603 @@ + + + + + + + prototype + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web-server/public/js/collections/MessageCollection.js b/web-server/public/js/collections/MessageCollection.js new file mode 100644 index 0000000..3bf285a --- /dev/null +++ b/web-server/public/js/collections/MessageCollection.js @@ -0,0 +1,7 @@ +define(["jquery", "backbone", "models/MessageModel"], function($, Backbone, MessageModel){ + var Collection = Backbone.Collection.extend({ + model : MessageModel, + url : 'messages' + }); + return Collection; +}); \ No newline at end of file diff --git a/web-server/public/js/collections/UserCollection.js b/web-server/public/js/collections/UserCollection.js new file mode 100644 index 0000000..3bf285a --- /dev/null +++ b/web-server/public/js/collections/UserCollection.js @@ -0,0 +1,7 @@ +define(["jquery", "backbone", "models/MessageModel"], function($, Backbone, MessageModel){ + var Collection = Backbone.Collection.extend({ + model : MessageModel, + url : 'messages' + }); + return Collection; +}); \ No newline at end of file diff --git a/web-server/public/js/desktop.js b/web-server/public/js/desktop.js new file mode 100644 index 0000000..ad47db3 --- /dev/null +++ b/web-server/public/js/desktop.js @@ -0,0 +1,24 @@ + +require.config({ + paths : { + "jquery" : "libs/jquery", + "underscore" : "libs/underscore", + "backbone" : "libs/backbone-min", + "bootstrap" : "libs/bootstrap.min", + "pomeloclient" : "libs/pomeloclient", + "socketio" : "libs/socket.io", + "resources" : 'libs/resources' + + }, + shim : { + "bootstrap" : { + "deps" : ["jquery"] + }, + "backbone" : { + deps : ['bootstrap', 'pomeloclient', 'socketio'] + } + } +}); +require(['jquery', 'backbone', 'routers/desktopRouter', 'bootstrap', 'resources'], function($, Backbone, Desktop){ + this.router = new Desktop(); +}); \ No newline at end of file diff --git a/web-server/public/js/libs/backbone-min.js b/web-server/public/js/libs/backbone-min.js new file mode 100644 index 0000000..8ea4b13 --- /dev/null +++ b/web-server/public/js/libs/backbone-min.js @@ -0,0 +1,2 @@ +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('