@pai-forge/riichi-mahjong 0.2.0 → 0.3.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/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 +42 -0
- package/dist/core/tehai.js +156 -0
- package/dist/errors.d.ts +83 -0
- package/dist/errors.js +117 -0
- package/dist/features/machi/index.d.ts +9 -0
- package/dist/features/machi/index.js +48 -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/score/constants.d.ts +27 -0
- package/dist/features/score/constants.js +30 -0
- package/dist/features/score/index.d.ts +45 -0
- package/dist/features/score/index.js +207 -0
- package/dist/features/score/lib/fu/constants.d.ts +37 -0
- package/dist/features/score/lib/fu/constants.js +45 -0
- package/dist/features/score/lib/fu/index.d.ts +11 -0
- package/dist/features/score/lib/fu/index.js +23 -0
- package/dist/features/score/lib/fu/lib/chiitoitsu.d.ts +5 -0
- package/dist/features/score/lib/fu/lib/chiitoitsu.js +17 -0
- package/dist/features/score/lib/fu/lib/kokushi.d.ts +6 -0
- package/dist/features/score/lib/fu/lib/kokushi.js +18 -0
- package/dist/features/score/lib/fu/lib/mentsu.d.ts +11 -0
- package/dist/features/score/lib/fu/lib/mentsu.js +136 -0
- package/dist/features/score/lib/fu/types.d.ts +56 -0
- package/dist/features/score/lib/fu/types.js +1 -0
- package/dist/features/score/types.d.ts +78 -0
- package/dist/features/score/types.js +22 -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 +21 -0
- package/dist/features/yaku/index.js +59 -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/iipeikou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/iipeikou.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/ryanpeikou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ryanpeikou.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 +21 -0
- package/dist/index.js +53 -0
- package/dist/types.d.ts +290 -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 +3 -2
|
@@ -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);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
import { HaiKind } from "../../../../types";
|
|
3
|
+
const SHOUSUUSHII_YAKU = {
|
|
4
|
+
name: "Shousuushii",
|
|
5
|
+
han: {
|
|
6
|
+
open: 13,
|
|
7
|
+
closed: 13,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const checkShousuushii = (hand) => {
|
|
11
|
+
if (hand.type !== "Mentsu") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const windTiles = [
|
|
15
|
+
HaiKind.Ton,
|
|
16
|
+
HaiKind.Nan,
|
|
17
|
+
HaiKind.Sha,
|
|
18
|
+
HaiKind.Pei,
|
|
19
|
+
];
|
|
20
|
+
// 1. 風牌の刻子・槓子をカウント
|
|
21
|
+
let windKoutsuCount = 0;
|
|
22
|
+
const triplets = hand.fourMentsu.filter((m) => m.type === "Koutsu" || m.type === "Kantsu");
|
|
23
|
+
for (const triplet of triplets) {
|
|
24
|
+
if (windTiles.includes(triplet.hais[0])) {
|
|
25
|
+
windKoutsuCount++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// 2. 風牌の雀頭があるかチェック
|
|
29
|
+
const isWindJantou = windTiles.includes(hand.jantou.hais[0]);
|
|
30
|
+
// 小四喜の条件: 風牌の刻子が3つ かつ 風牌の雀頭が1つ
|
|
31
|
+
// (合計で4種類の風牌が揃うことになる。例: 東東東 南南南 西西西 北北)
|
|
32
|
+
return windKoutsuCount === 3 && isWindJantou;
|
|
33
|
+
};
|
|
34
|
+
export const shousuushiiDefinition = createYakuDefinition(SHOUSUUSHII_YAKU, checkShousuushii);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const SUUANKOU_YAKU = {
|
|
2
|
+
name: "Suuankou",
|
|
3
|
+
han: {
|
|
4
|
+
open: 0, // 門前限定(構造上必然的にそうなるが、定義としても門前)
|
|
5
|
+
closed: 13, // 通常役満(13) または ダブル役満(26)
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
const checkSuuankou = (hand, context) => {
|
|
9
|
+
if (hand.type !== "Mentsu") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
// 1. 刻子・槓子を抽出
|
|
13
|
+
const triplets = hand.fourMentsu.filter((m) => m.type === "Koutsu" || m.type === "Kantsu");
|
|
14
|
+
let ankouCount = 0;
|
|
15
|
+
for (const triplet of triplets) {
|
|
16
|
+
// 副露している刻子は暗刻ではない
|
|
17
|
+
if (triplet.furo)
|
|
18
|
+
continue;
|
|
19
|
+
const isAgariHaiInTriplet = triplet.hais.includes(context.agariHai);
|
|
20
|
+
const isTanki = hand.jantou.hais[0] === context.agariHai;
|
|
21
|
+
if (context.isTsumo) {
|
|
22
|
+
// ツモなら、副露していなければ全て暗刻
|
|
23
|
+
ankouCount++;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// ロン和了の場合
|
|
27
|
+
if (isAgariHaiInTriplet) {
|
|
28
|
+
// 和了牌を含む刻子(シャボ待ちロン)は明刻
|
|
29
|
+
// 単騎待ちロンなら暗刻
|
|
30
|
+
if (isTanki) {
|
|
31
|
+
ankouCount++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// 和了牌を含まない刻子は暗刻
|
|
36
|
+
ankouCount++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return ankouCount === 4;
|
|
41
|
+
};
|
|
42
|
+
// 四暗刻はダブル役満(単騎待ち)判定が必要なため、
|
|
43
|
+
// createYakuDefinitionのデフォルトのgetHansuロジックをオーバーライドするか、
|
|
44
|
+
// このcheck関数内で判定してフラグを渡すなどの工夫が必要。
|
|
45
|
+
// しかし createYakuDefinition は boolean を返す check 関数しか受け取らない。
|
|
46
|
+
// ここでは factory 側を拡張するのではなく、この定義でカスタム実装を行うか、
|
|
47
|
+
// あるいは factory を通さずに直接 YakuDefinition を作る。
|
|
48
|
+
// factoryの改修はスコープ外のため、ここではオブジェクトリテラルで定義を作成する。
|
|
49
|
+
export const suuankouDefinition = {
|
|
50
|
+
yaku: SUUANKOU_YAKU,
|
|
51
|
+
isSatisfied: (hand, context) => checkSuuankou(hand, context),
|
|
52
|
+
getHansu: (hand, context) => {
|
|
53
|
+
if (!checkSuuankou(hand, context))
|
|
54
|
+
return 0;
|
|
55
|
+
// 四暗刻単騎の判定
|
|
56
|
+
const isTanki = hand.type === "Mentsu" && hand.jantou.hais[0] === context.agariHai;
|
|
57
|
+
// 単騎待ちならダブル役満(26)
|
|
58
|
+
if (isTanki) {
|
|
59
|
+
return 26;
|
|
60
|
+
}
|
|
61
|
+
return 13;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const SUUKANTSU_YAKU = {
|
|
3
|
+
name: "Suukantsu",
|
|
4
|
+
han: {
|
|
5
|
+
open: 13,
|
|
6
|
+
closed: 13,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkSuukantsu = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// 1. 槓子を抽出
|
|
14
|
+
const kantsuList = hand.fourMentsu.filter((m) => m.type === "Kantsu");
|
|
15
|
+
// 2. 槓子が4つあれば成立
|
|
16
|
+
return kantsuList.length === 4;
|
|
17
|
+
};
|
|
18
|
+
export const suukantsuDefinition = createYakuDefinition(SUUKANTSU_YAKU, checkSuukantsu);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isYaochu } from "../../../../core/hai";
|
|
2
|
+
import { createYakuDefinition } from "../../factory";
|
|
3
|
+
const TANYAO_YAKU = {
|
|
4
|
+
name: "Tanyao",
|
|
5
|
+
han: {
|
|
6
|
+
open: 1,
|
|
7
|
+
closed: 1,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const checkTanyao = (hand) => {
|
|
11
|
+
if (hand.type !== "Mentsu")
|
|
12
|
+
return false;
|
|
13
|
+
// 雀頭のチェック
|
|
14
|
+
if (isYaochu(hand.jantou.hais[0]))
|
|
15
|
+
return false;
|
|
16
|
+
// 面子のチェック
|
|
17
|
+
for (const mentsu of hand.fourMentsu) {
|
|
18
|
+
if (mentsu.hais.some(isYaochu))
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
23
|
+
export const tanyaoDefinition = createYakuDefinition(TANYAO_YAKU, checkTanyao);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const TOITOI_YAKU = {
|
|
3
|
+
name: "Toitoi",
|
|
4
|
+
han: {
|
|
5
|
+
open: 2,
|
|
6
|
+
closed: 2,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkToitoi = (hand) => {
|
|
10
|
+
if (hand.type !== "Mentsu") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// 全ての面子が刻子(Koutsu)または槓子(Kantsu)であることを確認
|
|
14
|
+
return hand.fourMentsu.every((mentsu) => mentsu.type === "Koutsu" || mentsu.type === "Kantsu");
|
|
15
|
+
};
|
|
16
|
+
export const toitoiDefinition = createYakuDefinition(TOITOI_YAKU, checkToitoi);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
import { HaiKind } from "../../../../types";
|
|
3
|
+
const TSUUIISOU_YAKU = {
|
|
4
|
+
name: "Tsuuiisou",
|
|
5
|
+
han: {
|
|
6
|
+
open: 13,
|
|
7
|
+
closed: 13,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const isJihai = (id) => {
|
|
11
|
+
return id >= HaiKind.Ton && id <= HaiKind.Chun;
|
|
12
|
+
};
|
|
13
|
+
const checkTsuuiisou = (hand) => {
|
|
14
|
+
const allHais = [];
|
|
15
|
+
if (hand.type === "Mentsu") {
|
|
16
|
+
// 面子手の場合
|
|
17
|
+
for (const mentsu of hand.fourMentsu) {
|
|
18
|
+
allHais.push(...mentsu.hais);
|
|
19
|
+
}
|
|
20
|
+
allHais.push(...hand.jantou.hais);
|
|
21
|
+
}
|
|
22
|
+
else if (hand.type === "Chiitoitsu") {
|
|
23
|
+
// 七対子の場合
|
|
24
|
+
for (const pair of hand.pairs) {
|
|
25
|
+
allHais.push(...pair.hais);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// 国士無双など(国士は字一色にはなり得ないが、構造上は考慮)
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// 全ての牌が字牌であれば成立
|
|
33
|
+
return allHais.every(isJihai);
|
|
34
|
+
};
|
|
35
|
+
export const tsuuiisouDefinition = createYakuDefinition(TSUUIISOU_YAKU, checkTsuuiisou);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HaiKind } from "../../../../types";
|
|
2
|
+
import { createYakuDefinition } from "../../factory";
|
|
3
|
+
function createYakuhaiDefinition(name, tile) {
|
|
4
|
+
const HAN_CONFIG = { closed: 1, open: 1 };
|
|
5
|
+
const check = (hand) => {
|
|
6
|
+
if (hand.type !== "Mentsu")
|
|
7
|
+
return false;
|
|
8
|
+
for (const mentsu of hand.fourMentsu) {
|
|
9
|
+
if (mentsu.type === "Koutsu" || mentsu.type === "Kantsu") {
|
|
10
|
+
if (mentsu.hais[0] === tile) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
return createYakuDefinition({ name, han: HAN_CONFIG }, check);
|
|
18
|
+
}
|
|
19
|
+
export const hakuDefinition = createYakuhaiDefinition("Haku", HaiKind.Haku);
|
|
20
|
+
export const hatsuDefinition = createYakuhaiDefinition("Hatsu", HaiKind.Hatsu);
|
|
21
|
+
export const chunDefinition = createYakuhaiDefinition("Chun", HaiKind.Chun);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./definitions";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./definitions";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { countHaiKind } from "../../../../core/tehai";
|
|
2
|
+
/**
|
|
3
|
+
* 手牌を七対子(7つの対子)として構造化する。
|
|
4
|
+
*/
|
|
5
|
+
export function getHouraStructuresForChiitoitsu(tehai) {
|
|
6
|
+
// 七対子は門前のみ(定義によっては鳴きも許容する場合があるが、一般的には門前)
|
|
7
|
+
if (tehai.exposed.length > 0)
|
|
8
|
+
return [];
|
|
9
|
+
const counts = countHaiKind(tehai.closed);
|
|
10
|
+
const pairs = [];
|
|
11
|
+
for (let i = 0; i < 34; i++) {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
|
+
const kind = i;
|
|
14
|
+
const count = counts[kind];
|
|
15
|
+
if (count === 2) {
|
|
16
|
+
pairs.push({ type: "Toitsu", hais: [kind, kind] });
|
|
17
|
+
}
|
|
18
|
+
else if (count === 4) {
|
|
19
|
+
// 4枚使いの七対子を認めるか(ローカルルール次第だが、通常は認めない)
|
|
20
|
+
// ここでは標準的なルールに従い、4枚あっても2対子とはみなさない実装とする
|
|
21
|
+
// ※4枚使い七対子を実装する場合は pairs.push(...) を2回行う
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
else if (count > 0) {
|
|
25
|
+
// 2枚でない牌がある場合は七対子不成立
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (pairs.length !== 7)
|
|
30
|
+
return [];
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
type: "Chiitoitsu",
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
35
|
+
pairs: pairs,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Tehai14 } from "../../../../types";
|
|
2
|
+
import type { HouraStructure } from "../../types";
|
|
3
|
+
export * from "./mentsu-te";
|
|
4
|
+
export * from "./chiitoitsu";
|
|
5
|
+
export * from "./kokushi";
|
|
6
|
+
/**
|
|
7
|
+
* 手牌をすべての可能な和了形に構造化する。
|
|
8
|
+
* 面子手、七対子、国士無双の全ての可能性を探索する。
|
|
9
|
+
*/
|
|
10
|
+
export declare function getHouraStructures(tehai: Tehai14): HouraStructure[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getHouraStructuresForMentsuTe } from "./mentsu-te";
|
|
2
|
+
import { getHouraStructuresForChiitoitsu } from "./chiitoitsu";
|
|
3
|
+
import { getHouraStructuresForKokushi } from "./kokushi";
|
|
4
|
+
export * from "./mentsu-te";
|
|
5
|
+
export * from "./chiitoitsu";
|
|
6
|
+
export * from "./kokushi";
|
|
7
|
+
/**
|
|
8
|
+
* 手牌をすべての可能な和了形に構造化する。
|
|
9
|
+
* 面子手、七対子、国士無双の全ての可能性を探索する。
|
|
10
|
+
*/
|
|
11
|
+
export function getHouraStructures(tehai) {
|
|
12
|
+
return [
|
|
13
|
+
...getHouraStructuresForMentsuTe(tehai),
|
|
14
|
+
...getHouraStructuresForChiitoitsu(tehai),
|
|
15
|
+
...getHouraStructuresForKokushi(tehai),
|
|
16
|
+
];
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { countHaiKind } from "../../../../core/tehai";
|
|
2
|
+
import { isYaochu } from "../../../../core/hai";
|
|
3
|
+
/**
|
|
4
|
+
* 手牌を国士無双(13種の么九牌+雀頭)として構造化する。
|
|
5
|
+
*/
|
|
6
|
+
export function getHouraStructuresForKokushi(tehai) {
|
|
7
|
+
// 国士無双は門前のみ
|
|
8
|
+
if (tehai.exposed.length > 0)
|
|
9
|
+
return [];
|
|
10
|
+
const counts = countHaiKind(tehai.closed);
|
|
11
|
+
const yaochuList = [];
|
|
12
|
+
let jantou;
|
|
13
|
+
for (let i = 0; i < 34; i++) {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
15
|
+
const kind = i;
|
|
16
|
+
const count = counts[kind];
|
|
17
|
+
if (count > 0) {
|
|
18
|
+
if (!isYaochu(kind))
|
|
19
|
+
return []; // 么九牌以外が含まれていれば不成立
|
|
20
|
+
if (count === 1) {
|
|
21
|
+
yaochuList.push(kind);
|
|
22
|
+
}
|
|
23
|
+
else if (count === 2) {
|
|
24
|
+
if (jantou !== undefined)
|
|
25
|
+
return []; // 雀頭が既に存在すれば不成立(複数棋の雀頭候補)
|
|
26
|
+
jantou = kind;
|
|
27
|
+
yaochuList.push(kind);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return []; // 3枚以上あれば不成立
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (yaochuList.length !== 13 || jantou === undefined)
|
|
35
|
+
return [];
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
type: "Kokushi",
|
|
39
|
+
yaochu: yaochuList,
|
|
40
|
+
jantou,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|