@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.
Files changed (151) hide show
  1. package/dist/core/dora.d.ts +14 -0
  2. package/dist/core/dora.js +71 -0
  3. package/dist/core/hai.d.ts +30 -0
  4. package/dist/core/hai.js +78 -0
  5. package/dist/core/machi.d.ts +11 -0
  6. package/dist/core/machi.js +58 -0
  7. package/dist/core/mentsu.d.ts +26 -0
  8. package/dist/core/mentsu.js +87 -0
  9. package/dist/core/tehai.d.ts +42 -0
  10. package/dist/core/tehai.js +156 -0
  11. package/dist/errors.d.ts +83 -0
  12. package/dist/errors.js +117 -0
  13. package/dist/features/machi/index.d.ts +9 -0
  14. package/dist/features/machi/index.js +48 -0
  15. package/dist/features/machi/types.d.ts +1 -0
  16. package/dist/features/machi/types.js +1 -0
  17. package/dist/features/parser/index.d.ts +19 -0
  18. package/dist/features/parser/index.js +28 -0
  19. package/dist/features/parser/mspz.d.ts +85 -0
  20. package/dist/features/parser/mspz.js +365 -0
  21. package/dist/features/points/constants.d.ts +27 -0
  22. package/dist/features/points/constants.js +30 -0
  23. package/dist/features/points/index.d.ts +21 -0
  24. package/dist/features/points/index.js +174 -0
  25. package/dist/features/points/lib/fu/constants.d.ts +37 -0
  26. package/dist/features/points/lib/fu/constants.js +45 -0
  27. package/dist/features/points/lib/fu/index.d.ts +11 -0
  28. package/dist/features/points/lib/fu/index.js +23 -0
  29. package/dist/features/points/lib/fu/lib/chiitoitsu.d.ts +5 -0
  30. package/dist/features/points/lib/fu/lib/chiitoitsu.js +17 -0
  31. package/dist/features/points/lib/fu/lib/kokushi.d.ts +6 -0
  32. package/dist/features/points/lib/fu/lib/kokushi.js +18 -0
  33. package/dist/features/points/lib/fu/lib/mentsu.d.ts +11 -0
  34. package/dist/features/points/lib/fu/lib/mentsu.js +122 -0
  35. package/dist/features/points/lib/fu/types.d.ts +55 -0
  36. package/dist/features/points/lib/fu/types.js +1 -0
  37. package/dist/features/points/types.d.ts +27 -0
  38. package/dist/features/points/types.js +1 -0
  39. package/dist/features/score/constants.d.ts +27 -0
  40. package/dist/features/score/constants.js +30 -0
  41. package/dist/features/score/index.d.ts +45 -0
  42. package/dist/features/score/index.js +207 -0
  43. package/dist/features/score/lib/fu/constants.d.ts +37 -0
  44. package/dist/features/score/lib/fu/constants.js +45 -0
  45. package/dist/features/score/lib/fu/index.d.ts +11 -0
  46. package/dist/features/score/lib/fu/index.js +23 -0
  47. package/dist/features/score/lib/fu/lib/chiitoitsu.d.ts +5 -0
  48. package/dist/features/score/lib/fu/lib/chiitoitsu.js +17 -0
  49. package/dist/features/score/lib/fu/lib/kokushi.d.ts +6 -0
  50. package/dist/features/score/lib/fu/lib/kokushi.js +18 -0
  51. package/dist/features/score/lib/fu/lib/mentsu.d.ts +11 -0
  52. package/dist/features/score/lib/fu/lib/mentsu.js +136 -0
  53. package/dist/features/score/lib/fu/types.d.ts +56 -0
  54. package/dist/features/score/lib/fu/types.js +1 -0
  55. package/dist/features/score/types.d.ts +78 -0
  56. package/dist/features/score/types.js +22 -0
  57. package/dist/features/shanten/index.d.ts +16 -0
  58. package/dist/features/shanten/index.js +25 -0
  59. package/dist/features/shanten/logic/chiitoitsu.d.ts +8 -0
  60. package/dist/features/shanten/logic/chiitoitsu.js +36 -0
  61. package/dist/features/shanten/logic/kokushi.d.ts +16 -0
  62. package/dist/features/shanten/logic/kokushi.js +48 -0
  63. package/dist/features/shanten/logic/mentsu-te.d.ts +8 -0
  64. package/dist/features/shanten/logic/mentsu-te.js +129 -0
  65. package/dist/features/yaku/factory.d.ts +13 -0
  66. package/dist/features/yaku/factory.js +19 -0
  67. package/dist/features/yaku/index.d.ts +21 -0
  68. package/dist/features/yaku/index.js +59 -0
  69. package/dist/features/yaku/lib/definitions/chiitoitsu.d.ts +2 -0
  70. package/dist/features/yaku/lib/definitions/chiitoitsu.js +12 -0
  71. package/dist/features/yaku/lib/definitions/chinitsu.d.ts +2 -0
  72. package/dist/features/yaku/lib/definitions/chinitsu.js +40 -0
  73. package/dist/features/yaku/lib/definitions/chinroutou.d.ts +2 -0
  74. package/dist/features/yaku/lib/definitions/chinroutou.js +21 -0
  75. package/dist/features/yaku/lib/definitions/chuuren-poutou.d.ts +2 -0
  76. package/dist/features/yaku/lib/definitions/chuuren-poutou.js +69 -0
  77. package/dist/features/yaku/lib/definitions/daisangen.d.ts +2 -0
  78. package/dist/features/yaku/lib/definitions/daisangen.js +26 -0
  79. package/dist/features/yaku/lib/definitions/daisuushii.d.ts +2 -0
  80. package/dist/features/yaku/lib/definitions/daisuushii.js +32 -0
  81. package/dist/features/yaku/lib/definitions/honchan.d.ts +2 -0
  82. package/dist/features/yaku/lib/definitions/honchan.js +29 -0
  83. package/dist/features/yaku/lib/definitions/honitsu.d.ts +2 -0
  84. package/dist/features/yaku/lib/definitions/honitsu.js +40 -0
  85. package/dist/features/yaku/lib/definitions/honroutou.d.ts +2 -0
  86. package/dist/features/yaku/lib/definitions/honroutou.js +33 -0
  87. package/dist/features/yaku/lib/definitions/iipeikou.d.ts +2 -0
  88. package/dist/features/yaku/lib/definitions/iipeikou.js +46 -0
  89. package/dist/features/yaku/lib/definitions/ikkitsuukan.d.ts +2 -0
  90. package/dist/features/yaku/lib/definitions/ikkitsuukan.js +56 -0
  91. package/dist/features/yaku/lib/definitions/index.d.ts +30 -0
  92. package/dist/features/yaku/lib/definitions/index.js +90 -0
  93. package/dist/features/yaku/lib/definitions/junchan.d.ts +2 -0
  94. package/dist/features/yaku/lib/definitions/junchan.js +25 -0
  95. package/dist/features/yaku/lib/definitions/kokushi.d.ts +2 -0
  96. package/dist/features/yaku/lib/definitions/kokushi.js +12 -0
  97. package/dist/features/yaku/lib/definitions/menzen-tsumo.d.ts +2 -0
  98. package/dist/features/yaku/lib/definitions/menzen-tsumo.js +8 -0
  99. package/dist/features/yaku/lib/definitions/pinfu.d.ts +2 -0
  100. package/dist/features/yaku/lib/definitions/pinfu.js +40 -0
  101. package/dist/features/yaku/lib/definitions/ryanpeikou.d.ts +2 -0
  102. package/dist/features/yaku/lib/definitions/ryanpeikou.js +33 -0
  103. package/dist/features/yaku/lib/definitions/ryuuiisou.d.ts +2 -0
  104. package/dist/features/yaku/lib/definitions/ryuuiisou.js +43 -0
  105. package/dist/features/yaku/lib/definitions/sanankou.d.ts +2 -0
  106. package/dist/features/yaku/lib/definitions/sanankou.js +49 -0
  107. package/dist/features/yaku/lib/definitions/sankantsu.d.ts +2 -0
  108. package/dist/features/yaku/lib/definitions/sankantsu.js +18 -0
  109. package/dist/features/yaku/lib/definitions/sanshoku-doujun.d.ts +2 -0
  110. package/dist/features/yaku/lib/definitions/sanshoku-doujun.js +58 -0
  111. package/dist/features/yaku/lib/definitions/sanshoku-doukou.d.ts +2 -0
  112. package/dist/features/yaku/lib/definitions/sanshoku-doukou.js +53 -0
  113. package/dist/features/yaku/lib/definitions/shousangen.d.ts +2 -0
  114. package/dist/features/yaku/lib/definitions/shousangen.js +28 -0
  115. package/dist/features/yaku/lib/definitions/shousuushii.d.ts +2 -0
  116. package/dist/features/yaku/lib/definitions/shousuushii.js +34 -0
  117. package/dist/features/yaku/lib/definitions/suuankou.d.ts +2 -0
  118. package/dist/features/yaku/lib/definitions/suuankou.js +63 -0
  119. package/dist/features/yaku/lib/definitions/suukantsu.d.ts +2 -0
  120. package/dist/features/yaku/lib/definitions/suukantsu.js +18 -0
  121. package/dist/features/yaku/lib/definitions/tanyao.d.ts +2 -0
  122. package/dist/features/yaku/lib/definitions/tanyao.js +23 -0
  123. package/dist/features/yaku/lib/definitions/toitoi.d.ts +2 -0
  124. package/dist/features/yaku/lib/definitions/toitoi.js +16 -0
  125. package/dist/features/yaku/lib/definitions/tsuuiisou.d.ts +2 -0
  126. package/dist/features/yaku/lib/definitions/tsuuiisou.js +35 -0
  127. package/dist/features/yaku/lib/definitions/yakuhai.d.ts +4 -0
  128. package/dist/features/yaku/lib/definitions/yakuhai.js +21 -0
  129. package/dist/features/yaku/lib/index.d.ts +1 -0
  130. package/dist/features/yaku/lib/index.js +1 -0
  131. package/dist/features/yaku/lib/structures/chiitoitsu.d.ts +6 -0
  132. package/dist/features/yaku/lib/structures/chiitoitsu.js +38 -0
  133. package/dist/features/yaku/lib/structures/index.d.ts +10 -0
  134. package/dist/features/yaku/lib/structures/index.js +17 -0
  135. package/dist/features/yaku/lib/structures/kokushi.d.ts +6 -0
  136. package/dist/features/yaku/lib/structures/kokushi.js +43 -0
  137. package/dist/features/yaku/lib/structures/mentsu-te.d.ts +24 -0
  138. package/dist/features/yaku/lib/structures/mentsu-te.js +127 -0
  139. package/dist/features/yaku/types.d.ts +121 -0
  140. package/dist/features/yaku/types.js +1 -0
  141. package/dist/features/yaku/utils.d.ts +19 -0
  142. package/dist/features/yaku/utils.js +34 -0
  143. package/dist/index.d.ts +21 -0
  144. package/dist/index.js +53 -0
  145. package/dist/types.d.ts +290 -0
  146. package/dist/types.js +97 -0
  147. package/dist/utils/assertions.d.ts +22 -0
  148. package/dist/utils/assertions.js +33 -0
  149. package/dist/utils/test-helpers.d.ts +55 -0
  150. package/dist/utils/test-helpers.js +124 -0
  151. package/package.json +3 -2
