JavaScriptでポーカー

JavaScriptでポーカーを作りました。
最初はコンピューターと対戦できるようにしたかったんですが、
まともに勝負できるAIを考えるのが果てしなく面倒そうなので、
ポーカーのシミュレーターにしました。


実行ボタンで開始します。
(画像読み込みの間、表示されないトランプがでるかもしれないです。)


内部処理としては、現実の

揃えられたトランプ→シャッフル→上から5枚引く→役チェック→
カードをすべて戻す→シャッフル→上から5枚引く→役チェック…

ということを繰り返していることになります。


役は以下のように判定しています。
一番正直な方法をそのまま実装した感じです。

Poker.checkHand = function(cards) {
    var flush = 1;
    for(var i = 1; i < 5; ++i){
        if(cards[0].suit != cards[i].suit){
            flush = 0;
            break;
        }
    }
    var pair = 0;
    for(var i = 0; i < 4; ++i){
        for(var j = i+1; j < 5; ++j){
            if(cards[i].number == cards[j].number)
                ++pair;
        }
    }       
    cards.sort(function(a, b){
        return (a.number > b.number) ? 1 : -1;
    });     
    var straight = 1;
    for(var i = 1; i < 5; ++i){
        if(cards[i-1].number + 1 != cards[i].number){
            straight = 0;
            break;
        }
    }       
    if(cards[0].number == 1 && cards[1].number == 10 && cards[2].number == 11 && cards[3].number == 12 && cards[4].number == 13){
        straight = 2;
    }
    return (flush * 100 + straight * 10 + pair);
}

ポーカーの役判定コードとしてはどう書く?orgに、すごくスマートなコードが上がっていたのですが、スマートすぎて何をやってるのかイマイチわかりませんでした…。


今のところ20ミリ秒ごとに32回チェックを繰り返すように設定しています。
多分、もうちょっと増やしても大丈夫だと思うのですが、
あまりやりすぎてブラウザがフリーズしても困るので…。


動かしてみると、最初引いたときの状態は、ノーペア(役なし)になる確率が50%、ワンペアになる確率が42%、ツーペアになる確率が4%…。
ということがわかって面白いですね。
ロイヤルストレートフラッシュくらいになると、ほぼ0%です。
イカサマ以外でこれが出るとしたら奇跡ですね。


