UNPKG

casino-server

Version:

An multi-rule scalable online poker game server powered by redis, node.js and socket.io

1,602 lines (1,358 loc) 36.2 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ var Client = require('../lib/client'), Poker = require('../lib/poker'), Jinhua = require('../lib/jinhua_poker'), Holdem = require('../lib/holdem_poker'); var client = null; Poker.toHTML = function(cards) { var html = ''; for(var i=0; i<cards.length; i++) { var card = cards[i]; var color = card >> 4; var number = card & 0xf; var png = color + '_' + number + '.png'; html += "<img src='img/" + png + "'/>"; } return html; }; $(document).ready(function(){ var socket = io(); socket.log_traffic = true; client = new Client(socket); var lang = $.cookie('lang'); if(lang) { $("select#lang option").filter(function() { return $(this).val() == lang; }).prop('selected', true); hotjs.i18n.setLang( lang ); hotjs.i18n.translate(); } $('select#lang').change(function(){ $( "select#lang option:selected" ).each(function() { $.cookie('lang', $(this).val()); location.reload(); }); }); socket.on('hello', function(data){ $('#messages').empty(); $('div#cmds').empty(); showRoom(null); addMsg(data.msg); setTimeout(function(){ var u = localStorage.getItem('x_userid'); var p = localStorage.getItem('x_passwd'); if(u && p) { login(u, p); } else { //socket.emit('hello', {}); client.rpc('fastsignup', 0, parseSignUpReply); } }, 1000); }); client.on('prompt', updateCmds); client.on('shout', function(ret){ addMsg(ret.who.name + _T_('shout:') + ret.msg); }); client.on('look', function(ret){ showRoom(ret); }); client.on('refresh', function(ret){ showRoom(client.room); }); client.on('enter', function(ret){ addMsg(ret.who.name + _T_('enter') + ret.where); showRoom(client.room); }); client.on('exit', function(ret){ addMsg(ret.who.name + _T_('exit') + ret.where); if(ret.uid === client.uid) { showRoom(null); list_games(); } else { showRoom(client.room); } }); client.on('takeseat', function(ret){ addMsg(ret.who.name + _T_('take seat') + ret.where); showRoom(client.room); }); client.on('unseat', function(ret){ addMsg(ret.who.name + _T_('unseat from') + ret.where); showRoom(client.room); }); client.on('say', function(ret){ addMsg(ret.who.name + _T_('say:') + ret.msg); }); client.on('gamestart', function(ret){ addMsg(_T('game start')); if(ret.room) { client.room = ret.room; } if(ret.inseats) { var seats = client.room.seats; var seat = ret.inseats[0]; var uid = seats[ seat ]; addMsg( 'first/D button: ' + uid + ' at seat ' + seat ); } }); client.on('deal', function(ret){ addMsg(_T('dealing cards')); var room_cards = client.room.cards; var deals = ret.deals; var item, seat, cards; while(deals.length > 0) { item = deals.pop(); seat = item[0]; cards = item[1]; if(seat >= 0) { room_cards[ seat ] = Poker.sortByNumber( cards ); } else { client.room.shared_cards = Poker.merge(client.room.shared_cards, cards); } } showRoom(client.room); if(ret.delay) { addMsg(_T_('delay') + ret.delay + _T_('seconds') + _T_('to bet') ); } }); client.on('moveturn', function(ret){ var seat = ret.seat; $('li.seat').removeClass('active'); $('li#seat'+seat).addClass('active'); if(ret.uid === client.uid) { $('#cmds').removeClass('inactive'); $('#cmds').addClass('active'); } else { $('#cmds').removeClass('active'); $('#cmds').addClass('inactive'); } addMsg(_T('now:') + seat + ', ' + ret.uid); }); client.on('countdown', function(ret){ addMsg(_T('count down:') + ret.seat + ', ' + ret.sec); }); client.on('fold', function(ret){ addMsg( ret.uid + _T_('at seat') + ret.seat + _T_('fold')); }); client.on('call', function(ret){ var seat = parseInt(ret.seat); addMsg( ret.uid + _T_('at seat') + seat + _T_('call') + ret.call); client.room.pot += ret.call; var chips = client.room.chips; if(chips) { chips[ seat ] += ret.call; } var gamers = client.room.gamers; if(ret.uid in gamers) { gamers[ ret.uid ].coins -= ret.call; } showRoom(client.room); }); client.on('raise', function(ret){ var seat = parseInt(ret.seat); var raise_sum = (ret.call + ret.raise); addMsg( ret.uid + _T_('at seat') + seat + _T_('raise') + ret.raise + ' (' + raise_sum + ')'); client.room.pot += raise_sum; var chips = client.room.chips; if(chips) { chips[ seat ] += raise_sum; } var gamers = client.room.gamers; if(ret.uid in gamers) { gamers[ ret.uid ].coins -= raise_sum; } showRoom(client.room); }); client.on('pk', function(ret){ addMsg( ret.uid + _T_('at seat') + ret.seat + _T('pk') + ret.pk_uid + _T_('at seat') + ret.pk_seat + ', ' + _T('result') + ': ' + (ret.win?_T('win'):_T('fail'))); var gamers = client.room.gamers; if(ret.uid in gamers) { gamers[ ret.uid ].coins -= ret.pk_cost; } showRoom(client.room); }); client.on('seecard', function(ret){ var seat = parseInt(ret.seat); addMsg( ret.uid + _T_('at seat') + seat + _T_('seecard') ); if(ret.cards) { client.room.cards[ seat ] = ret.cards; showRoom(client.room); } }); client.on('showcard', function(ret){ addMsg( ret.uid + _T_('at seat') + ret.seat + _T_('showcard') ); if(ret.cards) { client.room.cards[ parseInt(ret.seat) ] = ret.cards; showRoom(client.room); } }); client.on('gameover', function(ret){ addMsg( _T('game over!')); var shared_cards = client.room.shared_cards; var gamers = client.room.gamers; var cards = client.room.cards; var chips = client.room.chips; while(ret.length > 0) { var gamer = ret.shift(); var uid = gamer.uid; var n = (gamer.prize - gamer.chips); if(n > 0) n = '+' + n; var mycards = gamer.cards; var pattern = ''; if(mycards.length === 3) { pattern = Jinhua.patternString(mycards); addMsg( '#' + gamer.seat + ', ' + uid + ': ' + n + ', ' + _T_(pattern) ); } else { var maxFive = Holdem.sort( Holdem.maxFive(mycards, shared_cards) ); pattern = Holdem.patternString( maxFive ); addMsg( '#' + gamer.seat + ', ' + uid + ': ' + n + ', ' + _T_(pattern) + ' (' + Poker.visualize(maxFive) + ')' ); } cards[ gamer.seat ] = gamer.cards; chips[ gamer.seat ] = gamer.chips; // if gamer still in room if(uid in gamers) { delete gamer.cards; delete gamer.chips; delete gamer.prize; gamers[ uid ] = gamer; } } showRoom(client.room); }); client.on('bye', function(ret){ addMsg(ret); }); $('#m').focus(); $('form').submit(function(e) { execCmd(); return false; }); }); /* * cmds { * exit: true, * takeseat: true, * unseat: true, * call: true, * raise: [50,100,150], * raise: 'range,0,1000000', * fold: true, * pk: ['zhang3', 'li4', 'wang5'], * seecard: true, * showcard: true, * } */ function parseSignUpReply(err,ret){ parseReply(err,ret); if(! err) { addMsg(_T('account created:') + ret.uid + '/' + ret.passwd); login(ret.uid, ret.passwd); } } function onBtnClicked(e) { var method = $(this).attr('id'); switch(method) { case 'fastsignup': client.rpc(method, $(this).attr('arg'), parseSignUpReply); break; default: client.rpc(method, $(this).attr('arg'), parseReply); } } function onInputBtnClicked(e){ var method = $(this).attr('id'); client.rpc(method, $('input#'+method).val(), parseReply); $('input#'+method).val(''); } function onInputBoxEnter(e) { if(e.which == 13) onInputBtnClicked.call(this, e); } function onDialogBtnClicked(e) { var method = $(this).attr('id'); var dlg = $('div#'+method); var x = ($(window).width() - dlg.width()) / 2; var y = ($(window).height() - dlg.height()) / 2; dlg.show(); dlg.css({ position:'absolute', left: x + 'px', top: y + 'px' }); $(this).hide(); } function onDialogXClicked(e) { var method = $(this).attr('X'); $('div#'+method).hide(); $('button#'+method).show(); } function onDialogOKClicked(e) { var method = $(this).attr('OK'); var args = {}; $('input.' + method).each(function(i, v){ var input = $(this); args[ input.attr('id') ] = input.val(); }); switch(method) { case 'signup': client.rpc(method, args, parseSignUpReply); break; default: client.rpc(method, args, parseReply); } } function updateCmds( cmds ){ var v, div, btn, words, label, input; for(var k in cmds) { v = cmds[ k ]; if(v === null) { $('div#'+k).remove(); $('button#'+k).remove(); } else if(v === true) { btn = $('<button>').text(_T(k)).attr('id', k).attr('arg', 0).addClass('cmd'); $('#cmds').append(btn); btn.on('click', onBtnClicked); } else if(typeof v === 'string') { div = $('<div>').attr('id',k).addClass('cmd'); $('#cmds').append(div); input = $('<input>').attr('id', k).addClass('cmd'); words = v.split(','); switch(words[0]) { case 'range': input.attr('type', 'range'); if(words[1]) { var min = parseInt(words[1]); input.attr('min', min).val(min); } if(words[2]) input.attr('max', parseInt(words[2])); break; case 'number': input.attr('type', 'number').attr('size',5); if(words[1]) input.attr('min', parseInt(words[1])); if(words[2]) input.attr('max', parseInt(words[2])); break; case 'password': input.attr('type', 'password').attr('size',40); break; //case 'text': default: input.attr('type', 'text').attr('size',40); break; } div.append(input); btn = $('<button>').text(_T(k)).attr('id', k).addClass('cmd'); div.append(btn); btn.on('click', onInputBtnClicked); input.keydown(onInputBoxEnter); } else if( Object.prototype.toString.call( v ) === '[object Array]' ) { div = $('<div>').attr('id',k).addClass('cmd'); $('#cmds').append(div); for(var i=0; i<v.length; i++) { var arg = v[i]; var t_arg = (typeof arg === 'string') ? _T(arg) : arg; btn = $('<button>').text(_T(k)+' '+ t_arg).attr('id', k).attr('arg', arg).addClass('cmd'); div.append(btn); btn.on('click', onBtnClicked); } } else if( typeof v === 'object' ) { btn = $('<button>').text(_T(k)).attr('id', k).addClass('cmd'); $('#cmds').append(btn); var dlg = $('<div>').attr('id',k).addClass('dialog'); $('body').append(dlg); dlg.hide(); var dlgheader = $('<div>').addClass('dlgheader'); dlg.append(dlgheader); dlgheader.append($('<span>').text(_T(k))); var X = $('<button>').text('X').attr('X', k).addClass('cmd'); dlgheader.append(X); for(var j in v) { label = $('<label>').attr('for', j).text(_T(j)+':').addClass('cmd'); input = $('<input>').attr('id', j).addClass(k).addClass('cmd'); words = v[j].split(','); switch(words[0]) { case 'range': input.attr('type', 'range'); if(words[1]) input.attr('min', parseInt(words[1])); if(words[2]) input.attr('max', parseInt(words[2])); break; case 'number': input.attr('type', 'number').attr('size',5); if(words[1]) input.attr('min', parseInt(words[1])); if(words[2]) input.attr('max', parseInt(words[2])); break; case 'password': input.attr('type', 'password').attr('size',40); break; //case 'text': default: input.attr('type', 'text').attr('size',40); break; } switch(j) { // auto fill if we remember uid & passwd case 'uid': var u = localStorage.getItem('x_userid'); if(u) input.val(u); break; case 'passwd': var p = localStorage.getItem('x_passwd'); if(p) input.val(p); break; } dlg.append(label).append(input).append('<br/>'); } var dlgfooter = $('<div>').addClass('dlgfooter'); dlg.append(dlgfooter); var OK = $('<button>').text('OK').attr('OK', k).addClass('cmd'); dlgfooter.append(OK); btn.on('click', onDialogBtnClicked); OK.on('click', onDialogOKClicked); X.on('click', onDialogXClicked); } else { } } } function login(u, p) { client.rpc('login', { uid: u, passwd: p }, function(err,ret){ if(err) { localStorage.removeItem('x_userid'); localStorage.removeItem('x_passwd'); echo(ret); socket.emit('hello', {}); } else { $('#messages').empty(); $('div#cmds').empty(); showRoom(null); localStorage.setItem('x_userid', u); localStorage.setItem('x_passwd', p); addMsg(ret.token.uid + ' (' + ret.profile.name + ') ' + _T('login success')); if(ret.cmds) { updateCmds(ret.cmds); if('entergame' in ret.cmds) { list_games(); } } } }); } function list_games(){ client.rpc('games', 0, function(err, ret){ if(err) echo(ret); else { $('#roomname').text(_T('available games')); var list = $('#seats'); list.empty(); for(var i=0; i<ret.length; i++) { var game = ret[i]; var str = (i+1) + ', ' + _T_( game.id ) + ': ' + game.name + ' (' + game.desc + '), ' + game.rooms + ' rooms'; list.append($('<li>').text(str)); } } }); } function list_rooms( gameid ) { client.rpc('rooms', gameid, function(err, ret){ if(err) echo(ret); else { var list = $('#seats'); list.empty(); for(var i=0; i<ret.length; i++) { var room = ret[i]; var str = 'room id: ' + room.id + ', name: "' + room.name + '", seats: ' + room.seats_taken + '/' + room.seats_count + ', gamers: ' + room.gamers_count; list.append($('<li>').text(str)); } } }); } function addMsg(str) { $('#messages').append($('<li>').text(str).addClass('msg')); var msgs = $('li.msg'); var n = msgs.length - 20; if(n > 0) { for(var i=0; i<n; i++) { msgs[i].remove(); } } } function echo(ret) { addMsg( JSON.stringify(ret) ); } function echoReply(err, ret) { addMsg( JSON.stringify(ret) ); } function parseReply(err, ret) { if(err) addMsg(ret); else if(ret.cmds) updateCmds(ret.cmds); } function showRoom(room) { $('#roomname').empty(); $('#roomdesc').empty(); $('#sharedcards').empty(); $('#pot').empty(); $('#countdown').empty(); $('#seats').empty(); $('#mycards').empty(); if(! room) return; $('#roomname').text( _T('room number') + ': ' + room.id + ' (' + room.name + ')'); var gamers = room.gamers; var seats = room.seats; var cards = room.cards; var chips = room.chips; $('#roomdesc').text(_T('gamers in room') + ': ' + Object.keys(gamers).join(', ')); for(var i=0, len=seats.length; i<len; i++) { var uid = seats[i]; var g = uid ? gamers[ uid ] : null; var str = "#" + i + ': '; if(g) { str += g.uid + ' (' + g.name + ') [' + g.coins + ', ' + g.score + ', ' + g.exp + ', ' + g.level + ']'; if(cards && cards[i]) { str += _T_('private cards') + '[ ' + Poker.visualize( cards[i] ) + ' ]'; if(g.uid === client.uid) { $('#mycards').html( client.uid + ', ' + _T('my cards') + ': <br/>' + Poker.toHTML(cards[i]) ); } } if(chips && chips[i]) { str += _T_('bet') + '[ ' + chips[i] + ' ]'; } } else { str += '(' + _T('empty') + ')'; } $('#seats').append($('<li>').text(str).attr('id', 'seat'+i).addClass('seat')); } if(room.shared_cards) { $('#sharedcards').html( _T('shared cards') + ': <br/>' + Poker.toHTML(room.shared_cards) ); } if(room.pot) { $('#pot').text( _T('pot') + ': ' + room.pot ); } } function execCmd() { var cmd = $('#m').val() + ''; if(cmd.length === 0) return false; $('#m').val(''); $('#m').focus(); var words = cmd.split(' '); switch(words[0]) { case 'clear': $('#seats').empty(); $('#messages').empty(); break; case 'fastsignup': client.rpc('fastsignup', 0, parseSignUpReply); break; case 'signup': client.rpc('signup', { uid: words[1], passwd: words[2] }, parseSignUpReply); break; case 'login': login(words[1], words[2]); break; case 'logout': client.rpc('logout', 0, parseReply); break; case 'games': list_games(); break; case 'rooms': list_rooms( words[1] ); break; case 'entergame': client.rpc('entergame', words[1], parseReply); break; case 'enter': client.rpc('enter', words[1], parseReply); break; case 'look': client.rpc('look', 0, function(err, ret){ if(err) echo(ret); else { showRoom(ret); } }); break; case 'exit': client.rpc('exit', 0, function(err, ret){ if(err) echo(ret); else { echo(ret); showRoom(null); list_games(); } }); break; case 'takeseat': client.rpc('takeseat', words[1], parseReply); break; case 'unseat': client.rpc('unseat', 0, parseReply); break; case 'shout': words.shift(); client.rpc('shout', words.join(' '), parseReply ); break; case 'say': words.shift(); client.rpc('say', words.join(' '), parseReply ); break; default: //client.say( cmd, parseReply ); } } },{"../lib/client":2,"../lib/holdem_poker":3,"../lib/jinhua_poker":4,"../lib/poker":5}],2:[function(require,module,exports){ exports = module.exports = Client; function Client( socket ) { this.uid = null; this.pin = null; this.profile = {}; this.events = {}; this.room = null; this.cmds = {}; this.setUplink( socket ); } Client.prototype.setUplink = function(socket) { var client = this; client.uplink = socket; if(! socket.gamers) { socket.gamers = {}; socket.gamers_count = 0; socket.rpc_seq = 0; socket.rpc_callbacks = {}; socket.on('notify', function( msg ){ // { uid:x, e:xx, args:xxx } if(socket.log_traffic) console.log('notify', msg); if(! msg) return; if(typeof msg !== 'object') return; var event = msg.e; if(! event) return; var args = msg.args; var target; if(msg.uid) { target = socket.gamers[ msg.uid ]; if(target) target.onNotify( event, args ); } else { var gamers = socket.gamers; for(var uid in gamers) { target = gamers[ uid ]; if(target) target.onNotify( event, args ); } } }); socket.on('rpc_ret', function(reply){ if(socket.log_traffic) console.log('rpc_ret', reply); if(reply && reply.seq) { var seq = reply.seq, err = reply.err, ret = reply.ret; var callback = socket.rpc_callbacks[ seq ]; if(callback) { if(! err) { if(ret && ret.cmds) { client.filterCmds( ret.cmds ); } } var func = callback.func; if(typeof func === 'function') func(err, ret); delete socket.rpc_callbacks[ seq ]; } } }); } client.uid = ++ socket.rpc_seq; socket.gamers[ client.uid ] = client; return this; }; Client.prototype.on = function(event, func) { this.events[ event ] = func; return this; }; Client.prototype.filterCmds = function(cmds) { for(var i in cmds) { if(cmds[i] !== null) this.cmds[i] = 1; } var login = cmds.login; var signup = cmds.signup; var fastsignup = cmds.fastsignup; if(login) { for(i in this.cmds) { cmds[i] = null; } cmds.login = login; if(signup) cmds.signup = signup; if(fastsignup) cmds.fastsignup = fastsignup; } for(i in this.cmds){ if(this.cmds[i] === null) delete this.cmds[i]; } }; Client.prototype.onNotify = function(event, args) { switch(event) { case 'prompt': this.filterCmds(args); break; case 'look': this.room = args; break; case 'enter': this.room.gamers[ args.who.uid ] = args.who; break; case 'leave': args.who = this.room.gamers[ args.uid ]; break; case 'takeseat': this.room.seats[ args.where ] = args.uid; args.who = this.room.gamers[ args.uid ]; break; case 'unseat': this.room.seats[ args.where ] = null; args.who = this.room.gamers[ args.uid ]; break; case 'say': args.who = this.room.gamers[ args.uid ]; break; case 'refresh': var uid = args.uid; if(uid === this.uid) { this.profile = args.profile; } var room = this.room; if(room && (uid in room.gamers)) { room.gamers[ uid ] = args.profile; } } var func; if(typeof (func = this.events[event]) === 'function') { func(args); } else if(typeof (func = this.events['else']) === 'function') { func(args); } switch(event) { case 'bye': this.pin = null; this.profile = {}; this.room = null; this.cmds = {}; break; } }; Client.prototype.removeUplink = function() { var socket = client.uplink; if(socket) { client.socket = null; delete socket.gamers[ this.uid ]; socket.gamers_count --; } return this; }; /* * accepted methods and args: * * signup, {uid, passwd, name, email, phone, uuid} * login, {uid, passwd} * logout, 0 * * games, 0 * rooms, gameid * shout, msg * entergame, gameid * enter, roomid * say, msg * look, 0 * takeseat, seat or '' * unseat, 0 * leave, 0 * * follow, 0 * addchip, n * giveup, 0 * pk, uid * checkcard, 0 * showcard, 0 * */ Client.prototype.rpc = function(method, args, func) { var client = this; var socket = client.uplink; if(typeof func !== 'function') { throw 'need a callback func(err,ret)'; } var callback_func = func; switch(method) { case 'fastsignup': case 'signup': break; case 'login': callback_func = function(err, ret){ if(! err) { if(client.uid !== ret.token.uid) { delete socket.gamers[ client.uid ]; } client.uid = ret.token.uid; client.pin = ret.token.pin; client.profile = ret.profile; socket.gamers[ client.uid ] = client; } func(err, ret); }; break; default: if(! client.pin) { func(400, 'need login first'); return this; } } var callback = { seq: ++ socket.rpc_seq, func: callback_func, t: Date.now() }; socket.rpc_callbacks[ callback.seq ] = callback; var req = { seq: callback.seq, uid: client.uid, pin: client.pin, f: method, args: args }; socket.emit('rpc', req); if(socket.log_traffic) console.log('rpc', req); return this; }; },{}],3:[function(require,module,exports){ var Poker = require('./poker'); var POKER_CARDS = Poker.CARDS; var HIGH_CARD = 1, // 高牌, AQ953 ONE_PAIR = 2, // 一对, KK854 TWO_PAIR = 3, // 两对, KKJJ9 THREE = 4, // 三条, KKK98 STRAIGHT = 5, // 顺子, 98765 FLUSH = 6, // 同花, FULLHOUSE = 7, // 葫芦, KKK99 FOUR = 8, // 四条, KKKK9 STRAIGHT_FLUSH = 9, // 同花顺, 98765 ROYAL_FLUSH = 10; // 皇家同花顺, AKQJ10 var HOLDEM_PATTERNS = { 0: 'invalid', // 错误 1: 'high card', // 高牌 2: 'one pair', // 一对 3: 'two pair', // 两对 4: 'three of a kind', // 三条 5: 'straight', // 顺子 6: 'flush', // 同花 7: 'fullhouse', // 葫芦 8: 'four of a kind', // 四条 9: 'straight flush', // 同花顺 10: 'royal flush' // 皇家同花顺 }; var Holdem = { HIGH_CARD: 1, ONE_PAIR: 2, TWO_PAIR: 3, THREE: 4, STRAIGHT: 5, FLUSH: 6, FULLHOUSE: 7, FOUR: 8, STRAIGHT_FLUSH: 9, ROYAL_FLUSH: 10, PATTERNS: HOLDEM_PATTERNS, }; exports = module.exports = Holdem; Holdem.sort = function(cards) { if(cards.length != 5) return cards; Poker.sortByNumber(cards); var n0 = cards[0] & 0xf, n1 = cards[1] & 0xf, n2 = cards[2] & 0xf, n3 = cards[3] & 0xf, n4 = cards[4] & 0xf; var d0 = n0 - n1, d1 = n1 - n2, d2 = n2 - n3, d3 = n3 - n4; if((d1 === 0) && (d2 === 0)) { if(d0 === 0) { // XXXXM } else if(d3 === 0) { // MXXXX -> XXXXM cards.push( cards.shift() ); } else { // MXXXN var c0 = cards.shift(); cards.splice(3, 0, c0); } } else if((d0 === 0) && (d1 === 0)) { // XXXMN, or XXXMM } else if((d2 === 0) && (d3 === 0)) { // MNXXX -> XXXMN cards.push( cards.shift() ); cards.push( cards.shift() ); } else if((d0 === 0) && (d2 === 0)) { //edit by kalbas d1->d2 // XXYYM } else if((d0 === 0) && (d3 === 0)) { // XXMYY -> XXYYM var c2 = cards[2]; cards.splice(2, 1); cards.push( c2 ); } else if((d1 === 0) && (d3 === 0)) { // MXXYY -> XXYYM cards.push( cards.shift() ); } else if(d0 === 0) { // XXABC } else if(d1 === 0) { // AXXBC -> XXABC var c_0 = cards.shift(); cards.splice(2, 0, c_0); } else if(d2 === 0) { // ABXXC -> XXABC var c_2 = cards[2], c_3 = cards[3]; cards.splice(2, 2); cards.unshift(c_3); cards.unshift(c_2); } else if(d3 === 0) { //edit by kalbas added d3 condition // ABCXX -> XXABC cards.push( cards.shift() ); cards.push( cards.shift() ); cards.push( cards.shift() ); } else { // ABCDE } return cards; }; Holdem.rank = function(cards) { if(cards.length != 5) return 0; Holdem.sort(cards); var c0 = cards[0] >> 4, c1 = cards[1] >> 4, c2 = cards[2] >> 4, c3 = cards[3] >> 4, c4 = cards[4] >> 4; var n0 = cards[0] & 0xf, n1 = cards[1] & 0xf, n2 = cards[2] & 0xf, n3 = cards[3] & 0xf, n4 = cards[4] & 0xf; var d0 = n0 - n1, d1 = n1 - n2, d2 = n2 - n3, d3 = n3 - n4; var isFlush = ((c0 === c1) && (c1 === c2) && (c2 === c3) && (c3 === c4)); var isStraight; if ((n0 === 14) && (d0 === 9)){ isStraight = ((n0 === 14) && (d0 === 9) && (d1 === 1) && (d2 === 1) && (d3 === 1)); // edited by kalbas A 5 4 3 2 1 straight } else { isStraight = ((d0 === 1) && (d1 === 1) && (d2 === 1) && (d3 === 1)); } var rank = (n0 << 16) | (n1 << 12) | (n2 << 8) | (n3 << 4) | n4; //edit by kalbas //if we face an A5432 straight we should n0=1 and then calculate the rank if ((n0 === 14) && (d0 === 9) && (d1 === 1) && (d2 === 1) && (d3 === 1)) { var exceptionaln0 = 1; rank = (exceptionaln0 << 16) | (n1 << 12) | (n2 << 8) | (n3 << 4) | n4; } //end edit by kalbas var pattern = 0; if(isFlush && isStraight) { if(n4 === 10) { // Poker.NUMBER_RANK['A'] // edited by kalbas, n0=14 can be A5432 too, we use n4=11=jack pattern = ROYAL_FLUSH; } else { pattern = STRAIGHT_FLUSH; } } else if((d0 === 0) && (d1 === 0) && (d2 === 0)) { pattern = FOUR; } else if((d0 === 0) && (d1 === 0) && (d3 === 0)) { pattern = FULLHOUSE; } else if(isFlush) { pattern = FLUSH; } else if(isStraight) { pattern = STRAIGHT; } else if((d0 === 0) && (d1 === 0)) { pattern = THREE; } else if((d0 === 0) && (d2 === 0)) { pattern = TWO_PAIR; } else if((d0 === 0)) { pattern = ONE_PAIR; } else { pattern = HIGH_CARD; } return (pattern << 20) | rank; }; /* * 如有两名以上的牌手在最后一轮下注结束时仍未盖牌,则须进行斗牌。 * 斗牌时,每名牌手以自己的两张底牌,加上桌面五张公共牌,共七张牌中,取最大的五张牌组合决定胜负. * 当中可包括两张或一张底牌,甚至只有公共牌。 */ Holdem.maxFive = function(private_cards, shared_cards) { var cards = Poker.sort( Poker.merge( Poker.clone(private_cards), shared_cards ) ); var len = cards.length; if(len < 5 || len > 7 ) return null; var maxrank = 0, maxcards = null, i, j, tmp, tmprank; if(len === 5) { return cards; } else if(len === 6) { for(j=0; j<6; j++) { tmp = Poker.clone(cards); tmp.splice(j, 1); tmprank = Holdem.rank( tmp ); if(tmprank > maxrank) { maxrank = tmprank; maxcards = tmp; } } } else if(len === 7) { /* for(i=0; i<7; i++) { for(j=0; j<6; j++) { tmp = Poker.clone(cards); tmp.splice(i, 1); tmp.splice(j, 1); tmprank = Holdem.rank( tmp ); if(tmprank > maxrank) { maxrank = tmprank; maxcards = tmp; } } } */ // edit start by kalbas // we rank only board cards at first, all 5 of them tmprank = Holdem.rank( shared_cards ); if(tmprank > maxrank) { maxrank = tmprank; maxcards = shared_cards; } // we rank 1st hole card + 4 board cards for(j=0; j<5; j++) { tmp = Poker.clone( shared_cards ); tmp.splice(j,1); tmp.push( private_cards[0] ); tmprank = Holdem.rank( tmp ); if(tmprank > maxrank) { maxrank = tmprank; maxcards = tmp; } } // we rank 2nd hole card + 4 board cards for(j=0; j<5; j++) { tmp = Poker.clone( shared_cards ); tmp.splice(j,1); tmp.push( private_cards[1] ); tmprank = Holdem.rank( tmp ); if(tmprank > maxrank) { maxrank = tmprank; maxcards = tmp; } } // we rank two hole cards + 3 board cards var iii = [1,1,1,1,1,1,2,2,2,3]; var jjj = [2,2,2,3,3,4,3,3,4,4]; var kkk = [3,4,5,4,5,5,4,5,5,5]; /* There are 10 ways to choose 3 cards out of 5, (5x4x3)/(3x2x1) 123(45) 124(35) 125(34) 134(25) 135(24) 145(23) 234(15) 235(14) 245(13) 345(12) therefore: push : iii[n] + jjj[n] + kkk[n] and then add two hole cards */ for(j=0; j<10; j++) { tmp = []; tmp.push( shared_cards[iii[j]-1] ); tmp.push( shared_cards[jjj[j]-1] ); tmp.push( shared_cards[kkk[j]-1] ); tmp.push( private_cards[0] ); tmp.push( private_cards[1] ); tmprank = Holdem.rank( tmp ); if(tmprank > maxrank) { maxrank = tmprank; maxcards = tmp; } } cards = maxcards; console.log(Poker.visualize(cards)+' : '+Holdem.patternString(cards)); // end edit by kalbas } return maxcards; }; Holdem.pattern = function(cards) { return Holdem.rank(cards) >> 20; }; Holdem.patternString = function(cards) { return HOLDEM_PATTERNS[ Holdem.rank(cards) >> 20 ]; }; Holdem.compare = function(a, b) { return Holdem.rank(a) - Holdem.rank(b); }; Holdem.view = function(cards) { var rank = Holdem.rank(cards); var pattern = rank >> 20; var str = Poker.visualize(cards).join(',') + ' -> ' + HOLDEM_PATTERNS[ pattern ] + ', rank:' + rank; console.log( str ); }; },{"./poker":5}],4:[function(require,module,exports){ var Poker = require('./poker'); var POKER_CARDS = Poker.CARDS; var HIGH_CARD = 1, // 单张 PAIR = 2, // 对子 STRAIGHT = 3, // 顺子 FLUSH = 4, // 同花 STRAIGHT_FLUSH = 5, // 同花顺 THREE = 6; // 豹子 var JINHUA_PATTERNS = { 0: 'invalid', 1: 'danzhang', 2: 'duizi', 3: 'shunzi', 4: 'tonghua', 5: 'tonghuashun', 6: 'baozi' }; var Jinhua = { HIGH_CARD: 1, PAIR: 2, STRAIGHT: 3, FLUSH: 4, STRAIGHT_FLUSH: 5, THREE: 6, PATTERNS: JINHUA_PATTERNS, }; exports = module.exports = Jinhua; Jinhua.sort = function(cards) { if(cards.length != 3) return cards; Poker.sortByNumber(cards); var n1 = cards[1] & 0xf, n2 = cards[2] & 0xf; if(n1 === n2) { // avoid pair at end cards.push( cards.shift() ); } return cards; }; Jinhua.rank = function(cards) { if(cards.length != 3) return 0; Jinhua.sort(cards); var c0 = cards[0] >> 4, c1 = cards[1] >> 4, c2 = cards[2] >> 4; var n0 = cards[0] & 0xf, n1 = cards[1] & 0xf, n2 = cards[2] & 0xf; var d0 = n0 - n1, d1 = n1 - n2; var rank = (n0 << 8) | (n1 << 4) | n2; var pattern = 0; if((d0 === 0) && (d1 === 0)) { pattern = THREE; } else if((c0 === c1) && (c1 === c2)) { if((d0 === 1) && (d1 === 1)) { pattern = STRAIGHT_FLUSH; } else { pattern = FLUSH; } } else if((d0 === 1) && (d1 === 1)) { pattern = STRAIGHT; } else if((d0 === 0) || (d1 === 0)) { pattern = PAIR; } else { pattern = HIGH_CARD; } return (pattern << 12) | rank; }; Jinhua.pattern = function(cards) { return Jinhua.rank(cards) >> 12; }; Jinhua.patternString = function(cards) { return JINHUA_PATTERNS[ Jinhua.rank(cards) >> 12 ]; }; Jinhua.compare = function(a, b) { return Jinhua.rank(a) - Jinhua.rank(b); }; Jinhua.view = function(cards) { var rank = Jinhua.rank(cards); var pattern = rank >> 12; var str = Poker.visualize(cards).join(',') + ' -> ' + JINHUA_PATTERNS[ pattern ] + ', rank:' + rank; console.log( str ); }; },{"./poker":5}],5:[function(require,module,exports){ var POKER_COLORS = { 4: '♠', // spade 3: '♥', // heart 2: '♣', // club 1: '♦' // diamond }; var POKER_NUMBERS = { 14 : 'A', 13 : 'K', 12 : 'Q', 11 : 'J', 10 : '10', 9 : '9', 8 : '8', 7 : '7', 6 : '6', 5 : '5', 4 : '4', 3 : '3', 2 : '2', 0 : '?' }; var POKER_NUMBER_RANK = { 'A': 14, 'K': 13, 'Q': 12, 'J': 11, '10': 10, '9': 9, '8': 8, '7': 7, '6': 6, '5': 5, '4': 4, '3': 3, '2': 2, '?': 0, '': 0 }; var POKER_COLOR_RANK = { 'S': 4, 'H': 3, 'C': 2, 'D': 1, '': 0 }; var RED_JOKER = (6 << 4) | 15; var BLACK_JOKER = (5 << 4) | 15; var POKER_CARDS = {}; for(var color=1; color<=4; color++) { for(var number=2; number<=14; number++) { var card = (color << 4) | number; POKER_CARDS[ card ] = POKER_NUMBERS[ number ] + '' + POKER_COLORS[ color ]; } } POKER_CARDS[ RED_JOKER ] = '@'; POKER_CARDS[ BLACK_JOKER ] = '*'; POKER_CARDS[ 0 ] = '?'; exports = module.exports = Poker; function Poker(str){ if(typeof str === 'string') { var c = POKER_COLOR_RANK[ str.charAt(0) ]; var n = POKER_NUMBER_RANK[ str.substring(1) ]; if(c && n) { return (c << 4) | n; } else { return 0; } } else if(typeof str === 'object') { var cards = []; for(var i=0; i<str.length; i++) { cards.push( Poker(str[i]) ); } return cards; } else { return 0; } } Poker.RED_JOKER = RED_JOKER; Poker.BLACK_JOKER = BLACK_JOKER; Poker.SPADE = 4; Poker.HEART = 3; Poker.CLUB = 2; Poker.DIAMOND = 1; Poker.COLORS = POKER_COLORS; Poker.NUMBERS = POKER_NUMBERS; Poker.CARDS = POKER_CARDS; Poker.NUMBER_RANK = POKER_NUMBER_RANK; Poker.visualize = function( cards ) { if(typeof cards === 'number') return POKER_CARDS[ cards ]; var v_cards = []; for(var i=0, len=cards.length; i<len; i++) { v_cards.push( POKER_CARDS[ cards[i] ] ); } return v_cards; }; Poker.newSet = function( options ) { var no_joker = true, no_color = [], no_number = [], no_card = []; if(options) { if(typeof options.no_joker === 'boolean') no_joker = options.no_joker; if(typeof options.no_color === 'object') no_color = options.no_color; if(typeof options.no_number === 'object') no_number = options.no_number; if(typeof options.no_card === 'object') no_card = options.no_card; } var cards = []; for(var color=1; color<=4; color++) { if(no_color.indexOf(color) >= 0) continue; for(var number=2; number<=14; number++) { if(no_number.indexOf(number) >= 0) continue; var card = (color << 4) | number; if(no_card.indexOf(card) >= 0) continue; cards.push( card ); } } if(! no_joker) { cards.push( RED_JOKER ); cards.push( BLACK_JOKER ); } return cards; }; Poker.clone = function(cards) { var cloned = []; for(var i=0; i<cards.length; i++) { cloned[i] = cards[i]; } return cloned; }; Poker.draw = function(cards, n) { var len = cards.length; if(len < n) return []; var subset = []; while(n -- > 0) { var i = Math.floor( Math.random() * len ); subset.push( cards[i] ); cards.splice(i,1); // NOTICE: splice will return an array len --; } return subset; }; Poker.randomize = function( cards ) { var randomized = this.draw(cards, cards.length); while(randomized.length > 0) { cards.push( randomized.shift() ); } return cards; }; Poker.compareColorNumber = function(a, b) { if(a == b) return 0; else { var aColor = a >> 4, aNumber = a & 0x0f; var bColor = b >> 4, bNumber = b & 0x0f; if(aColor == bColor) return aNumber - bNumber; else return aColor - bColor; } }; Poker.compareNumberColor = function(a, b) { if(a == b) return 0; else { var aColor = a >> 4, aNumber = a & 0x0f; var bColor = b >> 4, bNumber = b & 0x0f; if(aNumber == bNumber) return aColor - bColor; else return aNumber - bNumber; } }; Poker.compare = function(a, b) { return (a & 0xff) - (b & 0xff); }; Poker.sort = Poker.sortByColor = function( cards ) { return cards.sort( Poker.compareColorNumber ).reverse(); }; Poker.sortByNumber = function( cards ) { return cards.sort( Poker.compareNumberColor ).reverse(); }; Poker.merge = function( a, b ) { return a.concat(b); }; Poker.print = function( cards ) { var str = cards.join(','); console.log( str ); }; Poker.view = function( cards ) { var str = Poker.visualize(cards).join(','); console.log( str ); }; },{}]},{},[1]);