@pai-forge/riichi-mahjong 0.3.4 → 0.3.5
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/index.d.ts +105 -20
- package/dist/index.js +115 -129
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,7 +70,7 @@ class MahjongError extends Error {
|
|
|
70
70
|
constructor(message) {
|
|
71
71
|
super(message);
|
|
72
72
|
this.name = "MahjongError";
|
|
73
|
-
Object.setPrototypeOf(this,
|
|
73
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
class ShoushaiError extends MahjongError {
|
|
@@ -80,7 +80,6 @@ class ShoushaiError extends MahjongError {
|
|
|
80
80
|
constructor(message = "手牌が規定枚数(13枚)より少ないです。") {
|
|
81
81
|
super(message);
|
|
82
82
|
this.name = "ShoushaiError";
|
|
83
|
-
Object.setPrototypeOf(this, ShoushaiError.prototype);
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
class TahaiError extends MahjongError {
|
|
@@ -90,7 +89,6 @@ class TahaiError extends MahjongError {
|
|
|
90
89
|
constructor(message = "手牌が規定枚数(13枚)より多いです。") {
|
|
91
90
|
super(message);
|
|
92
91
|
this.name = "TahaiError";
|
|
93
|
-
Object.setPrototypeOf(this, TahaiError.prototype);
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
class MahjongArgumentError extends MahjongError {
|
|
@@ -100,7 +98,6 @@ class MahjongArgumentError extends MahjongError {
|
|
|
100
98
|
constructor(message) {
|
|
101
99
|
super(message);
|
|
102
100
|
this.name = "MahjongArgumentError";
|
|
103
|
-
Object.setPrototypeOf(this, MahjongArgumentError.prototype);
|
|
104
101
|
}
|
|
105
102
|
}
|
|
106
103
|
class DuplicatedHaiIdError extends MahjongError {
|
|
@@ -110,7 +107,6 @@ class DuplicatedHaiIdError extends MahjongError {
|
|
|
110
107
|
constructor(message = "牌IDが重複しています。") {
|
|
111
108
|
super(message);
|
|
112
109
|
this.name = "DuplicatedHaiIdError";
|
|
113
|
-
Object.setPrototypeOf(this, DuplicatedHaiIdError.prototype);
|
|
114
110
|
}
|
|
115
111
|
}
|
|
116
112
|
class InvalidHaiQuantityError extends MahjongError {
|
|
@@ -120,7 +116,6 @@ class InvalidHaiQuantityError extends MahjongError {
|
|
|
120
116
|
constructor(message = "同種の牌が5枚以上存在します。") {
|
|
121
117
|
super(message);
|
|
122
118
|
this.name = "InvalidHaiQuantityError";
|
|
123
|
-
Object.setPrototypeOf(this, InvalidHaiQuantityError.prototype);
|
|
124
119
|
}
|
|
125
120
|
}
|
|
126
121
|
class ChomboError extends MahjongError {
|
|
@@ -130,7 +125,6 @@ class ChomboError extends MahjongError {
|
|
|
130
125
|
constructor(message = "不正な和了です。") {
|
|
131
126
|
super(message);
|
|
132
127
|
this.name = "ChomboError";
|
|
133
|
-
Object.setPrototypeOf(this, ChomboError.prototype);
|
|
134
128
|
}
|
|
135
129
|
}
|
|
136
130
|
class NoYakuError extends ChomboError {
|
|
@@ -140,7 +134,15 @@ class NoYakuError extends ChomboError {
|
|
|
140
134
|
constructor(message = "役が成立していません。") {
|
|
141
135
|
super(message);
|
|
142
136
|
this.name = "NoYakuError";
|
|
143
|
-
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
class MspzParseError extends MahjongError {
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
*/
|
|
143
|
+
constructor(message = "MSPZ文字列の解析に失敗しました。") {
|
|
144
|
+
super(message);
|
|
145
|
+
this.name = "MspzParseError";
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
function isTuple2(arr) {
|
|
@@ -211,7 +213,7 @@ function countHaiKind(hais) {
|
|
|
211
213
|
}
|
|
212
214
|
return counts;
|
|
213
215
|
}
|
|
214
|
-
function
|
|
216
|
+
function assertTehai13(tehai) {
|
|
215
217
|
const count = calculateTehaiCount(tehai);
|
|
216
218
|
if (count < 13) {
|
|
217
219
|
throw new ShoushaiError();
|
|
@@ -221,7 +223,10 @@ function validateTehai13(tehai) {
|
|
|
221
223
|
}
|
|
222
224
|
validateHaiConsistency(tehai);
|
|
223
225
|
}
|
|
224
|
-
function
|
|
226
|
+
function validateTehai13(tehai) {
|
|
227
|
+
assertTehai13(tehai);
|
|
228
|
+
}
|
|
229
|
+
function assertTehai14(tehai) {
|
|
225
230
|
const count = calculateTehaiCount(tehai);
|
|
226
231
|
if (count < 14) {
|
|
227
232
|
throw new ShoushaiError();
|
|
@@ -231,6 +236,9 @@ function validateTehai14(tehai) {
|
|
|
231
236
|
}
|
|
232
237
|
validateHaiConsistency(tehai);
|
|
233
238
|
}
|
|
239
|
+
function validateTehai14(tehai) {
|
|
240
|
+
assertTehai14(tehai);
|
|
241
|
+
}
|
|
234
242
|
function validateTehai(tehai) {
|
|
235
243
|
const count = calculateTehaiCount(tehai);
|
|
236
244
|
if (count < 13) {
|
|
@@ -255,13 +263,7 @@ function validateHaiConsistency(tehai) {
|
|
|
255
263
|
}
|
|
256
264
|
const counts = /* @__PURE__ */ new Map();
|
|
257
265
|
for (const hai of allHais) {
|
|
258
|
-
|
|
259
|
-
if (hai > 33) {
|
|
260
|
-
if (hai < 36) kind = Math.floor(hai / 4);
|
|
261
|
-
else if (hai < 72) kind = Math.floor((hai - 36) / 4) + 9;
|
|
262
|
-
else if (hai < 108) kind = Math.floor((hai - 72) / 4) + 18;
|
|
263
|
-
else kind = Math.floor((hai - 108) / 4) + 27;
|
|
264
|
-
}
|
|
266
|
+
const kind = isHaiIdMode ? haiIdToKindId(hai) : hai;
|
|
265
267
|
const current = counts.get(kind) ?? 0;
|
|
266
268
|
if (current + 1 > 4) {
|
|
267
269
|
throw new InvalidHaiQuantityError();
|
|
@@ -355,21 +357,12 @@ function getDoraNext(indicator) {
|
|
|
355
357
|
return indicator;
|
|
356
358
|
}
|
|
357
359
|
function countDora(tehai, indicators) {
|
|
358
|
-
let count = 0;
|
|
359
360
|
const doraHais = indicators.map(getDoraNext);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
for (const mentsu of tehai.exposed) {
|
|
366
|
-
for (const hai of mentsu.hais) {
|
|
367
|
-
for (const dora of doraHais) {
|
|
368
|
-
if (hai === dora) count++;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return count;
|
|
361
|
+
const allHais = [...tehai.closed, ...tehai.exposed.flatMap((m) => m.hais)];
|
|
362
|
+
return allHais.reduce(
|
|
363
|
+
(count, hai) => count + doraHais.filter((d) => d === hai).length,
|
|
364
|
+
0
|
|
365
|
+
);
|
|
373
366
|
}
|
|
374
367
|
function classifyMachi(hand, agariHai) {
|
|
375
368
|
if (hand.type !== "Mentsu") return void 0;
|
|
@@ -566,7 +559,7 @@ function getUkeire(tehai) {
|
|
|
566
559
|
];
|
|
567
560
|
const haiCounts = countHaiKind(allHais);
|
|
568
561
|
for (let i = 0; i < 34; i++) {
|
|
569
|
-
const tile = i;
|
|
562
|
+
const tile = asHaiKindId(i);
|
|
570
563
|
if (haiCounts[tile] >= 4) {
|
|
571
564
|
continue;
|
|
572
565
|
}
|
|
@@ -725,6 +718,53 @@ function isMenzen(tehai) {
|
|
|
725
718
|
function isKazehai(id) {
|
|
726
719
|
return id === HaiKind.Ton || id === HaiKind.Nan || id === HaiKind.Sha || id === HaiKind.Pei;
|
|
727
720
|
}
|
|
721
|
+
function countShuntsuPairs(hand) {
|
|
722
|
+
if (hand.type !== "Mentsu") {
|
|
723
|
+
return 0;
|
|
724
|
+
}
|
|
725
|
+
const shuntsuList = hand.fourMentsu.filter(
|
|
726
|
+
(mentsu) => mentsu.type === "Shuntsu"
|
|
727
|
+
);
|
|
728
|
+
const shuntsuCounts = /* @__PURE__ */ new Map();
|
|
729
|
+
for (const shuntsu of shuntsuList) {
|
|
730
|
+
const key = shuntsu.hais[0];
|
|
731
|
+
const currentCount = shuntsuCounts.get(key) ?? 0;
|
|
732
|
+
shuntsuCounts.set(key, currentCount + 1);
|
|
733
|
+
}
|
|
734
|
+
let pairCount = 0;
|
|
735
|
+
for (const count of shuntsuCounts.values()) {
|
|
736
|
+
pairCount += Math.floor(count / 2);
|
|
737
|
+
}
|
|
738
|
+
return pairCount;
|
|
739
|
+
}
|
|
740
|
+
function analyzeIshokuPattern(hand) {
|
|
741
|
+
let blocks;
|
|
742
|
+
if (hand.type === "Mentsu") {
|
|
743
|
+
blocks = [hand.jantou, ...hand.fourMentsu];
|
|
744
|
+
} else if (hand.type === "Chiitoitsu") {
|
|
745
|
+
blocks = hand.pairs;
|
|
746
|
+
} else {
|
|
747
|
+
return void 0;
|
|
748
|
+
}
|
|
749
|
+
const allHais = blocks.flatMap((b) => b.hais);
|
|
750
|
+
const hasJihai = allHais.some((k) => kindIdToHaiType(k) === HaiType.Jihai);
|
|
751
|
+
const suupais = allHais.filter((k) => isSuupai(k));
|
|
752
|
+
if (suupais.length === 0) {
|
|
753
|
+
return { hasJihai, suupaiSuit: void 0 };
|
|
754
|
+
}
|
|
755
|
+
const firstSuupai = suupais[0];
|
|
756
|
+
if (firstSuupai === void 0) {
|
|
757
|
+
return { hasJihai, suupaiSuit: void 0 };
|
|
758
|
+
}
|
|
759
|
+
const firstSuupaiType = kindIdToHaiType(firstSuupai);
|
|
760
|
+
const isAllSameType = suupais.every(
|
|
761
|
+
(k) => kindIdToHaiType(k) === firstSuupaiType
|
|
762
|
+
);
|
|
763
|
+
return {
|
|
764
|
+
hasJihai,
|
|
765
|
+
suupaiSuit: isAllSameType ? firstSuupaiType : void 0
|
|
766
|
+
};
|
|
767
|
+
}
|
|
728
768
|
function createYakuDefinition(yaku, check) {
|
|
729
769
|
return {
|
|
730
770
|
yaku,
|
|
@@ -798,25 +838,7 @@ const IIPEIKO_YAKU = {
|
|
|
798
838
|
}
|
|
799
839
|
};
|
|
800
840
|
const checkIipeikou = (hand) => {
|
|
801
|
-
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
804
|
-
const shuntsuList = hand.fourMentsu.filter(
|
|
805
|
-
(mentsu) => mentsu.type === "Shuntsu"
|
|
806
|
-
);
|
|
807
|
-
if (shuntsuList.length < 2) {
|
|
808
|
-
return false;
|
|
809
|
-
}
|
|
810
|
-
const shuntsuCounts = /* @__PURE__ */ new Map();
|
|
811
|
-
for (const shuntsu of shuntsuList) {
|
|
812
|
-
const key = shuntsu.hais[0];
|
|
813
|
-
const currentCount = shuntsuCounts.get(key) ?? 0;
|
|
814
|
-
shuntsuCounts.set(key, currentCount + 1);
|
|
815
|
-
}
|
|
816
|
-
let pairCount = 0;
|
|
817
|
-
for (const count of shuntsuCounts.values()) {
|
|
818
|
-
pairCount += Math.floor(count / 2);
|
|
819
|
-
}
|
|
841
|
+
const pairCount = countShuntsuPairs(hand);
|
|
820
842
|
return pairCount === 1;
|
|
821
843
|
};
|
|
822
844
|
const iipeikouDefinition = createYakuDefinition(
|
|
@@ -835,22 +857,13 @@ const checkRyanpeikou = (hand) => {
|
|
|
835
857
|
if (hand.type !== "Mentsu") {
|
|
836
858
|
return false;
|
|
837
859
|
}
|
|
838
|
-
const
|
|
860
|
+
const shuntsuCount = hand.fourMentsu.filter(
|
|
839
861
|
(mentsu) => mentsu.type === "Shuntsu"
|
|
840
|
-
);
|
|
841
|
-
if (
|
|
862
|
+
).length;
|
|
863
|
+
if (shuntsuCount < 4) {
|
|
842
864
|
return false;
|
|
843
865
|
}
|
|
844
|
-
const
|
|
845
|
-
for (const shuntsu of shuntsuList) {
|
|
846
|
-
const key = shuntsu.hais[0];
|
|
847
|
-
const currentCount = shuntsuCounts.get(key) ?? 0;
|
|
848
|
-
shuntsuCounts.set(key, currentCount + 1);
|
|
849
|
-
}
|
|
850
|
-
let pairCount = 0;
|
|
851
|
-
for (const count of shuntsuCounts.values()) {
|
|
852
|
-
pairCount += Math.floor(count / 2);
|
|
853
|
-
}
|
|
866
|
+
const pairCount = countShuntsuPairs(hand);
|
|
854
867
|
return pairCount >= 2;
|
|
855
868
|
};
|
|
856
869
|
const ryanpeikouDefinition = createYakuDefinition(
|
|
@@ -1510,26 +1523,9 @@ const HONITSU_YAKU = {
|
|
|
1510
1523
|
}
|
|
1511
1524
|
};
|
|
1512
1525
|
const checkHonitsu = (hand) => {
|
|
1513
|
-
|
|
1514
|
-
if (
|
|
1515
|
-
|
|
1516
|
-
} else if (hand.type === "Chiitoitsu") {
|
|
1517
|
-
blocks = hand.pairs;
|
|
1518
|
-
} else {
|
|
1519
|
-
return false;
|
|
1520
|
-
}
|
|
1521
|
-
const allHais = blocks.flatMap((b) => b.hais);
|
|
1522
|
-
const hasJihai = allHais.some((k) => kindIdToHaiType(k) === HaiType.Jihai);
|
|
1523
|
-
if (!hasJihai) return false;
|
|
1524
|
-
const suupais = allHais.filter((k) => isSuupai(k));
|
|
1525
|
-
if (suupais.length === 0) return false;
|
|
1526
|
-
const firstSuupai = suupais[0];
|
|
1527
|
-
if (firstSuupai === void 0) return false;
|
|
1528
|
-
const firstSuupaiType = kindIdToHaiType(firstSuupai);
|
|
1529
|
-
const isAllSameType = suupais.every(
|
|
1530
|
-
(k) => kindIdToHaiType(k) === firstSuupaiType
|
|
1531
|
-
);
|
|
1532
|
-
return isAllSameType;
|
|
1526
|
+
const result = analyzeIshokuPattern(hand);
|
|
1527
|
+
if (result === void 0) return false;
|
|
1528
|
+
return result.hasJihai && result.suupaiSuit !== void 0;
|
|
1533
1529
|
};
|
|
1534
1530
|
const honitsuDefinition = createYakuDefinition(
|
|
1535
1531
|
HONITSU_YAKU,
|
|
@@ -1543,26 +1539,9 @@ const CHINITSU_YAKU = {
|
|
|
1543
1539
|
}
|
|
1544
1540
|
};
|
|
1545
1541
|
const checkChinitsu = (hand) => {
|
|
1546
|
-
|
|
1547
|
-
if (
|
|
1548
|
-
|
|
1549
|
-
} else if (hand.type === "Chiitoitsu") {
|
|
1550
|
-
blocks = hand.pairs;
|
|
1551
|
-
} else {
|
|
1552
|
-
return false;
|
|
1553
|
-
}
|
|
1554
|
-
const allHais = blocks.flatMap((b) => b.hais);
|
|
1555
|
-
const hasJihai = allHais.some((k) => kindIdToHaiType(k) === HaiType.Jihai);
|
|
1556
|
-
if (hasJihai) return false;
|
|
1557
|
-
const suupais = allHais.filter((k) => isSuupai(k));
|
|
1558
|
-
if (suupais.length === 0) return false;
|
|
1559
|
-
const firstSuupai = suupais[0];
|
|
1560
|
-
if (firstSuupai === void 0) return false;
|
|
1561
|
-
const firstSuupaiType = kindIdToHaiType(firstSuupai);
|
|
1562
|
-
const isAllSameType = suupais.every(
|
|
1563
|
-
(k) => kindIdToHaiType(k) === firstSuupaiType
|
|
1564
|
-
);
|
|
1565
|
-
return isAllSameType;
|
|
1542
|
+
const result = analyzeIshokuPattern(hand);
|
|
1543
|
+
if (result === void 0) return false;
|
|
1544
|
+
return !result.hasJihai && result.suupaiSuit !== void 0;
|
|
1566
1545
|
};
|
|
1567
1546
|
const chinitsuDefinition = createYakuDefinition(
|
|
1568
1547
|
CHINITSU_YAKU,
|
|
@@ -1648,15 +1627,15 @@ function detectYakuForStructure(hand, context) {
|
|
|
1648
1627
|
function getTotalHan(yakuResult) {
|
|
1649
1628
|
return yakuResult.reduce((sum, [, han]) => sum + han, 0);
|
|
1650
1629
|
}
|
|
1651
|
-
function detectYaku(tehai,
|
|
1630
|
+
function detectYaku(tehai, config) {
|
|
1652
1631
|
const context = {
|
|
1653
1632
|
isMenzen: isMenzen(tehai),
|
|
1654
|
-
agariHai,
|
|
1655
|
-
bakaze: bakaze
|
|
1656
|
-
jikaze: jikaze
|
|
1657
|
-
doraMarkers: doraMarkers ?? [],
|
|
1658
|
-
uraDoraMarkers: uraDoraMarkers ?? [],
|
|
1659
|
-
isTsumo
|
|
1633
|
+
agariHai: config.agariHai,
|
|
1634
|
+
bakaze: config.bakaze,
|
|
1635
|
+
jikaze: config.jikaze,
|
|
1636
|
+
doraMarkers: config.doraMarkers ?? [],
|
|
1637
|
+
uraDoraMarkers: config.uraDoraMarkers ?? [],
|
|
1638
|
+
isTsumo: config.isTsumo
|
|
1660
1639
|
};
|
|
1661
1640
|
const structuralInterpretations = getHouraStructures(tehai);
|
|
1662
1641
|
let bestResult = [];
|
|
@@ -1680,7 +1659,7 @@ function isExtendedMspz(input) {
|
|
|
1680
1659
|
}
|
|
1681
1660
|
function asExtendedMspz(input) {
|
|
1682
1661
|
if (!isExtendedMspz(input)) {
|
|
1683
|
-
throw new
|
|
1662
|
+
throw new MspzParseError(`Invalid Extended MSPZ string: ${input}`);
|
|
1684
1663
|
}
|
|
1685
1664
|
return input;
|
|
1686
1665
|
}
|
|
@@ -1695,25 +1674,26 @@ function parseExtendedMspz$1(input) {
|
|
|
1695
1674
|
for (const char of input) {
|
|
1696
1675
|
if (char === "[") {
|
|
1697
1676
|
if (mode !== "closed")
|
|
1698
|
-
throw new
|
|
1677
|
+
throw new MspzParseError("Nested brackets are not supported");
|
|
1699
1678
|
if (current.length > 0) closedParts.push(current);
|
|
1700
1679
|
current = "[";
|
|
1701
1680
|
mode = "open";
|
|
1702
1681
|
} else if (char === "]") {
|
|
1703
|
-
if (mode !== "open")
|
|
1682
|
+
if (mode !== "open")
|
|
1683
|
+
throw new MspzParseError("Unexpected closing bracket ']'");
|
|
1704
1684
|
current += "]";
|
|
1705
1685
|
exposed.push(parseMentsuFromExtendedMspz(asExtendedMspz(current)));
|
|
1706
1686
|
current = "";
|
|
1707
1687
|
mode = "closed";
|
|
1708
1688
|
} else if (char === "(") {
|
|
1709
1689
|
if (mode !== "closed")
|
|
1710
|
-
throw new
|
|
1690
|
+
throw new MspzParseError("Nested parentheses are not supported");
|
|
1711
1691
|
if (current.length > 0) closedParts.push(current);
|
|
1712
1692
|
current = "(";
|
|
1713
1693
|
mode = "ankan";
|
|
1714
1694
|
} else if (char === ")") {
|
|
1715
1695
|
if (mode !== "ankan")
|
|
1716
|
-
throw new
|
|
1696
|
+
throw new MspzParseError("Unexpected closing parenthesis ')'");
|
|
1717
1697
|
current += ")";
|
|
1718
1698
|
exposed.push(parseMentsuFromExtendedMspz(asExtendedMspz(current)));
|
|
1719
1699
|
current = "";
|
|
@@ -1723,7 +1703,8 @@ function parseExtendedMspz$1(input) {
|
|
|
1723
1703
|
}
|
|
1724
1704
|
}
|
|
1725
1705
|
if (current.length > 0) {
|
|
1726
|
-
if (mode !== "closed")
|
|
1706
|
+
if (mode !== "closed")
|
|
1707
|
+
throw new MspzParseError("Unclosed bracket or parenthesis");
|
|
1727
1708
|
closedParts.push(current);
|
|
1728
1709
|
}
|
|
1729
1710
|
const fullClosedMspz = closedParts.join("");
|
|
@@ -1743,22 +1724,24 @@ function parseMentsuFromExtendedMspz(block) {
|
|
|
1743
1724
|
mode = "ankan";
|
|
1744
1725
|
content = block.slice(1, -1);
|
|
1745
1726
|
} else {
|
|
1746
|
-
throw new
|
|
1727
|
+
throw new MspzParseError(
|
|
1747
1728
|
`Invalid Extended MSPZ block: ${block} (must be [...] or (...))`
|
|
1748
1729
|
);
|
|
1749
1730
|
}
|
|
1750
1731
|
const ids = parseMspzToHaiKindIds(asMspz(content));
|
|
1751
1732
|
if (ids.length === 0) {
|
|
1752
|
-
throw new
|
|
1733
|
+
throw new MspzParseError("Empty mentsu specification");
|
|
1753
1734
|
}
|
|
1754
1735
|
const count = ids.length;
|
|
1755
1736
|
const isAllSame = ids.every((id) => id === ids[0]);
|
|
1756
1737
|
if (mode === "ankan") {
|
|
1757
1738
|
if (count !== 4 || !isAllSame) {
|
|
1758
|
-
throw new
|
|
1739
|
+
throw new MspzParseError(
|
|
1740
|
+
`Invalid Ankan: ${block} (must be 4 identical tiles)`
|
|
1741
|
+
);
|
|
1759
1742
|
}
|
|
1760
1743
|
if (!isTuple4(ids)) {
|
|
1761
|
-
throw new
|
|
1744
|
+
throw new MspzParseError("Internal Error: ids length check mismatch");
|
|
1762
1745
|
}
|
|
1763
1746
|
const kantsu = {
|
|
1764
1747
|
type: MentsuType.Kantsu,
|
|
@@ -1769,7 +1752,7 @@ function parseMentsuFromExtendedMspz(block) {
|
|
|
1769
1752
|
}
|
|
1770
1753
|
if (count === 4 && isAllSame) {
|
|
1771
1754
|
if (!isTuple4(ids)) {
|
|
1772
|
-
throw new
|
|
1755
|
+
throw new MspzParseError("Internal Error: ids length check mismatch");
|
|
1773
1756
|
}
|
|
1774
1757
|
const kantsu = {
|
|
1775
1758
|
type: MentsuType.Kantsu,
|
|
@@ -1780,7 +1763,7 @@ function parseMentsuFromExtendedMspz(block) {
|
|
|
1780
1763
|
return kantsu;
|
|
1781
1764
|
} else if (count === 3 && isAllSame) {
|
|
1782
1765
|
if (!isTuple3(ids)) {
|
|
1783
|
-
throw new
|
|
1766
|
+
throw new MspzParseError("Internal Error: ids length check mismatch");
|
|
1784
1767
|
}
|
|
1785
1768
|
const koutsu = {
|
|
1786
1769
|
type: MentsuType.Koutsu,
|
|
@@ -1791,7 +1774,7 @@ function parseMentsuFromExtendedMspz(block) {
|
|
|
1791
1774
|
return koutsu;
|
|
1792
1775
|
} else if (count === 3) {
|
|
1793
1776
|
if (!isTuple3(ids)) {
|
|
1794
|
-
throw new
|
|
1777
|
+
throw new MspzParseError("Internal Error: ids length check mismatch");
|
|
1795
1778
|
}
|
|
1796
1779
|
const shuntsu = {
|
|
1797
1780
|
type: MentsuType.Shuntsu,
|
|
@@ -1801,13 +1784,13 @@ function parseMentsuFromExtendedMspz(block) {
|
|
|
1801
1784
|
};
|
|
1802
1785
|
return shuntsu;
|
|
1803
1786
|
}
|
|
1804
|
-
throw new
|
|
1787
|
+
throw new MspzParseError(
|
|
1805
1788
|
`Invalid Mentsu specification: ${block} (must be 3 or 4 tiles)`
|
|
1806
1789
|
);
|
|
1807
1790
|
}
|
|
1808
1791
|
function asMspz(input) {
|
|
1809
1792
|
if (!isMspz(input)) {
|
|
1810
|
-
throw new
|
|
1793
|
+
throw new MspzParseError(`Invalid MSPZ string: ${input}`);
|
|
1811
1794
|
}
|
|
1812
1795
|
return input;
|
|
1813
1796
|
}
|
|
@@ -1938,7 +1921,7 @@ const VALID_FU_VALUES = [
|
|
|
1938
1921
|
function toFu(value) {
|
|
1939
1922
|
const fu = VALID_FU_VALUES.find((f) => f === value);
|
|
1940
1923
|
if (fu === void 0) {
|
|
1941
|
-
throw new
|
|
1924
|
+
throw new MahjongError(`Invalid fu value: ${value}`);
|
|
1942
1925
|
}
|
|
1943
1926
|
return fu;
|
|
1944
1927
|
}
|
|
@@ -2108,7 +2091,7 @@ function getLimitBasePoints(level) {
|
|
|
2108
2091
|
case ScoreLevel.Mangan:
|
|
2109
2092
|
return SCORE_BASE_MANGAN;
|
|
2110
2093
|
case ScoreLevel.Normal:
|
|
2111
|
-
return
|
|
2094
|
+
return void 0;
|
|
2112
2095
|
}
|
|
2113
2096
|
}
|
|
2114
2097
|
function createScoreContext(tehai, config) {
|
|
@@ -2126,7 +2109,7 @@ function createScoreContext(tehai, config) {
|
|
|
2126
2109
|
function calculateScoreForTehai(tehai, config) {
|
|
2127
2110
|
const context = createScoreContext(tehai, config);
|
|
2128
2111
|
const structuralInterpretations = getHouraStructures(tehai);
|
|
2129
|
-
let bestResult =
|
|
2112
|
+
let bestResult = void 0;
|
|
2130
2113
|
let maxTotalPoints = -1;
|
|
2131
2114
|
for (const hand of structuralInterpretations) {
|
|
2132
2115
|
const yakuResult = detectYakuForStructure(hand, context);
|
|
@@ -2203,11 +2186,14 @@ export {
|
|
|
2203
2186
|
MahjongArgumentError,
|
|
2204
2187
|
MahjongError,
|
|
2205
2188
|
MentsuType,
|
|
2189
|
+
MspzParseError,
|
|
2206
2190
|
NoYakuError,
|
|
2207
2191
|
ShoushaiError,
|
|
2208
2192
|
Tacha,
|
|
2209
2193
|
TahaiError,
|
|
2210
2194
|
YAOCHU_KIND_IDS,
|
|
2195
|
+
assertTehai13,
|
|
2196
|
+
assertTehai14,
|
|
2211
2197
|
calculateScoreForTehai,
|
|
2212
2198
|
calculateShanten,
|
|
2213
2199
|
classifyMachi,
|