本意是向写个网络版的斗地主,可真做起来才发现,单一个洗牌和发牌就有很多可思考和可以探讨的地方。
为了保证网络分发的效率和稳定性,真的如现实中一般一人一张的摸牌,就不如在服务器端发好三份牌,然后再通过网络发出去好一些了。而无论采用什么算法,其本质都是使得每个人获得每一张牌的概率都相同。这样的话,就有且只有两种思路,一种是洗牌,顺序发牌,一种是不洗牌,随机发牌。其中后一种思路又可以分为随机抽牌顺序发牌以及顺序发牌随机选人。
注:为了便于表述和讨论,我们这里不考虑留3张底牌的规则,规定每次发牌都是将54张牌发完,每个参与的玩家都能摸到18张牌。
实现代码在最后。
思路一:
办法一、关于洗牌的算法,最容易想到的比较简单的办法,就是从第一张牌开始,随机选一张牌与之交换,直到最后一张牌为止。这种算法,虽然使得任何一张牌都有可能被多次交换位置,但任何两张牌最后在某一位置X的概率是相等的,算法的时间复杂度也是O(n),总共的交换次数的期望值不到54次(随机选择的位置与当前位置相同的时候不进行交换,至少有一次交换发生这种情况的概率为60%以上)。这种办法也是目前网络上最流行的办法。
办法二、在第一种办法的基础上进行改进。
随机选择一张牌,与第54张牌进行交换;
从前53张牌随机选择一张牌,与第53张牌进行交换;
从前52张牌随机选择一张牌,与第52张牌进行交换;
…
直至到只剩下一张牌位置,不用再进行交换。
办法三、每次随机抽取两张牌进行交换,进行27次,这样循环的次数降低为了原来的一半,但其中任何一张牌仍然停留在原地的概率达到了36%以上,显然不太合理。
办法四、采用多线程方式,每个线程领取一张牌,然后再放回,通过线程的顺序难以确定性进行洗牌。
办法五、将前面的每一种办法里的一张牌更换为多张牌,例如随机选择两组牌进行随机的归并或者交换,这种办法最能模拟我们平时现实中的洗牌方式,但对于计算机来说时间的复杂度就比较高了。
思路二:
办法一、不洗牌,随机抽牌,顺序发牌,这其实是在前一种思路的第二种办法上进行改进。
随机抽取一张牌,发给第一个人,然后将第54张牌放入抽取的位置;
从前53张牌中随机抽取一张牌,发给第二个人,然后将第53张牌放入这次抽取的位置;
…
如此循环,直至将牌发完,这样的方法比洗牌时的每次循环减少了交换的时间,只需要一次赋值即可,并且不需要在洗牌之后,再次遍历进行发牌。
#include
#include
int main(void){
srand(time(0));
int a[54]; //扑克牌
int i = 0; //计数
int count = 54; //算法中 count == 54 – i
int num = 0;//选中的牌的下标
int poker_in_hands[3][18] = {};//每个人手中的牌
int human[3] = {};//每个人手中牌的数量,也是下次收牌的位置下标
//为扑克牌数组赋初始值
for(i = 0; i < 54; ++i){
a[i] = i;
}
//发牌
for(i = 0; i < 54; ++i){
num = rand() % count;
poker_in_hands[i % 3][human[i % 3]] = a[num];
++human[i % 3];
//以下四行
int temp = a[num];
a[num] = a[count - 1];
a[count - 1] = temp;
--count;
//以上四行的写法可以在发牌的同时得到了一副洗好的牌,也可以单独作为洗牌的算法来使用,如果打算重玩的时候再次由新牌洗起,可以只写 a[num] = a[count--];
//本算法的根本思想是每次将选中的数字发出去,并且将其移动到最后一个,以确保不会再次被选到(通过count--)
}
//打印每个人手中拿到的牌
int m, n;
for(m = 0; m < 3; ++m){
for(n = 0; n < 18; ++n){
printf("%3d", poker_in_hands[m][n]);
}
printf("\n");
}
return 0;
}
办法二、<最优化算法>
前面的所有办法,在发牌结束后,每个玩家的牌都是无序的,如果想要达到便于游戏使用的顺序牌序,则需要再次进行排序,虽然在54张牌的时候这种时间的多余消耗几乎可以忽略不计,但如果遇到更为复杂的类似情况,就会比较明显了。
基于这种考虑,这种方法不对牌的顺序进行打乱,顺序进行发牌,随机选择每次的收牌人,当某个人手中的牌数量达到18的时候,剥夺他的收牌权,这样只需要对初始牌进行一次遍历,就实现了所有的目的,每组牌都可以直接发送给对应的玩家进行呈现了。
#include
#include
int main(void){
srand(time(0));
int poker = 0; //扑克牌
int human[3] = {0};//每个人手里牌的数量
int* p_human[3] = {&(human[0]), &(human[1]), &(human[2])};//三个指针分别指向每个人手里的牌数
int poker_in_hands[3][18] = {};//每个人手里的牌
int* p_poker_in_hands[3] = {poker_in_hands[0], poker_in_hands[1], poker_in_hands[2]};//三个指针分别指向每个人手里拿到的牌
int count = 3;//三个人
int who = -1;//第几个人
do{
who = rand() % count;//随机指定摸牌的人
p_poker_in_hands[who][*p_human[who]] = poker;//将当前牌发给随机产生的那个人
++*(p_human[who]);//摸牌者手里的牌数加一
if(18 == *(p_human[who])){//如果这个人摸够了18张牌
p_human[who] = p_human[count - 1];//将指向这个人拿到的牌数的指针 指向最后一个人
p_poker_in_hands[who] = p_poker_in_hands[count - 1];//将指向这个人手里的牌的指针指向最后一个人,也就是说将最后一个人放到了原来摸够18张牌的人的位置
--count;//摸牌人数减一,取消最后一个人的摸牌权
}
++poker;//准备发下一张牌
}while(poker < 54);
//打印每个人摸到的扑克牌
int i = 0;
int j = 0;
for (i = 0; i < 3; ++i){
for(j = 0; j < 18; ++j){
printf("%3d", poker_in_hands[i][j]);
}
printf("\n");
}
return 0;
}