@pai-forge/riichi-mahjong 0.3.4 → 0.3.6

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.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, MahjongError.prototype);
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
- Object.setPrototypeOf(this, NoYakuError.prototype);
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) {
@@ -201,6 +203,481 @@ const YAOCHU_KIND_IDS = [
201
203
  function isYaochu(kind) {
202
204
  return YAOCHU_KIND_IDS.some((k) => k === kind);
203
205
  }
206
+ const defaultErrorConfig = {
207
+ withStackTrace: false
208
+ };
209
+ const createNeverThrowError = (message, result, config = defaultErrorConfig) => {
210
+ const data = result.isOk() ? { type: "Ok", value: result.value } : { type: "Err", value: result.error };
211
+ const maybeStack = config.withStackTrace ? new Error().stack : void 0;
212
+ return {
213
+ data,
214
+ message,
215
+ stack: maybeStack
216
+ };
217
+ };
218
+ function __awaiter(thisArg, _arguments, P, generator) {
219
+ function adopt(value) {
220
+ return value instanceof P ? value : new P(function(resolve) {
221
+ resolve(value);
222
+ });
223
+ }
224
+ return new (P || (P = Promise))(function(resolve, reject) {
225
+ function fulfilled(value) {
226
+ try {
227
+ step(generator.next(value));
228
+ } catch (e) {
229
+ reject(e);
230
+ }
231
+ }
232
+ function rejected(value) {
233
+ try {
234
+ step(generator["throw"](value));
235
+ } catch (e) {
236
+ reject(e);
237
+ }
238
+ }
239
+ function step(result) {
240
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
241
+ }
242
+ step((generator = generator.apply(thisArg, [])).next());
243
+ });
244
+ }
245
+ function __values(o) {
246
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
247
+ if (m) return m.call(o);
248
+ if (o && typeof o.length === "number") return {
249
+ next: function() {
250
+ if (o && i >= o.length) o = void 0;
251
+ return { value: o && o[i++], done: !o };
252
+ }
253
+ };
254
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
255
+ }
256
+ function __await(v) {
257
+ return this instanceof __await ? (this.v = v, this) : new __await(v);
258
+ }
259
+ function __asyncGenerator(thisArg, _arguments, generator) {
260
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
261
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
262
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function() {
263
+ return this;
264
+ }, i;
265
+ function awaitReturn(f) {
266
+ return function(v) {
267
+ return Promise.resolve(v).then(f, reject);
268
+ };
269
+ }
270
+ function verb(n, f) {
271
+ if (g[n]) {
272
+ i[n] = function(v) {
273
+ return new Promise(function(a, b) {
274
+ q.push([n, v, a, b]) > 1 || resume(n, v);
275
+ });
276
+ };
277
+ if (f) i[n] = f(i[n]);
278
+ }
279
+ }
280
+ function resume(n, v) {
281
+ try {
282
+ step(g[n](v));
283
+ } catch (e) {
284
+ settle(q[0][3], e);
285
+ }
286
+ }
287
+ function step(r) {
288
+ r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r);
289
+ }
290
+ function fulfill(value) {
291
+ resume("next", value);
292
+ }
293
+ function reject(value) {
294
+ resume("throw", value);
295
+ }
296
+ function settle(f, v) {
297
+ if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]);
298
+ }
299
+ }
300
+ function __asyncDelegator(o) {
301
+ var i, p;
302
+ return i = {}, verb("next"), verb("throw", function(e) {
303
+ throw e;
304
+ }), verb("return"), i[Symbol.iterator] = function() {
305
+ return this;
306
+ }, i;
307
+ function verb(n, f) {
308
+ i[n] = o[n] ? function(v) {
309
+ return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v;
310
+ } : f;
311
+ }
312
+ }
313
+ function __asyncValues(o) {
314
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
315
+ var m = o[Symbol.asyncIterator], i;
316
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() {
317
+ return this;
318
+ }, i);
319
+ function verb(n) {
320
+ i[n] = o[n] && function(v) {
321
+ return new Promise(function(resolve, reject) {
322
+ v = o[n](v), settle(resolve, reject, v.done, v.value);
323
+ });
324
+ };
325
+ }
326
+ function settle(resolve, reject, d, v) {
327
+ Promise.resolve(v).then(function(v2) {
328
+ resolve({ value: v2, done: d });
329
+ }, reject);
330
+ }
331
+ }
332
+ typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
333
+ var e = new Error(message);
334
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
335
+ };
336
+ class ResultAsync {
337
+ constructor(res) {
338
+ this._promise = res;
339
+ }
340
+ static fromSafePromise(promise) {
341
+ const newPromise = promise.then((value) => new Ok(value));
342
+ return new ResultAsync(newPromise);
343
+ }
344
+ static fromPromise(promise, errorFn) {
345
+ const newPromise = promise.then((value) => new Ok(value)).catch((e) => new Err(errorFn(e)));
346
+ return new ResultAsync(newPromise);
347
+ }
348
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
349
+ static fromThrowable(fn, errorFn) {
350
+ return (...args) => {
351
+ return new ResultAsync((() => __awaiter(this, void 0, void 0, function* () {
352
+ try {
353
+ return new Ok(yield fn(...args));
354
+ } catch (error) {
355
+ return new Err(errorFn ? errorFn(error) : error);
356
+ }
357
+ }))());
358
+ };
359
+ }
360
+ static combine(asyncResultList) {
361
+ return combineResultAsyncList(asyncResultList);
362
+ }
363
+ static combineWithAllErrors(asyncResultList) {
364
+ return combineResultAsyncListWithAllErrors(asyncResultList);
365
+ }
366
+ map(f) {
367
+ return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
368
+ if (res.isErr()) {
369
+ return new Err(res.error);
370
+ }
371
+ return new Ok(yield f(res.value));
372
+ })));
373
+ }
374
+ andThrough(f) {
375
+ return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
376
+ if (res.isErr()) {
377
+ return new Err(res.error);
378
+ }
379
+ const newRes = yield f(res.value);
380
+ if (newRes.isErr()) {
381
+ return new Err(newRes.error);
382
+ }
383
+ return new Ok(res.value);
384
+ })));
385
+ }
386
+ andTee(f) {
387
+ return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
388
+ if (res.isErr()) {
389
+ return new Err(res.error);
390
+ }
391
+ try {
392
+ yield f(res.value);
393
+ } catch (e) {
394
+ }
395
+ return new Ok(res.value);
396
+ })));
397
+ }
398
+ orTee(f) {
399
+ return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
400
+ if (res.isOk()) {
401
+ return new Ok(res.value);
402
+ }
403
+ try {
404
+ yield f(res.error);
405
+ } catch (e) {
406
+ }
407
+ return new Err(res.error);
408
+ })));
409
+ }
410
+ mapErr(f) {
411
+ return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
412
+ if (res.isOk()) {
413
+ return new Ok(res.value);
414
+ }
415
+ return new Err(yield f(res.error));
416
+ })));
417
+ }
418
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
419
+ andThen(f) {
420
+ return new ResultAsync(this._promise.then((res) => {
421
+ if (res.isErr()) {
422
+ return new Err(res.error);
423
+ }
424
+ const newValue = f(res.value);
425
+ return newValue instanceof ResultAsync ? newValue._promise : newValue;
426
+ }));
427
+ }
428
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
429
+ orElse(f) {
430
+ return new ResultAsync(this._promise.then((res) => __awaiter(this, void 0, void 0, function* () {
431
+ if (res.isErr()) {
432
+ return f(res.error);
433
+ }
434
+ return new Ok(res.value);
435
+ })));
436
+ }
437
+ match(ok2, _err) {
438
+ return this._promise.then((res) => res.match(ok2, _err));
439
+ }
440
+ unwrapOr(t) {
441
+ return this._promise.then((res) => res.unwrapOr(t));
442
+ }
443
+ /**
444
+ * @deprecated will be removed in 9.0.0.
445
+ *
446
+ * You can use `safeTry` without this method.
447
+ * @example
448
+ * ```typescript
449
+ * safeTry(async function* () {
450
+ * const okValue = yield* yourResult
451
+ * })
452
+ * ```
453
+ * Emulates Rust's `?` operator in `safeTry`'s body. See also `safeTry`.
454
+ */
455
+ safeUnwrap() {
456
+ return __asyncGenerator(this, arguments, function* safeUnwrap_1() {
457
+ return yield __await(yield __await(yield* __asyncDelegator(__asyncValues(yield __await(this._promise.then((res) => res.safeUnwrap()))))));
458
+ });
459
+ }
460
+ // Makes ResultAsync implement PromiseLike<Result>
461
+ then(successCallback, failureCallback) {
462
+ return this._promise.then(successCallback, failureCallback);
463
+ }
464
+ [Symbol.asyncIterator]() {
465
+ return __asyncGenerator(this, arguments, function* _a() {
466
+ const result = yield __await(this._promise);
467
+ if (result.isErr()) {
468
+ yield yield __await(errAsync(result.error));
469
+ }
470
+ return yield __await(result.value);
471
+ });
472
+ }
473
+ }
474
+ function errAsync(err2) {
475
+ return new ResultAsync(Promise.resolve(new Err(err2)));
476
+ }
477
+ const combineResultList = (resultList) => {
478
+ let acc = ok([]);
479
+ for (const result of resultList) {
480
+ if (result.isErr()) {
481
+ acc = err(result.error);
482
+ break;
483
+ } else {
484
+ acc.map((list) => list.push(result.value));
485
+ }
486
+ }
487
+ return acc;
488
+ };
489
+ const combineResultAsyncList = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
490
+ const combineResultListWithAllErrors = (resultList) => {
491
+ let acc = ok([]);
492
+ for (const result of resultList) {
493
+ if (result.isErr() && acc.isErr()) {
494
+ acc.error.push(result.error);
495
+ } else if (result.isErr() && acc.isOk()) {
496
+ acc = err([result.error]);
497
+ } else if (result.isOk() && acc.isOk()) {
498
+ acc.value.push(result.value);
499
+ }
500
+ }
501
+ return acc;
502
+ };
503
+ const combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
504
+ var Result;
505
+ (function(Result2) {
506
+ function fromThrowable(fn, errorFn) {
507
+ return (...args) => {
508
+ try {
509
+ const result = fn(...args);
510
+ return ok(result);
511
+ } catch (e) {
512
+ return err(errorFn ? errorFn(e) : e);
513
+ }
514
+ };
515
+ }
516
+ Result2.fromThrowable = fromThrowable;
517
+ function combine(resultList) {
518
+ return combineResultList(resultList);
519
+ }
520
+ Result2.combine = combine;
521
+ function combineWithAllErrors(resultList) {
522
+ return combineResultListWithAllErrors(resultList);
523
+ }
524
+ Result2.combineWithAllErrors = combineWithAllErrors;
525
+ })(Result || (Result = {}));
526
+ function ok(value) {
527
+ return new Ok(value);
528
+ }
529
+ function err(err2) {
530
+ return new Err(err2);
531
+ }
532
+ class Ok {
533
+ constructor(value) {
534
+ this.value = value;
535
+ }
536
+ isOk() {
537
+ return true;
538
+ }
539
+ isErr() {
540
+ return !this.isOk();
541
+ }
542
+ map(f) {
543
+ return ok(f(this.value));
544
+ }
545
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
546
+ mapErr(_f) {
547
+ return ok(this.value);
548
+ }
549
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
550
+ andThen(f) {
551
+ return f(this.value);
552
+ }
553
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
554
+ andThrough(f) {
555
+ return f(this.value).map((_value) => this.value);
556
+ }
557
+ andTee(f) {
558
+ try {
559
+ f(this.value);
560
+ } catch (e) {
561
+ }
562
+ return ok(this.value);
563
+ }
564
+ orTee(_f) {
565
+ return ok(this.value);
566
+ }
567
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
568
+ orElse(_f) {
569
+ return ok(this.value);
570
+ }
571
+ asyncAndThen(f) {
572
+ return f(this.value);
573
+ }
574
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
575
+ asyncAndThrough(f) {
576
+ return f(this.value).map(() => this.value);
577
+ }
578
+ asyncMap(f) {
579
+ return ResultAsync.fromSafePromise(f(this.value));
580
+ }
581
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
582
+ unwrapOr(_v) {
583
+ return this.value;
584
+ }
585
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
586
+ match(ok2, _err) {
587
+ return ok2(this.value);
588
+ }
589
+ safeUnwrap() {
590
+ const value = this.value;
591
+ return (function* () {
592
+ return value;
593
+ })();
594
+ }
595
+ _unsafeUnwrap(_) {
596
+ return this.value;
597
+ }
598
+ _unsafeUnwrapErr(config) {
599
+ throw createNeverThrowError("Called `_unsafeUnwrapErr` on an Ok", this, config);
600
+ }
601
+ // eslint-disable-next-line @typescript-eslint/no-this-alias, require-yield
602
+ *[Symbol.iterator]() {
603
+ return this.value;
604
+ }
605
+ }
606
+ class Err {
607
+ constructor(error) {
608
+ this.error = error;
609
+ }
610
+ isOk() {
611
+ return false;
612
+ }
613
+ isErr() {
614
+ return !this.isOk();
615
+ }
616
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
617
+ map(_f) {
618
+ return err(this.error);
619
+ }
620
+ mapErr(f) {
621
+ return err(f(this.error));
622
+ }
623
+ andThrough(_f) {
624
+ return err(this.error);
625
+ }
626
+ andTee(_f) {
627
+ return err(this.error);
628
+ }
629
+ orTee(f) {
630
+ try {
631
+ f(this.error);
632
+ } catch (e) {
633
+ }
634
+ return err(this.error);
635
+ }
636
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
637
+ andThen(_f) {
638
+ return err(this.error);
639
+ }
640
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
641
+ orElse(f) {
642
+ return f(this.error);
643
+ }
644
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
645
+ asyncAndThen(_f) {
646
+ return errAsync(this.error);
647
+ }
648
+ asyncAndThrough(_f) {
649
+ return errAsync(this.error);
650
+ }
651
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
652
+ asyncMap(_f) {
653
+ return errAsync(this.error);
654
+ }
655
+ unwrapOr(v) {
656
+ return v;
657
+ }
658
+ match(_ok, err2) {
659
+ return err2(this.error);
660
+ }
661
+ safeUnwrap() {
662
+ const error = this.error;
663
+ return (function* () {
664
+ yield err(error);
665
+ throw new Error("Do not use this generator out of `safeTry`");
666
+ })();
667
+ }
668
+ _unsafeUnwrap(config) {
669
+ throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
670
+ }
671
+ _unsafeUnwrapErr(_) {
672
+ return this.error;
673
+ }
674
+ *[Symbol.iterator]() {
675
+ const self = this;
676
+ yield self;
677
+ return self;
678
+ }
679
+ }
680
+ Result.fromThrowable;
204
681
  function calculateTehaiCount(tehai) {
205
682
  return tehai.closed.length + tehai.exposed.length * 3;
206
683
  }
