@pai-forge/riichi-mahjong 0.3.5 → 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
@@ -203,6 +203,481 @@ const YAOCHU_KIND_IDS = [
203
203
  function isYaochu(kind) {
204
204
  return YAOCHU_KIND_IDS.some((k) => k === kind);
205
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;
206
681
  function calculateTehaiCount(tehai) {
207
682
  return tehai.closed.length + tehai.exposed.length * 3;
208
683
  }
@@ -213,41 +688,47 @@ function countHaiKind(hais) {
213
688
  }
214
689
  return counts;
215
690
  }
216
- function assertTehai13(tehai) {
691
+ function validateTehai13(tehai) {
217
692
  const count = calculateTehaiCount(tehai);
218
693
  if (count < 13) {
219
- throw new ShoushaiError();
694
+ return err(new ShoushaiError());
220
695
  }
221
696
  if (count > 13) {
222
- throw new TahaiError();
697
+ return err(new TahaiError());
223
698
  }
224
- validateHaiConsistency(tehai);
225
- }
226
- function validateTehai13(tehai) {
227
- assertTehai13(tehai);
699
+ const consRes = validateHaiConsistency(tehai);
700
+ if (consRes.isErr()) {
701
+ return err(consRes.error);
702
+ }
703
+ return ok(tehai);
228
704
  }
229
- function assertTehai14(tehai) {
705
+ function validateTehai14(tehai) {
230
706
  const count = calculateTehaiCount(tehai);
231
707
  if (count < 14) {
232
- throw new ShoushaiError();
708
+ return err(new ShoushaiError());
233
709
  }
234
710
  if (count > 14) {
235
- throw new TahaiError();
711
+ return err(new TahaiError());
236
712
  }
237
- validateHaiConsistency(tehai);
238
- }
239
- function validateTehai14(tehai) {
240
- assertTehai14(tehai);
713
+ const consRes = validateHaiConsistency(tehai);
714
+ if (consRes.isErr()) {
715
+ return err(consRes.error);
716
+ }
717
+ return ok(tehai);
241
718
  }
242
719
  function validateTehai(tehai) {
243
720
  const count = calculateTehaiCount(tehai);
244
721
  if (count < 13) {
245
- throw new ShoushaiError();
722
+ return err(new ShoushaiError());
246
723
  }
247
724
  if (count > 14) {
248
- throw new TahaiError();
725
+ return err(new TahaiError());
249
726
  }
250
- validateHaiConsistency(tehai);
727
+ const consRes = validateHaiConsistency(tehai);
728
+ if (consRes.isErr()) {
729
+ return err(consRes.error);
730
+ }
731
+ return ok(tehai);
251
732
  }
252
733
  function validateHaiConsistency(tehai) {
253
734
  const allHais = [
@@ -258,7 +739,7 @@ function validateHaiConsistency(tehai) {
258
739
  if (isHaiIdMode) {
259
740
  const uniqueIds = new Set(allHais);
260
741
  if (uniqueIds.size !== allHais.length) {
261
- throw new DuplicatedHaiIdError();
742
+ return err(new DuplicatedHaiIdError());
262
743
  }
263
744
  }
264
745
  const counts = /* @__PURE__ */ new Map();
@@ -266,26 +747,17 @@ function validateHaiConsistency(tehai) {
266
747
  const kind = isHaiIdMode ? haiIdToKindId(hai) : hai;
267
748
  const current = counts.get(kind) ?? 0;
268
749
  if (current + 1 > 4) {
269
- throw new InvalidHaiQuantityError();
750
+ return err(new InvalidHaiQuantityError());
270
751
  }
271
752
  counts.set(kind, current + 1);
272
753
  }
754
+ return ok(void 0);
273
755
  }
274
756
  function isTehai13(tehai) {
275
- try {
276
- validateTehai13(tehai);
277
- return true;
278
- } catch {
279
- return false;
280
- }
757
+ return validateTehai13(tehai).isOk();
281
758
  }
282
759
  function isTehai14(tehai) {
283
- try {
284
- validateTehai14(tehai);
285
- return true;
286
- } catch {
287
- return false;
288
- }
760
+ return validateTehai14(tehai).isOk();
289
761
  }
290
762
  function isValidShuntsu(kindIds) {
291
763
  if (!isTuple3(kindIds)) return false;
@@ -544,11 +1016,12 @@ function countTaatsu(counts) {
544
1016
  return taatsu;
545
1017
  }
546
1018
  function calculateShanten(tehai, useChiitoitsu = true, useKokushi = true) {
547
- validateTehai13(tehai);
1019
+ const valRes = validateTehai13(tehai);
1020
+ if (valRes.isErr()) return err(valRes.error);
548
1021
  const chiitoitsuShanten = useChiitoitsu ? calculateChiitoitsuShanten(tehai) : Infinity;
549
1022
  const kokushiShanten = useKokushi ? calculateKokushiShanten(tehai) : Infinity;
550
1023
  const mentsuShanten = calculateMentsuTeShanten(tehai);
551
- return Math.min(chiitoitsuShanten, kokushiShanten, mentsuShanten);
1024
+ return ok(Math.min(chiitoitsuShanten, kokushiShanten, mentsuShanten));
552
1025
  }
553
1026
  function getUkeire(tehai) {
554
1027
  const currentShanten = calculateMentsuTeShanten(tehai);
@@ -765,17 +1238,68 @@ function analyzeIshokuPattern(hand) {
765
1238
  suupaiSuit: isAllSameType ? firstSuupaiType : void 0
766
1239
  };
767
1240
  }
768
- function createYakuDefinition(yaku, check) {
769
- return {
770
- yaku,
771
- isSatisfied: check,
772
- getHansu: (hand, context) => {
773
- if (!check(hand, context)) {
774
- return 0;
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;
775
1291
  }
776
- 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
777
1301
  }
778
- };
1302
+ });
779
1303
  }
780
1304
  const TANYAO_YAKU = {
781
1305
  name: "Tanyao",
@@ -792,10 +1316,11 @@ const checkTanyao = (hand) => {
792
1316
  }
793
1317
  return true;
794
1318
  };
795
- const tanyaoDefinition = createYakuDefinition(
796
- TANYAO_YAKU,
797
- checkTanyao
798
- );
1319
+ const tanyaoDefinition = createYaku(
1320
+ TANYAO_YAKU.name,
1321
+ TANYAO_YAKU.han.closed,
1322
+ TANYAO_YAKU.han.open
1323
+ ).require(checkTanyao).build();
799
1324
  const PINFU_YAKU = {
800
1325
  name: "Pinfu",
801
1326
  han: {
@@ -825,10 +1350,11 @@ const checkPinfu = (hand, context) => {
825
1350
  const waitType = classifyMachi(hand, context.agariHai);
826
1351
  return waitType === "Ryanmen";
827
1352
  };
828
- const pinfuDefinition = createYakuDefinition(
829
- PINFU_YAKU,
830
- checkPinfu
831
- );
1353
+ const pinfuDefinition = createYaku(
1354
+ PINFU_YAKU.name,
1355
+ PINFU_YAKU.han.closed,
1356
+ PINFU_YAKU.han.open
1357
+ ).require(checkPinfu).build();
832
1358
  const IIPEIKO_YAKU = {
833
1359
  name: "Iipeikou",
834
1360
  han: {
@@ -841,10 +1367,11 @@ const checkIipeikou = (hand) => {
841
1367
  const pairCount = countShuntsuPairs(hand);
842
1368
  return pairCount === 1;
843
1369
  };
844
- const iipeikouDefinition = createYakuDefinition(
845
- IIPEIKO_YAKU,
846
- checkIipeikou
847
- );
1370
+ const iipeikouDefinition = createYaku(
1371
+ IIPEIKO_YAKU.name,
1372
+ IIPEIKO_YAKU.han.closed,
1373
+ IIPEIKO_YAKU.han.open
1374
+ ).require(checkIipeikou).build();
848
1375
  const RYANPEIKOU_YAKU = {
849
1376
  name: "Ryanpeikou",
850
1377
  han: {
@@ -866,20 +1393,14 @@ const checkRyanpeikou = (hand) => {
866
1393
  const pairCount = countShuntsuPairs(hand);
867
1394
  return pairCount >= 2;
868
1395
  };
869
- const ryanpeikouDefinition = createYakuDefinition(
870
- RYANPEIKOU_YAKU,
871
- checkRyanpeikou
872
- );
873
- const SANANKOU_YAKU = {
874
- name: "Sanankou",
875
- han: {
876
- open: 2,
877
- closed: 2
878
- }
879
- };
880
- 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) => {
881
1402
  if (hand.type !== "Mentsu") {
882
- return false;
1403
+ return 0;
883
1404
  }
884
1405
  const triplets = hand.fourMentsu.filter(
885
1406
  (m) => m.type === "Koutsu" || m.type === "Kantsu"
@@ -901,12 +1422,69 @@ const checkSanankou = (hand, context) => {
901
1422
  }
902
1423
  }
903
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);
904
1481
  return ankouCount >= 3;
905
1482
  };
906
- const sanankouDefinition = createYakuDefinition(
907
- SANANKOU_YAKU,
908
- checkSanankou
909
- );
1483
+ const sanankouDefinition = createYaku(
1484
+ SANANKOU_YAKU.name,
1485
+ SANANKOU_YAKU.han.closed,
1486
+ SANANKOU_YAKU.han.open
1487
+ ).require(checkSanankou).build();
910
1488
  const SUUANKOU_YAKU = {
911
1489
  name: "Suuankou",
912
1490
  han: {
@@ -917,29 +1495,7 @@ const SUUANKOU_YAKU = {
917
1495
  }
918
1496
  };
919
1497
  const checkSuuankou = (hand, context) => {
920
- if (hand.type !== "Mentsu") {
921
- return false;
922
- }
923
- const triplets = hand.fourMentsu.filter(
924
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
925
- );
926
- let ankouCount = 0;
927
- for (const triplet of triplets) {
928
- if (triplet.furo) continue;
929
- const isAgariHaiInTriplet = triplet.hais.includes(context.agariHai);
930
- const isTanki = hand.jantou.hais[0] === context.agariHai;
931
- if (context.isTsumo) {
932
- ankouCount++;
933
- } else {
934
- if (isAgariHaiInTriplet) {
935
- if (isTanki) {
936
- ankouCount++;
937
- }
938
- } else {
939
- ankouCount++;
940
- }
941
- }
942
- }
1498
+ const ankouCount = countAnkou(hand, context);
943
1499
  return ankouCount === 4;
944
1500
  };
945
1501
  const suuankouDefinition = {
@@ -968,10 +1524,11 @@ const checkSankantsu = (hand) => {
968
1524
  const kantsuList = hand.fourMentsu.filter((m) => m.type === "Kantsu");
969
1525
  return kantsuList.length >= 3;
970
1526
  };
971
- const sankantsuDefinition = createYakuDefinition(
972
- SANKANTSU_YAKU,
973
- checkSankantsu
974
- );
1527
+ const sankantsuDefinition = createYaku(
1528
+ SANKANTSU_YAKU.name,
1529
+ SANKANTSU_YAKU.han.closed,
1530
+ SANKANTSU_YAKU.han.open
1531
+ ).require(checkSankantsu).build();
975
1532
  const SUUKANTSU_YAKU = {
976
1533
  name: "Suukantsu",
977
1534
  han: {
@@ -986,10 +1543,11 @@ const checkSuukantsu = (hand) => {
986
1543
  const kantsuList = hand.fourMentsu.filter((m) => m.type === "Kantsu");
987
1544
  return kantsuList.length === 4;
988
1545
  };
989
- const suukantsuDefinition = createYakuDefinition(
990
- SUUKANTSU_YAKU,
991
- checkSuukantsu
992
- );
1546
+ const suukantsuDefinition = createYaku(
1547
+ SUUKANTSU_YAKU.name,
1548
+ SUUKANTSU_YAKU.han.closed,
1549
+ SUUKANTSU_YAKU.han.open
1550
+ ).require(checkSuukantsu).build();
993
1551
  const TOITOI_YAKU = {
994
1552
  name: "Toitoi",
995
1553
  han: {
@@ -1005,10 +1563,11 @@ const checkToitoi = (hand) => {
1005
1563
  (mentsu) => mentsu.type === "Koutsu" || mentsu.type === "Kantsu"
1006
1564
  );
1007
1565
  };
1008
- const toitoiDefinition = createYakuDefinition(
1009
- TOITOI_YAKU,
1010
- checkToitoi
1011
- );
1566
+ const toitoiDefinition = createYaku(
1567
+ TOITOI_YAKU.name,
1568
+ TOITOI_YAKU.han.closed,
1569
+ TOITOI_YAKU.han.open
1570
+ ).require(checkToitoi).build();
1012
1571
  const CHIITOITSU_YAKU = {
1013
1572
  name: "Chiitoitsu",
1014
1573
  han: {
@@ -1020,10 +1579,11 @@ const CHIITOITSU_YAKU = {
1020
1579
  const checkChiitoitsu = (hand) => {
1021
1580
  return hand.type === "Chiitoitsu";
1022
1581
  };
1023
- const chiitoitsuDefinition = createYakuDefinition(
1024
- CHIITOITSU_YAKU,
1025
- checkChiitoitsu
1026
- );
1582
+ const chiitoitsuDefinition = createYaku(
1583
+ CHIITOITSU_YAKU.name,
1584
+ CHIITOITSU_YAKU.han.closed,
1585
+ CHIITOITSU_YAKU.han.open
1586
+ ).require(checkChiitoitsu).build();
1027
1587
  const HONCHAN_YAKU = {
1028
1588
  name: "Honchan",
1029
1589
  han: {
@@ -1046,10 +1606,11 @@ const checkHonchan = (hand) => {
1046
1606
  if (!hasJihai) return false;
1047
1607
  return true;
1048
1608
  };
1049
- const honchanDefinition = createYakuDefinition(
1050
- HONCHAN_YAKU,
1051
- checkHonchan
1052
- );
1609
+ const honchanDefinition = createYaku(
1610
+ HONCHAN_YAKU.name,
1611
+ HONCHAN_YAKU.han.closed,
1612
+ HONCHAN_YAKU.han.open
1613
+ ).require(checkHonchan).build();
1053
1614
  const JUNCHAN_YAKU = {
1054
1615
  name: "Junchan",
1055
1616
  han: {
@@ -1068,10 +1629,11 @@ const checkJunchan = (hand) => {
1068
1629
  if (!hasShuntsu) return false;
1069
1630
  return true;
1070
1631
  };
1071
- const junchanDefinition = createYakuDefinition(
1072
- JUNCHAN_YAKU,
1073
- checkJunchan
1074
- );
1632
+ const junchanDefinition = createYaku(
1633
+ JUNCHAN_YAKU.name,
1634
+ JUNCHAN_YAKU.han.closed,
1635
+ JUNCHAN_YAKU.han.open
1636
+ ).require(checkJunchan).build();
1075
1637
  const HONROUTOU_YAKU = {
1076
1638
  name: "Honroutou",
1077
1639
  han: {
@@ -1098,10 +1660,11 @@ const checkHonroutou = (hand) => {
1098
1660
  if (!hasJihai) return false;
1099
1661
  return true;
1100
1662
  };
1101
- const honroutouDefinition = createYakuDefinition(
1102
- HONROUTOU_YAKU,
1103
- checkHonroutou
1104
- );
1663
+ const honroutouDefinition = createYaku(
1664
+ HONROUTOU_YAKU.name,
1665
+ HONROUTOU_YAKU.han.closed,
1666
+ HONROUTOU_YAKU.han.open
1667
+ ).require(checkHonroutou).build();
1105
1668
  const CHINROUTOU_YAKU = {
1106
1669
  name: "Chinroutou",
1107
1670
  han: {
@@ -1118,10 +1681,11 @@ const checkChinroutou = (hand) => {
1118
1681
  if (!allRoutou) return false;
1119
1682
  return true;
1120
1683
  };
1121
- const chinroutouDefinition = createYakuDefinition(
1122
- CHINROUTOU_YAKU,
1123
- checkChinroutou
1124
- );
1684
+ const chinroutouDefinition = createYaku(
1685
+ CHINROUTOU_YAKU.name,
1686
+ CHINROUTOU_YAKU.han.closed,
1687
+ CHINROUTOU_YAKU.han.open
1688
+ ).require(checkChinroutou).build();
1125
1689
  const SHOUSANGEN_YAKU = {
1126
1690
  name: "Shousangen",
1127
1691
  han: {
@@ -1134,22 +1698,15 @@ const checkShousangen = (hand) => {
1134
1698
  return false;
1135
1699
  }
1136
1700
  const sangenpai = [HaiKind.Haku, HaiKind.Hatsu, HaiKind.Chun];
1137
- let sangenKoutsuCount = 0;
1138
- const triplets = hand.fourMentsu.filter(
1139
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1140
- );
1141
- for (const triplet of triplets) {
1142
- if (sangenpai.includes(triplet.hais[0])) {
1143
- sangenKoutsuCount++;
1144
- }
1145
- }
1701
+ const sangenKoutsuCount = countSpecificKoutsu(hand, sangenpai);
1146
1702
  const isSangenJantou = sangenpai.includes(hand.jantou.hais[0]);
1147
1703
  return sangenKoutsuCount === 2 && isSangenJantou;
1148
1704
  };
1149
- const shousangenDefinition = createYakuDefinition(
1150
- SHOUSANGEN_YAKU,
1151
- checkShousangen
1152
- );
1705
+ const shousangenDefinition = createYaku(
1706
+ SHOUSANGEN_YAKU.name,
1707
+ SHOUSANGEN_YAKU.han.closed,
1708
+ SHOUSANGEN_YAKU.han.open
1709
+ ).require(checkShousangen).build();
1153
1710
  const DAISANGEN_YAKU = {
1154
1711
  name: "Daisangen",
1155
1712
  han: {
@@ -1158,25 +1715,15 @@ const DAISANGEN_YAKU = {
1158
1715
  }
1159
1716
  };
1160
1717
  const checkDaisangen = (hand) => {
1161
- if (hand.type !== "Mentsu") {
1162
- return false;
1163
- }
1164
1718
  const sangenpai = [HaiKind.Haku, HaiKind.Hatsu, HaiKind.Chun];
1165
- let sangenKoutsuCount = 0;
1166
- const triplets = hand.fourMentsu.filter(
1167
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1168
- );
1169
- for (const triplet of triplets) {
1170
- if (sangenpai.includes(triplet.hais[0])) {
1171
- sangenKoutsuCount++;
1172
- }
1173
- }
1719
+ const sangenKoutsuCount = countSpecificKoutsu(hand, sangenpai);
1174
1720
  return sangenKoutsuCount === 3;
1175
1721
  };
1176
- const daisangenDefinition = createYakuDefinition(
1177
- DAISANGEN_YAKU,
1178
- checkDaisangen
1179
- );
1722
+ const daisangenDefinition = createYaku(
1723
+ DAISANGEN_YAKU.name,
1724
+ DAISANGEN_YAKU.han.closed,
1725
+ DAISANGEN_YAKU.han.open
1726
+ ).require(checkDaisangen).build();
1180
1727
  const TSUUIISOU_YAKU = {
1181
1728
  name: "Tsuuiisou",
1182
1729
  han: {
@@ -1188,25 +1735,13 @@ const isJihai = (id) => {
1188
1735
  return id >= HaiKind.Ton && id <= HaiKind.Chun;
1189
1736
  };
1190
1737
  const checkTsuuiisou = (hand) => {
1191
- const allHais = [];
1192
- if (hand.type === "Mentsu") {
1193
- for (const mentsu of hand.fourMentsu) {
1194
- allHais.push(...mentsu.hais);
1195
- }
1196
- allHais.push(...hand.jantou.hais);
1197
- } else if (hand.type === "Chiitoitsu") {
1198
- for (const pair of hand.pairs) {
1199
- allHais.push(...pair.hais);
1200
- }
1201
- } else {
1202
- return false;
1203
- }
1204
- return allHais.every(isJihai);
1738
+ return isAllHaisMatch(hand, isJihai);
1205
1739
  };
1206
- const tsuuiisouDefinition = createYakuDefinition(
1207
- TSUUIISOU_YAKU,
1208
- checkTsuuiisou
1209
- );
1740
+ const tsuuiisouDefinition = createYaku(
1741
+ TSUUIISOU_YAKU.name,
1742
+ TSUUIISOU_YAKU.han.closed,
1743
+ TSUUIISOU_YAKU.han.open
1744
+ ).require(checkTsuuiisou).build();
1210
1745
  const RYUUIISOU_YAKU = {
1211
1746
  name: "Ryuuiisou",
1212
1747
  han: {
@@ -1226,25 +1761,13 @@ const isGreen = (id) => {
1226
1761
  return GREEN_TILES.has(id);
1227
1762
  };
1228
1763
  const checkRyuuiisou = (hand) => {
1229
- const allHais = [];
1230
- if (hand.type === "Mentsu") {
1231
- for (const mentsu of hand.fourMentsu) {
1232
- allHais.push(...mentsu.hais);
1233
- }
1234
- allHais.push(...hand.jantou.hais);
1235
- } else if (hand.type === "Chiitoitsu") {
1236
- for (const pair of hand.pairs) {
1237
- allHais.push(...pair.hais);
1238
- }
1239
- } else {
1240
- return false;
1241
- }
1242
- return allHais.every(isGreen);
1764
+ return isAllHaisMatch(hand, isGreen);
1243
1765
  };
1244
- const ryuuiisouDefinition = createYakuDefinition(
1245
- RYUUIISOU_YAKU,
1246
- checkRyuuiisou
1247
- );
1766
+ const ryuuiisouDefinition = createYaku(
1767
+ RYUUIISOU_YAKU.name,
1768
+ RYUUIISOU_YAKU.han.closed,
1769
+ RYUUIISOU_YAKU.han.open
1770
+ ).require(checkRyuuiisou).build();
1248
1771
  const SHOUSUUSHII_YAKU = {
1249
1772
  name: "Shousuushii",
1250
1773
  han: {
@@ -1262,22 +1785,15 @@ const checkShousuushii = (hand) => {
1262
1785
  HaiKind.Sha,
1263
1786
  HaiKind.Pei
1264
1787
  ];
1265
- let windKoutsuCount = 0;
1266
- const triplets = hand.fourMentsu.filter(
1267
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1268
- );
1269
- for (const triplet of triplets) {
1270
- if (windTiles.includes(triplet.hais[0])) {
1271
- windKoutsuCount++;
1272
- }
1273
- }
1788
+ const windKoutsuCount = countSpecificKoutsu(hand, windTiles);
1274
1789
  const isWindJantou = windTiles.includes(hand.jantou.hais[0]);
1275
1790
  return windKoutsuCount === 3 && isWindJantou;
1276
1791
  };
1277
- const shousuushiiDefinition = createYakuDefinition(
1278
- SHOUSUUSHII_YAKU,
1279
- checkShousuushii
1280
- );
1792
+ const shousuushiiDefinition = createYaku(
1793
+ SHOUSUUSHII_YAKU.name,
1794
+ SHOUSUUSHII_YAKU.han.closed,
1795
+ SHOUSUUSHII_YAKU.han.open
1796
+ ).require(checkShousuushii).build();
1281
1797
  const DAISUUSHII_YAKU = {
1282
1798
  name: "Daisuushii",
1283
1799
  han: {
@@ -1287,30 +1803,20 @@ const DAISUUSHII_YAKU = {
1287
1803
  }
1288
1804
  };
1289
1805
  const checkDaisuushii = (hand) => {
1290
- if (hand.type !== "Mentsu") {
1291
- return false;
1292
- }
1293
1806
  const windTiles = [
1294
1807
  HaiKind.Ton,
1295
1808
  HaiKind.Nan,
1296
1809
  HaiKind.Sha,
1297
1810
  HaiKind.Pei
1298
1811
  ];
1299
- let windKoutsuCount = 0;
1300
- const triplets = hand.fourMentsu.filter(
1301
- (m) => m.type === "Koutsu" || m.type === "Kantsu"
1302
- );
1303
- for (const triplet of triplets) {
1304
- if (windTiles.includes(triplet.hais[0])) {
1305
- windKoutsuCount++;
1306
- }
1307
- }
1812
+ const windKoutsuCount = countSpecificKoutsu(hand, windTiles);
1308
1813
  return windKoutsuCount === 4;
1309
1814
  };
1310
- const daisuushiiDefinition = createYakuDefinition(
1311
- DAISUUSHII_YAKU,
1312
- checkDaisuushii
1313
- );
1815
+ const daisuushiiDefinition = createYaku(
1816
+ DAISUUSHII_YAKU.name,
1817
+ DAISUUSHII_YAKU.han.closed,
1818
+ DAISUUSHII_YAKU.han.open
1819
+ ).require(checkDaisuushii).build();
1314
1820
  const CHUUREN_POUTOU_YAKU = {
1315
1821
  name: "ChuurenPoutou",
1316
1822
  han: {
@@ -1353,24 +1859,22 @@ const checkChuurenPoutou = (hand, context) => {
1353
1859
  }
1354
1860
  return true;
1355
1861
  };
1356
- const chuurenPoutouDefinition = createYakuDefinition(
1357
- CHUUREN_POUTOU_YAKU,
1358
- checkChuurenPoutou
1359
- );
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();
1360
1867
  const checkKokushi = (hand) => {
1361
1868
  return hand.type === "Kokushi";
1362
1869
  };
1363
1870
  const KOKUSHI_HAN = {
1364
- closed: 13,
1365
- open: 0
1871
+ closed: 13
1366
1872
  };
1367
- const kokushiDefinition = createYakuDefinition(
1368
- {
1369
- name: "KokushiMusou",
1370
- han: KOKUSHI_HAN
1371
- },
1372
- checkKokushi
1373
- );
1873
+ const kokushiDefinition = createYaku(
1874
+ "KokushiMusou",
1875
+ KOKUSHI_HAN.closed,
1876
+ 0
1877
+ ).require(checkKokushi).build();
1374
1878
  const SANSHOKU_DOUJUN_YAKU = {
1375
1879
  name: "SanshokuDoujun",
1376
1880
  han: {
@@ -1388,37 +1892,30 @@ const checkSanshokuDoujun = (hand) => {
1388
1892
  if (shuntsuList.length < 3) {
1389
1893
  return false;
1390
1894
  }
1391
- for (let i = 0; i < shuntsuList.length; i++) {
1392
- for (let j = i + 1; j < shuntsuList.length; j++) {
1393
- for (let k = j + 1; k < shuntsuList.length; k++) {
1394
- const s1 = shuntsuList[i];
1395
- const s2 = shuntsuList[j];
1396
- const s3 = shuntsuList[k];
1397
- if (!s1 || !s2 || !s3) continue;
1398
- const firstHai1 = s1.hais[0];
1399
- const firstHai2 = s2.hais[0];
1400
- const firstHai3 = s3.hais[0];
1401
- const suit1 = Math.floor(firstHai1 / 9);
1402
- const suit2 = Math.floor(firstHai2 / 9);
1403
- const suit3 = Math.floor(firstHai3 / 9);
1404
- const suits = /* @__PURE__ */ new Set([suit1, suit2, suit3]);
1405
- if (suits.size !== 3) continue;
1406
- if (suit1 > 2 || suit2 > 2 || suit3 > 2) continue;
1407
- const num1 = firstHai1 % 9;
1408
- const num2 = firstHai2 % 9;
1409
- const num3 = firstHai3 % 9;
1410
- if (num1 === num2 && num2 === num3) {
1411
- return true;
1412
- }
1413
- }
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;
1414
1910
  }
1415
1911
  }
1416
1912
  return false;
1417
1913
  };
1418
- const sanshokuDoujunDefinition = createYakuDefinition(
1419
- SANSHOKU_DOUJUN_YAKU,
1420
- checkSanshokuDoujun
1421
- );
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();
1422
1919
  const SANSHOKU_DOUKOU_YAKU = {
1423
1920
  name: "SanshokuDoukou",
1424
1921
  han: {
@@ -1463,10 +1960,11 @@ const checkSanshokuDoukou = (hand) => {
1463
1960
  }
1464
1961
  return false;
1465
1962
  };
1466
- const sanshokuDoukouDefinition = createYakuDefinition(
1467
- SANSHOKU_DOUKOU_YAKU,
1468
- checkSanshokuDoukou
1469
- );
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();
1470
1968
  const IKKITSUUKAN_YAKU = {
1471
1969
  name: "Ikkitsuukan",
1472
1970
  han: {
@@ -1484,37 +1982,30 @@ const checkIkkitsuukan = (hand) => {
1484
1982
  if (shuntsuList.length < 3) {
1485
1983
  return false;
1486
1984
  }
1487
- for (let i = 0; i < shuntsuList.length; i++) {
1488
- for (let j = i + 1; j < shuntsuList.length; j++) {
1489
- for (let k = j + 1; k < shuntsuList.length; k++) {
1490
- const s1 = shuntsuList[i];
1491
- const s2 = shuntsuList[j];
1492
- const s3 = shuntsuList[k];
1493
- if (!s1 || !s2 || !s3) continue;
1494
- const firstHai1 = s1.hais[0];
1495
- const firstHai2 = s2.hais[0];
1496
- const firstHai3 = s3.hais[0];
1497
- const suit1 = Math.floor(firstHai1 / 9);
1498
- const suit2 = Math.floor(firstHai2 / 9);
1499
- const suit3 = Math.floor(firstHai3 / 9);
1500
- if (suit1 !== suit2 || suit2 !== suit3) continue;
1501
- if (suit1 > 2) continue;
1502
- const num1 = firstHai1 % 9;
1503
- const num2 = firstHai2 % 9;
1504
- const num3 = firstHai3 % 9;
1505
- const nums = /* @__PURE__ */ new Set([num1, num2, num3]);
1506
- if (nums.has(0) && nums.has(3) && nums.has(6)) {
1507
- return true;
1508
- }
1509
- }
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;
1510
2000
  }
1511
2001
  }
1512
2002
  return false;
1513
2003
  };
1514
- const ikkitsuukanDefinition = createYakuDefinition(
1515
- IKKITSUUKAN_YAKU,
1516
- checkIkkitsuukan
1517
- );
2004
+ const ikkitsuukanDefinition = createYaku(
2005
+ IKKITSUUKAN_YAKU.name,
2006
+ IKKITSUUKAN_YAKU.han.closed,
2007
+ IKKITSUUKAN_YAKU.han.open
2008
+ ).require(checkIkkitsuukan).build();
1518
2009
  const HONITSU_YAKU = {
1519
2010
  name: "Honitsu",
1520
2011
  han: {
@@ -1527,10 +2018,11 @@ const checkHonitsu = (hand) => {
1527
2018
  if (result === void 0) return false;
1528
2019
  return result.hasJihai && result.suupaiSuit !== void 0;
1529
2020
  };
1530
- const honitsuDefinition = createYakuDefinition(
1531
- HONITSU_YAKU,
1532
- checkHonitsu
1533
- );
2021
+ const honitsuDefinition = createYaku(
2022
+ HONITSU_YAKU.name,
2023
+ HONITSU_YAKU.han.closed,
2024
+ HONITSU_YAKU.han.open
2025
+ ).require(checkHonitsu).build();
1534
2026
  const CHINITSU_YAKU = {
1535
2027
  name: "Chinitsu",
1536
2028
  han: {
@@ -1543,10 +2035,11 @@ const checkChinitsu = (hand) => {
1543
2035
  if (result === void 0) return false;
1544
2036
  return !result.hasJihai && result.suupaiSuit !== void 0;
1545
2037
  };
1546
- const chinitsuDefinition = createYakuDefinition(
1547
- CHINITSU_YAKU,
1548
- checkChinitsu
1549
- );
2038
+ const chinitsuDefinition = createYaku(
2039
+ CHINITSU_YAKU.name,
2040
+ CHINITSU_YAKU.han.closed,
2041
+ CHINITSU_YAKU.han.open
2042
+ ).require(checkChinitsu).build();
1550
2043
  function createYakuhaiDefinition(name, tile) {
1551
2044
  const HAN_CONFIG = { closed: 1, open: 1 };
1552
2045
  const check = (hand) => {
@@ -1560,7 +2053,11 @@ function createYakuhaiDefinition(name, tile) {
1560
2053
  }
1561
2054
  return false;
1562
2055
  };
1563
- return createYakuDefinition({ name, han: HAN_CONFIG }, check);
2056
+ return createYaku(
2057
+ name,
2058
+ HAN_CONFIG.closed,
2059
+ HAN_CONFIG.open
2060
+ ).require(check).build();
1564
2061
  }
1565
2062
  const hakuDefinition = createYakuhaiDefinition("Haku", HaiKind.Haku);
1566
2063
  const hatsuDefinition = createYakuhaiDefinition("Hatsu", HaiKind.Hatsu);
@@ -1569,12 +2066,13 @@ const definition = {
1569
2066
  name: "MenzenTsumo",
1570
2067
  han: { open: 0, closed: 1 }
1571
2068
  };
1572
- const menzenTsumoDefinition = createYakuDefinition(
1573
- definition,
1574
- (hand, context) => {
1575
- return context.isMenzen && !!context.isTsumo;
1576
- }
1577
- );
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();
1578
2076
  const ALL_YAKU_DEFINITIONS = [
1579
2077
  tanyaoDefinition,
1580
2078
  pinfuDefinition,
@@ -1659,9 +2157,9 @@ function isExtendedMspz(input) {
1659
2157
  }
1660
2158
  function asExtendedMspz(input) {
1661
2159
  if (!isExtendedMspz(input)) {
1662
- throw new MspzParseError(`Invalid Extended MSPZ string: ${input}`);
2160
+ return err(new MspzParseError(`Invalid Extended MSPZ string: ${input}`));
1663
2161
  }
1664
- return input;
2162
+ return ok(input);
1665
2163
  }
1666
2164
  function isMspz(input) {
1667
2165
  return STANDARD_MSPZ_REGEX.test(input);
@@ -1674,28 +2172,36 @@ function parseExtendedMspz$1(input) {
1674
2172
  for (const char of input) {
1675
2173
  if (char === "[") {
1676
2174
  if (mode !== "closed")
1677
- throw new MspzParseError("Nested brackets are not supported");
2175
+ return err(new MspzParseError("Nested brackets are not supported"));
1678
2176
  if (current.length > 0) closedParts.push(current);
1679
2177
  current = "[";
1680
2178
  mode = "open";
1681
2179
  } else if (char === "]") {
1682
2180
  if (mode !== "open")
1683
- throw new MspzParseError("Unexpected closing bracket ']'");
2181
+ return err(new MspzParseError("Unexpected closing bracket ']'"));
1684
2182
  current += "]";
1685
- 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);
1686
2188
  current = "";
1687
2189
  mode = "closed";
1688
2190
  } else if (char === "(") {
1689
2191
  if (mode !== "closed")
1690
- throw new MspzParseError("Nested parentheses are not supported");
2192
+ return err(new MspzParseError("Nested parentheses are not supported"));
1691
2193
  if (current.length > 0) closedParts.push(current);
1692
2194
  current = "(";
1693
2195
  mode = "ankan";
1694
2196
  } else if (char === ")") {
1695
2197
  if (mode !== "ankan")
1696
- throw new MspzParseError("Unexpected closing parenthesis ')'");
2198
+ return err(new MspzParseError("Unexpected closing parenthesis ')'"));
1697
2199
  current += ")";
1698
- 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);
1699
2205
  current = "";
1700
2206
  mode = "closed";
1701
2207
  } else {
@@ -1704,15 +2210,17 @@ function parseExtendedMspz$1(input) {
1704
2210
  }
1705
2211
  if (current.length > 0) {
1706
2212
  if (mode !== "closed")
1707
- throw new MspzParseError("Unclosed bracket or parenthesis");
2213
+ return err(new MspzParseError("Unclosed bracket or parenthesis"));
1708
2214
  closedParts.push(current);
1709
2215
  }
1710
2216
  const fullClosedMspz = closedParts.join("");
1711
- const closedIds = parseMspzToHaiKindIds(asMspz(fullClosedMspz));
1712
- return {
2217
+ const mspzRes = asMspz(fullClosedMspz);
2218
+ if (mspzRes.isErr()) return err(mspzRes.error);
2219
+ const closedIds = parseMspzToHaiKindIds(mspzRes.value);
2220
+ return ok({
1713
2221
  closed: closedIds,
1714
2222
  exposed
1715
- };
2223
+ });
1716
2224
  }
1717
2225
  function parseMentsuFromExtendedMspz(block) {
1718
2226
  let mode;
@@ -1724,35 +2232,45 @@ function parseMentsuFromExtendedMspz(block) {
1724
2232
  mode = "ankan";
1725
2233
  content = block.slice(1, -1);
1726
2234
  } else {
1727
- throw new MspzParseError(
1728
- `Invalid Extended MSPZ block: ${block} (must be [...] or (...))`
2235
+ return err(
2236
+ new MspzParseError(
2237
+ `Invalid Extended MSPZ block: ${block} (must be [...] or (...))`
2238
+ )
1729
2239
  );
1730
2240
  }
1731
- const ids = parseMspzToHaiKindIds(asMspz(content));
2241
+ const mspzRes = asMspz(content);
2242
+ if (mspzRes.isErr()) return err(mspzRes.error);
2243
+ const ids = parseMspzToHaiKindIds(mspzRes.value);
1732
2244
  if (ids.length === 0) {
1733
- throw new MspzParseError("Empty mentsu specification");
2245
+ return err(new MspzParseError("Empty mentsu specification"));
1734
2246
  }
1735
2247
  const count = ids.length;
1736
2248
  const isAllSame = ids.every((id) => id === ids[0]);
1737
2249
  if (mode === "ankan") {
1738
2250
  if (count !== 4 || !isAllSame) {
1739
- throw new MspzParseError(
1740
- `Invalid Ankan: ${block} (must be 4 identical tiles)`
2251
+ return err(
2252
+ new MspzParseError(
2253
+ `Invalid Ankan: ${block} (must be 4 identical tiles)`
2254
+ )
1741
2255
  );
1742
2256
  }
1743
2257
  if (!isTuple4(ids)) {
1744
- throw new MspzParseError("Internal Error: ids length check mismatch");
2258
+ return err(
2259
+ new MspzParseError("Internal Error: ids length check mismatch")
2260
+ );
1745
2261
  }
1746
2262
  const kantsu = {
1747
2263
  type: MentsuType.Kantsu,
1748
2264
  hais: ids
1749
2265
  // Ankan has no furo info (or minimal)
1750
2266
  };
1751
- return kantsu;
2267
+ return ok(kantsu);
1752
2268
  }
1753
2269
  if (count === 4 && isAllSame) {
1754
2270
  if (!isTuple4(ids)) {
1755
- throw new MspzParseError("Internal Error: ids length check mismatch");
2271
+ return err(
2272
+ new MspzParseError("Internal Error: ids length check mismatch")
2273
+ );
1756
2274
  }
1757
2275
  const kantsu = {
1758
2276
  type: MentsuType.Kantsu,
@@ -1760,10 +2278,12 @@ function parseMentsuFromExtendedMspz(block) {
1760
2278
  furo: { type: FuroType.Daiminkan, from: Tacha.Toimen }
1761
2279
  // Default
1762
2280
  };
1763
- return kantsu;
2281
+ return ok(kantsu);
1764
2282
  } else if (count === 3 && isAllSame) {
1765
2283
  if (!isTuple3(ids)) {
1766
- throw new MspzParseError("Internal Error: ids length check mismatch");
2284
+ return err(
2285
+ new MspzParseError("Internal Error: ids length check mismatch")
2286
+ );
1767
2287
  }
1768
2288
  const koutsu = {
1769
2289
  type: MentsuType.Koutsu,
@@ -1771,10 +2291,12 @@ function parseMentsuFromExtendedMspz(block) {
1771
2291
  furo: { type: FuroType.Pon, from: Tacha.Toimen }
1772
2292
  // Default
1773
2293
  };
1774
- return koutsu;
2294
+ return ok(koutsu);
1775
2295
  } else if (count === 3) {
1776
2296
  if (!isTuple3(ids)) {
1777
- throw new MspzParseError("Internal Error: ids length check mismatch");
2297
+ return err(
2298
+ new MspzParseError("Internal Error: ids length check mismatch")
2299
+ );
1778
2300
  }
1779
2301
  const shuntsu = {
1780
2302
  type: MentsuType.Shuntsu,
@@ -1782,17 +2304,19 @@ function parseMentsuFromExtendedMspz(block) {
1782
2304
  furo: { type: FuroType.Chi, from: Tacha.Kamicha }
1783
2305
  // Default
1784
2306
  };
1785
- return shuntsu;
2307
+ return ok(shuntsu);
1786
2308
  }
1787
- throw new MspzParseError(
1788
- `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
+ )
1789
2313
  );
1790
2314
  }
1791
2315
  function asMspz(input) {
1792
2316
  if (!isMspz(input)) {
1793
- throw new MspzParseError(`Invalid MSPZ string: ${input}`);
2317
+ return err(new MspzParseError(`Invalid MSPZ string: ${input}`));
1794
2318
  }
1795
- return input;
2319
+ return ok(input);
1796
2320
  }
1797
2321
  function parseMspzToHaiKindIds(mspz) {
1798
2322
  const result = [];
@@ -1836,14 +2360,20 @@ function parseMspzToHaiKindIds(mspz) {
1836
2360
  return result;
1837
2361
  }
1838
2362
  function parseMspz(input) {
1839
- const ids = parseMspzToHaiKindIds(asMspz(input));
1840
- return {
2363
+ const mspzRes = asMspz(input);
2364
+ if (mspzRes.isErr()) return err(mspzRes.error);
2365
+ const ids = parseMspzToHaiKindIds(mspzRes.value);
2366
+ return ok({
1841
2367
  closed: ids,
1842
2368
  exposed: []
1843
- };
2369
+ });
1844
2370
  }
1845
2371
  function parseExtendedMspz(input) {
1846
- 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);
1847
2377
  }
1848
2378
  const FU_BASE = {
1849
2379
  NORMAL: 20,
@@ -2192,8 +2722,6 @@ export {
2192
2722
  Tacha,
2193
2723
  TahaiError,
2194
2724
  YAOCHU_KIND_IDS,
2195
- assertTehai13,
2196
- assertTehai14,
2197
2725
  calculateScoreForTehai,
2198
2726
  calculateShanten,
2199
2727
  classifyMachi,
@@ -2220,6 +2748,8 @@ export {
2220
2748
  kindIdToHaiType,
2221
2749
  parseExtendedMspz,
2222
2750
  parseMspz,
2223
- validateTehai
2751
+ validateTehai,
2752
+ validateTehai13,
2753
+ validateTehai14
2224
2754
  };
2225
2755
  //# sourceMappingURL=index.js.map