zoukankan      html  css  js  c++  java
  • 玩物丧志(天凤麻雀洗牌代码)

    //天凤牌山生成代码 http://tenhou.net/stat/rand/
    // http://blog.tenhou.net/article/30503297.html
    // 用/* */ 包括的是角田原版注释,以//打头的都是畅畅注释
    
    void SampleYamaShuffle(){
        static const char *haiDisp[34]={
            "一","二","三","四","五","六","七","八","九",
            "①","②","③","④","⑤","⑥","⑦","⑧","⑨",
            "1","2","3","4","5","6","7","8","9",
            "東","南","西","北","白","發","中"
        };
    
        WORD wShuffleVersionMajor=1;
        int i;
        _MTRAND mtRoot; 
    
        TCHAR szSeedSeqName[32];//szSeedSeqName:一个宽字符数组 内容是当前系统时间
        {
            DWORD seed[MTRAND_N]; /* これは公開されない*/
            // seed 具体的生成方式是不公开的。这里角田应该考虑到了如果公布了最初种子的生成方式,就可能会有人通过生成真实种子而获得牌山数据,真 “看破牌山”
    
            if (1){
                // 实际服务器操作 省略了
                HCRYPTPROV hCP; /* for Win32 */
    
                if (!CryptAcquireContext(&hCP,NULL,NULL,PROV_RSA_FULL,0)) throw 0;
                if (!CryptGenRandom(hCP,sizeof(seed),(BYTE*)seed)) throw 0;
                if (!CryptReleaseContext(hCP,0)) throw 0; //三行异常处理
    
                SYSTEMTIME st; //查询系统时间
                GetLocalTime(&st);
    
                wsprintf(szSeedSeqName,_T("%d.%04d.%02d%02d.%02d%02d"),
                    wShuffleVersionMajor,
                    st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute); //写入 szSeedSeqName
    
                printf("seedDailyPublic=%s",szSeedSeqName);
    
                if (1) for(i=0;i<sizeof(seed)/sizeof(*seed);++i) printf(",%08X",seed[i]);
                printf("
    ");
            }else{
                 /* 検証用の入力  szSeedSeqNameをキーに検索 */
                 // 输入验证    // 以szSeedSeqName作为关键字搜索
                for(i=0;i<sizeof(seed)/sizeof(*seed);++i) seed[0]=/* REPLACE HERE */0;
            }
            mtRoot.init_by_array(seed,sizeof(seed)/sizeof(*seed)); //mtRoot 用于作为随机数种子,进行 mt199327ar 算法的种子初始化,只进行一次初始化
            //综上所述: mtRoot 是一个可能以系统时间、登录名等东西进行填充,用于随机数生成算法的种子。
        }
    
        int nGame=0;
    
        for(;nGame<10;++nGame)
        { 
            /* 配牌が10回行われた場合*/   // 十场比赛 (这里角田是随便举例)
    
            // 循环使用 mtRoot 这个种子生成随机数
            // mt199327ar 算法:马特赛特旋转演算法产生一个伪随机数,一般为MtRand()。参考链接:http://blog.csdn.net/caimouse/article/details/55668071
    
            mtRoot.genrand_int32(); /* 席順決定などで3つ消費*/ // 一场比赛用三个随机数确定座位(四个座位只需三个随机数) 
            mtRoot.genrand_int32();
            mtRoot.genrand_int32(); /* 三麻不使用这个 */
            mtRoot.genrand_int32(); /* 未使用 */
    
            _MTRAND mtLocal; 
            {
                DWORD seed[MTRAND_N]; /* 136!より十分に大きく*/   //它比136大得多!
                for(i=0;i<sizeof(seed)/sizeof(*seed);++i) seed[i]=mtRoot.genrand_int32(); //这里仍然用 mtRoot 不断生成随机数
    
                mtLocal.init_by_array(seed,sizeof(seed)/sizeof(*seed));// 这些随机数填充 mtLocal,于是 mtLocal 相当于一个新种子
    
                printf("mt.seed=%s,%d",szSeedSeqName,nGame);
                if (1) for(i=0;i<sizeof(seed)/sizeof(*seed);++i) printf(",%08X",seed[i]);
    
                printf("
    ");
            }
            
            /* ここで牌譜にszSeedSeqName,nGame,seedなどを出力 */
            // 在这里输出szSeedSeqName,nGame,种子 等等
    
            int nKyoku=0;
    
            for(;nKyoku<10;++nKyoku){ // 配牌が10回行われた場合 (随便举了一个进行十次的牌局)
    
    
                DWORD rnd[SHA512_DIGEST_SIZE/sizeof(DWORD)*9]; // 把输出流序列化为无符号4字节整数数组,该数组称为RND
                // rnd 数组,这个数组用于生成牌山。
                // SHA512_DIGEST_SIZE:512/8 , sizeof(DWORD):4 , 所以这个数组的长度是 512/8/4*9 = 144 ,确保了136张麻将牌的顺序、两个色子都放得下。
    
                {
                    DWORD src[sizeof(rnd)/sizeof(*rnd)*2]; 
                    // src 数组,这个数组用于用于 SHA 512 散列算法。
                    // src 数组的长度是 rnd 的两倍,288
    
                    for(i=0;i<sizeof(src)/sizeof(*src);++i) src[i] = mtLocal.genrand_int32();// 一共循环使用288次 mt199327ar 算法,不断生成随机数,写进二进制流 src 数组。
                    
    
                    // 对SRC循环进行 SHA512 哈希。关于 SHA512 参考:https://baike.baidu.com/item/sha-512/3357968
    
                    /* 1024bit単位で512bitへhash*/   // 以1024位散列为单位的512位
    
                    // 简单来说,src 数组是一个输入,经过 SHA512 哈希算法,会产生一个输出。
                    // 哈希算法有两条特性:1. 输入稍微有变动输出就有极大变动。 2. 很难只通过输出来构造输入。
                    // 哈希算法的特性让角田无法操控牌山。
    
                    for(i=0;i<sizeof(rnd)/SHA512_DIGEST_SIZE;++i){ // 循环次数是:144/(512/8) = 2.25 ,也就是分两次进行哈希,两次哈希分段使用完 src 的输入,两次哈希的输出拼接成一个总的输出。
                        SHA2::sha512_ctx ctx;
                        SHA2::sha512_init(&ctx);
                        SHA2::sha512_update(&ctx,(BYTE*)src+i*SHA512_DIGEST_SIZE*2,SHA512_DIGEST_SIZE*2); // 一次输入 in = 1024 bit。 src 是长度为288的 DWORD 数组,总二进制长度为 1152,也就是分两次输入
                        SHA2::sha512_final (&ctx,(BYTE*)rnd+i*SHA512_DIGEST_SIZE); // 一次哈希输出 512 bit ,两次输出拼接成一个总输出。
                        //输出是放在 rnd 数组里的。
                    }
                }
    
                //最后一步:利用RND数组生成牌山,牌山为长度136的数组,基本思路是对RND数组的元素进行求余
                BYTE yama[136]; // サンマは108  
                                // yama是牌山数组
    
                for(i=0;i<136;++i) yama[i]=i; // 一开始牌山是做牌做好的,按顺序。
                for(i=0;i<136-1;++i) swap(yama[i],yama[i + (rnd[i]%(136-i))]);  // 然后根据刚才的 RND 数组(里面都是随机数)来 shuffle,也就是打乱牌山。(不断交换两张牌)
    
                printf("nGame=%d nKyoku=%d yama=",nGame,nKyoku);
                for(i=0;i<136;++i) printf("%s",haiDisp[yama[i]/4]); // 输出牌山
                printf("
    ");
    
                int dice0=rnd[135]%6;
                int dice1=rnd[136]%6; // rnd 数组的135 136 用于投骰子
    
                // rnd[137]~rnd[143]は未使用
            }
        }
    }
    

    畅畅总结:

    一局天凤麻将游戏中,生成牌山的要素:

    1.mtRoot:是一个可能以系统时间、登录名等东西进行填充,用于随机数生成算法的种子。此外,mtRoot还用来决定东南西北的座位

    • mtRoot 的具体生成方式角田并没有公开,如果公开了可能可以伪造种子来知晓牌山。

    2.mtLocal:用 mtRoot 生成的随机数,作为新的种子。

    3.src 数组,这个数组用于用于 SHA 512 散列算法。内容是以 mtLocal 做种子,288 次随机数算法取的一长串二进制流。

    4.rnd 数组:长度为 144。会对 src 进行 SHA 512 散列算法,RND 是存放算法的结果的数组。之后要利用 RND 数组生成牌山。此外,每一局投的骰子也是 rnd 里面来的。

    • rnd 数组的生成结果由于散列算法的性质,保证了公平性。

    5.yama 数组,长度 136 。就是牌山本体。思路很简单,一开始里面的数据初始化是 123456 这样的等差数列,然后对RND数组的元素进行求余后不断 swap 就可以打乱(shuffle)。

    获取东一局的游戏牌山需要从第1步执行到第5步,第二局(无论是东一一本场还是东二)及之后只需要执行第 3 步到第 5 步。

    虽然说第三步里面有随机要素,但其实四个人进到一桌坐下来,本质上这一局的牌山都已经安排好了。

  • 相关阅读:
    WCF 第四章 绑定 在多个绑定上暴露一个服务契约
    WCF 第五章 行为 事务跨操作事务流
    WCF 第五章 导出并发布元数据(服务行为)
    WCF 第五章 行为 通过配置文件暴露一个服务行为
    WCF 第五章 不支持会话的绑定的默认并发和实例
    WCF 第五章 并发和实例(服务行为)
    WCF 第五章 行为 总结
    WCF 第四章 绑定 绑定元素
    WCF 第五章 行为 事务之选择一个事务协议OleTx 或者WSAT
    WCF 第四章 绑定 比较各种绑定的性能和可扩展性
  • 原文地址:https://www.cnblogs.com/ZCplayground/p/8157542.html
Copyright © 2011-2022 走看看