An open API service indexing awesome lists of open source software.

https://github.com/paithiov909/shikakusphere

Miscellaneous functions for Japanese mahjong
https://github.com/paithiov909/shikakusphere

mahjong r r-package rcpp

Last synced: 6 months ago
JSON representation

Miscellaneous functions for Japanese mahjong

Awesome Lists containing this project

README

          

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
pkgload::load_all(export_all = FALSE)
```

# shikakusphere

[![shikakusphere status badge](https://paithiov909.r-universe.dev/badges/shikakusphere)](https://paithiov909.r-universe.dev/shikakusphere)
[![R-CMD-check](https://github.com/paithiov909/shikakusphere/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/paithiov909/shikakusphere/actions/workflows/R-CMD-check.yaml)
[![codecov](https://codecov.io/gh/paithiov909/shikakusphere/branch/main/graph/badge.svg)](https://app.codecov.io/gh/paithiov909/shikakusphere)

shikakusphere is a collection of miscellaneous funcitons for Japanese mahjong that wraps C++ sources derived from [tomohxx/shanten-number](https://github.com/tomohxx/shanten-number) and [TadaoYamaoka/cmajiang](https://github.com/TadaoYamaoka/cmajiang).

The name "shikakusphere" is a compound word of "shikaku" + "sphere". "shikaku" means "square" in Japanese.
It comes from "Shikakui Uchuu De Matteru Yo", which is the third ED of the anime "Saki", and sounds exactly like mahjong games.

## Installation

You can install the development version of shikakusphere from [GitHub](https://github.com/paithiov909/shikakusphere) with:

``` r
# install.packages("pak")
pak::pak("paithiov909/shikakusphere")
```

## Example

### 手牌の表現

手牌は[kobalab/majiang-core](https://github.com/kobalab/majiang-core)の記法で文字列として表現します。

**参考**

- [牌 · kobalab/majiang-core Wiki](https://github.com/kobalab/majiang-core/wiki/%E7%89%8C)
- [面子 · kobalab/majiang-core Wiki](https://github.com/kobalab/majiang-core/wiki/%E9%9D%A2%E5%AD%90)
- [牌姿 · kobalab/majiang-core Wiki](https://github.com/kobalab/majiang-core/wiki/%E7%89%8C%E5%A7%BF)
- [電脳麻将のプログラム中の中国語一覧 - koba::blog](https://blog.kobalab.net/entry/20170722/1500688645)

`paistr()`は、こうした手牌の文字列表現を扱いやすくするためのS3クラス(``)です。`print()`したときに、その文字列に含まれる牌の枚数を表示します。また、`plot()`で牌姿を画像としてプロットできます。

```{r}
library(shikakusphere)

hands <- c(
"m11p234s555z11122*",
"p1122334455667",
"m19p19s19z1234567",
"m055z7z7,m78-9,z5555,z666=",
"m055z7z7,m78-9,z5555,z666=,"
)

hands <- paistr(hands)
hands

plot(hands[4])
```

``を`print()`したときに表示される牌の枚数は不正確な場合があります。手牌の表現にどの牌が何枚含まれるかを正確に確認するには、`tidy()`を使うことができます。

```{r}
tidy(hands)
```

このかたちの表現は`lineup()`でlist of factorsにすることができます。

```{r}
tidy(hands) |>
lineup()
```

さらに、このかたちのlist of factors(またはlist of characters)は`lipai()`で文字列表現に変換できます(ただし、`lipai()`では`_*+=-,`は扱えません)。

```{r}
c(paste0("m", 1:9), paste0("s", 4:6), paste0("z", c(1, 1))) |>
lipai()
```

### シャンテン数・有効牌の確認

手牌のうちでも、副露面子を除いた打牌可能な部分のことを兵牌(bingpai)と呼びます。このパッケージで使用している実装における「シャンテン数」は、大まかには「その手牌の兵牌に注目したとき、手牌を聴牌形(和了形)に変形させるために必要なツモの最小数」のことを指しています。そのため、少牌だったりして素朴な意味での和了形になっていないかたちであっても、兵牌がn面子1雀頭のかたちになってさえいれば、ここでのシャンテン数は`-1`を返します。

また、ここでの「有効牌」というのは、その牌を新たにツモったときに、この意味でのシャンテン数が減少する牌のことです。兵牌の枚数が14, 11, 8, 5, 2枚の場合や、すでに和了形であって有効牌が定義できないときには、`character(0)`を有効牌として返します。なお、兵牌のなかに同じ牌が4枚ある場合についてのみ、有効牌であっても待ち牌としません。

```{r}
# シャンテン数
n_xiangting <- calc_xiangting(hands)
n_xiangting

