wip
|
@ -292,10 +292,10 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
await this.saveRoom(room);
|
await this.saveRoom(room);
|
||||||
|
|
||||||
if (res.type === 'tsumo') {
|
if (res.type === 'tsumo') {
|
||||||
this.globalEventService.publishMahjongRoomStream(room.id, 'log', { operation: 'tsumo', house: res.house, tile: res.tile });
|
this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile });
|
||||||
this.next(room, engine);
|
this.next(room, engine);
|
||||||
} else if (res.type === 'ponned') {
|
} else if (res.type === 'ponned') {
|
||||||
this.globalEventService.publishMahjongRoomStream(room.id, 'log', { operation: 'ponned', house: res.house, tile: res.tile });
|
this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { source: res.source, target: res.target, tile: res.tile });
|
||||||
const userId = engine.state.user1House === engine.state.turn ? room.user1Id : engine.state.user2House === engine.state.turn ? room.user2Id : engine.state.user3House === engine.state.turn ? room.user3Id : room.user4Id;
|
const userId = engine.state.user1House === engine.state.turn ? room.user1Id : engine.state.user2House === engine.state.turn ? room.user2Id : engine.state.user3House === engine.state.turn ? room.user3Id : room.user4Id;
|
||||||
this.waitForDahai(room, userId, engine);
|
this.waitForDahai(room, userId, engine);
|
||||||
} else if (res.type === 'kanned') {
|
} else if (res.type === 'kanned') {
|
||||||
|
@ -314,7 +314,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
const house = engine.state.turn;
|
const house = engine.state.turn;
|
||||||
const handTiles = house === 'e' ? engine.state.eHandTiles : house === 's' ? engine.state.sHandTiles : house === 'w' ? engine.state.wHandTiles : engine.state.nHandTiles;
|
const handTiles = house === 'e' ? engine.state.eHandTiles : house === 's' ? engine.state.sHandTiles : house === 'w' ? engine.state.wHandTiles : engine.state.nHandTiles;
|
||||||
this.dahai(room, engine, engine.state.turn, handTiles.at(-1));
|
this.dahai(room, engine, engine.state.turn, handTiles.at(-1));
|
||||||
}, 1000);
|
}, 500);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const userId = engine.state.user1House === engine.state.turn ? room.user1Id : engine.state.user2House === engine.state.turn ? room.user2Id : engine.state.user3House === engine.state.turn ? room.user3Id : room.user4Id;
|
const userId = engine.state.user1House === engine.state.turn ? room.user1Id : engine.state.user2House === engine.state.turn ? room.user2Id : engine.state.user3House === engine.state.turn ? room.user3Id : room.user4Id;
|
||||||
|
@ -323,7 +323,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async dahai(room: Room, engine: Mahjong.Engine.MasterGameEngine, house: Mahjong.Common.House, tile: Mahjong.Common.Tile, operationId?: string) {
|
private async dahai(room: Room, engine: Mahjong.Engine.MasterGameEngine, house: Mahjong.Common.House, tile: Mahjong.Common.Tile) {
|
||||||
const res = engine.op_dahai(house, tile);
|
const res = engine.op_dahai(house, tile);
|
||||||
room.gameState = engine.state;
|
room.gameState = engine.state;
|
||||||
await this.saveRoom(room);
|
await this.saveRoom(room);
|
||||||
|
@ -346,17 +346,23 @@ 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 = false;
|
||||||
}
|
}
|
||||||
if (aiHouses.includes(res.canChiHouse)) {
|
if (aiHouses.includes(res.canCiiHouse)) {
|
||||||
// TODO
|
// TODO: ちゃんと思考するようにする
|
||||||
|
//answers.cii = Math.random() < 0.25;
|
||||||
|
answers.cii = false;
|
||||||
}
|
}
|
||||||
if (aiHouses.includes(res.canKanHouse)) {
|
if (aiHouses.includes(res.canKanHouse)) {
|
||||||
// TODO
|
// TODO: ちゃんと思考するようにする
|
||||||
|
//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)) {
|
||||||
// TODO
|
// TODO: ちゃんと思考するようにする
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,24 +382,24 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
(res.canRonHouses.includes('n') && currentAnswers.ron.n == null)
|
(res.canRonHouses.includes('n') && currentAnswers.ron.n == null)
|
||||||
);
|
);
|
||||||
if (allAnswered || (Date.now() - waitingStartedAt > CALL_AND_RON_ASKING_TIMEOUT_MS)) {
|
if (allAnswered || (Date.now() - waitingStartedAt > CALL_AND_RON_ASKING_TIMEOUT_MS)) {
|
||||||
console.log('answerd');
|
console.log(allAnswered ? 'ask all answerd' : 'ask timeout');
|
||||||
await this.redisClient.del(`mahjong:gamePonAsking:${room.id}`);
|
await this.redisClient.del(`mahjong:gameCallAndRonAsking:${room.id}`);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
this.answer(room, engine, currentAnswers);
|
this.answer(room, engine, currentAnswers);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
this.globalEventService.publishMahjongRoomStream(room.id, 'log', { operation: 'dahai', house: house, tile, id: operationId });
|
this.globalEventService.publishMahjongRoomStream(room.id, 'dahai', { house: house, tile });
|
||||||
} else {
|
} else {
|
||||||
this.globalEventService.publishMahjongRoomStream(room.id, 'log', { operation: 'dahaiAndTsumo', house: house, dahaiTile: tile, tsumoTile: res.tsumoTile, id: operationId });
|
this.globalEventService.publishMahjongRoomStream(room.id, 'dahaiAndTsumo', { dahaiHouse: house, dahaiTile: tile, tsumoTile: res.tsumoTile });
|
||||||
|
|
||||||
this.next(room, engine);
|
this.next(room, engine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async op_dahai(roomId: MiMahjongGame['id'], user: MiUser, tile: string, operationId: string) {
|
public async op_dahai(roomId: MiMahjongGame['id'], user: MiUser, tile: string) {
|
||||||
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;
|
||||||
|
@ -403,7 +409,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
|
|
||||||
const engine = new Mahjong.Engine.MasterGameEngine(room.gameState);
|
const engine = new Mahjong.Engine.MasterGameEngine(room.gameState);
|
||||||
const myHouse = user.id === room.user1Id ? engine.state.user1House : user.id === room.user2Id ? engine.state.user2House : user.id === room.user3Id ? engine.state.user3House : engine.state.user4House;
|
const myHouse = user.id === room.user1Id ? engine.state.user1House : user.id === room.user2Id ? engine.state.user2House : user.id === room.user3Id ? engine.state.user3House : engine.state.user4House;
|
||||||
await this.dahai(room, engine, myHouse, tile, operationId);
|
await this.dahai(room, engine, myHouse, tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -462,7 +468,7 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
|
||||||
console.log('dahai timeout', userId, id);
|
console.log('dahai timeout', userId, id);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
const house = room.user1Id === userId ? engine.state.user1House : room.user2Id === userId ? engine.state.user2House : room.user3Id === userId ? engine.state.user3House : engine.state.user4House;
|
const house = room.user1Id === userId ? engine.state.user1House : room.user2Id === userId ? engine.state.user2House : room.user3Id === userId ? engine.state.user3House : engine.state.user4House;
|
||||||
const handTiles = house === 'e' ? engine.state.eHandTiles : house === 's' ? engine.state.sHandTiles : house === 'w' ? engine.state.wHandTiles : engine.state.nHandTiles;
|
const handTiles = engine.getHandTilesOf(house);
|
||||||
await this.dahai(room, engine, house, handTiles.at(-1));
|
await this.dahai(room, engine, house, handTiles.at(-1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class MahjongRoomChannel extends Channel {
|
||||||
case 'ready': this.ready(body); break;
|
case 'ready': this.ready(body); break;
|
||||||
case 'updateSettings': this.updateSettings(body.key, body.value); break;
|
case 'updateSettings': this.updateSettings(body.key, body.value); break;
|
||||||
case 'addAi': this.addAi(); break;
|
case 'addAi': this.addAi(); break;
|
||||||
case 'dahai': this.dahai(body.tile, body.id); break;
|
case 'dahai': this.dahai(body.tile); break;
|
||||||
case 'pon': this.pon(); break;
|
case 'pon': this.pon(); break;
|
||||||
case 'nop': this.nop(); break;
|
case 'nop': this.nop(); break;
|
||||||
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
|
||||||
|
@ -67,10 +67,10 @@ class MahjongRoomChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async dahai(tile: string, id: string) {
|
private async dahai(tile: string) {
|
||||||
if (this.user == null) return;
|
if (this.user == null) return;
|
||||||
|
|
||||||
this.mahjongService.op_dahai(this.roomId!, this.user, tile, id);
|
this.mahjongService.op_dahai(this.roomId!, this.user, tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
BIN
packages/frontend/assets/mahjong/tile-back.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
packages/frontend/assets/mahjong/tile-front.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
packages/frontend/assets/mahjong/tile-side.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
packages/frontend/assets/mahjong/tile-top.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
packages/frontend/assets/mahjong/tiles/chun.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
packages/frontend/assets/mahjong/tiles/e.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
packages/frontend/assets/mahjong/tiles/haku.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
packages/frontend/assets/mahjong/tiles/hatsu.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m1.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m2.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m3.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m4.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m5.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m6.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m7.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m8.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
packages/frontend/assets/mahjong/tiles/m9.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
packages/frontend/assets/mahjong/tiles/n.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p1.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p2.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p3.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p4.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p5.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p6.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p7.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p8.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
packages/frontend/assets/mahjong/tiles/p9.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s1.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s2.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s3.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s4.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s5.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s6.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s7.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s8.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
packages/frontend/assets/mahjong/tiles/s9.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
packages/frontend/assets/mahjong/tiles/w.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
|
@ -4,47 +4,82 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :contentMax="500">
|
<div :class="$style.root">
|
||||||
<div class="_gaps">
|
<div :class="$style.taku">
|
||||||
<div>
|
<div :class="$style.handTilesOfToimen">
|
||||||
{{ engine.myHouse }} {{ engine.state.turn }}
|
<div v-for="tile in engine.getHandTilesOf(Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)))" style="display: inline-block;">
|
||||||
</div>
|
<img :src="`/client-assets/mahjong/tile-back.png`" style="display: inline-block; width: 32px;"/>
|
||||||
<div class="_panel">
|
|
||||||
<div>{{ Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))) }} ho</div>
|
|
||||||
<div v-for="tile in engine.getHoTilesOf(Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse))))" style="display: inline-block;">
|
|
||||||
<img :src="`/client-assets/mahjong/tiles/${tile}.gif`"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="_panel">
|
|
||||||
<div>{{ Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)) }} ho</div>
|
|
||||||
<div v-for="tile in engine.getHoTilesOf(Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)))" style="display: inline-block;">
|
|
||||||
<img :src="`/client-assets/mahjong/tiles/${tile}.gif`"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="_panel">
|
|
||||||
<div>{{ Mahjong.Utils.prevHouse(engine.myHouse) }} ho</div>
|
|
||||||
<div v-for="tile in engine.getHoTilesOf(Mahjong.Utils.prevHouse(engine.myHouse))" style="display: inline-block;">
|
|
||||||
<img :src="`/client-assets/mahjong/tiles/${tile}.gif`"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="_panel">
|
|
||||||
<div>{{ engine.myHouse }} ho</div>
|
|
||||||
<div v-for="tile in engine.myHoTiles" style="display: inline-block;">
|
|
||||||
<img :src="`/client-assets/mahjong/tiles/${tile}.gif`"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="_panel">
|
<div :class="$style.handTilesOfKamitya">
|
||||||
<div>My hand</div>
|
<div v-for="tile in engine.getHandTilesOf(Mahjong.Utils.prevHouse(engine.myHouse))" :class="$style.sideTile">
|
||||||
<div v-for="tile in Mahjong.Utils.sortTiles(engine.myHandTiles)" style="display: inline-block;" @click="dahai(tile, $event)">
|
<img :src="`/client-assets/mahjong/tile-side.png`" style="display: inline-block; width: 32px;"/>
|
||||||
<img :src="`/client-assets/mahjong/tiles/${tile}.gif`"/>
|
|
||||||
</div>
|
</div>
|
||||||
<MkButton v-if="engine.state.canPon" @click="pon">Pon</MkButton>
|
</div>
|
||||||
<MkButton v-if="engine.state.canPon" @click="skip">Skip pon</MkButton>
|
|
||||||
|
<div :class="$style.handTilesOfSimotya">
|
||||||
|
<div v-for="tile in engine.getHandTilesOf(Mahjong.Utils.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>
|
||||||
|
|
||||||
|
<div :class="$style.hoTilesContainer">
|
||||||
|
<div :class="$style.hoTilesContainerOfToimen">
|
||||||
|
<div :class="$style.hoTilesOfToimen">
|
||||||
|
<div v-for="tile in engine.getHoTilesOf(Mahjong.Utils.prevHouse(Mahjong.Utils.prevHouse(engine.myHouse)))" :class="$style.hoTile">
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${tile}.png`" style="position: absolute; width: 100%;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.hoTilesContainerOfKamitya">
|
||||||
|
<div :class="$style.hoTilesOfKamitya">
|
||||||
|
<div v-for="tile in engine.getHoTilesOf(Mahjong.Utils.prevHouse(engine.myHouse))" :class="$style.hoTile">
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${tile}.png`" style="position: absolute; width: 100%;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.hoTilesContainerOfSimotya">
|
||||||
|
<div :class="$style.hoTilesOfSimotya">
|
||||||
|
<div v-for="tile in engine.getHoTilesOf(Mahjong.Utils.nextHouse(engine.myHouse))" :class="$style.hoTile">
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${tile}.png`" style="position: absolute; width: 100%;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.hoTilesContainerOfMe">
|
||||||
|
<div :class="$style.hoTilesOfMe">
|
||||||
|
<div v-for="tile in engine.myHoTiles" :class="$style.hoTile">
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${tile}.png`" style="position: absolute; width: 100%;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.handTilesOfMe">
|
||||||
|
<div v-for="tile in Mahjong.Utils.sortTiles((isMyTurn && iTsumoed) ? engine.myHandTiles.slice(0, engine.myHandTiles.length - 1) : engine.myHandTiles)" :class="$style.myTile" @click="dahai(tile, $event)">
|
||||||
|
<img :src="`/client-assets/mahjong/tile-front.png`" :class="$style.myTileBg"/>
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${tile}.png`" :class="$style.myTileFg"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="isMyTurn && iTsumoed" style="display: inline-block; margin-left: 5px;" :class="$style.myTile" @click="dahai(engine.myHandTiles.at(-1), $event)">
|
||||||
|
<img :src="`/client-assets/mahjong/tile-front.png`" :class="$style.myTileBg"/>
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${engine.myHandTiles.at(-1)}.png`" :class="$style.myTileFg"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.huroTilesOfMe">
|
||||||
|
<div v-for="huro in engine.getHurosOf(engine.myHouse)" style="display: inline-block;">
|
||||||
|
<div v-if="huro.type === 'pon'">
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${huro.tile}.png`"/>
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${huro.tile}.png`"/>
|
||||||
|
<img :src="`/client-assets/mahjong/tiles/${huro.tile}.png`"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MkButton v-if="engine.state.canPonSource != null" @click="pon">Pon</MkButton>
|
||||||
|
<MkButton v-if="engine.state.canPonSource != null" @click="skip">Skip pon</MkButton>
|
||||||
<MkButton v-if="isMyTurn && canHora">Hora</MkButton>
|
<MkButton v-if="isMyTurn && canHora">Hora</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -83,6 +118,25 @@ const canHora = computed(() => {
|
||||||
return Mahjong.Utils.getHoraSets(engine.value.myHandTiles).length > 0;
|
return Mahjong.Utils.getHoraSets(engine.value.myHandTiles).length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log(Mahjong.Utils.getHoraSets([
|
||||||
|
'm3',
|
||||||
|
'm3',
|
||||||
|
'm4',
|
||||||
|
'm4',
|
||||||
|
'm5',
|
||||||
|
'm5',
|
||||||
|
'p4',
|
||||||
|
'p4',
|
||||||
|
'p7',
|
||||||
|
'p8',
|
||||||
|
'p9',
|
||||||
|
's4',
|
||||||
|
's5',
|
||||||
|
's6',
|
||||||
|
]));
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (room.value.isStarted && !room.value.isEnded) {
|
if (room.value.isStarted && !room.value.isEnded) {
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
|
@ -102,10 +156,7 @@ if (room.value.isStarted && !room.value.isEnded) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const appliedOps: string[] = [];
|
|
||||||
|
|
||||||
const myTurnTimerRmain = ref<number>(room.value.timeLimitForEachTurn);
|
const myTurnTimerRmain = ref<number>(room.value.timeLimitForEachTurn);
|
||||||
const opTurnTimerRmain = ref<number>(room.value.timeLimitForEachTurn);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const TIMER_INTERVAL_SEC = 3;
|
const TIMER_INTERVAL_SEC = 3;
|
||||||
|
@ -131,37 +182,36 @@ function dahai(tile: Mahjong.Common.Tile, ev: MouseEvent) {
|
||||||
if (!isMyTurn.value) return;
|
if (!isMyTurn.value) return;
|
||||||
|
|
||||||
engine.value.op_dahai(engine.value.myHouse, tile);
|
engine.value.op_dahai(engine.value.myHouse, tile);
|
||||||
|
iTsumoed.value = false;
|
||||||
|
triggerRef(engine);
|
||||||
|
|
||||||
const id = Math.random().toString(36).slice(2);
|
|
||||||
appliedOps.push(id);
|
|
||||||
props.connection!.send('dahai', {
|
props.connection!.send('dahai', {
|
||||||
tile: tile,
|
tile: tile,
|
||||||
id,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function pon() {
|
function pon() {
|
||||||
engine.value.op_pon(engine.value.canPonTo, engine.value.myHouse);
|
engine.value.op_pon(engine.value.state.canPonSource, engine.value.myHouse);
|
||||||
|
triggerRef(engine);
|
||||||
|
|
||||||
const id = Math.random().toString(36).slice(2);
|
|
||||||
appliedOps.push(id);
|
|
||||||
props.connection!.send('pon', {
|
props.connection!.send('pon', {
|
||||||
id,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function skip() {
|
function skip() {
|
||||||
engine.value.op_nop(engine.value.myHouse);
|
engine.value.op_nop(engine.value.myHouse);
|
||||||
|
triggerRef(engine);
|
||||||
|
|
||||||
const id = Math.random().toString(36).slice(2);
|
|
||||||
appliedOps.push(id);
|
|
||||||
props.connection!.send('nop', {});
|
props.connection!.send('nop', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onStreamLog(log) {
|
const iTsumoed = ref(false);
|
||||||
if (log.id == null || !appliedOps.includes(log.id)) {
|
|
||||||
switch (log.operation) {
|
function onStreamDahai(log) {
|
||||||
case 'dahai': {
|
console.log('onStreamDahai', log);
|
||||||
|
|
||||||
|
if (log.house === engine.value.myHouse) return;
|
||||||
|
|
||||||
sound.playUrl('/client-assets/mahjong/dahai.mp3', {
|
sound.playUrl('/client-assets/mahjong/dahai.mp3', {
|
||||||
volume: 1,
|
volume: 1,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
|
@ -179,15 +229,10 @@ async function onStreamLog(log) {
|
||||||
triggerRef(engine);
|
triggerRef(engine);
|
||||||
|
|
||||||
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
||||||
opTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'dahaiAndTsumo': {
|
function onStreamTsumo(log) {
|
||||||
sound.playUrl('/client-assets/mahjong/dahai.mp3', {
|
console.log('onStreamTsumo', log);
|
||||||
volume: 1,
|
|
||||||
playbackRate: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
//if (log.house !== engine.value.state.turn) { // = desyncが発生している
|
//if (log.house !== engine.value.state.turn) { // = desyncが発生している
|
||||||
// const _room = await misskeyApi('mahjong/show-room', {
|
// const _room = await misskeyApi('mahjong/show-room', {
|
||||||
|
@ -197,23 +242,61 @@ async function onStreamLog(log) {
|
||||||
// return;
|
// return;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
engine.value.op_dahai(log.house, log.dahaiTile);
|
engine.value.op_tsumo(log.house, log.tile);
|
||||||
triggerRef(engine);
|
triggerRef(engine);
|
||||||
|
|
||||||
window.setTimeout(() => {
|
if (log.house === engine.value.myHouse) {
|
||||||
engine.value.op_tsumo(Mahjong.Utils.nextHouse(log.house), log.tsumoTile);
|
iTsumoed.value = true;
|
||||||
triggerRef(engine);
|
}
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
||||||
opTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
function onStreamDahaiAndTsumo(log) {
|
||||||
break;
|
console.log('onStreamDahaiAndTsumo', log);
|
||||||
|
|
||||||
|
//if (log.house !== engine.value.state.turn) { // = desyncが発生している
|
||||||
|
// const _room = await misskeyApi('mahjong/show-room', {
|
||||||
|
// roomId: props.room.id,
|
||||||
|
// });
|
||||||
|
// restoreRoom(_room);
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (log.dahaiHouse !== engine.value.myHouse) {
|
||||||
|
engine.value.op_dahai(log.dahaiHouse, log.dahaiTile);
|
||||||
|
triggerRef(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
engine.value.op_tsumo(Mahjong.Utils.nextHouse(log.dahaiHouse), log.tsumoTile);
|
||||||
|
triggerRef(engine);
|
||||||
|
|
||||||
|
if (Mahjong.Utils.nextHouse(log.dahaiHouse) === engine.value.myHouse) {
|
||||||
|
iTsumoed.value = true;
|
||||||
}
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStreamPonned(log) {
|
||||||
|
console.log('onStreamPonned', log);
|
||||||
|
|
||||||
|
//if (log.house !== engine.value.state.turn) { // = desyncが発生している
|
||||||
|
// const _room = await misskeyApi('mahjong/show-room', {
|
||||||
|
// roomId: props.room.id,
|
||||||
|
// });
|
||||||
|
// restoreRoom(_room);
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (log.target === engine.value.myHouse) return;
|
||||||
|
|
||||||
|
engine.value.op_pon(log.source, log.target);
|
||||||
|
triggerRef(engine);
|
||||||
|
|
||||||
|
myTurnTimerRmain.value = room.value.timeLimitForEachTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreRoom(_room) {
|
function restoreRoom(_room) {
|
||||||
|
@ -224,28 +307,187 @@ function restoreRoom(_room) {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.on('log', onStreamLog);
|
props.connection.on('dahai', onStreamDahai);
|
||||||
|
props.connection.on('tsumo', onStreamTsumo);
|
||||||
|
props.connection.on('dahaiAndTsumo', onStreamDahaiAndTsumo);
|
||||||
|
props.connection.on('ponned', onStreamPonned);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.on('log', onStreamLog);
|
props.connection.on('dahai', onStreamDahai);
|
||||||
|
props.connection.on('tsumo', onStreamTsumo);
|
||||||
|
props.connection.on('dahaiAndTsumo', onStreamDahaiAndTsumo);
|
||||||
|
props.connection.on('ponned', onStreamPonned);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDeactivated(() => {
|
onDeactivated(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.off('log', onStreamLog);
|
props.connection.off('dahai', onStreamDahai);
|
||||||
|
props.connection.off('tsumo', onStreamTsumo);
|
||||||
|
props.connection.off('dahaiAndTsumo', onStreamDahaiAndTsumo);
|
||||||
|
props.connection.off('ponned', onStreamPonned);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (props.connection != null) {
|
if (props.connection != null) {
|
||||||
props.connection.off('log', onStreamLog);
|
props.connection.off('dahai', onStreamDahai);
|
||||||
|
props.connection.off('tsumo', onStreamTsumo);
|
||||||
|
props.connection.off('dahaiAndTsumo', onStreamDahaiAndTsumo);
|
||||||
|
props.connection.off('ponned', onStreamPonned);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
background: #3C7A43;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taku {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
min-height: 600px;
|
||||||
|
margin: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handTilesOfToimen {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handTilesOfKamitya {
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handTilesOfSimotya {
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handTilesOfMe {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.huroTilesOfMe {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoTilesContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform-origin: center;
|
||||||
|
scale: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoTilesContainerOfToimen {
|
||||||
|
position: absolute;
|
||||||
|
bottom: calc(50% + 100px);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
.hoTilesOfToimen {
|
||||||
|
rotate: 180deg;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoTilesContainerOfKamitya {
|
||||||
|
position: absolute;
|
||||||
|
right: calc(50% + 100px);
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
height: min-content;
|
||||||
|
}
|
||||||
|
.hoTilesOfKamitya {
|
||||||
|
rotate: 90deg;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoTilesContainerOfSimotya {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% + 100px);
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
height: min-content;
|
||||||
|
}
|
||||||
|
.hoTilesOfSimotya {
|
||||||
|
rotate: -90deg;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoTilesContainerOfMe {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% + 100px);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
.hoTilesOfMe {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideTile {
|
||||||
|
margin-bottom: -26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoTile {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
aspect-ratio: 0.7;
|
||||||
|
background: #fff;
|
||||||
|
margin-bottom: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.myTile {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 35px;
|
||||||
|
aspect-ratio: 0.7;
|
||||||
|
}
|
||||||
|
.myTileBg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.myTileFg {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 70%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -7,6 +7,28 @@ import CRC32 from 'crc-32';
|
||||||
import { Tile, House, TILE_TYPES } from './common.js';
|
import { Tile, House, TILE_TYPES } from './common.js';
|
||||||
import * as Utils from './utils.js';
|
import * as Utils from './utils.js';
|
||||||
|
|
||||||
|
type Huro = {
|
||||||
|
type: 'pon';
|
||||||
|
tile: Tile;
|
||||||
|
from: House;
|
||||||
|
} | {
|
||||||
|
type: 'cii';
|
||||||
|
tiles: [Tile, Tile, Tile];
|
||||||
|
from: House;
|
||||||
|
} | {
|
||||||
|
type: 'kan';
|
||||||
|
tile: Tile;
|
||||||
|
from: House;
|
||||||
|
} | {
|
||||||
|
type: 'kakan';
|
||||||
|
tile: Tile;
|
||||||
|
from: House;
|
||||||
|
} | {
|
||||||
|
type: 'ankan';
|
||||||
|
tile: Tile;
|
||||||
|
from: House;
|
||||||
|
};
|
||||||
|
|
||||||
export type MasterState = {
|
export type MasterState = {
|
||||||
user1House: House;
|
user1House: House;
|
||||||
user2House: House;
|
user2House: House;
|
||||||
|
@ -21,14 +43,10 @@ export type MasterState = {
|
||||||
sHoTiles: Tile[];
|
sHoTiles: Tile[];
|
||||||
wHoTiles: Tile[];
|
wHoTiles: Tile[];
|
||||||
nHoTiles: Tile[];
|
nHoTiles: Tile[];
|
||||||
ePonnedTiles: { tile: Tile; from: House; }[];
|
eHuros: Huro[];
|
||||||
sPonnedTiles: { tile: Tile; from: House; }[];
|
sHuros: Huro[];
|
||||||
wPonnedTiles: { tile: Tile; from: House; }[];
|
wHuros: Huro[];
|
||||||
nPonnedTiles: { tile: Tile; from: House; }[];
|
nHuros: Huro[];
|
||||||
eCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
sCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
wCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
nCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
eRiichi: boolean;
|
eRiichi: boolean;
|
||||||
sRiichi: boolean;
|
sRiichi: boolean;
|
||||||
wRiichi: boolean;
|
wRiichi: boolean;
|
||||||
|
@ -38,6 +56,7 @@ export type MasterState = {
|
||||||
wPoints: number;
|
wPoints: number;
|
||||||
nPoints: number;
|
nPoints: number;
|
||||||
turn: House | null;
|
turn: House | null;
|
||||||
|
nextTurnAfterAsking: House | null;
|
||||||
|
|
||||||
ronAsking: {
|
ronAsking: {
|
||||||
/**
|
/**
|
||||||
|
@ -118,14 +137,10 @@ export class MasterGameEngine {
|
||||||
sHoTiles: [],
|
sHoTiles: [],
|
||||||
wHoTiles: [],
|
wHoTiles: [],
|
||||||
nHoTiles: [],
|
nHoTiles: [],
|
||||||
ePonnedTiles: [],
|
eHuros: [],
|
||||||
sPonnedTiles: [],
|
sHuros: [],
|
||||||
wPonnedTiles: [],
|
wHuros: [],
|
||||||
nPonnedTiles: [],
|
nHuros: [],
|
||||||
eCiiedTiles: [],
|
|
||||||
sCiiedTiles: [],
|
|
||||||
wCiiedTiles: [],
|
|
||||||
nCiiedTiles: [],
|
|
||||||
eRiichi: false,
|
eRiichi: false,
|
||||||
sRiichi: false,
|
sRiichi: false,
|
||||||
wRiichi: false,
|
wRiichi: false,
|
||||||
|
@ -135,14 +150,18 @@ export class MasterGameEngine {
|
||||||
wPoints: 25000,
|
wPoints: 25000,
|
||||||
nPoints: 25000,
|
nPoints: 25000,
|
||||||
turn: 'e',
|
turn: 'e',
|
||||||
|
nextTurnAfterAsking: null,
|
||||||
ponAsking: null,
|
ponAsking: null,
|
||||||
ciiAsking: null,
|
ciiAsking: null,
|
||||||
|
kanAsking: null,
|
||||||
|
ronAsking: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private tsumo(): Tile {
|
private tsumo(): Tile {
|
||||||
const tile = this.state.tiles.pop();
|
const tile = this.state.tiles.pop();
|
||||||
if (tile == null) throw new Error('No tiles left');
|
if (tile == null) throw new Error('No tiles left');
|
||||||
|
if (this.state.turn == null) throw new Error('Not your turn');
|
||||||
this.getHandTilesOf(this.state.turn).push(tile);
|
this.getHandTilesOf(this.state.turn).push(tile);
|
||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
|
@ -235,6 +254,8 @@ export class MasterGameEngine {
|
||||||
target: canCiiHouse,
|
target: canCiiHouse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
this.state.turn = null;
|
||||||
|
this.state.nextTurnAfterAsking = Utils.nextHouse(house);
|
||||||
return {
|
return {
|
||||||
asking: true,
|
asking: true,
|
||||||
canRonHouses: canRonHouses,
|
canRonHouses: canRonHouses,
|
||||||
|
@ -278,8 +299,8 @@ export class MasterGameEngine {
|
||||||
const source = this.state.kanAsking.source;
|
const source = this.state.kanAsking.source;
|
||||||
const target = this.state.kanAsking.target;
|
const target = this.state.kanAsking.target;
|
||||||
|
|
||||||
const tile = this.getHoTilesOf(source).pop();
|
const tile = this.getHoTilesOf(source).pop()!;
|
||||||
this.getKannedTilesOf(target).push({ tile, from: source });
|
this.getHurosOf(target).push({ type: 'kan', tile, from: source });
|
||||||
|
|
||||||
clearAsking();
|
clearAsking();
|
||||||
this.state.turn = target;
|
this.state.turn = target;
|
||||||
|
@ -291,14 +312,17 @@ export class MasterGameEngine {
|
||||||
const source = this.state.ponAsking.source;
|
const source = this.state.ponAsking.source;
|
||||||
const target = this.state.ponAsking.target;
|
const target = this.state.ponAsking.target;
|
||||||
|
|
||||||
const tile = this.getHoTilesOf(source).pop();
|
const tile = this.getHoTilesOf(source).pop()!;
|
||||||
this.getPonnedTilesOf(target).push({ tile, from: source });
|
this.getHandTilesOf(target).splice(this.getHandTilesOf(target).indexOf(tile), 1);
|
||||||
|
this.getHandTilesOf(target).splice(this.getHandTilesOf(target).indexOf(tile), 1);
|
||||||
|
this.getHurosOf(target).push({ type: 'pon', tile, from: source });
|
||||||
|
|
||||||
clearAsking();
|
clearAsking();
|
||||||
this.state.turn = target;
|
this.state.turn = target;
|
||||||
return {
|
return {
|
||||||
type: 'ponned',
|
type: 'ponned',
|
||||||
house: this.state.turn,
|
source,
|
||||||
|
target,
|
||||||
tile,
|
tile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -307,20 +331,22 @@ export class MasterGameEngine {
|
||||||
const source = this.state.ciiAsking.source;
|
const source = this.state.ciiAsking.source;
|
||||||
const target = this.state.ciiAsking.target;
|
const target = this.state.ciiAsking.target;
|
||||||
|
|
||||||
const tile = this.getHoTilesOf(source).pop();
|
const tile = this.getHoTilesOf(source).pop()!;
|
||||||
this.getCiiedTilesOf(target).push({ tile, from: source });
|
this.getCiiedTilesOf(target).push({ tile, from: source });
|
||||||
|
|
||||||
clearAsking();
|
clearAsking();
|
||||||
this.state.turn = target;
|
this.state.turn = target;
|
||||||
return {
|
return {
|
||||||
type: 'ciied',
|
type: 'ciied',
|
||||||
house: this.state.turn,
|
source,
|
||||||
|
target,
|
||||||
tile,
|
tile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAsking();
|
clearAsking();
|
||||||
this.state.turn = Utils.nextHouse(this.state.turn);
|
this.state.turn = this.state.nextTurnAfterAsking;
|
||||||
|
this.state.nextTurnAfterAsking = null;
|
||||||
|
|
||||||
const tile = this.tsumo();
|
const tile = this.tsumo();
|
||||||
|
|
||||||
|
@ -364,6 +390,7 @@ export class MasterGameEngine {
|
||||||
case 's': return this.state.sHandTiles;
|
case 's': return this.state.sHandTiles;
|
||||||
case 'w': return this.state.wHandTiles;
|
case 'w': return this.state.wHandTiles;
|
||||||
case 'n': return this.state.nHandTiles;
|
case 'n': return this.state.nHandTiles;
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,33 +400,17 @@ export class MasterGameEngine {
|
||||||
case 's': return this.state.sHoTiles;
|
case 's': return this.state.sHoTiles;
|
||||||
case 'w': return this.state.wHoTiles;
|
case 'w': return this.state.wHoTiles;
|
||||||
case 'n': return this.state.nHoTiles;
|
case 'n': return this.state.nHoTiles;
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPonnedTilesOf(house: House): { tile: Tile; from: House; }[] {
|
public getHurosOf(house: House): Huro[] {
|
||||||
switch (house) {
|
switch (house) {
|
||||||
case 'e': return this.state.ePonnedTiles;
|
case 'e': return this.state.eHuros;
|
||||||
case 's': return this.state.sPonnedTiles;
|
case 's': return this.state.sHuros;
|
||||||
case 'w': return this.state.wPonnedTiles;
|
case 'w': return this.state.wHuros;
|
||||||
case 'n': return this.state.nPonnedTiles;
|
case 'n': return this.state.nHuros;
|
||||||
}
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
|
||||||
|
|
||||||
public getCiiedTilesOf(house: House): { tiles: [Tile, Tile, Tile]; from: House; }[] {
|
|
||||||
switch (house) {
|
|
||||||
case 'e': return this.state.eCiiedTiles;
|
|
||||||
case 's': return this.state.sCiiedTiles;
|
|
||||||
case 'w': return this.state.wCiiedTiles;
|
|
||||||
case 'n': return this.state.nCiiedTiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getKannedTilesOf(house: House): { tile: Tile; from: House; }[] {
|
|
||||||
switch (house) {
|
|
||||||
case 'e': return this.state.ePonnedTiles;
|
|
||||||
case 's': return this.state.sPonnedTiles;
|
|
||||||
case 'w': return this.state.wPonnedTiles;
|
|
||||||
case 'n': return this.state.nPonnedTiles;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,14 +431,10 @@ export class MasterGameEngine {
|
||||||
sHoTiles: this.state.sHoTiles,
|
sHoTiles: this.state.sHoTiles,
|
||||||
wHoTiles: this.state.wHoTiles,
|
wHoTiles: this.state.wHoTiles,
|
||||||
nHoTiles: this.state.nHoTiles,
|
nHoTiles: this.state.nHoTiles,
|
||||||
ePonnedTiles: this.state.ePonnedTiles,
|
eHuros: this.state.eHuros,
|
||||||
sPonnedTiles: this.state.sPonnedTiles,
|
sHuros: this.state.sHuros,
|
||||||
wPonnedTiles: this.state.wPonnedTiles,
|
wHuros: this.state.wHuros,
|
||||||
nPonnedTiles: this.state.nPonnedTiles,
|
nHuros: this.state.nHuros,
|
||||||
eCiiedTiles: this.state.eCiiedTiles,
|
|
||||||
sCiiedTiles: this.state.sCiiedTiles,
|
|
||||||
wCiiedTiles: this.state.wCiiedTiles,
|
|
||||||
nCiiedTiles: this.state.nCiiedTiles,
|
|
||||||
eRiichi: this.state.eRiichi,
|
eRiichi: this.state.eRiichi,
|
||||||
sRiichi: this.state.sRiichi,
|
sRiichi: this.state.sRiichi,
|
||||||
wRiichi: this.state.wRiichi,
|
wRiichi: this.state.wRiichi,
|
||||||
|
@ -472,14 +479,10 @@ export type PlayerState = {
|
||||||
sHoTiles: Tile[];
|
sHoTiles: Tile[];
|
||||||
wHoTiles: Tile[];
|
wHoTiles: Tile[];
|
||||||
nHoTiles: Tile[];
|
nHoTiles: Tile[];
|
||||||
ePonnedTiles: { tile: Tile; from: House; }[];
|
eHuros: Huro[];
|
||||||
sPonnedTiles: { tile: Tile; from: House; }[];
|
sHuros: Huro[];
|
||||||
wPonnedTiles: { tile: Tile; from: House; }[];
|
wHuros: Huro[];
|
||||||
nPonnedTiles: { tile: Tile; from: House; }[];
|
nHuros: Huro[];
|
||||||
eCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
sCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
wCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
nCiiedTiles: { tiles: [Tile, Tile, Tile]; from: House; }[];
|
|
||||||
eRiichi: boolean;
|
eRiichi: boolean;
|
||||||
sRiichi: boolean;
|
sRiichi: boolean;
|
||||||
wRiichi: boolean;
|
wRiichi: boolean;
|
||||||
|
@ -490,7 +493,7 @@ export type PlayerState = {
|
||||||
nPoints: number;
|
nPoints: number;
|
||||||
latestDahaiedTile: Tile | null;
|
latestDahaiedTile: Tile | null;
|
||||||
turn: House | null;
|
turn: House | null;
|
||||||
canPonTo: House | null;
|
canPonSource: House | null;
|
||||||
canCiiTo: House | null;
|
canCiiTo: House | null;
|
||||||
canKanTo: House | null;
|
canKanTo: House | null;
|
||||||
canRonTo: House | null;
|
canRonTo: House | null;
|
||||||
|
@ -543,6 +546,7 @@ export class PlayerGameEngine {
|
||||||
case 's': return this.state.sHandTiles;
|
case 's': return this.state.sHandTiles;
|
||||||
case 'w': return this.state.wHandTiles;
|
case 'w': return this.state.wHandTiles;
|
||||||
case 'n': return this.state.nHandTiles;
|
case 'n': return this.state.nHandTiles;
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,37 +556,23 @@ export class PlayerGameEngine {
|
||||||
case 's': return this.state.sHoTiles;
|
case 's': return this.state.sHoTiles;
|
||||||
case 'w': return this.state.wHoTiles;
|
case 'w': return this.state.wHoTiles;
|
||||||
case 'n': return this.state.nHoTiles;
|
case 'n': return this.state.nHoTiles;
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPonnedTilesOf(house: House): { tile: Tile; from: House; }[] {
|
public getHurosOf(house: House): Huro[] {
|
||||||
switch (house) {
|
switch (house) {
|
||||||
case 'e': return this.state.ePonnedTiles;
|
case 'e': return this.state.eHuros;
|
||||||
case 's': return this.state.sPonnedTiles;
|
case 's': return this.state.sHuros;
|
||||||
case 'w': return this.state.wPonnedTiles;
|
case 'w': return this.state.wHuros;
|
||||||
case 'n': return this.state.nPonnedTiles;
|
case 'n': return this.state.nHuros;
|
||||||
}
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
|
||||||
|
|
||||||
public getCiiedTilesOf(house: House): { tiles: [Tile, Tile, Tile]; from: House; }[] {
|
|
||||||
switch (house) {
|
|
||||||
case 'e': return this.state.eCiiedTiles;
|
|
||||||
case 's': return this.state.sCiiedTiles;
|
|
||||||
case 'w': return this.state.wCiiedTiles;
|
|
||||||
case 'n': return this.state.nCiiedTiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getKannedTilesOf(house: House): { tile: Tile; from: House; }[] {
|
|
||||||
switch (house) {
|
|
||||||
case 'e': return this.state.ePonnedTiles;
|
|
||||||
case 's': return this.state.sPonnedTiles;
|
|
||||||
case 'w': return this.state.wPonnedTiles;
|
|
||||||
case 'n': return this.state.nPonnedTiles;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public op_tsumo(house: House, tile: Tile) {
|
public op_tsumo(house: House, tile: Tile) {
|
||||||
|
console.log('op_tsumo', this.state.turn, house, tile);
|
||||||
|
this.state.turn = house;
|
||||||
if (house === this.myHouse) {
|
if (house === this.myHouse) {
|
||||||
this.myHandTiles.push(tile);
|
this.myHandTiles.push(tile);
|
||||||
} else {
|
} else {
|
||||||
|
@ -591,8 +581,7 @@ export class PlayerGameEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public op_dahai(house: House, tile: Tile) {
|
public op_dahai(house: House, tile: Tile) {
|
||||||
console.log(this.state.turn, house, tile);
|
console.log('op_dahai', this.state.turn, house, tile);
|
||||||
|
|
||||||
if (this.state.turn !== house) throw new PlayerGameEngine.InvalidOperationError();
|
if (this.state.turn !== house) throw new PlayerGameEngine.InvalidOperationError();
|
||||||
|
|
||||||
if (house === this.myHouse) {
|
if (house === this.myHouse) {
|
||||||
|
@ -603,7 +592,7 @@ export class PlayerGameEngine {
|
||||||
this.getHoTilesOf(house).push(tile);
|
this.getHoTilesOf(house).push(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.turn = Utils.nextHouse(this.state.turn);
|
this.state.turn = null;
|
||||||
|
|
||||||
if (house === this.myHouse) {
|
if (house === this.myHouse) {
|
||||||
} else {
|
} else {
|
||||||
|
@ -611,22 +600,34 @@ export class PlayerGameEngine {
|
||||||
|
|
||||||
// TODO: canCii
|
// TODO: canCii
|
||||||
|
|
||||||
if (canPon) this.state.canPonTo = house;
|
if (canPon) this.state.canPonSource = house;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ポンします
|
||||||
|
* @param source 牌を捨てた人
|
||||||
|
* @param target ポンした人
|
||||||
|
*/
|
||||||
public op_pon(source: House, target: House) {
|
public op_pon(source: House, target: House) {
|
||||||
this.state.canPonTo = null;
|
this.state.canPonSource = null;
|
||||||
|
|
||||||
const lastTile = this.getHoTilesOf(source).pop();
|
const lastTile = this.getHoTilesOf(source).pop();
|
||||||
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
if (lastTile == null) throw new PlayerGameEngine.InvalidOperationError();
|
||||||
this.getPonnedTilesOf(target).push({ tile: lastTile, from: source });
|
if (target === this.myHouse) {
|
||||||
|
this.myHandTiles.splice(this.myHandTiles.indexOf(lastTile), 1);
|
||||||
|
this.myHandTiles.splice(this.myHandTiles.indexOf(lastTile), 1);
|
||||||
|
} else {
|
||||||
|
this.getHandTilesOf(target).unshift();
|
||||||
|
this.getHandTilesOf(target).unshift();
|
||||||
|
}
|
||||||
|
this.getHurosOf(target).push({ type: 'pon', tile: lastTile, from: source });
|
||||||
|
|
||||||
this.state.turn = target;
|
this.state.turn = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public op_nop() {
|
public op_nop() {
|
||||||
this.state.canPonTo = null;
|
this.state.canPonSource = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export function nextHouse(house: House): House {
|
||||||
case 's': return 'w';
|
case 's': return 'w';
|
||||||
case 'w': return 'n';
|
case 'w': return 'n';
|
||||||
case 'n': return 'e';
|
case 'n': return 'e';
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ export function prevHouse(house: House): House {
|
||||||
case 's': return 'e';
|
case 's': return 'e';
|
||||||
case 'w': return 's';
|
case 'w': return 's';
|
||||||
case 'n': return 'w';
|
case 'n': return 'w';
|
||||||
|
default: throw new Error(`unrecognized house: ${house}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,38 +181,36 @@ export function getHoraSets(handTiles: Tile[]): HoraSet[] {
|
||||||
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
|
tempHandTilesWithoutKotsu.splice(tempHandTilesWithoutKotsu.indexOf(kotsuTile), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 連番に並ぶようにソート
|
|
||||||
tempHandTilesWithoutKotsu.sort((a, b) => {
|
tempHandTilesWithoutKotsu.sort((a, b) => {
|
||||||
const aIndex = TILE_TYPES.indexOf(a);
|
const aIndex = TILE_TYPES.indexOf(a);
|
||||||
const bIndex = TILE_TYPES.indexOf(b);
|
const bIndex = TILE_TYPES.indexOf(b);
|
||||||
return aIndex - bIndex;
|
return aIndex - bIndex;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tempTempHandTilesWithoutKotsuAndShuntsu: (Tile | null)[] = [...tempHandTilesWithoutKotsu];
|
const tempHandTilesWithoutKotsuAndShuntsu: (Tile | null)[] = [...tempHandTilesWithoutKotsu];
|
||||||
|
|
||||||
const shuntsus: [Tile, Tile, Tile][] = [];
|
const shuntsus: [Tile, Tile, Tile][] = [];
|
||||||
let i = 0;
|
while (tempHandTilesWithoutKotsuAndShuntsu.length > 0) {
|
||||||
while (i < tempHandTilesWithoutKotsu.length) {
|
let isShuntu = false;
|
||||||
const headThree = tempHandTilesWithoutKotsu.slice(i, i + 3);
|
|
||||||
if (headThree.length !== 3) break;
|
|
||||||
|
|
||||||
for (const shuntuPattern of SHUNTU_PATTERNS) {
|
for (const shuntuPattern of SHUNTU_PATTERNS) {
|
||||||
if (headThree[0] === shuntuPattern[0] && headThree[1] === shuntuPattern[1] && headThree[2] === shuntuPattern[2]) {
|
if (
|
||||||
|
tempHandTilesWithoutKotsuAndShuntsu[0] === shuntuPattern[0] &&
|
||||||
|
tempHandTilesWithoutKotsuAndShuntsu.includes(shuntuPattern[1]) &&
|
||||||
|
tempHandTilesWithoutKotsuAndShuntsu.includes(shuntuPattern[2])
|
||||||
|
) {
|
||||||
shuntsus.push(shuntuPattern);
|
shuntsus.push(shuntuPattern);
|
||||||
tempTempHandTilesWithoutKotsuAndShuntsu[i] = null;
|
tempHandTilesWithoutKotsuAndShuntsu.splice(0, 1);
|
||||||
tempTempHandTilesWithoutKotsuAndShuntsu[i + 1] = null;
|
tempHandTilesWithoutKotsuAndShuntsu.splice(tempHandTilesWithoutKotsuAndShuntsu.indexOf(shuntuPattern[1]), 1);
|
||||||
tempTempHandTilesWithoutKotsuAndShuntsu[i + 2] = null;
|
tempHandTilesWithoutKotsuAndShuntsu.splice(tempHandTilesWithoutKotsuAndShuntsu.indexOf(shuntuPattern[2]), 1);
|
||||||
i += 3;
|
isShuntu = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
if (!isShuntu) tempHandTilesWithoutKotsuAndShuntsu.splice(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempHandTilesWithoutKotsuAndShuntsu = tempTempHandTilesWithoutKotsuAndShuntsu.filter(t => t != null) as Tile[];
|
if (shuntsus.length * 3 === tempHandTilesWithoutKotsu.length) { // アガリ形
|
||||||
|
|
||||||
if (tempHandTilesWithoutKotsuAndShuntsu.length === 0) { // アガリ形
|
|
||||||
horaSets.push({
|
horaSets.push({
|
||||||
head,
|
head,
|
||||||
mentsus: [...kotsuPattern.map(t => [t, t, t] as [Tile, Tile, Tile]), ...shuntsus],
|
mentsus: [...kotsuPattern.map(t => [t, t, t] as [Tile, Tile, Tile]), ...shuntsus],
|
||||||
|
|