使用 Javascript 和不一致的数组值洗牌?

shuffling cards with Javascript and inconsistent array values?

我正在 javascript 中构建一个像一副纸牌一样的小模块。我的第一种方法可行但非常简单,因此我想创建一些洗牌方法来模仿现实世界中洗牌背后的想法。

在其他一些有用的函数中,我创建了 riffle、overhand 和 cut 函数,这些函数似乎都在那里工作,但是当按顺序重复调用它们时,返回的包数量不一致,来自 运行一遍又一遍,它似乎是某种竞争条件,但似乎无法理解如何避免它。

相关的私有方法是:

riffle : function riffle() {
        var top         = Pack.slice(0, 26);
        var bottom = Pack.slice(26, 52);
        Pack = [];
        console.log('top is '+top.length+" and bottom is "+bottom.length);
        var hand    = 'right';
        var result = [];
        var i = 52;

        while (i > 0) {
            var drop    = Math.floor(Math.random()*3)+1;
            var cards;



            if (hand === 'right' ) {
                if (drop >= top.length) {
                    cards = top;
                } else {
                    cards = top.splice(0, drop);
                }
                hand = 'left';

            } else {
                if (drop >= bottom.length) {
                    cards = bottom;
                } else {
                    cards = bottom.splice(0, drop);
                }
                hand = 'right';
            }

            result = result.concat(cards);
            i -= drop;

        }
        Pack = result;
        console.log(Pack.length+" after riffle");
        return this;
    },

 cut : function cut(fn) {
        var top         = Pack.slice(0, 26);
        var bottom = Pack.slice(26, 52);
        Pack = [];
        console.log(top);
        Pack = bottom.concat(top);
        console.log(Pack.length+" after cut");
        if (fn && typeof(fn) === 'function') { fn(); }
        return this;
    }

稍后我有一个名为 shuffle 的特权方法调用它们:

    shuffle   : function shuffle(cb) {
        State.cardsOut = [];
        Internal.generatePack().cut().riffle().riffle()
                            .riffle().riffle().riffle();

        if (cb && typeof(cb) === 'function') { cb(); }
    }

注意:我从生成函数开始,该函数创建代表一整包 52 张卡片的对象数组。当我在洗牌和剪切后的不同时间控制台记录包时得到的结果各不相同,我似乎无法弄清楚为什么。

你可以在这里看到我在做什么

https://gist.github.com/Pushplaybang/66bc7a1fa5d84eee2236

任何帮助都会很棒。

drop 变量存储您应该从左手或右手翻牌的牌数。但是,有两种情况:

if (drop >= top.length) {
    cards = top;
}

if (drop >= bottom.length) {
    cards = bottom;
}

其中 drop 可以大于半副牌中剩余的牌数,因此从 i 中减去的牌数将多于您实际翻牌的牌数。您可以通过以下方式解决此问题:

if (drop >= top.length) {
    drop  = top.length;
    cards = top;
    top   = [];
}

if (drop >= bottom.length) {
    drop   = top.length;
    cards  = bottom;
    bottom = [];
}

(您需要清空数组,否则您最终可能会将相同的卡片添加两次)。

其他问题

  • 你在代码中有幻数(2652)这些可以是在 class 中定义的常量并给出适当的名称(即 PACK_SIZE = 52)这意味着如果您创建一个代表不同数量卡片的子class,那么它仍然有效。
  • hand 有两个可能的值,可以表示为布尔值,但您将其分配给字符串(同样您可以使用常量 LEFT_HAND = true, RIGHT_HAND = !LEFT_HAND)。
  • Pack 似乎是一个全局变量 - 我本以为它应该是 class.
  • 的成员
  • 您不需要命名函数,因为这只会污染全局命名空间:riffle : function riffle() { 可以只是一个匿名函数 riffle : function() {.
  • 性能 - 每次迭代都会创建额外的数组,并且卡片会移动多次。这可能会更有效率。

像这样:

PACK_SIZE: 52,
riffle : function() {
  var index_of_cards_riffled_from_top = 0;
  var index_of_cards_riffled_from_bottom = this.PACK_SIZE / 2;
  var riffled_cards = [];
  while ( index_of_cards_riffled_from_top < this.PACK_SIZE / 2
         || index_of_cards_riffled_from_bottom < this.PACK_SIZE ) {
    var num_cards_to_riffle_top = Math.min( this.PACK_SIZE / 2 - index_of_cards_riffled_from_top, Math.floor( Math.random() * 3 ) + 1 );
    var num_cards_to_riffle_bottom = Math.min( this.PACK_SIZE - index_of_cards_riffled_from_bottom, Math.floor( Math.random() * 3 ) + 1 );
    while ( num_cards_to_riffle_top > 0 ) {
      riffled_cards.push( this.Pack[ index_of_cards_riffled_from_top++ ] );
      num_cards_to_riffle_top--;
    }
    while ( num_cards_to_riffle_bottom > 0 ) {
      riffled_cards.push( this.Pack[ index_of_cards_riffled_from_bottom++ ] );
      num_cards_to_riffle_bottom--;
    }
  }
  this.Pack = riffled_cards;
}

虽然@MTO 的回答确实解决了我的问题,但我想说明一下我是如何选择开始重构这个函数的。

    riffle : function riffle() {
        var cutPos = Math.floor(Math.random()*rv)+( (cardCount-rv) / 2 );
        var splitPack = {
            left : Pack.splice(0, cutPos),
            right : Pack.splice(0, Pack.length)
        };

        var hand    = 'right',result = [], i = 52, cards;

        while(i > 0) {
            drop    = Math.floor(Math.random()*3)+1;

            if (drop >= splitPack[ hand ].length) {
                drop = splitPack[ hand ].length;
            } 

            cards = splitPack[ hand ].splice(0, drop);
            hand = (hand === 'left') ? 'right' : 'left';

            result = result.concat(cards);
            cards = [];
            i -= drop;

        }

        Pack = result;
        console.log(Pack.length+" after riffle");
        return this;
    },

一些事情:

  • 看似全局的元素实际上并不是全局的,因为它们都包含在一个创建新 "deck" 对象的函数中,并且一些元素需要私有,例如卡片中保留一次的卡片交易开始了。
  • 虽然布尔值对手来说效果很好,但我想稍微简化一下,所以将字符串用于 select obj 属性。
  • MTO 关于使用常量所说的一切都是绝对有效的。
  • 到现在每次拼接,我们正在从数组中删除元素。
  • 我更喜欢这种方法,因为它只使用一个 while 循环。
  • 最后,这种洗牌是为了模拟手牌洗牌,必须与其他手牌洗牌方法结合使用,最好以重复顺序结合,以产生有用的东西,
  • 如果您想要始终随机且高效的东西,请使用 fischer-yates 算法。