@pai-forge/riichi-mahjong 0.1.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 (44) hide show
  1. package/README.md +3 -0
  2. package/dist/core/tehai.d.ts +4 -0
  3. package/dist/core/tehai.js +70 -1
  4. package/dist/errors.d.ts +43 -0
  5. package/dist/errors.js +59 -0
  6. package/dist/features/machi/index.js +11 -0
  7. package/dist/features/points/constants.d.ts +5 -5
  8. package/dist/features/points/constants.js +7 -7
  9. package/dist/features/points/index.js +3 -3
  10. package/dist/features/score/constants.d.ts +27 -0
  11. package/dist/features/score/constants.js +30 -0
  12. package/dist/features/score/index.d.ts +45 -0
  13. package/dist/features/score/index.js +207 -0
  14. package/dist/features/score/lib/fu/constants.d.ts +37 -0
  15. package/dist/features/score/lib/fu/constants.js +45 -0
  16. package/dist/features/score/lib/fu/index.d.ts +11 -0
  17. package/dist/features/score/lib/fu/index.js +23 -0
  18. package/dist/features/score/lib/fu/lib/chiitoitsu.d.ts +5 -0
  19. package/dist/features/score/lib/fu/lib/chiitoitsu.js +17 -0
  20. package/dist/features/score/lib/fu/lib/kokushi.d.ts +6 -0
  21. package/dist/features/score/lib/fu/lib/kokushi.js +18 -0
  22. package/dist/features/score/lib/fu/lib/mentsu.d.ts +11 -0
  23. package/dist/features/score/lib/fu/lib/mentsu.js +136 -0
  24. package/dist/features/score/lib/fu/types.d.ts +56 -0
  25. package/dist/features/score/lib/fu/types.js +1 -0
  26. package/dist/features/score/types.d.ts +78 -0
  27. package/dist/features/score/types.js +22 -0
  28. package/dist/features/yaku/factory.d.ts +1 -1
  29. package/dist/features/yaku/factory.js +1 -1
  30. package/dist/features/yaku/index.d.ts +11 -2
  31. package/dist/features/yaku/index.js +30 -33
  32. package/dist/features/yaku/lib/definitions/daisuushii.js +1 -1
  33. package/dist/features/yaku/lib/definitions/iipeikou.d.ts +2 -0
  34. package/dist/features/yaku/lib/definitions/{iipeiko.js → iipeikou.js} +2 -2
  35. package/dist/features/yaku/lib/definitions/index.d.ts +2 -2
  36. package/dist/features/yaku/lib/definitions/index.js +5 -5
  37. package/dist/features/yaku/types.d.ts +6 -6
  38. package/dist/index.d.ts +12 -3
  39. package/dist/index.js +45 -1
  40. package/dist/types.d.ts +10 -0
  41. package/package.json +3 -2
  42. package/dist/features/yaku/lib/definitions/iipeiko.d.ts +0 -2
  43. /package/dist/features/yaku/lib/definitions/{ryanpeiko.d.ts → ryanpeikou.d.ts} +0 -0
  44. /package/dist/features/yaku/lib/definitions/{ryanpeiko.js → ryanpeikou.js} +0 -0
