This commit is contained in:
syuilo 2024-02-09 15:42:33 +09:00
parent 084e9449dc
commit bb042b46ac
5 changed files with 44 additions and 13 deletions

View file

@ -657,8 +657,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
if (mj.riichis[house]) {
// リーチ時はアガリ牌でない限りツモ切り
const horaSets = Mmj.getHoraSets(mj.handTileTypes[house]);
if (horaSets.length === 0) {
if (!Mmj.canHora(mj.handTileTypes[house])) {
setTimeout(() => {
this.dahai(room, mj, house, mj.handTiles[house].at(-1));
}, 500);

View file

@ -292,7 +292,7 @@ const isMyTurn = computed(() => {
});
const canHora = computed(() => {
return Mmj.getHoraSets(mj.value.myHandTileTypes).length > 0;
return Mmj.canHora(mj.value.myHandTileTypes).length;
});
const selectableTiles = ref<Mmj.TileType[] | null>(null);

View file

@ -237,6 +237,8 @@ export const PREV_TILE_FOR_SHUNTSU: Record<TileType, TileType | null> = {
chun: null,
};
const KOKUSHI_TILES: TileType[] = ['m1', 'm9', 'p1', 'p9', 's1', 's9', 'e', 's', 'w', 'n', 'haku', 'hatsu', 'chun'];
type EnvForCalcYaku = {
house: House;
@ -471,7 +473,7 @@ export const YAKU_DEFINITIONS = [{
// TODO: 両面待ちかどうか
const horaSets = getHoraSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
const horaSets = analyze1head3mentsuSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
return horaSets.some(horaSet => {
// 風牌判定(役牌でなければOK)
if (horaSet.head === state.seatWind) return false;
@ -489,7 +491,7 @@ export const YAKU_DEFINITIONS = [{
// 面前じゃないとダメ
if (state.huros.some(huro => CALL_HURO_TYPES.includes(huro.type))) return false;
const horaSets = getHoraSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
const horaSets = analyze1head3mentsuSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
return horaSets.some(horaSet => {
// 同じ順子が2つあるか
return horaSet.mentsus.some((mentsu) =>
@ -505,12 +507,26 @@ export const YAKU_DEFINITIONS = [{
if (state.huros.length > 0) {
if (state.huros.some(huro => huro.type === 'cii')) return false;
}
const horaSets = getHoraSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
const horaSets = analyze1head3mentsuSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
return horaSets.some(horaSet => {
// 全て刻子か?
if (!horaSet.mentsus.every((mentsu) => mentsu[0] === mentsu[1])) return false;
});
},
}, {
name: 'chitoitsu',
fan: 2,
isYakuman: false,
calc: (state: EnvForCalcYaku) => {
return isChitoitsu(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
},
}, {
name: 'kokushi',
fan: 13,
isYakuman: true,
calc: (state: EnvForCalcYaku) => {
return isKokushi(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
},
}];
export function fanToPoint(fan: number, isParent: boolean): number {
@ -709,7 +725,7 @@ function extractShuntsus(tiles: TileType[]): [TileType, TileType, TileType][] {
* @param handTiles
* @returns
*/
export function getHoraSets(handTiles: TileType[]): HoraSet[] {
function analyze1head3mentsuSets(handTiles: TileType[]): HoraSet[] {
const horaSets: HoraSet[] = [];
const headSet: TileType[] = [];
@ -808,6 +824,14 @@ export function getHoraSets(handTiles: TileType[]): HoraSet[] {
return horaSets;
}
export function canHora(handTiles: TileType[]): boolean {
if (isKokushi(handTiles)) return true;
if (isChitoitsu(handTiles)) return true;
const horaSets = analyze1head3mentsuSets(handTiles);
return horaSets.length > 0;
}
/**
*
* @param handTiles
@ -815,14 +839,23 @@ export function getHoraSets(handTiles: TileType[]): HoraSet[] {
export function getHoraTiles(handTiles: TileType[]): TileType[] {
return TILE_TYPES.filter(tile => {
const tempHandTiles = [...handTiles, tile];
const horaSets = getHoraSets(tempHandTiles);
const horaSets = analyze1head3mentsuSets(tempHandTiles);
return horaSets.length > 0;
});
}
// TODO: 国士無双判定関数
function isKokushi(handTiles: TileType[]): boolean {
return KOKUSHI_TILES.every(t => handTiles.includes(t));
}
// TODO: 七対子判定関数
function isChitoitsu(handTiles: TileType[]): boolean {
const countMap = new Map<TileType, number>();
for (const tile of handTiles) {
const count = (countMap.get(tile) ?? 0) + 1;
countMap.set(tile, count);
}
return Array.from(countMap.values()).every(c => c === 2);
}
export function getTilesForRiichi(handTiles: TileType[]): TileType[] {
return handTiles.filter(tile => {

View file

@ -108,8 +108,7 @@ class StateManager {
// TODO: ポンされるなどして自分の河にない場合の考慮
if (this.hoTileTypes[house].includes($type(tid))) return false;
const horaSets = Common.getHoraSets(this.handTileTypes[house].concat($type(tid)));
if (horaSets.length === 0) return false; // 完成形じゃない
if (!Common.canHora(this.handTileTypes[house].concat($type(tid)))) return false; // 完成形じゃない
// TODO
//const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc(this.state, { tsumoTile: null, ronTile: tile }));

View file

@ -218,7 +218,7 @@ export class PlayerGameEngine {
if (house === this.myHouse) {
} else {
const canRon = Common.getHoraSets(this.myHandTiles.concat(tid).map(id => $type(id))).length > 0;
const canRon = Common.canHora(this.myHandTiles.concat(tid).map(id => $type(id)));
const canPon = !this.isMeRiichi && this.myHandTileTypes.filter(t => t === $type(tid)).length === 2;
const canKan = !this.isMeRiichi && this.myHandTileTypes.filter(t => t === $type(tid)).length === 3;
const canCii = !this.isMeRiichi && house === Common.prevHouse(this.myHouse) &&