@@ -214,32 +691,44 @@ function countHaiKind(hais) {
214
691
  function validateTehai13(tehai) {
215
692
  const count = calculateTehaiCount(tehai);
216
693
  if (count < 13) {
217
- throw new ShoushaiError();
694
+ return err(new ShoushaiError());
218
695
  }
219
696
  if (count > 13) {
220
- throw new TahaiError();
697
+ return err(new TahaiError());
221
698
  }
222
- validateHaiConsistency(tehai);
699
+ const consRes = validateHaiConsistency(tehai);
700
+ if (consRes.isErr()) {
701
+ return err(consRes.error);
702
+ }
703
+ return ok(tehai);
223
704
  }
224
705
  function validateTehai14(tehai) {
225
706
  const count = calculateTehaiCount(tehai);
226
707
  if (count < 14) {
227
- throw new ShoushaiError();
708
+ return err(new ShoushaiError());
228
709
  }
229
710
  if (count > 14) {
230
- throw new TahaiError();
711
+ return err(new TahaiError());
712
+ }
713
+ const consRes = validateHaiConsistency(tehai);
714
+ if (consRes.isErr()) {
715
+ return err(consRes.error);
231
716
  }
232
- validateHaiConsistency(tehai);
717
+ return ok(tehai);
233
718
  }
234
719
  function validateTehai(tehai) {
235
720
  const count = calculateTehaiCount(tehai);
236
721
  if (count < 13) {
237
- throw new ShoushaiError();
722
+ return err(new ShoushaiError());
238
723
  }
239
724
  if (count > 14) {
240
- throw new TahaiError();
725
+ return err(new TahaiError());
241
726
  }
242
- validateHaiConsistency(tehai);
727
+ const consRes = validateHaiConsistency(tehai);
728
+ if (consRes.isErr()) {
729
+ return err(consRes.error);
730
+ }
731
+ return ok(tehai);
243
732
  }
244
733
  function validateHaiConsistency(tehai) {
245
734
  const allHais = [
@@ -250,40 +739,25 @@ function validateHaiConsistency(tehai) {
250
739
  if (isHaiIdMode) {
251
740
  const uniqueIds = new Set(allHais);
252
741
  if (uniqueIds.size !== allHais.length) {
253
- throw new DuplicatedHaiIdError();
742
+ return err(new DuplicatedHaiIdError());
254
743
  }
255
744
  }
256
745
  const counts = /* @__PURE__ */ new Map();
257
746
  for (const hai of allHais) {
258
- let kind = hai;
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
- }
747
+ const kind = isHaiIdMode ? haiIdToKindId(hai) : hai;
265
748
  const current = counts.get(kind) ?? 0;
266
749
  if (current + 1 > 4) {
267
- throw new InvalidHaiQuantityError();
750
+ return err(new InvalidHaiQuantityError());
268
751
  }
269
752
  counts.set(kind, current + 1);
270
753
  }
754
+ return ok(void 0);
271
755
  }
272
756
  function isTehai13(tehai) {
273
- try {
274
- validateTehai13(tehai);
275
- return true;
276
- } catch {
277
- return false;
278
- }
757
+ return validateTehai13(tehai).isOk();
279
758
  }
280
759
  function isTehai14(tehai) {
281
- try {
282
- validateTehai14(tehai);
283
- return true;
284
- } catch {
285
- return false;
286
- }
760
+ return validateTehai14(tehai).isOk();
287
761
  }
288
762
  function isValidShuntsu(kindIds) {
289
763
  if (!isTuple3(kindIds)) return false;
@@ -355,21 +829,12 @@ function getDoraNext(indicator) {
355
829
  return indicator;
356
830
  }
357
831
  function countDora(tehai, indicators) {
358
- let count = 0;
359
832
  const doraHais = indicators.map(getDoraNext);
360
- for (const hai of tehai.closed) {
361
- for (const dora of doraHais) {
362
- if (hai === dora) count++;
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;
833
+ const allHais = [...tehai.closed, ...tehai.exposed.flatMap((m) => m.hais)];
834
+ return allHais.reduce(
835
+ (count, hai) => count + doraHais.filter((d) => d === hai).length,
836
+ 0
837
+ );
373
838
  }
374
839
  function classifyMachi(hand, agariHai) {
375
840
  if (hand.type !== "Mentsu") return void 0;
@@ -551,11 +1016,12 @@ function countTaatsu(counts) {
551
1016
  return taatsu;
552
1017
  }
553
1018
  function calculateShanten(tehai, useChiitoitsu = true, useKokushi = true) {
554
- validateTehai13(tehai);
1019
+ const valRes = validateTehai13(tehai);
1020
+ if (valRes.isErr()) return err(valRes.error);
555
1021
  const chiitoitsuShanten = useChiitoitsu ? calculateChiitoitsuShanten(tehai) : Infinity;
556
1022
  const kokushiShanten = useKokushi ? calculateKokushiShanten(tehai) : Infinity;
557
1023
  const mentsuShanten = calculateMentsuTeShanten(tehai);
558
- return Math.min(chiitoitsuShanten, kokushiShanten, mentsuShanten);
1024
+ return ok(Math.min(chiitoitsuShanten, kokushiShanten, mentsuShanten));
559
1025
  }
560
1026
  function getUkeire(tehai) {
561
1027
  const currentShanten = calculateMentsuTeShanten(tehai);
@@ -566,7 +1032,7 @@ function getUkeire(tehai) {
566
1032
  ];
567
1033
  const haiCounts = countHaiKind(allHais);
568
1034
  for (let i = 0; i < 34; i++) {
569
- const tile = i;
1035
+ const tile = asHaiKindId(i);
570
1036
  if (haiCounts[tile] >= 4) {
571
1037
  continue;
572
1038
  }
@@ -725,17 +1191,115 @@ function isMenzen(tehai) {
725
1191
  function isKazehai(id) {
726
1192
  return id === HaiKind.Ton || id === HaiKind.Nan || id === HaiKind.Sha || id === HaiKind.Pei;
727
1193
  }
728
- function createYakuDefinition(yaku, check) {
1194
+ function countShuntsuPairs(hand) {
1195
+ if (hand.type !== "Mentsu") {
1196
+ return 0;
1197
+ }
1198
+ const shuntsuList = hand.fourMentsu.filter(
1199
+ (mentsu) => mentsu.type === "Shuntsu"
1200
+ );
1201
+ const shuntsuCounts = /* @__PURE__ */ new Map();
1202
+ for (const shuntsu of shuntsuList) {
1203
+ const key = shuntsu.hais[0];
1204
+ const currentCount = shuntsuCounts.get(key) ?? 0;
1205
+ shuntsuCounts.set(key, currentCount + 1);
1206
+ }
1207
+ let pairCount = 0;
1208
+ for (const count of shuntsuCounts.values()) {
1209
+ pairCount += Math.floor(count / 2);
1210
+ }
1211
+ return pairCount;
1212
+ }
1213
+ function analyzeIshokuPattern(hand) {
1214
+ let blocks;
1215
+ if (hand.type === "Mentsu") {
1216
+ blocks = [hand.jantou, ...hand.fourMentsu];
1217
+ } else if (hand.type === "Chiitoitsu") {
1218
+ blocks = hand.pairs;
1219
+ } else {
1220
+ return void 0;
1221
+ }
1222
+ const allHais = blocks.flatMap((b) => b.hais);
1223
+ const hasJihai = allHais.some((k) => kindIdToHaiType(k) === HaiType.Jihai);
1224
+ const suupais = allHais.filter((k) => isSuupai(k));
1225
+ if (suupais.length === 0) {
1226
+ return { hasJihai, suupaiSuit: void 0 };
1227
+ }
1228
+ const firstSuupai = suupais[0];
1229
+ if (firstSuupai === void 0) {
1230
+ return { hasJihai, suupaiSuit: void 0 };
1231
+ }
1232
+ const firstSuupaiType = kindIdToHaiType(firstSuupai);
1233
+ const isAllSameType = suupais.every(
1234
+ (k) => kindIdToHaiType(k) === firstSuupaiType
1235
+ );
729
1236
  return {
730
- yaku,
731
- isSatisfied: check,
732
- getHansu: (hand, context) => {
733
- if (!check(hand, context)) {
734
- return 0;
1237
+ hasJihai,
1238
+ suupaiSuit: isAllSameType ? firstSuupaiType : void 0
1239
+ };
1240
+ }
1241
+ class YakuBuilder {
1242
+ /**
1243
+ *
1244
+ */
1245
+ constructor(yaku) {
1246
+ this.yaku = yaku;
1247
+ }
1248
+ predicates = [];
1249
+ hanCalculator;
1250
+ /**
1251
+ *
1252
+ */
1253
+ require(predicate) {
1254
+ this.predicates.push(predicate);
1255
+ return this;
1256
+ }
1257
+ /**
1258
+ *
1259
+ */
1260
+ menzenOnly() {
1261
+ this.predicates.push((hand, context) => context.isMenzen);
1262
+ return this;
1263
+ }
1264
+ /**
1265
+ *
1266
+ */
1267
+ dynamicHan(calculator) {
1268
+ this.hanCalculator = calculator;
1269
+ return this;
1270
+ }
1271
+ /**
1272
+ *
1273
+ */
1274
+ build() {
1275
+ const yaku = this.yaku;
1276
+ const predicates = this.predicates;
1277
+ const calculator = this.hanCalculator;
1278
+ return {
1279
+ yaku,
1280
+ isSatisfied(hand, context) {
1281
+ return predicates.every((pred) => pred(hand, context));
1282
+ },
1283
+ getHansu(hand, context) {
1284
+ if (!this.isSatisfied(hand, context)) {
1285
+ return 0;
1286
+ }
1287
+ if (calculator) {
1288
+ return calculator(hand, context);
1289
+ }
1290
+ return context.isMenzen ? yaku.han.closed : yaku.han.open;
735
1291
  }
736
- return context.isMenzen ? yaku.han.closed : yaku.han.open;
1292
+ };
1293
+ }
1294
+ }
1295
+ function createYaku(name, closedHan, openHan = 0) {
1296
+ return new YakuBuilder({
1297
+ name,
1298
+ han: {
1299
+ closed: closedHan,
1300
+ open: openHan
737
1301
  }
738
- };
1302
+ });
739
1303
  }
740
1304
  const TANYAO_YAKU = {
741
1305
  name: "Tanyao",
@@ -752,10 +1316,11 @@ const checkTanyao = (hand) => {
752
1316
  }
753
1317
  return true;
754
1318
  };
755
- const tanyaoDefinition = createYakuDefinition(
756
- TANYAO_YAKU,
757
- checkTanyao
758
- );
1319
+ const tanyaoDefinition = createYaku(
1320
+ TANYAO_YAKU.name,
1321
+ TANYAO_YAKU.han.closed,
1322
+ TANYAO_YAKU.han.open
1323
+ ).require(checkTanyao).build();
759
1324
  const PINFU_YAKU = {
760
1325
  name: "Pinfu",
761
1326
  han: {
@@ -785,10 +1350,11 @@ const checkPinfu = (hand, context) => {
785
1350
  const waitType = classifyMachi(hand, context.agariHai);
786
1351
  return waitType === "Ryanmen";
787
1352
  };
788
- const pinfuDefinition = createYakuDefinition(
789
- PINFU_YAKU,
790
- checkPinfu
791
- );
1353
+ const pinfuDefinition = createYaku(
1354
+ PINFU_YAKU.name,
1355
+ PINFU_YAKU.han.closed,
1356
+ PINFU_YAKU.han.open
1357
+ ).require(checkPinfu).build();
792
1358
  const IIPEIKO_YAKU = {
793
1359
  name: "Iipeikou",
794
1360
  han: {
@@ -798,31 +1364,14 @@ const IIPEIKO_YAKU = {
798
1364
  }
799
1365
  };
800
1366
  const checkIipeikou = (hand) => {
801
- if (hand.type !== "Mentsu") {
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
- }
1367
+ const pairCount = countShuntsuPairs(hand);
820
1368
  return pairCount === 1;
821
1369
  };
822
- const iipeikouDefinition = createYakuDefinition(
823
- IIPEIKO_YAKU,
824
- checkIipeikou
825
- );
1370
+ const iipeikouDefinition = createYaku(
1371
+ IIPEIKO_YAKU.name,
1372
+ IIPEIKO_YAKU.han.closed,
1373
+ IIPEIKO_YAKU.han.open
1374
+ ).require(checkIipeikou).build();
826
1375
  const RYANPEIKOU_YAKU = {
827
1376
  name: "Ryanpeikou",
828
1377
  han: {
@@ -835,38 +1384,23 @@ const checkRyanpeikou = (hand) => {
835
1384
  if (hand.type !== "Mentsu") {
836
1385
  return false;
837
1386
  }
838
- const shuntsuList = hand.fourMentsu.filter(
1387
+ const shuntsuCount = hand.fourMentsu.filter(
839
1388
  (mentsu) => mentsu.type === "Shuntsu"
840
- );
841
- if (shuntsuList.length < 4) {
1389
+ ).length;
1390
+ if (shuntsuCount < 4) {
842
1391
  return false;
843
1392
  }
844
- const shuntsuCounts = /* @__PURE__ */ new Map();
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
- }
1393
+ const pairCount = countShuntsuPairs(hand);
854
1394
  return pairCount >= 2;
855
1395
  };
856
- const ryanpeikouDefinition = createYakuDefinition(
857
- RYANPEIKOU_YAKU,
858
- checkRyanpeikou
859
- );
860
- const SANANKOU_YAKU = {
861
- name: "Sanankou",
862
- han: {
863
- open: 2,
864
- closed: 2
865
- }
866
- };
867
- const checkSanankou = (hand, context) => {
1396
+ const ryanpeikouDefinition = createYaku(
1397
+ RYANPEIKOU_YAKU.name,
1398
+ RYANPEIKOU_YAKU.han.closed,
1399
+ RYANPEIKOU_YAKU.han.open
1400
+ ).require(checkRyanpeikou).build();
1401
+ const countAnkou = (hand, context) => {
868
1402
  if (hand.type !== "Mentsu") {
869
- return false;
1403
+ return 0;
870
1404
  }
871
1405
  const triplets = hand.fourMentsu.filter(
872
1406
  (m) => m.type === "Koutsu" || m.type === "Kantsu"
@@ -888,12 +1422,69 @@ const checkSanankou = (hand, context) => {
888
1422
  }
889
1423
  }
890
1424
  }
1425
+ return ankouCount;
1426
+ };
1427
+ const countSpecificKoutsu = (hand, targetKinds) => {
1428
+ if (hand.type !== "Mentsu") {
1429
+ return 0;
1430
+ }
1431
+ let count = 0;
1432
+ const triplets = hand.fourMentsu.filter(
1433
+ (m) => m.type === "Koutsu" || m.type === "Kantsu"
1434
+ );
1435
+ for (const triplet of triplets) {
1436
+ if (targetKinds.includes(triplet.hais[0])) {
1437
+ count++;
1438
+ }
1439
+ }
1440
+ return count;
1441
+ };
1442
+ const isAllHaisMatch = (hand, predicate) => {
1443
+ if (hand.type === "Mentsu") {
1444
+ const allHais = [
1445
+ ...hand.fourMentsu.flatMap((m) => m.hais),
1446
+ ...hand.jantou.hais
1447
+ ];
1448
+ return allHais.every(predicate);
1449
+ } else if (hand.type === "Chiitoitsu") {
1450
+ const allHais = hand.pairs.flatMap((p) => p.hais);
1451
+ return allHais.every(predicate);
1452
+ } else {
1453
+ return hand.yaochu.every(predicate);
1454
+ }
1455
+ };
1456
+ const getShuntsuCombinations3 = (shuntsuList) => {
1457
+ const combos = [];
1458
+ for (let i = 0; i < shuntsuList.length; i++) {
1459
+ for (let j = i + 1; j < shuntsuList.length; j++) {
1460
+ for (let k = j + 1; k < shuntsuList.length; k++) {
1461
+ const s1 = shuntsuList[i];
1462
+ const s2 = shuntsuList[j];
1463
+ const s3 = shuntsuList[k];
1464
+ if (s1 && s2 && s3) {
1465
+ combos.push([s1, s2, s3]);
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ return combos;
1471
+ };
1472
+ const SANANKOU_YAKU = {
1473
+ name: "Sanankou",
1474
+ han: {
1475
+ open: 2,
1476
+ closed: 2
1477
+ }
1478
+ };
1479
+ const checkSanankou = (hand, context) => {
1480
+ const ankouCount = countAnkou(hand, context);
891
1481
  return ankouCount >= 3;
892
1482
  };
893
- const sanankouDefinition = createYakuDefinition(
894
- SANANKOU_YAKU,
895
- checkSanankou
896
- );
1483
+ const sanankouDefinition = createYaku(
1484
+ SANANKOU_YAKU.name,
1485
+ SANANKOU_YAKU.han.closed,
1486
+ SANANKOU_YAKU.han.open
1487
+ ).require(checkSanankou).build();
897
1488
  const SUUANKOU_YAKU = {
898
1489
  name: "Suuankou",
899
1490
  han: {
@@ -904,29 +1495,7 @@ const SUUANKOU_YAKU = {
904
1495
  }
905
1496
  };
906
1497
  const checkSuuankou = (hand, context) => {
907
- if (hand.type !== "Mentsu") {
908
- return false;
909
- }
910
- const triplets = hand.fourMentsu.filter(
911
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
912
- );
913
- let ankouCount = 0;
914
- for (const triplet of triplets) {
915
- if (triplet.furo) continue;
916
- const isAgariHaiInTriplet = triplet.hais.includes(context.agariHai);
917
- const isTanki = hand.jantou.hais[0] === context.agariHai;
918
- if (context.isTsumo) {
919
- ankouCount++;
920
- } else {
921
- if (isAgariHaiInTriplet) {
922
- if (isTanki) {
923
- ankouCount++;
924
- }
925
- } else {
926
- ankouCount++;
927
- }
928
- }
929
- }
1498
+ const ankouCount = countAnkou(hand, context);
930
1499
  return ankouCount === 4;
931
1500
  };
932
1501
  const suuankouDefinition = {
@@ -955,10 +1524,11 @@ const checkSankantsu = (hand) => {
955
1524
  const kantsuList = hand.fourMentsu.filter((m) => m.type === "Kantsu");
956
1525
  return kantsuList.length >= 3;
957
1526
  };
958
- const sankantsuDefinition = createYakuDefinition(
959
- SANKANTSU_YAKU,
960
- checkSankantsu
961
- );
1527
+ const sankantsuDefinition = createYaku(
1528
+ SANKANTSU_YAKU.name,
1529
+ SANKANTSU_YAKU.han.closed,
1530
+ SANKANTSU_YAKU.han.open
1531
+ ).require(checkSankantsu).build();
962
1532
  const SUUKANTSU_YAKU = {
963
1533
  name: "Suukantsu",
964
1534
  han: {
@@ -973,10 +1543,11 @@ const checkSuukantsu = (hand) => {
973
1543
  const kantsuList = hand.fourMentsu.filter((m) => m.type === "Kantsu");
974
1544
  return kantsuList.length === 4;
975
1545
  };
976
- const suukantsuDefinition = createYakuDefinition(
977
- SUUKANTSU_YAKU,
978
- checkSuukantsu
979
- );
1546
+ const suukantsuDefinition = createYaku(
1547
+ SUUKANTSU_YAKU.name,
1548
+ SUUKANTSU_YAKU.han.closed,
1549
+ SUUKANTSU_YAKU.han.open
1550
+ ).require(checkSuukantsu).build();
980
1551
  const TOITOI_YAKU = {
981
1552
  name: "Toitoi",
982
1553
  han: {
@@ -992,10 +1563,11 @@ const checkToitoi = (hand) => {
992
1563
  (mentsu) => mentsu.type === "Koutsu" || mentsu.type === "Kantsu"
993
1564
  );
994
1565
  };
995
- const toitoiDefinition = createYakuDefinition(
996
- TOITOI_YAKU,
997
- checkToitoi
998
- );
1566
+ const toitoiDefinition = createYaku(
1567
+ TOITOI_YAKU.name,
1568
+ TOITOI_YAKU.han.closed,
1569
+ TOITOI_YAKU.han.open
1570
+ ).require(checkToitoi).build();
999
1571
  const CHIITOITSU_YAKU = {
1000
1572
  name: "Chiitoitsu",
1001
1573
  han: {
@@ -1007,10 +1579,11 @@ const CHIITOITSU_YAKU = {
1007
1579
  const checkChiitoitsu = (hand) => {
1008
1580
  return hand.type === "Chiitoitsu";
1009
1581
  };
1010
- const chiitoitsuDefinition = createYakuDefinition(
1011
- CHIITOITSU_YAKU,
1012
- checkChiitoitsu
1013
- );
1582
+ const chiitoitsuDefinition = createYaku(
1583
+ CHIITOITSU_YAKU.name,
1584
+ CHIITOITSU_YAKU.han.closed,
1585
+ CHIITOITSU_YAKU.han.open
1586
+ ).require(checkChiitoitsu).build();
1014
1587
  const HONCHAN_YAKU = {
1015
1588
  name: "Honchan",
1016
1589
  han: {
@@ -1033,10 +1606,11 @@ const checkHonchan = (hand) => {
1033
1606
  if (!hasJihai) return false;
1034
1607
  return true;
1035
1608
  };
1036
- const honchanDefinition = createYakuDefinition(
1037
- HONCHAN_YAKU,
1038
- checkHonchan
1039
- );
1609
+ const honchanDefinition = createYaku(
1610
+ HONCHAN_YAKU.name,
1611
+ HONCHAN_YAKU.han.closed,
1612
+ HONCHAN_YAKU.han.open
1613
+ ).require(checkHonchan).build();
1040
1614
  const JUNCHAN_YAKU = {
1041
1615
  name: "Junchan",
1042
1616
  han: {
@@ -1055,10 +1629,11 @@ const checkJunchan = (hand) => {
1055
1629
  if (!hasShuntsu) return false;
1056
1630
  return true;
1057
1631
  };
1058
- const junchanDefinition = createYakuDefinition(
1059
- JUNCHAN_YAKU,
1060
- checkJunchan
1061
- );
1632
+ const junchanDefinition = createYaku(
1633
+ JUNCHAN_YAKU.name,
1634
+ JUNCHAN_YAKU.han.closed,
1635
+ JUNCHAN_YAKU.han.open
1636
+ ).require(checkJunchan).build();
1062
1637
  const HONROUTOU_YAKU = {
1063
1638
  name: "Honroutou",
1064
1639
  han: {
@@ -1085,10 +1660,11 @@ const checkHonroutou = (hand) => {
1085
1660
  if (!hasJihai) return false;
1086
1661
  return true;
1087
1662
  };
1088
- const honroutouDefinition = createYakuDefinition(
1089
- HONROUTOU_YAKU,
1090
- checkHonroutou
1091
- );
1663
+ const honroutouDefinition = createYaku(
1664
+ HONROUTOU_YAKU.name,
1665
+ HONROUTOU_YAKU.han.closed,
1666
+ HONROUTOU_YAKU.han.open
1667
+ ).require(checkHonroutou).build();
1092
1668
  const CHINROUTOU_YAKU = {
1093
1669
  name: "Chinroutou",
1094
1670
  han: {
@@ -1105,10 +1681,11 @@ const checkChinroutou = (hand) => {
1105
1681
  if (!allRoutou) return false;
1106
1682
  return true;
1107
1683
  };
1108
- const chinroutouDefinition = createYakuDefinition(
1109
- CHINROUTOU_YAKU,
1110
- checkChinroutou
1111
- );
1684
+ const chinroutouDefinition = createYaku(
1685
+ CHINROUTOU_YAKU.name,
1686
+ CHINROUTOU_YAKU.han.closed,
1687
+ CHINROUTOU_YAKU.han.open
1688
+ ).require(checkChinroutou).build();
1112
1689
  const SHOUSANGEN_YAKU = {
1113
1690
  name: "Shousangen",
1114
1691
  han: {
@@ -1121,22 +1698,15 @@ const checkShousangen = (hand) => {
1121
1698
  return false;
1122
1699
  }
1123
1700
  const sangenpai = [HaiKind.Haku, HaiKind.Hatsu, HaiKind.Chun];
1124
- let sangenKoutsuCount = 0;
1125
- const triplets = hand.fourMentsu.filter(
1126
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1127
- );
1128
- for (const triplet of triplets) {
1129
- if (sangenpai.includes(triplet.hais[0])) {
1130
- sangenKoutsuCount++;
1131
- }
1132
- }
1701
+ const sangenKoutsuCount = countSpecificKoutsu(hand, sangenpai);
1133
1702
  const isSangenJantou = sangenpai.includes(hand.jantou.hais[0]);
1134
1703
  return sangenKoutsuCount === 2 && isSangenJantou;
1135
1704
  };
1136
- const shousangenDefinition = createYakuDefinition(
1137
- SHOUSANGEN_YAKU,
1138
- checkShousangen
1139
- );
1705
+ const shousangenDefinition = createYaku(
1706
+ SHOUSANGEN_YAKU.name,
1707
+ SHOUSANGEN_YAKU.han.closed,
1708
+ SHOUSANGEN_YAKU.han.open
1709
+ ).require(checkShousangen).build();
1140
1710
  const DAISANGEN_YAKU = {
1141
1711
  name: "Daisangen",
1142
1712
  han: {
@@ -1145,25 +1715,15 @@ const DAISANGEN_YAKU = {
1145
1715
  }
1146
1716
  };
1147
1717
  const checkDaisangen = (hand) => {
1148
- if (hand.type !== "Mentsu") {
1149
- return false;
1150
- }
1151
1718
  const sangenpai = [HaiKind.Haku, HaiKind.Hatsu, HaiKind.Chun];
1152
- let sangenKoutsuCount = 0;
1153
- const triplets = hand.fourMentsu.filter(
1154
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1155
- );
1156
- for (const triplet of triplets) {
1157
- if (sangenpai.includes(triplet.hais[0])) {
1158
- sangenKoutsuCount++;
1159
- }
1160
- }
1719
+ const sangenKoutsuCount = countSpecificKoutsu(hand, sangenpai);
1161
1720
  return sangenKoutsuCount === 3;
1162
1721
  };
1163
- const daisangenDefinition = createYakuDefinition(
1164
- DAISANGEN_YAKU,
1165
- checkDaisangen
1166
- );
1722
+ const daisangenDefinition = createYaku(
1723
+ DAISANGEN_YAKU.name,
1724
+ DAISANGEN_YAKU.han.closed,
1725
+ DAISANGEN_YAKU.han.open
1726
+ ).require(checkDaisangen).build();
1167
1727
  const TSUUIISOU_YAKU = {
1168
1728
  name: "Tsuuiisou",
1169
1729
  han: {
@@ -1175,25 +1735,13 @@ const isJihai = (id) => {
1175
1735
  return id >= HaiKind.Ton && id <= HaiKind.Chun;
1176
1736
  };
1177
1737
  const checkTsuuiisou = (hand) => {
1178
- const allHais = [];
1179
- if (hand.type === "Mentsu") {
1180
- for (const mentsu of hand.fourMentsu) {
1181
- allHais.push(...mentsu.hais);
1182
- }
1183
- allHais.push(...hand.jantou.hais);
1184
- } else if (hand.type === "Chiitoitsu") {
1185
- for (const pair of hand.pairs) {
1186
- allHais.push(...pair.hais);
1187
- }
1188
- } else {
1189
- return false;
1190
- }
1191
- return allHais.every(isJihai);
1738
+ return isAllHaisMatch(hand, isJihai);
1192
1739
  };
1193
- const tsuuiisouDefinition = createYakuDefinition(
1194
- TSUUIISOU_YAKU,
1195
- checkTsuuiisou
1196
- );
1740
+ const tsuuiisouDefinition = createYaku(
1741
+ TSUUIISOU_YAKU.name,
1742
+ TSUUIISOU_YAKU.han.closed,
1743
+ TSUUIISOU_YAKU.han.open
1744
+ ).require(checkTsuuiisou).build();
1197
1745
  const RYUUIISOU_YAKU = {
1198
1746
  name: "Ryuuiisou",
1199
1747
  han: {
@@ -1213,25 +1761,13 @@ const isGreen = (id) => {
1213
1761
  return GREEN_TILES.has(id);
1214
1762
  };
1215
1763
  const checkRyuuiisou = (hand) => {
1216
- const allHais = [];
1217
- if (hand.type === "Mentsu") {
1218
- for (const mentsu of hand.fourMentsu) {
1219
- allHais.push(...mentsu.hais);
1220
- }
1221
- allHais.push(...hand.jantou.hais);
1222
- } else if (hand.type === "Chiitoitsu") {
1223
- for (const pair of hand.pairs) {
1224
- allHais.push(...pair.hais);
1225
- }
1226
- } else {
1227
- return false;
1228
- }
1229
- return allHais.every(isGreen);
1764
+ return isAllHaisMatch(hand, isGreen);
1230
1765
  };
1231
- const ryuuiisouDefinition = createYakuDefinition(
1232
- RYUUIISOU_YAKU,
1233
- checkRyuuiisou
1234
- );
1766
+ const ryuuiisouDefinition = createYaku(
1767
+ RYUUIISOU_YAKU.name,
1768
+ RYUUIISOU_YAKU.han.closed,
1769
+ RYUUIISOU_YAKU.han.open
1770
+ ).require(checkRyuuiisou).build();
1235
1771
  const SHOUSUUSHII_YAKU = {
1236
1772
  name: "Shousuushii",
1237
1773
  han: {
@@ -1249,22 +1785,15 @@ const checkShousuushii = (hand) => {
1249
1785
  HaiKind.Sha,
1250
1786
  HaiKind.Pei
1251
1787
  ];
1252
- let windKoutsuCount = 0;
1253
- const triplets = hand.fourMentsu.filter(
1254
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1255
- );
1256
- for (const triplet of triplets) {
1257
- if (windTiles.includes(triplet.hais[0])) {
1258
- windKoutsuCount++;
1259
- }
1260
- }
1788
+ const windKoutsuCount = countSpecificKoutsu(hand, windTiles);
1261
1789
  const isWindJantou = windTiles.includes(hand.jantou.hais[0]);
1262
1790
  return windKoutsuCount === 3 && isWindJantou;
1263
1791
  };
1264
- const shousuushiiDefinition = createYakuDefinition(
1265
- SHOUSUUSHII_YAKU,
1266
- checkShousuushii
1267
- );
1792
+ const shousuushiiDefinition = createYaku(
1793
+ SHOUSUUSHII_YAKU.name,
1794
+ SHOUSUUSHII_YAKU.han.closed,
1795
+ SHOUSUUSHII_YAKU.han.open
1796
+ ).require(checkShousuushii).build();
1268
1797
  const DAISUUSHII_YAKU = {
1269
1798
  name: "Daisuushii",
1270
1799
  han: {
@@ -1274,30 +1803,20 @@ const DAISUUSHII_YAKU = {
1274
1803
  }
1275
1804
  };
1276
1805
  const checkDaisuushii = (hand) => {
1277
- if (hand.type !== "Mentsu") {
1278
- return false;
1279
- }
1280
1806
  const windTiles = [
1281
1807
  HaiKind.Ton,
1282
1808
  HaiKind.Nan,
1283
1809
  HaiKind.Sha,
1284
1810
  HaiKind.Pei
1285
1811
  ];
1286
- let windKoutsuCount = 0;
1287
- const triplets = hand.fourMentsu.filter(
1288
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1289
- );
1290
- for (const triplet of triplets) {
1291
- if (windTiles.includes(triplet.hais[0])) {
1292
- windKoutsuCount++;
1293
- }
1294
- }
1812
+ const windKoutsuCount = countSpecificKoutsu(hand, windTiles);
1295
1813
  return windKoutsuCount === 4;
1296
1814
  };
1297
- const daisuushiiDefinition = createYakuDefinition(
1298
- DAISUUSHII_YAKU,
1299
- checkDaisuushii
1300
- );
1815
+ const daisuushiiDefinition = createYaku(
1816
+ DAISUUSHII_YAKU.name,
1817
+ DAISUUSHII_YAKU.han.closed,
1818
+ DAISUUSHII_YAKU.han.open
1819
+ ).require(checkDaisuushii).build();
1301
1820
  const CHUUREN_POUTOU_YAKU = {
1302
1821
  name: "ChuurenPoutou",
1303
1822
  han: {
@@ -1340,24 +1859,22 @@ const checkChuurenPoutou = (hand, context) => {
1340
1859
  }
1341
1860
  return true;
1342
1861
  };
1343
- const chuurenPoutouDefinition = createYakuDefinition(
1344
- CHUUREN_POUTOU_YAKU,
1345
- checkChuurenPoutou
1346
- );
1862
+ const chuurenPoutouDefinition = createYaku(
1863
+ CHUUREN_POUTOU_YAKU.name,
1864
+ CHUUREN_POUTOU_YAKU.han.closed,
1865
+ CHUUREN_POUTOU_YAKU.han.open
1866
+ ).require(checkChuurenPoutou).build();
1347
1867
  const checkKokushi = (hand) => {
1348
1868
  return hand.type === "Kokushi";
1349
1869
  };
1350
1870
  const KOKUSHI_HAN = {
1351
- closed: 13,
1352
- open: 0
1871
+ closed: 13
1353
1872
  };
1354
- const kokushiDefinition = createYakuDefinition(
1355
- {
1356
- name: "KokushiMusou",
1357
- han: KOKUSHI_HAN
1358
- },
1359
- checkKokushi
1360
- );
1873
+ const kokushiDefinition = createYaku(
1874
+ "KokushiMusou",
1875
+ KOKUSHI_HAN.closed,
1876
+ 0
1877
+ ).require(checkKokushi).build();
1361
1878
  const SANSHOKU_DOUJUN_YAKU = {
1362
1879
  name: "SanshokuDoujun",
1363
1880
  han: {
@@ -1375,37 +1892,30 @@ const checkSanshokuDoujun = (hand) => {
1375
1892
  if (shuntsuList.length < 3) {
1376
1893
  return false;
1377
1894
  }
1378
- for (let i = 0; i < shuntsuList.length; i++) {
1379
- for (let j = i + 1; j < shuntsuList.length; j++) {
1380
- for (let k = j + 1; k < shuntsuList.length; k++) {
1381
- const s1 = shuntsuList[i];
1382
- const s2 = shuntsuList[j];
1383
- const s3 = shuntsuList[k];
1384
- if (!s1 || !s2 || !s3) continue;
1385
- const firstHai1 = s1.hais[0];
1386
- const firstHai2 = s2.hais[0];
1387
- const firstHai3 = s3.hais[0];
1388
- const suit1 = Math.floor(firstHai1 / 9);
1389
- const suit2 = Math.floor(firstHai2 / 9);
1390
- const suit3 = Math.floor(firstHai3 / 9);
1391
- const suits = /* @__PURE__ */ new Set([suit1, suit2, suit3]);
1392
- if (suits.size !== 3) continue;
1393
- if (suit1 > 2 || suit2 > 2 || suit3 > 2) continue;
1394
- const num1 = firstHai1 % 9;
1395
- const num2 = firstHai2 % 9;
1396
- const num3 = firstHai3 % 9;
1397
- if (num1 === num2 && num2 === num3) {
1398
- return true;
1399
- }
1400
- }
1895
+ for (const [s1, s2, s3] of getShuntsuCombinations3(shuntsuList)) {
1896
+ const firstHai1 = s1.hais[0];
1897
+ const firstHai2 = s2.hais[0];
1898
+ const firstHai3 = s3.hais[0];
1899
+ const suit1 = Math.floor(firstHai1 / 9);
1900
+ const suit2 = Math.floor(firstHai2 / 9);
1901
+ const suit3 = Math.floor(firstHai3 / 9);
1902
+ const suits = /* @__PURE__ */ new Set([suit1, suit2, suit3]);
1903
+ if (suits.size !== 3) continue;
1904
+ if (suit1 > 2 || suit2 > 2 || suit3 > 2) continue;
1905
+ const num1 = firstHai1 % 9;
1906
+ const num2 = firstHai2 % 9;
1907
+ const num3 = firstHai3 % 9;
1908
+ if (num1 === num2 && num2 === num3) {
1909
+ return true;
1401
1910
  }
1402
1911
  }
1403
1912
  return false;
1404
1913
  };
1405
- const sanshokuDoujunDefinition = createYakuDefinition(
1406
- SANSHOKU_DOUJUN_YAKU,
1407
- checkSanshokuDoujun
1408
- );
1914
+ const sanshokuDoujunDefinition = createYaku(
1915
+ SANSHOKU_DOUJUN_YAKU.name,
1916
+ SANSHOKU_DOUJUN_YAKU.han.closed,
1917
+ SANSHOKU_DOUJUN_YAKU.han.open
1918
+ ).require(checkSanshokuDoujun).build();
1409
1919
  const SANSHOKU_DOUKOU_YAKU = {
1410
1920
  name: "SanshokuDoukou",
1411
1921
  han: {
@@ -1450,10 +1960,11 @@ const checkSanshokuDoukou = (hand) => {
1450
1960
  }
1451
1961
  return false;
1452
1962
  };
1453
- const sanshokuDoukouDefinition = createYakuDefinition(
1454
- SANSHOKU_DOUKOU_YAKU,
1455
- checkSanshokuDoukou
1456
- );
1963
+ const sanshokuDoukouDefinition = createYaku(
1964
+ SANSHOKU_DOUKOU_YAKU.name,
1965
+ SANSHOKU_DOUKOU_YAKU.han.closed,
1966
+ SANSHOKU_DOUKOU_YAKU.han.open
1967
+ ).require(checkSanshokuDoukou).build();
1457
1968
  const IKKITSUUKAN_YAKU = {
1458
1969
  name: "Ikkitsuukan",
1459
1970
  han: {
@@ -1471,37 +1982,30 @@ const checkIkkitsuukan = (hand) => {
1471
1982
  if (shuntsuList.length < 3) {
1472
1983
  return false;
1473
1984
  }
1474
- for (let i = 0; i < shuntsuList.length; i++) {
1475
- for (let j = i + 1; j < shuntsuList.length; j++) {
1476
- for (let k = j + 1; k < shuntsuList.length; k++) {
1477
- const s1 = shuntsuList[i];
1478
- const s2 = shuntsuList[j];
1479
- const s3 = shuntsuList[k];
1480
- if (!s1 || !s2 || !s3) continue;
1481
- const firstHai1 = s1.hais[0];
1482
- const firstHai2 = s2.hais[0];
1483
- const firstHai3 = s3.hais[0];
1484
- const suit1 = Math.floor(firstHai1 / 9);
1485
- const suit2 = Math.floor(firstHai2 / 9);
1486
- const suit3 = Math.floor(firstHai3 / 9);
1487
- if (suit1 !== suit2 || suit2 !== suit3) continue;
1488
- if (suit1 > 2) continue;
1489
- const num1 = firstHai1 % 9;
1490
- const num2 = firstHai2 % 9;
1491
- const num3 = firstHai3 % 9;
1492
- const nums = /* @__PURE__ */ new Set([num1, num2, num3]);
1493
- if (nums.has(0) && nums.has(3) && nums.has(6)) {
1494
- return true;
1495
- }
1496
- }
1985
+ for (const [s1, s2, s3] of getShuntsuCombinations3(shuntsuList)) {
1986
+ const firstHai1 = s1.hais[0];
1987
+ const firstHai2 = s2.hais[0];
1988
+ const firstHai3 = s3.hais[0];
1989
+ const suit1 = Math.floor(firstHai1 / 9);
1990
+ const suit2 = Math.floor(firstHai2 / 9);
1991
+ const suit3 = Math.floor(firstHai3 / 9);
1992
+ if (suit1 !== suit2 || suit2 !== suit3) continue;
1993
+ if (suit1 > 2) continue;
1994
+ const num1 = firstHai1 % 9;
1995
+ const num2 = firstHai2 % 9;
1996
+ const num3 = firstHai3 % 9;
1997
+ const nums = /* @__PURE__ */ new Set([num1, num2, num3]);
1998
+ if (nums.has(0) && nums.has(3) && nums.has(6)) {
1999
+ return true;
1497
2000
  }
1498
2001
  }
1499
2002
  return false;
1500
2003
  };
1501
- const ikkitsuukanDefinition = createYakuDefinition(
1502
- IKKITSUUKAN_YAKU,
1503
- checkIkkitsuukan
1504
- );
2004
+ const ikkitsuukanDefinition = createYaku(
2005
+ IKKITSUUKAN_YAKU.name,
2006
+ IKKITSUUKAN_YAKU.han.closed,
2007
+ IKKITSUUKAN_YAKU.han.open
2008
+ ).require(checkIkkitsuukan).build();
1505
2009
  const HONITSU_YAKU = {
1506
2010
  name: "Honitsu",
1507
2011
  han: {
@@ -1510,31 +2014,15 @@ const HONITSU_YAKU = {
1510
2014
  }
1511
2015
  };
1512
2016
  const checkHonitsu = (hand) => {
1513
- let blocks;
1514
- if (hand.type === "Mentsu") {
1515
- blocks = [hand.jantou, ...hand.fourMentsu];
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;
1533
- };
1534
- const honitsuDefinition = createYakuDefinition(
1535
- HONITSU_YAKU,
1536
- checkHonitsu
1537
- );
2017
+ const result = analyzeIshokuPattern(hand);
2018
+ if (result === void 0) return false;
2019
+ return result.hasJihai && result.suupaiSuit !== void 0;
2020
+ };
2021
+ const honitsuDefinition = createYaku(
2022
+ HONITSU_YAKU.name,
2023
+ HONITSU_YAKU.han.closed,
2024
+ HONITSU_YAKU.han.open
2025
+ ).require(checkHonitsu).build();
1538
2026
  const CHINITSU_YAKU = {
1539
2027
  name: "Chinitsu",
1540
2028
  han: {
@@ -1543,31 +2031,15 @@ const CHINITSU_YAKU = {
1543
2031
  }
1544
2032
  };
1545
2033
  const checkChinitsu = (hand) => {
1546
- let blocks;
1547
- if (hand.type === "Mentsu") {
1548
- blocks = [hand.jantou, ...hand.fourMentsu];
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;
1566
- };
1567
- const chinitsuDefinition = createYakuDefinition(
1568
- CHINITSU_YAKU,
1569
- checkChinitsu
1570
- );
2034
+ const result = analyzeIshokuPattern(hand);
2035
+ if (result === void 0) return false;
2036
+ return !result.hasJihai && result.suupaiSuit !== void 0;
2037
+ };
2038
+ const chinitsuDefinition = createYaku(
2039
+ CHINITSU_YAKU.name,
2040
+ CHINITSU_YAKU.han.closed,
2041
+ CHINITSU_YAKU.han.open
2042
+ ).require(checkChinitsu).build();
1571
2043
  function createYakuhaiDefinition(name, tile) {
1572
2044
  const HAN_CONFIG = { closed: 1, open: 1 };
1573
2045
  const check = (hand) => {
@@ -1581,7 +2053,11 @@ function createYakuhaiDefinition(name, tile) {
1581
2053
  }
1582
2054
  return false;
1583
2055
  };
1584
- return createYakuDefinition({ name, han: HAN_CONFIG }, check);
2056
+ return createYaku(
2057
+ name,
2058
+ HAN_CONFIG.closed,
2059
+ HAN_CONFIG.open
2060
+ ).require(check).build();
1585
2061
  }
1586
2062
  const hakuDefinition = createYakuhaiDefinition("Haku", HaiKind.Haku);
1587
2063
  const hatsuDefinition = createYakuhaiDefinition("Hatsu", HaiKind.Hatsu);
@@ -1590,12 +2066,13 @@ const definition = {
1590
2066
  name: "MenzenTsumo",
1591
2067
  han: { open: 0, closed: 1 }
1592
2068
  };
1593
- const menzenTsumoDefinition = createYakuDefinition(
1594
- definition,
1595
- (hand, context) => {
1596
- return context.isMenzen && !!context.isTsumo;
1597
- }
1598
- );
2069
+ const menzenTsumoDefinition = createYaku(
2070
+ definition.name,
2071
+ definition.han.closed,
2072
+ definition.han.open
2073
+ ).require((hand, context) => {
2074
+ return context.isMenzen && !!context.isTsumo;
2075
+ }).build();
1599
2076
  const ALL_YAKU_DEFINITIONS = [
1600
2077
  tanyaoDefinition,
1601
2078
  pinfuDefinition,
@@ -1648,15 +2125,15 @@ function detectYakuForStructure(hand, context) {
1648
2125
  function getTotalHan(yakuResult) {
1649
2126
  return yakuResult.reduce((sum, [, han]) => sum + han, 0);
1650
2127
  }
1651
- function detectYaku(tehai, agariHai, bakaze, jikaze, doraMarkers, uraDoraMarkers, isTsumo) {
2128
+ function detectYaku(tehai, config) {
1652
2129
  const context = {
1653
2130
  isMenzen: isMenzen(tehai),
1654
- agariHai,
1655
- bakaze: bakaze !== void 0 && isKazehai(bakaze) ? bakaze : void 0,
1656
- jikaze: jikaze !== void 0 && isKazehai(jikaze) ? jikaze : void 0,
1657
- doraMarkers: doraMarkers ?? [],
1658
- uraDoraMarkers: uraDoraMarkers ?? [],
1659
- isTsumo
2131
+ agariHai: config.agariHai,
2132
+ bakaze: config.bakaze,
2133
+ jikaze: config.jikaze,
2134
+ doraMarkers: config.doraMarkers ?? [],
2135
+ uraDoraMarkers: config.uraDoraMarkers ?? [],
2136
+ isTsumo: config.isTsumo
1660
2137
  };
1661
2138
  const structuralInterpretations = getHouraStructures(tehai);
1662
2139
  let bestResult = [];
@@ -1680,9 +2157,9 @@ function isExtendedMspz(input) {
1680
2157
  }
1681
2158
  function asExtendedMspz(input) {
1682
2159
  if (!isExtendedMspz(input)) {
1683
- throw new Error(`Invalid Extended MSPZ string: ${input}`);
2160
+ return err(new MspzParseError(`Invalid Extended MSPZ string: ${input}`));
1684
2161
  }
1685
- return input;
2162
+ return ok(input);
1686
2163
  }
1687
2164
  function isMspz(input) {
1688
2165
  return STANDARD_MSPZ_REGEX.test(input);
@@ -1695,27 +2172,36 @@ function parseExtendedMspz$1(input) {
1695
2172
  for (const char of input) {
1696
2173
  if (char === "[") {
1697
2174
  if (mode !== "closed")
1698
- throw new Error("Nested brackets are not supported");
2175
+ return err(new MspzParseError("Nested brackets are not supported"));
1699
2176
  if (current.length > 0) closedParts.push(current);
1700
2177
  current = "[";
1701
2178
  mode = "open";
1702
2179
  } else if (char === "]") {
1703
- if (mode !== "open") throw new Error("Unexpected closing bracket ']'");
2180
+ if (mode !== "open")
2181
+ return err(new MspzParseError("Unexpected closing bracket ']'"));
1704
2182
  current += "]";
1705
- exposed.push(parseMentsuFromExtendedMspz(asExtendedMspz(current)));
2183
+ const extMspzRes = asExtendedMspz(current);
2184
+ if (extMspzRes.isErr()) return err(extMspzRes.error);
2185
+ const mentsuRes = parseMentsuFromExtendedMspz(extMspzRes.value);
2186
+ if (mentsuRes.isErr()) return err(mentsuRes.error);
2187
+ exposed.push(mentsuRes.value);
1706
2188
  current = "";
1707
2189
  mode = "closed";
1708
2190
  } else if (char === "(") {
1709
2191
  if (mode !== "closed")
1710
- throw new Error("Nested parentheses are not supported");
2192
+ return err(new MspzParseError("Nested parentheses are not supported"));
1711
2193
  if (current.length > 0) closedParts.push(current);
1712
2194
  current = "(";
1713
2195
  mode = "ankan";
1714
2196
  } else if (char === ")") {
1715
2197
  if (mode !== "ankan")
1716
- throw new Error("Unexpected closing parenthesis ')'");
2198
+ return err(new MspzParseError("Unexpected closing parenthesis ')'"));
1717
2199
  current += ")";
1718
- exposed.push(parseMentsuFromExtendedMspz(asExtendedMspz(current)));
2200
+ const extMspzRes = asExtendedMspz(current);
2201
+ if (extMspzRes.isErr()) return err(extMspzRes.error);
2202
+ const mentsuRes = parseMentsuFromExtendedMspz(extMspzRes.value);
2203
+ if (mentsuRes.isErr()) return err(mentsuRes.error);
2204
+ exposed.push(mentsuRes.value);
1719
2205
  current = "";
1720
2206
  mode = "closed";
1721
2207
  } else {
@@ -1723,15 +2209,18 @@ function parseExtendedMspz$1(input) {
1723
2209
  }
1724
2210
  }
1725
2211
  if (current.length > 0) {
1726
- if (mode !== "closed") throw new Error("Unclosed bracket or parenthesis");
2212
+ if (mode !== "closed")
2213
+ return err(new MspzParseError("Unclosed bracket or parenthesis"));
1727
2214
  closedParts.push(current);
1728
2215
  }
1729
2216
  const fullClosedMspz = closedParts.join("");
1730
- const closedIds = parseMspzToHaiKindIds(asMspz(fullClosedMspz));
1731
- return {
2217
+ const mspzRes = asMspz(fullClosedMspz);
2218
+ if (mspzRes.isErr()) return err(mspzRes.error);
2219
+ const closedIds = parseMspzToHaiKindIds(mspzRes.value);
2220
+ return ok({
1732
2221
  closed: closedIds,
1733
2222
  exposed
1734
- };
2223
+ });
1735
2224
  }
1736
2225
  function parseMentsuFromExtendedMspz(block) {
1737
2226
  let mode;
@@ -1743,33 +2232,45 @@ function parseMentsuFromExtendedMspz(block) {
1743
2232
  mode = "ankan";
1744
2233
  content = block.slice(1, -1);
1745
2234
  } else {
1746
- throw new Error(
1747
- `Invalid Extended MSPZ block: ${block} (must be [...] or (...))`
2235
+ return err(
2236
+ new MspzParseError(
2237
+ `Invalid Extended MSPZ block: ${block} (must be [...] or (...))`
2238
+ )
1748
2239
  );
1749
2240
  }
1750
- const ids = parseMspzToHaiKindIds(asMspz(content));
2241
+ const mspzRes = asMspz(content);
2242
+ if (mspzRes.isErr()) return err(mspzRes.error);
2243
+ const ids = parseMspzToHaiKindIds(mspzRes.value);
1751
2244
  if (ids.length === 0) {
1752
- throw new Error("Empty mentsu specification");
2245
+ return err(new MspzParseError("Empty mentsu specification"));
1753
2246
  }
1754
2247
  const count = ids.length;
1755
2248
  const isAllSame = ids.every((id) => id === ids[0]);
1756
2249
  if (mode === "ankan") {
1757
2250
  if (count !== 4 || !isAllSame) {
1758
- throw new Error(`Invalid Ankan: ${block} (must be 4 identical tiles)`);
2251
+ return err(
2252
+ new MspzParseError(
2253
+ `Invalid Ankan: ${block} (must be 4 identical tiles)`
2254
+ )
2255
+ );
1759
2256
  }
1760
2257
  if (!isTuple4(ids)) {
1761
- throw new Error("Internal Error: ids length check mismatch");
2258
+ return err(
2259
+ new MspzParseError("Internal Error: ids length check mismatch")
2260
+ );
1762
2261
  }
1763
2262
  const kantsu = {
1764
2263
  type: MentsuType.Kantsu,
1765
2264
  hais: ids
1766
2265
  // Ankan has no furo info (or minimal)
1767
2266
  };
1768
- return kantsu;
2267
+ return ok(kantsu);
1769
2268
  }
1770
2269
  if (count === 4 && isAllSame) {
1771
2270
  if (!isTuple4(ids)) {
1772
- throw new Error("Internal Error: ids length check mismatch");
2271
+ return err(
2272
+ new MspzParseError("Internal Error: ids length check mismatch")
2273
+ );
1773
2274
  }
1774
2275
  const kantsu = {
1775
2276
  type: MentsuType.Kantsu,
@@ -1777,10 +2278,12 @@ function parseMentsuFromExtendedMspz(block) {
1777
2278
  furo: { type: FuroType.Daiminkan, from: Tacha.Toimen }
1778
2279
  // Default
1779
2280
  };
1780
- return kantsu;
2281
+ return ok(kantsu);
1781
2282
  } else if (count === 3 && isAllSame) {
1782
2283
  if (!isTuple3(ids)) {
1783
- throw new Error("Internal Error: ids length check mismatch");
2284
+ return err(
2285
+ new MspzParseError("Internal Error: ids length check mismatch")
2286
+ );
1784
2287
  }
1785
2288
  const koutsu = {
1786
2289
  type: MentsuType.Koutsu,
@@ -1788,10 +2291,12 @@ function parseMentsuFromExtendedMspz(block) {
1788
2291
  furo: { type: FuroType.Pon, from: Tacha.Toimen }
1789
2292
  // Default
1790
2293
  };
1791
- return koutsu;
2294
+ return ok(koutsu);
1792
2295
  } else if (count === 3) {
1793
2296
  if (!isTuple3(ids)) {
1794
- throw new Error("Internal Error: ids length check mismatch");
2297
+ return err(
2298
+ new MspzParseError("Internal Error: ids length check mismatch")
2299
+ );
1795
2300
  }
1796
2301
  const shuntsu = {
1797
2302
  type: MentsuType.Shuntsu,
@@ -1799,17 +2304,19 @@ function parseMentsuFromExtendedMspz(block) {
1799
2304
  furo: { type: FuroType.Chi, from: Tacha.Kamicha }
1800
2305
  // Default
1801
2306
  };
1802
- return shuntsu;
2307
+ return ok(shuntsu);
1803
2308
  }
1804
- throw new Error(
1805
- `Invalid Mentsu specification: ${block} (must be 3 or 4 tiles)`
2309
+ return err(
2310
+ new MspzParseError(
2311
+ `Invalid Mentsu specification: ${block} (must be 3 or 4 tiles)`
2312
+ )
1806
2313
  );
1807
2314
  }
1808
2315
  function asMspz(input) {
1809
2316
  if (!isMspz(input)) {
1810
- throw new Error(`Invalid MSPZ string: ${input}`);
2317
+ return err(new MspzParseError(`Invalid MSPZ string: ${input}`));
1811
2318
  }
1812
- return input;
2319
+ return ok(input);
1813
2320
  }
1814
2321
  function parseMspzToHaiKindIds(mspz) {
1815
2322
  const result = [];
@@ -1853,14 +2360,20 @@ function parseMspzToHaiKindIds(mspz) {
1853
2360
  return result;
1854
2361
  }
1855
2362
  function parseMspz(input) {
1856
- const ids = parseMspzToHaiKindIds(asMspz(input));
1857
- return {
2363
+ const mspzRes = asMspz(input);
2364
+ if (mspzRes.isErr()) return err(mspzRes.error);
2365
+ const ids = parseMspzToHaiKindIds(mspzRes.value);
2366
+ return ok({
1858
2367
  closed: ids,
1859
2368
  exposed: []
1860
- };
2369
+ });
1861
2370
  }
1862
2371
  function parseExtendedMspz(input) {
1863
- return parseExtendedMspz$1(asExtendedMspz(input));
2372
+ const extMspzRes = asExtendedMspz(input);
2373
+ if (extMspzRes.isErr()) return err(extMspzRes.error);
2374
+ const parsedRes = parseExtendedMspz$1(extMspzRes.value);
2375
+ if (parsedRes.isErr()) return err(parsedRes.error);
2376
+ return ok(parsedRes.value);
1864
2377
  }
1865
2378
  const FU_BASE = {
1866
2379
  NORMAL: 20,
@@ -1938,7 +2451,7 @@ const VALID_FU_VALUES = [
1938
2451
  function toFu(value) {
1939
2452
  const fu = VALID_FU_VALUES.find((f) => f === value);
1940
2453
  if (fu === void 0) {
1941
- throw new Error(`Invalid fu value: ${value}`);
2454
+ throw new MahjongError(`Invalid fu value: ${value}`);
1942
2455
  }
1943
2456
  return fu;
1944
2457
  }
@@ -2108,7 +2621,7 @@ function getLimitBasePoints(level) {
2108
2621
  case ScoreLevel.Mangan:
2109
2622
  return SCORE_BASE_MANGAN;
2110
2623
  case ScoreLevel.Normal:
2111
- return null;
2624
+ return void 0;
2112
2625
  }
2113
2626
  }
2114
2627
  function createScoreContext(tehai, config) {
@@ -2126,7 +2639,7 @@ function createScoreContext(tehai, config) {
2126
2639
  function calculateScoreForTehai(tehai, config) {
2127
2640
  const context = createScoreContext(tehai, config);
2128
2641
  const structuralInterpretations = getHouraStructures(tehai);
2129
- let bestResult = null;
2642
+ let bestResult = void 0;
2130
2643
  let maxTotalPoints = -1;
2131
2644
  for (const hand of structuralInterpretations) {
2132
2645
  const yakuResult = detectYakuForStructure(hand, context);
@@ -2203,6 +2716,7 @@ export {
2203
2716
  MahjongArgumentError,
2204
2717
  MahjongError,
2205
2718
  MentsuType,
2719
+ MspzParseError,
2206
2720
  NoYakuError,
2207
2721
  ShoushaiError,
2208
2722
  Tacha,
@@ -2234,6 +2748,8 @@ export {
2234
2748
  kindIdToHaiType,
2235
2749
  parseExtendedMspz,
2236
2750
  parseMspz,
2237
- validateTehai
2751
+ validateTehai,
2752
+ validateTehai13,
2753
+ validateTehai14
2238
2754
  };
2239
2755
  //# sourceMappingURL=index.js.map