1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-24 17:24:14 +02:00
This commit is contained in:
syuilo
2024-02-03 13:52:13 +09:00
parent 7cdaa10d46
commit 2dd886e285
10 changed files with 351 additions and 133 deletions

View File

@@ -158,7 +158,7 @@ type EnvForCalcYaku = {
house: House;
/**
* 和了る人の手牌(副露した牌は含まない)
* 和了る人の手牌(副露牌および和了る際のツモ牌・ロン牌は含まない)
*/
handTiles: Tile[];
@@ -214,6 +214,12 @@ export const YAKU_DEFINITIONS = [{
calc: (state: EnvForCalcYaku) => {
return state.riichi;
},
}, {
name: 'tsumo',
fan: 1,
calc: (state: EnvForCalcYaku) => {
return state.tsumoTile != null;
},
}, {
name: 'red',
fan: 1,
@@ -272,7 +278,7 @@ export const YAKU_DEFINITIONS = [{
calc: (state: EnvForCalcYaku) => {
const yaochuTiles: Tile[] = ['m1', 'm9', 'p1', 'p9', 's1', 's9', 'e', 's', 'w', 'n', 'haku', 'hatsu', 'chun'];
return (
(state.handTiles.filter(t => yaochuTiles.includes(t)).length === 0) &&
(!state.handTiles.some(t => yaochuTiles.includes(t))) &&
(state.huros.filter(huro =>
huro.type === 'pon' ? yaochuTiles.includes(huro.tile) :
huro.type === 'ankan' ? yaochuTiles.includes(huro.tile) :
@@ -281,6 +287,27 @@ export const YAKU_DEFINITIONS = [{
false).length === 0)
);
},
}, {
name: 'pinfu',
fan: 1,
calc: (state: EnvForCalcYaku) => {
// 面前じゃないとダメ
if (state.huros.length !== 0) return false;
// 三元牌はダメ
if (state.handTiles.some(t => ['haku', 'hatsu', 'chun'].includes(t))) return false;
// TODO: 両面待ちかどうか
const horaSets = getHoraSets(state.handTiles.concat(state.tsumoTile ?? state.ronTile));
return horaSets.some(horaSet => {
// 風牌判定(役牌でなければOK)
if (horaSet.head === state.seatWind) return false;
if (horaSet.head === state.fieldWind) return false;
// 全て順子か?
if (horaSet.mentsus.some((mentsu) => mentsu[0] === mentsu[1])) return false;
});
},
}];
export function fanToPoint(fan: number, isParent: boolean): number {
@@ -423,29 +450,44 @@ export const SHUNTU_PATTERNS: [Tile, Tile, Tile][] = [
['s7', 's8', 's9'],
];
const SHUNTU_PATTERN_IDS = [
'm123',
'm234',
'm345',
'm456',
'm567',
'm678',
'm789',
'p123',
'p234',
'p345',
'p456',
'p567',
'p678',
'p789',
's123',
's234',
's345',
's456',
's567',
's678',
's789',
] as const;
export type KyokuResult = {
yakus: { name: string; fan: number; }[];
doraCount: number;
pointDeltas: { e: number; s: number; w: number; n: number; };
};
function extractShuntsus(tiles: Tile[]): [Tile, Tile, Tile][] {
const tempTiles = [...tiles];
tempTiles.sort((a, b) => {
const aIndex = TILE_TYPES.indexOf(a);
const bIndex = TILE_TYPES.indexOf(b);
return aIndex - bIndex;
});
const shuntsus: [Tile, Tile, Tile][] = [];
while (tempTiles.length > 0) {
let isShuntu = false;
for (const shuntuPattern of SHUNTU_PATTERNS) {
if (
tempTiles[0] === shuntuPattern[0] &&
tempTiles.includes(shuntuPattern[1]) &&
tempTiles.includes(shuntuPattern[2])
) {
shuntsus.push(shuntuPattern);
tempTiles.splice(0, 1);
tempTiles.splice(tempTiles.indexOf(shuntuPattern[1]), 1);
tempTiles.splice(tempTiles.indexOf(shuntuPattern[2]), 1);
isShuntu = true;
break;
}
}
if (!isShuntu) tempTiles.splice(0, 1);
}
return shuntsus;
}
/**
* アガリ形パターン一覧を取得
@@ -537,34 +579,7 @@ export function getHoraSets(handTiles: Tile[]): HoraSet[] {
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
}
tempHandTilesWithoutKotsu.sort((a, b) => {
const aIndex = TILE_TYPES.indexOf(a);
const bIndex = TILE_TYPES.indexOf(b);
return aIndex - bIndex;
});
const tempHandTilesWithoutKotsuAndShuntsu: (Tile | null)[] = [...tempHandTilesWithoutKotsu];
const shuntsus: [Tile, Tile, Tile][] = [];
while (tempHandTilesWithoutKotsuAndShuntsu.length > 0) {
let isShuntu = false;
for (const shuntuPattern of SHUNTU_PATTERNS) {
if (
tempHandTilesWithoutKotsuAndShuntsu[0] === shuntuPattern[0] &&
tempHandTilesWithoutKotsuAndShuntsu.includes(shuntuPattern[1]) &&
tempHandTilesWithoutKotsuAndShuntsu.includes(shuntuPattern[2])
) {
shuntsus.push(shuntuPattern);
tempHandTilesWithoutKotsuAndShuntsu.splice(0, 1);
tempHandTilesWithoutKotsuAndShuntsu.splice(tempHandTilesWithoutKotsuAndShuntsu.indexOf(shuntuPattern[1]), 1);
tempHandTilesWithoutKotsuAndShuntsu.splice(tempHandTilesWithoutKotsuAndShuntsu.indexOf(shuntuPattern[2]), 1);
isShuntu = true;
break;
}
}
if (!isShuntu) tempHandTilesWithoutKotsuAndShuntsu.splice(0, 1);
}
const shuntsus = extractShuntsus(tempHandTilesWithoutKotsu);
if (shuntsus.length * 3 === tempHandTilesWithoutKotsu.length) { // アガリ形
horaSets.push({

View File

@@ -380,6 +380,7 @@ export class MasterGameEngine {
return {
asking: false as const,
tsumoTile: tsumoTile,
next: this.state.turn,
};
}
@@ -439,6 +440,11 @@ export class MasterGameEngine {
console.log('yakus', house, yakus);
this.endKyoku();
return {
handTiles: this.state.handTiles[house],
tsumoTile: this.state.handTiles[house].at(-1)!,
};
}
public commit_resolveCallAndRonInterruption(answers: {

View File

@@ -64,6 +64,12 @@ export type PlayerState = {
canRonTo: House | null;
};
export type KyokuResult = {
yakus: { name: string; fan: number; }[];
doraCount: number;
pointDeltas: { e: number; s: number; w: number; n: number; };
};
export class PlayerGameEngine {
/**
* このエラーが発生したときはdesyncが疑われる
@@ -145,10 +151,33 @@ export class PlayerGameEngine {
if (this.state.turn !== house) throw new PlayerGameEngine.InvalidOperationError();
}
public commit_tsumoHora(house: House) {
public commit_tsumoHora(house: House, handTiles: Tile[], tsumoTile: Tile): KyokuResult {
console.log('commit_tsumoHora', this.state.turn, house);
// TODO: ツモした人の手牌情報を貰う必要がある
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
house: house,
handTiles: handTiles,
huros: this.state.huros[house],
tsumoTile: tsumoTile,
ronTile: null,
riichi: this.state.riichis[house],
}));
const doraCount = Common.calcOwnedDoraCount(handTiles, this.state.huros[house], this.doras);
const fans = yakus.map(yaku => yaku.fan).reduce((a, b) => a + b, 0) + doraCount;
const pointDeltas = Common.calcTsumoHoraPointDeltas(house, fans);
this.state.points.e += pointDeltas.e;
this.state.points.s += pointDeltas.s;
this.state.points.w += pointDeltas.w;
this.state.points.n += pointDeltas.n;
return {
yakus: yakus.map(yaku => ({
name: yaku.name,
fan: yaku.fan,
})),
doraCount,
pointDeltas,
};
}
/**
@@ -161,23 +190,16 @@ export class PlayerGameEngine {
s: Tile[];
w: Tile[];
n: Tile[];
}) {
}): Record<House, KyokuResult | null> {
console.log('commit_ronHora', this.state.turn, callers, callee);
this.state.canRonSource = null;
const yakusMap: Record<House, { name: string; fan: number; }[]> = {
e: [] as { name: string; fan: number; }[],
s: [] as { name: string; fan: number; }[],
w: [] as { name: string; fan: number; }[],
n: [] as { name: string; fan: number; }[],
};
const doraCountsMap: Record<House, number> = {
e: 0,
s: 0,
w: 0,
n: 0,
const resultMap: Record<House, KyokuResult> = {
e: { yakus: [], doraCount: 0, pointDeltas: { e: 0, s: 0, w: 0, n: 0 } },
s: { yakus: [], doraCount: 0, pointDeltas: { e: 0, s: 0, w: 0, n: 0 } },
w: { yakus: [], doraCount: 0, pointDeltas: { e: 0, s: 0, w: 0, n: 0 } },
n: { yakus: [], doraCount: 0, pointDeltas: { e: 0, s: 0, w: 0, n: 0 } },
};
for (const house of callers) {
@@ -194,14 +216,17 @@ export class PlayerGameEngine {
const point = Common.fanToPoint(fans, house === 'e');
this.state.points[callee] -= point;
this.state.points[house] += point;
yakusMap[house] = yakus.map(yaku => ({ name: yaku.name, fan: yaku.fan }));
doraCountsMap[house] = doraCount;
console.log('yakus', house, yakus);
resultMap[house].yakus = yakus.map(yaku => ({ name: yaku.name, fan: yaku.fan }));
resultMap[house].doraCount = doraCount;
resultMap[house].pointDeltas[callee] = -point;
resultMap[house].pointDeltas[house] = point;
}
return {
yakusMap,
doraCountsMap,
e: callers.includes('e') ? resultMap.e : null,
s: callers.includes('s') ? resultMap.s : null,
w: callers.includes('w') ? resultMap.w : null,
n: callers.includes('n') ? resultMap.n : null,
};
}