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 步。

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

  • 相关阅读:
    spring:事务的5大隔离级别,7大传播行为
    spring事务管理(xml配置)与spring自带连接数据库JdbcTemplate
    spring顾问包装通知
    Spring常见的两种增强方式
    Spring-增强方式注解实现方式
    Spring----aop(面向切面编程)
    Spring_IOC
    服务器项目代码在本地打断点调试
    FreeMarker标签出错问题
    Eclipse启动项目没加载到webapps目录下
  • 原文地址:https://www.cnblogs.com/ZCplayground/p/8157542.html
Copyright © 2011-2022 走看看