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



我正在用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(); }
}

注意:我从一个generate函数开始,它创建一个对象数组,表示一整包52张牌。当我在洗牌和切割后的不同时间控制台记录包时,我得到的结果各不相同,我似乎不明白为什么。

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

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

任何帮助都会很棒。

drop变量存储您应该从左手或右手重复的牌的数量。然而,有两种情况:

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

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

其中CCD_ 2可以大于包的一半中剩余的牌的数量,因此从CCD_。您可以通过以下方式解决此问题:

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

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

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

其他问题

  • 代码中有幻数(2652),它们可以是类中定义的常量,并给定适当的名称(即PACK_SIZE = 52),这意味着如果您创建一个表示不同数量卡片的子类,那么它仍然可以工作
  • hand有两个可能的值,它们可以表示为布尔值,但您可以将其指定为字符串(同样,您可以使用常量LEFT_HAND = true, RIGHT_HAND = !LEFT_HAND)
  • Pack似乎是一个全局变量——我本以为它应该是类的一个成员
  • 您不需要命名函数,因为这只会污染全局命名空间: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;
},

几件事:

  • 看起来全局的元素实际上并不是,因为这些元素都被封装在一个创建新"牌组"对象的函数中,并且一些元素需要是私有的,例如交易开始后留在包中的牌
  • 虽然布尔值对指针来说很好,但我想把它简化一下,所以使用字符串来选择obj属性
  • MTO所说的关于使用常量的一切都是绝对有效的
  • 现在每次拼接,我们都从数组中删除元素
  • 我更喜欢这种方法,因为它只使用一个while循环
  • 最后,这种类型的洗牌是为了模仿手洗牌,必须与其他手洗牌方法相结合,最好是以重复的顺序,才能产生有用的东西
  • 如果你想要一个持续随机和有效的东西,使用fischer-yates算法

最新更新