麻雀基本ライブラリを使ってみる

[電脳麻将](https://kobalab.net/majiang/) という麻雀アプリを開発しているのですが、基本となるライブラリを [@kobalab/majiang-core](https://www.npmjs.com/package/@kobalab/majiang-core) として [Node.js](https://nodejs.org/ja/) の [npm](https://www.npmjs.com/) パッケージとしました。

 - シャンテン数計算
 - 和了打点計算
 - AI同士の自動対戦

といったことが行えます。

APIのドキュメントは
 - https://github.com/kobalab/majiang-core/wiki

にあります。

ここでは使い方を簡単に説明します。

## インストール

作業用ディレクトリにインストールします。
```shell
$ mkdir work
$ cd work
$ npm init -y
$ npm i @kobalab/majiang-core
```

## シャンテン数計算

手はじめにシャンテン数計算をしてみます。プログラムを書くのも面倒なので、NodeのREPLを使います。
```shell
$ node
Welcome to Node.js v16.17.0.
Type ".help" for more information.
> 
```
変数 Majiang にライブラリをロードします。
```js
> Majiang = require('@kobalab/majiang-core')
{
  rule: [Function (anonymous)],
  Shoupai: [class Shoupai],
  Shan: [class Shan],
  He: [class He],
  Board: [class Board],
  Game: [class Game],
  Player: [class Player],
  Util: {
    xiangting_guoshi: [Function: xiangting_guoshi],
    xiangting_qidui: [Function: xiangting_qidui],
    xiangting_yiban: [Function: xiangting_yiban],
    xiangting: [Function: xiangting],
    tingpai: [Function: tingpai],
    hule: [Function: hule],
    hule_param: [Function: hule_param],
    hule_mianzi: [Function: hule_mianzi]
  }
}
```
以下の牌姿のシャンテン数を計算してみましょう。いわゆる「完全イーシャンテン」の形です。
<span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/man2.gif" width="24" height="34" alt="m2"><img src="https://kobalab.github.io/paiga/man3.gif" width="24" height="34" alt="m3"><img src="https://kobalab.github.io/paiga/man3.gif" width="24" height="34" alt="m3"><img src="https://kobalab.github.io/paiga/man4.gif" width="24" height="34" alt="m4"><img src="https://kobalab.github.io/paiga/man4.gif" width="24" height="34" alt="m4"><img src="https://kobalab.github.io/paiga/man5.gif" width="24" height="34" alt="m5"><img src="https://kobalab.github.io/paiga/man7.gif" width="24" height="34" alt="m7"><img src="https://kobalab.github.io/paiga/man7.gif" width="24" height="34" alt="m7"><img src="https://kobalab.github.io/paiga/pin6.gif" width="24" height="34" alt="p6"><img src="https://kobalab.github.io/paiga/pin6.gif" width="24" height="34" alt="p6"><img src="https://kobalab.github.io/paiga/pin7.gif" width="24" height="34" alt="p7"><img src="https://kobalab.github.io/paiga/sou5.gif" width="24" height="34" alt="s5"><img src="https://kobalab.github.io/paiga/sou6.gif" width="24" height="34" alt="s6"></span>
手牌のインスタンスを生成します。[^1]
[^1]: 手牌の指定方法は https://github.com/kobalab/majiang-core/wiki/%E7%89%8C%E5%A7%BF を参照して下さい。
```js
> s = Majiang.Shoupai.fromString('m23344577p667s56')
```

シャンテン数を計算します。
```js
> Majiang.Util.xiangting(s)
1
```
1 と結果が出ました。

では有効牌(引くとシャンテン数が進む牌)を調べてみましょう。
```js
> Majiang.Util.tingpai(s)
[ 'm7', 'p5', 'p6', 'p8', 's4', 's7' ]
```
<span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/man7.gif" width="24" height="34" alt="m7"></span> <span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/pin5.gif" width="24" height="34" alt="p5"></span> <span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/pin6.gif" width="24" height="34" alt="p6"></span> <span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/pin8.gif" width="24" height="34" alt="p8"></span> <span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/sou4.gif" width="24" height="34" alt="s4"></span> <span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/sou7.gif" width="24" height="34" alt="s7"></span> の6種でテンパイすると分かりました。

では <span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/man7.gif" width="24" height="34" alt="m7"></span> を引いた場合のシャンテン数を計算してみましょう。
```js
> Majiang.Util.xiangting(s.zimo('m7'))
0
```
テンパイ(シャンテン数 = 0)しました。

## 和了打点計算

次に和了点を計算します。

[東場 南家] 1本場 供託なし
<span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/ura.gif" width="19" height="26" alt="_"><img src="https://kobalab.github.io/paiga/ura.gif" width="19" height="26" alt="_"><img src="https://kobalab.github.io/paiga/pin1.gif" width="19" height="26" alt="p1"><img src="https://kobalab.github.io/paiga/ura.gif" width="19" height="26" alt="_"><img src="https://kobalab.github.io/paiga/ura.gif" width="19" height="26" alt="_"><img src="https://kobalab.github.io/paiga/ura.gif" width="19" height="26" alt="_"><img src="https://kobalab.github.io/paiga/ura.gif" width="19" height="26" alt="_"></span>
<span style="white-space:pre;"><img src="https://kobalab.github.io/paiga/man1.gif" width="24" height="34" alt="m1"><img src="https://kobalab.github.io/paiga/man2.gif" width="24" height="34" alt="m2"><img src="https://kobalab.github.io/paiga/man3.gif" width="24" height="34" alt="m3"><img src="https://kobalab.github.io/paiga/pin1.gif" width="24" height="34" alt="p1"><img src="https://kobalab.github.io/paiga/pin2.gif" width="24" height="34" alt="p2"><img src="https://kobalab.github.io/paiga/pin3.gif" width="24" height="34" alt="p3"><img src="https://kobalab.github.io/paiga/ton.gif" width="24" height="34" alt="z1"> <img src="https://kobalab.github.io/paiga/nan.gif" width="24" height="34" alt="z2"><img src="https://kobalab.github.io/paiga/ynan.gif" width="34" height="24" alt="z2-"><img src="https://kobalab.github.io/paiga/nan.gif" width="24" height="34" alt="z2"> <img src="https://kobalab.github.io/paiga/ysou1.gif" width="34" height="24" alt="s1-"><img src="https://kobalab.github.io/paiga/sou2.gif" width="24" height="34" alt="s2"><img src="https://kobalab.github.io/paiga/sou3.gif" width="24" height="34" alt="s3"></span>

の手牌で上家から <span style="white-space:pre;margin:0"><img src="https://kobalab.github.io/paiga/ton.gif" width="24" height="34" alt="z1"></span> でロン和了した場合を計算します。
```js
> Majiang.Util.hule(
    Majiang.Shoupai.fromString('m123p123z1,s1-23,z222='), 'z1-',
    Majiang.Util.hule_param({
        zhuangfeng: 0,      // 東場
        menfeng:    1,      // 南家
        baopai:     'p1',   // ドラ表示牌
        changbang:  1,      // 1本場
        lizhibang:  0,      // 供託なし
    })
);
```

デフォルトのルールでは「切り上げ満貫」がないため、30符4翻＝7700点、積み場を含めると東家から南家に8000点の支払いとなります。

```js
{
  hupai: [      // 和了役一覧
    { name: '自風 南', fanshu: 1 },
    { name: '三色同順', fanshu: 1 },
    { name: '混全帯幺九', fanshu: 1 },
    { name: 'ドラ', fanshu: 1 }
  ],
  fu: 30,       // 符
  fanshu: 4,    // 翻数
  defen: 7700,  // 和了打点
  fenpei: [ -8000, 8000, 0, 0 ]     // 局収支
}
```

ルールを「切り上げ満貫あり」に変えてみます。
```js
> Majiang.Util.hule(
    Majiang.Shoupai.fromString('m123p123z1,s1-23,z222='), 'z1-',
    Majiang.Util.hule_param({
        zhuangfeng: 0,      // 東場
        menfeng:    1,      // 南家
        baopai:     'p1',   // ドラ表示牌
        changbang:  1,      // 1本場
        lizhibang:  0,      // 供託なし
        rule:       Majiang.rule({'切り上げ満貫あり': true})    // ルール
    })
);
```
和了打点が満貫(＝8000点)になりました。
```js
{
  hupai: [      // 和了役一覧
    { name: '自風 南', fanshu: 1 },
    { name: '三色同順', fanshu: 1 },
    { name: '混全帯幺九', fanshu: 1 },
    { name: 'ドラ', fanshu: 1 }
  ],
  fu: 30,       // 符
  fanshu: 4,    // 翻数
  defen: 8000,  // 和了打点
  fenpei: [ -8300, 8300, 0, 0 ]     // 局収支
}
```
## 書籍
<a href="https://www.amazon.co.jp/dp/4798067881"><img src="https://m.media-amazon.com/images/I/51DMflZaBNL._SL500_.jpg" title="対戦型麻雀ゲームAIのアルゴリズムと実装" width=200></a>

電脳麻将のプログラムを解説した書籍を出版しました。
 - [対戦型麻雀ゲームAIのアルゴリズムと実装](https://www.amazon.co.jp/dp/4798067881)

書籍では [@kobalab/majiang-core](https://www.npmjs.com/package/@kobalab/majiang-core) だけでなく [@kobalab/majiang-ai](https://www.npmjs.com/package/@kobalab/majiang-ai) も扱っています。