wip
This commit is contained in:
parent
c47203b888
commit
3f810a856c
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue