天凤麻雀吧 关注:11,933贴子:573,892

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

只看楼主收藏回复

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


好玩版棋牌,棋牌免费下载,刺激好玩!

玩正版棋牌,送金币.棋牌,好玩就是硬道理!24小时尊享服务等待你!<好玩>的棋牌,<好玩>的棋牌,<天天送惊喜>新版棋牌,刺激好玩!

广告
占楼预留,此楼请勿回复。
作者保留权利。


回复
举报|2楼2016-04-02 18:11
    甲、什么是天凤牌谱


    这里所述的天凤牌谱,指的是天凤(Windows版)对局后生成的mjlog数据文件。然而楼主在使用linux。这也简单,装个wine还是能用的……扯远了。你也可以从网上下载到mjlog文件,比如官方的凤凰牌谱zip包。
    我们这里选择了ASAPIN大神的升天凤位战文件作为示例。下载ASAPIN大神的牌谱ZIP包后,可以在其中找到这个文件。
    这个文件的文件名是

    2011020417gm-00a9-0000-b67fcaa3\&tw\=1.mjlog
    显然的,前面部分是文件id,你也可以用浏览器查看这个牌谱,很容易拼出下列URL:
    tenhou.net/0/?log=2011020417gm-00a9-0000-b67fcaa3&tw=1


    收起回复
    举报|3楼2016-04-02 18:15
      来看看这个文件名吧。
      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
        既然知道了时间,那就很容易找到这场比赛。网上有用户日志列表存在,比如那个ranking.pl的,也可以用我刚写的那个
        suehara.tk/html/#name=ASAPIN
        (注意,由于记录太长,不建议用IE打开。后续会考虑做前端分页。)

        很容易就能定位到下面这条记录(如果时间不对,注意右上角的地球选项)

        2位 27分 2011-02-04 17:15 四鳳南喰赤 十段3965pt+45pt うきでん(+48.0)ASAPIN(+14.0)超ヒモリロ(-25.0)-ron-(-37.0)

        首先看一下牌谱URL,确实log参数是一致的。那么打开看看,主视角是吃了4位,下家(南起)吃了2位,这些都和前面的那些一致。那么主视角叫什么呢?这位给凤凰送pt的叫做-ron-。
        然后回到手里的mjlog文件。打开一看是乱码?不要紧,实际上是一个gzip压缩包,只需要使用类似下列命令即可解压缩:
        [suehara@linux tenhou]$ cat 2011020417gm-00a9-0000-b67fcaa3\&tw\=1.mjlog | gzip -d > 2011020417gm-00a9-0000-b67fcaa3.xml
        或者,如果使用Windows的话,直接用7-zip之类工具解压缩即可,生成的文件命名为xml文件,就可以用浏览器等打开。


        回复
        举报|6楼2016-04-02 18:39
          前排围观技术贴.


          回复
          举报|来自Android客户端7楼2016-04-02 18:43
            乙、麻将牌的表示



            虽然习惯上表示一枚麻将牌使用的是2m表示二万、5p表示五饼这个样子,但是毫不奇怪地,在天凤日志文件中,表示一枚麻将牌用的是数字。那么,怎样用数字表示麻将牌呢?

            通常,我们见到的麻将牌买来的时候是这个样子的,当然也可以叠放:


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


            回复
            举报|8楼2016-04-02 19:09
              天凤对麻将牌的表示也是这个原理。不过顺序自然是日本顺序。
              首先是1-9m,然后是1-9p,再然后是1-9s,最后是东南西北白发中。
              这个顺序,就是自动理牌的时候的摆放顺序。
              而且,数字是从0开始的,到135结束;没有花牌,共计136枚。
              遇到赤宝,则该种牌最前面的那个替换为赤宝,如5p的最先一枚被替换为0p。
              从0开始是符合程序员思维的。而且计算也比较方便。
              比如给出一个数字74,是什么牌?
              可以很简单地算出,74 / 36 = 2 (/表示整数除法,%表示取余数,下同),表示这是一枚索子;然后74 % 36 = 2,2 / 4 = 0,这是一枚小鸡,并且是小鸡中的第三枚。
              那么问0p用多少表示?
              可以知道,p从36开始,1-4p占用了16个数字,0p是第一个5p,即36+16=52。


              回复
              举报|9楼2016-04-02 19:17
                虽然不懂但看上去好高深的样子


                回复
                举报|来自Android客户端10楼2016-04-02 19:23
                  虽然懂但看上去好高深的样子


                  回复
                  举报|11楼2016-04-02 19:27
                    丙、整体对局信息
                    打开上述XML文件,就可以看到一个巨大的标签
                    <mjloggm ver="2.3">包围着整个文件。很明显的,它指的是天凤日志文件,版本号2.3。具体版本号有什么区别呢?这大概只有去问角田了。
                    里边罗列着平行的众多标签。说实话,这个结构糟透了,至少也要把一局圈在一起吧……可能是因为什么历史原因,只好这样了。
                    第一个标签是SHUFFLE。其中有一个很长的属性,是生成牌山用的seed。具体的说明可以在天凤博客上找到,我没有验证。
                    <GO type="169" lobby="0"/>

                    然后是这一个标签。type字段169,就是上面对战规则00a9的十进制形式,表示凤南食赤。lobby表示个室ID,lobby=0那就是公式战(大厅)。
                    <UN n0="%2D%72%6F%6E%2D" n1="%41%53%41%50%49%4E" n2="%E3%81%86%E3%81%8D%E3%81%A7%E3%82%93" n3="%E8%B6%85%E3%83%92%E3%83%A2%E3%83%AA%E3%83%AD" dan="16,19,16,18"rate="2135.55,2260.11,2018.07,2198.52" sx="M,M,M,M"/>
                    这是用户名标签。n0到n3是4个对局者的名字,当然,经过了URL转义。可以和上面给出的对局者信息对比,他们是对应的。dan是对局者级别,从0=新人开始,到1=9级,9=1级,10=初段,19=10段,20=天凤位。rate就是我们常说的R值了,可见角田至少保存了小数点后2位的R值的,当然展示的时候只有整数了。最后是四家在本局对局使用的性别,M表示可爱的男孩纸,F表示帅气的女孩纸……这个值是登录的时候指定的,在那边果然性别不是问题啊。

                    <TAIKYOKU oya="0"/>
                    对局,东起的是主视角,就是上面的n0,叫做-ron-的同学。


                    回复
                    举报|12楼2016-04-02 21:11
                      丁、牌局
                      牌局,这里指的是有人和到,或者流局,可以用类似“东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
                                役表如下。这个表是手敲的,一些用了中文翻译,比如两杯口,纯属作者习惯和敲着顺手。当然,也可能有误漏,请斧正。
                                ID NAME
                                0 门清自摸和
                                1 立直
                                2 一发
                                3 抢杠
                                4 岭上开花
                                5 海底捞月
                                6 河底捞鱼
                                7 平和
                                8 断幺九
                                9 一杯口
                                10 自风东
                                11 自风南
                                12 自风西
                                13 自风北
                                14 场风东
                                15 场风南
                                16 场风西
                                17 场风北
                                18 役牌白
                                19 役牌发
                                20 役牌中
                                21 双立直
                                22 七对子
                                23 混全带幺九
                                24 一气通贯
                                25 三色同顺
                                26 三色同刻
                                27 三杠子
                                28 对对和
                                29 三暗刻
                                30 小三元
                                31 混老头
                                32 两杯口
                                33 纯全带幺九
                                34 混一色
                                35 清一色
                                36 人和
                                37 天和
                                38 地和
                                39 大三元
                                40 四暗刻
                                41 四暗刻单骑
                                42 字一色
                                43 绿一色
                                44 清老头
                                45 九莲宝灯
                                46 纯正九莲宝灯
                                47 国士无双
                                48 国士无双十三面
                                49 大四喜
                                50 小四喜
                                51 四杠子
                                52 宝牌
                                53 里宝
                                54 赤宝


                                收起回复
                                举报|18楼2016-04-02 21:56
                                  戊、鸣牌
                                  为什么把鸣牌单独来写呢?因为设计太变态了。
                                  如果说前面那些都能用黑盒的手段猜出来,这个鸣牌……就有点呵呵了。
                                  回到刚才的东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
                                              @吧主
                                              本楼全部原创,申请精华


                                              收起回复
                                              举报|25楼2016-04-02 22:50
                                                杠的鸣牌内容被度娘吃掉了,已经申请恢复。稍后应该就能出来。


                                                回复
                                                举报|26楼2016-04-02 23:02
                                                  技术贴,学习


                                                  回复
                                                  举报|来自Android客户端27楼2016-04-02 23:12
                                                    mark·····


                                                    回复
                                                    举报|28楼2016-04-02 23:53
                                                      真技术贴


                                                      回复
                                                      举报|来自Android客户端29楼2016-04-03 00:12
                                                        马一下 回头细看


                                                        回复
                                                        举报|来自手机贴吧30楼2016-04-03 02:18
                                                          可啪


                                                          回复
                                                          举报|来自Android客户端31楼2016-04-03 02:36