@@ -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,136 @@
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
+ const VALID_FU_VALUES = [
7
+ 20, 25, 30, 40, 50, 60, 70, 80, 90, 100, 110,
8
+ ];
9
+ /**
10
+ * 数値を Fu 型に変換する(無効な値の場合はエラー)
11
+ */
12
+ function toFu(value) {
13
+ const fu = VALID_FU_VALUES.find((f) => f === value);
14
+ if (fu === undefined) {
15
+ throw new Error(`Invalid fu value: ${value}`);
16
+ }
17
+ return fu;
18
+ }
19
+ /**
20
+ * 面子手の符を計算する
21
+ *
22
+ * @param hand 面子手の構造
23
+ * @param context 和了コンテキスト
24
+ * @param isPinfu 平和成立フラグ
25
+ */
26
+ export function calculateMentsuFu(hand, context, isPinfu) {
27
+ const details = {
28
+ base: FU_BASE.NORMAL,
29
+ mentsu: 0,
30
+ jantou: 0,
31
+ machi: 0,
32
+ agari: 0,
33
+ };
34
+ // 1. 面子符 (MentsuFu)
35
+ for (const mentsu of hand.fourMentsu) {
36
+ let fu = 0;
37
+ const isYaochuMentsu = isYaochu(mentsu.hais[0]);
38
+ if (mentsu.type === "Koutsu") {
39
+ // 刻子
40
+ const openFu = isYaochuMentsu
41
+ ? FU_KOUTSU.YAOCHU_OPEN
42
+ : FU_KOUTSU.SUUPAI_OPEN;
43
+ const closedFu = isYaochuMentsu
44
+ ? FU_KOUTSU.YAOCHU_CLOSED
45
+ : FU_KOUTSU.SUUPAI_CLOSED;
46
+ fu = closedFu;
47
+ // 明刻判定
48
+ let isOpen = !!mentsu.furo;
49
+ if (!isOpen && !context.isTsumo) {
50
+ // ロン和了で、かつその牌を含む刻子であれば明刻扱い
51
+ if (mentsu.hais.includes(context.agariHai)) {
52
+ isOpen = true;
53
+ }
54
+ }
55
+ if (isOpen) {
56
+ fu = openFu;
57
+ }
58
+ details.mentsu += fu;
59
+ }
60
+ else if (mentsu.type === "Kantsu") {
61
+ // 槓子
62
+ const openFu = isYaochuMentsu
63
+ ? FU_KANTSU.YAOCHU_OPEN
64
+ : FU_KANTSU.SUUPAI_OPEN;
65
+ const closedFu = isYaochuMentsu
66
+ ? FU_KANTSU.YAOCHU_CLOSED
67
+ : FU_KANTSU.SUUPAI_CLOSED;
68
+ fu = closedFu;
69
+ if (mentsu.furo) {
70
+ fu = openFu;
71
+ }
72
+ details.mentsu += fu;
73
+ }
74
+ }
75
+ // 2. 雀頭符 (JantouFu)
76
+ const headHai = hand.jantou.hais[0];
77
+ let jantouFu = 0;
78
+ if (headHai === HaiKind.Haku ||
79
+ headHai === HaiKind.Hatsu ||
80
+ headHai === HaiKind.Chun) {
81
+ jantouFu += FU_JANTOU.YAKUHAI;
82
+ }
83
+ if (headHai === context.bakaze) {
84
+ jantouFu += FU_JANTOU.YAKUHAI;
85
+ }
86
+ if (headHai === context.jikaze) {
87
+ jantouFu += FU_JANTOU.YAKUHAI;
88
+ }
89
+ // 連風牌の加算上限
90
+ if (jantouFu > FU_JANTOU.DOUBLE_WIND_CAP) {
91
+ jantouFu = FU_JANTOU.DOUBLE_WIND_CAP;
92
+ }
93
+ details.jantou = jantouFu;
94
+ // 3. 待ち符 (MachiFu)
95
+ const machiType = classifyMachi(hand, context.agariHai);
96
+ if (machiType === "Kanchan")
97
+ details.machi = FU_MACHI.KANCHAN;
98
+ else if (machiType === "Penchan")
99
+ details.machi = FU_MACHI.PENCHAN;
100
+ else if (machiType === "Tanki")
101
+ details.machi = FU_MACHI.TANKI;
102
+ else
103
+ details.machi = 0; // Ryanmen, Shanpon
104
+ // 4. 和了符 (AgariFu)
105
+ if (context.isTsumo) {
106
+ if (!isPinfu) {
107
+ details.agari = FU_AGARI.TSUMO;
108
+ }
109
+ }
110
+ else {
111
+ if (context.isMenzen) {
112
+ details.agari = FU_AGARI.MENZEN_RON;
113
+ }
114
+ }
115
+ // 合計
116
+ let sum = details.base +
117
+ details.mentsu +
118
+ details.jantou +
119
+ details.machi +
120
+ details.agari;
121
+ // 平和ツモ例外
122
+ if (isPinfu && context.isTsumo) {
123
+ return { total: FU_PINFU_TSUMO, details };
124
+ }
125
+ // 切り上げ (喰いタン平和形など)
126
+ if (sum === 20 && !context.isTsumo && !context.isMenzen) {
127
+ sum = FU_OPEN_PINFU_GLAZE;
128
+ }
129
+ else {
130
+ sum = Math.ceil(sum / 10) * 10;
131
+ }
132
+ return {
133
+ total: toFu(sum),
134
+ details,
135
+ };
136
+ }
@@ -0,0 +1,56 @@
1
+ import type { Fu } from "../../../../types";
2
+ /**
3
+ * 符の構成要素 (FuDetails)
4
+ *
5
+ * 符計算の内訳を保持するインターフェース。
6
+ */
7
+ export interface FuDetails {
8
+ /**
9
+ * 符底 (FuTei)
10
+ *
11
+ * 一般的な和了形: 20符
12
+ * 七対子: 25符
13
+ */
14
+ readonly base: 20 | 25;
15
+ /**
16
+ * 面子符の合計 (MentsuFu)
17
+ *
18
+ * 刻子・槓子による加算符。順子は0符。
19
+ */
20
+ readonly mentsu: number;
21
+ /**
22
+ * 雀頭符 (JantouFu)
23
+ *
24
+ * 役牌の対子による加算符。
25
+ */
26
+ readonly jantou: number;
27
+ /**
28
+ * 待ち符 (MachiFu)
29
+ *
30
+ * 単騎・嵌張・辺張待ちによる加算符(2符)。
31
+ */
32
+ readonly machi: number;
33
+ /**
34
+ * 和了符 (AgariFu)
35
+ *
36
+ * ツモ和了: 2符
37
+ * 門前ロン: 10符
38
+ */
39
+ readonly agari: number;
40
+ }
41
+ /**
42
+ * 符計算結果 (FuResult)
43
+ */
44
+ export interface FuResult {
45
+ /**
46
+ * 最終的な符数 (Total)
47
+ *
48
+ * 内訳の合計を10符単位で切り上げたもの。
49
+ * (例: 22符 -> 30符)
50
+ */
51
+ readonly total: Fu;
52
+ /**
53
+ * 計算の内訳
54
+ */
55
+ readonly details: FuDetails;
56
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -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
+ };
@@ -3,7 +3,7 @@ import { YakuDefinition, HouraContext } from "./types";
3
3
  /**
4
4
  * 役定義を作成するファクトリ関数
5
5
  *
6
- * @param yaku 役の設定情報(名前、飜数など)
6
+ * @param yaku 役の設定情報(名前、翻数など)
7
7
  * @param check 役の成立条件を判定する関数 (真偽値を返す)
8
8
  * @returns YakuDefinition (isSatisfied, getHansu を持つ)
9
9
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 役定義を作成するファクトリ関数
3
3
  *
4
- * @param yaku 役の設定情報(名前、飜数など)
4
+ * @param yaku 役の設定情報(名前、翻数など)
5
5
  * @param check 役の成立条件を判定する関数 (真偽値を返す)
6
6
  * @returns YakuDefinition (isSatisfied, getHansu を持つ)
7
7
  */
