diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 0fd9bf6bb0..a93dd5e274 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -215,11 +215,13 @@ export interface MahjongRoomEventTypes {
dahai: {
house: Mahjong.Common.House;
tile: Mahjong.Common.Tile;
+ riichi: boolean;
};
dahaiAndTsumo: {
dahaiHouse: Mahjong.Common.House;
dahaiTile: Mahjong.Common.Tile;
tsumoTile: Mahjong.Common.Tile;
+ riichi: boolean;
};
ponned: {
caller: Mahjong.Common.House;
diff --git a/packages/backend/src/core/MahjongService.ts b/packages/backend/src/core/MahjongService.ts
index 0120b36e8a..5903284b6e 100644
--- a/packages/backend/src/core/MahjongService.ts
+++ b/packages/backend/src/core/MahjongService.ts
@@ -26,7 +26,7 @@ import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
-const CALL_AND_RON_ASKING_TIMEOUT_MS = 1000 * 5; // 5sec
+const CALL_AND_RON_ASKING_TIMEOUT_MS = 1000 * 7; // 7sec
const TURN_TIMEOUT_MS = 1000 * 30; // 30sec
const NEXT_KYOKU_CONFIRMATION_TIMEOUT_MS = 1000 * 15; // 15sec
@@ -318,7 +318,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
switch (res.type) {
case 'tsumo':
this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile });
- this.next(room, engine);
+ this.waitForTurn(room, res.turn, engine);
break;
case 'ponned':
this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { caller: res.caller, callee: res.callee, tile: res.tile });
@@ -349,23 +349,6 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
}
}
- @bindThis
- private async next(room: Room, engine: Mahjong.MasterGameEngine) {
- const turn = engine.state.turn;
- if (turn == null) throw new Error('turn is null');
-
- const aiHouses = [[1, room.user1Ai], [2, room.user2Ai], [3, room.user3Ai], [4, room.user4Ai]].filter(([id, ai]) => ai).map(([id, ai]) => engine.getHouse(id));
-
- if (aiHouses.includes(turn)) {
- // TODO: ちゃんと思考するようにする
- setTimeout(() => {
- this.dahai(room, engine, turn, engine.state.handTiles[turn].at(-1));
- }, 500);
- } else {
- this.waitForTurn(room, turn, engine);
- }
- }
-
@bindThis
private async endKyoku(room: Room, engine: Mahjong.MasterGameEngine) {
const confirmation: NextKyokuConfirmation = {
@@ -469,11 +452,11 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
}
}, 1000);
- this.globalEventService.publishMahjongRoomStream(room.id, 'dahai', { house: house, tile });
+ this.globalEventService.publishMahjongRoomStream(room.id, 'dahai', { house: house, tile, riichi });
} else {
- this.globalEventService.publishMahjongRoomStream(room.id, 'dahaiAndTsumo', { dahaiHouse: house, dahaiTile: tile, tsumoTile: res.tsumoTile });
+ this.globalEventService.publishMahjongRoomStream(room.id, 'dahaiAndTsumo', { dahaiHouse: house, dahaiTile: tile, tsumoTile: res.tsumoTile, riichi });
- this.next(room, engine);
+ this.waitForTurn(room, res.next, engine);
}
}
@@ -556,7 +539,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
const res = engine.commit_tsumoHora(myHouse);
- this.globalEventService.publishMahjongRoomStream(room.id, 'tsumoHora', { });
+ this.globalEventService.publishMahjongRoomStream(room.id, 'tsumoHora', { house: myHouse, handTiles: res.handTiles, tsumoTile: res.tsumoTile });
}
@bindThis
@@ -624,6 +607,8 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
*/
@bindThis
private async waitForTurn(room: Room, house: Mahjong.Common.House, engine: Mahjong.MasterGameEngine) {
+ const aiHouses = [[1, room.user1Ai], [2, room.user2Ai], [3, room.user3Ai], [4, room.user4Ai]].filter(([id, ai]) => ai).map(([id, ai]) => engine.getHouse(id));
+
if (engine.state.riichis[house]) {
// リーチ時はアガリ牌でない限りツモ切り
const handTiles = engine.state.handTiles[house];
@@ -636,6 +621,13 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
}
}
+ if (aiHouses.includes(house)) {
+ setTimeout(() => {
+ this.dahai(room, engine, house, engine.state.handTiles[house].at(-1));
+ }, 500);
+ return;
+ }
+
const id = Math.random().toString(36).slice(2);
console.log('waitForTurn', house, id);
this.redisClient.sadd(`mahjong:gameTurnWaiting:${room.id}`, id);
diff --git a/packages/frontend/assets/mahjong/riichi.png b/packages/frontend/assets/mahjong/riichi.png
new file mode 100644
index 0000000000..2860f5dc65
Binary files /dev/null and b/packages/frontend/assets/mahjong/riichi.png differ
diff --git a/packages/frontend/assets/mahjong/ron.png b/packages/frontend/assets/mahjong/ron.png
index 6e1f889865..c508f81adb 100644
Binary files a/packages/frontend/assets/mahjong/ron.png and b/packages/frontend/assets/mahjong/ron.png differ
diff --git a/packages/frontend/assets/mahjong/tsumo.png b/packages/frontend/assets/mahjong/tsumo.png
index 14577818ab..547d01cbb7 100644
Binary files a/packages/frontend/assets/mahjong/tsumo.png and b/packages/frontend/assets/mahjong/tsumo.png differ
diff --git a/packages/frontend/src/pages/mahjong/huro.vue b/packages/frontend/src/pages/mahjong/huro.vue
new file mode 100644
index 0000000000..1eaa5971f1
--- /dev/null
+++ b/packages/frontend/src/pages/mahjong/huro.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/src/pages/mahjong/room.game.vue b/packages/frontend/src/pages/mahjong/room.game.vue
index ec9bc2a298..c44b708685 100644
--- a/packages/frontend/src/pages/mahjong/room.game.vue
+++ b/packages/frontend/src/pages/mahjong/room.game.vue
@@ -56,6 +56,22 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -107,44 +123,66 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
@@ -156,6 +194,18 @@ SPDX-License-Identifier: AGPL-3.0-only
Riichi
+
+
+
+
{{ house === 'e' ? i18n.ts._mahjong.east : house === 's' ? i18n.ts._mahjong.south : house === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}
+
+
{{ yaku.name }} ({{ yaku.fan }})
+
+
{{ res.doraCount }} dora
+
{{ res.pointDeltas.e }} / {{ res.pointDeltas.s }} / {{ res.pointDeltas.w }} / {{ res.pointDeltas.n }}
+
+
+
@@ -164,6 +214,7 @@ import { computed, onActivated, onDeactivated, onMounted, onUnmounted, reactive,
import * as Misskey from 'misskey-js';
import * as Mahjong from 'misskey-mahjong';
import XTile from './tile.vue';
+import XHuro from './huro.vue';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
@@ -202,6 +253,7 @@ const ciiSerifHouses = reactive>({ e: fals
const ponSerifHouses = reactive>({ e: false, s: false, w: false, n: false });
const kanSerifHouses = reactive>({ e: false, s: false, w: false, n: false });
const tsumoSerifHouses = reactive>({ e: false, s: false, w: false, n: false });
+const riichiSerifHouses = reactive>({ e: false, s: false, w: false, n: false });
/*
console.log(Mahjong.Common.getTilesForRiichi([
@@ -338,6 +390,19 @@ function skip() {
const iTsumoed = ref(false);
const kyokuEnded = ref(false);
+const kyokuResults = ref;
+} | null>>({
+ e: null,
+ s: null,
+ w: null,
+ n: null,
+});
function kyokuEnd() {
kyokuEnded.value = true;
@@ -359,9 +424,14 @@ function onStreamDahai(log) {
// return;
//}
- engine.value.commit_dahai(log.house, log.tile);
+ engine.value.commit_dahai(log.house, log.tile, log.riichi);
triggerRef(engine);
+ riichiSerifHouses[log.house] = log.riichi;
+ window.setTimeout(() => {
+ riichiSerifHouses[log.house] = false;
+ }, 2000);
+
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
}
@@ -402,9 +472,14 @@ function onStreamDahaiAndTsumo(log) {
playbackRate: 1,
});
- engine.value.commit_dahai(log.dahaiHouse, log.dahaiTile);
+ engine.value.commit_dahai(log.dahaiHouse, log.dahaiTile, log.riichi);
triggerRef(engine);
+ riichiSerifHouses[log.dahaiHouse] = log.riichi;
+ window.setTimeout(() => {
+ riichiSerifHouses[log.dahaiHouse] = false;
+ }, 2000);
+
window.setTimeout(() => {
engine.value.commit_tsumo(Mahjong.Common.nextHouse(log.dahaiHouse), log.tsumoTile);
triggerRef(engine);
@@ -431,9 +506,9 @@ function onStreamPonned(log) {
engine.value.commit_pon(log.caller, log.callee);
triggerRef(engine);
- ponSerifHouses[log.house] = true;
+ ponSerifHouses[log.caller] = true;
window.setTimeout(() => {
- ponSerifHouses[log.house] = false;
+ ponSerifHouses[log.caller] = false;
}, 2000);
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
@@ -442,21 +517,31 @@ function onStreamPonned(log) {
function onStreamRonned(log) {
console.log('onStreamRonned', log);
- engine.value.commit_ronHora(log.callers, log.callee, log.handTiles);
+ const res = engine.value.commit_ronHora(log.callers, log.callee, log.handTiles);
triggerRef(engine);
+ kyokuResults.value = res;
+ kyokuEnded.value = true;
+
for (const caller of log.callers) {
ronSerifHouses[caller] = true;
}
+
+ console.log('ronned', res);
}
function onStreamTsumoHora(log) {
console.log('onStreamTsumoHora', log);
+ const res = engine.value.commit_tsumoHora(log.house, log.handTiles, log.tsumoTile);
+ triggerRef(engine);
+
+ kyokuResults.value[log.house] = res;
+ kyokuEnded.value = true;
+
tsumoSerifHouses[log.house] = true;
- engine.value.commit_tsumoHora();
- triggerRef(engine);
+ console.log('tsumohora', res);
}
function restoreRoom(_room) {
@@ -516,6 +601,18 @@ onUnmounted(() => {
100% { translate: -130px; }
}
+.transition_serif_enterActive,
+.transition_serif_leaveActive {
+ transition: opacity 0.1s linear, scale 0.1s linear;
+}
+.transition_serif_enterFrom {
+ scale: 1.25;
+ opacity: 0;
+}
+.transition_serif_leaveTo {
+ opacity: 0;
+}
+
.root {
background: #3C7A43;
background-image: url('/client-assets/mahjong/bg.jpg');
@@ -535,6 +632,22 @@ onUnmounted(() => {
box-sizing: border-box;
}
+.kyokuResult {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+ max-width: 800px;
+ margin: auto;
+ box-sizing: border-box;
+ background: #0009;
+ color: #fff;
+}
+
.centerPanel {
position: absolute;
top: 0;
@@ -634,6 +747,24 @@ onUnmounted(() => {
right: 80px;
}
+.huroTilesOfToimen {
+ position: absolute;
+ top: 0;
+ left: 80px;
+}
+
+.huroTilesOfKamitya {
+ position: absolute;
+ top: 80px;
+ left: 0;
+}
+
+.huroTilesOfSimotya {
+ position: absolute;
+ top: 80px;
+ right: 0;
+}
+
.hoTilesContainer {
position: absolute;
top: 0;
@@ -766,6 +897,7 @@ onUnmounted(() => {
aspect-ratio: 0.7;
transition: translate 0.1s ease;
cursor: pointer;
+ overflow: clip;
}
.myTile:hover {
translate: 0 -10px;
diff --git a/packages/misskey-mahjong/src/common.ts b/packages/misskey-mahjong/src/common.ts
index 27c2276ad4..d696dea0fb 100644
--- a/packages/misskey-mahjong/src/common.ts
+++ b/packages/misskey-mahjong/src/common.ts
@@ -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({
diff --git a/packages/misskey-mahjong/src/engine.master.ts b/packages/misskey-mahjong/src/engine.master.ts
index 26707bfb65..452918bb08 100644
--- a/packages/misskey-mahjong/src/engine.master.ts
+++ b/packages/misskey-mahjong/src/engine.master.ts
@@ -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: {
diff --git a/packages/misskey-mahjong/src/engine.player.ts b/packages/misskey-mahjong/src/engine.player.ts
index 0f6d86b874..578555ffd4 100644
--- a/packages/misskey-mahjong/src/engine.player.ts
+++ b/packages/misskey-mahjong/src/engine.player.ts
@@ -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 {
console.log('commit_ronHora', this.state.turn, callers, callee);
this.state.canRonSource = null;
- const yakusMap: Record = {
- 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 = {
- e: 0,
- s: 0,
- w: 0,
- n: 0,
+ const resultMap: Record = {
+ 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,
};
}