wip
This commit is contained in:
parent
9ea29fe84c
commit
7cdaa10d46
|
@ -428,8 +428,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
if (aiHouses.includes(res.canPonHouse)) {
|
if (aiHouses.includes(res.canPonHouse)) {
|
||||||
// TODO: ちゃんと思考するようにする
|
// TODO: ちゃんと思考するようにする
|
||||||
//answers.pon = Math.random() < 0.25;
|
answers.pon = Math.random() < 0.25;
|
||||||
answers.pon = false;
|
|
||||||
}
|
}
|
||||||
if (aiHouses.includes(res.canCiiHouse)) {
|
if (aiHouses.includes(res.canCiiHouse)) {
|
||||||
// TODO: ちゃんと思考するようにする
|
// TODO: ちゃんと思考するようにする
|
||||||
|
@ -438,8 +437,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
}
|
}
|
||||||
if (aiHouses.includes(res.canKanHouse)) {
|
if (aiHouses.includes(res.canKanHouse)) {
|
||||||
// TODO: ちゃんと思考するようにする
|
// TODO: ちゃんと思考するようにする
|
||||||
//answers.kan = Math.random() < 0.25;
|
answers.kan = Math.random() < 0.25;
|
||||||
answers.kan = false;
|
|
||||||
}
|
}
|
||||||
for (const h of res.canRonHouses) {
|
for (const h of res.canRonHouses) {
|
||||||
if (aiHouses.includes(h)) {
|
if (aiHouses.includes(h)) {
|
||||||
|
@ -501,7 +499,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.Utils.isTile(tile)) return;
|
if (!Mahjong.Common.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);
|
||||||
|
@ -629,7 +627,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
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.Utils.getHoraSets(handTiles);
|
const horaSets = Mahjong.Common.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: 318 KiB After Width: | Height: | Size: 177 KiB |
|
@ -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.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)) === 'e' ? i18n.ts._mahjong.east : Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)) === 's' ? i18n.ts._mahjong.south : Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span>
|
<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.centerPanelPoint">{{ engine.state.points[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))] }}</span>
|
<span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.Common.prevHouse(Mahjong.Common.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.Utils.prevHouse(engine.myHouse) === 'e' ? i18n.ts._mahjong.east : Mahjong.Utils.prevHouse(engine.myHouse) === 's' ? i18n.ts._mahjong.south : Mahjong.Utils.prevHouse(engine.myHouse) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span>
|
<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.centerPanelPoint">{{ engine.state.points[Mahjong.Utils.prevHouse(engine.myHouse)] }}</span>
|
<span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.Common.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.Utils.nextHouse(engine.myHouse) === 'e' ? i18n.ts._mahjong.east : Mahjong.Utils.nextHouse(engine.myHouse) === 's' ? i18n.ts._mahjong.south : Mahjong.Utils.nextHouse(engine.myHouse) === 'w' ? i18n.ts._mahjong.west : i18n.ts._mahjong.north }}</span>
|
<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.centerPanelPoint">{{ engine.state.points[Mahjong.Utils.nextHouse(engine.myHouse)] }}</span>
|
<span :class="$style.centerPanelPoint">{{ engine.state.points[Mahjong.Common.nextHouse(engine.myHouse)] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.centerPanelTickerMe">
|
<div :class="$style.centerPanelTickerMe">
|
||||||
|
@ -32,23 +32,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span :class="$style.centerPanelPoint">{{ engine.state.points[engine.myHouse] }}</span>
|
<span :class="$style.centerPanelPoint">{{ engine.state.points[engine.myHouse] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>{{ engine.state.tilesCount }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="$style.handTilesOfToimen">
|
<div :class="$style.handTilesOfToimen">
|
||||||
<div v-for="tile in engine.state.handTiles[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" style="display: inline-block;">
|
<div v-for="tile in engine.state.handTiles[Mahjong.Common.prevHouse(Mahjong.Common.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.Utils.prevHouse(engine.myHouse)]" :class="$style.sideTile">
|
<div v-for="tile in engine.state.handTiles[Mahjong.Common.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.Utils.nextHouse(engine.myHouse)]" :class="$style.sideTile">
|
<div v-for="tile in engine.state.handTiles[Mahjong.Common.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>
|
||||||
|
@ -56,21 +59,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.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :class="$style.hoTile" :style="{ zIndex: engine.state.hoTiles[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))].length - i }">
|
<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 }">
|
||||||
<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.Utils.prevHouse(engine.myHouse)]" :class="$style.hoTile">
|
<div v-for="tile in engine.state.hoTiles[Mahjong.Common.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.Utils.nextHouse(engine.myHouse)]" :class="$style.hoTile" :style="{ zIndex: engine.state.hoTiles[Mahjong.Utils.nextHouse(engine.myHouse)].length - i }">
|
<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 }">
|
||||||
<XTile :tile="tile" variation="5" :doras="engine.doras"/>
|
<XTile :tile="tile" variation="5" :doras="engine.doras"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div :class="$style.handTilesOfMe">
|
<div :class="$style.handTilesOfMe">
|
||||||
<div
|
<div
|
||||||
v-for="tile in Mahjong.Utils.sortTiles((isMyTurn && iTsumoed) ? engine.myHandTiles.slice(0, engine.myHandTiles.length - 1) : engine.myHandTiles)"
|
v-for="tile in Mahjong.Common.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)"
|
||||||
>
|
>
|
||||||
|
@ -116,25 +119,25 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div :class="$style.serifContainer">
|
<div :class="$style.serifContainer">
|
||||||
<div :class="$style.serifContainerOfToimen">
|
<div :class="$style.serifContainerOfToimen">
|
||||||
<img v-if="ronSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
<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-else-if="ciiSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/cii.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="ponSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/pon.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="kanSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/kan.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="tsumoSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/tsumo.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%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.serifContainerOfKamitya">
|
<div :class="$style.serifContainerOfKamitya">
|
||||||
<img v-if="ronSerifHouses[Mahjong.Utils.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
<img v-if="ronSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
||||||
<img v-else-if="ciiSerifHouses[Mahjong.Utils.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/cii.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="ponSerifHouses[Mahjong.Utils.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/pon.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="kanSerifHouses[Mahjong.Utils.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/kan.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="tsumoSerifHouses[Mahjong.Utils.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.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%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.serifContainerOfSimotya">
|
<div :class="$style.serifContainerOfSimotya">
|
||||||
<img v-if="ronSerifHouses[Mahjong.Utils.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
<img v-if="ronSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
||||||
<img v-else-if="ciiSerifHouses[Mahjong.Utils.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/cii.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="ponSerifHouses[Mahjong.Utils.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/pon.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="kanSerifHouses[Mahjong.Utils.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/kan.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="tsumoSerifHouses[Mahjong.Utils.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.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%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.serifContainerOfMe">
|
<div :class="$style.serifContainerOfMe">
|
||||||
<img v-if="ronSerifHouses[engine.myHouse]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
<img v-if="ronSerifHouses[engine.myHouse]" :src="`/client-assets/mahjong/ron.png`" style="display: block; width: 100%;"/>
|
||||||
|
@ -190,7 +193,7 @@ const isMyTurn = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const canHora = computed(() => {
|
const canHora = computed(() => {
|
||||||
return Mahjong.Utils.getHoraSets(engine.value.myHandTiles).length > 0;
|
return Mahjong.Common.getHoraSets(engine.value.myHandTiles).length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectableTiles = ref<Mahjong.Common.Tile[] | null>(null);
|
const selectableTiles = ref<Mahjong.Common.Tile[] | null>(null);
|
||||||
|
@ -201,7 +204,7 @@ const kanSerifHouses = reactive<Record<Mahjong.Common.House, boolean>>({ e: fals
|
||||||
const tsumoSerifHouses = reactive<Record<Mahjong.Common.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 });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
console.log(Mahjong.Utils.getTilesForRiichi([
|
console.log(Mahjong.Common.getTilesForRiichi([
|
||||||
'm1',
|
'm1',
|
||||||
'm2',
|
'm2',
|
||||||
'm2',
|
'm2',
|
||||||
|
@ -219,7 +222,7 @@ console.log(Mahjong.Utils.getTilesForRiichi([
|
||||||
]));
|
]));
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
console.log(Mahjong.Utils.getHoraSets([
|
console.log(Mahjong.Common.getHoraSets([
|
||||||
'm3',
|
'm3',
|
||||||
'm3',
|
'm3',
|
||||||
'm4',
|
'm4',
|
||||||
|
@ -298,8 +301,8 @@ function riichi() {
|
||||||
if (!isMyTurn.value) return;
|
if (!isMyTurn.value) return;
|
||||||
|
|
||||||
riichiSelect = true;
|
riichiSelect = true;
|
||||||
selectableTiles.value = Mahjong.Utils.getTilesForRiichi(engine.value.myHandTiles);
|
selectableTiles.value = Mahjong.Common.getTilesForRiichi(engine.value.myHandTiles);
|
||||||
console.log(Mahjong.Utils.getTilesForRiichi(engine.value.myHandTiles));
|
console.log(Mahjong.Common.getTilesForRiichi(engine.value.myHandTiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
function kakan() {
|
function kakan() {
|
||||||
|
@ -403,10 +406,10 @@ function onStreamDahaiAndTsumo(log) {
|
||||||
triggerRef(engine);
|
triggerRef(engine);
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
engine.value.commit_tsumo(Mahjong.Utils.nextHouse(log.dahaiHouse), log.tsumoTile);
|
engine.value.commit_tsumo(Mahjong.Common.nextHouse(log.dahaiHouse), log.tsumoTile);
|
||||||
triggerRef(engine);
|
triggerRef(engine);
|
||||||
|
|
||||||
if (Mahjong.Utils.nextHouse(log.dahaiHouse) === engine.value.myHouse) {
|
if (Mahjong.Common.nextHouse(log.dahaiHouse) === engine.value.myHouse) {
|
||||||
iTsumoed.value = true;
|
iTsumoed.value = true;
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
|
@ -360,3 +360,249 @@ export function calcTsumoHoraPointDeltas(house: House, fans: number): Record<Hou
|
||||||
|
|
||||||
return deltas;
|
return deltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTile(tile: string): tile is Tile {
|
||||||
|
return TILE_TYPES.includes(tile as Tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortTiles(tiles: Tile[]): Tile[] {
|
||||||
|
tiles.sort((a, b) => {
|
||||||
|
const aIndex = TILE_TYPES.indexOf(a);
|
||||||
|
const bIndex = TILE_TYPES.indexOf(b);
|
||||||
|
return aIndex - bIndex;
|
||||||
|
});
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextHouse(house: House): House {
|
||||||
|
switch (house) {
|
||||||
|
case 'e': return 's';
|
||||||
|
case 's': return 'w';
|
||||||
|
case 'w': return 'n';
|
||||||
|
case 'n': return 'e';
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prevHouse(house: House): House {
|
||||||
|
switch (house) {
|
||||||
|
case 'e': return 'n';
|
||||||
|
case 's': return 'e';
|
||||||
|
case 'w': return 's';
|
||||||
|
case 'n': return 'w';
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HoraSet = {
|
||||||
|
head: Tile;
|
||||||
|
mentsus: [Tile, Tile, Tile][];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SHUNTU_PATTERNS: [Tile, Tile, Tile][] = [
|
||||||
|
['m1', 'm2', 'm3'],
|
||||||
|
['m2', 'm3', 'm4'],
|
||||||
|
['m3', 'm4', 'm5'],
|
||||||
|
['m4', 'm5', 'm6'],
|
||||||
|
['m5', 'm6', 'm7'],
|
||||||
|
['m6', 'm7', 'm8'],
|
||||||
|
['m7', 'm8', 'm9'],
|
||||||
|
['p1', 'p2', 'p3'],
|
||||||
|
['p2', 'p3', 'p4'],
|
||||||
|
['p3', 'p4', 'p5'],
|
||||||
|
['p4', 'p5', 'p6'],
|
||||||
|
['p5', 'p6', 'p7'],
|
||||||
|
['p6', 'p7', 'p8'],
|
||||||
|
['p7', 'p8', 'p9'],
|
||||||
|
['s1', 's2', 's3'],
|
||||||
|
['s2', 's3', 's4'],
|
||||||
|
['s3', 's4', 's5'],
|
||||||
|
['s4', 's5', 's6'],
|
||||||
|
['s5', 's6', 's7'],
|
||||||
|
['s6', 's7', 's8'],
|
||||||
|
['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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アガリ形パターン一覧を取得
|
||||||
|
* @param handTiles ポン、チー、カンした牌を含まない手牌
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getHoraSets(handTiles: Tile[]): HoraSet[] {
|
||||||
|
const horaSets: HoraSet[] = [];
|
||||||
|
|
||||||
|
const headSet: Tile[] = [];
|
||||||
|
const countMap = new Map<Tile, number>();
|
||||||
|
for (const tile of handTiles) {
|
||||||
|
const count = (countMap.get(tile) ?? 0) + 1;
|
||||||
|
countMap.set(tile, count);
|
||||||
|
if (count === 2) {
|
||||||
|
headSet.push(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const head of headSet) {
|
||||||
|
const tempHandTiles = [...handTiles];
|
||||||
|
tempHandTiles.splice(tempHandTiles.indexOf(head), 1);
|
||||||
|
tempHandTiles.splice(tempHandTiles.indexOf(head), 1);
|
||||||
|
|
||||||
|
const kotsuTileSet: Tile[] = []; // インデックスアクセスしたいため配列だが実態はSet
|
||||||
|
for (const [t, c] of countMap.entries()) {
|
||||||
|
if (t === head) continue; // 同じ牌種は4枚しかないので、頭と同じ牌種は刻子になりえない
|
||||||
|
if (c >= 3) {
|
||||||
|
kotsuTileSet.push(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let kotsuPatterns: Tile[][];
|
||||||
|
if (kotsuTileSet.length === 0) {
|
||||||
|
kotsuPatterns = [
|
||||||
|
[],
|
||||||
|
];
|
||||||
|
} else if (kotsuTileSet.length === 1) {
|
||||||
|
kotsuPatterns = [
|
||||||
|
[],
|
||||||
|
[kotsuTileSet[0]],
|
||||||
|
];
|
||||||
|
} else if (kotsuTileSet.length === 2) {
|
||||||
|
kotsuPatterns = [
|
||||||
|
[],
|
||||||
|
[kotsuTileSet[0]],
|
||||||
|
[kotsuTileSet[1]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1]],
|
||||||
|
];
|
||||||
|
} else if (kotsuTileSet.length === 3) {
|
||||||
|
kotsuPatterns = [
|
||||||
|
[],
|
||||||
|
[kotsuTileSet[0]],
|
||||||
|
[kotsuTileSet[1]],
|
||||||
|
[kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[1], kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[2]],
|
||||||
|
];
|
||||||
|
} else if (kotsuTileSet.length === 4) {
|
||||||
|
kotsuPatterns = [
|
||||||
|
[],
|
||||||
|
[kotsuTileSet[0]],
|
||||||
|
[kotsuTileSet[1]],
|
||||||
|
[kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[1], kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[1], kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[2], kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[2]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[2], kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[1], kotsuTileSet[2], kotsuTileSet[3]],
|
||||||
|
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[2], kotsuTileSet[3]],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new Error('arienai');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const kotsuPattern of kotsuPatterns) {
|
||||||
|
const tempHandTilesWithoutKotsu = [...tempHandTiles];
|
||||||
|
for (const kotsuTile of kotsuPattern) {
|
||||||
|
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
|
||||||
|
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shuntsus.length * 3 === tempHandTilesWithoutKotsu.length) { // アガリ形
|
||||||
|
horaSets.push({
|
||||||
|
head,
|
||||||
|
mentsus: [...kotsuPattern.map(t => [t, t, t] as [Tile, Tile, Tile]), ...shuntsus],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return horaSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アガリ牌リストを取得
|
||||||
|
* @param handTiles ポン、チー、カンした牌を含まない手牌
|
||||||
|
*/
|
||||||
|
export function getHoraTiles(handTiles: Tile[]): Tile[] {
|
||||||
|
return TILE_TYPES.filter(tile => {
|
||||||
|
const tempHandTiles = [...handTiles, tile];
|
||||||
|
const horaSets = getHoraSets(tempHandTiles);
|
||||||
|
return horaSets.length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 国士無双判定関数
|
||||||
|
|
||||||
|
// TODO: 七対子判定関数
|
||||||
|
|
||||||
|
export function getTilesForRiichi(handTiles: Tile[]): Tile[] {
|
||||||
|
return handTiles.filter(tile => {
|
||||||
|
const tempHandTiles = [...handTiles];
|
||||||
|
tempHandTiles.splice(tempHandTiles.indexOf(tile), 1);
|
||||||
|
const horaTiles = getHoraTiles(tempHandTiles);
|
||||||
|
return horaTiles.length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextTileForDora(tile: Tile): Tile {
|
||||||
|
return NEXT_TILE_FOR_DORA_MAP[tile];
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
import CRC32 from 'crc-32';
|
import CRC32 from 'crc-32';
|
||||||
import { Tile, House, Huro, TILE_TYPES, YAKU_DEFINITIONS } from './common.js';
|
import { Tile, House, Huro, TILE_TYPES, YAKU_DEFINITIONS } from './common.js';
|
||||||
import * as Common from './common.js';
|
import * as Common from './common.js';
|
||||||
import * as Utils from './utils.js';
|
|
||||||
import { PlayerState } from './engine.player.js';
|
import { PlayerState } from './engine.player.js';
|
||||||
|
|
||||||
export type MasterState = {
|
export type MasterState = {
|
||||||
|
@ -116,7 +115,7 @@ export class MasterGameEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get doras(): Tile[] {
|
public get doras(): Tile[] {
|
||||||
return this.state.kingTiles.slice(0, this.state.activatedDorasCount).map(t => Utils.nextTileForDora(t));
|
return this.state.kingTiles.slice(0, this.state.activatedDorasCount).map(t => Common.nextTileForDora(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createInitialState(): MasterState {
|
public static createInitialState(): MasterState {
|
||||||
|
@ -199,7 +198,7 @@ export class MasterGameEngine {
|
||||||
// TODO: ポンされるなどして自分の河にない場合の考慮
|
// TODO: ポンされるなどして自分の河にない場合の考慮
|
||||||
if (this.state.hoTiles[house].includes(tile)) return false;
|
if (this.state.hoTiles[house].includes(tile)) return false;
|
||||||
|
|
||||||
const horaSets = Utils.getHoraSets(this.state.handTiles[house].concat(tile));
|
const horaSets = Common.getHoraSets(this.state.handTiles[house].concat(tile));
|
||||||
if (horaSets.length === 0) return false; // 完成形じゃない
|
if (horaSets.length === 0) return false; // 完成形じゃない
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -213,8 +212,12 @@ export class MasterGameEngine {
|
||||||
return this.state.handTiles[house].filter(t => t === tile).length === 2;
|
return this.state.handTiles[house].filter(t => t === tile).length === 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private canCii(house: House, tile: Tile): boolean {
|
private canCii(caller: House, callee: House, tile: Tile): boolean {
|
||||||
// TODO
|
if (callee !== Common.prevHouse(caller)) return false;
|
||||||
|
const hand = this.state.handTiles[caller];
|
||||||
|
return Common.SHUNTU_PATTERNS.some(pattern =>
|
||||||
|
pattern.includes(tile) &&
|
||||||
|
pattern.filter(t => hand.includes(t)).length >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHouse(index: 1 | 2 | 3 | 4): House {
|
public getHouse(index: 1 | 2 | 3 | 4): House {
|
||||||
|
@ -266,7 +269,7 @@ export class MasterGameEngine {
|
||||||
if (riichi) {
|
if (riichi) {
|
||||||
const tempHandTiles = [...this.state.handTiles[house]];
|
const tempHandTiles = [...this.state.handTiles[house]];
|
||||||
tempHandTiles.splice(tempHandTiles.indexOf(tile), 1);
|
tempHandTiles.splice(tempHandTiles.indexOf(tile), 1);
|
||||||
if (Utils.getHoraTiles(tempHandTiles).length === 0) throw new Error('Not tenpai');
|
if (Common.getHoraTiles(tempHandTiles).length === 0) throw new Error('Not tenpai');
|
||||||
if (this.state.points[house] < 1000) throw new Error('Not enough points');
|
if (this.state.points[house] < 1000) throw new Error('Not enough points');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +363,7 @@ export class MasterGameEngine {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.state.turn = null;
|
this.state.turn = null;
|
||||||
this.state.nextTurnAfterAsking = Utils.nextHouse(house);
|
this.state.nextTurnAfterAsking = Common.nextHouse(house);
|
||||||
return {
|
return {
|
||||||
asking: true as const,
|
asking: true as const,
|
||||||
canRonHouses: canRonHouses,
|
canRonHouses: canRonHouses,
|
||||||
|
@ -370,7 +373,7 @@ export class MasterGameEngine {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.turn = Utils.nextHouse(house);
|
this.state.turn = Common.nextHouse(house);
|
||||||
|
|
||||||
const tsumoTile = this.tsumo();
|
const tsumoTile = this.tsumo();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
import CRC32 from 'crc-32';
|
import CRC32 from 'crc-32';
|
||||||
import { Tile, House, Huro, TILE_TYPES, YAKU_DEFINITIONS } from './common.js';
|
import { Tile, House, Huro, TILE_TYPES, YAKU_DEFINITIONS } from './common.js';
|
||||||
import * as Common from './common.js';
|
import * as Common from './common.js';
|
||||||
import * as Utils from './utils.js';
|
|
||||||
|
|
||||||
export type PlayerState = {
|
export type PlayerState = {
|
||||||
user1House: House;
|
user1House: House;
|
||||||
|
@ -97,7 +96,7 @@ export class PlayerGameEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get doras(): Tile[] {
|
public get doras(): Tile[] {
|
||||||
return this.state.doraIndicateTiles.map(t => Utils.nextTileForDora(t));
|
return this.state.doraIndicateTiles.map(t => Common.nextTileForDora(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
public commit_tsumo(house: House, tile: Tile) {
|
public commit_tsumo(house: House, tile: Tile) {
|
||||||
|
@ -131,7 +130,7 @@ export class PlayerGameEngine {
|
||||||
|
|
||||||
if (house === this.myHouse) {
|
if (house === this.myHouse) {
|
||||||
} else {
|
} else {
|
||||||
const canRon = Utils.getHoraSets(this.myHandTiles.concat(tile)).length > 0;
|
const canRon = Common.getHoraSets(this.myHandTiles.concat(tile)).length > 0;
|
||||||
const canPon = this.myHandTiles.filter(t => t === tile).length === 2;
|
const canPon = this.myHandTiles.filter(t => t === tile).length === 2;
|
||||||
|
|
||||||
// TODO: canCii
|
// TODO: canCii
|
||||||
|
@ -243,7 +242,7 @@ export class PlayerGameEngine {
|
||||||
if (this.state.riichis[this.myHouse]) return false;
|
if (this.state.riichis[this.myHouse]) return false;
|
||||||
if (this.state.points[this.myHouse] < 1000) return false;
|
if (this.state.points[this.myHouse] < 1000) return false;
|
||||||
if (!this.isMenzen) return false;
|
if (!this.isMenzen) return false;
|
||||||
if (Utils.getTilesForRiichi(this.myHandTiles).length === 0) return false;
|
if (Common.getTilesForRiichi(this.myHandTiles).length === 0) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
export * as Serializer from './serializer.js';
|
export * as Serializer from './serializer.js';
|
||||||
export * as Common from './common.js';
|
export * as Common from './common.js';
|
||||||
export * as Utils from './utils.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';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Tile } from './engine.player.js';
|
import { Tile } from './common.js';
|
||||||
|
|
||||||
export type Log = {
|
export type Log = {
|
||||||
time: number;
|
time: number;
|
||||||
|
|
|
@ -1,248 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { House, NEXT_TILE_FOR_DORA_MAP, TILE_TYPES, Tile } from './common.js';
|
|
||||||
|
|
||||||
export function isTile(tile: string): tile is Tile {
|
|
||||||
return TILE_TYPES.includes(tile as Tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sortTiles(tiles: Tile[]): Tile[] {
|
|
||||||
tiles.sort((a, b) => {
|
|
||||||
const aIndex = TILE_TYPES.indexOf(a);
|
|
||||||
const bIndex = TILE_TYPES.indexOf(b);
|
|
||||||
return aIndex - bIndex;
|
|
||||||
});
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nextHouse(house: House): House {
|
|
||||||
switch (house) {
|
|
||||||
case 'e': return 's';
|
|
||||||
case 's': return 'w';
|
|
||||||
case 'w': return 'n';
|
|
||||||
case 'n': return 'e';
|
|
||||||
default: throw new Error(`unrecognized house: ${house}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function prevHouse(house: House): House {
|
|
||||||
switch (house) {
|
|
||||||
case 'e': return 'n';
|
|
||||||
case 's': return 'e';
|
|
||||||
case 'w': return 's';
|
|
||||||
case 'n': return 'w';
|
|
||||||
default: throw new Error(`unrecognized house: ${house}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type HoraSet = {
|
|
||||||
head: Tile;
|
|
||||||
mentsus: [Tile, Tile, Tile][];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SHUNTU_PATTERNS: [Tile, Tile, Tile][] = [
|
|
||||||
['m1', 'm2', 'm3'],
|
|
||||||
['m2', 'm3', 'm4'],
|
|
||||||
['m3', 'm4', 'm5'],
|
|
||||||
['m4', 'm5', 'm6'],
|
|
||||||
['m5', 'm6', 'm7'],
|
|
||||||
['m6', 'm7', 'm8'],
|
|
||||||
['m7', 'm8', 'm9'],
|
|
||||||
['p1', 'p2', 'p3'],
|
|
||||||
['p2', 'p3', 'p4'],
|
|
||||||
['p3', 'p4', 'p5'],
|
|
||||||
['p4', 'p5', 'p6'],
|
|
||||||
['p5', 'p6', 'p7'],
|
|
||||||
['p6', 'p7', 'p8'],
|
|
||||||
['p7', 'p8', 'p9'],
|
|
||||||
['s1', 's2', 's3'],
|
|
||||||
['s2', 's3', 's4'],
|
|
||||||
['s3', 's4', 's5'],
|
|
||||||
['s4', 's5', 's6'],
|
|
||||||
['s5', 's6', 's7'],
|
|
||||||
['s6', 's7', 's8'],
|
|
||||||
['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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* アガリ形パターン一覧を取得
|
|
||||||
* @param handTiles ポン、チー、カンした牌を含まない手牌
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function getHoraSets(handTiles: Tile[]): HoraSet[] {
|
|
||||||
const horaSets: HoraSet[] = [];
|
|
||||||
|
|
||||||
const headSet: Tile[] = [];
|
|
||||||
const countMap = new Map<Tile, number>();
|
|
||||||
for (const tile of handTiles) {
|
|
||||||
const count = (countMap.get(tile) ?? 0) + 1;
|
|
||||||
countMap.set(tile, count);
|
|
||||||
if (count === 2) {
|
|
||||||
headSet.push(tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const head of headSet) {
|
|
||||||
const tempHandTiles = [...handTiles];
|
|
||||||
tempHandTiles.splice(tempHandTiles.indexOf(head), 1);
|
|
||||||
tempHandTiles.splice(tempHandTiles.indexOf(head), 1);
|
|
||||||
|
|
||||||
const kotsuTileSet: Tile[] = []; // インデックスアクセスしたいため配列だが実態はSet
|
|
||||||
for (const [t, c] of countMap.entries()) {
|
|
||||||
if (t === head) continue; // 同じ牌種は4枚しかないので、頭と同じ牌種は刻子になりえない
|
|
||||||
if (c >= 3) {
|
|
||||||
kotsuTileSet.push(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let kotsuPatterns: Tile[][];
|
|
||||||
if (kotsuTileSet.length === 0) {
|
|
||||||
kotsuPatterns = [
|
|
||||||
[],
|
|
||||||
];
|
|
||||||
} else if (kotsuTileSet.length === 1) {
|
|
||||||
kotsuPatterns = [
|
|
||||||
[],
|
|
||||||
[kotsuTileSet[0]],
|
|
||||||
];
|
|
||||||
} else if (kotsuTileSet.length === 2) {
|
|
||||||
kotsuPatterns = [
|
|
||||||
[],
|
|
||||||
[kotsuTileSet[0]],
|
|
||||||
[kotsuTileSet[1]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1]],
|
|
||||||
];
|
|
||||||
} else if (kotsuTileSet.length === 3) {
|
|
||||||
kotsuPatterns = [
|
|
||||||
[],
|
|
||||||
[kotsuTileSet[0]],
|
|
||||||
[kotsuTileSet[1]],
|
|
||||||
[kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[1], kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[2]],
|
|
||||||
];
|
|
||||||
} else if (kotsuTileSet.length === 4) {
|
|
||||||
kotsuPatterns = [
|
|
||||||
[],
|
|
||||||
[kotsuTileSet[0]],
|
|
||||||
[kotsuTileSet[1]],
|
|
||||||
[kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[1], kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[1], kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[2], kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[2]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[2], kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[1], kotsuTileSet[2], kotsuTileSet[3]],
|
|
||||||
[kotsuTileSet[0], kotsuTileSet[1], kotsuTileSet[2], kotsuTileSet[3]],
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
throw new Error('arienai');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const kotsuPattern of kotsuPatterns) {
|
|
||||||
const tempHandTilesWithoutKotsu = [...tempHandTiles];
|
|
||||||
for (const kotsuTile of kotsuPattern) {
|
|
||||||
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
|
|
||||||
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shuntsus.length * 3 === tempHandTilesWithoutKotsu.length) { // アガリ形
|
|
||||||
horaSets.push({
|
|
||||||
head,
|
|
||||||
mentsus: [...kotsuPattern.map(t => [t, t, t] as [Tile, Tile, Tile]), ...shuntsus],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return horaSets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* アガリ牌リストを取得
|
|
||||||
* @param handTiles ポン、チー、カンした牌を含まない手牌
|
|
||||||
*/
|
|
||||||
export function getHoraTiles(handTiles: Tile[]): Tile[] {
|
|
||||||
return TILE_TYPES.filter(tile => {
|
|
||||||
const tempHandTiles = [...handTiles, tile];
|
|
||||||
const horaSets = getHoraSets(tempHandTiles);
|
|
||||||
return horaSets.length > 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTilesForRiichi(handTiles: Tile[]): Tile[] {
|
|
||||||
return handTiles.filter(tile => {
|
|
||||||
const tempHandTiles = [...handTiles];
|
|
||||||
tempHandTiles.splice(tempHandTiles.indexOf(tile), 1);
|
|
||||||
const horaTiles = getHoraTiles(tempHandTiles);
|
|
||||||
return horaTiles.length > 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nextTileForDora(tile: Tile): Tile {
|
|
||||||
return NEXT_TILE_FOR_DORA_MAP[tile];
|
|
||||||
}
|
|
Loading…
Reference in a new issue