fix: lint

This commit is contained in:
zyoshoka 2025-04-18 19:47:31 +09:00
parent 131605070a
commit 977218bda5
No known key found for this signature in database
6 changed files with 661 additions and 621 deletions

View File

@ -1,25 +1,25 @@
import { build } from "esbuild"; import { build } from 'esbuild';
import { globSync } from "glob"; import { globSync } from 'glob';
const entryPoints = globSync("./src/**/**.{ts,tsx}"); const entryPoints = globSync('./src/**/**.{ts,tsx}');
/** @type {import('esbuild').BuildOptions} */ /** @type {import('esbuild').BuildOptions} */
const options = { const options = {
entryPoints, entryPoints,
minify: true, minify: true,
outdir: "./built/esm", outdir: './built/esm',
target: "es2022", target: 'es2022',
platform: "browser", platform: 'browser',
format: "esm", format: 'esm',
}; };
if (process.env.WATCH === "true") { if (process.env.WATCH === 'true') {
options.watch = { options.watch = {
onRebuild(error, result) { onRebuild(error, result) {
if (error) { if (error) {
console.error("watch build failed:", error); console.error('watch build failed:', error);
} else { } else {
console.log("watch build succeeded:", result); console.log('watch build succeeded:', result);
} }
}, },
}; };

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { FourMentsuOneJyantou, mentsuEquals, TILE_NUMBER_MAP, TileType } from "./common.js"; import { FourMentsuOneJyantou, mentsuEquals, TILE_NUMBER_MAP, TileType } from './common.js';
export type Shape = 'fourMentsuOneJyantou' | 'chitoitsu' | 'kokushi'; export type Shape = 'fourMentsuOneJyantou' | 'chitoitsu' | 'kokushi';
@ -24,7 +24,7 @@ export function calcWaitPatterns(fourMentsuOneJyantou: FourMentsuOneJyantou | nu
const result: FourMentsuOneJyantouWithWait[] = []; const result: FourMentsuOneJyantouWithWait[] = [];
if (fourMentsuOneJyantou.head == agariTile) { if (fourMentsuOneJyantou.head === agariTile) {
result.push({ result.push({
head: fourMentsuOneJyantou.head, head: fourMentsuOneJyantou.head,
mentsus: fourMentsuOneJyantou.mentsus, mentsus: fourMentsuOneJyantou.mentsus,
@ -44,7 +44,7 @@ export function calcWaitPatterns(fourMentsuOneJyantou: FourMentsuOneJyantou | nu
waitedFor: 'mentsu', waitedFor: 'mentsu',
agariTile, agariTile,
waitedTaatsu: mentsu.toSpliced(agariTileIndex, 1) as [TileType, TileType], waitedTaatsu: mentsu.toSpliced(agariTileIndex, 1) as [TileType, TileType],
}) });
checkedMentsus.push(mentsu); checkedMentsus.push(mentsu);
} }
@ -55,9 +55,9 @@ export function isRyanmen(taatsu: [TileType, TileType]): boolean {
const number1 = TILE_NUMBER_MAP[taatsu[0]]; const number1 = TILE_NUMBER_MAP[taatsu[0]];
const number2 = TILE_NUMBER_MAP[taatsu[1]]; const number2 = TILE_NUMBER_MAP[taatsu[1]];
if (number1 == null || number2 == null) return false; if (number1 == null || number2 == null) return false;
return number1 != 1 && number2 != 9 && number1 + 1 == number2; return number1 !== 1 && number2 !== 9 && number1 + 1 === number2;
} }
export function isToitsu(taatsu: [TileType, TileType]): boolean { export function isToitsu(taatsu: [TileType, TileType]): boolean {
return taatsu[0] == taatsu[1]; return taatsu[0] === taatsu[1];
} }

View File

@ -54,7 +54,7 @@ export type TileId = number;
// NOTE: 0 は"不明"(他プレイヤーの手牌など)を表すものとして予約されている // NOTE: 0 は"不明"(他プレイヤーの手牌など)を表すものとして予約されている
export const TILE_ID_MAP = new Map<TileId, TileInstance>([ export const TILE_ID_MAP = new Map<TileId, TileInstance>([
/* eslint-disable no-multi-spaces */ /* eslint-disable @stylistic/no-multi-spaces */
[1, { t: 'm1' }], [2, { t: 'm1' }], [3, { t: 'm1' }], [4, { t: 'm1' }], [1, { t: 'm1' }], [2, { t: 'm1' }], [3, { t: 'm1' }], [4, { t: 'm1' }],
[5, { t: 'm2' }], [6, { t: 'm2' }], [7, { t: 'm2' }], [8, { t: 'm2' }], [5, { t: 'm2' }], [6, { t: 'm2' }], [7, { t: 'm2' }], [8, { t: 'm2' }],
[9, { t: 'm3' }], [10, { t: 'm3' }], [11, { t: 'm3' }], [12, { t: 'm3' }], [9, { t: 'm3' }], [10, { t: 'm3' }], [11, { t: 'm3' }], [12, { t: 'm3' }],
@ -89,7 +89,7 @@ export const TILE_ID_MAP = new Map<TileId, TileInstance>([
[125, { t: 'haku' }], [126, { t: 'haku' }], [127, { t: 'haku' }], [128, { t: 'haku' }], [125, { t: 'haku' }], [126, { t: 'haku' }], [127, { t: 'haku' }], [128, { t: 'haku' }],
[129, { t: 'hatsu' }], [130, { t: 'hatsu' }], [131, { t: 'hatsu' }], [132, { t: 'hatsu' }], [129, { t: 'hatsu' }], [130, { t: 'hatsu' }], [131, { t: 'hatsu' }], [132, { t: 'hatsu' }],
[133, { t: 'chun' }], [134, { t: 'chun' }], [135, { t: 'chun' }], [136, { t: 'chun' }], [133, { t: 'chun' }], [134, { t: 'chun' }], [135, { t: 'chun' }], [136, { t: 'chun' }],
/* eslint-enable no-multi-spaces */ /* eslint-enable @stylistic/no-multi-spaces */
]); ]);
export function findTileByIdOrFail(tid: TileId): TileInstance { export function findTileByIdOrFail(tid: TileId): TileInstance {
@ -130,7 +130,7 @@ export type PointFactor = {
} | { } | {
isYakuman: true; isYakuman: true;
value: number; value: number;
} };
export const CALL_HURO_TYPES = ['pon', 'cii', 'minkan'] as const; export const CALL_HURO_TYPES = ['pon', 'cii', 'minkan'] as const;
@ -386,7 +386,7 @@ export function calcTsumoHoraPointDeltas(house: House, fansOrFactor: number | Po
n: 0, n: 0,
}; };
const point = typeof fansOrFactor == 'number' ? fanToPoint(fansOrFactor, isParent) : calcPoint(fansOrFactor, isParent); const point = typeof fansOrFactor === 'number' ? fanToPoint(fansOrFactor, isParent) : calcPoint(fansOrFactor, isParent);
deltas[house] = point; deltas[house] = point;
if (isParent) { if (isParent) {
const childPoint = Math.ceil(point / 3); const childPoint = Math.ceil(point / 3);
@ -464,7 +464,7 @@ export function isKotsu(tiles: [TileType, TileType, TileType]): boolean {
} }
export function mentsuEquals(tiles1: [TileType, TileType, TileType], tiles2: [TileType, TileType, TileType]): boolean { export function mentsuEquals(tiles1: [TileType, TileType, TileType], tiles2: [TileType, TileType, TileType]): boolean {
return tiles1[0] == tiles2[0] && tiles1[1] == tiles2[1] && tiles1[2] == tiles2[2]; return tiles1[0] === tiles2[0] && tiles1[1] === tiles2[1] && tiles1[2] === tiles2[2];
} }
export const SHUNTU_PATTERNS: [TileType, TileType, TileType][] = [ export const SHUNTU_PATTERNS: [TileType, TileType, TileType][] = [

View File

@ -68,7 +68,7 @@ export const YAKUMAN_NAMES = [
'chiho', 'chiho',
] as const; ] as const;
type NormalYakuName = typeof NORMAL_YAKU_NAMES[number] type NormalYakuName = typeof NORMAL_YAKU_NAMES[number];
type YakumanName = typeof YAKUMAN_NAMES[number]; type YakumanName = typeof YAKUMAN_NAMES[number];
@ -286,7 +286,7 @@ function countIndenticalMentsuPairs(mentsus: [TileType, TileType, TileType][]) {
let result = 0; let result = 0;
const singleMentsus: [TileType, TileType, TileType][] = []; const singleMentsus: [TileType, TileType, TileType][] = [];
loop: for (const mentsu of mentsus) { loop: for (const mentsu of mentsus) {
for (let i = 0 ; i < singleMentsus.length ; i++) { for (let i = 0; i < singleMentsus.length; i++) {
if (mentsuEquals(mentsu, singleMentsus[i])) { if (mentsuEquals(mentsu, singleMentsus[i])) {
result++; result++;
singleMentsus.splice(i, 1); singleMentsus.splice(i, 1);
@ -302,18 +302,19 @@ function countIndenticalMentsuPairs(mentsus: [TileType, TileType, TileType][]) {
* (34) * (34)
*/ */
function countAnkos(state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait) { function countAnkos(state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait) {
let ankans = state.huros.filter(huro => huro.type == 'ankan').length; const ankans = state.huros.filter(huro => huro.type === 'ankan').length;
const handKotsus = fourMentsuOneJyantou.mentsus.filter(mentsu => isKotsu(mentsu)).length; const handKotsus = fourMentsuOneJyantou.mentsus.filter(mentsu => isKotsu(mentsu)).length;
// ロンによりできた刻子は暗刻ではない // ロンによりできた刻子は暗刻ではない
if (state.ronTile != null && fourMentsuOneJyantou.waitedFor == 'mentsu' && isToitsu(fourMentsuOneJyantou.waitedTaatsu)) { if (state.ronTile != null && fourMentsuOneJyantou.waitedFor === 'mentsu' && isToitsu(fourMentsuOneJyantou.waitedTaatsu)) {
return ankans + handKotsus - 1; return ankans + handKotsus - 1;
} }
return ankans + handKotsus; return ankans + handKotsus;
} }
export const NORMAL_YAKU_DEFINITIONS: NormalYakuDefinition[] = [{ export const NORMAL_YAKU_DEFINITIONS: NormalYakuDefinition[] = [
{
name: 'tsumo', name: 'tsumo',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
@ -323,61 +324,67 @@ export const NORMAL_YAKU_DEFINITIONS: NormalYakuDefinition[] = [{
return state.tsumoTile != null; return state.tsumoTile != null;
}, },
}, { },
{
name: 'riichi', name: 'riichi',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return !state.doubleRiichi && (state.riichi ?? false); return !state.doubleRiichi && (state.riichi ?? false);
}, },
}, { },
{
name: 'double-riichi', name: 'double-riichi',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku) => { calc: (state: EnvForCalcYaku) => {
return state.doubleRiichi ?? false; return state.doubleRiichi ?? false;
} },
}, { },
{
name: 'ippatsu', name: 'ippatsu',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return state.ippatsu ?? false; return state.ippatsu ?? false;
}, },
}, { },
{
name: 'rinshan', name: 'rinshan',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return (state.tsumoTile != null && state.rinshan) ?? false; return (state.tsumoTile != null && state.rinshan) ?? false;
} },
}, { },
{
name: 'haitei', name: 'haitei',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return (state.tsumoTile != null && state.haitei) ?? false; return (state.tsumoTile != null && state.haitei) ?? false;
} },
}, { },
{
name: 'hotei', name: 'hotei',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return (state.ronTile != null && state.hotei) ?? false; return (state.ronTile != null && state.hotei) ?? false;
} },
}, },
new Yakuhai('red', 'chun'), new Yakuhai('red', 'chun'),
new Yakuhai('white', 'haku'), new Yakuhai('white', 'haku'),
new Yakuhai('green', 'hatsu'), new Yakuhai('green', 'hatsu'),
new FieldWind('field-wind-e', 'e'), new FieldWind('field-wind-e', 'e'),
new FieldWind('field-wind-s', 's'), new FieldWind('field-wind-s', 's'),
new FieldWind('field-wind-w', 'w'), new FieldWind('field-wind-w', 'w'),
new FieldWind('field-wind-n', 'n'), new FieldWind('field-wind-n', 'n'),
new SeatWind('seat-wind-e', 'e'), new SeatWind('seat-wind-e', 'e'),
new SeatWind('seat-wind-s', 's'), new SeatWind('seat-wind-s', 's'),
new SeatWind('seat-wind-w', 'w'), new SeatWind('seat-wind-w', 'w'),
new SeatWind('seat-wind-n', 'n'), new SeatWind('seat-wind-n', 'n'),
{ {
name: 'tanyao', name: 'tanyao',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
@ -392,7 +399,8 @@ new SeatWind('seat-wind-n', 'n'),
false).length === 0) false).length === 0)
); );
}, },
}, { },
{
name: 'pinfu', name: 'pinfu',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
@ -405,7 +413,7 @@ new SeatWind('seat-wind-n', 'n'),
if (state.handTiles.some(t => ['haku', 'hatsu', 'chun'].includes(t))) return false; if (state.handTiles.some(t => ['haku', 'hatsu', 'chun'].includes(t))) return false;
// 両面待ちかどうか // 両面待ちかどうか
if (!(fourMentsuOneJyantou != null && fourMentsuOneJyantou.waitedFor == 'mentsu' && isRyanmen(fourMentsuOneJyantou.waitedTaatsu))) return false; if (!(fourMentsuOneJyantou != null && fourMentsuOneJyantou.waitedFor === 'mentsu' && isRyanmen(fourMentsuOneJyantou.waitedTaatsu))) return false;
// 風牌判定(役牌でなければOK) // 風牌判定(役牌でなければOK)
if (fourMentsuOneJyantou.head === state.seatWind) return false; if (fourMentsuOneJyantou.head === state.seatWind) return false;
@ -416,7 +424,8 @@ new SeatWind('seat-wind-n', 'n'),
return true; return true;
}, },
}, { },
{
name: 'honitsu', name: 'honitsu',
fan: 3, fan: 3,
isYakuman: false, isYakuman: false,
@ -443,7 +452,8 @@ new SeatWind('seat-wind-n', 'n'),
return true; return true;
}, },
}, { },
{
name: 'chinitsu', name: 'chinitsu',
fan: 6, fan: 6,
isYakuman: false, isYakuman: false,
@ -470,7 +480,8 @@ new SeatWind('seat-wind-n', 'n'),
return true; return true;
}, },
}, { },
{
name: 'iipeko', name: 'iipeko',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
@ -481,9 +492,10 @@ new SeatWind('seat-wind-n', 'n'),
if (state.huros.some(huro => includes(CALL_HURO_TYPES, huro.type))) return false; if (state.huros.some(huro => includes(CALL_HURO_TYPES, huro.type))) return false;
// 同じ順子が2つあるか // 同じ順子が2つあるか
return countIndenticalMentsuPairs(fourMentsuOneJyantou.mentsus) == 1; return countIndenticalMentsuPairs(fourMentsuOneJyantou.mentsus) === 1;
}, },
}, { },
{
name: 'ryampeko', name: 'ryampeko',
fan: 3, fan: 3,
isYakuman: false, isYakuman: false,
@ -494,9 +506,10 @@ new SeatWind('seat-wind-n', 'n'),
if (state.huros.some(huro => includes(CALL_HURO_TYPES, huro.type))) return false; if (state.huros.some(huro => includes(CALL_HURO_TYPES, huro.type))) return false;
// 2つの同じ順子が2組あるか // 2つの同じ順子が2組あるか
return countIndenticalMentsuPairs(fourMentsuOneJyantou.mentsus) == 2; return countIndenticalMentsuPairs(fourMentsuOneJyantou.mentsus) === 2;
}, },
}, { },
{
name: 'toitoi', name: 'toitoi',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -512,30 +525,34 @@ new SeatWind('seat-wind-n', 'n'),
return true; return true;
}, },
}, { },
{
name: 'sananko', name: 'sananko',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait | null) => {
return fourMentsuOneJyantou != null && countAnkos(state, fourMentsuOneJyantou) == 3; return fourMentsuOneJyantou != null && countAnkos(state, fourMentsuOneJyantou) === 3;
}, },
}, { },
{
name: 'honroto', name: 'honroto',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku) => { calc: (state: EnvForCalcYaku) => {
return state.huros.every(huro => huro.type != 'cii' && includes(YAOCHU_TILES, huro.tile)) && return state.huros.every(huro => huro.type !== 'cii' && includes(YAOCHU_TILES, huro.tile)) &&
state.handTiles.every(tile => includes(YAOCHU_TILES, tile)); state.handTiles.every(tile => includes(YAOCHU_TILES, tile));
} },
}, { },
{
name: 'sankantsu', name: 'sankantsu',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return fourMentsuOneJyantou != null && return fourMentsuOneJyantou != null &&
state.huros.filter(huro => huro.type == 'ankan' || huro.type == 'minkan').length == 3; state.huros.filter(huro => huro.type === 'ankan' || huro.type === 'minkan').length === 3;
} },
}, { },
{
name: 'sanshoku-dojun', name: 'sanshoku-dojun',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -569,7 +586,8 @@ new SeatWind('seat-wind-n', 'n'),
return false; return false;
}, },
}, { },
{
name: 'sanshoku-doko', name: 'sanshoku-doko',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -603,7 +621,8 @@ new SeatWind('seat-wind-n', 'n'),
return false; return false;
}, },
}, { },
{
name: 'ittsu', name: 'ittsu',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -612,7 +631,7 @@ new SeatWind('seat-wind-n', 'n'),
if (fourMentsuOneJyantou == null) return false; if (fourMentsuOneJyantou == null) return false;
const shuntsus = fourMentsuOneJyantou.mentsus.filter(tiles => isShuntu(tiles)); const shuntsus = fourMentsuOneJyantou.mentsus.filter(tiles => isShuntu(tiles));
shuntsus.push(...state.huros.filter((huro): huro is Cii => huro.type == 'cii').map(huro => huro.tiles)); shuntsus.push(...state.huros.filter((huro): huro is Cii => huro.type === 'cii').map(huro => huro.tiles));
if (shuntsus.some(tiles => tiles[0] === 'm1' && tiles[1] === 'm2' && tiles[2] === 'm3')) { if (shuntsus.some(tiles => tiles[0] === 'm1' && tiles[1] === 'm2' && tiles[2] === 'm3')) {
if (shuntsus.some(tiles => tiles[0] === 'm4' && tiles[1] === 'm5' && tiles[2] === 'm6')) { if (shuntsus.some(tiles => tiles[0] === 'm4' && tiles[1] === 'm5' && tiles[2] === 'm6')) {
@ -638,7 +657,8 @@ new SeatWind('seat-wind-n', 'n'),
return false; return false;
}, },
}, { },
{
name: 'chanta', name: 'chanta',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -658,15 +678,16 @@ new SeatWind('seat-wind-n', 'n'),
// いずれかの雀頭か面子に字牌を含まないとダメ // いずれかの雀頭か面子に字牌を含まないとダメ
if (!(includes(CHAR_TILES, head) || if (!(includes(CHAR_TILES, head) ||
mentsus.some(mentsu => includes(CHAR_TILES, mentsu[0])) || mentsus.some(mentsu => includes(CHAR_TILES, mentsu[0])) ||
huros.some(huro => huro.type != 'cii' && includes(CHAR_TILES, huro.tile)))) return false; huros.some(huro => huro.type !== 'cii' && includes(CHAR_TILES, huro.tile)))) return false;
// 全ての面子に幺九牌が含まれる // 全ての面子に幺九牌が含まれる
return (mentsus.every(mentsu => mentsu.some(tile => includes(YAOCHU_TILES, tile))) && return (mentsus.every(mentsu => mentsu.some(tile => includes(YAOCHU_TILES, tile))) &&
huros.every(huro => huro.type == 'cii' ? huros.every(huro => huro.type === 'cii' ?
huro.tiles.some(tile => includes(YAOCHU_TILES, tile)) : huro.tiles.some(tile => includes(YAOCHU_TILES, tile)) :
includes(YAOCHU_TILES, huro.tile))); includes(YAOCHU_TILES, huro.tile)));
}, },
}, { },
{
name: 'junchan', name: 'junchan',
fan: 3, fan: 3,
isYakuman: false, isYakuman: false,
@ -685,11 +706,12 @@ new SeatWind('seat-wind-n', 'n'),
// 全ての面子に老頭牌が含まれる // 全ての面子に老頭牌が含まれる
return (mentsus.every(mentsu => mentsu.some(tile => includes(TERMINAL_TILES, tile))) && return (mentsus.every(mentsu => mentsu.some(tile => includes(TERMINAL_TILES, tile))) &&
huros.every(huro => huro.type == 'cii' ? huros.every(huro => huro.type === 'cii' ?
huro.tiles.some(tile => includes(TERMINAL_TILES, tile)) : huro.tiles.some(tile => includes(TERMINAL_TILES, tile)) :
includes(TERMINAL_TILES, huro.tile))); includes(TERMINAL_TILES, huro.tile)));
}, },
}, { },
{
name: 'chitoitsu', name: 'chitoitsu',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -703,7 +725,8 @@ new SeatWind('seat-wind-n', 'n'),
} }
return Array.from(countMap.values()).every(c => c === 2); return Array.from(countMap.values()).every(c => c === 2);
}, },
}, { },
{
name: 'shosangen', name: 'shosangen',
fan: 2, fan: 2,
isYakuman: false, isYakuman: false,
@ -730,23 +753,27 @@ new SeatWind('seat-wind-n', 'n'),
return false; return false;
}, },
}]; },
];
export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [
{
name: 'suanko-tanki', name: 'suanko-tanki',
isYakuman: true, isYakuman: true,
isDoubleYakuman: true, isDoubleYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait | null) => {
return fourMentsuOneJyantou != null && fourMentsuOneJyantou.waitedFor == 'head' && countAnkos(state, fourMentsuOneJyantou) == 4; return fourMentsuOneJyantou != null && fourMentsuOneJyantou.waitedFor === 'head' && countAnkos(state, fourMentsuOneJyantou) === 4;
} },
}, { },
{
name: 'suanko', name: 'suanko',
isYakuman: true, isYakuman: true,
upper: 'suanko-tanki', upper: 'suanko-tanki',
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantouWithWait | null) => {
return fourMentsuOneJyantou != null && countAnkos(state, fourMentsuOneJyantou) == 4; return fourMentsuOneJyantou != null && countAnkos(state, fourMentsuOneJyantou) === 4;
} },
}, { },
{
name: 'daisangen', name: 'daisangen',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
@ -766,7 +793,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return kotsuTiles.includes('haku') && kotsuTiles.includes('hatsu') && kotsuTiles.includes('chun'); return kotsuTiles.includes('haku') && kotsuTiles.includes('hatsu') && kotsuTiles.includes('chun');
}, },
}, { },
{
name: 'shosushi', name: 'shosushi',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
@ -792,7 +820,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return false; return false;
}, },
}, { },
{
name: 'daisushi', name: 'daisushi',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
@ -812,7 +841,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return kotsuTiles.includes('e') && kotsuTiles.includes('s') && kotsuTiles.includes('w') && kotsuTiles.includes('n'); return kotsuTiles.includes('e') && kotsuTiles.includes('s') && kotsuTiles.includes('w') && kotsuTiles.includes('n');
}, },
}, { },
{
name: 'tsuiso', name: 'tsuiso',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku) => { calc: (state: EnvForCalcYaku) => {
@ -832,7 +862,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return true; return true;
}, },
}, { },
{
name: 'ryuiso', name: 'ryuiso',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
@ -847,22 +878,25 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return true; return true;
}, },
}, { },
{
name: 'chinroto', name: 'chinroto',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return fourMentsuOneJyantou != null && return fourMentsuOneJyantou != null &&
state.huros.every(huro => huro.type != 'cii' && includes(TERMINAL_TILES, huro.tile)) && state.huros.every(huro => huro.type !== 'cii' && includes(TERMINAL_TILES, huro.tile)) &&
state.handTiles.every(tile => includes(TERMINAL_TILES, tile)); state.handTiles.every(tile => includes(TERMINAL_TILES, tile));
} },
}, { },
{
name: 'sukantsu', name: 'sukantsu',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return fourMentsuOneJyantou != null && return fourMentsuOneJyantou != null &&
state.huros.filter(huro => huro.type == 'ankan' || huro.type == 'minkan').length == 4; state.huros.filter(huro => huro.type === 'ankan' || huro.type === 'minkan').length === 4;
} },
}, { },
{
name: 'churen-9', name: 'churen-9',
isYakuman: true, isYakuman: true,
isDoubleYakuman: true, isDoubleYakuman: true,
@ -901,7 +935,8 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return false; return false;
}, },
}, { },
{
name: 'churen', name: 'churen',
upper: 'churen-9', upper: 'churen-9',
isYakuman: true, isYakuman: true,
@ -933,34 +968,39 @@ export const YAKUMAN_DEFINITIONS: YakumanDefinition[] = [{
return false; return false;
}, },
}, { },
{
name: 'kokushi-13', name: 'kokushi-13',
isYakuman: true, isYakuman: true,
isDoubleYakuman: true, isDoubleYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
const agariTile = state.tsumoTile ?? state.ronTile; const agariTile = state.tsumoTile ?? state.ronTile;
return KOKUSHI_TILES.every(t => state.handTiles.includes(t)) && countTiles(state.handTiles, agariTile) == 2; return KOKUSHI_TILES.every(t => state.handTiles.includes(t)) && countTiles(state.handTiles, agariTile) === 2;
} },
}, { },
{
name: 'kokushi', name: 'kokushi',
isYakuman: true, isYakuman: true,
upper: 'kokushi-13', upper: 'kokushi-13',
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return KOKUSHI_TILES.every(t => state.handTiles.includes(t)) && KOKUSHI_TILES.some(t => countTiles(state.handTiles, t) == 2); return KOKUSHI_TILES.every(t => state.handTiles.includes(t)) && KOKUSHI_TILES.some(t => countTiles(state.handTiles, t) === 2);
}, },
}, { },
{
name: 'tenho', name: 'tenho',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku) => { calc: (state: EnvForCalcYaku) => {
return (state.firstTurn ?? false) && state.tsumoTile != null && state.seatWind == 'e'; return (state.firstTurn ?? false) && state.tsumoTile != null && state.seatWind === 'e';
} },
}, { },
{
name: 'chiho', name: 'chiho',
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku) => { calc: (state: EnvForCalcYaku) => {
return (state.firstTurn ?? false) && state.tsumoTile != null && state.seatWind != 'e'; return (state.firstTurn ?? false) && state.tsumoTile != null && state.seatWind !== 'e';
} },
}]; },
];
export function convertHuroForCalcYaku(huro: Huro): HuroForCalcYaku { export function convertHuroForCalcYaku(huro: Huro): HuroForCalcYaku {
switch (huro.type) { switch (huro.type) {
@ -970,7 +1010,7 @@ export function convertHuroForCalcYaku(huro: Huro): HuroForCalcYaku {
return { return {
type: huro.type, type: huro.type,
tile: TILE_ID_MAP.get(huro.tiles[0])!.t, tile: TILE_ID_MAP.get(huro.tiles[0])!.t,
} };
case 'cii': case 'cii':
return { return {
type: 'cii', type: 'cii',
@ -986,7 +1026,7 @@ const NORMAL_YAKU_DATA_MAP = new Map<NormalYakuName, Required<NormalYakuData>>(
fan: yaku.fan, fan: yaku.fan,
isYakuman: false, isYakuman: false,
kuisagari: yaku.kuisagari ?? false, kuisagari: yaku.kuisagari ?? false,
}] as const) }] as const),
); );
const YAKUMAN_DATA_MAP = new Map<YakuName, Required<YakumanData>>( const YAKUMAN_DATA_MAP = new Map<YakuName, Required<YakumanData>>(
@ -996,7 +1036,7 @@ const YAKUMAN_DATA_MAP = new Map<YakuName, Required<YakumanData>>(
fan: null, fan: null,
isYakuman: true, isYakuman: true,
isDoubleYakuman: yaku.isDoubleYakuman ?? false, isDoubleYakuman: yaku.isDoubleYakuman ?? false,
}]) }]),
); );
export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet { export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
@ -1009,7 +1049,7 @@ export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
throw new TypeError('Agari tile not included in hand tiles'); throw new TypeError('Agari tile not included in hand tiles');
} }
if (state.handTiles.length + state.huros.length * 3 != 14) { if (state.handTiles.length + state.huros.length * 3 !== 14) {
throw new TypeError('Invalid tile count'); throw new TypeError('Invalid tile count');
} }
@ -1017,7 +1057,7 @@ export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
if (oneHeadFourMentsuPatterns.length === 0) oneHeadFourMentsuPatterns.push(null); if (oneHeadFourMentsuPatterns.length === 0) oneHeadFourMentsuPatterns.push(null);
const waitPatterns = oneHeadFourMentsuPatterns.map( const waitPatterns = oneHeadFourMentsuPatterns.map(
fourMentsuOneJyantou => calcWaitPatterns(fourMentsuOneJyantou, agariTile) fourMentsuOneJyantou => calcWaitPatterns(fourMentsuOneJyantou, agariTile),
).flat(); ).flat();
const yakumanPatterns = waitPatterns.map(fourMentsuOneJyantouWithWait => { const yakumanPatterns = waitPatterns.map(fourMentsuOneJyantouWithWait => {
@ -1038,13 +1078,13 @@ export function calcYakusWithDetail(state: EnvForCalcYaku): YakuSet {
const yakuPatterns = waitPatterns.map( const yakuPatterns = waitPatterns.map(
fourMentsuOneJyantouWithWait => NORMAL_YAKU_DEFINITIONS.filter( fourMentsuOneJyantouWithWait => NORMAL_YAKU_DEFINITIONS.filter(
yakuDef => yakuDef.calc(state, fourMentsuOneJyantouWithWait) yakuDef => yakuDef.calc(state, fourMentsuOneJyantouWithWait),
).map(yakuDef => NORMAL_YAKU_DATA_MAP.get(yakuDef.name)!) ).map(yakuDef => NORMAL_YAKU_DATA_MAP.get(yakuDef.name)!),
).filter(yakus => yakus.length > 0); ).filter(yakus => yakus.length > 0);
const isMenzen = state.huros.some(huro => includes(CALL_HURO_TYPES, huro.type)); const isMenzen = state.huros.some(huro => includes(CALL_HURO_TYPES, huro.type));
if (yakuPatterns.length == 0) { if (yakuPatterns.length === 0) {
return new NormalYakuSet(isMenzen, []); return new NormalYakuSet(isMenzen, []);
} }

View File

@ -613,7 +613,7 @@ export class MasterGameEngine {
public commit_kakan(house: House, tid: TileId) { public commit_kakan(house: House, tid: TileId) {
const tx = this.startTransaction(); const tx = this.startTransaction();
const pon = tx.$state.huros[house].find(h => h.type === 'pon' && $type(h.tiles[0]) === $type(tid)) as Huro & {type: 'pon'}; const pon = tx.$state.huros[house].find(h => h.type === 'pon' && $type(h.tiles[0]) === $type(tid)) as Huro & { type: 'pon' };
if (pon == null) throw new Error('No such pon'); if (pon == null) throw new Error('No such pon');
tx.$state.handTiles[house].splice(tx.$state.handTiles[house].indexOf(tid), 1); tx.$state.handTiles[house].splice(tx.$state.handTiles[house].indexOf(tid), 1);
const tiles = [tid, ...pon.tiles] as const; const tiles = [tid, ...pon.tiles] as const;
@ -686,7 +686,7 @@ export class MasterGameEngine {
doubleRiichi: tx.$state.doubleRiichis[house], doubleRiichi: tx.$state.doubleRiichis[house],
ippatsu: tx.$state.ippatsus[house], ippatsu: tx.$state.ippatsus[house],
rinshan: tx.$state.rinshanFlags[house], rinshan: tx.$state.rinshanFlags[house],
haitei: tx.$state.tiles.length == 0, haitei: tx.$state.tiles.length === 0,
}); });
const doraCount = const doraCount =
Common.calcOwnedDoraCount(tx.handTileTypes[house], tx.$state.huros[house], tx.doras) + Common.calcOwnedDoraCount(tx.handTileTypes[house], tx.$state.huros[house], tx.doras) +
@ -743,7 +743,7 @@ export class MasterGameEngine {
riichi: tx.$state.riichis[house], riichi: tx.$state.riichis[house],
doubleRiichi: tx.$state.doubleRiichis[house], doubleRiichi: tx.$state.doubleRiichis[house],
ippatsu: tx.$state.ippatsus[house], ippatsu: tx.$state.ippatsus[house],
hotei: tx.$state.tiles.length == 0, hotei: tx.$state.tiles.length === 0,
}); });
const doraCount = const doraCount =
Common.calcOwnedDoraCount(tx.handTileTypes[house], tx.$state.huros[house], tx.doras) + Common.calcOwnedDoraCount(tx.handTileTypes[house], tx.$state.huros[house], tx.doras) +

View File

@ -270,7 +270,7 @@ export class PlayerGameEngine {
doubleRiichi: this.state.doubleRiichis[house], doubleRiichi: this.state.doubleRiichis[house],
ippatsu: this.state.ippatsus[house], ippatsu: this.state.ippatsus[house],
rinshan: this.state.rinshanFlags[house], rinshan: this.state.rinshanFlags[house],
haitei: this.state.tilesCount == 0, haitei: this.state.tilesCount === 0,
}); });
const doraCount = const doraCount =
Common.calcOwnedDoraCount(handTiles.map(id => $type(id)), this.state.huros[house], this.doras) + Common.calcOwnedDoraCount(handTiles.map(id => $type(id)), this.state.huros[house], this.doras) +
@ -322,7 +322,7 @@ export class PlayerGameEngine {
riichi: this.state.riichis[house], riichi: this.state.riichis[house],
doubleRiichi: this.state.doubleRiichis[house], doubleRiichi: this.state.doubleRiichis[house],
ippatsu: this.state.ippatsus[house], ippatsu: this.state.ippatsus[house],
hotei: this.state.tilesCount == 0, hotei: this.state.tilesCount === 0,
}); });
const doraCount = const doraCount =
Common.calcOwnedDoraCount(handTiles[house].map(id => $type(id)), this.state.huros[house], this.doras) + Common.calcOwnedDoraCount(handTiles[house].map(id => $type(id)), this.state.huros[house], this.doras) +