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)) {
|
||||
// TODO: ちゃんと思考するようにする
|
||||
//answers.pon = Math.random() < 0.25;
|
||||
answers.pon = false;
|
||||
answers.pon = Math.random() < 0.25;
|
||||
}
|
||||
if (aiHouses.includes(res.canCiiHouse)) {
|
||||
// TODO: ちゃんと思考するようにする
|
||||
|
@ -438,8 +437,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
|||
}
|
||||
if (aiHouses.includes(res.canKanHouse)) {
|
||||
// TODO: ちゃんと思考するようにする
|
||||
//answers.kan = Math.random() < 0.25;
|
||||
answers.kan = false;
|
||||
answers.kan = Math.random() < 0.25;
|
||||
}
|
||||
for (const h of res.canRonHouses) {
|
||||
if (aiHouses.includes(h)) {
|
||||
|
@ -501,7 +499,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
|||
const room = await this.getRoom(roomId);
|
||||
if (room == 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 myHouse = getHouseOfUserId(room, engine, user.id);
|
||||
|
@ -629,7 +627,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
|||
if (engine.state.riichis[house]) {
|
||||
// リーチ時はアガリ牌でない限りツモ切り
|
||||
const handTiles = engine.state.handTiles[house];
|
||||
const horaSets = Mahjong.Utils.getHoraSets(handTiles);
|
||||
const horaSets = Mahjong.Common.getHoraSets(handTiles);
|
||||
if (horaSets.length === 0) {
|
||||
setTimeout(() => {
|
||||
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 :class="$style.centerPanelTickerToi">
|
||||
<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.centerPanelPoint">{{ engine.state.points[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))] }}</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.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.centerPanelTickerKami">
|
||||
<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.centerPanelPoint">{{ engine.state.points[Mahjong.Utils.prevHouse(engine.myHouse)] }}</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.Common.prevHouse(engine.myHouse)] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.centerPanelTickerSimo">
|
||||
<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.centerPanelPoint">{{ engine.state.points[Mahjong.Utils.nextHouse(engine.myHouse)] }}</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.Common.nextHouse(engine.myHouse)] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ engine.state.tilesCount }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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;"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,21 +59,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.hoTilesContainer">
|
||||
<div :class="$style.hoTilesContainerOfToimen">
|
||||
<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"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.hoTilesContainerOfKamitya">
|
||||
<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"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.hoTilesContainerOfSimotya">
|
||||
<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"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -86,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<div :class="$style.handTilesOfMe">
|
||||
<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) }]"
|
||||
@click="chooseTile(tile, $event)"
|
||||
>
|
||||
|
@ -116,25 +119,25 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<div :class="$style.serifContainer">
|
||||
<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-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="ponSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.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="tsumoSerifHouses[Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/tsumo.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.Common.prevHouse(Mahjong.Common.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="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.Common.prevHouse(Mahjong.Common.prevHouse(engine.myHouse))]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/>
|
||||
</div>
|
||||
<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-else-if="ciiSerifHouses[Mahjong.Utils.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="kanSerifHouses[Mahjong.Utils.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-if="ronSerifHouses[Mahjong.Common.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="ponSerifHouses[Mahjong.Common.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="tsumoSerifHouses[Mahjong.Common.prevHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/>
|
||||
</div>
|
||||
<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-else-if="ciiSerifHouses[Mahjong.Utils.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="kanSerifHouses[Mahjong.Utils.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-if="ronSerifHouses[Mahjong.Common.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="ponSerifHouses[Mahjong.Common.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="tsumoSerifHouses[Mahjong.Common.nextHouse(engine.myHouse)]" :src="`/client-assets/mahjong/tsumo.png`" style="display: block; width: 100%;"/>
|
||||
</div>
|
||||
<div :class="$style.serifContainerOfMe">
|
||||
<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(() => {
|
||||
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);
|
||||
|
@ -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 });
|
||||
|
||||
/*
|
||||
console.log(Mahjong.Utils.getTilesForRiichi([
|
||||
console.log(Mahjong.Common.getTilesForRiichi([
|
||||
'm1',
|
||||
'm2',
|
||||
'm2',
|
||||
|
@ -219,7 +222,7 @@ console.log(Mahjong.Utils.getTilesForRiichi([
|
|||
]));
|
||||
*/
|
||||
/*
|
||||
console.log(Mahjong.Utils.getHoraSets([
|
||||
console.log(Mahjong.Common.getHoraSets([
|
||||
'm3',
|
||||
'm3',
|
||||
'm4',
|
||||
|
@ -298,8 +301,8 @@ function riichi() {
|
|||
if (!isMyTurn.value) return;
|
||||
|
||||
riichiSelect = true;
|
||||
selectableTiles.value = Mahjong.Utils.getTilesForRiichi(engine.value.myHandTiles);
|
||||
console.log(Mahjong.Utils.getTilesForRiichi(engine.value.myHandTiles));
|
||||
selectableTiles.value = Mahjong.Common.getTilesForRiichi(engine.value.myHandTiles);
|
||||
console.log(Mahjong.Common.getTilesForRiichi(engine.value.myHandTiles));
|
||||
}
|
||||
|
||||
function kakan() {
|
||||
|
@ -403,10 +406,10 @@ function onStreamDahaiAndTsumo(log) {
|
|||
triggerRef(engine);
|
||||
|
||||
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);
|
||||
|
||||
if (Mahjong.Utils.nextHouse(log.dahaiHouse) === engine.value.myHouse) {
|
||||
if (Mahjong.Common.nextHouse(log.dahaiHouse) === engine.value.myHouse) {
|
||||
iTsumoed.value = true;
|
||||
}
|
||||
}, 100);
|
||||
|
|
|
@ -360,3 +360,249 @@ export function calcTsumoHoraPointDeltas(house: House, fans: number): Record<Hou
|
|||
|
||||
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 { Tile, House, Huro, TILE_TYPES, YAKU_DEFINITIONS } from './common.js';
|
||||
import * as Common from './common.js';
|
||||
import * as Utils from './utils.js';
|
||||
import { PlayerState } from './engine.player.js';
|
||||
|
||||
export type MasterState = {
|
||||
|
@ -116,7 +115,7 @@ export class MasterGameEngine {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -199,7 +198,7 @@ export class MasterGameEngine {
|
|||
// TODO: ポンされるなどして自分の河にない場合の考慮
|
||||
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; // 完成形じゃない
|
||||
|
||||
// TODO
|
||||
|
@ -213,8 +212,12 @@ export class MasterGameEngine {
|
|||
return this.state.handTiles[house].filter(t => t === tile).length === 2;
|
||||
}
|
||||
|
||||
private canCii(house: House, tile: Tile): boolean {
|
||||
// TODO
|
||||
private canCii(caller: House, callee: House, tile: Tile): boolean {
|
||||
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 {
|
||||
|
@ -266,7 +269,7 @@ export class MasterGameEngine {
|
|||
if (riichi) {
|
||||
const tempHandTiles = [...this.state.handTiles[house]];
|
||||
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');
|
||||
}
|
||||
|
||||
|
@ -360,7 +363,7 @@ export class MasterGameEngine {
|
|||
};
|
||||
}
|
||||
this.state.turn = null;
|
||||
this.state.nextTurnAfterAsking = Utils.nextHouse(house);
|
||||
this.state.nextTurnAfterAsking = Common.nextHouse(house);
|
||||
return {
|
||||
asking: true as const,
|
||||
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();
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import CRC32 from 'crc-32';
|
||||
import { Tile, House, Huro, TILE_TYPES, YAKU_DEFINITIONS } from './common.js';
|
||||
import * as Common from './common.js';
|
||||
import * as Utils from './utils.js';
|
||||
|
||||
export type PlayerState = {
|
||||
user1House: House;
|
||||
|
@ -97,7 +96,7 @@ export class PlayerGameEngine {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -131,7 +130,7 @@ export class PlayerGameEngine {
|
|||
|
||||
if (house === this.myHouse) {
|
||||
} 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;
|
||||
|
||||
// TODO: canCii
|
||||
|
@ -243,7 +242,7 @@ export class PlayerGameEngine {
|
|||
if (this.state.riichis[this.myHouse]) return false;
|
||||
if (this.state.points[this.myHouse] < 1000) 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
export * as Serializer from './serializer.js';
|
||||
export * as Common from './common.js';
|
||||
export * as Utils from './utils.js';
|
||||
|
||||
export { MasterGameEngine } from './engine.master.js';
|
||||
export type { MasterState } from './engine.master.js';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Tile } from './engine.player.js';
|
||||
import { Tile } from './common.js';
|
||||
|
||||
export type Log = {
|
||||
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