一个数组实现扑克牌均匀随机洗牌------多次洗牌能否避免黑客的计算?

闲来无事,研究下纸牌发牌,按斗地主来发吧,思路如下:

1,新建一个数组,长度52,将四种花色和大小王存储进数组

2,循环0至51,在循环因子i至52之间取随机数(能取到下界,不能取到上界),取到的随机数作为数组元素下标取该元素,与第i个元素交换位置,循环结束即排序完毕

3,输出纸牌即可。

思路明确,"啪啪啪~~" 12秒之后 贴上代码

1初始化数组

            //声明存放纸牌的数组
            string[] Card = new string[54];
            //初始化四种花色和大小王,分别用▲★◆■+数字和♂♀代表
            for (int i = 0; i < 54; i++)
            {
                switch(i/13)
                {
                    case 0: Card[i] = '▲' + (i % 13 + 1).ToString(); break;
                    case 1: Card[i] = '★' + (i % 13 + 1).ToString(); break;
                    case 2: Card[i] = '◆' + (i % 13 + 1).ToString(); break;
                    case 3: Card[i] = '■' + (i % 13 + 1).ToString(); break;
                    case 4: Card[i] = i % 13 == 0 ? "♂" : "♀"; break;
                    default: break;
                }
            }

 2 排序

        //纸牌洗牌函数(排序)
        public static string[] Sort(string[] Card)
        {
            //定义随机数
            Random ran=new Random();
            if (Card.Count() > 0)
            {
                for (int i = 0; i < Card.Count(); i++)
                {
                    int R=ran.Next(i, Card.Count());
                    //交换第i张牌和i之后的随机一张牌
                    string flag = Card[i];
                    Card[i] = Card[R];
                    Card[R] = flag;
                }
            }
            return Card;
        }

 3打印出来

            //打印
            //定义变量判断是否发完一家牌
            int pionter = 0;
            foreach (var card in Card)
            {
                Console.Write(card);
                pionter++;
                //发完一家
                if (pionter % 17 == 0)
                {
                    Console.Write("\n");
                    Console.Write("*****************************************************************");
                    Console.Write("\n");
                }
            }    

运行结果如下:

理论上说这个排序应该已经OK了, 但是看了《当随机不够随机:一个在线扑克游戏的教训》这篇博文之后,我们会意识到这种排序由于种子选取的原因(系统时间)存在被破解的风险

那么问题来了,怎么来避免这种情况呢 ? 首先我们尝试排序多次,多少次合适呢,完全可以排序随机次数,暂定在(5-15)之前随机一个数字作为排序次数,

那么我们可以修改我们的排序方法为如下代码:

        /// <summary>
        /// 纸牌洗牌函数(排序
        /// </summary>
        /// <param name="Card">待排序数组</param>
        /// <param name="num">排序次数</param>
        /// <returns></returns>
        public static string[] Sort(string[] Card,int num)
        {
            //定义随机数
            Random ran=new Random();
            if (Card.Count() > 0)
            {
                for (int i = 0; i < Card.Count(); i++)
                {
                    int R=ran.Next(i, Card.Count());
                    //交换第i张牌和i之后的随机一张牌
                    string flag = Card[i];
                    Card[i] = Card[R];
                    Card[R] = flag;
                }
            }
            //排序次数大于1则递归继续排序,当次数小于等于1时,返回Card
            if (num > 1)
            {
                num--;
                Sort(Card,num);
            }
            return Card;
        }

 理论上我们多次排序之后,通过运算解出排列的解已经非常困难了,先贴上全部的代码,然后再考虑我们的排序方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        Random ran=new Random();
        static void Main(string[] args)
        {
            //声明存放纸牌的数组
            string[] Card = new string[54];
            //初始化四种花色和大小王,分别用▲★◆■+数字和♂♀代表
            for (int i = 0; i < 54; i++)
            {
                switch (i / 13)
                {
                    case 0: Card[i] = '▲' + (i % 13 + 1).ToString(); break;
                    case 1: Card[i] = '★' + (i % 13 + 1).ToString(); break;
                    case 2: Card[i] = '◆' + (i % 13 + 1).ToString(); break;
                    case 3: Card[i] = '■' + (i % 13 + 1).ToString(); break;
                    case 4: Card[i] = i % 13 == 0 ? "♂" : "♀"; break;
                    default: break;
                }
            }
            Random Ran = new Random();
            int num = Ran.Next(5, 15);
            //调用洗牌函数洗牌
            Card = Sort(Card, num);

            //打印
            //定义变量判断是否发完一家牌
            int pionter = 0;
            foreach (var card in Card)
            {
                Console.Write(card);
                pionter++;
                //发完一家
                if (pionter % 17 == 0)
                {
                    Console.Write("\n");
                    Console.Write("*****************************************************************");
                    Console.Write("\n");
                }
            }
        }
        /// <summary>
        /// 纸牌洗牌函数(排序
        /// </summary>
        /// <param name="Card">待排序数组</param>
        /// <param name="num">排序次数</param>
        /// <returns></returns>
        public static string[] Sort(string[] Card,int num)
        {
            //定义随机数
            Random ran=new Random();
            if (Card.Count() > 0)
            {
                for (int i = 0; i < Card.Count(); i++)
                {
                    int R=ran.Next(i, Card.Count());
                    //交换第i张牌和i之后的随机一张牌
                    string flag = Card[i];
                    Card[i] = Card[R];
                    Card[R] = flag;
                }
            }
            //排序次数大于1则递归继续排序,当次数小于等于1时,返回Card
            if (num > 1)
            {
                num--;
                Sort(Card,num);
            }
            return Card;
        }
    }
}

 1,我们的排序方法是从第一个数字开始排列,在一轮排序中,当排到第i个元素时,i之前的已经排列完毕,固定不动,

但是i之后的所有元素被抽到的概率是一样的,这能够保证排到每一张牌时,都是剩下所有牌均匀概率取1

2,我们不是排到i时候将除i之外的所有元素随机取1个来和i交换,这种方法显然会导致已排完的元素再次交换,导致洗牌不均匀的问题

3,我们排序每次都是从第一个开始来排,为什么不是随机一个元素开始排(例如从第10个元素开始排,排到53之后,再排0到9,实现很简单),

我个人思考之后认为这与从第一个元素排的随机程度和被破解的难度应该是一致的(貌似是废话)。

说了一堆~现就这样吧 OK that's all!

 

posted @ 2014-12-09 16:30  阿拉蕾家的小铁匠  阅读(767)  评论(2编辑  收藏  举报