This commit is contained in:
syuilo 2024-02-03 17:00:45 +09:00
parent 2dd886e285
commit 586a458c7a
12 changed files with 424 additions and 149 deletions

210
locales/index.d.ts vendored
View file

@ -9649,6 +9649,216 @@ export interface Locale extends ILocale {
* *
*/ */
"north": string; "north": string;
/**
*
*/
"dora": string;
/**
*
*/
"redDora": string;
/**
*
*/
"fan": string;
"_yakus": {
/**
*
*/
"riichi": string;
/**
*
*/
"ippatsu": string;
/**
*
*/
"tsumo": string;
/**
*
*/
"tanyao": string;
/**
*
*/
"pinfu": string;
/**
*
*/
"iipeko": string;
/**
*
*/
"field-wind-e": string;
/**
*
*/
"field-wind-s": string;
/**
*
*/
"seat-wind-e": string;
/**
*
*/
"seat-wind-s": string;
/**
* 西
*/
"seat-wind-w": string;
/**
*
*/
"seat-wind-n": string;
/**
*
*/
"white": string;
/**
*
*/
"green": string;
/**
*
*/
"red": string;
/**
*
*/
"rinshan": string;
/**
*
*/
"chankan": string;
/**
*
*/
"haitei": string;
/**
*
*/
"hotei": string;
/**
*
*/
"sanshoku-dojun": string;
/**
*
*/
"sanshoku-doko": string;
/**
*
*/
"ittsu": string;
/**
*
*/
"chanta": string;
/**
*
*/
"chitoitsu": string;
/**
*
*/
"toitoi": string;
/**
*
*/
"sananko": string;
/**
*
*/
"honroto": string;
/**
*
*/
"sankantsu": string;
/**
*
*/
"shosangen": string;
/**
*
*/
"double-riichi": string;
/**
*
*/
"honitsu": string;
/**
*
*/
"junchan": string;
/**
*
*/
"ryampeko": string;
/**
*
*/
"chinitsu": string;
/**
*
*/
"kokushi": string;
/**
* 13
*/
"kokushi-13": string;
/**
*
*/
"suanko": string;
/**
*
*/
"suanko-tanki": string;
/**
*
*/
"daisangen": string;
/**
*
*/
"tsuiso": string;
/**
*
*/
"shosushi": string;
/**
*
*/
"daisushi": string;
/**
*
*/
"ryuiso": string;
/**
*
*/
"chinroto": string;
/**
*
*/
"sukantsu": string;
/**
*
*/
"churen": string;
/**
*
*/
"pure-churen": string;
/**
*
*/
"tenho": string;
/**
*
*/
"chiho": string;
};
}; };
"_offlineScreen": { "_offlineScreen": {
/** /**

View file

@ -2571,6 +2571,59 @@ _mahjong:
south: "南" south: "南"
west: "西" west: "西"
north: "北" north: "北"
dora: "ドラ"
redDora: "赤ドラ"
fan: "飜"
_yakus:
"riichi": "立直"
"ippatsu": "一発"
"tsumo": "門前清自摸和"
"tanyao": "断么"
"pinfu": "平和"
"iipeko": "一盃口"
"field-wind-e": "東"
"field-wind-s": "南"
"seat-wind-e": "東"
"seat-wind-s": "南"
"seat-wind-w": "西"
"seat-wind-n": "北"
"white": "白"
"green": "發"
"red": "中"
"rinshan": "嶺上開花"
"chankan": "搶槓"
"haitei": "海底摸月"
"hotei": "河底撈魚"
"sanshoku-dojun": "三色同順"
"sanshoku-doko": "三色同刻"
"ittsu": "一気通貫"
"chanta": "混全帯么九"
"chitoitsu": "七対子"
"toitoi": "対々"
"sananko": "三暗刻"
"honroto": "混老頭"
"sankantsu": "三槓子"
"shosangen": "小三元"
"double-riichi": "ダブル立直"
"honitsu": "混一色"
"junchan": "清全帯么九"
"ryampeko": "ニ盃口"
"chinitsu": "清一色"
"kokushi": "国士無双"
"kokushi-13": "国士無双13面待ち"
"suanko": "四暗刻"
"suanko-tanki": "四暗刻単騎待ち"
"daisangen": "大三元"
"tsuiso": "字一色"
"shosushi": "小四喜"
"daisushi": "大四喜"
"ryuiso": "緑一色"
"chinroto": "清老頭"
"sukantsu": "四槓子"
"churen": "九蓮宝燈"
"pure-churen": "純正九連宝灯"
"tenho": "天和"
"chiho": "地和"
_offlineScreen: _offlineScreen:
title: "オフライン - サーバーに接続できません" title: "オフライン - サーバーに接続できません"

View file

@ -209,30 +209,30 @@ export interface MahjongRoomEventTypes {
room: Packed<'MahjongRoomDetailed'>; room: Packed<'MahjongRoomDetailed'>;
}; };
tsumo: { tsumo: {
house: Mahjong.Common.House; house: Mahjong.House;
tile: Mahjong.Common.Tile; tile: Mahjong.Tile;
}; };
dahai: { dahai: {
house: Mahjong.Common.House; house: Mahjong.House;
tile: Mahjong.Common.Tile; tile: Mahjong.Tile;
riichi: boolean; riichi: boolean;
}; };
dahaiAndTsumo: { dahaiAndTsumo: {
dahaiHouse: Mahjong.Common.House; dahaiHouse: Mahjong.House;
dahaiTile: Mahjong.Common.Tile; dahaiTile: Mahjong.Tile;
tsumoTile: Mahjong.Common.Tile; tsumoTile: Mahjong.Tile;
riichi: boolean; riichi: boolean;
}; };
ponned: { ponned: {
caller: Mahjong.Common.House; caller: Mahjong.House;
callee: Mahjong.Common.House; callee: Mahjong.House;
tile: Mahjong.Common.Tile; tile: Mahjong.Tile;
}; };
kanned: { kanned: {
caller: Mahjong.Common.House; caller: Mahjong.House;
callee: Mahjong.Common.House; callee: Mahjong.House;
tile: Mahjong.Common.Tile; tile: Mahjong.Tile;
rinsyan: Mahjong.Common.Tile; rinsyan: Mahjong.Tile;
}; };
ronned: { ronned: {
}; };

View file

@ -77,11 +77,11 @@ type NextKyokuConfirmation = {
user4: boolean; user4: boolean;
}; };
function getUserIdOfHouse(room: Room, engine: Mahjong.MasterGameEngine, house: Mahjong.Common.House): MiUser['id'] { function getUserIdOfHouse(room: Room, engine: Mahjong.MasterGameEngine, house: Mahjong.House): MiUser['id'] {
return engine.state.user1House === house ? room.user1Id : engine.state.user2House === house ? room.user2Id : engine.state.user3House === house ? room.user3Id : room.user4Id; return engine.state.user1House === house ? room.user1Id : engine.state.user2House === house ? room.user2Id : engine.state.user3House === house ? room.user3Id : room.user4Id;
} }
function getHouseOfUserId(room: Room, engine: Mahjong.MasterGameEngine, userId: MiUser['id']): Mahjong.Common.House { function getHouseOfUserId(room: Room, engine: Mahjong.MasterGameEngine, userId: MiUser['id']): Mahjong.House {
return userId === room.user1Id ? engine.state.user1House : userId === room.user2Id ? engine.state.user2House : userId === room.user3Id ? engine.state.user3House : engine.state.user4House; return userId === room.user1Id ? engine.state.user1House : userId === room.user2Id ? engine.state.user2House : userId === room.user3Id ? engine.state.user3House : engine.state.user4House;
} }
@ -310,7 +310,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
pon: answers.pon ?? false, pon: answers.pon ?? false,
cii: answers.cii ?? false, cii: answers.cii ?? false,
kan: answers.kan ?? false, kan: answers.kan ?? false,
ron: [...(answers.ron.e ? ['e'] : []), ...(answers.ron.s ? ['s'] : []), ...(answers.ron.w ? ['w'] : []), ...(answers.ron.n ? ['n'] : [])] as Mahjong.Common.House[], ron: [...(answers.ron.e ? ['e'] : []), ...(answers.ron.s ? ['s'] : []), ...(answers.ron.w ? ['w'] : []), ...(answers.ron.n ? ['n'] : [])] as Mahjong.House[],
}); });
room.gameState = engine.state; room.gameState = engine.state;
await this.saveRoom(room); await this.saveRoom(room);
@ -376,7 +376,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
} }
@bindThis @bindThis
private async dahai(room: Room, engine: Mahjong.MasterGameEngine, house: Mahjong.Common.House, tile: Mahjong.Common.Tile, riichi = false) { private async dahai(room: Room, engine: Mahjong.MasterGameEngine, house: Mahjong.House, tile: Mahjong.Tile, riichi = false) {
const res = engine.commit_dahai(house, tile, riichi); const res = engine.commit_dahai(house, tile, riichi);
room.gameState = engine.state; room.gameState = engine.state;
await this.saveRoom(room); await this.saveRoom(room);
@ -482,7 +482,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
const room = await this.getRoom(roomId); const room = await this.getRoom(roomId);
if (room == null) return; if (room == null) return;
if (room.gameState == null) return; if (room.gameState == null) return;
if (!Mahjong.Common.isTile(tile)) return; if (!Mahjong.isTile(tile)) return;
const engine = new Mahjong.MasterGameEngine(room.gameState); const engine = new Mahjong.MasterGameEngine(room.gameState);
const myHouse = getHouseOfUserId(room, engine, user.id); const myHouse = getHouseOfUserId(room, engine, user.id);
@ -606,13 +606,13 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
* @param engine * @param engine
*/ */
@bindThis @bindThis
private async waitForTurn(room: Room, house: Mahjong.Common.House, engine: Mahjong.MasterGameEngine) { private async waitForTurn(room: Room, house: Mahjong.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)); 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]) { if (engine.state.riichis[house]) {
// リーチ時はアガリ牌でない限りツモ切り // リーチ時はアガリ牌でない限りツモ切り
const handTiles = engine.state.handTiles[house]; const handTiles = engine.state.handTiles[house];
const horaSets = Mahjong.Common.getHoraSets(handTiles); const horaSets = Mahjong.getHoraSets(handTiles);
if (horaSets.length === 0) { if (horaSets.length === 0) {
setTimeout(() => { setTimeout(() => {
this.dahai(room, engine, house, handTiles.at(-1)); this.dahai(room, engine, house, handTiles.at(-1));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -34,9 +34,9 @@ import * as Mahjong from 'misskey-mahjong';
import XTile from './tile.vue'; import XTile from './tile.vue';
const props = defineProps<{ const props = defineProps<{
huro: Mahjong.Common.Huro; huro: Mahjong.Huro;
variation: string; variation: string;
doras: Mahjong.Common.Tile[]; doras: Mahjong.Tile[];
}>(); }>();
</script> </script>

View file

@ -10,20 +10,20 @@ SPDX-License-Identifier: AGPL-3.0-only
<div style="text-align: center;"> <div style="text-align: center;">
<div :class="$style.centerPanelTickerToi"> <div :class="$style.centerPanelTickerToi">
<div style="position: absolute; left: 10px; bottom: 5px;"> <div style="position: absolute; left: 10px; bottom: 5px;">
<span :class="$style.centerPanelHouse">{{ Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse)) === 'e' ? i18n.ts._mahjong.east : Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse)) === 's' ? i18n.ts._mahjong.south : Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse)) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span> <span :class="$style.centerPanelHouse">{{ Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse)) === 'e' ? i18n.ts._mahjong.east : Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse)) === 's' ? i18n.ts._mahjong.south : Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse)) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span>
<span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))] }}</span> <span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))] }}</span>
</div> </div>
</div> </div>
<div :class="$style.centerPanelTickerKami"> <div :class="$style.centerPanelTickerKami">
<div style="position: absolute; left: 10px; bottom: 5px;"> <div style="position: absolute; left: 10px; bottom: 5px;">
<span :class="$style.centerPanelHouse">{{ Mahjong.Common.prevHouse(engine.myHouse) === 'e' ? i18n.ts._mahjong.east : Mahjong.Common.prevHouse(engine.myHouse) === 's' ? i18n.ts._mahjong.south : Mahjong.Common.prevHouse(engine.myHouse) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span> <span :class="$style.centerPanelHouse">{{ Mahjong.prevHouse(engine.myHouse) === 'e' ? i18n.ts._mahjong.east : Mahjong.prevHouse(engine.myHouse) === 's' ? i18n.ts._mahjong.south : Mahjong.prevHouse(engine.myHouse) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span>
<span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.Common.prevHouse(engine.myHouse)] }}</span> <span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.prevHouse(engine.myHouse)] }}</span>
</div> </div>
</div> </div>
<div :class="$style.centerPanelTickerSimo"> <div :class="$style.centerPanelTickerSimo">
<div style="position: absolute; left: 10px; bottom: 5px;"> <div style="position: absolute; left: 10px; bottom: 5px;">
<span :class="$style.centerPanelHouse">{{ Mahjong.Common.nextHouse(engine.myHouse) === 'e' ? i18n.ts._mahjong.east : Mahjong.Common.nextHouse(engine.myHouse) === 's' ? i18n.ts._mahjong.south : Mahjong.Common.nextHouse(engine.myHouse) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span> <span :class="$style.centerPanelHouse">{{ Mahjong.nextHouse(engine.myHouse) === 'e' ? i18n.ts._mahjong.east : Mahjong.nextHouse(engine.myHouse) === 's' ? i18n.ts._mahjong.south : Mahjong.nextHouse(engine.myHouse) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span>
<span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.Common.nextHouse(engine.myHouse)] }}</span> <span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.nextHouse(engine.myHouse)] }}</span>
</div> </div>
</div> </div>
<div :class="$style.centerPanelTickerMe"> <div :class="$style.centerPanelTickerMe">
@ -39,33 +39,33 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div :class="$style.handTilesOfToimen"> <div :class="$style.handTilesOfToimen">
<div v-for="tile in engine.state.handTiles[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" style="display: inline-block;"> <div v-for="tile in engine.state.handTiles[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" style="display: inline-block;">
<img :src="`/client-assets/mahjong/tile-back.png`" :class="$style.handTileImgOfToimen"/> <img :src="`/client-assets/mahjong/tile-back.png`" :class="$style.handTileImgOfToimen"/>
</div> </div>
</div> </div>
<div :class="$style.handTilesOfKamitya"> <div :class="$style.handTilesOfKamitya">
<div v-for="tile in engine.state.handTiles[Mahjong.Common.prevHouse(engine.myHouse)]" :class="$style.sideTile"> <div v-for="tile in engine.state.handTiles[Mahjong.prevHouse(engine.myHouse)]" :class="$style.sideTile">
<img :src="`/client-assets/mahjong/tile-side.png`" style="display: inline-block; width: 32px;"/> <img :src="`/client-assets/mahjong/tile-side.png`" style="display: inline-block; width: 32px;"/>
</div> </div>
</div> </div>
<div :class="$style.handTilesOfSimotya"> <div :class="$style.handTilesOfSimotya">
<div v-for="tile in engine.state.handTiles[Mahjong.Common.nextHouse(engine.myHouse)]" :class="$style.sideTile"> <div v-for="tile in engine.state.handTiles[Mahjong.nextHouse(engine.myHouse)]" :class="$style.sideTile">
<img :src="`/client-assets/mahjong/tile-side.png`" style="display: inline-block; width: 32px; scale: -1 1;"/> <img :src="`/client-assets/mahjong/tile-side.png`" style="display: inline-block; width: 32px; scale: -1 1;"/>
</div> </div>
</div> </div>
<div :class="$style.huroTilesOfToimen"> <div :class="$style.huroTilesOfToimen">
<XHuro v-for="huro in engine.state.huros[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :huro="huro" :variation="1" :doras="engine.doras"/> <XHuro v-for="huro in engine.state.huros[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :huro="huro" :variation="1" :doras="engine.doras"/>
</div> </div>
<div :class="$style.huroTilesOfKamitya"> <div :class="$style.huroTilesOfKamitya">
<XHuro v-for="huro in engine.state.huros[Mahjong.Common.prevHouse(engine.myHouse)]" :huro="huro" :variation="1" :doras="engine.doras"/> <XHuro v-for="huro in engine.state.huros[Mahjong.prevHouse(engine.myHouse)]" :huro="huro" :variation="1" :doras="engine.doras"/>
</div> </div>
<div :class="$style.huroTilesOfSimotya"> <div :class="$style.huroTilesOfSimotya">
<XHuro v-for="huro in engine.state.huros[Mahjong.Common.nextHouse(engine.myHouse)]" :huro="huro" :variation="1" :doras="engine.doras"/> <XHuro v-for="huro in engine.state.huros[Mahjong.nextHouse(engine.myHouse)]" :huro="huro" :variation="1" :doras="engine.doras"/>
</div> </div>
<div :class="$style.huroTilesOfMe"> <div :class="$style.huroTilesOfMe">
@ -75,21 +75,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.hoTilesContainer"> <div :class="$style.hoTilesContainer">
<div :class="$style.hoTilesContainerOfToimen"> <div :class="$style.hoTilesContainerOfToimen">
<div :class="$style.hoTilesOfToimen"> <div :class="$style.hoTilesOfToimen">
<div v-for="(tile, i) in engine.state.hoTiles[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :class="$style.hoTile" :style="{ zIndex: engine.state.hoTiles[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))].length - i }"> <div v-for="(tile, i) in engine.state.hoTiles[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :class="$style.hoTile" :style="{ zIndex: engine.state.hoTiles[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))].length - i }">
<XTile :tile="tile" variation="2" :doras="engine.doras"/> <XTile :tile="tile" variation="2" :doras="engine.doras"/>
</div> </div>
</div> </div>
</div> </div>
<div :class="$style.hoTilesContainerOfKamitya"> <div :class="$style.hoTilesContainerOfKamitya">
<div :class="$style.hoTilesOfKamitya"> <div :class="$style.hoTilesOfKamitya">
<div v-for="tile in engine.state.hoTiles[Mahjong.Common.prevHouse(engine.myHouse)]" :class="$style.hoTile"> <div v-for="tile in engine.state.hoTiles[Mahjong.prevHouse(engine.myHouse)]" :class="$style.hoTile">
<XTile :tile="tile" variation="4" :doras="engine.doras"/> <XTile :tile="tile" variation="4" :doras="engine.doras"/>
</div> </div>
</div> </div>
</div> </div>
<div :class="$style.hoTilesContainerOfSimotya"> <div :class="$style.hoTilesContainerOfSimotya">
<div :class="$style.hoTilesOfSimotya"> <div :class="$style.hoTilesOfSimotya">
<div v-for="(tile, i) in engine.state.hoTiles[Mahjong.Common.nextHouse(engine.myHouse)]" :class="$style.hoTile" :style="{ zIndex: engine.state.hoTiles[Mahjong.Common.nextHouse(engine.myHouse)].length - i }"> <div v-for="(tile, i) in engine.state.hoTiles[Mahjong.nextHouse(engine.myHouse)]" :class="$style.hoTile" :style="{ zIndex: engine.state.hoTiles[Mahjong.nextHouse(engine.myHouse)].length - i }">
<XTile :tile="tile" variation="5" :doras="engine.doras"/> <XTile :tile="tile" variation="5" :doras="engine.doras"/>
</div> </div>
</div> </div>
@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.handTilesOfMe"> <div :class="$style.handTilesOfMe">
<div <div
v-for="tile in Mahjong.Common.sortTiles((isMyTurn && iTsumoed) ? engine.myHandTiles.slice(0, engine.myHandTiles.length - 1) : engine.myHandTiles)" v-for="tile in Mahjong.sortTiles((isMyTurn && iTsumoed) ? engine.myHandTiles.slice(0, engine.myHandTiles.length - 1) : engine.myHandTiles)"
:class="[$style.myTile, { [$style.myTileNonSelectable]: selectableTiles != null && !selectableTiles.includes(tile), [$style.myTileDora]: engine.doras.includes(tile) }]" :class="[$style.myTile, { [$style.myTileNonSelectable]: selectableTiles != null && !selectableTiles.includes(tile), [$style.myTileDora]: engine.doras.includes(tile) }]"
@click="chooseTile(tile, $event)" @click="chooseTile(tile, $event)"
> >
@ -131,12 +131,12 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="$style.transition_serif_enterFrom" :enterFromClass="$style.transition_serif_enterFrom"
:leaveToClass="$style.transition_serif_leaveTo" :leaveToClass="$style.transition_serif_leaveTo"
> >
<img v-if="ronSerifHouses[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/> <img v-if="ronSerifHouses[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
<img v-else-if="ciiSerifHouses[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/cii.png`" style="display: block; width: 100%;"/> <img v-else-if="ciiSerifHouses[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/cii.png`" style="display: block; width: 100%;"/>
<img v-else-if="ponSerifHouses[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/pon.png`" style="display: block; width: 100%;"/> <img v-else-if="ponSerifHouses[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/pon.png`" style="display: block; width: 100%;"/>
<img v-else-if="kanSerifHouses[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/kan.png`" style="display: block; width: 100%;"/> <img v-else-if="kanSerifHouses[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/kan.png`" style="display: block; width: 100%;"/>
<img v-else-if="tsumoSerifHouses[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/> <img v-else-if="tsumoSerifHouses[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/>
<img v-else-if="riichiSerifHouses[Mahjong.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/riichi.png`" style="display: block; width: 100%;"/> <img v-else-if="riichiSerifHouses[Mahjong.prevHouse(Mahjong.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/riichi.png`" style="display: block; width: 100%;"/>
</Transition> </Transition>
</div> </div>
<div :class="$style.serifContainerOfKamitya"> <div :class="$style.serifContainerOfKamitya">
@ -146,12 +146,12 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="$style.transition_serif_enterFrom" :enterFromClass="$style.transition_serif_enterFrom"
:leaveToClass="$style.transition_serif_leaveTo" :leaveToClass="$style.transition_serif_leaveTo"
> >
<img v-if="ronSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/> <img v-if="ronSerifHouses[Mahjong.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
<img v-else-if="ciiSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/cii.png`" style="display: block; width: 100%;"/> <img v-else-if="ciiSerifHouses[Mahjong.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/cii.png`" style="display: block; width: 100%;"/>
<img v-else-if="ponSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/pon.png`" style="display: block; width: 100%;"/> <img v-else-if="ponSerifHouses[Mahjong.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/pon.png`" style="display: block; width: 100%;"/>
<img v-else-if="kanSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/kan.png`" style="display: block; width: 100%;"/> <img v-else-if="kanSerifHouses[Mahjong.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/kan.png`" style="display: block; width: 100%;"/>
<img v-else-if="tsumoSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/> <img v-else-if="tsumoSerifHouses[Mahjong.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/>
<img v-else-if="riichiSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/riichi.png`" style="display: block; width: 100%;"/> <img v-else-if="riichiSerifHouses[Mahjong.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/riichi.png`" style="display: block; width: 100%;"/>
</Transition> </Transition>
</div> </div>
<div :class="$style.serifContainerOfSimotya"> <div :class="$style.serifContainerOfSimotya">
@ -161,12 +161,12 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="$style.transition_serif_enterFrom" :enterFromClass="$style.transition_serif_enterFrom"
:leaveToClass="$style.transition_serif_leaveTo" :leaveToClass="$style.transition_serif_leaveTo"
> >
<img v-if="ronSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/> <img v-if="ronSerifHouses[Mahjong.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
<img v-else-if="ciiSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/cii.png`" style="display: block; width: 100%;"/> <img v-else-if="ciiSerifHouses[Mahjong.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/cii.png`" style="display: block; width: 100%;"/>
<img v-else-if="ponSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/pon.png`" style="display: block; width: 100%;"/> <img v-else-if="ponSerifHouses[Mahjong.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/pon.png`" style="display: block; width: 100%;"/>
<img v-else-if="kanSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/kan.png`" style="display: block; width: 100%;"/> <img v-else-if="kanSerifHouses[Mahjong.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/kan.png`" style="display: block; width: 100%;"/>
<img v-else-if="tsumoSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/> <img v-else-if="tsumoSerifHouses[Mahjong.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/>
<img v-else-if="riichiSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/riichi.png`" style="display: block; width: 100%;"/> <img v-else-if="riichiSerifHouses[Mahjong.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/riichi.png`" style="display: block; width: 100%;"/>
</Transition> </Transition>
</div> </div>
<div :class="$style.serifContainerOfMe"> <div :class="$style.serifContainerOfMe">
@ -194,14 +194,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="isMyTurn && engine.canRiichi()" primary @click="riichi">Riichi</MkButton> <MkButton v-if="isMyTurn && engine.canRiichi()" primary @click="riichi">Riichi</MkButton>
</div> </div>
</div> </div>
<div v-if="kyokuEnded" :class="$style.kyokuResult"> <div v-if="showKyokuResults" :class="$style.kyokuResult">
<div v-for="(res, house) in kyokuResults" :key="house"> <div v-for="(res, house) in kyokuResults" :key="house">
<div v-if="res != null"> <div v-if="res != null">
<div>{{ house === 'e' ? i18n.ts._mahjong.east : house === 's' ? i18n.ts._mahjong.south : house === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</div> <div>{{ house === 'e' ? i18n.ts._mahjong.east : house === 's' ? i18n.ts._mahjong.south : house === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</div>
<div v-for="yaku in res.yakus"> <div v-for="yaku in res.yakus">
<div>{{ yaku.name }} ({{ yaku.fan }})</div> <div>{{ i18n.ts._mahjong._yakus[yaku.name] }} {{ yaku.fan }}{{ i18n.ts._mahjong.fan }}</div>
</div> </div>
<div>{{ res.doraCount }} dora</div> <div>{{ i18n.ts._mahjong.dora }} {{ res.doraCount }}{{ i18n.ts._mahjong.fan }}</div>
<div>{{ res.pointDeltas.e }} / {{ res.pointDeltas.s }} / {{ res.pointDeltas.w }} / {{ res.pointDeltas.n }}</div> <div>{{ res.pointDeltas.e }} / {{ res.pointDeltas.s }} / {{ res.pointDeltas.w }} / {{ res.pointDeltas.n }}</div>
</div> </div>
</div> </div>
@ -244,19 +244,19 @@ const isMyTurn = computed(() => {
}); });
const canHora = computed(() => { const canHora = computed(() => {
return Mahjong.Common.getHoraSets(engine.value.myHandTiles).length > 0; return Mahjong.getHoraSets(engine.value.myHandTiles).length > 0;
}); });
const selectableTiles = ref<Mahjong.Common.Tile[] | null>(null); const selectableTiles = ref<Mahjong.Tile[] | null>(null);
const ronSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: false, s: false, w: false, n: false }); const ronSerifHouses = reactive<Record<Mahjong.House, boolean>>({ e: false, s: false, w: false, n: false });
const ciiSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: false, s: false, w: false, n: false }); const ciiSerifHouses = reactive<Record<Mahjong.House, boolean>>({ e: false, s: false, w: false, n: false });
const ponSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: false, s: false, w: false, n: false }); const ponSerifHouses = reactive<Record<Mahjong.House, boolean>>({ e: false, s: false, w: false, n: false });
const kanSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: false, s: false, w: false, n: false }); const kanSerifHouses = reactive<Record<Mahjong.House, boolean>>({ e: false, s: false, w: false, n: false });
const tsumoSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: false, s: false, w: false, n: false }); const tsumoSerifHouses = reactive<Record<Mahjong.House, boolean>>({ e: false, s: false, w: false, n: false });
const riichiSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: false, s: false, w: false, n: false }); const riichiSerifHouses = reactive<Record<Mahjong.House, boolean>>({ e: false, s: false, w: false, n: false });
/* /*
console.log(Mahjong.Common.getTilesForRiichi([ console.log(Mahjong.getTilesForRiichi([
'm1', 'm1',
'm2', 'm2',
'm2', 'm2',
@ -274,7 +274,7 @@ console.log(Mahjong.Common.getTilesForRiichi([
])); ]));
*/ */
/* /*
console.log(Mahjong.Common.getHoraSets([ console.log(Mahjong.getHoraSets([
'm3', 'm3',
'm3', 'm3',
'm4', 'm4',
@ -335,7 +335,7 @@ if (!props.room.isEnded) {
let riichiSelect = false; let riichiSelect = false;
function chooseTile(tile: Mahjong.Common.Tile, ev: MouseEvent) { function chooseTile(tile: Mahjong.Tile, ev: MouseEvent) {
if (!isMyTurn.value) return; if (!isMyTurn.value) return;
iTsumoed.value = false; iTsumoed.value = false;
@ -353,8 +353,8 @@ function riichi() {
if (!isMyTurn.value) return; if (!isMyTurn.value) return;
riichiSelect = true; riichiSelect = true;
selectableTiles.value = Mahjong.Common.getTilesForRiichi(engine.value.myHandTiles); selectableTiles.value = Mahjong.getTilesForRiichi(engine.value.myHandTiles);
console.log(Mahjong.Common.getTilesForRiichi(engine.value.myHandTiles)); console.log(Mahjong.getTilesForRiichi(engine.value.myHandTiles));
} }
function kakan() { function kakan() {
@ -389,14 +389,14 @@ function skip() {
} }
const iTsumoed = ref(false); const iTsumoed = ref(false);
const kyokuEnded = ref(false); const showKyokuResults = ref(false);
const kyokuResults = ref<Record<Mahjong.Common.House, { const kyokuResults = ref<Record<Mahjong.House, {
yakus: { yakus: {
name: string; name: string;
fan: number; fan: number;
}[]; }[];
doraCount: number; doraCount: number;
pointDeltas: Record<Mahjong.Common.House, number>; pointDeltas: Record<Mahjong.House, number>;
} | null>>({ } | null>>({
e: null, e: null,
s: null, s: null,
@ -405,7 +405,7 @@ const kyokuResults = ref<Record<Mahjong.Common.House, {
}); });
function kyokuEnd() { function kyokuEnd() {
kyokuEnded.value = true; showKyokuResults.value = true;
} }
function onStreamDahai(log) { function onStreamDahai(log) {
@ -481,10 +481,10 @@ function onStreamDahaiAndTsumo(log) {
}, 2000); }, 2000);
window.setTimeout(() => { window.setTimeout(() => {
engine.value.commit_tsumo(Mahjong.Common.nextHouse(log.dahaiHouse), log.tsumoTile); engine.value.commit_tsumo(Mahjong.nextHouse(log.dahaiHouse), log.tsumoTile);
triggerRef(engine); triggerRef(engine);
if (Mahjong.Common.nextHouse(log.dahaiHouse) === engine.value.myHouse) { if (Mahjong.nextHouse(log.dahaiHouse) === engine.value.myHouse) {
iTsumoed.value = true; iTsumoed.value = true;
} }
}, 100); }, 100);
@ -521,7 +521,10 @@ function onStreamRonned(log) {
triggerRef(engine); triggerRef(engine);
kyokuResults.value = res; kyokuResults.value = res;
kyokuEnded.value = true;
window.setTimeout(() => {
showKyokuResults.value = true;
}, 1500);
for (const caller of log.callers) { for (const caller of log.callers) {
ronSerifHouses[caller] = true; ronSerifHouses[caller] = true;
@ -537,7 +540,10 @@ function onStreamTsumoHora(log) {
triggerRef(engine); triggerRef(engine);
kyokuResults.value[log.house] = res; kyokuResults.value[log.house] = res;
kyokuEnded.value = true;
window.setTimeout(() => {
showKyokuResults.value = true;
}, 1500);
tsumoSerifHouses[log.house] = true; tsumoSerifHouses[log.house] = true;

View file

@ -15,9 +15,9 @@ import { computed } from 'vue';
import * as Mahjong from 'misskey-mahjong'; import * as Mahjong from 'misskey-mahjong';
const props = defineProps<{ const props = defineProps<{
tile: Mahjong.Common.Tile; tile: Mahjong.Tile;
variation: string; variation: string;
doras: Mahjong.Common.Tile[]; doras: Mahjong.Tile[];
}>(); }>();
const isDora = computed(() => props.doras.includes(props.tile)); const isDora = computed(() => props.doras.includes(props.tile));

View file

@ -101,59 +101,6 @@ export const NEXT_TILE_FOR_DORA_MAP: Record<Tile, Tile> = {
chun: 'haku', chun: 'haku',
}; };
export const yakuNames = [
'riichi',
'ippatsu',
'tsumo',
'tanyao',
'pinfu',
'iipeko',
'field-wind',
'seat-wind',
'white',
'green',
'red',
'rinshan',
'chankan',
'haitei',
'hotei',
'sanshoku-dojun',
'sanshoku-doko',
'ittsu',
'chanta',
'chitoitsu',
'toitoi',
'sananko',
'honroto',
'sankantsu',
'shosangen',
'double-riichi',
'honitsu',
'junchan',
'ryampeko',
'chinitsu',
'dora',
'red-dora',
] as const;
export const yakumanNames = [
'kokushi',
'kokushi-13',
'suanko',
'suanko-tanki',
'daisangen',
'tsuiso',
'shosushi',
'daisushi',
'ryuiso',
'chinroto',
'sukantsu',
'churen',
'pure-churen',
'tenho',
'chiho',
] as const;
type EnvForCalcYaku = { type EnvForCalcYaku = {
house: House; house: House;
@ -260,15 +207,80 @@ export const YAKU_DEFINITIONS = [{
); );
}, },
}, { }, {
name: 'seat-wind', name: 'field-wind-e',
fan: 1, fan: 1,
calc: (state: EnvForCalcYaku) => { calc: (state: EnvForCalcYaku) => {
return ( return state.fieldWind === 'e' && (
(state.handTiles.filter(t => t === state.house).length >= 3) || (state.handTiles.filter(t => t === 'e').length >= 3) ||
(state.huros.filter(huro => (state.huros.filter(huro =>
huro.type === 'pon' ? huro.tile === state.house : huro.type === 'pon' ? huro.tile === 'e' :
huro.type === 'ankan' ? huro.tile === state.house : huro.type === 'ankan' ? huro.tile === 'e' :
huro.type === 'minkan' ? huro.tile === state.house : huro.type === 'minkan' ? huro.tile === 'e' :
false).length >= 3)
);
},
}, {
name: 'field-wind-s',
fan: 1,
calc: (state: EnvForCalcYaku) => {
return state.fieldWind === 's' && (
(state.handTiles.filter(t => t === 's').length >= 3) ||
(state.huros.filter(huro =>
huro.type === 'pon' ? huro.tile === 's' :
huro.type === 'ankan' ? huro.tile === 's' :
huro.type === 'minkan' ? huro.tile === 's' :
false).length >= 3)
);
},
}, {
name: 'seat-wind-e',
fan: 1,
calc: (state: EnvForCalcYaku) => {
return state.house === 'e' && (
(state.handTiles.filter(t => t === 'e').length >= 3) ||
(state.huros.filter(huro =>
huro.type === 'pon' ? huro.tile === 'e' :
huro.type === 'ankan' ? huro.tile === 'e' :
huro.type === 'minkan' ? huro.tile === 'e' :
false).length >= 3)
);
},
}, {
name: 'seat-wind-s',
fan: 1,
calc: (state: EnvForCalcYaku) => {
return state.house === 's' && (
(state.handTiles.filter(t => t === 's').length >= 3) ||
(state.huros.filter(huro =>
huro.type === 'pon' ? huro.tile === 's' :
huro.type === 'ankan' ? huro.tile === 's' :
huro.type === 'minkan' ? huro.tile === 's' :
false).length >= 3)
);
},
}, {
name: 'seat-wind-w',
fan: 1,
calc: (state: EnvForCalcYaku) => {
return state.house === 'w' && (
(state.handTiles.filter(t => t === 'w').length >= 3) ||
(state.huros.filter(huro =>
huro.type === 'pon' ? huro.tile === 'w' :
huro.type === 'ankan' ? huro.tile === 'w' :
huro.type === 'minkan' ? huro.tile === 'w' :
false).length >= 3)
);
},
}, {
name: 'seat-wind-n',
fan: 1,
calc: (state: EnvForCalcYaku) => {
return state.house === 'n' && (
(state.handTiles.filter(t => t === 'n').length >= 3) ||
(state.huros.filter(huro =>
huro.type === 'pon' ? huro.tile === 'n' :
huro.type === 'ankan' ? huro.tile === 'n' :
huro.type === 'minkan' ? huro.tile === 'n' :
false).length >= 3) false).length >= 3)
); );
}, },
@ -450,12 +462,6 @@ export const SHUNTU_PATTERNS: [Tile, Tile, Tile][] = [
['s7', 's8', 's9'], ['s7', 's8', 's9'],
]; ];
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][] { function extractShuntsus(tiles: Tile[]): [Tile, Tile, Tile][] {
const tempTiles = [...tiles]; const tempTiles = [...tiles];

View file

@ -4,7 +4,7 @@
*/ */
export * as Serializer from './serializer.js'; export * as Serializer from './serializer.js';
export * as Common from './common.js'; export * from './common.js';
export { MasterGameEngine } from './engine.master.js'; export { MasterGameEngine } from './engine.master.js';
export type { MasterState } from './engine.master.js'; export type { MasterState } from './engine.master.js';