@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.
- package/README.md +3 -0
- package/dist/core/tehai.d.ts +4 -0
- package/dist/core/tehai.js +70 -1
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +59 -0
- package/dist/features/machi/index.js +11 -0
- package/dist/features/points/constants.d.ts +5 -5
- package/dist/features/points/constants.js +7 -7
- package/dist/features/points/index.js +3 -3
- package/dist/features/score/constants.d.ts +27 -0
- package/dist/features/score/constants.js +30 -0
- package/dist/features/score/index.d.ts +45 -0
- package/dist/features/score/index.js +207 -0
- package/dist/features/score/lib/fu/constants.d.ts +37 -0
- package/dist/features/score/lib/fu/constants.js +45 -0
- package/dist/features/score/lib/fu/index.d.ts +11 -0
- package/dist/features/score/lib/fu/index.js +23 -0
- package/dist/features/score/lib/fu/lib/chiitoitsu.d.ts +5 -0
- package/dist/features/score/lib/fu/lib/chiitoitsu.js +17 -0
- package/dist/features/score/lib/fu/lib/kokushi.d.ts +6 -0
- package/dist/features/score/lib/fu/lib/kokushi.js +18 -0
- package/dist/features/score/lib/fu/lib/mentsu.d.ts +11 -0
- package/dist/features/score/lib/fu/lib/mentsu.js +136 -0
- package/dist/features/score/lib/fu/types.d.ts +56 -0
- package/dist/features/score/lib/fu/types.js +1 -0
- package/dist/features/score/types.d.ts +78 -0
- package/dist/features/score/types.js +22 -0
- package/dist/features/yaku/factory.d.ts +1 -1
- package/dist/features/yaku/factory.js +1 -1
- package/dist/features/yaku/index.d.ts +11 -2
- package/dist/features/yaku/index.js +30 -33
- package/dist/features/yaku/lib/definitions/daisuushii.js +1 -1
- package/dist/features/yaku/lib/definitions/iipeikou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/{iipeiko.js → iipeikou.js} +2 -2
- package/dist/features/yaku/lib/definitions/index.d.ts +2 -2
- package/dist/features/yaku/lib/definitions/index.js +5 -5
- package/dist/features/yaku/types.d.ts +6 -6
- package/dist/index.d.ts +12 -3
- package/dist/index.js +45 -1
- package/dist/types.d.ts +10 -0
- package/package.json +3 -2
- package/dist/features/yaku/lib/definitions/iipeiko.d.ts +0 -2
- /package/dist/features/yaku/lib/definitions/{ryanpeiko.d.ts → ryanpeikou.d.ts} +0 -0
- /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,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,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
|
+
};
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
}
|
|
@@ -6,7 +6,7 @@ const IIPEIKO_YAKU = {
|
|
|
6
6
|
closed: 1,
|
|
7
7
|
},
|
|
8
8
|
};
|
|
9
|
-
const
|
|
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
|
|
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 "./
|
|
5
|
-
export * from "./
|
|
4
|
+
export * from "./iipeikou";
|
|
5
|
+
export * from "./ryanpeikou";
|
|
6
6
|
export * from "./sanankou";
|
|
7
7
|
export * from "./suuankou";
|
|
8
8
|
export * from "./sankantsu";
|