@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.
Files changed (134) hide show
  1. package/README.md +58 -0
  2. package/dist/core/dora.d.ts +14 -0
  3. package/dist/core/dora.js +71 -0
  4. package/dist/core/hai.d.ts +30 -0
  5. package/dist/core/hai.js +78 -0
  6. package/dist/core/machi.d.ts +11 -0
  7. package/dist/core/machi.js +58 -0
  8. package/dist/core/mentsu.d.ts +26 -0
  9. package/dist/core/mentsu.js +87 -0
  10. package/dist/core/tehai.d.ts +38 -0
  11. package/dist/core/tehai.js +87 -0
  12. package/dist/errors.d.ts +40 -0
  13. package/dist/errors.js +58 -0
  14. package/dist/features/machi/index.d.ts +9 -0
  15. package/dist/features/machi/index.js +37 -0
  16. package/dist/features/machi/types.d.ts +1 -0
  17. package/dist/features/machi/types.js +1 -0
  18. package/dist/features/parser/index.d.ts +19 -0
  19. package/dist/features/parser/index.js +28 -0
  20. package/dist/features/parser/mspz.d.ts +85 -0
  21. package/dist/features/parser/mspz.js +365 -0
  22. package/dist/features/points/constants.d.ts +27 -0
  23. package/dist/features/points/constants.js +30 -0
  24. package/dist/features/points/index.d.ts +21 -0
  25. package/dist/features/points/index.js +174 -0
  26. package/dist/features/points/lib/fu/constants.d.ts +37 -0
  27. package/dist/features/points/lib/fu/constants.js +45 -0
  28. package/dist/features/points/lib/fu/index.d.ts +11 -0
  29. package/dist/features/points/lib/fu/index.js +23 -0
  30. package/dist/features/points/lib/fu/lib/chiitoitsu.d.ts +5 -0
  31. package/dist/features/points/lib/fu/lib/chiitoitsu.js +17 -0
  32. package/dist/features/points/lib/fu/lib/kokushi.d.ts +6 -0
  33. package/dist/features/points/lib/fu/lib/kokushi.js +18 -0
  34. package/dist/features/points/lib/fu/lib/mentsu.d.ts +11 -0
  35. package/dist/features/points/lib/fu/lib/mentsu.js +122 -0
  36. package/dist/features/points/lib/fu/types.d.ts +55 -0
  37. package/dist/features/points/lib/fu/types.js +1 -0
  38. package/dist/features/points/types.d.ts +27 -0
  39. package/dist/features/points/types.js +1 -0
  40. package/dist/features/shanten/index.d.ts +16 -0
  41. package/dist/features/shanten/index.js +25 -0
  42. package/dist/features/shanten/logic/chiitoitsu.d.ts +8 -0
  43. package/dist/features/shanten/logic/chiitoitsu.js +36 -0
  44. package/dist/features/shanten/logic/kokushi.d.ts +16 -0
  45. package/dist/features/shanten/logic/kokushi.js +48 -0
  46. package/dist/features/shanten/logic/mentsu-te.d.ts +8 -0
  47. package/dist/features/shanten/logic/mentsu-te.js +129 -0
  48. package/dist/features/yaku/factory.d.ts +13 -0
  49. package/dist/features/yaku/factory.js +19 -0
  50. package/dist/features/yaku/index.d.ts +12 -0
  51. package/dist/features/yaku/index.js +62 -0
  52. package/dist/features/yaku/lib/definitions/chiitoitsu.d.ts +2 -0
  53. package/dist/features/yaku/lib/definitions/chiitoitsu.js +12 -0
  54. package/dist/features/yaku/lib/definitions/chinitsu.d.ts +2 -0
  55. package/dist/features/yaku/lib/definitions/chinitsu.js +40 -0
  56. package/dist/features/yaku/lib/definitions/chinroutou.d.ts +2 -0
  57. package/dist/features/yaku/lib/definitions/chinroutou.js +21 -0
  58. package/dist/features/yaku/lib/definitions/chuuren-poutou.d.ts +2 -0
  59. package/dist/features/yaku/lib/definitions/chuuren-poutou.js +69 -0
  60. package/dist/features/yaku/lib/definitions/daisangen.d.ts +2 -0
  61. package/dist/features/yaku/lib/definitions/daisangen.js +26 -0
  62. package/dist/features/yaku/lib/definitions/daisuushii.d.ts +2 -0
  63. package/dist/features/yaku/lib/definitions/daisuushii.js +32 -0
  64. package/dist/features/yaku/lib/definitions/honchan.d.ts +2 -0
  65. package/dist/features/yaku/lib/definitions/honchan.js +29 -0
  66. package/dist/features/yaku/lib/definitions/honitsu.d.ts +2 -0
  67. package/dist/features/yaku/lib/definitions/honitsu.js +40 -0
  68. package/dist/features/yaku/lib/definitions/honroutou.d.ts +2 -0
  69. package/dist/features/yaku/lib/definitions/honroutou.js +33 -0
  70. package/dist/features/yaku/lib/definitions/iipeiko.d.ts +2 -0
  71. package/dist/features/yaku/lib/definitions/iipeiko.js +46 -0
  72. package/dist/features/yaku/lib/definitions/ikkitsuukan.d.ts +2 -0
  73. package/dist/features/yaku/lib/definitions/ikkitsuukan.js +56 -0
  74. package/dist/features/yaku/lib/definitions/index.d.ts +30 -0
  75. package/dist/features/yaku/lib/definitions/index.js +90 -0
  76. package/dist/features/yaku/lib/definitions/junchan.d.ts +2 -0
  77. package/dist/features/yaku/lib/definitions/junchan.js +25 -0
  78. package/dist/features/yaku/lib/definitions/kokushi.d.ts +2 -0
  79. package/dist/features/yaku/lib/definitions/kokushi.js +12 -0
  80. package/dist/features/yaku/lib/definitions/menzen-tsumo.d.ts +2 -0
  81. package/dist/features/yaku/lib/definitions/menzen-tsumo.js +8 -0
  82. package/dist/features/yaku/lib/definitions/pinfu.d.ts +2 -0
  83. package/dist/features/yaku/lib/definitions/pinfu.js +40 -0
  84. package/dist/features/yaku/lib/definitions/ryanpeiko.d.ts +2 -0
  85. package/dist/features/yaku/lib/definitions/ryanpeiko.js +33 -0
  86. package/dist/features/yaku/lib/definitions/ryuuiisou.d.ts +2 -0
  87. package/dist/features/yaku/lib/definitions/ryuuiisou.js +43 -0
  88. package/dist/features/yaku/lib/definitions/sanankou.d.ts +2 -0
  89. package/dist/features/yaku/lib/definitions/sanankou.js +49 -0
  90. package/dist/features/yaku/lib/definitions/sankantsu.d.ts +2 -0
  91. package/dist/features/yaku/lib/definitions/sankantsu.js +18 -0
  92. package/dist/features/yaku/lib/definitions/sanshoku-doujun.d.ts +2 -0
  93. package/dist/features/yaku/lib/definitions/sanshoku-doujun.js +58 -0
  94. package/dist/features/yaku/lib/definitions/sanshoku-doukou.d.ts +2 -0
  95. package/dist/features/yaku/lib/definitions/sanshoku-doukou.js +53 -0
  96. package/dist/features/yaku/lib/definitions/shousangen.d.ts +2 -0
  97. package/dist/features/yaku/lib/definitions/shousangen.js +28 -0
  98. package/dist/features/yaku/lib/definitions/shousuushii.d.ts +2 -0
  99. package/dist/features/yaku/lib/definitions/shousuushii.js +34 -0
  100. package/dist/features/yaku/lib/definitions/suuankou.d.ts +2 -0
  101. package/dist/features/yaku/lib/definitions/suuankou.js +63 -0
  102. package/dist/features/yaku/lib/definitions/suukantsu.d.ts +2 -0
  103. package/dist/features/yaku/lib/definitions/suukantsu.js +18 -0
  104. package/dist/features/yaku/lib/definitions/tanyao.d.ts +2 -0
  105. package/dist/features/yaku/lib/definitions/tanyao.js +23 -0
  106. package/dist/features/yaku/lib/definitions/toitoi.d.ts +2 -0
  107. package/dist/features/yaku/lib/definitions/toitoi.js +16 -0
  108. package/dist/features/yaku/lib/definitions/tsuuiisou.d.ts +2 -0
  109. package/dist/features/yaku/lib/definitions/tsuuiisou.js +35 -0
  110. package/dist/features/yaku/lib/definitions/yakuhai.d.ts +4 -0
  111. package/dist/features/yaku/lib/definitions/yakuhai.js +21 -0
  112. package/dist/features/yaku/lib/index.d.ts +1 -0
  113. package/dist/features/yaku/lib/index.js +1 -0
  114. package/dist/features/yaku/lib/structures/chiitoitsu.d.ts +6 -0
  115. package/dist/features/yaku/lib/structures/chiitoitsu.js +38 -0
  116. package/dist/features/yaku/lib/structures/index.d.ts +10 -0
  117. package/dist/features/yaku/lib/structures/index.js +17 -0
  118. package/dist/features/yaku/lib/structures/kokushi.d.ts +6 -0
  119. package/dist/features/yaku/lib/structures/kokushi.js +43 -0
  120. package/dist/features/yaku/lib/structures/mentsu-te.d.ts +24 -0
  121. package/dist/features/yaku/lib/structures/mentsu-te.js +127 -0
  122. package/dist/features/yaku/types.d.ts +121 -0
  123. package/dist/features/yaku/types.js +1 -0
  124. package/dist/features/yaku/utils.d.ts +19 -0
  125. package/dist/features/yaku/utils.js +34 -0
  126. package/dist/index.d.ts +12 -0
  127. package/dist/index.js +9 -0
  128. package/dist/types.d.ts +280 -0
  129. package/dist/types.js +97 -0
  130. package/dist/utils/assertions.d.ts +22 -0
  131. package/dist/utils/assertions.js +33 -0
  132. package/dist/utils/test-helpers.d.ts +55 -0
  133. package/dist/utils/test-helpers.js +124 -0
  134. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Riichi Mahjong Library
