@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.d.ts +107 -30
- package/dist/index.js +961 -445
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -70,7 +70,7 @@ class MahjongError extends Error {
|
|
|
70
70
|
constructor(message) {
|
|
71
71
|
super(message);
|
|
72
72
|
this.name = "MahjongError";
|
|
73
|
-
Object.setPrototypeOf(this,
|
|
73
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
class ShoushaiError extends MahjongError {
|
|
@@ -80,7 +80,6 @@ class ShoushaiError extends MahjongError {
|
|
|
80
80
|
constructor(message = "手牌が規定枚数(13枚)より少ないです。") {
|
|
81
81
|
super(message);
|
|
82
82
|
this.name = "ShoushaiError";
|
|
83
|
-
Object.setPrototypeOf(this, ShoushaiError.prototype);
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
class TahaiError extends MahjongError {
|
|
@@ -90,7 +89,6 @@ class TahaiError extends MahjongError {
|
|
|
90
89
|
constructor(message = "手牌が規定枚数(13枚)より多いです。") {
|
|
91
90
|
super(message);
|
|
92
91
|
this.name = "TahaiError";
|
|
93
|
-
Object.setPrototypeOf(this, TahaiError.prototype);
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
class MahjongArgumentError extends MahjongError {
|
|
@@ -100,7 +98,6 @@ class MahjongArgumentError extends MahjongError {
|
|
|
100
98
|
constructor(message) {
|
|
101
99
|
super(message);
|
|
102
100
|
this.name = "MahjongArgumentError";
|
|
103
|
-
Object.setPrototypeOf(this, MahjongArgumentError.prototype);
|
|
104
101
|
}
|
|
105
102
|
}
|
|
106
103
|
class DuplicatedHaiIdError extends MahjongError {
|
|
@@ -110,7 +107,6 @@ class DuplicatedHaiIdError extends MahjongError {
|
|
|
110
107
|
constructor(message = "牌IDが重複しています。") {
|
|
111
108
|
super(message);
|
|
112
109
|
this.name = "DuplicatedHaiIdError";
|
|
113
|
-
Object.setPrototypeOf(this, DuplicatedHaiIdError.prototype);
|
|
114
110
|
}
|
|
115
111
|
}
|
|
116
112
|
class InvalidHaiQuantityError extends MahjongError {
|
|
@@ -120,7 +116,6 @@ class InvalidHaiQuantityError extends MahjongError {
|
|
|
120
116
|
constructor(message = "同種の牌が5枚以上存在します。") {
|
|
121
117
|
super(message);
|
|
122
118
|
this.name = "InvalidHaiQuantityError";
|
|
123
|
-
Object.setPrototypeOf(this, InvalidHaiQuantityError.prototype);
|
|
124
119
|
}
|
|
125
120
|
}
|
|
126
121
|
class ChomboError extends MahjongError {
|
|
@@ -130,7 +125,6 @@ class ChomboError extends MahjongError {
|
|
|
130
125
|
constructor(message = "不正な和了です。") {
|
|
131
126
|
super(message);
|
|
132
127
|
this.name = "ChomboError";
|
|
133
|
-
Object.setPrototypeOf(this, ChomboError.prototype);
|
|
134
128
|
}
|
|
135
129
|
}
|
|
136
130
|
class NoYakuError extends ChomboError {
|
|
@@ -140,7 +134,15 @@ class NoYakuError extends ChomboError {
|
|
|
140
134
|
constructor(message = "役が成立していません。") {
|
|
141
135
|
super(message);
|
|
142
136
|
this.name = "NoYakuError";
|
|
143
|
-
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
class MspzParseError extends MahjongError {
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
*/
|
|
143
|
+
constructor(message = "MSPZ文字列の解析に失敗しました。") {
|
|
144
|
+
super(message);
|
|
145
|
+
this.name = "MspzParseError";
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
function isTuple2(arr) {
|
|
@@ -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
|
-
|
|
694
|
+
return err(new ShoushaiError());
|
|
218
695
|
}
|
|
219
696
|
if (count > 13) {
|
|
220
|
-
|
|
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
|
-
|
|
708
|
+
return err(new ShoushaiError());
|
|
228
709
|
}
|
|
229
710
|
if (count > 14) {
|
|
230
|
-
|
|
711
|
+
return err(new TahaiError());
|
|
712
|
+
}
|
|
713
|
+
const consRes = validateHaiConsistency(tehai);
|
|
714
|
+
if (consRes.isErr()) {
|
|
715
|
+
return err(consRes.error);
|
|
231
716
|
}
|
|
232
|
-
|
|
717
|
+
return ok(tehai);
|
|
233
718
|
}
|
|
234
719
|
function validateTehai(tehai) {
|
|
235
720
|
const count = calculateTehaiCount(tehai);
|
|
236
721
|
if (count < 13) {
|
|
237
|
-
|
|
722
|
+
return err(new ShoushaiError());
|
|
238
723
|
}
|
|
239
724
|
if (count > 14) {
|
|
240
|
-
|
|
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
|
-
|
|
742
|
+
return err(new DuplicatedHaiIdError());
|
|
254
743
|
}
|
|
255
744
|
}
|
|
256
745
|
const counts = /* @__PURE__ */ new Map();
|
|
257
746
|
for (const hai of allHais) {
|
|
258
|
-
|
|
259
|
-
if (hai > 33) {
|
|
260
|
-
if (hai < 36) kind = Math.floor(hai / 4);
|
|
261
|
-
else if (hai < 72) kind = Math.floor((hai - 36) / 4) + 9;
|
|
262
|
-
else if (hai < 108) kind = Math.floor((hai - 72) / 4) + 18;
|
|
263
|
-
else kind = Math.floor((hai - 108) / 4) + 27;
|
|
264
|
-
}
|
|
747
|
+
const kind = isHaiIdMode ? haiIdToKindId(hai) : hai;
|
|
265
748
|
const current = counts.get(kind) ?? 0;
|
|
266
749
|
if (current + 1 > 4) {
|
|
267
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
for (const mentsu of tehai.exposed) {
|
|
366
|
-
for (const hai of mentsu.hais) {
|
|
367
|
-
for (const dora of doraHais) {
|
|
368
|
-
if (hai === dora) count++;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return count;
|
|
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
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
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 =
|
|
756
|
-
TANYAO_YAKU,
|
|
757
|
-
|
|
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 =
|
|
789
|
-
PINFU_YAKU,
|
|
790
|
-
|
|
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
|
-
|
|
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 =
|
|
823
|
-
IIPEIKO_YAKU,
|
|
824
|
-
|
|
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
|
|
1387
|
+
const shuntsuCount = hand.fourMentsu.filter(
|
|
839
1388
|
(mentsu) => mentsu.type === "Shuntsu"
|
|
840
|
-
);
|
|
841
|
-
if (
|
|
1389
|
+
).length;
|
|
1390
|
+
if (shuntsuCount < 4) {
|
|
842
1391
|
return false;
|
|
843
1392
|
}
|
|
844
|
-
const
|
|
845
|
-
for (const shuntsu of shuntsuList) {
|
|
846
|
-
const key = shuntsu.hais[0];
|
|
847
|
-
const currentCount = shuntsuCounts.get(key) ?? 0;
|
|
848
|
-
shuntsuCounts.set(key, currentCount + 1);
|
|
849
|
-
}
|
|
850
|
-
let pairCount = 0;
|
|
851
|
-
for (const count of shuntsuCounts.values()) {
|
|
852
|
-
pairCount += Math.floor(count / 2);
|
|
853
|
-
}
|
|
1393
|
+
const pairCount = countShuntsuPairs(hand);
|
|
854
1394
|
return pairCount >= 2;
|
|
855
1395
|
};
|
|
856
|
-
const ryanpeikouDefinition =
|
|
857
|
-
RYANPEIKOU_YAKU,
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
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
|
|
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 =
|
|
894
|
-
SANANKOU_YAKU,
|
|
895
|
-
|
|
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
|
-
|
|
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 =
|
|
959
|
-
SANKANTSU_YAKU,
|
|
960
|
-
|
|
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 =
|
|
977
|
-
SUUKANTSU_YAKU,
|
|
978
|
-
|
|
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 =
|
|
996
|
-
TOITOI_YAKU,
|
|
997
|
-
|
|
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 =
|
|
1011
|
-
CHIITOITSU_YAKU,
|
|
1012
|
-
|
|
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 =
|
|
1037
|
-
HONCHAN_YAKU,
|
|
1038
|
-
|
|
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 =
|
|
1059
|
-
JUNCHAN_YAKU,
|
|
1060
|
-
|
|
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 =
|
|
1089
|
-
HONROUTOU_YAKU,
|
|
1090
|
-
|
|
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 =
|
|
1109
|
-
CHINROUTOU_YAKU,
|
|
1110
|
-
|
|
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
|
-
|
|
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 =
|
|
1137
|
-
SHOUSANGEN_YAKU,
|
|
1138
|
-
|
|
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
|
-
|
|
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 =
|
|
1164
|
-
DAISANGEN_YAKU,
|
|
1165
|
-
|
|
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
|
-
|
|
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 =
|
|
1194
|
-
TSUUIISOU_YAKU,
|
|
1195
|
-
|
|
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
|
-
|
|
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 =
|
|
1232
|
-
RYUUIISOU_YAKU,
|
|
1233
|
-
|
|
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
|
-
|
|
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 =
|
|
1265
|
-
SHOUSUUSHII_YAKU,
|
|
1266
|
-
|
|
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
|
-
|
|
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 =
|
|
1298
|
-
DAISUUSHII_YAKU,
|
|
1299
|
-
|
|
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 =
|
|
1344
|
-
CHUUREN_POUTOU_YAKU,
|
|
1345
|
-
|
|
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 =
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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 (
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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 =
|
|
1406
|
-
SANSHOKU_DOUJUN_YAKU,
|
|
1407
|
-
|
|
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 =
|
|
1454
|
-
SANSHOKU_DOUKOU_YAKU,
|
|
1455
|
-
|
|
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 (
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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 =
|
|
1502
|
-
IKKITSUUKAN_YAKU,
|
|
1503
|
-
|
|
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
|
-
|
|
1514
|
-
if (
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
-
|
|
1547
|
-
if (
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
|
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 =
|
|
1594
|
-
definition,
|
|
1595
|
-
|
|
1596
|
-
|
|
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,
|
|
2128
|
+
function detectYaku(tehai, config) {
|
|
1652
2129
|
const context = {
|
|
1653
2130
|
isMenzen: isMenzen(tehai),
|
|
1654
|
-
agariHai,
|
|
1655
|
-
bakaze: bakaze
|
|
1656
|
-
jikaze: jikaze
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
2180
|
+
if (mode !== "open")
|
|
2181
|
+
return err(new MspzParseError("Unexpected closing bracket ']'"));
|
|
1704
2182
|
current += "]";
|
|
1705
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2198
|
+
return err(new MspzParseError("Unexpected closing parenthesis ')'"));
|
|
1717
2199
|
current += ")";
|
|
1718
|
-
|
|
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")
|
|
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
|
|
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
|
-
|
|
1747
|
-
|
|
2235
|
+
return err(
|
|
2236
|
+
new MspzParseError(
|
|
2237
|
+
`Invalid Extended MSPZ block: ${block} (must be [...] or (...))`
|
|
2238
|
+
)
|
|
1748
2239
|
);
|
|
1749
2240
|
}
|
|
1750
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1805
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|