@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,69 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
const CHUUREN_POUTOU_YAKU = {
|
|
3
|
+
name: "ChuurenPoutou",
|
|
4
|
+
han: {
|
|
5
|
+
open: 0, // 門前限定
|
|
6
|
+
closed: 13,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
const checkChuurenPoutou = (hand, context) => {
|
|
10
|
+
// 1. 門前でなければならない
|
|
11
|
+
if (!context.isMenzen) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// 構造は Mentsu 手のみ(基本的には)
|
|
15
|
+
// 構造解析結果がどうあれ、元の手牌構成が九蓮宝燈の形かどうかを確認する
|
|
16
|
+
const allHais = [];
|
|
17
|
+
if (hand.type === "Mentsu") {
|
|
18
|
+
// 面子手の場合
|
|
19
|
+
for (const mentsu of hand.fourMentsu) {
|
|
20
|
+
allHais.push(...mentsu.hais);
|
|
21
|
+
}
|
|
22
|
+
allHais.push(...hand.jantou.hais);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// 九蓮宝燈は通常、面子手の特殊形として扱われることが多いが、
|
|
26
|
+
// 構造解析器が Mentsu として解釈できない場合も考慮すべきか?
|
|
27
|
+
// 一旦 Mentsu 型として解釈されていることを前提とする
|
|
28
|
+
// (九蓮宝燈は 111+234+567+8999+α のように分解可能なので Mentsu になるはず)
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// 2. 混一色チェック(全て同じ色、字牌なし)
|
|
32
|
+
if (allHais.length === 0)
|
|
33
|
+
return false;
|
|
34
|
+
const firstHai = allHais[0];
|
|
35
|
+
if (firstHai === undefined)
|
|
36
|
+
return false;
|
|
37
|
+
// 字牌が含まれていたらNG
|
|
38
|
+
if (firstHai >= 27)
|
|
39
|
+
return false;
|
|
40
|
+
const suit = Math.floor(firstHai / 9); // 0, 1, 2
|
|
41
|
+
for (const hai of allHais) {
|
|
42
|
+
if (hai >= 27)
|
|
43
|
+
return false; // 字牌混入
|
|
44
|
+
if (Math.floor(hai / 9) !== suit)
|
|
45
|
+
return false; // 色混在
|
|
46
|
+
}
|
|
47
|
+
// 3. 数牌のカウントチェック
|
|
48
|
+
// 1が3枚以上, 9が3枚以上, 2-8が1枚以上
|
|
49
|
+
const counts = Array(9).fill(0);
|
|
50
|
+
for (const hai of allHais) {
|
|
51
|
+
const num = hai % 9; // 0-8
|
|
52
|
+
counts[num]++;
|
|
53
|
+
}
|
|
54
|
+
// 1 (index 0) >= 3
|
|
55
|
+
if (counts[0] < 3)
|
|
56
|
+
return false;
|
|
57
|
+
// 9 (index 8) >= 3
|
|
58
|
+
if (counts[8] < 3)
|
|
59
|
+
return false;
|
|
60
|
+
// 2-8 (index 1-7) >= 1
|
|
61
|
+
for (let i = 1; i <= 7; i++) {
|
|
62
|
+
if (counts[i] < 1)
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// 合計14枚で上記を満たしていれば、必ず九蓮宝燈の形になる
|
|
66
|
+
// (3+3+7 = 13枚が必須パーツで、残り1枚は何でもよいため)
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
export const chuurenPoutouDefinition = createYakuDefinition(CHUUREN_POUTOU_YAKU, checkChuurenPoutou);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
import { HaiKind } from "../../../../types";
|
|
3
|
+
const DAISANGEN_YAKU = {
|
|
4
|
+
name: "Daisangen",
|
|
5
|
+
han: {
|
|
6
|
+
open: 13,
|
|
7
|
+
closed: 13,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const checkDaisangen = (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
|
+
// 大三元の条件: 三元牌の刻子が3つ全てあること
|
|
24
|
+
return sangenKoutsuCount === 3;
|
|
25
|
+
};
|
|
26
|
+
export const daisangenDefinition = createYakuDefinition(DAISANGEN_YAKU, checkDaisangen);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createYakuDefinition } from "../../factory";
|
|
2
|
+
import { HaiKind } from "../../../../types";
|
|
3
|
+
const DAISUUSHII_YAKU = {
|
|
4
|
+
name: "Daisuushii",
|
|
5
|
+
han: {
|
|
6
|
+
// TODO: ダブル役満(26翻)とするかはルールによるため、一旦通常の役満として実装
|
|
7
|
+
open: 13,
|
|
8
|
+
closed: 13,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const checkDaisuushii = (hand) => {
|
|
12
|
+
if (hand.type !== "Mentsu") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const windTiles = [
|
|
16
|
+
HaiKind.Ton,
|
|
17
|
+
HaiKind.Nan,
|
|
18
|
+
HaiKind.Sha,
|
|
19
|
+
HaiKind.Pei,
|
|
20
|
+
];
|
|
21
|
+
// 1. 風牌の刻子・槓子をカウント
|
|
22
|
+
let windKoutsuCount = 0;
|
|
23
|
+
const triplets = hand.fourMentsu.filter((m) => m.type === "Koutsu" || m.type === "Kantsu");
|
|
24
|
+
for (const triplet of triplets) {
|
|
25
|
+
if (windTiles.includes(triplet.hais[0])) {
|
|
26
|
+
windKoutsuCount++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// 大四喜の条件: 風牌の刻子が4つ全てあること
|
|
30
|
+
return windKoutsuCount === 4;
|
|
31
|
+
};
|
|
32
|
+
export const daisuushiiDefinition = createYakuDefinition(DAISUUSHII_YAKU, checkDaisuushii);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isYaochu, kindIdToHaiType } from "../../../../core/hai";
|
|
2
|
+
import { HaiType } from "../../../../types";
|
|
3
|
+
import { createYakuDefinition } from "../../factory";
|
|
4
|
+
const HONCHAN_YAKU = {
|
|
5
|
+
name: "Honchan",
|
|
6
|
+
han: {
|
|
7
|
+
open: 1,
|
|
8
|
+
closed: 2,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const checkHonchan = (hand) => {
|
|
12
|
+
if (hand.type !== "Mentsu")
|
|
13
|
+
return false;
|
|
14
|
+
const allBlocks = [hand.jantou, ...hand.fourMentsu];
|
|
15
|
+
// 1. 全ての面子・雀頭に么九牌(1・9・字牌)が含まれること
|
|
16
|
+
const allHasYaochu = allBlocks.every((block) => block.hais.some((k) => isYaochu(k)));
|
|
17
|
+
if (!allHasYaochu)
|
|
18
|
+
return false;
|
|
19
|
+
// 2. 少なくとも1つの順子が含まれること(混老頭の除外)
|
|
20
|
+
const hasShuntsu = hand.fourMentsu.some((m) => m.type === "Shuntsu");
|
|
21
|
+
if (!hasShuntsu)
|
|
22
|
+
return false;
|
|
23
|
+
// 3. 少なくとも1つの字牌が含まれること(純全帯幺九の除外)
|
|
24
|
+
const hasJihai = allBlocks.some((block) => block.hais.some((k) => kindIdToHaiType(k) === HaiType.Jihai));
|
|
25
|
+
if (!hasJihai)
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
export const honchanDefinition = createYakuDefinition(HONCHAN_YAKU, checkHonchan);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isSuupai, kindIdToHaiType } from "../../../../core/hai";
|
|
2
|
+
import { HaiType } from "../../../../types";
|
|
3
|
+
import { createYakuDefinition } from "../../factory";
|
|
4
|
+
const HONITSU_YAKU = {
|
|
5
|
+
name: "Honitsu",
|
|
6
|
+
han: {
|
|
7
|
+
open: 2,
|
|
8
|
+
closed: 3,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const checkHonitsu = (hand) => {
|
|
12
|
+
let blocks;
|
|
13
|
+
if (hand.type === "Mentsu") {
|
|
14
|
+
blocks = [hand.jantou, ...hand.fourMentsu];
|
|
15
|
+
}
|
|
16
|
+
else if (hand.type === "Chiitoitsu") {
|
|
17
|
+
blocks = hand.pairs;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
// ブロック内の全ての牌をフラットな配列にする
|
|
23
|
+
const allHais = blocks.flatMap((b) => b.hais);
|
|
24
|
+
// 1. 字牌が少なくとも1つ含まれること(清一色の除外)
|
|
25
|
+
const hasJihai = allHais.some((k) => kindIdToHaiType(k) === HaiType.Jihai);
|
|
26
|
+
if (!hasJihai)
|
|
27
|
+
return false;
|
|
28
|
+
// 2. 数牌が全て同じ種類であること
|
|
29
|
+
const suupais = allHais.filter((k) => isSuupai(k));
|
|
30
|
+
// 数牌が含まれていない場合は字一色(または不成立)なので、ホンイツではない
|
|
31
|
+
if (suupais.length === 0)
|
|
32
|
+
return false;
|
|
33
|
+
const firstSuupai = suupais[0];
|
|
34
|
+
if (firstSuupai === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
const firstSuupaiType = kindIdToHaiType(firstSuupai);
|
|
37
|
+
const isAllSameType = suupais.every((k) => kindIdToHaiType(k) === firstSuupaiType);
|
|
38
|
+
return isAllSameType;
|
|
39
|
+
};
|
|
40
|
+
export const honitsuDefinition = createYakuDefinition(HONITSU_YAKU, checkHonitsu);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isYaochu, kindIdToHaiType } from "../../../../core/hai";
|
|
2
|
+
import { HaiType } from "../../../../types";
|
|
3
|
+
import { createYakuDefinition } from "../../factory";
|
|
4
|
+
const HONROUTOU_YAKU = {
|
|
5
|
+
name: "Honroutou",
|
|
6
|
+
han: {
|
|
7
|
+
open: 2,
|
|
8
|
+
closed: 2,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const checkHonroutou = (hand) => {
|
|
12
|
+
let blocks;
|
|
13
|
+
if (hand.type === "Mentsu") {
|
|
14
|
+
blocks = [hand.jantou, ...hand.fourMentsu];
|
|
15
|
+
}
|
|
16
|
+
else if (hand.type === "Chiitoitsu") {
|
|
17
|
+
blocks = hand.pairs;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
// 1. 全ての牌が么九牌(1・9・字牌)であること
|
|
23
|
+
// これにより順子(123など)が含まれる可能性も排除される(2,3は么九牌ではないため)
|
|
24
|
+
const allYaochu = blocks.every((block) => block.hais.every((k) => isYaochu(k)));
|
|
25
|
+
if (!allYaochu)
|
|
26
|
+
return false;
|
|
27
|
+
// 2. 少なくとも1つの字牌が含まれること(清老頭の除外)
|
|
28
|
+
const hasJihai = blocks.some((block) => block.hais.some((k) => kindIdToHaiType(k) === HaiType.Jihai));
|
|
29
|
+
if (!hasJihai)
|
|
30
|
+
return false;
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
export const honroutouDefinition = createYakuDefinition(HONROUTOU_YAKU, checkHonroutou);
|
|
@@ -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 checkIipeikou = (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 iipeikouDefinition = createYakuDefinition(IIPEIKO_YAKU, checkIipeikou);
|
|
@@ -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 "./iipeikou";
|
|
5
|
+
export * from "./ryanpeikou";
|
|
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 { iipeikouDefinition } from "./iipeikou";
|
|
4
|
+
import { ryanpeikouDefinition } from "./ryanpeikou";
|
|
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 "./iipeikou";
|
|
33
|
+
export * from "./ryanpeikou";
|
|
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
|
+
iipeikouDefinition,
|
|
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);
|