@@ -1,12 +1,21 @@
1
1
  import type { Tehai14, HaiKindId } from "../../types";
2
- import type { YakuResult } from "./types";
2
+ import type { YakuResult, HouraStructure } from "./types";
3
+ import type { HouraContext } from "./types";
3
4
  export type { HouraStructure, YakuResult, YakuName, Hansu, TehaiYaku, YakuHanConfig, Yakuhai, } from "./types";
4
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;
5
14
  /**
6
15
  * 手牌の構造役を検出する
7
16
  *
8
17
  * @param tehai 判定対象の手牌
9
18
  * @param agariHai 和了牌
10
- * @returns 成立した役と飜数のリスト(最も高得点となる解釈の結果)
19
+ * @returns 成立した役と翻数のリスト(最も高得点となる解釈の結果)
11
20
  */
12
21
  export declare function detectYaku(tehai: Tehai14, agariHai: HaiKindId, bakaze?: HaiKindId, jikaze?: HaiKindId, doraMarkers?: readonly HaiKindId[], uraDoraMarkers?: readonly HaiKindId[], isTsumo?: boolean): YakuResult;
@@ -2,18 +2,41 @@ import { getHouraStructures } from "./lib/structures";
2
2
  import { isMenzen, isKazehai } from "./utils";
3
3
  import { ALL_YAKU_DEFINITIONS } from "./lib/definitions";
4
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
+ }
5
30
  /**
6
31
  * 手牌の構造役を検出する
7
32
  *
8
33
  * @param tehai 判定対象の手牌
9
34
  * @param agariHai 和了牌
10
- * @returns 成立した役と飜数のリスト(最も高得点となる解釈の結果)
35
+ * @returns 成立した役と翻数のリスト(最も高得点となる解釈の結果)
11
36
  */
