mj-simulator 0.1.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/data/honor-table.bin +0 -0
- package/data/suit-table.bin +0 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +278 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/constants.d.ts +13 -0
- package/dist/core/constants.js +19 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/hand.d.ts +37 -0
- package/dist/core/hand.js +105 -0
- package/dist/core/hand.js.map +1 -0
- package/dist/core/remaining.d.ts +12 -0
- package/dist/core/remaining.js +26 -0
- package/dist/core/remaining.js.map +1 -0
- package/dist/core/tile.d.ts +45 -0
- package/dist/core/tile.js +140 -0
- package/dist/core/tile.js.map +1 -0
- package/dist/ev/ev-calculator.d.ts +10 -0
- package/dist/ev/ev-calculator.js +257 -0
- package/dist/ev/ev-calculator.js.map +1 -0
- package/dist/ev/types.d.ts +16 -0
- package/dist/ev/types.js +2 -0
- package/dist/ev/types.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +11 -0
- package/dist/init.js +24 -0
- package/dist/init.js.map +1 -0
- package/dist/mc/simulator.d.ts +18 -0
- package/dist/mc/simulator.js +11 -0
- package/dist/mc/simulator.js.map +1 -0
- package/dist/mc/wasm-bridge.d.ts +30 -0
- package/dist/mc/wasm-bridge.js +56 -0
- package/dist/mc/wasm-bridge.js.map +1 -0
- package/dist/models/danger.d.ts +34 -0
- package/dist/models/danger.js +147 -0
- package/dist/models/danger.js.map +1 -0
- package/dist/models/evaluation.d.ts +31 -0
- package/dist/models/evaluation.js +46 -0
- package/dist/models/evaluation.js.map +1 -0
- package/dist/scoring/decomposer.d.ts +7 -0
- package/dist/scoring/decomposer.js +106 -0
- package/dist/scoring/decomposer.js.map +1 -0
- package/dist/scoring/fu.d.ts +5 -0
- package/dist/scoring/fu.js +47 -0
- package/dist/scoring/fu.js.map +1 -0
- package/dist/scoring/score-calculator.d.ts +11 -0
- package/dist/scoring/score-calculator.js +164 -0
- package/dist/scoring/score-calculator.js.map +1 -0
- package/dist/scoring/types.d.ts +38 -0
- package/dist/scoring/types.js +2 -0
- package/dist/scoring/types.js.map +1 -0
- package/dist/scoring/wait-type.d.ts +11 -0
- package/dist/scoring/wait-type.js +65 -0
- package/dist/scoring/wait-type.js.map +1 -0
- package/dist/scoring/yaku.d.ts +7 -0
- package/dist/scoring/yaku.js +490 -0
- package/dist/scoring/yaku.js.map +1 -0
- package/dist/shanten/init.d.ts +9 -0
- package/dist/shanten/init.js +26 -0
- package/dist/shanten/init.js.map +1 -0
- package/dist/shanten/lookup-table.d.ts +37 -0
- package/dist/shanten/lookup-table.js +228 -0
- package/dist/shanten/lookup-table.js.map +1 -0
- package/dist/shanten/shanten-calculator.d.ts +18 -0
- package/dist/shanten/shanten-calculator.js +101 -0
- package/dist/shanten/shanten-calculator.js.map +1 -0
- package/dist/shanten/ukeire.d.ts +35 -0
- package/dist/shanten/ukeire.js +103 -0
- package/dist/shanten/ukeire.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/worker-client.d.ts +42 -0
- package/dist/worker-client.js +83 -0
- package/dist/worker-client.js.map +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +67 -0
- package/dist/worker.js.map +1 -0
- package/node_modules/mj-mc-wasm/mj_mc_wasm.d.ts +57 -0
- package/node_modules/mj-mc-wasm/mj_mc_wasm.js +235 -0
- package/node_modules/mj-mc-wasm/mj_mc_wasm_bg.wasm +0 -0
- package/node_modules/mj-mc-wasm/package.json +15 -0
- package/package.json +44 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { NUM_TILE_TYPES } from "../core/constants.js";
|
|
2
|
+
import { toNormalTile } from "../core/tile.js";
|
|
3
|
+
import { decomposeHand } from "./decomposer.js";
|
|
4
|
+
import { determineWaitTypes } from "./wait-type.js";
|
|
5
|
+
import { evaluateYaku } from "./yaku.js";
|
|
6
|
+
import { calculateFu } from "./fu.js";
|
|
7
|
+
/**
|
|
8
|
+
* ツモ和了の点数を基本点から計算する
|
|
9
|
+
* @param basicPoints 基本点
|
|
10
|
+
* @param isDealer 親かどうか
|
|
11
|
+
* @returns 合計獲得点数
|
|
12
|
+
*/
|
|
13
|
+
function tsumoPoints(basicPoints, isDealer) {
|
|
14
|
+
if (isDealer) {
|
|
15
|
+
// 親ツモ: 子3人から各 ceil(basicPoints*2/100)*100
|
|
16
|
+
const each = Math.ceil((basicPoints * 2) / 100) * 100;
|
|
17
|
+
return each * 3;
|
|
18
|
+
}
|
|
19
|
+
// 子ツモ: 親から ceil(basicPoints*2/100)*100, 子2人から各 ceil(basicPoints/100)*100
|
|
20
|
+
const fromDealer = Math.ceil((basicPoints * 2) / 100) * 100;
|
|
21
|
+
const fromChild = Math.ceil(basicPoints / 100) * 100;
|
|
22
|
+
return fromDealer + fromChild * 2;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 翻と符から基本点を計算する
|
|
26
|
+
*/
|
|
27
|
+
function calcBasicPoints(han, fu) {
|
|
28
|
+
if (han >= 13)
|
|
29
|
+
return 8000;
|
|
30
|
+
if (han >= 11)
|
|
31
|
+
return 6000;
|
|
32
|
+
if (han >= 8)
|
|
33
|
+
return 4000;
|
|
34
|
+
if (han >= 6)
|
|
35
|
+
return 3000;
|
|
36
|
+
if (han >= 5)
|
|
37
|
+
return 2000;
|
|
38
|
+
const basic = fu * Math.pow(2, han + 2);
|
|
39
|
+
return Math.min(basic, 2000);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 裏ドラ1枚の確率分布を計算する
|
|
43
|
+
* @param hand 14枚の和了形
|
|
44
|
+
* @returns dist[k] = P(裏ドラk枚) (k=0..4)
|
|
45
|
+
*/
|
|
46
|
+
function calcUraDoraDist1(hand) {
|
|
47
|
+
// 手牌14枚中、通常牌IDごとの枚数を集計(赤ドラは通常5にマージ)
|
|
48
|
+
const counts = new Array(NUM_TILE_TYPES).fill(0);
|
|
49
|
+
for (let i = 0; i < hand.length; i++) {
|
|
50
|
+
if (hand[i] > 0) {
|
|
51
|
+
const normal = toNormalTile(i);
|
|
52
|
+
counts[normal] += hand[i];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 裏ドラ表示牌の next が牌種 t である確率 = 1/34
|
|
56
|
+
// P(ura=k | 1枚) = Σ{ counts[t] === k の t の数 } / 34
|
|
57
|
+
const dist = new Array(5).fill(0); // k=0..4
|
|
58
|
+
for (let t = 0; t < NUM_TILE_TYPES; t++) {
|
|
59
|
+
const k = Math.min(counts[t], 4);
|
|
60
|
+
dist[k] += 1 / NUM_TILE_TYPES;
|
|
61
|
+
}
|
|
62
|
+
return dist;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 2つの確率分布を畳み込む
|
|
66
|
+
*/
|
|
67
|
+
function convolveDists(a, b) {
|
|
68
|
+
const maxK = a.length + b.length - 2;
|
|
69
|
+
const result = new Array(maxK + 1).fill(0);
|
|
70
|
+
for (let i = 0; i < a.length; i++) {
|
|
71
|
+
for (let j = 0; j < b.length; j++) {
|
|
72
|
+
result[i + j] += a[i] * b[j];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 裏ドラの確率分布を計算する(複数枚対応)
|
|
79
|
+
*/
|
|
80
|
+
function calcUraDoraDist(hand, uraCount) {
|
|
81
|
+
const dist1 = calcUraDoraDist1(hand);
|
|
82
|
+
let dist = [1]; // delta(0)
|
|
83
|
+
for (let i = 0; i < uraCount; i++) {
|
|
84
|
+
dist = convolveDists(dist, dist1);
|
|
85
|
+
}
|
|
86
|
+
return dist;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 特定の分解・待ちでの得点を計算する(裏ドラなし)
|
|
90
|
+
*/
|
|
91
|
+
function calcScoreForWait(decomp, agariTile, waitType, hand, context, isDealer) {
|
|
92
|
+
const yaku = evaluateYaku(decomp, agariTile, waitType, hand, context);
|
|
93
|
+
const han = yaku.reduce((sum, y) => sum + y.han, 0);
|
|
94
|
+
if (han === 0)
|
|
95
|
+
return null;
|
|
96
|
+
const isPinfu = yaku.some((y) => y.name === "平和");
|
|
97
|
+
const fu = calculateFu(decomp, waitType, isPinfu, context);
|
|
98
|
+
const basicPoints = calcBasicPoints(han, fu);
|
|
99
|
+
const totalPoints = tsumoPoints(basicPoints, isDealer);
|
|
100
|
+
return { yaku, han, fu, totalPoints };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 手牌と和了牌から得点を計算する
|
|
104
|
+
* @param hand 14枚の和了形
|
|
105
|
+
* @param agariTile 和了牌 (ツモった牌)
|
|
106
|
+
* @param context ゲーム状態
|
|
107
|
+
* @returns 最高得点の結果、または役なしの場合 null
|
|
108
|
+
*/
|
|
109
|
+
export function calculateScore(hand, agariTile, context) {
|
|
110
|
+
const decompositions = decomposeHand(hand);
|
|
111
|
+
if (decompositions.length === 0)
|
|
112
|
+
return null;
|
|
113
|
+
const isDealer = context.seatWind === 27; // 東 = 親
|
|
114
|
+
const uraDoraCount = context.uraDoraCount ?? 0;
|
|
115
|
+
const useUraDora = context.riichi && uraDoraCount > 0;
|
|
116
|
+
let bestResult = null;
|
|
117
|
+
for (const decomp of decompositions) {
|
|
118
|
+
if (decomp.kind === "chiitoi") {
|
|
119
|
+
// 七対子: 単騎のみ
|
|
120
|
+
const result = calcScoreForWait(decomp, agariTile, "tanki", hand, context, isDealer);
|
|
121
|
+
if (result) {
|
|
122
|
+
const totalPoints = useUraDora
|
|
123
|
+
? calcExpectedPointsWithUra(hand, result.han, result.fu, isDealer, uraDoraCount)
|
|
124
|
+
: result.totalPoints;
|
|
125
|
+
if (bestResult === null || totalPoints > bestResult.totalPoints) {
|
|
126
|
+
bestResult = { yaku: result.yaku, han: result.han, fu: result.fu, totalPoints };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// 通常手: 全待ち候補を試す
|
|
132
|
+
const waitTypes = determineWaitTypes(decomp, agariTile);
|
|
133
|
+
for (const waitType of waitTypes) {
|
|
134
|
+
const result = calcScoreForWait(decomp, agariTile, waitType, hand, context, isDealer);
|
|
135
|
+
if (result) {
|
|
136
|
+
const totalPoints = useUraDora
|
|
137
|
+
? calcExpectedPointsWithUra(hand, result.han, result.fu, isDealer, uraDoraCount)
|
|
138
|
+
: result.totalPoints;
|
|
139
|
+
if (bestResult === null || totalPoints > bestResult.totalPoints) {
|
|
140
|
+
bestResult = { yaku: result.yaku, han: result.han, fu: result.fu, totalPoints };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return bestResult;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 裏ドラ期待値込みの点数を計算する
|
|
150
|
+
*/
|
|
151
|
+
function calcExpectedPointsWithUra(hand, baseHan, fu, isDealer, uraCount) {
|
|
152
|
+
const dist = calcUraDoraDist(hand, uraCount);
|
|
153
|
+
let expected = 0;
|
|
154
|
+
for (let k = 0; k < dist.length; k++) {
|
|
155
|
+
if (dist[k] <= 0)
|
|
156
|
+
continue;
|
|
157
|
+
const han = baseHan + k;
|
|
158
|
+
const basicPoints = calcBasicPoints(han, fu);
|
|
159
|
+
const points = tsumoPoints(basicPoints, isDealer);
|
|
160
|
+
expected += dist[k] * points;
|
|
161
|
+
}
|
|
162
|
+
return Math.round(expected);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=score-calculator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score-calculator.js","sourceRoot":"","sources":["../../src/scoring/score-calculator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TileId } from "../core/tile.js";
|
|
2
|
+
export type MentsuType = "shuntsu" | "koutsu";
|
|
3
|
+
export interface Mentsu {
|
|
4
|
+
type: MentsuType;
|
|
5
|
+
tile: TileId;
|
|
6
|
+
}
|
|
7
|
+
/** 通常手の分解 (4面子+1雀頭) */
|
|
8
|
+
export interface RegularDecomposition {
|
|
9
|
+
kind: "regular";
|
|
10
|
+
mentsu: [Mentsu, Mentsu, Mentsu, Mentsu];
|
|
11
|
+
jantou: TileId;
|
|
12
|
+
}
|
|
13
|
+
/** 七対子の分解 */
|
|
14
|
+
export interface ChiitoiDecomposition {
|
|
15
|
+
kind: "chiitoi";
|
|
16
|
+
pairs: TileId[];
|
|
17
|
+
}
|
|
18
|
+
export type HandDecomposition = RegularDecomposition | ChiitoiDecomposition;
|
|
19
|
+
export type WaitType = "ryanmen" | "kanchan" | "penchan" | "shanpon" | "tanki";
|
|
20
|
+
export interface YakuResult {
|
|
21
|
+
name: string;
|
|
22
|
+
han: number;
|
|
23
|
+
}
|
|
24
|
+
/** 得点計算に必要なゲーム状態 */
|
|
25
|
+
export interface GameContext {
|
|
26
|
+
seatWind: TileId;
|
|
27
|
+
roundWind: TileId;
|
|
28
|
+
doraIndicators: TileId[];
|
|
29
|
+
useRedDora: boolean;
|
|
30
|
+
riichi: boolean;
|
|
31
|
+
uraDoraCount?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ScoreResult {
|
|
34
|
+
yaku: YakuResult[];
|
|
35
|
+
han: number;
|
|
36
|
+
fu: number;
|
|
37
|
+
totalPoints: number;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/scoring/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TileId } from "../core/tile.js";
|
|
2
|
+
import type { RegularDecomposition, WaitType } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 和了牌と通常手分解から待ち種類の候補をすべて返す
|
|
5
|
+
* 和了牌が複数の面子に含まれうる場合、それぞれの待ちを返す
|
|
6
|
+
*/
|
|
7
|
+
export declare function determineWaitTypes(decomposition: RegularDecomposition, agariTile: TileId): WaitType[];
|
|
8
|
+
/**
|
|
9
|
+
* 後方互換: 最初に見つかった待ちを返す
|
|
10
|
+
*/
|
|
11
|
+
export declare function determineWaitType(decomposition: RegularDecomposition, agariTile: TileId): WaitType;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SUIT_NUM } from "../core/constants.js";
|
|
2
|
+
import { toNormalTile } from "../core/tile.js";
|
|
3
|
+
/**
|
|
4
|
+
* 和了牌と通常手分解から待ち種類の候補をすべて返す
|
|
5
|
+
* 和了牌が複数の面子に含まれうる場合、それぞれの待ちを返す
|
|
6
|
+
*/
|
|
7
|
+
export function determineWaitTypes(decomposition, agariTile) {
|
|
8
|
+
const tile = toNormalTile(agariTile);
|
|
9
|
+
const results = [];
|
|
10
|
+
// 単騎: 和了牌が雀頭
|
|
11
|
+
if (tile === decomposition.jantou) {
|
|
12
|
+
results.push("tanki");
|
|
13
|
+
}
|
|
14
|
+
// 面子を探す
|
|
15
|
+
for (const m of decomposition.mentsu) {
|
|
16
|
+
if (m.type === "koutsu") {
|
|
17
|
+
if (tile === m.tile) {
|
|
18
|
+
results.push("shanpon");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// 順子: m.tile, m.tile+1, m.tile+2
|
|
23
|
+
const base = m.tile;
|
|
24
|
+
if (tile < base || tile > base + 2)
|
|
25
|
+
continue;
|
|
26
|
+
const pos = tile - base; // 0, 1, 2
|
|
27
|
+
if (pos === 1) {
|
|
28
|
+
results.push("kanchan");
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// 色内での数字 (0-8)
|
|
32
|
+
const baseNum = base % SUIT_NUM; // 0=1牌, 8=9牌
|
|
33
|
+
if (pos === 0) {
|
|
34
|
+
// 左端に和了牌 = 順子の右端が9 (baseNum+2===8) → 辺張
|
|
35
|
+
if (baseNum + 2 === 8) {
|
|
36
|
+
results.push("penchan"); // 789
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
results.push("ryanmen");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (pos === 2) {
|
|
43
|
+
// 右端に和了牌 = 順子の左端が1 (baseNum===0) → 辺張
|
|
44
|
+
if (baseNum === 0) {
|
|
45
|
+
results.push("penchan"); // 123
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
results.push("ryanmen");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// フォールバック(通常到達しない)
|
|
54
|
+
if (results.length === 0) {
|
|
55
|
+
results.push("tanki");
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 後方互換: 最初に見つかった待ちを返す
|
|
61
|
+
*/
|
|
62
|
+
export function determineWaitType(decomposition, agariTile) {
|
|
63
|
+
return determineWaitTypes(decomposition, agariTile)[0];
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=wait-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait-type.js","sourceRoot":"","sources":["../../src/scoring/wait-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAU,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGvD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAmC,EACnC,SAAiB;IAEjB,MAAM,IAAI,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACrC,MAAM,OAAO,GAAe,EAAE,CAAC;IAE/B,aAAa;IACb,IAAI,IAAI,KAAK,aAAa,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,QAAQ;IACR,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACxB,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,iCAAiC;YACjC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;YACpB,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC;gBAAE,SAAS;YAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,UAAU;YAEnC,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,SAAS;YACX,CAAC;YAED,eAAe;YACf,MAAM,OAAO,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC,aAAa;YAE9C,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;gBACd,wCAAwC;gBACxC,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBACtB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;gBACjC,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;iBAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;gBACrB,sCAAsC;gBACtC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;gBACjC,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,aAAmC,EACnC,SAAiB;IAEjB,OAAO,kBAAkB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HandArray } from "../core/hand.js";
|
|
2
|
+
import { TileId } from "../core/tile.js";
|
|
3
|
+
import type { HandDecomposition, GameContext, WaitType, YakuResult } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* 役を判定する (ツモ和了・門前専用)
|
|
6
|
+
*/
|
|
7
|
+
export declare function evaluateYaku(decomposition: HandDecomposition, agariTile: TileId, waitType: WaitType, hand: HandArray, context: GameContext): YakuResult[];
|