This commit is contained in:
syuilo 2024-02-11 14:23:37 +09:00
parent c47203b888
commit 3f810a856c
2 changed files with 201 additions and 19 deletions

View file

@ -15,8 +15,14 @@ export const NORMAL_YAKU_NAMES = [
'tanyao', 'tanyao',
'pinfu', 'pinfu',
'iipeko', 'iipeko',
'field-wind', 'field-wind-e',
'seat-wind', 'field-wind-s',
'field-wind-w',
'field-wind-n',
'seat-wind-e',
'seat-wind-s',
'seat-wind-w',
'seat-wind-n',
'white', 'white',
'green', 'green',
'red', 'red',
@ -71,8 +77,6 @@ export type EnvForCalcYaku = {
*/ */
handTiles: TileType[]; handTiles: TileType[];
tenpaiTiles: TileType[];
/** /**
* *
*/ */
@ -121,8 +125,10 @@ export type EnvForCalcYaku = {
type YakuDefiniyion = { type YakuDefiniyion = {
name: YakuName; name: YakuName;
fan: number; upper?: YakuName;
fan?: number;
isYakuman?: boolean; isYakuman?: boolean;
isDoubleYakuman?: boolean;
kuisagari?: boolean; kuisagari?: boolean;
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => boolean; calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => boolean;
}; };
@ -131,7 +137,7 @@ function countTiles(tiles: TileType[], target: TileType): number {
return tiles.filter(t => t === target).length; return tiles.filter(t => t === target).length;
} }
export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{ export const NORAML_YAKU_DEFINITIONS: YakuDefiniyion[] = [{
name: 'tsumo', name: 'tsumo',
fan: 1, fan: 1,
isYakuman: false, isYakuman: false,
@ -141,7 +147,7 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
// 面前じゃないとダメ // 面前じゃないとダメ
if (state.huros.some(huro => CALL_HURO_TYPES.includes(huro.type))) return false; if (state.huros.some(huro => CALL_HURO_TYPES.includes(huro.type))) return false;
return state.isTsumo; return state.tsumoTile != null;
}, },
}, { }, {
name: 'riichi', name: 'riichi',
@ -579,9 +585,10 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
return false; return false;
}, },
}, { }];
export const YAKUMAN_DEFINITIONS: YakuDefiniyion[] = [{
name: 'daisangen', name: 'daisangen',
fan: 13,
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false; if (fourMentsuOneJyantou == null) return false;
@ -602,7 +609,6 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
}, },
}, { }, {
name: 'shosushi', name: 'shosushi',
fan: 13,
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false; if (fourMentsuOneJyantou == null) return false;
@ -629,7 +635,6 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
}, },
}, { }, {
name: 'daisushi', name: 'daisushi',
fan: 13,
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false; if (fourMentsuOneJyantou == null) return false;
@ -650,7 +655,6 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
}, },
}, { }, {
name: 'tsuiso', name: 'tsuiso',
fan: 13,
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false; if (fourMentsuOneJyantou == null) return false;
@ -673,7 +677,6 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
}, },
}, { }, {
name: 'ryuiso', name: 'ryuiso',
fan: 13,
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false; if (fourMentsuOneJyantou == null) return false;
@ -687,9 +690,76 @@ export const YAKU_DEFINITIONS: YakuDefiniyion[] = [{
return true; return true;
}, },
}, {
name: 'churen-9',
isYakuman: true,
isDoubleYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false;
// 面前じゃないとダメ
if (state.huros.some(huro => CALL_HURO_TYPES.includes(huro.type))) return false;
const agariTile = state.tsumoTile ?? state.ronTile;
const tempaiTiles = [...state.handTiles];
tempaiTiles.splice(state.handTiles.indexOf(agariTile), 1);
if (isManzu(agariTile)) {
if ((countTiles(tempaiTiles, 'm1') === 3) && (countTiles(tempaiTiles, 'm9') === 3)) {
if (tempaiTiles.includes('m2') && tempaiTiles.includes('m3') && tempaiTiles.includes('m4') && tempaiTiles.includes('m5') && tempaiTiles.includes('m6') && tempaiTiles.includes('m7') && tempaiTiles.includes('m8')) {
return true;
}
}
} else if (isPinzu(agariTile)) {
if ((countTiles(tempaiTiles, 'p1') === 3) && (countTiles(tempaiTiles, 'p9') === 3)) {
if (tempaiTiles.includes('p2') && tempaiTiles.includes('p3') && tempaiTiles.includes('p4') && tempaiTiles.includes('p5') && tempaiTiles.includes('p6') && tempaiTiles.includes('p7') && tempaiTiles.includes('p8')) {
return true;
}
}
} else if (isSouzu(agariTile)) {
if ((countTiles(tempaiTiles, 's1') === 3) && (countTiles(tempaiTiles, 's9') === 3)) {
if (tempaiTiles.includes('s2') && tempaiTiles.includes('s3') && tempaiTiles.includes('s4') && tempaiTiles.includes('s5') && tempaiTiles.includes('s6') && tempaiTiles.includes('s7') && tempaiTiles.includes('s8')) {
return true;
}
}
}
return false;
},
}, {
name: 'churen',
upper: 'churen-9',
isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
if (fourMentsuOneJyantou == null) return false;
// 面前じゃないとダメ
if (state.huros.some(huro => CALL_HURO_TYPES.includes(huro.type))) return false;
if (isManzu(state.handTiles[0])) {
if ((countTiles(state.handTiles, 'm1') === 3) && (countTiles(state.handTiles, 'm9') === 3)) {
if (state.handTiles.includes('m2') && state.handTiles.includes('m3') && state.handTiles.includes('m4') && state.handTiles.includes('m5') && state.handTiles.includes('m6') && state.handTiles.includes('m7') && state.handTiles.includes('m8')) {
return true;
}
}
} else if (isPinzu(state.handTiles[0])) {
if ((countTiles(state.handTiles, 'p1') === 3) && (countTiles(state.handTiles, 'p9') === 3)) {
if (state.handTiles.includes('p2') && state.handTiles.includes('p3') && state.handTiles.includes('p4') && state.handTiles.includes('p5') && state.handTiles.includes('p6') && state.handTiles.includes('p7') && state.handTiles.includes('p8')) {
return true;
}
}
} else if (isSouzu(state.handTiles[0])) {
if ((countTiles(state.handTiles, 's1') === 3) && (countTiles(state.handTiles, 's9') === 3)) {
if (state.handTiles.includes('s2') && state.handTiles.includes('s3') && state.handTiles.includes('s4') && state.handTiles.includes('s5') && state.handTiles.includes('s6') && state.handTiles.includes('s7') && state.handTiles.includes('s8')) {
return true;
}
}
}
return false;
},
}, { }, {
name: 'kokushi', name: 'kokushi',
fan: 13,
isYakuman: true, isYakuman: true,
calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => { calc: (state: EnvForCalcYaku, fourMentsuOneJyantou: FourMentsuOneJyantou | null) => {
return KOKUSHI_TILES.every(t => state.handTiles.includes(t)); return KOKUSHI_TILES.every(t => state.handTiles.includes(t));
@ -700,8 +770,24 @@ export function calcYakus(state: EnvForCalcYaku): YakuName[] {
const oneHeadFourMentsuPatterns: (FourMentsuOneJyantou | null)[] = analyzeFourMentsuOneJyantou(state.handTiles); const oneHeadFourMentsuPatterns: (FourMentsuOneJyantou | null)[] = analyzeFourMentsuOneJyantou(state.handTiles);
if (oneHeadFourMentsuPatterns.length === 0) oneHeadFourMentsuPatterns.push(null); if (oneHeadFourMentsuPatterns.length === 0) oneHeadFourMentsuPatterns.push(null);
const yakumanPatterns = oneHeadFourMentsuPatterns.map(fourMentsuOneJyantou => {
const matchedYakus: YakuDefiniyion[] = [];
for (const yakuDef of YAKUMAN_DEFINITIONS) {
if (yakuDef.upper && matchedYakus.some(yaku => yaku.name === yakuDef.upper)) continue;
const matched = yakuDef.calc(state, fourMentsuOneJyantou);
if (matched) {
matchedYakus.push(yakuDef);
}
}
return matchedYakus;
}).filter(yakus => yakus.length > 0);
if (yakumanPatterns.length > 0) {
return yakumanPatterns[0].map(yaku => yaku.name);
}
const yakuPatterns = oneHeadFourMentsuPatterns.map(fourMentsuOneJyantou => { const yakuPatterns = oneHeadFourMentsuPatterns.map(fourMentsuOneJyantou => {
return YAKU_DEFINITIONS.map(yakuDef => { return NORAML_YAKU_DEFINITIONS.map(yakuDef => {
const result = yakuDef.calc(state, fourMentsuOneJyantou); const result = yakuDef.calc(state, fourMentsuOneJyantou);
return result ? yakuDef : null; return result ? yakuDef : null;
}).filter(yaku => yaku != null) as YakuDefiniyion[]; }).filter(yaku => yaku != null) as YakuDefiniyion[];
@ -715,9 +801,9 @@ export function calcYakus(state: EnvForCalcYaku): YakuName[] {
let fan = 0; let fan = 0;
for (const yaku of yakus) { for (const yaku of yakus) {
if (yaku.kuisagari && !isMenzen) { if (yaku.kuisagari && !isMenzen) {
fan += yaku.fan - 1; fan += yaku.fan! - 1;
} else { } else {
fan += yaku.fan; fan += yaku.fan!;
} }
} }
if (fan > maxFan) { if (fan > maxFan) {

View file

@ -16,5 +16,101 @@ describe('Yaku', () => {
riichi: true, riichi: true,
}), ['riichi']); }), ['riichi']);
}); });
} });
}
describe('churen', () => {
it('valid', () => {
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm3', 'm4', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm5'],
huros: [],
tsumoTile: 'm5',
}), ['churen']);
});
it('invalid', () => {
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm3', 'm4', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm2'],
huros: [],
tsumoTile: 'm2',
}).includes('churen'), false);
});
});
describe('churen-9', () => {
it('valid', () => {
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm1'],
huros: [],
tsumoTile: 'm1',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm2'],
huros: [],
tsumoTile: 'm2',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm3'],
huros: [],
tsumoTile: 'm3',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm4'],
huros: [],
tsumoTile: 'm4',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm5'],
huros: [],
tsumoTile: 'm5',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm6'],
huros: [],
tsumoTile: 'm6',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm7'],
huros: [],
tsumoTile: 'm7',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm8'],
huros: [],
tsumoTile: 'm8',
}), ['churen-9']);
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm9'],
huros: [],
tsumoTile: 'm9',
}), ['churen-9']);
});
it('invalid', () => {
assert.deepStrictEqual(calcYakus({
house: 'e',
handTiles: ['m1', 'm1', 'm1', 'm2', 'm3', 'm3', 'm4', 'm6', 'm7', 'm8', 'm9', 'm9', 'm9', 'm5'],
huros: [],
tsumoTile: 'm5',
}).includes('churen-9'), false);
});
});
});