天凤麻雀吧 关注:16,891贴子:604,061

【开坑】天凤牌谱文件格式试解析

取消只看楼主收藏回复

一楼喂熊。
此贴尝试解析天凤牌谱文件。
并不涉及打牌机巧、麻将理论。
而且,并没有完全完成解析,只有一部分功能。


1楼2016-04-02 18:09回复
    占楼预留,此楼请勿回复。
    作者保留权利。


    2楼2016-04-02 18:11
    回复
      来看看这个文件名吧。
      2011020417gm-00a9-0000-b67fcaa3\&tw\=1.mjlog
      后面的mjlog是扩展名
      tw=1表示主视角(ASAPIN大神)是南起,这里为了方便起见我们使用tw=0的主视角,即主视角采用东起那位。东起那位是谁呢?别着急。
      然后是前面的一个看起来像UUID的东西,但是他明显不是UUID啊。
      2011020417gm-00a9-0000-b67fcaa3
      2011020417:表示发生在2011年02月04日17时(UTC+9)
      gm:我只见过gm
      00a9:对战规则,凤南食赤。这个规则映射表可以在天凤首页-乱数中找到,在这里罗列如下:
      四般东////// 07 四般南////// 0f
      四般东食//// 03 四般南食//// 0b
      四般东食赤// 01 四般南食赤// 09
      四般东食赤速 41 四般南食赤速 49
      三般东食赤// 11 三般南食赤// 19
      三般东食赤速 51 三般南食赤速 59
      四上东////// 87 四上南////// 8f
      四上东食//// 83 四上南食//// 8b
      四上东食赤// 81 四上南食赤// 89
      四上东食赤速 c1 四上南食赤速 c9
      三上东食赤// 91 三上南食赤// 99
      三上东食赤速 d1 三上南食赤速 d9
      四特东////// 27 四特南////// 2f
      四特东食//// 23 四特南食//// 2b
      四特东食赤// 21 四特南食赤// 29
      四特东食赤速 61 四特南食赤速 69
      三特东食赤// 31 三特南食赤// 39
      三特东食赤速 71 三特南食赤速 79
      四凤东////// a7 四凤南////// af
      四凤东食//// a3 四凤南食//// ab
      四凤东食赤// a1 四凤南食赤// a9
      四凤东食赤速 e1 四凤南食赤速 e9
      三凤东食赤// b1 三凤南食赤// b9
      三凤东食赤速 f1 三凤南食赤速 f9
      另外有雀庄、技能战等配置,在此不再列出。
      后面的数字就是对战的id了,在这里我们可以认为是随机的。


      4楼2016-04-02 18:26
      回复
        乙、麻将牌的表示
        虽然习惯上表示一枚麻将牌使用的是2m表示二万、5p表示五饼这个样子,但是毫不奇怪地,在天凤日志文件中,表示一枚麻将牌用的是数字。那么,怎样用数字表示麻将牌呢?
        通常,我们见到的麻将牌买来的时候是这个样子的,当然也可以叠放:

        (图片来自网络,公有领域)
        想想看,这样排列地麻将牌是不是就可以用一个数字表示一枚牌了。
        比如从上面的1p开始,用1-4表示4枚1p;再用5-8表示4枚2p……用37-40表示4枚小鸡,就可以用144个数字表示所有的牌。


        8楼2016-04-02 19:09
        收起回复
          丁、牌局
          牌局,这里指的是有人和到,或者流局,可以用类似“东3-2本场”这样指明的一局牌。
          从一个INIT标签开始,一局就开始了。
          <INIT seed="0,0,0,2,2,112" ten="250,250,250,250" oya="0" hai0="48,16,19,34,2,76,13,7,128,1,39,121,87" hai1="17,62,79,52,56,57,82,98,32,103,24,70,54"hai2="55,30,12,26,31,90,3,4,80,125,66,102,78" hai3="120,130,42,67,114,93,5,61,20,108,41,100,84"/>
          seed是初时化用的一些信息。
          第一个0表示东1局。0-3表示东1-东4,然后4-7表示南场,如果必要,还可以用8-11表示西场。
          第二个0表示0本场,场上没有本场棒。
          第三个0表示场上立直棒的数量,现在当然是没有了。
          ten表示四家点数,从主视角开始依次记录,单位是100点。很显然的,现在大家都是25000点。
          hai0至hai3就是各家配牌了。其中数字的含义请参考(乙)章节中对牌的阐述。


          13楼2016-04-02 21:18
          收起回复
            <T107/><D39/><U47/><E70/><V14/><F125/><W131/><G5/><T96/><D121/><U51/>
            ……
            这些是什么呢?
            当然是摸牌、舍牌了。
            前面的字母,TUVW表示摸牌,DEFG表示舍牌。具体的字母似乎和牌旋转的方向有关,但是我们不需要渲染动画的话知道他们是摸和切就对了……
            那么T107就是当前(亲,主视角)摸了107牌。107是什么呢?参考(乙)中的内容计算一下:
            107 / 36 = 2 这是一枚索子;107 % 36 = 35;35 / 4 = 8;这是一枚9s。打开牌谱看一下,主视角第一枚确实是9s。
            然后D39呢?自然就是舍弃了39的牌。容易得知,39是一枚1p,也和实际相符。
            如此循环下去,牌局就进行下去了。


            14楼2016-04-02 21:23
            回复
              随着牌局的进行,我们可以看到下面的片段:
              ……
              <T15/><D15/><U43/><REACH who="1" step="1"/><E115/>
              <REACH who="1" ten="250,240,250,250" step="2"/><V94/><F68/>
              ……
              注意到了很明显的REACH,立直标记。
              按照日麻规则,立直分2步,首先宣布立直,然后丢出立直宣言牌,没有人和的话放下立直棒。这里也是这样的。
              首先是REACH step=1,表示立直的第一步,喊“立直”!谁呢?who=1,表示主视角下家。
              然后呢?是E115标签,表示他舍弃了115牌。115是什么呢?是一枚南。然后完成了立直的步骤2,放了棒子,点数发生了变化,于是重新用ten声明了场上点数。


              15楼2016-04-02 21:36
              回复
                稍后的鸣牌暂时不表。最终,这一局流局了,最后留下流局的标签:
                <RYUUKYOKU ba="0,1" sc="250,-10,240,30,250,-10,250,-10" hai1="43,47,49,51,52,54,56,57,62,79,82,101,103"/>
                ba表示目前场上供托的情况,0个本场棒和1个立直棒。sc表示各家点数和变化,单位一样是100点,从主视角开始。hai开头表示需要展示出来的手牌。
                这是一个正常流局。那么还有不正常流局吗?当然有了。比如各种途流。还有流满。如果是非正常流局,会用type属性写出流局类型。
                yao9 九种九牌
                reach4 四家立直
                ron3 三家和了
                kan4 四杠散了
                kaze4 四风连打
                nm 流局满贯


                16楼2016-04-02 21:42
                回复
                  <INIT seed="1,1,1,3,1,69" ten="240,270,240,240" oya="1" hai0="85,87,20,68,26,89,117,15,134,82,72,75,48" hai1="122,36,38,65,22,3,56,14,32,98,111,47,96"hai2="62,49,50,25,52,43,42,9,17,24,81,132,6" hai3="46,83,95,100,73,115,127,77,86,110,116,76,13"/>
                  第二局开始了。东2局,1本场,1个立直棒在那里。色子不管,DORA表示牌69,是一枚9p。亲是主视角下家。配牌不表。
                  中间略过,最终有人和牌了:
                  <AGARI ba="1,3" hai="1,6,9,24,25,37,42,44,45,49,52,58,60,64" machi="44" ten="30,8000,1" yaku="1,1,7,1,52,1,54,1,53,1" doraHai="69" doraHaiUra="59" who="2"fromWho="3" sc="240,0,260,0,230,113,240,-83"/>
                  AGARI!ba定义和流局一样,一本场、三棒子;和牌形态用hai属性表示;和到的牌用machi属性,值44,是一枚3p。
                  下一个属性ten表示的是和到的点数,我真的无法理解为什么这里的单位不是100点而是1点了,真的是一个bad design。或许是为了显示出来方便?
                  ten属性3个字段,30表示30符,8000表示8000点,1表示荣和(当然0的话表示自摸)。
                  多少番、什么役在yaku属性表示,每2个数字1组,第一个表示役,第二个表示他的番数。如1,1表示役立直(序号=1)、1番。
                  doraHai很显然的dora指示牌,是69,是9p;因为立直了有doraHaiUra,里宝指示牌59,表示一枚6p。
                  who是和到的人,2表示主视角对家;fromWho是放铳的人,3表示主视角上家。
                  sc则是点数变化,这里的单位又回到了100点。


                  17楼2016-04-02 21:54
                  回复(2)
                    戊、鸣牌
                    为什么把鸣牌单独来写呢?因为设计太变态了。
                    如果说前面那些都能用黑盒的手段猜出来,这个鸣牌……就有点呵呵了。
                    回到刚才的东1局。
                    <F68/><N who="3" m="42031"/><G23/>
                    这样N的标记就是鸣牌了。他有2个属性,who表示鸣牌的人,3表示的是主视角下家;而这个m让作者猜了一宿。
                    看多了牌局不难发现,m值没有超过6w的。如果涉及的是万子,比较小;字牌,比较大。吃牌,一定是奇数……
                    回想到(甲)中有关对局规则的内容。其实对局规则那个字段是一个二进制字段,不同的bit表示不同的含义。再考虑鸣牌的m值,是否有关呢?


                    19楼2016-04-02 22:04
                    回复
                      那么转成二进制吧。
                      42031 = 1010 0100 0010 1111 (b)
                      首先解出来的是最后3个bit,字段名称KUI。
                      这里用标记bit0_1表示bit0到bit1的值。对于bit0,表示的是2的0次方,也就是1的那一位,即bitn = (x >> n) & 1;对于bitm_n(m>n),计算机表达式写作 (x >> n) & ((1 << m) - 1)
                      标记(b)表示是二进制。没有标记的是十进制。
                      KUI = bit0_1 = 42031 & 3 = 3,表示这枚牌的来路。这里3表示的是上家,2是对家,1是下家,0是自己。
                      注意这里又是设计不统一,这里的3表示是鸣牌者的上家;因为属性who=3,鸣牌者是主视角的上家,所以这里被鸣的牌是主视角的对家打出的。
                      然后开始搜索鸣牌类型。如果bit2为真,则表示是“吃”。


                      20楼2016-04-02 22:12
                      收起回复
                        明白了这次鸣牌是“吃”,那么继续解析。
                        鸣牌信息在bit10_15,也就是10 1001 (b) = 41
                        吃出来的面子有3枚牌,用41除以3:
                        41 / 3 = 13, 41 % 3 = 2
                        这里的余数1表示被鸣的牌是哪一枚,1表示是中间大小的那一枚(0表示数字最小,2表示最大)
                        13呢?花色有3种(吃个字牌我看看?),可能的组合每种花色有7种(123,234,……,789)
                        那么
                        13 / 7 = 1,花色=1,表示是饼子(0万1饼2索)
                        13 % 7 = 6,最小的牌是7p(0=1p,1=2p,……,6=7p)
                        那么被鸣的牌是(鸣牌者上家的)9p,组成的面子是789p
                        如果遇到赤宝怎么处理呢?数数看,bit4_9一共6个bit是空白的,正好两两一组,可以决定参与鸣牌的牌是同样牌中的哪一枚。bit4_5(=2)表示最小的(7p)是第三枚,7p用数字表示是60至64,第三枚是62;bit6_7表示次小的(8p)是第一枚,用数字表示是64;bit8_9表示最大的(9p)是第一枚,用数字表示是68。
                        校验一下呢?主视角对家刚刚打出的牌正是68,那么就对上了。


                        21楼2016-04-02 22:23
                        收起回复
                          再来看看碰牌的情况。
                          东四局二本场主视角在开场不久就鸣了东,对应代码片段
                          <F109/><N who="0" m="42058"/><D6/>
                          好了。109是东无疑,那么N属性m=42058怎样解释呢?
                          首先还是转成二进制
                          42058 = 1010 0100 0100 1010 (b)
                          首先是鸣牌来源:42058 & 3 = 2,表明来自对家。
                          判断鸣牌类型,bit2为假,那么就不是吃。是不是碰呢?看bit3,如果bit3为真那就是碰。
                          还记得吃的情况bit3的含义吗?那里表示最小的牌是同种类的哪一枚。这样的bit复用固然节约了数据存储量,不过在这个天凤文件到处展开写xml的世界……真的有必要吗。
                          因为碰的牌可能是字牌了,所以这里用bit9_15一共7个bit表示牌的信息。
                          bit9_15 = 101 0010 (b) = 82
                          碰出的面子也是3枚一组,那么仿照吃的处理方法:
                          82 % 3 = 1,82 / 3 = 27
                          碰的牌是27吗?不是,碰的牌三枚都是一样的,所以这里只需要指明是那个牌就行了,二不需要具体哪一枚。很容易猜到他应该是27 * 4 = 108,也就是东。
                          碰的牌是3枚,同种牌有4枚,那么记录下不用的那一枚即可。于是这里用bit5_6记录了不用的牌,是10(b)=2。也就是说,鸣的3枚是[108, 108 + 1, 108 + 3]。没有出线的牌是108+2。
                          还有上面bit9_15除以3的余数没有用。参考前面对吃的处理,他表示被鸣的牌是哪一枚,这里是1,也就是
                          [108, 108 + 1, 108 + 3][1] = 109。
                          对比之前的舍牌,正是109。


                          22楼2016-04-02 22:36
                          回复
                            杠牌在高手的对局中很少见。我们用了其他对局来做例子。对局不贴,只给出m和解释。
                            暗杠的例子。某m=14848。
                            转成二进制:m = 14848 = 11 1010 0000 0000(b)
                            判断来源:m & 3 = 0 是自己!什么时候能自己鸣自己呢?暗杠。
                            检查鸣牌类型,bit3_5均为0,那么是杠。而且鸣自己的牌,暗杠无疑。
                            鸣的牌在bit8_15,bit8_15 = 11 1010 (b) = 58 (=6p)
                            那么就是在这里暗杠了6p。
                            明杠的例子。某m=17921。
                            转成二进制:m = 17921 = 100 0110 0000 0001 (b)
                            来源:m & 3 = 1 (下家)
                            检查鸣牌类型,bit3_5均为0,那么是杠。鸣其他人的牌,大明杠。
                            鸣的牌在bit8_15,bit8_15 = 100 0110 (b) = 70 (=9p)
                            在这里大明杠了下家的9p,而且是70那一枚9p。
                            加杠的例子。某m=47666
                            转成二进制:m = 47666 = 1011 1010 0011 0010 (b)
                            来源:m & 3 = 2 (对家)
                            鸣牌类型检查,在bit4找到了1,那么是加杠
                            接下来的处理和碰类似,bit5_6原本表示没用的牌,这里表示加杠的牌:
                            bit5_6 = 1 ,表示加杠的是第二枚
                            牌的信息处理和碰类似,bit9_15 = 101 1101 (b) = 93
                            被鸣的牌(碰的那一枚,不是加杠的)93 % 3 = 0
                            对应的牌93 / 3 * 4 = 124 (=白)
                            碰的样子和被碰的牌 [124 + 0, 124 + 2, 124 + 3][0] = 124
                            加杠的牌就是剩下那一枚,124 + bit5_6 = 125


                            23楼2016-04-02 22:47
                            回复
                              己、其他
                              中间必然还有其他未尽之处。比如拔北的鸣牌(提示,bit5),或者新翻出的DORA(用DORA标签),以及掉线(BYE标签),感觉都相当容易理解,不在此列出。目前的内容对于解析牌谱,甚至是做完整的牌谱展示都够用了。
                              当然也有其他地方,还请有识之士补充。谢谢观赏。
                              (全文完)


                              24楼2016-04-02 22:49
                              回复