天鳳のログから和了データを抽出する その3
和了データの抽出
前回では、INIT タグから場風と親か子であるかという情報を抜き出しました。今回は、AGARI タグから和了データを抜き出します。
データ形式
麻雀の点数計算のアルゴリズムの正当性を確かめるために和了データを使いたいので、一行で和了データを表すことを考えます。
今回は次のようなデータ形式に変換することを考えます。データはすべて数字で空白区切りであるとします。
Bakaze Jikaze Parent Tumo Hu Han Point AgariHai Hai Huuro Yaku Dara UraDora
牌の数値化
萬子は 1~9 で表し、赤五萬は 0 とします。筒子は 11~19で表し、赤⑤は 10 とします。索子は 21~29 で表し、赤Ⅴは 20 とします。東南西北は 31~34 とし、白發中は 35~36 とします。
Bakaze
場風牌を表します。例えばこの値が 31 なら場風は東となります。
Jikaze
自風を表します。
Parent
この値が 1 なら親で 0 なら子を表します。
Tumo
この値が 1 なら自摸上がりで 0 ならロン上がりを表します。
Hu Han Point
それぞれ、符、翻、点数を表します。基本的に、5翻以上のときも符の値は存在しますが、国士無双のときは0となります。
AgariHai
和了した牌を表します。
Hai
手牌を表します。副露した牌はここには含まれませんが、上がり牌は含まれます。手牌の数を とし、 番目の牌の値を とすると、
のように表します。また、 となることはありません。
Huuro
副露している牌を表します。副露数を とすると、
となります。 番目の副露した面子を とすると、
type kind id red
と表します。
type は 0, 1, 2 の値を表し、0 なら順子、1 なら刻子、2 なら槓子とします。
kind は 0 なら暗槓以外(ポン、チー、大明槓、加槓)、1 なら暗槓とします。
id は面子の中で最小の牌の値とします。例えば、678で面子を晒しているとき、id の値は 6 となります。刻子や槓子はその牌の値となります。
red は副露した面子に赤牌が含まれるときは、1 であり、0 のときは含まれません。
Yaku
役に対応する値と翻数を表します。上がりにおける役の数を とすると、
となります。 番目の役を とすると、
id han
となります。id は役に対応する値で、han はその役の翻数となります。また、役満では han の値は 1 とします。役の対応は、
が詳しいです。
Dora UraDora
Dora は表ドラと槓ドラを表します。UraDora は裏ドラを表します。Dora の数を とすると、
と表します。UraDora についても同様ですが、リーチしていないときは、[tex; o = 0] となり、以降に数値はありません。
面子の解析
AGARI タグの 'm' という属性が副露した面子の情報を表しています。この情報は、16ビットで解釈されているので、上記の面子のデータ形式に変換する必要があります。
実際には、AGARI タグの副露の情報は10進数で表現されているので、その値を とします。
天鳳では牌にそれぞれの値がついていて、0~133までのIDが振り分けられています。赤牌は、16、52、88となります。
順子の場合
ビット | 情報 |
---|---|
1-2 | 誰から鳴いたか |
3 | 必ず1 |
4-5 | 牌1のID |
6-7 | 牌2のID |
8-9 | 牌3のID |
11-16 | 順子のパターン |
3ビット目が1のときが順子なので、Python でこれを確認するには、
n & (1<<2)
の値が0でなければ、3ビット目が1であると確認できます。次に順子の中で最小の牌の値を求めます。これは、11~16ビットから計算できます。順子のパターンは、123~789の7パターンあり、萬子、筒子、索子とどの牌を鳴いたかを合わせると、 通りあります。このパターンを bit とすると、次のように計算できます。
bit = 0 for i in range(6): if n & (1<<(10 + i)): bit += (1<<i)
この値を牌の値に変換するには、次のようになります。
bit //= 3 if bit <= 6: bit += 1 elif bit <= 13: bit += 4 else: bit += 7
3で割っているのは、どの牌を鳴いたかという情報が必要ないためです。
鳴いた牌に赤牌が含まれるかどうかは、345, 456, 567 の順子のパターンを調べれば良く、
if bit % 10 == 3 or bit % 10 == 4 or bit % 10 == 5: # 345 456 567 if (n & (1<<(7 - 2 * (bit % 10 - 3)))) == 0 and (n & (1<<(8 - 2 * (bit % 10 - 3)))) == 0: is_red = 1
と計算すれば良いです。
刻子
順子のときと同様に調べれば良いです。刻子や槓子の場合も合わせると、次のようになります。
def get_mentu(n): if n & (1<<2): # 順子 bit = 0 for i in range(6): if n & (1<<(10 + i)): bit += (1<<i) bit //= 3 if bit <= 6: bit += 1 elif bit <= 13: bit += 4 else: bit += 7 is_red = 0 if bit % 10 == 3 or bit % 10 == 4 or bit % 10 == 5: # 345 456 567 if (n & (1<<(7 - 2 * (bit % 10 - 3)))) == 0 and (n & (1<<(8 - 2 * (bit % 10 - 3)))) == 0: is_red = 1 return '%d %d %d %d' % (0, 0, bit, is_red) else: # 刻子 if n & (1<<(3)) or n & (1<<(4)): # 刻子 or 加槓 mentu_type = 1 # 刻子 if n & (1<<(4)): mentu_type = 2 # 槓子(加槓) bit = 0 for i in range(7): if n & (1<<(9 + i)): bit += (1<<i) bit //= 3 if bit <= 8: bit += 1 elif bit <= 17: bit += 2 elif bit <= 26: bit += 3 else: bit += 4 is_red = 0 if mentu_type == 2 and bit % 10 == 5 and bit <= 25: is_red = 1 if mentu_type == 1 and bit % 10 == 5 and bit <= 25 and (n & (1<<5) != 0 or n & (1<<6) != 0): is_red = 1 return '%d %d %d %d' % (mentu_type, 0, bit, is_red) else: # 暗槓 or 大明槓 is_red = 0 mentu_type = 2 mentu_kind = 0 if n & 1 == 0 and n & (1<<1) == 0: mentu_kind = 1 # 暗槓 bit = 0 for i in range(8): if n & (1<<(i + 8)): bit += 1<<i bit //= 4 if bit <= 8: bit += 1 elif bit <= 17: bit += 2 elif bit <= 26: bit += 3 else: bit += 4 if bit % 10 == 5 and bit <= 25: is_red = 1 return '%d %d %d %d' % (mentu_type, mentu_kind, bit, is_red)
INIT タグと AGARI タグからテストデータを生成
INIT タグとAGARIタグのデータから上記のデータ形式に変換するプログラムです。
import os import re import glob script_dir = script_dir = os.path.abspath(os.path.dirname(__file__)) # 牌のIDを種類に変換 萬子(1~9) 筒子(11~19 索子(21~29) 東南西北(31,32,33,34) 白發中(35,36,37) # 赤は 0 10 20 hai_to_kind = [0] * 136 def init_hai_to_kind(): for i in range(9): for j in range(4): hai_to_kind[i * 4 + j] = i + 1 hai_to_kind[i * 4 + j + 36] = 10 + i + 1 hai_to_kind[i * 4 + j + 72] = 20 + i + 1 for i in range(7): for j in range(4): hai_to_kind[i * 4 + j + 108] = 31 + i hai_to_kind[16] = 0 hai_to_kind[52] = 10 hai_to_kind[88] = 20 init_hai_to_kind() def get_mentu(n): if n & (1<<2): # 順子 bit = 0 for i in range(6): if n & (1<<(10 + i)): bit += (1<<i) bit //= 3 if bit <= 6: bit += 1 elif bit <= 13: bit += 4 else: bit += 7 is_red = 0 if bit % 10 == 3 or bit % 10 == 4 or bit % 10 == 5: # 345 456 567 if (n & (1<<(7 - 2 * (bit % 10 - 3)))) == 0 and (n & (1<<(8 - 2 * (bit % 10 - 3)))) == 0: is_red = 1 return '%d %d %d %d' % (0, 0, bit, is_red) else: # 刻子 if n & (1<<(3)) or n & (1<<(4)): # 刻子 or 加槓 mentu_type = 1 # 刻子 if n & (1<<(4)): mentu_type = 2 # 槓子(加槓) bit = 0 for i in range(7): if n & (1<<(9 + i)): bit += (1<<i) bit //= 3 if bit <= 8: bit += 1 elif bit <= 17: bit += 2 elif bit <= 26: bit += 3 else: bit += 4 is_red = 0 if mentu_type == 2 and bit % 10 == 5 and bit <= 25: is_red = 1 if mentu_type == 1 and bit % 10 == 5 and bit <= 25 and (n & (1<<5) != 0 or n & (1<<6) != 0): is_red = 1 return '%d %d %d %d' % (mentu_type, 0, bit, is_red) else: # 暗槓 or 大明槓 is_red = 0 mentu_type = 2 mentu_kind = 0 if n & 1 == 0 and n & (1<<1) == 0: mentu_kind = 1 # 暗槓 bit = 0 for i in range(8): if n & (1<<(i + 8)): bit += 1<<i bit //= 4 if bit <= 8: bit += 1 elif bit <= 17: bit += 2 elif bit <= 26: bit += 3 else: bit += 4 if bit % 10 == 5 and bit <= 25: is_red = 1 return '%d %d %d %d' % (mentu_type, mentu_kind, bit, is_red) def get_testdata(path): test_data = [] with open(path, 'r', encoding='utf_8') as f: line1 = f.readline() cnt = 0 while line1: cnt += 1 line2 = f.readline() line1 = line1.strip() line1 = re.sub('["<>/]', '', line1) data1 = line1.split(' ') dic1 = dict() for d in data1: v = d.split('=') if len(v) == 1: continue dic1[v[0]] = list(map(int, v[1].split(','))) line2 = line2.strip() line2 = re.sub('["<>/]', '', line2) data = line2.split(' ') dic = dict() for d in data: v = d.split('=') if len(v) == 1: continue if v[0] == 'owari': continue dic[v[0]] = list(map(int, v[1].split(','))) bakaze = dic1['seed'][0] / 4 + 31 jikaze = (dic['who'][0] - dic1['oya'][0] + 4) % 4 + 31 tumo = 0 if dic['fromWho'] == dic['who']: tumo = 1 parent = 0 if dic1['oya'] == dic['who']: parent = 1 han = 0 hu = 0 yaku_list = [] if 'yaku' in dic: hu = dic['ten'][0] han = sum(dic['yaku'][1::2]) yaku_list = dic['yaku'] if 'yakuman' in dic: # 役満 for yakuman in dic['yakuman']: yaku_list.append(yakuman) yaku_list.append(1) tmp = '%d %d %d %d %d %d %d %d' % (bakaze, jikaze, parent, tumo, hu, han, dic['ten'][1], hai_to_kind[dic['machi'][0]]) tmp += ' ' + str(len(dic['hai'])) for hai in dic['hai']: tmp += ' ' + str(hai_to_kind[hai]) if 'm' in dic: tmp += ' ' + str(len(dic['m'])) for m in dic['m']: tmp += ' ' + get_mentu(m) else: tmp += ' 0' tmp += ' ' + str(len(yaku_list) // 2) for yaku in yaku_list: tmp += ' ' + str(yaku) tmp += ' ' + str(len(dic['doraHai'])) for d in dic['doraHai']: tmp += ' ' + str(hai_to_kind[d]) if 'doraHaiUra' in dic: tmp += ' ' + str(len(dic['doraHaiUra'])) for d in dic['doraHaiUra']: tmp += ' ' + str(hai_to_kind[d]) else: tmp += ' 0' test_data.append(tmp) line1 = f.readline() return test_data