12
37
  export function detectYaku(tehai, agariHai, bakaze, jikaze, doraMarkers, uraDoraMarkers, isTsumo) {
13
- // 1. 基本情報の抽出
14
- const isMenzenValue = isMenzen(tehai);
15
38
  const context = {
16
- isMenzen: isMenzenValue,
39
+ isMenzen: isMenzen(tehai),
17
40
  agariHai,
18
41
  bakaze: bakaze !== undefined && isKazehai(bakaze) ? bakaze : undefined,
19
42
  jikaze: jikaze !== undefined && isKazehai(jikaze) ? jikaze : undefined,
@@ -21,42 +44,16 @@ export function detectYaku(tehai, agariHai, bakaze, jikaze, doraMarkers, uraDora
21
44
  uraDoraMarkers: uraDoraMarkers ?? [],
22
45
  isTsumo,
23
46
  };
47
+ const structuralInterpretations = getHouraStructures(tehai);
24
48
  let bestResult = [];
25
49
  let maxHan = -1;
26
- // 2. 手牌の構造分解(面子手、七対子、国士無双)と役判定
27
- const structuralInterpretations = getHouraStructures(tehai);
28
50
  for (const hand of structuralInterpretations) {
29
- const currentResult = [];
30
- let currentHan = 0;
31
- for (const definition of ALL_YAKU_DEFINITIONS) {
32
- if (definition.isSatisfied(hand, context)) {
33
- const hansu = definition.getHansu(hand, context);
34
- // 喰い下がり0の場合は不成立 (getHansuが0を返すはずだが念のため)
35
- if (hansu === 0)
36
- continue;
37
- // TODO: 役牌のダブル役(ダブ東など)の場合、現状の YakuhaiDefinition は Han * count を返すが、
38
- // YakuResult の形式としては [Name, TotalHan] なのか [Name, Han], [Name, Han] なのか議論が必要。
39
- // 現状の実装: [checker.yaku.name, hanValue] を count 回 push していた。
40
- // 新しい getHansu は total han を返すため、1回 push すればよいのか?
41
- // しかし役の数え方としては「役牌:白」「役牌:發」は別だが、「ダブ東」は「役牌:東」が2つ?
42
- // 一般的には「自風 東」「場風 東」という2つの役扱い。
43
- // しかしここでは YakuhaiDefinition が汎用的になっている。
44
- // ユーザー要望の simple refactor に従い、getHansu が返す値をそのまま1つの役として扱う。
45
- // ただし Yakuhai の getHansu は (open * count) を返しているので、
46
- // ダブ東なら 2飜 の役が1つ、という扱いになる。
47
- currentResult.push([definition.yaku.name, hansu]);
48
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
49
- currentHan += hansu;
50
- }
51
- }
51
+ const currentResult = detectYakuForStructure(hand, context);
52
+ const currentHan = getTotalHan(currentResult);
52
53
  if (currentHan > maxHan) {
53
54
  maxHan = currentHan;
54
55
  bestResult = currentResult;
55
56
  }
56
57
  }
57
- // どの構造としても解釈できない、または役が成立しない場合は空配列
58
- if (maxHan === -1) {
59
- return [];
60
- }
61
58
  return bestResult;
62
59
  }
@@ -3,7 +3,7 @@ import { HaiKind } from "../../../../types";
3
3
  const DAISUUSHII_YAKU = {
4
4
  name: "Daisuushii",
5
5
  han: {
6
- // TODO: ダブル役満(26飜)とするかはルールによるため、一旦通常の役満として実装
6
+ // TODO: ダブル役満(26翻)とするかはルールによるため、一旦通常の役満として実装
7
7
  open: 13,
8
8
  closed: 13,
9
9
  },
@@ -0,0 +1,2 @@
1
+ import type { YakuDefinition } from "../../types";
2
+ export declare const iipeikouDefinition: YakuDefinition;
@@ -6,7 +6,7 @@ const IIPEIKO_YAKU = {
6
6
  closed: 1,
7
7
  },
8
8
  };
9
- const checkIipeiko = (hand) => {
9
+ const checkIipeikou = (hand) => {
10
10
  if (hand.type !== "Mentsu") {
11
11
  return false;
12
12
  }
@@ -43,4 +43,4 @@ const checkIipeiko = (hand) => {
43
43
  }
44
44
  return false;
45
45
  };
46
- export const iipeikoDefinition = createYakuDefinition(IIPEIKO_YAKU, checkIipeiko);
46
+ export const iipeikouDefinition = createYakuDefinition(IIPEIKO_YAKU, checkIipeikou);
@@ -1,8 +1,8 @@
1
1
  import type { YakuDefinition } from "../../types";
2
2
  export * from "./tanyao";
3
3
  export * from "./pinfu";
4
- export * from "./iipeiko";
5
- export * from "./ryanpeiko";
4
+ export * from "./iipeikou";
5
+ export * from "./ryanpeikou";
6
6
  export * from "./sanankou";
7
7
  export * from "./suuankou";
8
8
  export * from "./sankantsu";