# 有効牌
collect_tingpai(hands[n_xiangting$num >= 0])
collect_tingpai(c("m1234444p456s789", "m13p456s789z11,m2222"))
```

「あと何枚ツモれば手牌全体として聴牌形にできるか」という意味でのシャンテン数は、たとえば次のようにして計算できると思います。

```{r}
dplyr::tibble(
hand = paistr(c("m1", "m1134", "m1,m45-6", "m1122p334455,s6-78", "m19p19s19z112345")),
calc_xiangting(hand),
calculate(hand)
) |>
dplyr::mutate(
bingpai = rowSums(bingpai),
n_zimo_to_hule = dplyr::case_when(
mode == "yiban" & bingpai + n_fulou * 3 < 13 ~ 13 - bingpai - n_fulou * 3,
.default = num
)
)
```

### 得点・あがり役の確認

`calc_defen()`はvectorizeされていないため、必ず、長さが1の文字列表現を渡してください。

```{r}
# 得点
score <- calc_defen(hands[n_xiangting$num == -1][1], baopai = "z1")
score

# ロン和了のときはこんなふうに書く
calc_defen("m345567p234s3378", baopai = "z1", rongpai = "s9=")

# あがり役の表示
parse_hupai(score$hupai, "jp")
```

### 牌譜ファイルを扱う

```{r include = FALSE}
stopifnot(requireNamespace("convlog", quietly = TRUE))
```

[convlog](https://github.com/paithiov909/convlog)パッケージを使うと、天鳳のJSON形式(`tenhou.net/6`)のファイルから牌譜を読み込むことができます。

`convlog::read_tenhou6()`の戻り値は、次のようなかたちをしています。

```{r}
json_log <-
list.files(
system.file("testdata/", package = "convlog"),
pattern = "*.json$",
full.names = TRUE
) |>
convlog::read_tenhou6()

json_log
```

この関数は、天鳳のJSON形式を一度MJAIフォーマットのJSON文字列へと変換したうえでパースするため、牌の表現などがshikakusphereで用いているものとは異なっています。MJAIフォーマットにおける牌の表現をshikakusphereで用いている表現に変換するには、`trans_tile()`, `mjai_target()`, `mjai_conv()`を使って、次のようにします。

```{r}
paifu <-
json_log[["paifu"]] |>
dplyr::filter(
type %in% c("tsumo", "dahai", "chi", "pon", "daiminkan", "kakan", "ankan", "reach")
) |>
dplyr::mutate(
# ツモ切りかを判定する場合、あらかじめ`NA`を`FALSE`で埋めておく
tsumogiri = dplyr::if_else(is.na(tsumogiri), FALSE, tsumogiri),
# 牌の表現の変換
pai = trans_tile(pai)
) |>
dplyr::mutate(
# 副露メンツの変換
pai = mjai_conv(type, pai, consumed, mjai_target(actor, target)),
# ツモ切りなら末尾に"_"を付ける
pai = dplyr::if_else(tsumogiri,
paste0(pai, "_"),
pai
),
# リーチ宣言牌なら末尾に"*"を付ける
pai = dplyr::if_else(
dplyr::lag(type, default = "") == "reach",
paste0(pai, "*"),
pai
),
.by = c(game_id, round_id, actor)
)

paifu
```

`proceed()`を使うと、ゲームのある時点におけるプレイヤーの手牌の状態を再現することができます。この関数には、各プレイヤーの配牌とその時点までのツモ・打牌をそれぞれlistとして与えます。チー・ポンと大明槓についてはツモの一部、暗槓と加槓については打牌の一部とします。

たとえば、ここで読み込んでいるデータについて、各本場の終わりの時点におけるプレイヤーの手牌を再現するには、次のようにします。

```{r}
qipai <-
json_log[["round_info"]] |>
dplyr::rowwise() |>
dplyr::reframe(
game_id = game_id,
round_id = round_id,
actor = 0:3,
tehais
) |>
dplyr::mutate(
qipai = list(trans_tile(as.character(tehais))),
.by = c(game_id, round_id, actor),
.keep = "unused"
)

paifu |>
dplyr::left_join(qipai, by = dplyr::join_by(game_id, round_id, actor)) |>
dplyr::summarise(
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)
)
```

# License

GPL (>= 3). This package contains the [tinyxml2](https://github.com/leethomason/tinyxml2) code released under the zlib license.