@pai-forge/riichi-mahjong 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/core/dora.d.ts +14 -0
- package/dist/core/dora.js +71 -0
- package/dist/core/hai.d.ts +30 -0
- package/dist/core/hai.js +78 -0
- package/dist/core/machi.d.ts +11 -0
- package/dist/core/machi.js +58 -0
- package/dist/core/mentsu.d.ts +26 -0
- package/dist/core/mentsu.js +87 -0
- package/dist/core/tehai.d.ts +38 -0
- package/dist/core/tehai.js +87 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.js +58 -0
- package/dist/features/machi/index.d.ts +9 -0
- package/dist/features/machi/index.js +37 -0
- package/dist/features/machi/types.d.ts +1 -0
- package/dist/features/machi/types.js +1 -0
- package/dist/features/parser/index.d.ts +19 -0
- package/dist/features/parser/index.js +28 -0
- package/dist/features/parser/mspz.d.ts +85 -0
- package/dist/features/parser/mspz.js +365 -0
- package/dist/features/points/constants.d.ts +27 -0
- package/dist/features/points/constants.js +30 -0
- package/dist/features/points/index.d.ts +21 -0
- package/dist/features/points/index.js +174 -0
- package/dist/features/points/lib/fu/constants.d.ts +37 -0
- package/dist/features/points/lib/fu/constants.js +45 -0
- package/dist/features/points/lib/fu/index.d.ts +11 -0
- package/dist/features/points/lib/fu/index.js +23 -0
- package/dist/features/points/lib/fu/lib/chiitoitsu.d.ts +5 -0
- package/dist/features/points/lib/fu/lib/chiitoitsu.js +17 -0
- package/dist/features/points/lib/fu/lib/kokushi.d.ts +6 -0
- package/dist/features/points/lib/fu/lib/kokushi.js +18 -0
- package/dist/features/points/lib/fu/lib/mentsu.d.ts +11 -0
- package/dist/features/points/lib/fu/lib/mentsu.js +122 -0
- package/dist/features/points/lib/fu/types.d.ts +55 -0
- package/dist/features/points/lib/fu/types.js +1 -0
- package/dist/features/points/types.d.ts +27 -0
- package/dist/features/points/types.js +1 -0
- package/dist/features/shanten/index.d.ts +16 -0
- package/dist/features/shanten/index.js +25 -0
- package/dist/features/shanten/logic/chiitoitsu.d.ts +8 -0
- package/dist/features/shanten/logic/chiitoitsu.js +36 -0
- package/dist/features/shanten/logic/kokushi.d.ts +16 -0
- package/dist/features/shanten/logic/kokushi.js +48 -0
- package/dist/features/shanten/logic/mentsu-te.d.ts +8 -0
- package/dist/features/shanten/logic/mentsu-te.js +129 -0
- package/dist/features/yaku/factory.d.ts +13 -0
- package/dist/features/yaku/factory.js +19 -0
- package/dist/features/yaku/index.d.ts +12 -0
- package/dist/features/yaku/index.js +62 -0
- package/dist/features/yaku/lib/definitions/chiitoitsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chiitoitsu.js +12 -0
- package/dist/features/yaku/lib/definitions/chinitsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chinitsu.js +40 -0
- package/dist/features/yaku/lib/definitions/chinroutou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chinroutou.js +21 -0
- package/dist/features/yaku/lib/definitions/chuuren-poutou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chuuren-poutou.js +69 -0
- package/dist/features/yaku/lib/definitions/daisangen.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/daisangen.js +26 -0
- package/dist/features/yaku/lib/definitions/daisuushii.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/daisuushii.js +32 -0
- package/dist/features/yaku/lib/definitions/honchan.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/honchan.js +29 -0
- package/dist/features/yaku/lib/definitions/honitsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/honitsu.js +40 -0
- package/dist/features/yaku/lib/definitions/honroutou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/honroutou.js +33 -0
- package/dist/features/yaku/lib/definitions/iipeiko.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/iipeiko.js +46 -0
- package/dist/features/yaku/lib/definitions/ikkitsuukan.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ikkitsuukan.js +56 -0
- package/dist/features/yaku/lib/definitions/index.d.ts +30 -0
- package/dist/features/yaku/lib/definitions/index.js +90 -0
- package/dist/features/yaku/lib/definitions/junchan.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/junchan.js +25 -0
- package/dist/features/yaku/lib/definitions/kokushi.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/kokushi.js +12 -0
- package/dist/features/yaku/lib/definitions/menzen-tsumo.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/menzen-tsumo.js +8 -0
- package/dist/features/yaku/lib/definitions/pinfu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/pinfu.js +40 -0
- package/dist/features/yaku/lib/definitions/ryanpeiko.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ryanpeiko.js +33 -0
- package/dist/features/yaku/lib/definitions/ryuuiisou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ryuuiisou.js +43 -0
- package/dist/features/yaku/lib/definitions/sanankou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sanankou.js +49 -0
- package/dist/features/yaku/lib/definitions/sankantsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sankantsu.js +18 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doujun.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doujun.js +58 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doukou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doukou.js +53 -0
- package/dist/features/yaku/lib/definitions/shousangen.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/shousangen.js +28 -0
- package/dist/features/yaku/lib/definitions/shousuushii.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/shousuushii.js +34 -0
- package/dist/features/yaku/lib/definitions/suuankou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/suuankou.js +63 -0
- package/dist/features/yaku/lib/definitions/suukantsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/suukantsu.js +18 -0
- package/dist/features/yaku/lib/definitions/tanyao.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/tanyao.js +23 -0
- package/dist/features/yaku/lib/definitions/toitoi.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/toitoi.js +16 -0
- package/dist/features/yaku/lib/definitions/tsuuiisou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/tsuuiisou.js +35 -0
- package/dist/features/yaku/lib/definitions/yakuhai.d.ts +4 -0
- package/dist/features/yaku/lib/definitions/yakuhai.js +21 -0
- package/dist/features/yaku/lib/index.d.ts +1 -0
- package/dist/features/yaku/lib/index.js +1 -0
- package/dist/features/yaku/lib/structures/chiitoitsu.d.ts +6 -0
- package/dist/features/yaku/lib/structures/chiitoitsu.js +38 -0
- package/dist/features/yaku/lib/structures/index.d.ts +10 -0
- package/dist/features/yaku/lib/structures/index.js +17 -0
- package/dist/features/yaku/lib/structures/kokushi.d.ts +6 -0
- package/dist/features/yaku/lib/structures/kokushi.js +43 -0
- package/dist/features/yaku/lib/structures/mentsu-te.d.ts +24 -0
- package/dist/features/yaku/lib/structures/mentsu-te.js +127 -0
- package/dist/features/yaku/types.d.ts +121 -0
- package/dist/features/yaku/types.js +1 -0
- package/dist/features/yaku/utils.d.ts +19 -0
- package/dist/features/yaku/utils.js +34 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +9 -0
- package/dist/types.d.ts +280 -0
- package/dist/types.js +97 -0
- package/dist/utils/assertions.d.ts +22 -0
- package/dist/utils/assertions.js +33 -0
- package/dist/utils/test-helpers.d.ts +55 -0
- package/dist/utils/test-helpers.js +124 -0
- package/package.json +62 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const IIPEIKO_YAKU = {
|
|
3
|
+
name: "Iipeikou",
|
|
4
|
+
han: {
|
|
5
|
+
open: 0, // 門前限定
|
|
6
|
+
closed: 1,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkIipeiko = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const shuntsuList = hand.fourMentsu.filter((mentsu) => mentsu.type === "Shuntsu");
|
|
14
|
+
// 順子が2つ未満なら一盃口はあり得ない
|
|
15
|
+
if (shuntsuList.length < 2) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// 同じ順子が2つあるか探す
|
|
19
|
+
// haisの内容比較が必要。Shuntsu.haisはソート済みであることを前提とするか、
|
|
20
|
+
// ここで比較用キーを作って判定する。
|
|
21
|
+
// ライブラリの仕様としてShuntsuのhaisは [T, T, T] だが順序保証は型定義上は明示されていないものの、
|
|
22
|
+
// 一般的な実装として昇順になっているはず。
|
|
23
|
+
// 安全のため、各順子の牌をソートした文字列などをキーにして比較する。
|
|
24
|
+
// ただし、Shuntsu定義上 [T, T, T] で、順子である以上連続しているため、
|
|
25
|
+
// 先頭の牌(最小の牌)が同じで、種類(萬子/筒子/索子)が同じなら同一順子とみなせる。
|
|
26
|
+
// しかし HaiKindId の単純な数値比較で十分。
|
|
27
|
+
// 例えば 1m, 2m, 3m の順子は [0, 1, 2]。
|
|
28
|
+
// 順子の構成牌IDが完全一致するかどうかを見れば良い。
|
|
29
|
+
for (let i = 0; i < shuntsuList.length; i++) {
|
|
30
|
+
for (let j = i + 1; j < shuntsuList.length; j++) {
|
|
31
|
+
const shuntsuA = shuntsuList[i];
|
|
32
|
+
const shuntsuB = shuntsuList[j];
|
|
33
|
+
if (!shuntsuA || !shuntsuB)
|
|
34
|
+
continue;
|
|
35
|
+
// 牌のID列が完全に一致するか
|
|
36
|
+
const isSame = shuntsuA.hais[0] === shuntsuB.hais[0] &&
|
|
37
|
+
shuntsuA.hais[1] === shuntsuB.hais[1] &&
|
|
38
|
+
shuntsuA.hais[2] === shuntsuB.hais[2];
|
|
39
|
+
if (isSame) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
export const iipeikoDefinition = createYakuDefinition(IIPEIKO_YAKU, checkIipeiko);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const IKKITSUUKAN_YAKU = {
|
|
3
|
+
name: "Ikkitsuukan",
|
|
4
|
+
han: {
|
|
5
|
+
open: 1,
|
|
6
|
+
closed: 2,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkIkkitsuukan = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const shuntsuList = hand.fourMentsu.filter((mentsu) => mentsu.type === "Shuntsu");
|
|
14
|
+
if (shuntsuList.length < 3) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// 順子リストから3つの組み合わせを全てチェックし、一気通貫の条件を満たすものを探す
|
|
18
|
+
// 条件:
|
|
19
|
+
// 1. 3つの順子が全て同じ色(萬子、筒子、索子のいずれか)であること
|
|
20
|
+
// 2. 3つの順子がそれぞれ 1-2-3, 4-5-6, 7-8-9 であること
|
|
21
|
+
// (インデックスの余りが 0, 3, 6 となること)
|
|
22
|
+
for (let i = 0; i < shuntsuList.length; i++) {
|
|
23
|
+
for (let j = i + 1; j < shuntsuList.length; j++) {
|
|
24
|
+
for (let k = j + 1; k < shuntsuList.length; k++) {
|
|
25
|
+
const s1 = shuntsuList[i];
|
|
26
|
+
const s2 = shuntsuList[j];
|
|
27
|
+
const s3 = shuntsuList[k];
|
|
28
|
+
if (!s1 || !s2 || !s3)
|
|
29
|
+
continue;
|
|
30
|
+
const firstHai1 = s1.hais[0];
|
|
31
|
+
const firstHai2 = s2.hais[0];
|
|
32
|
+
const firstHai3 = s3.hais[0];
|
|
33
|
+
const suit1 = Math.floor(firstHai1 / 9);
|
|
34
|
+
const suit2 = Math.floor(firstHai2 / 9);
|
|
35
|
+
const suit3 = Math.floor(firstHai3 / 9);
|
|
36
|
+
// 全て同じ色でなければならない
|
|
37
|
+
if (suit1 !== suit2 || suit2 !== suit3)
|
|
38
|
+
continue;
|
|
39
|
+
// 字牌が含まれていないかチェック(念のため)
|
|
40
|
+
if (suit1 > 2)
|
|
41
|
+
continue;
|
|
42
|
+
// 数値(インデックス)を取得
|
|
43
|
+
const num1 = firstHai1 % 9;
|
|
44
|
+
const num2 = firstHai2 % 9;
|
|
45
|
+
const num3 = firstHai3 % 9;
|
|
46
|
+
// 0 (1-2-3), 3 (4-5-6), 6 (7-8-9) が揃っていれば成立
|
|
47
|
+
const nums = new Set([num1, num2, num3]);
|
|
48
|
+
if (nums.has(0) && nums.has(3) && nums.has(6)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
56
|
+
export const ikkitsuukanDefinition = createYakuDefinition(IKKITSUUKAN_YAKU, checkIkkitsuukan);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { YakuDefinition } from "../../types";
|
|
2
|
+
export * from "./tanyao";
|
|
3
|
+
export * from "./pinfu";
|
|
4
|
+
export * from "./iipeiko";
|
|
5
|
+
export * from "./ryanpeiko";
|
|
6
|
+
export * from "./sanankou";
|
|
7
|
+
export * from "./suuankou";
|
|
8
|
+
export * from "./sankantsu";
|
|
9
|
+
export * from "./suukantsu";
|
|
10
|
+
export * from "./toitoi";
|
|
11
|
+
export * from "./chiitoitsu";
|
|
12
|
+
export * from "./honchan";
|
|
13
|
+
export * from "./junchan";
|
|
14
|
+
export * from "./honroutou";
|
|
15
|
+
export * from "./chinroutou";
|
|
16
|
+
export * from "./shousangen";
|
|
17
|
+
export * from "./daisangen";
|
|
18
|
+
export * from "./tsuuiisou";
|
|
19
|
+
export * from "./ryuuiisou";
|
|
20
|
+
export * from "./shousuushii";
|
|
21
|
+
export * from "./daisuushii";
|
|
22
|
+
export * from "./chuuren-poutou";
|
|
23
|
+
export * from "./kokushi";
|
|
24
|
+
export * from "./sanshoku-doujun";
|
|
25
|
+
export * from "./sanshoku-doukou";
|
|
26
|
+
export * from "./ikkitsuukan";
|
|
27
|
+
export * from "./honitsu";
|
|
28
|
+
export * from "./chinitsu";
|
|
29
|
+
export * from "./yakuhai";
|
|
30
|
+
export declare const ALL_YAKU_DEFINITIONS: YakuDefinition[];
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { tanyaoDefinition } from "./tanyao";
|
|
2
|
+
import { pinfuDefinition } from "./pinfu";
|
|
3
|
+
import { iipeikoDefinition } from "./iipeiko";
|
|
4
|
+
import { ryanpeikouDefinition } from "./ryanpeiko";
|
|
5
|
+
import { sanankouDefinition } from "./sanankou";
|
|
6
|
+
import { suuankouDefinition } from "./suuankou";
|
|
7
|
+
import { sankantsuDefinition } from "./sankantsu";
|
|
8
|
+
import { suukantsuDefinition } from "./suukantsu";
|
|
9
|
+
import { toitoiDefinition } from "./toitoi";
|
|
10
|
+
import { chiitoitsuDefinition } from "./chiitoitsu";
|
|
11
|
+
import { honchanDefinition } from "./honchan";
|
|
12
|
+
import { junchanDefinition } from "./junchan";
|
|
13
|
+
import { honroutouDefinition } from "./honroutou";
|
|
14
|
+
import { chinroutouDefinition } from "./chinroutou";
|
|
15
|
+
import { shousangenDefinition } from "./shousangen";
|
|
16
|
+
import { daisangenDefinition } from "./daisangen";
|
|
17
|
+
import { tsuuiisouDefinition } from "./tsuuiisou";
|
|
18
|
+
import { ryuuiisouDefinition } from "./ryuuiisou";
|
|
19
|
+
import { shousuushiiDefinition } from "./shousuushii";
|
|
20
|
+
import { daisuushiiDefinition } from "./daisuushii";
|
|
21
|
+
import { chuurenPoutouDefinition } from "./chuuren-poutou";
|
|
22
|
+
import { kokushiDefinition } from "./kokushi";
|
|
23
|
+
import { sanshokuDoujunDefinition } from "./sanshoku-doujun";
|
|
24
|
+
import { sanshokuDoukouDefinition } from "./sanshoku-doukou";
|
|
25
|
+
import { ikkitsuukanDefinition } from "./ikkitsuukan";
|
|
26
|
+
import { honitsuDefinition } from "./honitsu";
|
|
27
|
+
import { chinitsuDefinition } from "./chinitsu";
|
|
28
|
+
import { hakuDefinition, hatsuDefinition, chunDefinition } from "./yakuhai";
|
|
29
|
+
import { menzenTsumoDefinition } from "./menzen-tsumo";
|
|
30
|
+
export * from "./tanyao";
|
|
31
|
+
export * from "./pinfu";
|
|
32
|
+
export * from "./iipeiko";
|
|
33
|
+
export * from "./ryanpeiko";
|
|
34
|
+
export * from "./sanankou";
|
|
35
|
+
export * from "./suuankou";
|
|
36
|
+
export * from "./sankantsu";
|
|
37
|
+
export * from "./suukantsu";
|
|
38
|
+
export * from "./toitoi";
|
|
39
|
+
export * from "./chiitoitsu";
|
|
40
|
+
export * from "./honchan";
|
|
41
|
+
export * from "./junchan";
|
|
42
|
+
export * from "./honroutou";
|
|
43
|
+
export * from "./chinroutou";
|
|
44
|
+
export * from "./shousangen";
|
|
45
|
+
export * from "./daisangen";
|
|
46
|
+
export * from "./tsuuiisou";
|
|
47
|
+
export * from "./ryuuiisou";
|
|
48
|
+
export * from "./shousuushii";
|
|
49
|
+
export * from "./daisuushii";
|
|
50
|
+
export * from "./chuuren-poutou";
|
|
51
|
+
export * from "./kokushi";
|
|
52
|
+
export * from "./sanshoku-doujun";
|
|
53
|
+
export * from "./sanshoku-doukou";
|
|
54
|
+
export * from "./ikkitsuukan";
|
|
55
|
+
export * from "./honitsu";
|
|
56
|
+
export * from "./chinitsu";
|
|
57
|
+
export * from "./yakuhai";
|
|
58
|
+
export const ALL_YAKU_DEFINITIONS = [
|
|
59
|
+
tanyaoDefinition,
|
|
60
|
+
pinfuDefinition,
|
|
61
|
+
iipeikoDefinition,
|
|
62
|
+
ryanpeikouDefinition,
|
|
63
|
+
sanankouDefinition,
|
|
64
|
+
suuankouDefinition,
|
|
65
|
+
sankantsuDefinition,
|
|
66
|
+
suukantsuDefinition,
|
|
67
|
+
toitoiDefinition,
|
|
68
|
+
chiitoitsuDefinition,
|
|
69
|
+
honchanDefinition,
|
|
70
|
+
junchanDefinition,
|
|
71
|
+
honroutouDefinition,
|
|
72
|
+
chinroutouDefinition,
|
|
73
|
+
shousangenDefinition,
|
|
74
|
+
daisangenDefinition,
|
|
75
|
+
tsuuiisouDefinition,
|
|
76
|
+
ryuuiisouDefinition,
|
|
77
|
+
shousuushiiDefinition,
|
|
78
|
+
daisuushiiDefinition,
|
|
79
|
+
chuurenPoutouDefinition,
|
|
80
|
+
kokushiDefinition,
|
|
81
|
+
sanshokuDoujunDefinition,
|
|
82
|
+
sanshokuDoukouDefinition,
|
|
83
|
+
ikkitsuukanDefinition,
|
|
84
|
+
honitsuDefinition,
|
|
85
|
+
chinitsuDefinition,
|
|
86
|
+
hakuDefinition,
|
|
87
|
+
hatsuDefinition,
|
|
88
|
+
chunDefinition,
|
|
89
|
+
menzenTsumoDefinition,
|
|
90
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isSuupai, isYaochu } from "../../../../core/hai";
|
|
2
|
+
import { createYakuDefinition } from "../../factory";
|
|
3
|
+
const JUNCHAN_YAKU = {
|
|
4
|
+
name: "Junchan",
|
|
5
|
+
han: {
|
|
6
|
+
open: 2,
|
|
7
|
+
closed: 3,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const checkJunchan = (hand) => {
|
|
11
|
+
if (hand.type !== "Mentsu")
|
|
12
|
+
return false;
|
|
13
|
+
const allBlocks = [hand.jantou, ...hand.fourMentsu];
|
|
14
|
+
// 1. 全ての面子・雀頭に老頭牌(1・9)が含まれること
|
|
15
|
+
// isYaochu(k) && isSuupai(k) で「字牌を除く么九牌」=「老頭牌」となる
|
|
16
|
+
const allHasRoutou = allBlocks.every((block) => block.hais.some((k) => isYaochu(k) && isSuupai(k)));
|
|
17
|
+
if (!allHasRoutou)
|
|
18
|
+
return false;
|
|
19
|
+
// 2. 少なくとも1つの順子が含まれること(清老頭の除外)
|
|
20
|
+
const hasShuntsu = hand.fourMentsu.some((m) => m.type === "Shuntsu");
|
|
21
|
+
if (!hasShuntsu)
|
|
22
|
+
return false;
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
export const junchanDefinition = createYakuDefinition(JUNCHAN_YAKU, checkJunchan);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const checkKokushi = (hand) => {
|
|
3
|
+
return hand.type === "Kokushi";
|
|
4
|
+
};
|
|
5
|
+
const KOKUSHI_HAN = {
|
|
6
|
+
closed: 13,
|
|
7
|
+
open: 0,
|
|
8
|
+
};
|
|
9
|
+
export const kokushiDefinition = createYakuDefinition({
|
|
10
|
+
name: "KokushiMusou",
|
|
11
|
+
han: KOKUSHI_HAN,
|
|
12
|
+
}, checkKokushi);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const definition = {
|
|
3
|
+
name: "MenzenTsumo",
|
|
4
|
+
han: { open: 0, closed: 1 },
|
|
5
|
+
};
|
|
6
|
+
export const menzenTsumoDefinition = createYakuDefinition(definition, (hand, context) => {
|
|
7
|
+
return context.isMenzen && !!context.isTsumo;
|
|
8
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HaiKind } from "../../../../types";
|
|
2
|
+
import { classifyMachi } from "../../../../core/machi";
|
|
3
|
+
import { MahjongArgumentError } from "../../../../errors";
|
|
4
|
+
import { createYakuDefinition } from "../../factory";
|
|
5
|
+
const PINFU_YAKU = {
|
|
6
|
+
name: "Pinfu",
|
|
7
|
+
han: {
|
|
8
|
+
open: 0,
|
|
9
|
+
closed: 1,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
const checkPinfu = (hand, context) => {
|
|
13
|
+
// 1. 門前であること
|
|
14
|
+
if (!context.isMenzen)
|
|
15
|
+
return false;
|
|
16
|
+
if (hand.type !== "Mentsu")
|
|
17
|
+
return false;
|
|
18
|
+
// 2. 雀頭が役牌でないこと
|
|
19
|
+
// 三元牌、場風、自風が含まれていないことを確認
|
|
20
|
+
const jantouKind = hand.jantou.hais[0];
|
|
21
|
+
if (context.bakaze === undefined || context.jikaze === undefined) {
|
|
22
|
+
throw new MahjongArgumentError("Pinfu check requires bakaze and jikaze in context");
|
|
23
|
+
}
|
|
24
|
+
const yakuhaiList = [
|
|
25
|
+
HaiKind.Haku,
|
|
26
|
+
HaiKind.Hatsu,
|
|
27
|
+
HaiKind.Chun,
|
|
28
|
+
context.bakaze,
|
|
29
|
+
context.jikaze,
|
|
30
|
+
];
|
|
31
|
+
if (yakuhaiList.includes(jantouKind))
|
|
32
|
+
return false;
|
|
33
|
+
// 3. 全て順子であること
|
|
34
|
+
if (!hand.fourMentsu.every((m) => m.type === "Shuntsu"))
|
|
35
|
+
return false;
|
|
36
|
+
// 4. 両面待ちであること
|
|
37
|
+
const waitType = classifyMachi(hand, context.agariHai);
|
|
38
|
+
return waitType === "Ryanmen";
|
|
39
|
+
};
|
|
40
|
+
export const pinfuDefinition = createYakuDefinition(PINFU_YAKU, checkPinfu);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const RYANPEIKOU_YAKU = {
|
|
3
|
+
name: "Ryanpeikou",
|
|
4
|
+
han: {
|
|
5
|
+
open: 0, // 門前限定
|
|
6
|
+
closed: 3,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkRyanpeikou = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const shuntsuList = hand.fourMentsu.filter((mentsu) => mentsu.type === "Shuntsu");
|
|
14
|
+
// 順子が4つなければ二盃口はあり得ない
|
|
15
|
+
if (shuntsuList.length < 4) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// 各順子の出現数をカウントする
|
|
19
|
+
// Shuntsuは [T, T, T] で、先頭の牌IDが同じなら同じ順子とみなす
|
|
20
|
+
const shuntsuCounts = new Map();
|
|
21
|
+
for (const shuntsu of shuntsuList) {
|
|
22
|
+
const key = shuntsu.hais[0];
|
|
23
|
+
const currentCount = shuntsuCounts.get(key) ?? 0;
|
|
24
|
+
shuntsuCounts.set(key, currentCount + 1);
|
|
25
|
+
}
|
|
26
|
+
let pairCount = 0;
|
|
27
|
+
for (const count of shuntsuCounts.values()) {
|
|
28
|
+
// 同じ順子が2つで1ペア。4つなら2ペア。
|
|
29
|
+
pairCount += Math.floor(count / 2);
|
|
30
|
+
}
|
|
31
|
+
return pairCount >= 2;
|
|
32
|
+
};
|
|
33
|
+
export const ryanpeikouDefinition = createYakuDefinition(RYANPEIKOU_YAKU, checkRyanpeikou);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
import { HaiKind } from "../../../../types";
|
|
3
|
+
const RYUUIISOU_YAKU = {
|
|
4
|
+
name: "Ryuuiisou",
|
|
5
|
+
han: {
|
|
6
|
+
open: 13,
|
|
7
|
+
closed: 13,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const GREEN_TILES = new Set([
|
|
11
|
+
HaiKind.SouZu2,
|
|
12
|
+
HaiKind.SouZu3,
|
|
13
|
+
HaiKind.SouZu4,
|
|
14
|
+
HaiKind.SouZu6,
|
|
15
|
+
HaiKind.SouZu8,
|
|
16
|
+
HaiKind.Hatsu,
|
|
17
|
+
]);
|
|
18
|
+
const isGreen = (id) => {
|
|
19
|
+
return GREEN_TILES.has(id);
|
|
20
|
+
};
|
|
21
|
+
const checkRyuuiisou = (hand) => {
|
|
22
|
+
const allHais = [];
|
|
23
|
+
if (hand.type === "Mentsu") {
|
|
24
|
+
// 面子手の場合
|
|
25
|
+
for (const mentsu of hand.fourMentsu) {
|
|
26
|
+
allHais.push(...mentsu.hais);
|
|
27
|
+
}
|
|
28
|
+
allHais.push(...hand.jantou.hais);
|
|
29
|
+
}
|
|
30
|
+
else if (hand.type === "Chiitoitsu") {
|
|
31
|
+
// 七対子の場合
|
|
32
|
+
for (const pair of hand.pairs) {
|
|
33
|
+
allHais.push(...pair.hais);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// 国士無双など
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
// 全ての牌が緑色牌であれば成立
|
|
41
|
+
return allHais.every(isGreen);
|
|
42
|
+
};
|
|
43
|
+
export const ryuuiisouDefinition = createYakuDefinition(RYUUIISOU_YAKU, checkRyuuiisou);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const SANANKOU_YAKU = {
|
|
3
|
+
name: "Sanankou",
|
|
4
|
+
han: {
|
|
5
|
+
open: 2,
|
|
6
|
+
closed: 2,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkSanankou = (hand, context) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// 1. 刻子・槓子を抽出
|
|
14
|
+
const triplets = hand.fourMentsu.filter((m) => m.type === "Koutsu" || m.type === "Kantsu");
|
|
15
|
+
let ankouCount = 0;
|
|
16
|
+
for (const triplet of triplets) {
|
|
17
|
+
// 副露している刻子は暗刻ではない
|
|
18
|
+
if (triplet.furo)
|
|
19
|
+
continue;
|
|
20
|
+
const isAgariHaiInTriplet = triplet.hais.includes(context.agariHai);
|
|
21
|
+
// ロン和了の場合、和了牌を含む刻子は明刻扱いとなる(シャボ待ちの場合)。
|
|
22
|
+
// ただし、単騎待ちの場合は暗刻扱いとなる。
|
|
23
|
+
// 単騎待ちかどうかの判定: 雀頭の牌が和了牌と同じかどうか
|
|
24
|
+
const isTanki = hand.jantou.hais[0] === context.agariHai;
|
|
25
|
+
if (context.isTsumo) {
|
|
26
|
+
// ツモなら、副露していなければ全て暗刻
|
|
27
|
+
ankouCount++;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// ロン和了の場合
|
|
31
|
+
if (isAgariHaiInTriplet) {
|
|
32
|
+
// 和了牌を含む刻子の場合
|
|
33
|
+
if (isTanki) {
|
|
34
|
+
// 単騎待ちなら暗刻
|
|
35
|
+
ankouCount++;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// シャボ(等の)待ちでロンした場合は明刻扱いなのでカウントしない
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// 和了牌を含まない刻子は暗刻
|
|
43
|
+
ankouCount++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return ankouCount >= 3;
|
|
48
|
+
};
|
|
49
|
+
export const sanankouDefinition = createYakuDefinition(SANANKOU_YAKU, checkSanankou);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const SANKANTSU_YAKU = {
|
|
3
|
+
name: "Sankantsu",
|
|
4
|
+
han: {
|
|
5
|
+
open: 2,
|
|
6
|
+
closed: 2,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkSankantsu = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// 1. 槓子を抽出
|
|
14
|
+
const kantsuList = hand.fourMentsu.filter((m) => m.type === "Kantsu");
|
|
15
|
+
// 2. 槓子が3つ以上あれば成立
|
|
16
|
+
return kantsuList.length >= 3;
|
|
17
|
+
};
|
|
18
|
+
export const sankantsuDefinition = createYakuDefinition(SANKANTSU_YAKU, checkSankantsu);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const SANSHOKU_DOUJUN_YAKU = {
|
|
3
|
+
name: "SanshokuDoujun",
|
|
4
|
+
han: {
|
|
5
|
+
open: 1,
|
|
6
|
+
closed: 2,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkSanshokuDoujun = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const shuntsuList = hand.fourMentsu.filter((mentsu) => mentsu.type === "Shuntsu");
|
|
14
|
+
if (shuntsuList.length < 3) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// 順子リストから3つの組み合わせを全てチェックし、三色同順の条件を満たすものを探す
|
|
18
|
+
// 条件:
|
|
19
|
+
// 1. 3つの順子がそれぞれ異なる色(萬子、筒子、索子)であること
|
|
20
|
+
// 2. 3つの順子の構成数字が同じであること(例: 123m, 123p, 123s)
|
|
21
|
+
// ヘルパーロジック:
|
|
22
|
+
// HaiKindId の範囲: 0-8 (ManZu), 9-17 (PinZu), 18-26 (SouZu)
|
|
23
|
+
// 9で割った商が色(0, 1, 2)、余りが数値(0-8)を表す
|
|
24
|
+
for (let i = 0; i < shuntsuList.length; i++) {
|
|
25
|
+
for (let j = i + 1; j < shuntsuList.length; j++) {
|
|
26
|
+
for (let k = j + 1; k < shuntsuList.length; k++) {
|
|
27
|
+
const s1 = shuntsuList[i];
|
|
28
|
+
const s2 = shuntsuList[j];
|
|
29
|
+
const s3 = shuntsuList[k];
|
|
30
|
+
if (!s1 || !s2 || !s3)
|
|
31
|
+
continue;
|
|
32
|
+
const firstHai1 = s1.hais[0];
|
|
33
|
+
const firstHai2 = s2.hais[0];
|
|
34
|
+
const firstHai3 = s3.hais[0];
|
|
35
|
+
const suit1 = Math.floor(firstHai1 / 9);
|
|
36
|
+
const suit2 = Math.floor(firstHai2 / 9);
|
|
37
|
+
const suit3 = Math.floor(firstHai3 / 9);
|
|
38
|
+
// 異なる色(0, 1, 2)でなければならない
|
|
39
|
+
const suits = new Set([suit1, suit2, suit3]);
|
|
40
|
+
if (suits.size !== 3)
|
|
41
|
+
continue;
|
|
42
|
+
// 全て数牌(0, 1, 2)でなければならない
|
|
43
|
+
// ※通常、Shuntsuに字牌は含まれないが、念のためチェック
|
|
44
|
+
if (suit1 > 2 || suit2 > 2 || suit3 > 2)
|
|
45
|
+
continue;
|
|
46
|
+
// 数値(インデックス)が一致するかチェック
|
|
47
|
+
const num1 = firstHai1 % 9;
|
|
48
|
+
const num2 = firstHai2 % 9;
|
|
49
|
+
const num3 = firstHai3 % 9;
|
|
50
|
+
if (num1 === num2 && num2 === num3) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
};
|
|
58
|
+
export const sanshokuDoujunDefinition = createYakuDefinition(SANSHOKU_DOUJUN_YAKU, checkSanshokuDoujun);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const SANSHOKU_DOUKOU_YAKU = {
|
|
3
|
+
name: "SanshokuDoukou",
|
|
4
|
+
han: {
|
|
5
|
+
open: 2,
|
|
6
|
+
closed: 2,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkSanshokuDoukou = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// 1. 刻子・槓子を抽出
|
|
14
|
+
const triplets = hand.fourMentsu.filter((m) => m.type === "Koutsu" || m.type === "Kantsu");
|
|
15
|
+
if (triplets.length < 3) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// 2. 刻子の組み合わせ(3つ)をチェック
|
|
19
|
+
for (let i = 0; i < triplets.length; i++) {
|
|
20
|
+
for (let j = i + 1; j < triplets.length; j++) {
|
|
21
|
+
for (let k = j + 1; k < triplets.length; k++) {
|
|
22
|
+
const t1 = triplets[i];
|
|
23
|
+
const t2 = triplets[j];
|
|
24
|
+
const t3 = triplets[k];
|
|
25
|
+
if (!t1 || !t2 || !t3)
|
|
26
|
+
continue;
|
|
27
|
+
const id1 = t1.hais[0];
|
|
28
|
+
const id2 = t2.hais[0];
|
|
29
|
+
const id3 = t3.hais[0];
|
|
30
|
+
// 字牌が含まれていたら対象外 (字牌ID >= 27)
|
|
31
|
+
if (id1 >= 27 || id2 >= 27 || id3 >= 27)
|
|
32
|
+
continue;
|
|
33
|
+
const suit1 = Math.floor(id1 / 9);
|
|
34
|
+
const suit2 = Math.floor(id2 / 9);
|
|
35
|
+
const suit3 = Math.floor(id3 / 9);
|
|
36
|
+
// ※ 0:萬子, 1:筒子, 2:索子
|
|
37
|
+
const suits = new Set([suit1, suit2, suit3]);
|
|
38
|
+
// 異なる3色でなければならない
|
|
39
|
+
if (suits.size !== 3)
|
|
40
|
+
continue;
|
|
41
|
+
const num1 = id1 % 9;
|
|
42
|
+
const num2 = id2 % 9;
|
|
43
|
+
const num3 = id3 % 9;
|
|
44
|
+
// 同じ数字でなければならない
|
|
45
|
+
if (num1 === num2 && num2 === num3) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
export const sanshokuDoukouDefinition = createYakuDefinition(SANSHOKU_DOUKOU_YAKU, checkSanshokuDoukou);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
import { HaiKind } from "../../../../types";
|
|
3
|
+
const SHOUSANGEN_YAKU = {
|
|
4
|
+
name: "Shousangen",
|
|
5
|
+
han: {
|
|
6
|
+
open: 2,
|
|
7
|
+
closed: 2,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const checkShousangen = (hand) => {
|
|
11
|
+
if (hand.type !== "Mentsu") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const sangenpai = [HaiKind.Haku, HaiKind.Hatsu, HaiKind.Chun];
|
|
15
|
+
// 1. 三元牌の刻子・槓子をカウント
|
|
16
|
+
let sangenKoutsuCount = 0;
|
|
17
|
+
const triplets = hand.fourMentsu.filter((m) => m.type === "Koutsu" || m.type === "Kantsu");
|
|
18
|
+
for (const triplet of triplets) {
|
|
19
|
+
if (sangenpai.includes(triplet.hais[0])) {
|
|
20
|
+
sangenKoutsuCount++;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 2. 三元牌の雀頭があるかチェック
|
|
24
|
+
const isSangenJantou = sangenpai.includes(hand.jantou.hais[0]);
|
|
25
|
+
// 小三元の条件: 三元牌の刻子が2つ かつ 三元牌の雀頭が1つ
|
|
26
|
+
return sangenKoutsuCount === 2 && isSangenJantou;
|
|
27
|
+
};
|
|
28
|
+
export const shousangenDefinition = createYakuDefinition(SHOUSANGEN_YAKU, checkShousangen);
|