こういうのを知っていると現実にポーカーをやった時に、
「ノーペアになる確率が50%、ワンペアになる確率が42%…(メガネクイッ」
って出来るのでカッコいい…ことはないですが、何かの役に立つかもしれません。


ソースコードは以下においておきます。いつもよりは短めです。

// 一枚のカード
var Card = function(id,suit,number) {
    this.x = 0;
    this.y = 0;
    this.suit = suit;
    this.number = number;
    this.name = id + "" + this.suit + "" + this.number; 
    var url = "image/" + this.suit + "" + this.number + ".png";
    document.write('<img id="'+this.name+'" src="'+url+'" STYLE="position:absolute;top:'+this.y+';left:'+this.x+';visibility:hidden">');
}

Card.prototype.put = function(x,y) {
    var element = document.getElementById(this.name);
    element.style.left = x;
    element.style.top  = y;
    element.style.visibility = "visible";
}

Card.prototype.hidden = function() {
    document.getElementById(this.name).style.visibility = "hidden";
}

Card.SUIT_SPADE = 0;
Card.SUIT_DIA   = 1;
Card.SUIT_HEART = 2;
Card.SUIT_CLUB  = 3;
Card.SUIT_NONE  = 4;

// 1組52枚のトランプ
var Trump = function() {
    this.cards = new Array();
    for(var i = 0; i < 4; ++i){
        for(var j = 1; j < 14; ++j){
            this.cards.push(new Card(this.instanceCount.length,i,j));
        }
    }
    this.cards.reverse();
    this.instanceCount.push(this);
}
Trump.prototype.instanceCount = new Array();

// 一番上から一枚引きます。
Trump.prototype.drawTop = function() {
    return this.cards.pop();
}
// 一番上にカードを戻します。
Trump.prototype.returnTop = function(card) {
    this.cards.push(card);
}
// トランプをシャッフルします。
// Fisher-Yates
// http://la.ma.la/blog/diary_200608300350.htm
Trump.prototype.shuffle = function() {
    var i = this.cards.length;
    while(i){
        var j = Math.floor(Math.random()*i);
        var t = this.cards[--i];
        this.cards[i] = this.cards[j];
        this.cards[j] = t;
    }
    return this.cards;
}

var Poker = function() {
}

// 役を判定します。
Poker.checkHand = function(cards) {
    var flush = 1;
    for(var i = 1; i < 5; ++i){
        if(cards[0].suit != cards[i].suit){
            flush = 0;
            break;
        }
    }
    var pair = 0;
    for(var i = 0; i < 4; ++i){
        for(var j = i+1; j < 5; ++j){
            if(cards[i].number == cards[j].number)
                ++pair;
        }
    }       
    cards.sort(function(a, b){
        return (a.number > b.number) ? 1 : -1;
    });     
    var straight = 1;
    for(var i = 1; i < 5; ++i){
        if(cards[i-1].number + 1 != cards[i].number){
            straight = 0;
            break;
        }
    }       
    if(cards[0].number == 1 && cards[1].number == 10 && cards[2].number == 11 && cards[3].number == 12 && cards[4].number == 13){
        straight = 2;
    }
    return (flush * 100 + straight * 10 + pair);
}

// 役定数
Poker.HD_ROYAL_FLUSH      = 120;
Poker.HD_STRAIGHT_FLUSH   = 110;
Poker.HD_FLUSH            = 100;
Poker.HD_ROYAL_STRAIGHT   = 20;
Poker.HD_STRAIGHT         = 10;
Poker.HD_FOUR_OF_A_KIND   = 6;
Poker.HD_FULL_HOUSE       = 4;
Poker.HD_THREE_OF_A_KIND  = 3;
Poker.HD_TWO_PAIR         = 2;
Poker.HD_ONE_PAIR         = 1;
Poker.HD_NO_PAIR          = 0;

// checkHandの戻り値を役の名前に変換します。
Poker.codeToName = function(code) {
    switch(code){
        case Poker.HD_NO_PAIR:         return "No pair";
        case Poker.HD_ONE_PAIR:        return "One pair";
        case Poker.HD_TWO_PAIR:        return "Two pair";
        case Poker.HD_THREE_OF_A_KIND: return "Three of a kind";
        case Poker.HD_STRAIGHT:        return "Straight";
        case Poker.HD_ROYAL_STRAIGHT:  return "Straight";
        case Poker.HD_FLUSH:           return "Flush";
        case Poker.HD_FULL_HOUSE:      return "Full house";
        case Poker.HD_FOUR_OF_A_KIND:  return "Four of a kind";
        case Poker.HD_STRAIGHT_FLUSH:  return "Straight flush";
        case Poker.HD_ROYAL_FLUSH:     return "Royal flush";
    }
    return undefined;
}

// checkHandの戻り値を役の強さに変換します。
Poker.codeToRank = function(code) {
    switch(code){
        case Poker.HD_NO_PAIR:         return 0;
        case Poker.HD_ONE_PAIR:        return 1;
        case Poker.HD_TWO_PAIR:        return 2;
        case Poker.HD_THREE_OF_A_KIND: return 3;
        case Poker.HD_STRAIGHT:        return 4;
        case Poker.HD_ROYAL_STRAIGHT:  return 4;
        case Poker.HD_FLUSH:           return 5;
        case Poker.HD_FULL_HOUSE:      return 6;
        case Poker.HD_FOUR_OF_A_KIND:  return 7;
        case Poker.HD_STRAIGHT_FLUSH:  return 8;
        case Poker.HD_ROYAL_FLUSH:     return 9;
    }
    return undefined;
}

var nameList = ["ノーペア","1ペア","2ペア","3カード","ストレート","フラッシュ","フルハウス","4カード","ストレートフラッシュ","ロイヤルストレートフラッシュ"];
var handList = new Array(10);
var allCount = 0;
for(var i = 0; i < handList.length; ++i){
    handList[i] = 0;
}
document.write('<b><font size="4" id="hand"></font></b></br>');
document.write('<font size="1" id="alls"></font></br>');
document.write('\
    <div id="list">\
    '+setList()+'\
    </div>\
');
document.write('<input type="button" value="STOP" onclick="press(this)">');
        
var trump = new Trump();
trump.shuffle();

var player = new Array();

show();

var waitCount = 0;      
var timer = start();
function start() {
    var timer = setInterval(function(){
        if(--waitCount <= 0){
            waitCount = 0;
            for(var i = 0; i < 32; ++i){
                if(play()) break;
            }
        }
    },20);
    return timer;
}       
function press(button) {
    if(timer == 0){
        timer = start();
        button.value = "STOP";
    }else{
        clearInterval(timer);
        timer = 0;
        button.value = "START";
    }
}

function show() {
    ++allCount;
    for(var i = 0; i < 5; ++i){
        player[i] = trump.drawTop();
        player[i].put(30+i*70,210);
    }
    var result = Poker.checkHand(player);
    var rank   = Poker.codeToRank(result);
    ++handList[rank];
    var p = handList[rank] * 1.0 / allCount;
    p = Math.round(p * 10000);
    p = p / 10000;
    document.getElementById("hand").innerHTML      = nameList[rank];
    document.getElementById("hand"+rank).innerHTML = handList[rank].toString();
    document.getElementById("prob"+rank).innerHTML = p.toString();
    document.getElementById("alls").innerHTML      = allCount.toString();
    switch(rank){
        //case 7: waitCount = 30;  break;
        case 8: waitCount = 120; break;
        case 9: waitCount = 180; break;
    }
    return waitCount > 0;
}

function play(evt) {
    for(var i = 0; i < 5; ++i){
        player[i].hidden();
        trump.returnTop(player[i]);
    }
    trump.shuffle();
    return show();
}

function setList() {
    var ret = new Array();
    ret.push('<table border = 0>');
    ret.push('<tr>');
        for(var j = 1; j >= 0; --j){
            ret.push('<td>');
            ret.push('<table border = 1 width="180">');
            for(var i = ((j+1)*5)-1; i >= (j*5); --i){
            ret.push('\
                <tr>\
                    <td><font size="1">'+nameList[i]+'</font></td>\
                    <td><font size="1"><div id="hand'+i+'">'+handList[i]+'</div></font></td>\
                    <td><font size="1"><div id="prob'+i+'">0</div></font></td>\
                </tr>');
            }
            ret.push('</table>');
            ret.push('</td>');
        }
    ret.push('</tr></table>');
    return ret.join("");
}