mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-24 17:24:14 +02:00
wip
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user