2
+
3
+ リーチ麻雀のロジック(シャンテン数計算、点数計算など)を提供するTypeScriptライブラリです。
4
+
5
+ > [!NOTE]
6
+ > This package is **Pure ESM**. Please use `import` (not `require`) to use this library.
7
+ <br>
8
+ 本パッケージは **Pure ESM** です。利用する際は `require` ではなく `import` を使用してください。
9
+
10
+ ## 前提条件 (Prerequisites)
11
+
12
+ 開発やテスト実行には以下のツールが必要です。
13
+
14
+ - **Node.js**: v20.0.0 以上
15
+ - **Docker**: 受け入れテスト(リファレンス実装との比較)を実行する場合は必要
16
+
17
+ ## セットアップ (Setup)
18
+
19
+ 依存パッケージをインストールします。
20
+
21
+ ```bash
22
+ npm install @pai-forge/riichi-mahjong
23
+ ```
24
+
25
+ ## テストの実行 (Running Tests)
26
+
27
+ ### ユニットテスト (Unit Tests)
28
+
29
+ `src` ディレクトリ内の主要なロジックに対するユニットテストを実行します。
30
+
31
+ ```bash
32
+ npm test
33
+ ```
34
+ または
35
+ ```bash
36
+ npx vitest src
37
+ ```
38
+
39
+ ### 受け入れテスト (Acceptance Tests)
40
+
41
+ Pythonの [`mahjong`](https://github.com/MahjongRepository/mahjong) ライブラリをリファレンス実装として使用し、計算結果の相互検証を行います。
42
+ 実行時に自動的に検証用のDockerイメージ (`riichi-mahjong-verifier`) がビルドされます。
43
+
44
+ ```bash
45
+ npx vitest tests/acceptance
46
+ ```
47
+
48
+ 特定のテストファイルのみを実行する場合:
49
+
50
+ ```bash
51
+ npx vitest tests/acceptance/shanten.test.ts
52
+ ```
53
+
54
+ ## その他のコマンド (Other Commands)
55
+
56
+ - **ビルド**: `npm run build`
57
+ - **リント**: `npm run lint`
58
+ - **フォーマット**: `npm run format`
@@ -0,0 +1,14 @@
1
+ import { type HaiKindId, type Tehai } from "../types";
2
+ /**
3
+ * ドラ表示牌から次の牌(ドラ)を求める
4
+ * @param indicator ドラ表示牌のID (HaiKindId)
5
+ * @returns ドラ牌のID (HaiKindId)
6
+ */
7
+ export declare function getDoraNext(indicator: HaiKindId): HaiKindId;
8
+ /**
9
+ * 手牌に含まれるドラの数を数える
10
+ * @param tehai 手牌
11
+ * @param indicators ドラ表示牌のリスト
12
+ * @returns ドラの総数
13
+ */
14
+ export declare function countDora(tehai: Tehai, indicators: readonly HaiKindId[]): number;
@@ -0,0 +1,71 @@
1
+ import { HaiKind } from "../types";
2
+ import { kindIdToHaiType } from "./hai";
3
+ import { HaiType } from "../types";
4
+ import { asHaiKindId } from "../utils/assertions";
5
+ /**
6
+ * ドラ表示牌から次の牌(ドラ)を求める
7
+ * @param indicator ドラ表示牌のID (HaiKindId)
8
+ * @returns ドラ牌のID (HaiKindId)
9
+ */
10
+ export function getDoraNext(indicator) {
11
+ const type = kindIdToHaiType(indicator);
12
+ if (type === HaiType.Manzu) {
13
+ if (indicator === HaiKind.ManZu9)
14
+ return HaiKind.ManZu1;
15
+ return asHaiKindId(indicator + 1);
16
+ }
17
+ if (type === HaiType.Pinzu) {
18
+ if (indicator === HaiKind.PinZu9)
19
+ return HaiKind.PinZu1;
20
+ return asHaiKindId(indicator + 1);
21
+ }
22
+ if (type === HaiType.Souzu) {
23
+ if (indicator === HaiKind.SouZu9)
24
+ return HaiKind.SouZu1;
25
+ return asHaiKindId(indicator + 1);
26
+ }
27
+ // Jihai
28
+ // Ton(27) -> Nan(28) -> Sha(29) -> Pei(30) -> Ton(27)
29
+ if (indicator === HaiKind.Pei)
30
+ return HaiKind.Ton;
31
+ if (indicator >= HaiKind.Ton && indicator < HaiKind.Pei) {
32
+ return asHaiKindId(indicator + 1);
33
+ }
34
+ // Haku(31) -> Hatsu(32) -> Chun(33) -> Haku(31)
35
+ if (indicator === HaiKind.Chun)
36
+ return HaiKind.Haku;
37
+ if (indicator >= HaiKind.Haku && indicator < HaiKind.Chun) {
38
+ return asHaiKindId(indicator + 1);
39
+ }
40
+ // Should not happen for valid HaiKindId
41
+ return indicator;
42
+ }
43
+ /**
44
+ * 手牌に含まれるドラの数を数える
45
+ * @param tehai 手牌
46
+ * @param indicators ドラ表示牌のリスト
47
+ * @returns ドラの総数
48
+ */
49
+ export function countDora(tehai, indicators) {
50
+ let count = 0;
51
+ // Calculate actual dora hinds
52
+ const doraHais = indicators.map(getDoraNext);
53
+ // Count in closed hand
54
+ for (const hai of tehai.closed) {
55
+ for (const dora of doraHais) {
56
+ if (hai === dora)
57
+ count++;
58
+ }
59
+ }
60
+ // Count in exposed mentsu
61
+ for (const mentsu of tehai.exposed) {
62
+ for (const hai of mentsu.hais) {
63
+ for (const dora of doraHais) {
64
+ if (hai === dora)
65
+ count++;
66
+ }
67
+ }
68
+ }
69
+ // TODO: Add Akadora counting logic here
70
+ return count;
71
+ }
@@ -0,0 +1,30 @@
1
+ import { type HaiId, type HaiKindId, HaiType } from "../types";
2
+ /**
3
+ * 牌種IDから牌種タイプを取得する
4
+ */
5
+ export declare function kindIdToHaiType(kind: HaiKindId): HaiType;
6
+ /**
7
+ * 物理牌IDから牌種IDを取得する
8
+ * 0-35: 萬子 (36枚 = 9種 * 4枚) -> 0-8
9
+ * 36-71: 筒子 (36枚) -> 9-17
10
+ * 72-107: 索子 (36枚) -> 18-26
11
+ * 108-135: 字牌 (28枚 = 7種 * 4枚) -> 27-33
12
+ */
13
+ export declare function haiIdToKindId(id: HaiId): HaiKindId;
14
+ /**
15
+ * 牌種IDから数値(1-9)を取得する
16
+ * 字牌の場合は undefined を返す
17
+ */
18
+ export declare function haiKindToNumber(kind: HaiKindId): number | undefined;
19
+ /**
20
+ * 数牌かどうかを判定する
21
+ */
22
+ export declare function isSuupai(kind: HaiKindId): boolean;
23
+ /**
24
+ * 么九牌(1,9,字牌)の牌種IDセット
25
+ */
26
+ export declare const YAOCHU_KIND_IDS: readonly [0, 8, 9, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33];
27
+ /**
28
+ * 么九牌(1,9,字牌)かどうかを判定する
29
+ */
30
+ export declare function isYaochu(kind: HaiKindId): boolean;
@@ -0,0 +1,78 @@
1
+ import { HaiKind, HaiType } from "../types";
2
+ import { asHaiKindId } from "../utils/assertions";
3
+ /**
4
+ * 牌種IDから牌種タイプを取得する
5
+ */
6
+ export function kindIdToHaiType(kind) {
7
+ if (kind >= HaiKind.ManZu1 && kind <= HaiKind.ManZu9) {
8
+ return HaiType.Manzu;
9
+ }
10
+ if (kind >= HaiKind.PinZu1 && kind <= HaiKind.PinZu9) {
11
+ return HaiType.Pinzu;
12
+ }
13
+ if (kind >= HaiKind.SouZu1 && kind <= HaiKind.SouZu9) {
14
+ return HaiType.Souzu;
15
+ }
16
+ return HaiType.Jihai;
17
+ }
18
+ /**
19
+ * 物理牌IDから牌種IDを取得する
20
+ * 0-35: 萬子 (36枚 = 9種 * 4枚) -> 0-8
21
+ * 36-71: 筒子 (36枚) -> 9-17
22
+ * 72-107: 索子 (36枚) -> 18-26
23
+ * 108-135: 字牌 (28枚 = 7種 * 4枚) -> 27-33
24
+ */
25
+ export function haiIdToKindId(id) {
26
+ if (id < 36)
27
+ return asHaiKindId(Math.floor(id / 4));
28
+ if (id < 72)
29
+ return asHaiKindId(Math.floor((id - 36) / 4) + 9);
30
+ if (id < 108)
31
+ return asHaiKindId(Math.floor((id - 72) / 4) + 18);
32
+ return asHaiKindId(Math.floor((id - 108) / 4) + 27);
33
+ }
34
+ /**
35
+ * 牌種IDから数値(1-9)を取得する
36
+ * 字牌の場合は undefined を返す
37
+ */
38
+ export function haiKindToNumber(kind) {
39
+ const type = kindIdToHaiType(kind);
40
+ if (type === HaiType.Jihai)
41
+ return undefined;
42
+ if (type === HaiType.Manzu)
43
+ return kind - HaiKind.ManZu1 + 1;
44
+ if (type === HaiType.Pinzu)
45
+ return kind - HaiKind.PinZu1 + 1;
46
+ // if (kindIdToHaiType(kind) === HaiType.Souzu)
47
+ return kind - HaiKind.SouZu1 + 1;
48
+ }
49
+ /**
50
+ * 数牌かどうかを判定する
51
+ */
52
+ export function isSuupai(kind) {
53
+ return kindIdToHaiType(kind) !== HaiType.Jihai;
54
+ }
55
+ /**
56
+ * 么九牌(1,9,字牌)の牌種IDセット
57
+ */
58
+ export const YAOCHU_KIND_IDS = [
59
+ HaiKind.ManZu1,
60
+ HaiKind.ManZu9,
61
+ HaiKind.PinZu1,
62
+ HaiKind.PinZu9,
63
+ HaiKind.SouZu1,
64
+ HaiKind.SouZu9,
65
+ HaiKind.Ton,
66
+ HaiKind.Nan,
67
+ HaiKind.Sha,
68
+ HaiKind.Pei,
69
+ HaiKind.Haku,
70
+ HaiKind.Hatsu,
71
+ HaiKind.Chun,
72
+ ];
73
+ /**
74
+ * 么九牌(1,9,字牌)かどうかを判定する
75
+ */
76
+ export function isYaochu(kind) {
77
+ return YAOCHU_KIND_IDS.some((k) => k === kind);
78
+ }
@@ -0,0 +1,11 @@
1
+ import type { HaiKindId } from "../types";
2
+ import type { HouraStructure } from "../features/yaku/types";
3
+ /** 待ちの形 */
4
+ export type MachiType = "Tanki" | "Shanpon" | "Ryanmen" | "Kanchan" | "Penchan";
5
+ /**
6
+ * 手牌構造と和了牌から待ちの形を判定する
7
+ * @param hand 分解された手牌構造
8
+ * @param agariHai 和了牌
9
+ * @returns 待ちの形(判定できない、または Shanpon などの場合は undefined)
10
+ */
11
+ export declare function classifyMachi(hand: HouraStructure, agariHai: HaiKindId): MachiType | undefined;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 手牌構造と和了牌から待ちの形を判定する
3
+ * @param hand 分解された手牌構造
4
+ * @param agariHai 和了牌
5
+ * @returns 待ちの形(判定できない、または Shanpon などの場合は undefined)
6
+ */
7
+ export function classifyMachi(hand, agariHai) {
8
+ if (hand.type !== "Mentsu")
9
+ return undefined;
10
+ // 1. 雀頭での和了(単騎待ち)
11
+ if (hand.jantou.hais.includes(agariHai)) {
12
+ return "Tanki";
13
+ }
14
+ // 2. 順子・刻子・槓子での和了
15
+ for (const mentsu of hand.fourMentsu) {
16
+ if (mentsu.type === "Shuntsu") {
17
+ const machi = classifyShuntsuWait(mentsu, agariHai);
18
+ if (machi)
19
+ return machi;
20
+ }
21
+ else {
22
+ // 3. 刻子・槓子での和了(双碰待ち)
23
+ // 刻子の一部が和了牌=シャボ待ちで和了
24
+ if (mentsu.hais.includes(agariHai)) {
25
+ return "Shanpon";
26
+ }
27
+ }
28
+ }
29
+ return undefined;
30
+ }
31
+ /**
32
+ * 順子における待ちの形を判定する(内部ヘルパー)
33
+ */
34
+ function classifyShuntsuWait(shuntsu, agariHai) {
35
+ const { hais } = shuntsu;
36
+ if (!hais.includes(agariHai))
37
+ return undefined;
38
+ const [a, b, c] = hais; // 順子はソートされている前提
39
+ if (agariHai === a) {
40
+ // [Agari, b, c]
41
+ const valC = c % 9;
42
+ if (valC === 8)
43
+ return "Penchan";
44
+ return "Ryanmen";
45
+ }
46
+ if (agariHai === c) {
47
+ // [a, b, Agari]
48
+ const valA = a % 9;
49
+ if (valA === 0)
50
+ return "Penchan";
51
+ return "Ryanmen";
52
+ }
53
+ if (agariHai === b) {
54
+ // [a, Agari, c]
55
+ return "Kanchan";
56
+ }
57
+ return undefined;
58
+ }
@@ -0,0 +1,26 @@
1
+ import type { HaiKindId } from "../types";
2
+ /**
3
+ * 順子かどうかを検証する
4
+
5
+ */
6
+ export declare function isValidShuntsu(kindIds: readonly HaiKindId[]): boolean;
7
+ /**
8
+ * 刻子かどうかを検証する
9
+
10
+ */
11
+ export declare function isValidKoutsu(kindIds: readonly HaiKindId[]): boolean;
12
+ /**
13
+ * 槓子かどうかを検証する
14
+
15
+ */
16
+ export declare function isValidKantsu(kindIds: readonly HaiKindId[]): boolean;
17
+ /**
18
+ * 対子かどうかを検証する
19
+
20
+ */
21
+ export declare function isValidToitsu(kindIds: readonly HaiKindId[]): boolean;
22
+ /**
23
+ * 塔子かどうかを検証する
24
+
25
+ */
26
+ export declare function isValidTatsu(kindIds: readonly HaiKindId[]): boolean;
@@ -0,0 +1,87 @@
1
+ import { haiKindToNumber, isSuupai, kindIdToHaiType } from "./hai";
2
+ import { isTuple2, isTuple3, isTuple4 } from "../utils/assertions";
3
+ // バリデーションロジックは「HaiKindId の配列」に対して行うものと定義する。
4
+ // HaiId を持つ Mentsu を検証したい場合は、呼び出し側で KindId に変換してから渡す必要がある。
5
+ // ただし、利便性のために `convertHaiIdToKindId` ヘルパーを export する。
6
+ /**
7
+ * 順子かどうかを検証する
8
+
9
+ */
10
+ export function isValidShuntsu(kindIds) {
11
+ if (!isTuple3(kindIds))
12
+ return false;
13
+ const [a, b, c] = kindIds;
14
+ if (!isSuupai(a) || !isSuupai(b) || !isSuupai(c))
15
+ return false;
16
+ const typeA = kindIdToHaiType(a);
17
+ const typeB = kindIdToHaiType(b);
18
+ const typeC = kindIdToHaiType(c);
19
+ if (typeA !== typeB || typeA !== typeC)
20
+ return false;
21
+ const numA = haiKindToNumber(a);
22
+ const numB = haiKindToNumber(b);
23
+ const numC = haiKindToNumber(c);
24
+ if (numA === undefined || numB === undefined || numC === undefined)
25
+ return false;
26
+ // ソートして連続性をチェック
27
+ const sorted = [numA, numB, numC].sort((x, y) => x - y);
28
+ // Safe to access since we just created it with 3 elements
29
+ // But strict check might complain about index access on array
30
+ if (!isTuple3(sorted))
31
+ return false; // Should be always true
32
+ return sorted[0] + 1 === sorted[1] && sorted[1] + 1 === sorted[2];
33
+ }
34
+ /**
35
+ * 刻子かどうかを検証する
36
+
37
+ */
38
+ export function isValidKoutsu(kindIds) {
39
+ if (!isTuple3(kindIds))
40
+ return false;
41
+ const [a, b, c] = kindIds;
42
+ return a === b && b === c;
43
+ }
44
+ /**
45
+ * 槓子かどうかを検証する
46
+
47
+ */
48
+ export function isValidKantsu(kindIds) {
49
+ if (!isTuple4(kindIds))
50
+ return false;
51
+ const [a, b, c, d] = kindIds;
52
+ return a === b && b === c && c === d;
53
+ }
54
+ /**
55
+ * 対子かどうかを検証する
56
+
57
+ */
58
+ export function isValidToitsu(kindIds) {
59
+ if (!isTuple2(kindIds))
60
+ return false;
61
+ const [a, b] = kindIds;
62
+ return a === b;
63
+ }
64
+ /**
65
+ * 塔子かどうかを検証する
66
+
67
+ */
68
+ export function isValidTatsu(kindIds) {
69
+ if (!isTuple2(kindIds))
70
+ return false;
71
+ const [a, b] = kindIds;
72
+ // 数牌でなければならない
73
+ if (!isSuupai(a) || !isSuupai(b))
74
+ return false;
75
+ // 同じ種類でなければならない
76
+ const typeA = kindIdToHaiType(a);
77
+ const typeB = kindIdToHaiType(b);
78
+ if (typeA !== typeB)
79
+ return false;
80
+ const numA = haiKindToNumber(a);
81
+ const numB = haiKindToNumber(b);
82
+ if (numA === undefined || numB === undefined)
83
+ return false;
84
+ const diff = Math.abs(numA - numB);
85
+ // 差が1 (ペンチャン/リャンメン) または 2 (カンチャン) ならOK
86
+ return diff === 1 || diff === 2;
87
+ }
@@ -0,0 +1,38 @@
1
+ import type { HaiId, HaiKindDistribution, HaiKindId, Tehai, Tehai13, Tehai14 } from "../types";
2
+ /**
3
+ * 手牌の有効枚数を計算します。
4
+ * 副露(槓子含む)は一律3枚として計算します。
5
+ */
6
+ export declare function calculateTehaiCount<T extends HaiKindId | HaiId>(tehai: Tehai<T>): number;
7
+ /**
8
+ * 牌種ごとの枚数をカウントします。
9
+ */
10
+ export declare function countHaiKind(hais: readonly HaiKindId[]): HaiKindDistribution;
11
+ /**
12
+ * 手牌がTehai13(有効枚数13枚)であるか検証します。
13
+ * @throws {ShoushaiError} 枚数が不足している場合
14
+ * @throws {TahaiError} 枚数が超過している場合
15
+ */
16
+ export declare function validateTehai13<T extends HaiKindId | HaiId>(tehai: Tehai<T>): void;
17
+ /**
18
+ * 手牌がTehai14(有効枚数14枚)であるか検証します。
19
+ * @throws {ShoushaiError} 枚数が不足している場合
20
+ * @throws {TahaiError} 枚数が超過している場合
21
+ */
22
+ export declare function validateTehai14<T extends HaiKindId | HaiId>(tehai: Tehai<T>): void;
23
+ /**
24
+ * 手牌がTehai13またはTehai14(有効枚数が13または14枚)であるか検証します。
25
+ * シャンテン計算や待ち判定など、13枚/14枚の区別なく手牌として扱いたい場合に使用します。
26
+ *
27
+ * @throws {ShoushaiError} 枚数が不足している場合 (< 13)
28
+ * @throws {TahaiError} 枚数が超過している場合 (> 14)
29
+ */
30
+ export declare function validateTehai<T extends HaiKindId | HaiId>(tehai: Tehai<T>): void;
31
+ /**
32
+ * Type Guard for Tehai13
33
+ */
34
+ export declare function isTehai13<T extends HaiKindId | HaiId>(tehai: Tehai<T>): tehai is Tehai13<T>;
35
+ /**
36
+ * Type Guard for Tehai14
37
+ */
38
+ export declare function isTehai14<T extends HaiKindId | HaiId>(tehai: Tehai<T>): tehai is Tehai14<T>;
@@ -0,0 +1,87 @@
1
+ import { ShoushaiError, TahaiError } from "../errors";
2
+ /**
3
+ * 手牌の有効枚数を計算します。
4
+ * 副露(槓子含む)は一律3枚として計算します。
5
+ */
6
+ export function calculateTehaiCount(tehai) {
7
+ return tehai.closed.length + tehai.exposed.length * 3;
8
+ }
9
+ /**
10
+ * 牌種ごとの枚数をカウントします。
11
+ */
12
+ export function countHaiKind(hais) {
13
+ const counts = Array.from({ length: 34 }, () => 0);
14
+ for (const hai of hais) {
15
+ counts[hai] = (counts[hai] ?? 0) + 1;
16
+ }
17
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
18
+ return counts;
19
+ }
20
+ /**
21
+ * 手牌がTehai13(有効枚数13枚)であるか検証します。
22
+ * @throws {ShoushaiError} 枚数が不足している場合
23
+ * @throws {TahaiError} 枚数が超過している場合
24
+ */
25
+ export function validateTehai13(tehai) {
26
+ const count = calculateTehaiCount(tehai);
27
+ if (count < 13) {
28
+ throw new ShoushaiError();
29
+ }
30
+ if (count > 13) {
31
+ throw new TahaiError();
32
+ }
33
+ }
34
+ /**
35
+ * 手牌がTehai14(有効枚数14枚)であるか検証します。
36
+ * @throws {ShoushaiError} 枚数が不足している場合
37
+ * @throws {TahaiError} 枚数が超過している場合
38
+ */
39
+ export function validateTehai14(tehai) {
40
+ const count = calculateTehaiCount(tehai);
41
+ if (count < 14) {
42
+ throw new ShoushaiError();
43
+ }
44
+ if (count > 14) {
45
+ throw new TahaiError();
46
+ }
47
+ }
48
+ /**
49
+ * 手牌がTehai13またはTehai14(有効枚数が13または14枚)であるか検証します。
50
+ * シャンテン計算や待ち判定など、13枚/14枚の区別なく手牌として扱いたい場合に使用します。
51
+ *
52
+ * @throws {ShoushaiError} 枚数が不足している場合 (< 13)
53
+ * @throws {TahaiError} 枚数が超過している場合 (> 14)
54
+ */
55
+ export function validateTehai(tehai) {
56
+ const count = calculateTehaiCount(tehai);
57
+ if (count < 13) {
58
+ throw new ShoushaiError();
59
+ }
60
+ if (count > 14) {
61
+ throw new TahaiError();
62
+ }
63
+ }
64
+ /**
65
+ * Type Guard for Tehai13
66
+ */
67
+ export function isTehai13(tehai) {
68
+ try {
69
+ validateTehai13(tehai);
70
+ return true;
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Type Guard for Tehai14
78
+ */
79
+ export function isTehai14(tehai) {
80
+ try {
81
+ validateTehai14(tehai);
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * riichi-mahjong ライブラリの基本エラークラス
3
+ * 全てのカスタムエラーはこのクラスを継承します。
4
+ */
5
+ export declare class MahjongError extends Error {
6
+ /**
7
+ *
8
+ */
9
+ constructor(message: string);
10
+ }
11
+ /**
12
+ * ツモれなかった場合のエラー (少牌)
13
+ * 手牌が規定枚数(13枚)より少ない場合にスローされます。
14
+ */
15
+ export declare class ShoushaiError extends MahjongError {
16
+ /**
17
+ *
18
+ */
19
+ constructor(message?: string);
20
+ }
21
+ /**
22
+ * 切り忘れの場合のエラー (多牌)
23
+ * 手牌が規定枚数(13枚)より多い場合にスローされます。
24
+ */
25
+ export declare class TahaiError extends MahjongError {
26
+ /**
27
+ *
28
+ */
29
+ constructor(message?: string);
30
+ }
31
+ /**
32
+ * 引数が不正な場合のエラー
33
+ * 必要なパラメータが不足している、または不正な値の場合にスローされます。
34
+ */
35
+ export declare class MahjongArgumentError extends MahjongError {
36
+ /**
37
+ *
38
+ */
39
+ constructor(message: string);
40
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * riichi-mahjong ライブラリの基本エラークラス
3
+ * 全てのカスタムエラーはこのクラスを継承します。
4
+ */
5
+ export class MahjongError extends Error {
6
+ /**
7
+ *
8
+ */
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "MahjongError";
12
+ // TypeScriptでカスタムエラーを正しく動作させるためのハック
13
+ // TypeScriptでカスタムエラーを正しく動作させるためのハック
14
+ Object.setPrototypeOf(this, MahjongError.prototype);
15
+ }
16
+ }
17
+ /**
18
+ * ツモれなかった場合のエラー (少牌)
19
+ * 手牌が規定枚数(13枚)より少ない場合にスローされます。
20
+ */
21
+ export class ShoushaiError extends MahjongError {
22
+ /**
23
+ *
24
+ */
25
+ constructor(message = "手牌が規定枚数(13枚)より少ないです。") {
26
+ super(message);
27
+ this.name = "ShoushaiError";
28
+ Object.setPrototypeOf(this, ShoushaiError.prototype);
29
+ }
30
+ }
31
+ /**
32
+ * 切り忘れの場合のエラー (多牌)
33
+ * 手牌が規定枚数(13枚)より多い場合にスローされます。
34
+ */
35
+ export class TahaiError extends MahjongError {
36
+ /**
37
+ *
38
+ */
39
+ constructor(message = "手牌が規定枚数(13枚)より多いです。") {
40
+ super(message);
41
+ this.name = "TahaiError";
42
+ Object.setPrototypeOf(this, TahaiError.prototype);
43
+ }
44
+ }
45
+ /**
46
+ * 引数が不正な場合のエラー
47
+ * 必要なパラメータが不足している、または不正な値の場合にスローされます。
48
+ */
49
+ export class MahjongArgumentError extends MahjongError {
50
+ /**
51
+ *
52
+ */
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "MahjongArgumentError";
56
+ Object.setPrototypeOf(this, MahjongArgumentError.prototype);
57
+ }
58
+ }
@@ -0,0 +1,9 @@
1
+ import type { HaiKindId, Tehai13 } from "../../types";
2
+ /**
3
+ * 手牌の受け入れ(有効牌)を計算する。
4
+ * 今回は面子手のみを対象とし、七対子や国士無双は考慮しない。
5
+ *
6
+ * @param tehai 手牌 (13枚)
7
+ * @returns シャンテン数を進める牌のリスト
8
+ */
9
+ export declare function getUkeire(tehai: Tehai13): HaiKindId[];