@@ -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,42 @@
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
+ * @throws {InvalidHaiQuantityError} 同一種の牌が5枚以上ある場合
22
+ * @throws {DuplicatedHaiIdError} 物理牌モードでIDが重複している場合
23
+ */
24
+ export declare function validateTehai14<T extends HaiKindId | HaiId>(tehai: Tehai<T>): void;
25
+ /**
26
+ * 手牌がTehai13またはTehai14(有効枚数が13または14枚)であるか検証します。
27
+ * シャンテン計算や待ち判定など、13枚/14枚の区別なく手牌として扱いたい場合に使用します。
28
+ *
29
+ * @throws {ShoushaiError} 枚数が不足している場合 (< 13)
30
+ * @throws {TahaiError} 枚数が超過している場合 (> 14)
31
+ * @throws {InvalidHaiQuantityError} 同一種の牌が5枚以上ある場合
32
+ * @throws {DuplicatedHaiIdError} 物理牌モードでIDが重複している場合
33
+ */
34
+ export declare function validateTehai<T extends HaiKindId | HaiId>(tehai: Tehai<T>): void;
35
+ /**
36
+ * Type Guard for Tehai13
37
+ */
38
+ export declare function isTehai13<T extends HaiKindId | HaiId>(tehai: Tehai<T>): tehai is Tehai13<T>;
39
+ /**
40
+ * Type Guard for Tehai14
41
+ */
42
+ export declare function isTehai14<T extends HaiKindId | HaiId>(tehai: Tehai<T>): tehai is Tehai14<T>;
@@ -0,0 +1,156 @@
1
+ import { DuplicatedHaiIdError, InvalidHaiQuantityError, 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
+ validateHaiConsistency(tehai);
34
+ }
35
+ /**
36
+ * 手牌がTehai14(有効枚数14枚)であるか検証します。
37
+ * @throws {ShoushaiError} 枚数が不足している場合
38
+ * @throws {TahaiError} 枚数が超過している場合
39
+ * @throws {InvalidHaiQuantityError} 同一種の牌が5枚以上ある場合
40
+ * @throws {DuplicatedHaiIdError} 物理牌モードでIDが重複している場合
41
+ */
42
+ export function validateTehai14(tehai) {
43
+ const count = calculateTehaiCount(tehai);
44
+ if (count < 14) {
45
+ throw new ShoushaiError();
46
+ }
47
+ if (count > 14) {
48
+ throw new TahaiError();
49
+ }
50
+ validateHaiConsistency(tehai);
51
+ }
52
+ /**
53
+ * 手牌がTehai13またはTehai14(有効枚数が13または14枚)であるか検証します。
54
+ * シャンテン計算や待ち判定など、13枚/14枚の区別なく手牌として扱いたい場合に使用します。
55
+ *
56
+ * @throws {ShoushaiError} 枚数が不足している場合 (< 13)
57
+ * @throws {TahaiError} 枚数が超過している場合 (> 14)
58
+ * @throws {InvalidHaiQuantityError} 同一種の牌が5枚以上ある場合
59
+ * @throws {DuplicatedHaiIdError} 物理牌モードでIDが重複している場合
60
+ */
61
+ export function validateTehai(tehai) {
62
+ const count = calculateTehaiCount(tehai);
63
+ if (count < 13) {
64
+ throw new ShoushaiError();
65
+ }
66
+ if (count > 14) {
67
+ throw new TahaiError();
68
+ }
69
+ validateHaiConsistency(tehai);
70
+ }
71
+ /**
72
+ * 手牌の整合性を検証します。
73
+ * - 物理的な牌ID (`HaiId`) が使われている場合、重複チェックを行います。
74
+ * - 牌種ID (`HaiKindId`) の場合(または変換後)、同一牌種が5枚以上ないかチェックします。
75
+ *
76
+ * @throws {DuplicatedHaiIdError}
77
+ * @throws {InvalidHaiQuantityError}
78
+ */
79
+ function validateHaiConsistency(tehai) {
80
+ const allHais = [
81
+ ...tehai.closed,
82
+ ...tehai.exposed.flatMap((m) => m.hais),
83
+ ];
84
+ // 1. Check for physical HaiId usage (any id > 33)
85
+ // HaiKindId is 0-33. Any value > 33 implies HaiId (0-135).
86
+ // Note: Low HaiIds (0-33) are ambiguous, but if mix of high and low exists, it's HaiId.
87
+ // If only low values exist, we can't strictly distinguish, but treating as KindId is safe
88
+ // unless the user provided [0, 0] intending HaiId 0 and HaiId 0.
89
+ // However, normally HaiKindId 0 is ManZu1.
90
+ // Strategy: If MAX(id) > 33, treat as HaiId.
91
+ const isHaiIdMode = allHais.some((h) => h > 33);
92
+ if (isHaiIdMode) {
93
+ // Check for duplicate HaiIds
94
+ const uniqueIds = new Set(allHais);
95
+ if (uniqueIds.size !== allHais.length) {
96
+ throw new DuplicatedHaiIdError();
97
+ }
98
+ }
99
+ // 2. Check for Kind quantity (max 4 per kind)
100
+ const counts = new Map();
101
+ for (const hai of allHais) {
102
+ // If HaiId mode, convert to KindId
103
+ // If KindId mode, use as is
104
+ // import { haiIdToKindId } from "./hai"; <--- Need to import or implement logic
105
+ // Since we are in core/tehai, and core/hai depends on types.
106
+ // Let's defer strict conversion.
107
+ // For now, assume generic T validation behavior.
108
+ // But we need `haiIdToKindId`.
109
+ // Let's implement logic inline or use import.
110
+ // Circular dependency risk? core/tehai -> core/hai.
111
+ // core/hai imports types. core/tehai imports types. Should be fine.
112
+ // But I need to import it at top of file.
113
+ // Using inline logic to avoid circular deps if any (though likely safe)
114
+ // 0-35 -> 0-8, etc.
115
+ let kind = hai;
116
+ if (hai > 33) {
117
+ if (hai < 36)
118
+ kind = Math.floor(hai / 4);
119
+ else if (hai < 72)
120
+ kind = Math.floor((hai - 36) / 4) + 9;
121
+ else if (hai < 108)
122
+ kind = Math.floor((hai - 72) / 4) + 18;
123
+ else
124
+ kind = Math.floor((hai - 108) / 4) + 27;
125
+ }
126
+ const current = counts.get(kind) ?? 0;
127
+ if (current + 1 > 4) {
128
+ throw new InvalidHaiQuantityError();
129
+ }
130
+ counts.set(kind, current + 1);
131
+ }
132
+ }
133
+ /**
134
+ * Type Guard for Tehai13
135
+ */
136
+ export function isTehai13(tehai) {
137
+ try {
138
+ validateTehai13(tehai);
139
+ return true;
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ }
145
+ /**
146
+ * Type Guard for Tehai14
147
+ */
148
+ export function isTehai14(tehai) {
149
+ try {
150
+ validateTehai14(tehai);
151
+ return true;
152
+ }
153
+ catch {
154
+ return false;
155
+ }
156
+ }
@@ -0,0 +1,83 @@
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
+ }
41
+ /**
42
+ * 牌IDが重複している場合のエラー
43
+ * (物理的な牌IDは一意である必要があります)
44
+ */
45
+ export declare class DuplicatedHaiIdError extends MahjongError {
46
+ /**
47
+ *
48
+ */
49
+ constructor(message?: string);
50
+ }
51
+ /**
52
+ * 牌の枚数が不正な場合のエラー
53
+ * (同種の牌は最大4枚までです)
54
+ */
55
+ export declare class InvalidHaiQuantityError extends MahjongError {
56
+ /**
57
+ *
58
+ */
59
+ constructor(message?: string);
60
+ }
61
+ /**
62
+ * チョンボ(錯和)の基底エラークラス
63
+ *
64
+ * 不正な和了宣言に関するエラーの基底クラス。
65
+ * 具体的なチョンボ種別(役なし、フリテン等)はサブクラスで定義する。
66
+ */
67
+ export declare class ChomboError extends MahjongError {
68
+ /**
69
+ *
70
+ */
71
+ constructor(message?: string);
72
+ }
73
+ /**
74
+ * 役なし和了のエラー
75
+ *
76
+ * 和了形は成立しているが、役が一つも成立していない場合にスローされます。
77
+ */
78
+ export declare class NoYakuError extends ChomboError {
79
+ /**
80
+ *
81
+ */
82
+ constructor(message?: string);
83
+ }