Javaで麻雀のシャンテン数を計算してみた！

#Javaで麻雀のシャンテン数を計算してみた！

何となくシャンテン数が計算してみたくなったのでやってみました。

~~めんどかった~~

※シャンテンがわからない方は[こちら](http://mjclv.com/basic/sp12.html)

##シャンテン計算方法考察

ぶっちゃけネットに色々方法とか転がってましたが、自分で考えました。

~~ので間違ってるかもしれないけどシラネｗ~~

シャンテンを考える上で、3種類考える必要があります。

1. 国士無双
2. 七対子
3. 通常の上がり

一つずつ考察していきます。

## 国士無双
これが一番簡単ですね。

[国士無双](http://www2.odn.ne.jp/~cbm15900/html/y14.html)は数字牌の1･9及び字牌7種を各1枚ずつで、どれか1種だけ2枚揃える特殊役になります。

なので、
```
シャンテン数 = 13 - (1･9･字牌の種類数) - (1･9･字牌がトイツがあれば1)
```
となります。

## 七対子
こちらも割と単純です。

[七対子](http://www2.odn.ne.jp/~cbm15900/html/y9.html)は、トイツを7種揃える特殊役になります。

同じ牌が4つあってもトイツにカウントされないことを考慮に入れると、
```
シャンテン数 = 6 - (トイツの種類数) + (牌の種類数が7に満たない場合は7 - 種類数)
```

となります。

## 通常の上がり

無論これがすっげーめんどくさいです。

通常麻雀は、トイツが一つとアンコもしくはシュンツを4つ揃えることで上がることができます。

シャンテン数の計算は

```
シャンテン数 = 8 - (アンコの数 + シュンツの数) * 2 - (トイツの数 + ターツの数) + (アンコ・シュンツ・トイツ・ターツが6つある場合は+1) + (アンコ・シュンツが4つそろっておらず、アンコ・シュンツ・ターツが5つ以上あり、トイツがない場合は+1)
```
となる・・・と思いますｗ

後ろ二つについて軽く説明すると

```
アンコ・シュンツ・トイツ・ターツが6つある場合は+1
```

頭と面子を合わせても5つしか必要ないため、シャンテン数を減らすために使ってはいけないトイツ・ターツが存在するので1足します。

```
アンコ・シュンツが4つそろっておらず、アンコ・シュンツ・ターツが5つ以上あり、トイツがない場合は+1
```

いざ、数が揃っていても、頭が決まっていない場合はターツを一つ頭に変換する必要があるので1たします。

## 各種面子カウント方法

ここからが本題。

実際に牌を並べた時に、どれを面子として取り扱うかが中々に難しい作業になります。

軽くググってみると再帰的に総当たりが必要とかあったわけですが、~~**面倒**~~スマートじゃないので、頑張って考えてみました。(実際は書きながら)

### ベース
基本は決定順番は
1. シュンツ
2. アンコ
3. トイツ
4. ターツ

の順番で決定をしていきます。

例を一つ上げながら説明します

```
|三|四|五|七|七|七|一|一|二|五|九|八|九|
|萬|萬|萬|萬|萬|萬|筒|筒|筒|筒|筒|索|索|

```

### シュンツの確定
端から順に確定をさせていきます。

三萬～五萬までが3つ並んでいるのでこれを確定します。

なお、シュンツの確定は、小さい数字から確定させた場合と、大きな数字から確定させた場合で、その他の面子の残り方が変わってくる影響でシャンテン数が変わるので、一通りの確認において、両方操作を行い、シャンテン数が小さい方を採用します。

### アンコ・トイツの確定

シュンツで確定した面子を除き、2枚・3枚重なっているものをそれぞれ、トイツ・アンコと確定します。

### ターツ
残った牌で一つ飛ばしまでの数字牌をターツと確定します。

### 例外処理

シュンツを優先するよりも、アンコ/ターツを優先して確定した場合が、シャンテン数が低くなることがあります。(それなりに結構)

その場合、配牌の中から、トイツ・アンコの牌を探し出して、それぞれの牌がトイツ・アンコの確定を先にした方がシャンテン数が低くなるようであればそちらを優先します。

例えばこんな配牌です。

```
|一|一|二|三|四|六|七|八|九|九|九|八|九|
|萬|萬|萬|萬|萬|萬|萬|萬|萬|萬|筒|索|索|

```
本来、二萬～四萬をシュンツ・一萬と九萬をトイツとしたいところですが、シュンツを上から確定させても、下から確定させても一萬と九萬両方をトイツに確定できません。

この場合に備え、

1. 一度ベースのシャンテン計算を走らせる
2. 配牌から一萬・九萬がトイツであることを確認
3. 一萬をトイツとして先に確定した計算を行う。
4. 九萬をトイツとして先に確定した計算を行う。
5. 3/4の計算の方がシャンテン数が小さければそれぞれ先にトイツと確定をして計算を行う。(この場合は3も4もシャンテン数が小さくなるので、一萬と九萬をトイツと先に確定してから計算を行う。)

といった形で回避をすればいっかなーと！思ってます。


## 実際に組んでみた
ちょいここに貼るには長くなったので、githubに公開しました。

https://github.com/satthi/majong_shanten/blob/master/Majong.java

~~※大分変数の使い方とか適当極まりない気しかしないけど気のせいです。~~

とりあえず、メンチンに限定してかなり確認をしたのでおそらくおそらく正しい値が出ると思ってます！

## Java的なお話
[前回](http://qiita.com/satthi/items/78edbf6cf4614e1151ae)に引き続きJavaで書いてみたわけですが、**すっごい苦労しました。**

何が問題って、配列がすっごいめんどい・・・。

PHPだと、簡単に配列の結合や代入だったり、型もいろんな方が自由に使えるんだけど、Javaってそう簡単にはいかないんですね・・・。

PHPだと

```php
$hoge = array(
    'num' => 1,
    'type' => 1,
    'name' => '一萬'
);
```

みたいなのが簡単にできたんですけど、Javaだと

```java
HashMap<String,Integer>haipai = new HashMap<String,Integer>());
haipai.put("num", 1);
haipai.put("type", 1);
//これはこの書き方ではできない
//haipai.put("name", "一萬");
```

てなところが、うまくできなかったです・・・。(たぶんちゃんとやれる方法はあるはずなんですけどよくわからんかった！)

他にも、配列の一部キーの削除とかも

```php
//PHP
$hoge = array(
    'hoge',
    'fuga',
    'hage'
);
unset($hoge[0]);
unset($hoge[1]);
//hageが残る
```

```java
//Java
ArrayList<String> hoge= new ArrayList<String>();
hoge.add("hoge");
hoge.add("fuga");
hoge.add("hage");
hoge.remove(0);
hoge.remove(1);
//removeのたびにkeyが詰まっているのでfugaが残る。
```
みたいな形で、何で思った通りに動かないかがなかなかわからなくて苦労しました・・・。

すっごい勉強になってます・・・。

次何題材にしよっかなー。