@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,78 @@
1
+ import type { Fu, HaiKindId, Kazehai } from "../../types";
2
+ import type { HouraContext } from "../yaku/types";
3
+ /**
4
+ * 点数計算用コンテキスト (ScoreContext)
5
+ *
6
+ * HouraContext を拡張し、点数計算に必要な追加情報を持つ。
7
+ *
8
+ * HouraContext は役判定に特化しており、isOya は役の成立・翻数に影響しない。
9
+ * isOya は支払い計算(親ロン/子ロン、親ツモ/子ツモの倍率差)にのみ必要なため、
10
+ * 点数計算用のコンテキストとして分離している。
11
+ */
12
+ export interface ScoreContext extends HouraContext {
13
+ /** 和了者が親かどうか */
14
+ readonly isOya: boolean;
15
+ }
16
+ export interface ScoreCalculationConfig {
17
+ /** 和了牌 */
18
+ agariHai: HaiKindId;
19
+ /** ツモ和了かどうか (必須) */
20
+ isTsumo: boolean;
21
+ /** 自風 (必須) */
22
+ jikaze: Kazehai;
23
+ /** 場風 (必須) */
24
+ bakaze: Kazehai;
25
+ /** ドラ表示牌 (必須、なければ空配列) */
26
+ doraMarkers: readonly HaiKindId[];
27
+ /** 裏ドラ表示牌 (任意) */
28
+ uraDoraMarkers?: readonly HaiKindId[];
29
+ }
30
+ /** ロン和了時の支払い */
31
+ export interface Ron {
32
+ type: "ron";
33
+ /** 振り込んだプレイヤーが支払う点数 */
34
+ amount: number;
35
+ }
36
+ /** 子のツモ和了時の支払い */
37
+ export interface KoTsumo {
38
+ type: "koTsumo";
39
+ /** [子の支払い, 親の支払い] */
40
+ readonly amount: readonly [number, number];
41
+ }
42
+ /** 親のツモ和了時の支払い */
43
+ export interface OyaTsumo {
44
+ type: "oyaTsumo";
45
+ /** 子全員が支払う点数(オール) */
46
+ amount: number;
47
+ }
48
+ /** 支払い情報 */
49
+ export type Payment = Ron | KoTsumo | OyaTsumo;
50
+ /**
51
+ * 点数レベル (ScoreLevel)
52
+ *
53
+ * 翻数と基本点から決まる点数の区分。
54
+ * 満貫以上では符に関係なく固定の基本点が適用される。
55
+ */
56
+ export declare const ScoreLevel: {
57
+ /** 満貫未満(通常計算) */
58
+ readonly Normal: "Normal";
59
+ /** 満貫(5翻、または基本点2000以上) */
60
+ readonly Mangan: "Mangan";
61
+ /** 跳満(6-7翻) */
62
+ readonly Haneman: "Haneman";
63
+ /** 倍満(8-10翻) */
64
+ readonly Baiman: "Baiman";
65
+ /** 三倍満(11-12翻) */
66
+ readonly Sanbaiman: "Sanbaiman";
67
+ /** 役満(13翻以上) */
68
+ readonly Yakuman: "Yakuman";
69
+ /** ダブル役満(26翻以上、または役満複合) */
70
+ readonly DoubleYakuman: "DoubleYakuman";
71
+ };
72
+ export type ScoreLevel = (typeof ScoreLevel)[keyof typeof ScoreLevel];
73
+ export interface ScoreResult {
74
+ han: number;
75
+ fu: Fu;
76
+ scoreLevel: ScoreLevel;
77
+ payment: Payment;
78
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 点数レベル (ScoreLevel)
3
+ *
4
+ * 翻数と基本点から決まる点数の区分。
5
+ * 満貫以上では符に関係なく固定の基本点が適用される。
6
+ */
7
+ export const ScoreLevel = {
8
+ /** 満貫未満(通常計算) */
9
+ Normal: "Normal",
10
+ /** 満貫(5翻、または基本点2000以上) */
11
+ Mangan: "Mangan",
12
+ /** 跳満(6-7翻) */
13
+ Haneman: "Haneman",
14
+ /** 倍満(8-10翻) */
15
+ Baiman: "Baiman",
16
+ /** 三倍満(11-12翻) */
17
+ Sanbaiman: "Sanbaiman",
18
+ /** 役満(13翻以上) */
19
+ Yakuman: "Yakuman",
20
+ /** ダブル役満(26翻以上、または役満複合) */
21
+ DoubleYakuman: "DoubleYakuman",
22
+ };
@@ -0,0 +1,16 @@
1
+ import type { Tehai13 } from "../../types";
2
+ import { calculateChiitoitsuShanten } from "./logic/chiitoitsu";
3
+ import { calculateKokushiShanten } from "./logic/kokushi";
4
+ import { calculateMentsuTeShanten } from "./logic/mentsu-te";
5
+ export { calculateChiitoitsuShanten, calculateKokushiShanten, calculateMentsuTeShanten, };
6
+ /**
7
+ * シャンテン数を計算します。
8
+ * 面子手、七対子、国士無双のシャンテン数のうち最小値を返します。
9
+ *
10
+ * NOTE: 入力は必ず牌種ID (`HaiKindId`) である必要があります。
11
+ * 物理牌ID (`HaiId`) を持っている場合は、事前に `haiIdToKindId` で変換してください。
12
+ *
13
+ * @param tehai 手牌
14
+ * @returns シャンテン数
15
+ */
16
+ export declare function calculateShanten(tehai: Tehai13, useChiitoitsu?: boolean, useKokushi?: boolean): number;
@@ -0,0 +1,25 @@
1
+ import { validateTehai13 } from "../../core/tehai";
2
+ import { calculateChiitoitsuShanten } from "./logic/chiitoitsu";
3
+ import { calculateKokushiShanten } from "./logic/kokushi";
4
+ import { calculateMentsuTeShanten } from "./logic/mentsu-te";
5
+ export { calculateChiitoitsuShanten, calculateKokushiShanten, calculateMentsuTeShanten, };
6
+ /**
7
+ * シャンテン数を計算します。
8
+ * 面子手、七対子、国士無双のシャンテン数のうち最小値を返します。
9
+ *
10
+ * NOTE: 入力は必ず牌種ID (`HaiKindId`) である必要があります。
11
+ * 物理牌ID (`HaiId`) を持っている場合は、事前に `haiIdToKindId` で変換してください。
12
+ *
13
+ * @param tehai 手牌
14
+ * @returns シャンテン数
15
+ */
16
+ export function calculateShanten(tehai, useChiitoitsu = true, useKokushi = true) {
17
+ // Facadeパターン: 公開APIのエントリーポイントで入力を保証する
18
+ validateTehai13(tehai);
19
+ const chiitoitsuShanten = useChiitoitsu
20
+ ? calculateChiitoitsuShanten(tehai)
21
+ : Infinity;
22
+ const kokushiShanten = useKokushi ? calculateKokushiShanten(tehai) : Infinity;
23
+ const mentsuShanten = calculateMentsuTeShanten(tehai);
24
+ return Math.min(chiitoitsuShanten, kokushiShanten, mentsuShanten);
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { Tehai13 } from "../../../types";
2
+ /**
3
+ * 七対子のシャンテン数を計算する
4
+ *
5
+ * @param tehai 手牌 (13枚)
6
+ * @returns シャンテン数 (0: 聴牌, -1: 和了 - 理論上)
7
+ */
8
+ export declare function calculateChiitoitsuShanten(tehai: Tehai13): number;
@@ -0,0 +1,36 @@
1
+ import { countHaiKind, validateTehai13 } from "../../../core/tehai";
2
+ /**
3
+ * 七対子のシャンテン数を計算する
4
+ *
5
+ * @param tehai 手牌 (13枚)
6
+ * @returns シャンテン数 (0: 聴牌, -1: 和了 - 理論上)
7
+ */
8
+ export function calculateChiitoitsuShanten(tehai) {
9
+ // 防御的プログラミング (Defensive Programming):
10
+ // 公開API(calculateShanten)側でもバリデーションが行われる想定だが(Facadeパターン)、
11
+ // 内部整合性を保つため、ここでも独立してバリデーションを実施する。
12
+ validateTehai13(tehai);
13
+ // シャンテン数を計算する前にバリデーションを実行する
14
+ // 七対子は門前のみ
15
+ if (tehai.exposed.length > 0) {
16
+ return Infinity;
17
+ }
18
+ // HaiId/HaiKindId の正規化は廃止。呼び出し元で HaiKindId を保証する。
19
+ const haiCounts = countHaiKind(tehai.closed);
20
+ let pairs = 0;
21
+ let kinds = 0;
22
+ for (const count of haiCounts) {
23
+ if (count > 0) {
24
+ kinds++;
25
+ }
26
+ if (count >= 2) {
27
+ pairs++;
28
+ }
29
+ }
30
+ let shanten = 6 - pairs;
31
+ // 種類不足ペナルティ
32
+ if (kinds < 7) {
33
+ shanten += 7 - kinds;
34
+ }
35
+ return shanten;
36
+ }
@@ -0,0 +1,16 @@
1
+ import type { Tehai13 } from "../../../types";
2
+ /**
3
+ * 国士無双のシャンテン数を計算します。
4
+ *
5
+ * ルール:
6
+ * - 13種類の么九牌(1,9,字牌)を各1枚ずつ揃える。
7
+ * - そのうちのどれか1種類が対子(2枚)になっている必要がある。
8
+ * - 門前限定。
9
+ *
10
+ * 計算式:
11
+ * シャンテン数 = 13 - (么九牌の種類数) - (么九牌の対子があるか ? 1 : 0)
12
+ *
13
+ * @param tehai 手牌
14
+ * @returns シャンテン数 (0: 聴牌, -1: 和了(理論上))。副露している場合は Infinity。
15
+ */
16
+ export declare function calculateKokushiShanten(tehai: Tehai13): number;
@@ -0,0 +1,48 @@
1
+ import { isYaochu } from "../../../core/hai";
2
+ import { countHaiKind, validateTehai13 } from "../../../core/tehai";
3
+ import { asHaiKindId } from "../../../utils/assertions";
4
+ /**
5
+ * 国士無双のシャンテン数を計算します。
6
+ *
7
+ * ルール:
8
+ * - 13種類の么九牌(1,9,字牌)を各1枚ずつ揃える。
9
+ * - そのうちのどれか1種類が対子(2枚)になっている必要がある。
10
+ * - 門前限定。
11
+ *
12
+ * 計算式:
13
+ * シャンテン数 = 13 - (么九牌の種類数) - (么九牌の対子があるか ? 1 : 0)
14
+ *
15
+ * @param tehai 手牌
16
+ * @returns シャンテン数 (0: 聴牌, -1: 和了(理論上))。副露している場合は Infinity。
17
+ */
18
+ export function calculateKokushiShanten(tehai) {
19
+ // 防御的プログラミング (Defensive Programming):
20
+ // 公開API(calculateShanten)側でもバリデーションが行われる想定だが(Facadeパターン)、
21
+ // 内部整合性を保つため、ここでも独立してバリデーションを実施する。
22
+ validateTehai13(tehai);
23
+ // 国士無双は門前のみ
24
+ if (tehai.exposed.length > 0) {
25
+ return Infinity;
26
+ }
27
+ const dist = countHaiKind(tehai.closed);
28
+ // 么九牌の種類数をカウント
29
+ // 同時に、么九牌の対子が存在するかもチェック
30
+ let uniqueYaochuCount = 0;
31
+ let hasYaochuPair = false;
32
+ for (let i = 0; i < dist.length; i++) {
33
+ const kind = asHaiKindId(i);
34
+ if (!isYaochu(kind)) {
35
+ continue;
36
+ }
37
+ const count = dist[i];
38
+ if (count !== undefined && count > 0) {
39
+ uniqueYaochuCount++;
40
+ if (count >= 2) {
41
+ hasYaochuPair = true;
42
+ }
43
+ }
44
+ }
45
+ const pairBonus = hasYaochuPair ? 1 : 0;
46
+ // シャンテン数 = 13 - (種類の数) - (対子ボーナス)
47
+ return 13 - uniqueYaochuCount - pairBonus;
48
+ }
@@ -0,0 +1,8 @@
1
+ import type { Tehai } from "../../../types";
2
+ /**
3
+ * 面子手(4面子1雀頭)のシャンテン数を計算する
4
+ *
5
+ * @param tehai 手牌 (13枚 or 14枚)
6
+ * @returns シャンテン数
7
+ */
8
+ export declare function calculateMentsuTeShanten(tehai: Tehai): number;
@@ -0,0 +1,129 @@
1
+ import { countHaiKind, validateTehai } from "../../../core/tehai";
2
+ /**
3
+ * 面子手(4面子1雀頭)のシャンテン数を計算する
4
+ *
5
+ * @param tehai 手牌 (13枚 or 14枚)
6
+ * @returns シャンテン数
7
+ */
8
+ export function calculateMentsuTeShanten(tehai) {
9
+ // 防御的プログラミング
10
+ validateTehai(tehai);
11
+ const counts = countHaiKind(tehai.closed);
12
+ // Mutation is required for the algorithm, so we convert to a mutable number array
13
+ const mutableCounts = Array.from(counts);
14
+ const exposedCount = tehai.exposed.length;
15
+ // 基本シャンテン数 (8 - 2 * 面子数)
16
+ let minShanten = 8 - 2 * exposedCount;
17
+ // 1. 雀頭がある場合
18
+ for (let i = 0; i < 34; i++) {
19
+ if ((mutableCounts[i] ?? 0) >= 2) {
20
+ mutableCounts[i] = (mutableCounts[i] ?? 0) - 2;
21
+ const { m, t } = searchMentsu(mutableCounts);
22
+ const currentMentsu = exposedCount + m;
23
+ const effectiveTaatsu = Math.min(4 - currentMentsu, t);
24
+ const shanten = 8 - 2 * currentMentsu - effectiveTaatsu - 1;
25
+ minShanten = Math.min(minShanten, shanten);
26
+ mutableCounts[i] = (mutableCounts[i] ?? 0) + 2;
27
+ }
28
+ }
29
+ // 2. 雀頭がない場合
30
+ {
31
+ const { m, t } = searchMentsu(mutableCounts);
32
+ const currentMentsu = exposedCount + m;
33
+ const effectiveTaatsu = Math.min(4 - currentMentsu, t);
34
+ const shanten = 8 - 2 * currentMentsu - effectiveTaatsu;
35
+ minShanten = Math.min(minShanten, shanten);
36
+ }
37
+ return minShanten;
38
+ }
39
+ /**
40
+ * 面子と塔子の最大数を探索する
41
+ */
42
+ function searchMentsu(counts) {
43
+ let maxScore = -1;
44
+ let bestResult = { m: 0, t: 0 };
45
+ // copies needed because we mutate counts
46
+ const w = [...counts];
47
+ const search = (index, m) => {
48
+ // 34種類すべて見終わったら塔子を数える
49
+ if (index >= 34) {
50
+ const t = countTaatsu(w);
51
+ const score = 2 * m + t;
52
+ if (score > maxScore) {
53
+ maxScore = score;
54
+ bestResult = { m, t };
55
+ }
56
+ return;
57
+ }
58
+ // 牌がない場合は次に進む
59
+ if ((w[index] ?? 0) === 0) {
60
+ search(index + 1, m);
61
+ return;
62
+ }
63
+ // A. 刻子 (3枚) の場合
64
+ if ((w[index] ?? 0) >= 3) {
65
+ w[index] = (w[index] ?? 0) - 3;
66
+ search(index, m + 1);
67
+ w[index] = (w[index] ?? 0) + 3;
68
+ }
69
+ // B. 順子 (3枚) の場合
70
+ if (index < 27) {
71
+ const mod = index % 9;
72
+ if (mod < 7) {
73
+ if ((w[index] ?? 0) > 0 &&
74
+ (w[index + 1] ?? 0) > 0 &&
75
+ (w[index + 2] ?? 0) > 0) {
76
+ w[index] = (w[index] ?? 0) - 1;
77
+ w[index + 1] = (w[index + 1] ?? 0) - 1;
78
+ w[index + 2] = (w[index + 2] ?? 0) - 1;
79
+ search(index, m + 1);
80
+ w[index] = (w[index] ?? 0) + 1;
81
+ w[index + 1] = (w[index + 1] ?? 0) + 1;
82
+ w[index + 2] = (w[index + 2] ?? 0) + 1;
83
+ }
84
+ }
85
+ }
86
+ // C. 面子として使わない場合
87
+ search(index + 1, m);
88
+ };
89
+ search(0, 0);
90
+ return bestResult;
91
+ }
92
+ /**
93
+ * 残った牌から塔子(対子、両面、嵌張、辺張)の数を数える
94
+ */
95
+ function countTaatsu(counts) {
96
+ let taatsu = 0;
97
+ // countsのコピーを作成
98
+ const w = [...counts];
99
+ for (let i = 0; i < 34; i++) {
100
+ if ((w[i] ?? 0) === 0)
101
+ continue;
102
+ // 順子系の塔子 (1枚 + 1枚)
103
+ if (i < 27) {
104
+ const mod = i % 9;
105
+ // 辺張・両面 (i, i+1)
106
+ if (mod < 8) {
107
+ if ((w[i] ?? 0) > 0 && (w[i + 1] ?? 0) > 0) {
108
+ w[i] = (w[i] ?? 0) - 1;
109
+ w[i + 1] = (w[i + 1] ?? 0) - 1;
110
+ taatsu++;
111
+ }
112
+ }
113
+ // 嵌張 (i, i+2)
114
+ if (mod < 7) {
115
+ if ((w[i] ?? 0) > 0 && (w[i + 2] ?? 0) > 0) {
116
+ w[i] = (w[i] ?? 0) - 1;
117
+ w[i + 2] = (w[i + 2] ?? 0) - 1;
118
+ taatsu++;
119
+ }
120
+ }
121
+ }
122
+ // 対子 (2枚)
123
+ if ((w[i] ?? 0) >= 2) {
124
+ w[i] = (w[i] ?? 0) - 2;
125
+ taatsu++;
126
+ }
127
+ }
128
+ return taatsu;
129
+ }
@@ -0,0 +1,13 @@
1
+ import type { YakuHanConfig, YakuName, HouraStructure } from "./types";
2
+ import { YakuDefinition, HouraContext } from "./types";
3
+ /**
4
+ * 役定義を作成するファクトリ関数
5
+ *
6
+ * @param yaku 役の設定情報(名前、翻数など)
7
+ * @param check 役の成立条件を判定する関数 (真偽値を返す)
8
+ * @returns YakuDefinition (isSatisfied, getHansu を持つ)
9
+ */
10
+ export declare function createYakuDefinition(yaku: Readonly<{
11
+ name: YakuName;
12
+ han: YakuHanConfig;
13
+ }>, check: (hand: HouraStructure, context: HouraContext) => boolean): YakuDefinition;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 役定義を作成するファクトリ関数
3
+ *
4
+ * @param yaku 役の設定情報(名前、翻数など)
5
+ * @param check 役の成立条件を判定する関数 (真偽値を返す)
6
+ * @returns YakuDefinition (isSatisfied, getHansu を持つ)
7
+ */
8
+ export function createYakuDefinition(yaku, check) {
9
+ return {
10
+ yaku,
11
+ isSatisfied: check,
12
+ getHansu: (hand, context) => {
13
+ if (!check(hand, context)) {
14
+ return 0;
15
+ }
16
+ return context.isMenzen ? yaku.han.closed : yaku.han.open;
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,21 @@
1
+ import type { Tehai14, HaiKindId } from "../../types";
2
+ import type { YakuResult, HouraStructure } from "./types";
3
+ import type { HouraContext } from "./types";
4
+ export type { HouraStructure, YakuResult, YakuName, Hansu, TehaiYaku, YakuHanConfig, Yakuhai, } from "./types";
5
+ export * from "./lib";
6
+ /**
7
+ * 単一の和了構造に対して役を判定する
8
+ *
9
+ * @param hand 和了構造(面子手、七対子、国士無双のいずれか)
10
+ * @param context 和了コンテキスト
11
+ * @returns 成立した役と翻数のリスト
12
+ */
13
+ export declare function detectYakuForStructure(hand: HouraStructure, context: HouraContext): YakuResult;
14
+ /**
15
+ * 手牌の構造役を検出する
16
+ *
17
+ * @param tehai 判定対象の手牌
18
+ * @param agariHai 和了牌
19
+ * @returns 成立した役と翻数のリスト(最も高得点となる解釈の結果)
20
+ */
21
+ export declare function detectYaku(tehai: Tehai14, agariHai: HaiKindId, bakaze?: HaiKindId, jikaze?: HaiKindId, doraMarkers?: readonly HaiKindId[], uraDoraMarkers?: readonly HaiKindId[], isTsumo?: boolean): YakuResult;
@@ -0,0 +1,59 @@
1
+ import { getHouraStructures } from "./lib/structures";
2
+ import { isMenzen, isKazehai } from "./utils";
3
+ import { ALL_YAKU_DEFINITIONS } from "./lib/definitions";
4
+ export * from "./lib";
5
+ /**
6
+ * 単一の和了構造に対して役を判定する
7
+ *
8
+ * @param hand 和了構造(面子手、七対子、国士無双のいずれか)
9
+ * @param context 和了コンテキスト
10
+ * @returns 成立した役と翻数のリスト
11
+ */
12
+ export function detectYakuForStructure(hand, context) {
13
+ const result = [];
14
+ for (const definition of ALL_YAKU_DEFINITIONS) {
15
+ if (definition.isSatisfied(hand, context)) {
16
+ const hansu = definition.getHansu(hand, context);
17
+ if (hansu === 0)
18
+ continue;
19
+ result.push([definition.yaku.name, hansu]);
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+ /**
25
+ * YakuResult から総翻数を計算する
26
+ */
27
+ function getTotalHan(yakuResult) {
28
+ return yakuResult.reduce((sum, [, han]) => sum + han, 0);
29
+ }
30
+ /**
31
+ * 手牌の構造役を検出する
32
+ *
33
+ * @param tehai 判定対象の手牌
34
+ * @param agariHai 和了牌
35
+ * @returns 成立した役と翻数のリスト(最も高得点となる解釈の結果)
36
+ */
37
+ export function detectYaku(tehai, agariHai, bakaze, jikaze, doraMarkers, uraDoraMarkers, isTsumo) {
38
+ const context = {
39
+ isMenzen: isMenzen(tehai),
40
+ agariHai,
41
+ bakaze: bakaze !== undefined && isKazehai(bakaze) ? bakaze : undefined,
42
+ jikaze: jikaze !== undefined && isKazehai(jikaze) ? jikaze : undefined,
43
+ doraMarkers: doraMarkers ?? [],
44
+ uraDoraMarkers: uraDoraMarkers ?? [],
45
+ isTsumo,
46
+ };
47
+ const structuralInterpretations = getHouraStructures(tehai);
48
+ let bestResult = [];
49
+ let maxHan = -1;
50
+ for (const hand of structuralInterpretations) {
51
+ const currentResult = detectYakuForStructure(hand, context);
52
+ const currentHan = getTotalHan(currentResult);
53
+ if (currentHan > maxHan) {
54
+ maxHan = currentHan;
55
+ bestResult = currentResult;
56
+ }
57
+ }
58
+ return bestResult;
59
+ }
@@ -0,0 +1,2 @@
1
+ import type { YakuDefinition } from "../../types";
2
+ export declare const chiitoitsuDefinition: YakuDefinition;
@@ -0,0 +1,12 @@
1
+ import { createYakuDefinition } from "../../factory";
2
+ const CHIITOITSU_YAKU = {
3
+ name: "Chiitoitsu",
4
+ han: {
5
+ open: 0, // 門前限定
6
+ closed: 2,
7
+ },
8
+ };
9
+ const checkChiitoitsu = (hand) => {
10
+ return hand.type === "Chiitoitsu";
11
+ };
12
+ export const chiitoitsuDefinition = createYakuDefinition(CHIITOITSU_YAKU, checkChiitoitsu);
@@ -0,0 +1,2 @@
1
+ import type { YakuDefinition } from "../../types";
2
+ export declare const chinitsuDefinition: YakuDefinition;
@@ -0,0 +1,40 @@
1
+ import { isSuupai, kindIdToHaiType } from "../../../../core/hai";
2
+ import { HaiType } from "../../../../types";
3
+ import { createYakuDefinition } from "../../factory";
4
+ const CHINITSU_YAKU = {
5
+ name: "Chinitsu",
6
+ han: {
7
+ open: 5,
8
+ closed: 6,
9
+ },
10
+ };
11
+ const checkChinitsu = (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. 字牌が含まれていないこと
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
+ // 数牌が含まれていない(字一色想定だが上記でJihaiチェック済みなので事実上ありえない)場合は不成立
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 chinitsuDefinition = createYakuDefinition(CHINITSU_YAKU, checkChinitsu);
@@ -0,0 +1,2 @@
1
+ import type { YakuDefinition } from "../../types";
2
+ export declare const chinroutouDefinition: YakuDefinition;
@@ -0,0 +1,21 @@
1
+ import { isSuupai, isYaochu } from "../../../../core/hai";
2
+ import { createYakuDefinition } from "../../factory";
3
+ const CHINROUTOU_YAKU = {
4
+ name: "Chinroutou",
5
+ han: {
6
+ open: 13,
7
+ closed: 13,
8
+ },
9
+ };
10
+ const checkChinroutou = (hand) => {
11
+ // 老頭牌は6種類(1m,9m,1p,9p,1s,9s)しかないため、七対子(7種)は成立しない
12
+ if (hand.type !== "Mentsu")
13
+ return false;
14
+ const allBlocks = [hand.jantou, ...hand.fourMentsu];
15
+ // 全てが老頭牌(字牌以外の么九牌)で構成されていること
16
+ const allRoutou = allBlocks.every((block) => block.hais.every((k) => isYaochu(k) && isSuupai(k)));
17
+ if (!allRoutou)
18
+ return false;
19
+ return true;
20
+ };
21
+ export const chinroutouDefinition = createYakuDefinition(CHINROUTOU_YAKU, checkChinroutou);
@@ -0,0 +1,2 @@
1
+ import type { YakuDefinition } from "../../types";
2
+ export declare const chuurenPoutouDefinition: YakuDefinition;