@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.
- package/dist/core/dora.d.ts +14 -0
- package/dist/core/dora.js +71 -0
- package/dist/core/hai.d.ts +30 -0
- package/dist/core/hai.js +78 -0
- package/dist/core/machi.d.ts +11 -0
- package/dist/core/machi.js +58 -0
- package/dist/core/mentsu.d.ts +26 -0
- package/dist/core/mentsu.js +87 -0
- package/dist/core/tehai.d.ts +42 -0
- package/dist/core/tehai.js +156 -0
- package/dist/errors.d.ts +83 -0
- package/dist/errors.js +117 -0
- package/dist/features/machi/index.d.ts +9 -0
- package/dist/features/machi/index.js +48 -0
- package/dist/features/machi/types.d.ts +1 -0
- package/dist/features/machi/types.js +1 -0
- package/dist/features/parser/index.d.ts +19 -0
- package/dist/features/parser/index.js +28 -0
- package/dist/features/parser/mspz.d.ts +85 -0
- package/dist/features/parser/mspz.js +365 -0
- package/dist/features/points/constants.d.ts +27 -0
- package/dist/features/points/constants.js +30 -0
- package/dist/features/points/index.d.ts +21 -0
- package/dist/features/points/index.js +174 -0
- package/dist/features/points/lib/fu/constants.d.ts +37 -0
- package/dist/features/points/lib/fu/constants.js +45 -0
- package/dist/features/points/lib/fu/index.d.ts +11 -0
- package/dist/features/points/lib/fu/index.js +23 -0
- package/dist/features/points/lib/fu/lib/chiitoitsu.d.ts +5 -0
- package/dist/features/points/lib/fu/lib/chiitoitsu.js +17 -0
- package/dist/features/points/lib/fu/lib/kokushi.d.ts +6 -0
- package/dist/features/points/lib/fu/lib/kokushi.js +18 -0
- package/dist/features/points/lib/fu/lib/mentsu.d.ts +11 -0
- package/dist/features/points/lib/fu/lib/mentsu.js +122 -0
- package/dist/features/points/lib/fu/types.d.ts +55 -0
- package/dist/features/points/lib/fu/types.js +1 -0
- package/dist/features/points/types.d.ts +27 -0
- package/dist/features/points/types.js +1 -0
- 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/shanten/index.d.ts +16 -0
- package/dist/features/shanten/index.js +25 -0
- package/dist/features/shanten/logic/chiitoitsu.d.ts +8 -0
- package/dist/features/shanten/logic/chiitoitsu.js +36 -0
- package/dist/features/shanten/logic/kokushi.d.ts +16 -0
- package/dist/features/shanten/logic/kokushi.js +48 -0
- package/dist/features/shanten/logic/mentsu-te.d.ts +8 -0
- package/dist/features/shanten/logic/mentsu-te.js +129 -0
- package/dist/features/yaku/factory.d.ts +13 -0
- package/dist/features/yaku/factory.js +19 -0
- package/dist/features/yaku/index.d.ts +21 -0
- package/dist/features/yaku/index.js +59 -0
- package/dist/features/yaku/lib/definitions/chiitoitsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chiitoitsu.js +12 -0
- package/dist/features/yaku/lib/definitions/chinitsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chinitsu.js +40 -0
- package/dist/features/yaku/lib/definitions/chinroutou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chinroutou.js +21 -0
- package/dist/features/yaku/lib/definitions/chuuren-poutou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/chuuren-poutou.js +69 -0
- package/dist/features/yaku/lib/definitions/daisangen.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/daisangen.js +26 -0
- package/dist/features/yaku/lib/definitions/daisuushii.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/daisuushii.js +32 -0
- package/dist/features/yaku/lib/definitions/honchan.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/honchan.js +29 -0
- package/dist/features/yaku/lib/definitions/honitsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/honitsu.js +40 -0
- package/dist/features/yaku/lib/definitions/honroutou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/honroutou.js +33 -0
- package/dist/features/yaku/lib/definitions/iipeikou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/iipeikou.js +46 -0
- package/dist/features/yaku/lib/definitions/ikkitsuukan.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ikkitsuukan.js +56 -0
- package/dist/features/yaku/lib/definitions/index.d.ts +30 -0
- package/dist/features/yaku/lib/definitions/index.js +90 -0
- package/dist/features/yaku/lib/definitions/junchan.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/junchan.js +25 -0
- package/dist/features/yaku/lib/definitions/kokushi.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/kokushi.js +12 -0
- package/dist/features/yaku/lib/definitions/menzen-tsumo.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/menzen-tsumo.js +8 -0
- package/dist/features/yaku/lib/definitions/pinfu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/pinfu.js +40 -0
- package/dist/features/yaku/lib/definitions/ryanpeikou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ryanpeikou.js +33 -0
- package/dist/features/yaku/lib/definitions/ryuuiisou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/ryuuiisou.js +43 -0
- package/dist/features/yaku/lib/definitions/sanankou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sanankou.js +49 -0
- package/dist/features/yaku/lib/definitions/sankantsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sankantsu.js +18 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doujun.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doujun.js +58 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doukou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/sanshoku-doukou.js +53 -0
- package/dist/features/yaku/lib/definitions/shousangen.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/shousangen.js +28 -0
- package/dist/features/yaku/lib/definitions/shousuushii.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/shousuushii.js +34 -0
- package/dist/features/yaku/lib/definitions/suuankou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/suuankou.js +63 -0
- package/dist/features/yaku/lib/definitions/suukantsu.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/suukantsu.js +18 -0
- package/dist/features/yaku/lib/definitions/tanyao.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/tanyao.js +23 -0
- package/dist/features/yaku/lib/definitions/toitoi.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/toitoi.js +16 -0
- package/dist/features/yaku/lib/definitions/tsuuiisou.d.ts +2 -0
- package/dist/features/yaku/lib/definitions/tsuuiisou.js +35 -0
- package/dist/features/yaku/lib/definitions/yakuhai.d.ts +4 -0
- package/dist/features/yaku/lib/definitions/yakuhai.js +21 -0
- package/dist/features/yaku/lib/index.d.ts +1 -0
- package/dist/features/yaku/lib/index.js +1 -0
- package/dist/features/yaku/lib/structures/chiitoitsu.d.ts +6 -0
- package/dist/features/yaku/lib/structures/chiitoitsu.js +38 -0
- package/dist/features/yaku/lib/structures/index.d.ts +10 -0
- package/dist/features/yaku/lib/structures/index.js +17 -0
- package/dist/features/yaku/lib/structures/kokushi.d.ts +6 -0
- package/dist/features/yaku/lib/structures/kokushi.js +43 -0
- package/dist/features/yaku/lib/structures/mentsu-te.d.ts +24 -0
- package/dist/features/yaku/lib/structures/mentsu-te.js +127 -0
- package/dist/features/yaku/types.d.ts +121 -0
- package/dist/features/yaku/types.js +1 -0
- package/dist/features/yaku/utils.d.ts +19 -0
- package/dist/features/yaku/utils.js +34 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +53 -0
- package/dist/types.d.ts +290 -0
- package/dist/types.js +97 -0
- package/dist/utils/assertions.d.ts +22 -0
- package/dist/utils/assertions.js +33 -0
- package/dist/utils/test-helpers.d.ts +55 -0
- package/dist/utils/test-helpers.js +124 -0
- package/package.json +3 -2
|
@@ -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_SCORE_LIMIT = 2000;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Tehai14, type Fu } from "../../types";
|
|
2
|
+
import type { FuResult } from "./lib/fu/types";
|
|
3
|
+
import { ScoreLevel, type ScoreCalculationConfig, type ScoreContext, type ScoreResult, type Payment, type Ron, type KoTsumo, type OyaTsumo } from "./types";
|
|
4
|
+
export type { ScoreCalculationConfig, ScoreResult, Payment, Ron, KoTsumo, OyaTsumo, };
|
|
5
|
+
export { ScoreLevel };
|
|
6
|
+
/**
|
|
7
|
+
* 基本点を計算する
|
|
8
|
+
* 基本点 = 符 × 2^(2+翻)
|
|
9
|
+
*
|
|
10
|
+
* 基本点は子がツモ和了したときの他の子1人あたりの支払い点数に相当する。
|
|
11
|
+
* - 子ツモ: 子の支払い = 基本点, 親の支払い = 基本点 × 2
|
|
12
|
+
* - 子ロン: 支払い = 基本点 × 4
|
|
13
|
+
* - 親ツモ: 各子の支払い = 基本点 × 2
|
|
14
|
+
* - 親ロン: 支払い = 基本点 × 6
|
|
15
|
+
*/
|
|
16
|
+
export declare function calculateBasePoints(fu: Fu, han: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* 支払い情報から和了者が受け取る総点数を計算する
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPaymentTotal(payment: Readonly<Payment>): number;
|
|
21
|
+
/**
|
|
22
|
+
* 翻数と基本点から点数レベルを判定する
|
|
23
|
+
*
|
|
24
|
+
* @param han 翻数
|
|
25
|
+
* @param basePoints 基本点(符 × 2^(2+翻))
|
|
26
|
+
* @returns 点数レベル
|
|
27
|
+
*/
|
|
28
|
+
export declare function getScoreLevel(han: number, basePoints: number): ScoreLevel;
|
|
29
|
+
/**
|
|
30
|
+
* 手牌とコンテキストから点数を計算する(公開API)
|
|
31
|
+
*
|
|
32
|
+
* 手牌の構造解析を行い、最も高点となる解釈を採用して点数を返します。
|
|
33
|
+
*
|
|
34
|
+
* 注: 同一手牌で「翻数が高いが符が低い解釈」と「翻数が低いが符が高い解釈」が
|
|
35
|
+
* 両立するケースは実質的に存在しないため、翻数最大の解釈を採用しています。
|
|
36
|
+
*
|
|
37
|
+
* @param tehai 手牌 (14枚)
|
|
38
|
+
* @param config 点数計算の設定 (場風、自風、ドラなど)
|
|
39
|
+
* @returns 点数計算結果
|
|
40
|
+
*/
|
|
41
|
+
export declare function calculateScoreForTehai(tehai: Tehai14, config: Readonly<ScoreCalculationConfig>): ScoreResult;
|
|
42
|
+
/**
|
|
43
|
+
* 基本的な点数計算ロジック (内部用・テスト用)
|
|
44
|
+
*/
|
|
45
|
+
export declare function calculateScoreFromHanAndFu(yakuHansu: number, fuResult: Readonly<FuResult>, dora: number, context: Readonly<ScoreContext>): ScoreResult;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { HaiKind } from "../../types";
|
|
2
|
+
import { NoYakuError } from "../../errors";
|
|
3
|
+
import { countDora } from "../../core/dora";
|
|
4
|
+
import { getHouraStructures } from "../yaku/lib/structures";
|
|
5
|
+
import { detectYakuForStructure } from "../yaku";
|
|
6
|
+
import { calculateFu } from "./lib/fu";
|
|
7
|
+
import { isMenzen } from "../yaku/utils";
|
|
8
|
+
import { BASE_SCORE_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";
|
|
9
|
+
import { ScoreLevel, } from "./types";
|
|
10
|
+
export { ScoreLevel };
|
|
11
|
+
/**
|
|
12
|
+
* 100点単位で切り上げる
|
|
13
|
+
*/
|
|
14
|
+
function ceil100(points) {
|
|
15
|
+
return Math.ceil(points / 100) * 100;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 基本点を計算する
|
|
19
|
+
* 基本点 = 符 × 2^(2+翻)
|
|
20
|
+
*
|
|
21
|
+
* 基本点は子がツモ和了したときの他の子1人あたりの支払い点数に相当する。
|
|
22
|
+
* - 子ツモ: 子の支払い = 基本点, 親の支払い = 基本点 × 2
|
|
23
|
+
* - 子ロン: 支払い = 基本点 × 4
|
|
24
|
+
* - 親ツモ: 各子の支払い = 基本点 × 2
|
|
25
|
+
* - 親ロン: 支払い = 基本点 × 6
|
|
26
|
+
*/
|
|
27
|
+
export function calculateBasePoints(fu, han) {
|
|
28
|
+
return fu * Math.pow(2, 2 + han);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 支払い情報から和了者が受け取る総点数を計算する
|
|
32
|
+
*/
|
|
33
|
+
export function getPaymentTotal(payment) {
|
|
34
|
+
switch (payment.type) {
|
|
35
|
+
case "ron":
|
|
36
|
+
return payment.amount;
|
|
37
|
+
case "koTsumo":
|
|
38
|
+
return payment.amount[0] * 2 + payment.amount[1];
|
|
39
|
+
case "oyaTsumo":
|
|
40
|
+
return payment.amount * 3;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 翻数と基本点から点数レベルを判定する
|
|
45
|
+
*
|
|
46
|
+
* @param han 翻数
|
|
47
|
+
* @param basePoints 基本点(符 × 2^(2+翻))
|
|
48
|
+
* @returns 点数レベル
|
|
49
|
+
*/
|
|
50
|
+
export function getScoreLevel(han, basePoints) {
|
|
51
|
+
if (han >= 26) {
|
|
52
|
+
return ScoreLevel.DoubleYakuman;
|
|
53
|
+
}
|
|
54
|
+
if (han >= HAN_YAKUMAN) {
|
|
55
|
+
return ScoreLevel.Yakuman;
|
|
56
|
+
}
|
|
57
|
+
if (han >= HAN_SANBAIMAN) {
|
|
58
|
+
return ScoreLevel.Sanbaiman;
|
|
59
|
+
}
|
|
60
|
+
if (han >= HAN_BAIMAN) {
|
|
61
|
+
return ScoreLevel.Baiman;
|
|
62
|
+
}
|
|
63
|
+
if (han >= HAN_HANEMAN) {
|
|
64
|
+
return ScoreLevel.Haneman;
|
|
65
|
+
}
|
|
66
|
+
if (han >= HAN_MANGAN || basePoints >= BASE_SCORE_LIMIT) {
|
|
67
|
+
return ScoreLevel.Mangan;
|
|
68
|
+
}
|
|
69
|
+
return ScoreLevel.Normal;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 点数レベルに対応する基本点を取得する
|
|
73
|
+
*
|
|
74
|
+
* @param level 点数レベル
|
|
75
|
+
* @returns 基本点(Normal の場合は null)
|
|
76
|
+
*/
|
|
77
|
+
function getLimitBasePoints(level) {
|
|
78
|
+
switch (level) {
|
|
79
|
+
case ScoreLevel.DoubleYakuman:
|
|
80
|
+
return SCORE_BASE_YAKUMAN * 2;
|
|
81
|
+
case ScoreLevel.Yakuman:
|
|
82
|
+
return SCORE_BASE_YAKUMAN;
|
|
83
|
+
case ScoreLevel.Sanbaiman:
|
|
84
|
+
return SCORE_BASE_SANBAIMAN;
|
|
85
|
+
case ScoreLevel.Baiman:
|
|
86
|
+
return SCORE_BASE_BAIMAN;
|
|
87
|
+
case ScoreLevel.Haneman:
|
|
88
|
+
return SCORE_BASE_HANEMAN;
|
|
89
|
+
case ScoreLevel.Mangan:
|
|
90
|
+
return SCORE_BASE_MANGAN;
|
|
91
|
+
case ScoreLevel.Normal:
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 点数計算用コンテキストを作成する
|
|
97
|
+
*
|
|
98
|
+
* @param tehai 手牌 (14枚)
|
|
99
|
+
* @param config 点数計算の設定
|
|
100
|
+
* @returns 点数計算用コンテキスト
|
|
101
|
+
*/
|
|
102
|
+
function createScoreContext(tehai, config) {
|
|
103
|
+
return {
|
|
104
|
+
isMenzen: isMenzen(tehai),
|
|
105
|
+
agariHai: config.agariHai,
|
|
106
|
+
bakaze: config.bakaze,
|
|
107
|
+
jikaze: config.jikaze,
|
|
108
|
+
isTsumo: config.isTsumo,
|
|
109
|
+
isOya: config.jikaze === HaiKind.Ton,
|
|
110
|
+
doraMarkers: config.doraMarkers,
|
|
111
|
+
...(config.uraDoraMarkers ? { uraDoraMarkers: config.uraDoraMarkers } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 手牌とコンテキストから点数を計算する(公開API)
|
|
116
|
+
*
|
|
117
|
+
* 手牌の構造解析を行い、最も高点となる解釈を採用して点数を返します。
|
|
118
|
+
*
|
|
119
|
+
* 注: 同一手牌で「翻数が高いが符が低い解釈」と「翻数が低いが符が高い解釈」が
|
|
120
|
+
* 両立するケースは実質的に存在しないため、翻数最大の解釈を採用しています。
|
|
121
|
+
*
|
|
122
|
+
* @param tehai 手牌 (14枚)
|
|
123
|
+
* @param config 点数計算の設定 (場風、自風、ドラなど)
|
|
124
|
+
* @returns 点数計算結果
|
|
125
|
+
*/
|
|
126
|
+
export function calculateScoreForTehai(tehai, config) {
|
|
127
|
+
const context = createScoreContext(tehai, config);
|
|
128
|
+
const structuralInterpretations = getHouraStructures(tehai);
|
|
129
|
+
let bestResult = null;
|
|
130
|
+
let maxTotalPoints = -1;
|
|
131
|
+
for (const hand of structuralInterpretations) {
|
|
132
|
+
// 1. 役の判定
|
|
133
|
+
const yakuResult = detectYakuForStructure(hand, context);
|
|
134
|
+
const yakuHansu = yakuResult.reduce((sum, [, han]) => sum + han, 0);
|
|
135
|
+
// 役がない場合はこの構造は不成立
|
|
136
|
+
if (yakuHansu === 0)
|
|
137
|
+
continue;
|
|
138
|
+
// 2. 符の計算
|
|
139
|
+
const isPinfu = yakuResult.some(([name]) => name === "Pinfu");
|
|
140
|
+
const fuResult = calculateFu(hand, context, isPinfu);
|
|
141
|
+
// 3. ドラの計算
|
|
142
|
+
const dora = countDora(tehai, context.doraMarkers);
|
|
143
|
+
// 4. 点数計算
|
|
144
|
+
const result = calculateScoreFromHanAndFu(yakuHansu, fuResult, dora, context);
|
|
145
|
+
const total = getPaymentTotal(result.payment);
|
|
146
|
+
if (total > maxTotalPoints) {
|
|
147
|
+
maxTotalPoints = total;
|
|
148
|
+
bestResult = result;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!bestResult) {
|
|
152
|
+
throw new NoYakuError();
|
|
153
|
+
}
|
|
154
|
+
return bestResult;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 基本的な点数計算ロジック (内部用・テスト用)
|
|
158
|
+
*/
|
|
159
|
+
export function calculateScoreFromHanAndFu(yakuHansu, fuResult, dora, context) {
|
|
160
|
+
const totalHan = yakuHansu + dora;
|
|
161
|
+
const fu = fuResult.total;
|
|
162
|
+
// 基本点の計算
|
|
163
|
+
const rawBasePoints = calculateBasePoints(fu, totalHan);
|
|
164
|
+
// 点数レベルの判定
|
|
165
|
+
const scoreLevel = getScoreLevel(totalHan, rawBasePoints);
|
|
166
|
+
// 満貫以上なら固定の基本点、それ以外は計算値を使用
|
|
167
|
+
const basePoints = getLimitBasePoints(scoreLevel) ?? rawBasePoints;
|
|
168
|
+
// 支払い計算
|
|
169
|
+
const payment = calculatePayment(basePoints, context);
|
|
170
|
+
return {
|
|
171
|
+
han: totalHan,
|
|
172
|
+
fu: fu,
|
|
173
|
+
scoreLevel,
|
|
174
|
+
payment,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 基本点から支払い情報を計算する
|
|
179
|
+
*/
|
|
180
|
+
function calculatePayment(basePoints, context) {
|
|
181
|
+
if (context.isTsumo) {
|
|
182
|
+
if (context.isOya) {
|
|
183
|
+
// 親ツモ: オール (基本点 * 2)
|
|
184
|
+
const allPay = ceil100(basePoints * 2);
|
|
185
|
+
return { type: "oyaTsumo", amount: allPay };
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// 子ツモ: 親の支払い = 基本点 * 2, 子の支払い = 基本点 * 1
|
|
189
|
+
const parentPay = ceil100(basePoints * 2);
|
|
190
|
+
const childPay = ceil100(basePoints * 1);
|
|
191
|
+
return { type: "koTsumo", amount: [childPay, parentPay] };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// ロン和了
|
|
196
|
+
if (context.isOya) {
|
|
197
|
+
// 親ロン: 基本点 * 6
|
|
198
|
+
const pay = ceil100(basePoints * 6);
|
|
199
|
+
return { type: "ron", amount: pay };
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// 子ロン: 基本点 * 4
|
|
203
|
+
const pay = ceil100(basePoints * 4);
|
|
204
|
+
return { type: "ron", amount: pay };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -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 {};
|