@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,27 @@
1
+ export declare const SCORE_OYA_MULTIPLIER = 1.5;
2
+ export declare const SCORE_BASE_MANGAN = 2000;
3
+ export declare const SCORE_BASE_HANEMAN = 3000;
4
+ export declare const SCORE_BASE_BAIMAN = 4000;
5
+ export declare const SCORE_BASE_SANBAIMAN = 6000;
6
+ export declare const SCORE_BASE_YAKUMAN = 8000;
7
+ /**
8
+ * 満貫以上の判定基準となる翻数
9
+ *
10
+ * "5翻" であれば符数に関わらず満貫以上が確定するため 5 を設定しています。
11
+ *
12
+ * Q: 4翻は満貫ではないのか?
13
+ * A: 4翻でも符数が高ければ(40符以上など)満貫になりますが、以下のようなケースでは満貫(8000点)に届きません。
14
+ * - 七対子 (25符): 6400点
15
+ * - 門前ツモ・愚形など (30符): 7900点 (※切り上げ満貫なしの場合)
16
+ * - 鳴き手・ロン (30符): 7700点 (※切り上げ満貫なしの場合)
17
+ *
18
+ * そのため、4翻以下の場合は計算による基本点が基準値(2000)を超えたかどうかで判定します。
19
+ */
20
+ export declare const HAN_MANGAN = 5;
21
+ export declare const HAN_HANEMAN = 6;
22
+ export declare const HAN_BAIMAN = 8;
23
+ export declare const HAN_SANBAIMAN = 11;
24
+ export declare const HAN_YAKUMAN = 13;
25
+ export declare const HAS_YAKUMAN = 13;
26
+ export declare const HAS_DOUBLE_YAKUMAN = 26;
27
+ export declare const BASE_POINT_LIMIT = 2000;
@@ -0,0 +1,30 @@
1
+ export const SCORE_OYA_MULTIPLIER = 1.5; // 親は1.5倍
2
+ export const SCORE_BASE_MANGAN = 2000;
3
+ export const SCORE_BASE_HANEMAN = 3000;
4
+ export const SCORE_BASE_BAIMAN = 4000;
5
+ export const SCORE_BASE_SANBAIMAN = 6000;
6
+ export const SCORE_BASE_YAKUMAN = 8000;
7
+ /**
8
+ * 満貫以上の判定基準となる翻数
9
+ *
10
+ * "5翻" であれば符数に関わらず満貫以上が確定するため 5 を設定しています。
11
+ *
12
+ * Q: 4翻は満貫ではないのか?
13
+ * A: 4翻でも符数が高ければ(40符以上など)満貫になりますが、以下のようなケースでは満貫(8000点)に届きません。
14
+ * - 七対子 (25符): 6400点
15
+ * - 門前ツモ・愚形など (30符): 7900点 (※切り上げ満貫なしの場合)
16
+ * - 鳴き手・ロン (30符): 7700点 (※切り上げ満貫なしの場合)
17
+ *
18
+ * そのため、4翻以下の場合は計算による基本点が基準値(2000)を超えたかどうかで判定します。
19
+ */
20
+ export const HAN_MANGAN = 5;
21
+ export const HAN_HANEMAN = 6;
22
+ export const HAN_BAIMAN = 8;
23
+ export const HAN_SANBAIMAN = 11;
24
+ export const HAN_YAKUMAN = 13;
25
+ export const HAS_YAKUMAN = 13; // 便宜上の翻数
26
+ export const HAS_DOUBLE_YAKUMAN = 26;
27
+ // 切り上げ満貫の閾値 (30符4翻 = 1920 -> 2000? 60符3翻=1920)
28
+ // 一般的には 2000点(子) / 3000点(親) が満貫の最低点(ベース)
29
+ // 符計算による基本点が 2000 を超えたら満貫
30
+ export const BASE_POINT_LIMIT = 2000;
@@ -0,0 +1,21 @@
1
+ import { type Tehai14 } from "../../types";
2
+ import type { FuResult } from "./lib/fu/types";
3
+ import type { HouraContext } from "../yaku/types";
4
+ import type { ScoreCalculationConfig, ScoreLevel, ScoreResult } from "./types";
5
+ export type { ScoreCalculationConfig, ScoreLevel, ScoreResult };
6
+ /**
7
+ * 手牌とコンテキストから点数を計算する(公開API)
8
+ *
9
+ * 手牌の構造解析を行い、最も高点となる解釈を採用して点数を返します。
10
+ *
11
+ * @param tehai 手牌 (14枚)
12
+ * @param config 点数計算の設定 (場風、自風、ドラなど)
13
+ * @returns 点数計算結果
14
+ */
15
+ export declare function calculateScore(tehai: Tehai14, config: Readonly<ScoreCalculationConfig>): ScoreResult;
16
+ /**
17
+ * 基本的な点数計算ロジック (内部用・テスト用)
18
+ */
19
+ export declare function calculateBasicScore(yakuHansu: number, fuResult: Readonly<FuResult>, dora: number, context: Readonly<HouraContext & {
20
+ isOya: boolean;
21
+ }>): ScoreResult;
@@ -0,0 +1,174 @@
1
+ import { HaiKind } from "../../types";
2
+ import { countDora } from "../../core/dora";
3
+ import { getHouraStructures } from "../yaku/lib/structures";
4
+ import { ALL_YAKU_DEFINITIONS } from "../yaku/lib/definitions";
5
+ import { calculateFu } from "./lib/fu";
6
+ import { isMenzen } from "../yaku/utils";
7
+ import { BASE_POINT_LIMIT, HAN_BAIMAN, HAN_HANEMAN, HAN_MANGAN, HAN_SANBAIMAN, HAN_YAKUMAN, SCORE_BASE_BAIMAN, SCORE_BASE_HANEMAN, SCORE_BASE_MANGAN, SCORE_BASE_SANBAIMAN, SCORE_BASE_YAKUMAN, } from "./constants";
8
+ /**
9
+ * 100点単位で切り上げる
10
+ */
11
+ function ceil100(points) {
12
+ return Math.ceil(points / 100) * 100;
13
+ }
14
+ /**
15
+ * 手牌とコンテキストから点数を計算する(公開API)
16
+ *
17
+ * 手牌の構造解析を行い、最も高点となる解釈を採用して点数を返します。
18
+ *
19
+ * @param tehai 手牌 (14枚)
20
+ * @param config 点数計算の設定 (場風、自風、ドラなど)
21
+ * @returns 点数計算結果
22
+ */
23
+ export function calculateScore(tehai, config) {
24
+ const menzen = isMenzen(tehai);
25
+ const isOya = config.jikaze === HaiKind.Ton;
26
+ const context = {
27
+ isMenzen: menzen,
28
+ agariHai: config.agariHai,
29
+ bakaze: config.bakaze,
30
+ jikaze: config.jikaze,
31
+ isTsumo: config.isTsumo,
32
+ isOya: isOya,
33
+ doraMarkers: config.doraMarkers,
34
+ ...(config.uraDoraMarkers ? { uraDoraMarkers: config.uraDoraMarkers } : {}),
35
+ };
36
+ const structuralInterpretations = getHouraStructures(tehai);
37
+ let bestResult = null;
38
+ let maxTotalPoints = -1;
39
+ for (const hand of structuralInterpretations) {
40
+ // 1. 役の判定
41
+ let yakuHansu = 0;
42
+ let isPinfu = false;
43
+ for (const definition of ALL_YAKU_DEFINITIONS) {
44
+ if (definition.isSatisfied(hand, context)) {
45
+ const h = definition.getHansu(hand, context);
46
+ if (h > 0) {
47
+ yakuHansu += h;
48
+ if (definition.yaku.name === "Pinfu") {
49
+ isPinfu = true;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ // 役がない場合はこの構造は不成立
55
+ if (yakuHansu === 0)
56
+ continue;
57
+ // 2. 符の計算
58
+ const fuResult = calculateFu(hand, context, isPinfu);
59
+ // 3. ドラの計算
60
+ // context.doraMarkers にドラ表示牌が入っている前提
61
+ const dora = countDora(tehai, context.doraMarkers);
62
+ // 4. 点数計算 (基本計算)
63
+ const result = calculateBasicScore(yakuHansu, fuResult, dora, context);
64
+ if (result.points.total > maxTotalPoints) {
65
+ maxTotalPoints = result.points.total;
66
+ bestResult = result;
67
+ }
68
+ }
69
+ if (!bestResult) {
70
+ throw new Error("役が成立していません (No Yaku found)");
71
+ }
72
+ return bestResult;
73
+ }
74
+ /**
75
+ * 基本的な点数計算ロジック (内部用・テスト用)
76
+ */
77
+ export function calculateBasicScore(yakuHansu, fuResult, dora, context) {
78
+ const totalHan = yakuHansu + dora;
79
+ const fu = fuResult.total;
80
+ let basePoints = fu * Math.pow(2, 2 + totalHan);
81
+ let level = "Normal";
82
+ // 満貫以上の判定
83
+ // 5翻以上 は満貫確定
84
+ // 4翻以下でも 基本点が2000を超えたら満貫 (切り上げ満貫採用なら1920->2000)
85
+ // ここでは厳密な計算 (2000以上) とする。※30符4翻は1920なのでNormal、60符3翻は1920、70符3翻(2240)は満貫
86
+ if (totalHan >= HAN_YAKUMAN) {
87
+ // 役満(数え役満)
88
+ // ダブル役満等は呼び出し元でHandを判定して Han=26 とかに固定して渡してもらう想定
89
+ // またはHan=13以上はすべてYakumanとして扱う(シングル)
90
+ // ここでは13以上をYakuman、26以上をDoubleとする簡易判定を入れる
91
+ if (totalHan >= 26) {
92
+ level = "DoubleYakuman"; // 例
93
+ basePoints = SCORE_BASE_YAKUMAN * 2;
94
+ }
95
+ else {
96
+ level = "Yakuman";
97
+ basePoints = SCORE_BASE_YAKUMAN;
98
+ }
99
+ }
100
+ else if (totalHan >= HAN_SANBAIMAN) {
101
+ level = "Sanbaiman";
102
+ basePoints = SCORE_BASE_SANBAIMAN;
103
+ }
104
+ else if (totalHan >= HAN_BAIMAN) {
105
+ level = "Baiman";
106
+ basePoints = SCORE_BASE_BAIMAN;
107
+ }
108
+ else if (totalHan >= HAN_HANEMAN) {
109
+ level = "Haneman";
110
+ basePoints = SCORE_BASE_HANEMAN;
111
+ }
112
+ else if (totalHan >= HAN_MANGAN || basePoints >= BASE_POINT_LIMIT) {
113
+ level = "Mangan";
114
+ basePoints = SCORE_BASE_MANGAN;
115
+ }
116
+ // 支払い計算
117
+ let mainPayment = 0;
118
+ let subPayment = 0;
119
+ let totalScore = 0;
120
+ if (context.isTsumo) {
121
+ // ツモ和了
122
+ if (context.isOya) {
123
+ // 親ツモ: オール (基本点 * 2)
124
+ const pay = ceil100(basePoints * 2);
125
+ mainPayment = pay;
126
+ // ScorePayment型の定義について:
127
+ // main: ロンなら支払い総額、ツモなら親の支払い
128
+ // sub: ツモの時の子の支払い
129
+ // 親ツモの場合、「親の支払い」は発生せず、全員が「子」として支払います。
130
+ // ここでは `main` を「子一人あたりの支払い」として使用します。
131
+ // 親ツモ: 子の支払い(main) * 3
132
+ // 子ツモ: 親の支払い(main) + 子の支払い(sub) * 2
133
+ const childPay = ceil100(basePoints * 2);
134
+ mainPayment = childPay; // 各子の支払い
135
+ subPayment = 0;
136
+ totalScore = childPay * 3;
137
+ }
138
+ else {
139
+ // 子ツモ
140
+ // 親の支払い: 基本点 * 2
141
+ // 子の支払い: 基本点 * 1
142
+ const parentPay = ceil100(basePoints * 2);
143
+ const childPay = ceil100(basePoints * 1);
144
+ mainPayment = parentPay;
145
+ subPayment = childPay;
146
+ totalScore = parentPay + childPay * 2;
147
+ }
148
+ }
149
+ else {
150
+ // ロン和了
151
+ if (context.isOya) {
152
+ // 親ロン: 基本点 * 6
153
+ const pay = ceil100(basePoints * 6);
154
+ mainPayment = pay;
155
+ totalScore = pay;
156
+ }
157
+ else {
158
+ // 子ロン: 基本点 * 4
159
+ const pay = ceil100(basePoints * 4);
160
+ mainPayment = pay;
161
+ totalScore = pay;
162
+ }
163
+ }
164
+ return {
165
+ han: totalHan,
166
+ fu: fu,
167
+ level,
168
+ points: {
169
+ main: mainPayment,
170
+ sub: subPayment,
171
+ total: totalScore,
172
+ },
173
+ };
174
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 符計算に関する定数定義
3
+ */
4
+ export declare const FU_BASE: {
5
+ readonly NORMAL: 20;
6
+ readonly CHIITOITSU: 25;
7
+ readonly KOKUSHI: 20;
8
+ };
9
+ export declare const FU_AGARI: {
10
+ readonly TSUMO: 2;
11
+ readonly MENZEN_RON: 10;
12
+ };
13
+ export declare const FU_JANTOU: {
14
+ readonly YAKUHAI: 2;
15
+ readonly DOUBLE_WIND_CAP: 2;
16
+ };
17
+ export declare const FU_MACHI: {
18
+ readonly KANCHAN: 2;
19
+ readonly PENCHAN: 2;
20
+ readonly TANKI: 2;
21
+ readonly RYANMEN: 0;
22
+ readonly SHANPON: 0;
23
+ };
24
+ export declare const FU_KOUTSU: {
25
+ readonly SUUPAI_OPEN: 2;
26
+ readonly SUUPAI_CLOSED: 4;
27
+ readonly YAOCHU_OPEN: 4;
28
+ readonly YAOCHU_CLOSED: 8;
29
+ };
30
+ export declare const FU_KANTSU: {
31
+ readonly SUUPAI_OPEN: 8;
32
+ readonly SUUPAI_CLOSED: 16;
33
+ readonly YAOCHU_OPEN: 16;
34
+ readonly YAOCHU_CLOSED: 32;
35
+ };
36
+ export declare const FU_PINFU_TSUMO = 20;
37
+ export declare const FU_OPEN_PINFU_GLAZE = 30;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 符計算に関する定数定義
3
+ */
4
+ // 符底 (FuTei)
5
+ export const FU_BASE = {
6
+ NORMAL: 20,
7
+ CHIITOITSU: 25,
8
+ KOKUSHI: 20, // 便宜上
9
+ };
10
+ // 和了符 (AgariFu)
11
+ export const FU_AGARI = {
12
+ TSUMO: 2,
13
+ MENZEN_RON: 10,
14
+ };
15
+ // 雀頭符 (JantouFu)
16
+ export const FU_JANTOU = {
17
+ YAKUHAI: 2,
18
+ // 連風牌の扱い(設定により4符または2符)
19
+ DOUBLE_WIND_CAP: 2,
20
+ };
21
+ // 待ち符 (MachiFu)
22
+ export const FU_MACHI = {
23
+ KANCHAN: 2,
24
+ PENCHAN: 2,
25
+ TANKI: 2,
26
+ RYANMEN: 0,
27
+ SHANPON: 0,
28
+ };
29
+ // 面子符 (MentsuFu)
30
+ // [Yaochu/Suupai]_[Open/Closed]
31
+ export const FU_KOUTSU = {
32
+ SUUPAI_OPEN: 2,
33
+ SUUPAI_CLOSED: 4,
34
+ YAOCHU_OPEN: 4,
35
+ YAOCHU_CLOSED: 8,
36
+ };
37
+ export const FU_KANTSU = {
38
+ SUUPAI_OPEN: 8,
39
+ SUUPAI_CLOSED: 16,
40
+ YAOCHU_OPEN: 16,
41
+ YAOCHU_CLOSED: 32,
42
+ };
43
+ // 例外処理用定数
44
+ export const FU_PINFU_TSUMO = 20;
45
+ export const FU_OPEN_PINFU_GLAZE = 30; // 喰いタン平和形などの20符切り上げ
@@ -0,0 +1,11 @@
1
+ import type { HouraStructure, HouraContext } from "../../../yaku/types";
2
+ import type { FuResult } from "./types";
3
+ /**
4
+ * 手牌の符を計算する (calculateFu)
5
+ *
6
+ * @param hand 和了形の手牌構造
7
+ * @param context 和了コンテキスト (場風、自風、ツモ/ロン等)
8
+ * @param isPinfu 平和が成立しているかどうか (平和ツモ20符例外の適用に必要)
9
+ * @returns 符計算結果
10
+ */
11
+ export declare function calculateFu(hand: HouraStructure, context: HouraContext, isPinfu?: boolean): FuResult;
@@ -0,0 +1,23 @@
1
+ import { calculateChiitoitsuFu } from "./lib/chiitoitsu";
2
+ import { calculateKokushiFu } from "./lib/kokushi";
3
+ import { calculateMentsuFu } from "./lib/mentsu";
4
+ /**
5
+ * 手牌の符を計算する (calculateFu)
6
+ *
7
+ * @param hand 和了形の手牌構造
8
+ * @param context 和了コンテキスト (場風、自風、ツモ/ロン等)
9
+ * @param isPinfu 平和が成立しているかどうか (平和ツモ20符例外の適用に必要)
10
+ * @returns 符計算結果
11
+ */
12
+ export function calculateFu(hand, context, isPinfu = false) {
13
+ // 1. 七対子 (ChiiToitsu)
14
+ if (hand.type === "Chiitoitsu") {
15
+ return calculateChiitoitsuFu();
16
+ }
17
+ // 2. 国士無双 (Kokushi)
18
+ if (hand.type === "Kokushi") {
19
+ return calculateKokushiFu();
20
+ }
21
+ // 3. 面子手 (Mentsu)
22
+ return calculateMentsuFu(hand, context, isPinfu);
23
+ }
@@ -0,0 +1,5 @@
1
+ import type { FuResult } from "../types";
2
+ /**
3
+ * 七対子の符を計算する
4
+ */
5
+ export declare function calculateChiitoitsuFu(): FuResult;
@@ -0,0 +1,17 @@
1
+ import { FU_BASE } from "../constants";
2
+ /**
3
+ * 七対子の符を計算する
4
+ */
5
+ export function calculateChiitoitsuFu() {
6
+ const details = {
7
+ base: FU_BASE.CHIITOITSU,
8
+ mentsu: 0,
9
+ jantou: 0,
10
+ machi: 0,
11
+ agari: 0,
12
+ };
13
+ return {
14
+ total: FU_BASE.CHIITOITSU,
15
+ details,
16
+ };
17
+ }
@@ -0,0 +1,6 @@
1
+ import type { FuResult } from "../types";
2
+ /**
3
+ * 国士無双の符を計算する
4
+ * (点数計算上は役満固定だが、便宜上符底のみを返す)
5
+ */
6
+ export declare function calculateKokushiFu(): FuResult;
@@ -0,0 +1,18 @@
1
+ import { FU_BASE } from "../constants";
2
+ /**
3
+ * 国士無双の符を計算する
4
+ * (点数計算上は役満固定だが、便宜上符底のみを返す)
5
+ */
6
+ export function calculateKokushiFu() {
7
+ const details = {
8
+ base: FU_BASE.KOKUSHI,
9
+ mentsu: 0,
10
+ jantou: 0,
11
+ machi: 0,
12
+ agari: 0,
13
+ };
14
+ return {
15
+ total: FU_BASE.KOKUSHI,
16
+ details,
17
+ };
18
+ }
@@ -0,0 +1,11 @@
1
+ import type { FuResult } from "../types";
2
+ import type { HouraContext } from "../../../../yaku/types";
3
+ import type { MentsuHouraStructure } from "../../../../yaku/types";
4
+ /**
5
+ * 面子手の符を計算する
6
+ *
7
+ * @param hand 面子手の構造
8
+ * @param context 和了コンテキスト
9
+ * @param isPinfu 平和成立フラグ
10
+ */
11
+ export declare function calculateMentsuFu(hand: MentsuHouraStructure, context: HouraContext, isPinfu: boolean): FuResult;
@@ -0,0 +1,122 @@
1
+ import { isYaochu } from "../../../../../core/hai";
2
+ import { HaiKind } from "../../../../../types";
3
+ import { classifyMachi } from "../../../../../core/machi";
4
+ import { FU_BASE, FU_KOUTSU, FU_KANTSU, FU_JANTOU, FU_MACHI, FU_AGARI, FU_PINFU_TSUMO, FU_OPEN_PINFU_GLAZE, } from "../constants";
5
+ /**
6
+ * 面子手の符を計算する
7
+ *
8
+ * @param hand 面子手の構造
9
+ * @param context 和了コンテキスト
10
+ * @param isPinfu 平和成立フラグ
11
+ */
12
+ export function calculateMentsuFu(hand, context, isPinfu) {
13
+ const details = {
14
+ base: FU_BASE.NORMAL,
15
+ mentsu: 0,
16
+ jantou: 0,
17
+ machi: 0,
18
+ agari: 0,
19
+ };
20
+ // 1. 面子符 (MentsuFu)
21
+ for (const mentsu of hand.fourMentsu) {
22
+ let fu = 0;
23
+ const isYaochuMentsu = isYaochu(mentsu.hais[0]);
24
+ if (mentsu.type === "Koutsu") {
25
+ // 刻子
26
+ const openFu = isYaochuMentsu
27
+ ? FU_KOUTSU.YAOCHU_OPEN
28
+ : FU_KOUTSU.SUUPAI_OPEN;
29
+ const closedFu = isYaochuMentsu
30
+ ? FU_KOUTSU.YAOCHU_CLOSED
31
+ : FU_KOUTSU.SUUPAI_CLOSED;
32
+ fu = closedFu;
33
+ // 明刻判定
34
+ let isOpen = !!mentsu.furo;
35
+ if (!isOpen && !context.isTsumo) {
36
+ // ロン和了で、かつその牌を含む刻子であれば明刻扱い
37
+ if (mentsu.hais.includes(context.agariHai)) {
38
+ isOpen = true;
39
+ }
40
+ }
41
+ if (isOpen) {
42
+ fu = openFu;
43
+ }
44
+ details.mentsu += fu;
45
+ }
46
+ else if (mentsu.type === "Kantsu") {
47
+ // 槓子
48
+ const openFu = isYaochuMentsu
49
+ ? FU_KANTSU.YAOCHU_OPEN
50
+ : FU_KANTSU.SUUPAI_OPEN;
51
+ const closedFu = isYaochuMentsu
52
+ ? FU_KANTSU.YAOCHU_CLOSED
53
+ : FU_KANTSU.SUUPAI_CLOSED;
54
+ fu = closedFu;
55
+ if (mentsu.furo) {
56
+ fu = openFu;
57
+ }
58
+ details.mentsu += fu;
59
+ }
60
+ }
61
+ // 2. 雀頭符 (JantouFu)
62
+ const headHai = hand.jantou.hais[0];
63
+ let jantouFu = 0;
64
+ if (headHai === HaiKind.Haku ||
65
+ headHai === HaiKind.Hatsu ||
66
+ headHai === HaiKind.Chun) {
67
+ jantouFu += FU_JANTOU.YAKUHAI;
68
+ }
69
+ if (headHai === context.bakaze) {
70
+ jantouFu += FU_JANTOU.YAKUHAI;
71
+ }
72
+ if (headHai === context.jikaze) {
73
+ jantouFu += FU_JANTOU.YAKUHAI;
74
+ }
75
+ // 連風牌の加算上限
76
+ if (jantouFu > FU_JANTOU.DOUBLE_WIND_CAP) {
77
+ jantouFu = FU_JANTOU.DOUBLE_WIND_CAP;
78
+ }
79
+ details.jantou = jantouFu;
80
+ // 3. 待ち符 (MachiFu)
81
+ const machiType = classifyMachi(hand, context.agariHai);
82
+ if (machiType === "Kanchan")
83
+ details.machi = FU_MACHI.KANCHAN;
84
+ else if (machiType === "Penchan")
85
+ details.machi = FU_MACHI.PENCHAN;
86
+ else if (machiType === "Tanki")
87
+ details.machi = FU_MACHI.TANKI;
88
+ else
89
+ details.machi = 0; // Ryanmen, Shanpon
90
+ // 4. 和了符 (AgariFu)
91
+ if (context.isTsumo) {
92
+ if (!isPinfu) {
93
+ details.agari = FU_AGARI.TSUMO;
94
+ }
95
+ }
96
+ else {
97
+ if (context.isMenzen) {
98
+ details.agari = FU_AGARI.MENZEN_RON;
99
+ }
100
+ }
101
+ // 合計
102
+ let sum = details.base +
103
+ details.mentsu +
104
+ details.jantou +
105
+ details.machi +
106
+ details.agari;
107
+ // 平和ツモ例外
108
+ if (isPinfu && context.isTsumo) {
109
+ return { total: FU_PINFU_TSUMO, details };
110
+ }
111
+ // 切り上げ (喰いタン平和形など)
112
+ if (sum === 20 && !context.isTsumo && !context.isMenzen) {
113
+ sum = FU_OPEN_PINFU_GLAZE;
114
+ }
115
+ else {
116
+ sum = Math.ceil(sum / 10) * 10;
117
+ }
118
+ return {
119
+ total: sum,
120
+ details,
121
+ };
122
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 符の構成要素 (FuDetails)
3
+ *
4
+ * 符計算の内訳を保持するインターフェース。
5
+ */
6
+ export interface FuDetails {
7
+ /**
8
+ * 符底 (FuTei)
9
+ *
10
+ * 一般的な和了形: 20符
11
+ * 七対子: 25符
12
+ */
13
+ readonly base: 20 | 25;
14
+ /**
15
+ * 面子符の合計 (MentsuFu)
16
+ *
17
+ * 刻子・槓子による加算符。順子は0符。
18
+ */
19
+ readonly mentsu: number;
20
+ /**
21
+ * 雀頭符 (JantouFu)
22
+ *
23
+ * 役牌の対子による加算符。
24
+ */
25
+ readonly jantou: number;
26
+ /**
27
+ * 待ち符 (MachiFu)
28
+ *
29
+ * 単騎・嵌張・辺張待ちによる加算符(2符)。
30
+ */
31
+ readonly machi: number;
32
+ /**
33
+ * 和了符 (AgariFu)
34
+ *
35
+ * ツモ和了: 2符
36
+ * 門前ロン: 10符
37
+ */
38
+ readonly agari: number;
39
+ }
40
+ /**
41
+ * 符計算結果 (FuResult)
42
+ */
43
+ export interface FuResult {
44
+ /**
45
+ * 最終的な符数 (Total)
46
+ *
47
+ * 内訳の合計を10符単位で切り上げたもの。
48
+ * (例: 22符 -> 30符)
49
+ */
50
+ readonly total: number;
51
+ /**
52
+ * 計算の内訳
53
+ */
54
+ readonly details: FuDetails;
55
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { HaiKindId, Kazehai } from "../../types";
2
+ export interface ScoreCalculationConfig {
3
+ /** 和了牌 */
4
+ agariHai: HaiKindId;
5
+ /** ツモ和了かどうか (必須) */
6
+ isTsumo: boolean;
7
+ /** 自風 (必須) */
8
+ jikaze: Kazehai;
9
+ /** 場風 (必須) */
10
+ bakaze: Kazehai;
11
+ /** ドラ表示牌 (必須、なければ空配列) */
12
+ doraMarkers: readonly HaiKindId[];
13
+ /** 裏ドラ表示牌 (任意) */
14
+ uraDoraMarkers?: readonly HaiKindId[];
15
+ }
16
+ export type ScoreLevel = "Normal" | "Mangan" | "Haneman" | "Baiman" | "Sanbaiman" | "Yakuman" | "DoubleYakuman" | "TripleYakuman";
17
+ export interface ScorePayment {
18
+ main: number;
19
+ sub?: number;
20
+ total: number;
21
+ }
22
+ export interface ScoreResult {
23
+ han: number;
24
+ fu: number;
25
+ level: ScoreLevel;
26
+ points: ScorePayment;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ export declare const SCORE_OYA_MULTIPLIER = 1.5;
2
+ export declare const SCORE_BASE_MANGAN = 2000;
3
+ export declare const SCORE_BASE_HANEMAN = 3000;
4
+ export declare const SCORE_BASE_BAIMAN = 4000;
5
+ export declare const SCORE_BASE_SANBAIMAN = 6000;
6
+ export declare const SCORE_BASE_YAKUMAN = 8000;
7
+ /**
8
+ * 満貫以上の判定基準となる翻数
9
+ *
10
+ * "5翻" であれば符数に関わらず満貫以上が確定するため 5 を設定しています。
11
+ *
12
+ * Q: 4翻は満貫ではないのか?
13
+ * A: 4翻でも符数が高ければ(40符以上など)満貫になりますが、以下のようなケースでは満貫(8000点)に届きません。
14
+ * - 七対子 (25符): 6400点
15
+ * - 門前ツモ・愚形など (30符): 7900点 (※切り上げ満貫なしの場合)
16
+ * - 鳴き手・ロン (30符): 7700点 (※切り上げ満貫なしの場合)
17
+ *
18
+ * そのため、4翻以下の場合は計算による基本点が基準値(2000)を超えたかどうかで判定します。
19
+ */
20
+ export declare const HAN_MANGAN = 5;
21
+ export declare const HAN_HANEMAN = 6;
22
+ export declare const HAN_BAIMAN = 8;
23
+ export declare const HAN_SANBAIMAN = 11;
24
+ export declare const HAN_YAKUMAN = 13;
25
+ export declare const HAS_YAKUMAN = 13;
26
+ export declare const HAS_DOUBLE_YAKUMAN = 26;
27
+ export declare const BASE_SCORE_LIMIT = 2000;