| 牌種 | 場所ID | 枚数 |
|---|---|---|
| p2 | 1 | 3 |
| s3 | 1 | 1 |
| s4 | 1 | 1 |
| s5 | 1 | 1 |
| z1 | 1 | 3 |
| z2 | 1 | 3 |
| z3 | 1 | 1 |
麻雀のデータをtidyに扱えるRパッケージの紹介
Japanese Riichi Mahjong: 日本で遊ばれているルールの麻雀(不完全情報・ゼロサムゲーム)
日本における麻雀は、そこそこ複雑で、競技性の高いゲーム
→わりと大量の対局ログを扱いたい需要があるらしい
状況(特徴量)を入力として与えたとき、有利な行動(ラベル)を返すモデルを学習してみたい
[mpsz]の4種類[mps]は1-9の9ランク、zは1-7の7ランクあって、それぞれが4枚ずつある(3*9+7)*4=136枚のタイルを用いる[mps]の5については、4枚中1枚ずつを「赤牌」に置き換えることも(データとしてはランクを0として扱う)"_"で表すことにして、表現としてはz0に割り当てるm0, p0, s0は赤牌
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| m | 🀋 | 🀇 | 🀈 | 🀉 | 🀊 | 🀋 | 🀌 | 🀍 | 🀎 | 🀏 |
| p | 🀝 | 🀙 | 🀚 | 🀛 | 🀜 | 🀝 | 🀞 | 🀟 | 🀠 | 🀡 |
| s | 🀔 | 🀐 | 🀑 | 🀒 | 🀓 | 🀔 | 🀕 | 🀖 | 🀗 | 🀘 |
| z | 🀫 | 🀀 | 🀁 | 🀂 | 🀃 | 🀆 | 🀅 | 🀄 |
牌種をチャンネルとして、牌を9(数値または字牌の種類)×4(牌数)の2次元の面で表した、畳み込みニューラルネットワークで構成する1
次のようなarrayを用意するということ
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|
| n1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
| n2 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| n3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| n4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
たとえば、次のような特徴量(テーブルデータ)をつくる1
Tidy datasets are easy to manipulate, model and visualize, and have a specific structure: each variable is a column, each observation is a row, and each type of observational unit is a table.
Hadley Wickham (2014). Tidy data.
The Journal of Statistical Software, 59.
Rではこれを扱うための枠組みが非常に強力なため、データを縦長のデータフレームとして持てるようにすると、「Rっぽい書き方」に着地させやすい
麻雀牌をTidy Dataにする場合:
| 牌種 | 場所ID | 枚数 |
|---|---|---|
| p2 | 1 | 3 |
| s3 | 1 | 1 |
| s4 | 1 | 1 |
| s5 | 1 | 1 |
| z1 | 1 | 3 |
| z2 | 1 | 3 |
| z3 | 1 | 1 |
それぞれの牌が何枚かずつで一組になっていることを表せる形式であれば、データとしての意味は保ったまま、いくつかの表現ができる
"p222s345z1112223"c("p2", "p2", "p2", "s3", "s4", "s5", "z1", "z1", "z1", "z2", "z2", "z2", "z3")"p222s345z1112223"c("p2", "p2", "p2", "s3", "s4", "s5", "z1", "z1", "z1", "z2", "z2", "z2", "z3")→これらの表現のあいだで変換できたら便利そう
paistr()手牌など、牌のまとまりを表現するための独自S3クラス
library(shikakusphere)
(hands <-
paistr(
c(
"p222345z234567",
"p11222345z12345",
"m055z7z7,m78-9,z5555,z666=",
"m055z77,m78-9,z5555,z666=,",
"m123p055s789z1117*"
)
)
)
#> <skksph_paistr[5]>
#> [1] <12>'p222345z234567' <13>'p11222345z12345'
#> [3] <15>'m055z7z7,m78-9,z5555,z666=' <15>'m055z77,m78-9,z5555,z666=,'
#> [5] <13>'m123p055s789z1117*'tidy(<skksph_paistr>)1→3の変換(ただし、副露か純手牌かの情報は保持しない)
lineup()3→2の変換
(hands <- lineup(hands))
#> [[1]]
#> [1] p2 p2 p2 p3 p4 p5 z2 z3 z4 z5 z6 z7
#> 38 Levels: _ m0 m1 m2 m3 m4 m5 m6 m7 m8 m9 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 ... z7
#>
#> [[2]]
#> [1] p1 p1 p2 p2 p2 p3 p4 p5 z1 z2 z3 z4 z5
#> 38 Levels: _ m0 m1 m2 m3 m4 m5 m6 m7 m8 m9 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 ... z7
#>
#> [[3]]
#> [1] m0 m5 m5 m7 m8 m9 z5 z5 z5 z5 z6 z6 z6 z7 z7
#> 38 Levels: _ m0 m1 m2 m3 m4 m5 m6 m7 m8 m9 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 ... z7
#>
#> [[4]]
#> [1] m0 m5 m5 m7 m8 m9 z5 z5 z5 z5 z6 z6 z6 z7 z7
#> 38 Levels: _ m0 m1 m2 m3 m4 m5 m6 m7 m8 m9 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 ... z7
#>
#> [[5]]
#> [1] m1 m2 m3 p0 p5 p5 s7 s8 s9 z1 z1 z1 z7
#> 38 Levels: _ m0 m1 m2 m3 m4 m5 m6 m7 m8 m9 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 ... z7lipai()2→1の変換(tidy()を経由しているため、元のかたちに戻るわけではない)
dplyr::arannge()で並べ替えできるようにしておくいくつかの形式があるが、天鳳のJSON形式がおそらくもっとも一般的
Mjai Event JSONというのもある
savvyを使って書いたRラッパーでtibbleにしたデータの例
json <- list.files(
system.file("testdata/", package = "convlog"),
pattern = "*.json$",
full.names = TRUE
)
(dat <- convlog::read_tenhou6(json))
#> $game_info
#> # A tibble: 21 × 4
#> game_id names qijia aka
#> <int> <named list> <int> <lgl>
#> 1 1 <chr [4]> 4 TRUE
#> 2 2 <chr [4]> 0 TRUE
#> 3 3 <chr [4]> 0 TRUE
#> 4 4 <chr [4]> 0 TRUE
#> 5 5 <chr [4]> 0 TRUE
#> 6 6 <chr [4]> 0 TRUE
#> 7 7 <chr [4]> 0 TRUE
#> 8 8 <chr [4]> 0 TRUE
#> 9 9 <chr [4]> 0 TRUE
#> 10 10 <chr [4]> 0 TRUE
#> # ℹ 11 more rows
#>
#> $round_info
#> # A tibble: 33 × 10
#> game_id round_id bakaze dora_marker kyoku honba kyotaku oya scores tehais
#> <int> <int> <chr> <chr> <int> <int> <int> <int> <list> <list>
#> 1 1 1 E 3s 1 0 0 0 <int> <chr[…]>
#> 2 2 1 E 4m 3 3 0 2 <int> <chr[…]>
#> 3 3 1 E 6m 1 0 0 0 <int> <chr[…]>
#> 4 4 1 E E 4 0 0 3 <int> <chr[…]>
#> 5 5 1 E 8p 2 2 0 1 <int> <chr[…]>
#> 6 6 1 E 7s 1 0 0 0 <int> <chr[…]>
#> 7 7 1 S 6s 4 0 0 3 <int> <chr[…]>
#> 8 8 1 S 2m 4 1 1 3 <int> <chr[…]>
#> 9 9 1 S 2m 1 0 0 0 <int> <chr[…]>
#> 10 10 1 S 7s 2 2 0 1 <int> <chr[…]>
#> # ℹ 23 more rows
#>
#> $paifu
#> # A tibble: 3,255 × 12
#> game_id round_id event_id type actor target pai tsumogiri consumed
#> <int> <int> <int> <chr> <int> <int> <chr> <lgl> <list>
#> 1 1 1 1 tsumo 0 NA S NA <NULL>
#> 2 1 1 2 dahai 0 NA N FALSE <NULL>
#> 3 1 1 3 tsumo 1 NA 2m NA <NULL>
#> 4 1 1 4 dahai 1 NA 8p FALSE <NULL>
#> 5 1 1 5 tsumo 2 NA 6p NA <NULL>
#> 6 1 1 6 dahai 2 NA 8s FALSE <NULL>
#> 7 1 1 7 tsumo 3 NA 1m NA <NULL>
#> 8 1 1 8 dahai 3 NA P FALSE <NULL>
#> 9 1 1 9 tsumo 0 NA 1m NA <NULL>
#> 10 1 1 10 dahai 0 NA 1p FALSE <NULL>
#> # ℹ 3,245 more rows
#> # ℹ 3 more variables: dora_marker <chr>, deltas <list>, ura_markers <list>まずはじめに、リーチ宣言があった本場について、各本場で1つ目のリーチ直後の打牌までを残す
paifu <-
dat[["paifu"]] |>
dplyr::filter(
# ここでは、ゲームの進行にかかわるイベントだけを残す
type %in% c("tsumo", "dahai", "chi", "pon", "daiminkan", "kakan", "ankan", "reach")
) |>
dplyr::filter(
any(type == "reach"),
# リーチ宣言は`tsumo->reach->dahai`の順なので
# 次のようにすると、各本場で最初のリーチ直後の`dahai`までを取れる
(dplyr::lag(type, default = "") == "reach") |>
dplyr::consecutive_id() <= 2,
.by = c(game_id, round_id)
) |>
dplyr::mutate(pai = trans_tile(pai))
paifu
#> # A tibble: 848 × 12
#> game_id round_id event_id type actor target pai tsumogiri consumed
#> <int> <int> <int> <chr> <int> <int> <chr> <lgl> <list>
#> 1 5 1 1 tsumo 1 NA m3 NA <NULL>
#> 2 5 1 2 dahai 1 NA z2 FALSE <NULL>
#> 3 5 1 3 tsumo 2 NA s9 NA <NULL>
#> 4 5 1 4 dahai 2 NA s9 TRUE <NULL>
#> 5 5 1 5 tsumo 3 NA z2 NA <NULL>
#> 6 5 1 6 dahai 3 NA m9 FALSE <NULL>
#> 7 5 1 7 tsumo 0 NA z7 NA <NULL>
#> 8 5 1 8 dahai 0 NA z2 FALSE <NULL>
#> 9 5 1 9 tsumo 1 NA z1 NA <NULL>
#> 10 5 1 10 dahai 1 NA z7 FALSE <NULL>
#> # ℹ 838 more rows
#> # ℹ 3 more variables: dora_marker <chr>, deltas <list>, ura_markers <list>その時点までの打牌(捨て牌)は全員から見えるので、ふつうに集計すればよい
summary_dapai <-
paifu |>
dplyr::filter(type == "dahai") |>
dplyr::summarize(n = dplyr::n(), .by = c(game_id, round_id, pai)) |>
dplyr::mutate(
tile = factor(pai, levels = shikakusphere::tiles[["cmajiang"]]),
.keep = "unused"
)
summary_dapai
#> # A tibble: 256 × 4
#> game_id round_id n tile
#> <int> <int> <int> <fct>
#> 1 5 1 3 z2
#> 2 5 1 3 s9
#> 3 5 1 1 m9
#> 4 5 1 2 z7
#> 5 5 1 1 z3
#> 6 5 1 1 m3
#> 7 5 1 1 p4
#> 8 5 1 2 z5
#> 9 5 1 2 s6
#> 10 5 1 1 p2
#> # ℹ 246 more rows手牌は自分のものだけ見えるはずなので、どのプレイヤー視点かを決めてから集計する
ここでは、リーチした人の下家(右隣)の手牌について集計してみる
まず先に、各本場におけるプレイヤーの配牌をtibbleにまとめておく
qipai <-
dat[["round_info"]] |>
dplyr::rowwise() |>
dplyr::reframe(
game_id = game_id,
round_id = round_id,
actor = 0:3,
tehais
) |>
dplyr::group_by(game_id, round_id, actor) |>
dplyr::mutate(qipai = list(trans_tile(as.character(tehais))), .keep = "unused") |>
dplyr::ungroup()
qipai
#> # A tibble: 132 × 4
#> game_id round_id actor qipai
#> <int> <int> <int> <list>
#> 1 1 1 0 <chr [13]>
#> 2 1 1 1 <chr [13]>
#> 3 1 1 2 <chr [13]>
#> 4 1 1 3 <chr [13]>
#> 5 2 1 0 <chr [13]>
#> 6 2 1 1 <chr [13]>
#> 7 2 1 2 <chr [13]>
#> 8 2 1 3 <chr [13]>
#> 9 3 1 0 <chr [13]>
#> 10 3 1 1 <chr [13]>
#> # ℹ 122 more rows次に、リーチしたプレイヤーの下家をまとめあげて……
pov <- dplyr::filter(paifu, type == "reach") |>
dplyr::select(game_id, round_id, actor) |>
# プレイヤーのid [0...3] は反時計回りに振られている(0の下家は1, 対面は2, 上家が3)
dplyr::mutate(shimocha = (actor + 1) %% 4, .keep = "unused")
pov
#> # A tibble: 14 × 3
#> game_id round_id shimocha
#> <int> <int> <dbl>
#> 1 5 1 3
#> 2 6 1 3
#> 3 10 1 1
#> 4 11 1 2
#> 5 13 1 1
#> 6 14 1 0
#> 7 16 1 3
#> 8 16 4 0
#> 9 16 6 1
#> 10 16 7 1
#> 11 16 9 0
#> 12 16 10 1
#> 13 18 1 0
#> 14 21 2 1次のようにすると、その時点における下家の手牌を再現できる
shoupai <- paifu |>
dplyr::left_join(pov, by = dplyr::join_by(game_id, round_id)) |>
dplyr::filter(actor == shimocha) |>
dplyr::summarize(
zimo = list(pai[which(type %in% c("tsumo", "chi", "pon", "daiminkan"))]),
dapai = list(pai[which(type %in% c("dahai", "kakan", "ankan"))]),
.by = c(game_id, round_id, actor)
) |>
dplyr::left_join(qipai, by = dplyr::join_by(game_id, round_id, actor)) |>
dplyr::reframe(
game_id = game_id,
round_id = round_id,
player = actor,
last_state = proceed(qipai, zimo, dapai) # この関数で手牌を再現する
)
shoupai
#> # A tibble: 14 × 4
#> game_id round_id player last_state
#> <int> <int> <int> <paistr>
#> 1 5 1 3 <13>'m2405666p456s44z5'
#> 2 6 1 3 <13>'s1234567z223344'
#> 3 10 1 1 <13>'m4456p999s2489z22'
#> 4 11 1 2 <13>'m456p56789s14689'
#> 5 13 1 1 <13>'m33789p357s34778'
#> 6 14 1 0 <13>'m67789s1367899z4'
#> 7 16 1 3 <13>'m12330p23488s144'
#> 8 16 4 0 <13>'m788889p24s45679'
#> 9 16 6 1 <13>'m4567p11223346s3'
#> 10 16 7 1 <13>'m1p2346s11233z223'
#> 11 16 9 0 <13>'m2235p11340s2278'
#> 12 16 10 1 <13>'m136p347s44789z56'
#> 13 18 1 0 <13>'m22244p4405s2356'
#> 14 21 2 1 <13>'m34p407s11223566'後は、ふつうに集計すればよい
summary_shoupai <- shoupai |>
dplyr::reframe(
tidy(last_state),
.by = c(game_id, round_id, player)
) |>
dplyr::select(!c(player, id))
summary_shoupai
#> # A tibble: 145 × 4
#> game_id round_id tile n
#> <int> <int> <fct> <int>
#> 1 5 1 m0 1
#> 2 5 1 m2 1
#> 3 5 1 m4 1
#> 4 5 1 m5 1
#> 5 5 1 m6 3
#> 6 5 1 p4 1
#> 7 5 1 p5 1
#> 8 5 1 p6 1
#> 9 5 1 s4 2
#> 10 5 1 z5 1
#> # ℹ 135 more rows副露は全員から見えるので、ふつうに集計できる。実際の副露メンツを表す文字列をつくるのがポイント
summary_fulou <- paifu |>
dplyr::group_by(game_id, round_id, actor) |>
dplyr::mutate(
# `mjai_conv()`で副露メンツを表す文字列をつくれる
pai = mjai_conv(type, pai, consumed, mjai_target(actor, target))
) |>
dplyr::group_by(game_id, round_id) |>
dplyr::filter(type %in% c("chi", "pon", "daiminkan", "ankan", "kakan")) |>
dplyr::summarize(pai = paste0(pai, collapse = ",") |> paistr(), .groups = "keep") |>
dplyr::reframe(tidy(pai)) |>
dplyr::select(!id)
summary_fulou
#> # A tibble: 22 × 4
#> game_id round_id tile n
#> <int> <int> <fct> <int>
#> 1 5 1 m6 3
#> 2 5 1 m7 3
#> 3 5 1 s5 3
#> 4 5 1 z7 3
#> 5 6 1 p1 3
#> 6 6 1 p2 3
#> 7 6 1 p3 3
#> 8 6 1 z5 3
#> 9 6 1 z6 3
#> 10 11 1 m7 3
#> # ℹ 12 more rows集計した縦長のデータを横長に展開する
feat <- list(dapai = summary_dapai, shoupai = summary_shoupai, fulou = summary_fulou) |>
purrr::imap(\(tbl, name) {
dplyr::mutate(tbl,
where = name,
tile = forcats::fct_drop(tile, only = "_") # 裏向きの牌を表す水準"_"をdropする
)
}) |>
purrr::list_rbind() |>
tidyr::pivot_wider(
id_cols = c(game_id, round_id),
names_from = c(where, tile),
names_expand = TRUE,
values_from = n,
values_fill = 0
)
feat
#> # A tibble: 14 × 113
#> game_id round_id dapai_m0 dapai_m1 dapai_m2 dapai_m3 dapai_m4 dapai_m5
#> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 5 1 0 2 0 1 0 1
#> 2 6 1 0 1 0 0 0 0
#> 3 10 1 0 1 1 0 0 1
#> 4 11 1 0 1 0 0 0 0
#> 5 13 1 0 1 0 0 1 2
#> 6 14 1 0 2 0 1 1 0
#> 7 16 1 0 0 1 0 1 0
#> 8 16 4 0 2 1 0 2 2
#> 9 16 6 0 1 1 1 0 0
#> 10 16 7 0 3 1 0 0 0
#> 11 16 9 0 1 0 2 1 0
#> 12 16 10 0 0 1 0 0 0
#> 13 18 1 0 2 0 0 1 1
#> 14 21 2 0 2 0 0 2 1
#> # ℹ 105 more variables: dapai_m6 <int>, dapai_m7 <int>, dapai_m8 <int>,
#> # dapai_m9 <int>, dapai_p0 <int>, dapai_p1 <int>, dapai_p2 <int>,
#> # dapai_p3 <int>, dapai_p4 <int>, dapai_p5 <int>, dapai_p6 <int>,
#> # dapai_p7 <int>, dapai_p8 <int>, dapai_p9 <int>, dapai_s0 <int>,
#> # dapai_s1 <int>, dapai_s2 <int>, dapai_s3 <int>, dapai_s4 <int>,
#> # dapai_s5 <int>, dapai_s6 <int>, dapai_s7 <int>, dapai_s8 <int>,
#> # dapai_s9 <int>, dapai_z1 <int>, dapai_z2 <int>, dapai_z3 <int>, …まず、リーチしたプレイヤーをまとめる
reach_player <- paifu |>
dplyr::filter(type == "reach") |>
dplyr::mutate(who_reaches = actor, .keep = "unused") |>
dplyr::select(game_id, round_id, who_reaches)
reach_player
#> # A tibble: 14 × 3
#> game_id round_id who_reaches
#> <int> <int> <int>
#> 1 5 1 2
#> 2 6 1 2
#> 3 10 1 0
#> 4 11 1 1
#> 5 13 1 0
#> 6 14 1 3
#> 7 16 1 2
#> 8 16 4 3
#> 9 16 6 0
#> 10 16 7 0
#> 11 16 9 3
#> 12 16 10 0
#> 13 18 1 3
#> 14 21 2 0そして、リーチしたプレイヤーの手牌を再現し、それらの当たり牌を調べる
label <- paifu |>
dplyr::left_join(reach_player, by = dplyr::join_by(game_id, round_id)) |>
dplyr::filter(actor == who_reaches) |>
dplyr::summarize(
zimo = list(pai[which(type %in% c("tsumo", "chi", "pon", "daiminkan"))]),
dapai = list(pai[which(type %in% c("dahai", "kakan", "ankan"))]),
.by = c(game_id, round_id, actor)
) |>
dplyr::left_join(qipai, by = dplyr::join_by(game_id, round_id, actor)) |>
dplyr::reframe(
game_id = game_id,
round_id = round_id,
player = actor,
last_state = proceed(qipai, zimo, dapai)
) |>
# `collect_tingpai()`で当たり牌を集められる
dplyr::mutate(atari_pai = collect_tingpai(last_state)) |>
tidyr::unnest_longer(atari_pai)
label
#> # A tibble: 26 × 5
#> game_id round_id player last_state atari_pai
#> <int> <int> <int> <paistr> <chr>
#> 1 5 1 2 <13>'m11234p78s123406' p6
#> 2 5 1 2 <13>'m11234p78s123406' p9
#> 3 6 1 2 <13>'m1234567s555z777' m1
#> 4 6 1 2 <13>'m1234567s555z777' m4
#> 5 6 1 2 <13>'m1234567s555z777' m7
#> 6 10 1 0 <13>'m234p44s12334789' s2
#> 7 10 1 0 <13>'m234p44s12334789' s5
#> 8 11 1 1 <13>'m123p1233445s123' p1
#> 9 11 1 1 <13>'m123p1233445s123' p4
#> 10 13 1 0 <13>'m4067799p340s789' m7
#> # ℹ 16 more rowsたとえば「s2は当たり牌か」のみを2値で予測するモデルを学習したいなら、次のようにすればよい
dplyr::left_join(
dplyr::summarize(label,
label = ("s2" %in% atari_pai),
.by = c(game_id, round_id)
),
feat,
by = dplyr::join_by(game_id, round_id)
)
#> # A tibble: 14 × 114
#> game_id round_id label dapai_m0 dapai_m1 dapai_m2 dapai_m3 dapai_m4 dapai_m5
#> <int> <int> <lgl> <int> <int> <int> <int> <int> <int>
#> 1 5 1 FALSE 0 2 0 1 0 1
#> 2 6 1 FALSE 0 1 0 0 0 0
#> 3 10 1 TRUE 0 1 1 0 0 1
#> 4 11 1 FALSE 0 1 0 0 0 0
#> 5 13 1 FALSE 0 1 0 0 1 2
#> 6 14 1 FALSE 0 2 0 1 1 0
#> 7 16 1 TRUE 0 0 1 0 1 0
#> 8 16 4 FALSE 0 2 1 0 2 2
#> 9 16 6 FALSE 0 1 1 1 0 0
#> 10 16 7 FALSE 0 3 1 0 0 0
#> 11 16 9 FALSE 0 1 0 2 1 0
#> 12 16 10 FALSE 0 0 1 0 0 0
#> 13 18 1 FALSE 0 2 0 0 1 1
#> 14 21 2 FALSE 0 2 0 0 2 1
#> # ℹ 105 more variables: dapai_m6 <int>, dapai_m7 <int>, dapai_m8 <int>,
#> # dapai_m9 <int>, dapai_p0 <int>, dapai_p1 <int>, dapai_p2 <int>,
#> # dapai_p3 <int>, dapai_p4 <int>, dapai_p5 <int>, dapai_p6 <int>,
#> # dapai_p7 <int>, dapai_p8 <int>, dapai_p9 <int>, dapai_s0 <int>,
#> # dapai_s1 <int>, dapai_s2 <int>, dapai_s3 <int>, dapai_s4 <int>,
#> # dapai_s5 <int>, dapai_s6 <int>, dapai_s7 <int>, dapai_s8 <int>,
#> # dapai_s9 <int>, dapai_z1 <int>, dapai_z2 <int>, dapai_z3 <int>, …