diff --git a/packages/backend/src/core/MahjongService.ts b/packages/backend/src/core/MahjongService.ts index 1c36aea0c1..33449334db 100644 --- a/packages/backend/src/core/MahjongService.ts +++ b/packages/backend/src/core/MahjongService.ts @@ -77,6 +77,10 @@ type NextKyokuConfirmation = { user4: boolean; }; +function getUserIdOfHouse(room: Room, engine: Mahjong.MasterGameEngine, house: Mahjong.Common.House): MiUser['id'] { + return engine.state.user1House === house ? room.user1Id : engine.state.user2House === house ? room.user2Id : engine.state.user3House === house ? room.user3Id : room.user4Id; +} + @Injectable() export class MahjongService implements OnApplicationShutdown, OnModuleInit { private notificationService: NotificationService; @@ -302,62 +306,71 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { pon: answers.pon ?? false, cii: answers.cii ?? false, kan: answers.kan ?? false, - ron: [...(answers.ron.e ? ['e'] : []), ...(answers.ron.s ? ['s'] : []), ...(answers.ron.w ? ['w'] : []), ...(answers.ron.n ? ['n'] : [])], + ron: [...(answers.ron.e ? ['e'] : []), ...(answers.ron.s ? ['s'] : []), ...(answers.ron.w ? ['w'] : []), ...(answers.ron.n ? ['n'] : [])] as Mahjong.Common.House[], }); room.gameState = engine.state; await this.saveRoom(room); - if (res.type === 'tsumo') { - this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile }); - this.next(room, engine); - } else if (res.type === 'ponned') { - this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { caller: res.caller, callee: res.callee, 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; - this.waitForTurn(room, userId, engine); - } else if (res.type === 'kanned') { - this.globalEventService.publishMahjongRoomStream(room.id, 'kanned', { caller: res.caller, callee: res.callee, tile: res.tile, rinsyan: res.rinsyan }); - 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.waitForTurn(room, userId, engine); - } else if (res.type === 'ronned') { - this.globalEventService.publishMahjongRoomStream(room.id, 'ronned', { - callers: res.callers, - callee: res.callee, - handTiles: { - e: engine.state.handTiles.e, - s: engine.state.handTiles.s, - w: engine.state.handTiles.w, - n: engine.state.handTiles.n, - }, - }); - this.endKyoku(room, engine); + switch (res.type) { + case 'tsumo': + this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile }); + this.next(room, engine); + break; + case 'ponned': + this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { caller: res.caller, callee: res.callee, tile: res.tile }); + this.waitForTurn(room, res.turn, engine); + break; + case 'kanned': + this.globalEventService.publishMahjongRoomStream(room.id, 'kanned', { caller: res.caller, callee: res.callee, tile: res.tile, rinsyan: res.rinsyan }); + this.waitForTurn(room, res.turn, engine); + break; + case 'ronned': + this.globalEventService.publishMahjongRoomStream(room.id, 'ronned', { + callers: res.callers, + callee: res.callee, + handTiles: { + e: engine.state.handTiles.e, + s: engine.state.handTiles.s, + w: engine.state.handTiles.w, + n: engine.state.handTiles.n, + }, + }); + this.endKyoku(room, engine); + break; + case 'ryukyoku': + this.globalEventService.publishMahjongRoomStream(room.id, 'ryukyoku', { + }); + this.endKyoku(room, engine); + break; } } @bindThis private async next(room: Room, engine: Mahjong.MasterGameEngine) { - const aiHouses = [[1, room.user1Ai], [2, room.user2Ai], [3, room.user3Ai], [4, room.user4Ai]].filter(([id, ai]) => ai).map(([id, ai]) => engine.getHouse(id)); - const 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 turn = engine.state.turn; + if (turn == null) throw new Error('turn is null'); - if (aiHouses.includes(engine.state.turn)) { + const aiHouses = [[1, room.user1Ai], [2, room.user2Ai], [3, room.user3Ai], [4, room.user4Ai]].filter(([id, ai]) => ai).map(([id, ai]) => engine.getHouse(id)); + + if (aiHouses.includes(turn)) { // TODO: ちゃんと思考するようにする setTimeout(() => { - const house = engine.state.turn; - this.dahai(room, engine, engine.state.turn, engine.state.handTiles[house].at(-1)); + this.dahai(room, engine, turn, engine.state.handTiles[turn].at(-1)); }, 500); } else { - if (engine.state.riichis[engine.state.turn]) { + if (engine.state.riichis[turn]) { // リーチ時はアガリ牌でない限りツモ切り - const handTiles = engine.state.handTiles[engine.state.turn]; + const handTiles = engine.state.handTiles[turn]; const horaSets = Mahjong.Utils.getHoraSets(handTiles); if (horaSets.length === 0) { setTimeout(() => { - this.dahai(room, engine, engine.state.turn, handTiles.at(-1)); + this.dahai(room, engine, turn, handTiles.at(-1)); }, 500); } else { - this.waitForTurn(room, userId, engine); + this.waitForTurn(room, turn, engine); } } else { - this.waitForTurn(room, userId, engine); + this.waitForTurn(room, turn, engine); } } } @@ -607,13 +620,13 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { * 制限時間が過ぎたらツモ切り * NOTE: 時間切れチェックが行われたときにタイミングによっては次のwaitingが始まっている場合があることを考慮し、Setに一意のIDを格納する構造としている * @param room - * @param userId + * @param house * @param engine */ @bindThis - private async waitForTurn(room: Room, userId: MiUser['id'], engine: Mahjong.MasterGameEngine) { + private async waitForTurn(room: Room, house: Mahjong.Common.House, engine: Mahjong.MasterGameEngine) { const id = Math.random().toString(36).slice(2); - console.log('waitForTurn', userId, id); + console.log('waitForTurn', house, id); this.redisClient.sadd(`mahjong:gameTurnWaiting:${room.id}`, id); const waitingStartedAt = Date.now(); const interval = setInterval(async () => { @@ -624,9 +637,8 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit { } if (Date.now() - waitingStartedAt > TURN_TIMEOUT_MS) { await this.redisClient.srem(`mahjong:gameTurnWaiting:${room.id}`, id); - console.log('turn timeout', userId, id); + console.log('turn timeout', house, id); 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 handTiles = engine.state.handTiles[house]; await this.dahai(room, engine, house, handTiles.at(-1)); return; diff --git a/packages/misskey-mahjong/src/engine.master.ts b/packages/misskey-mahjong/src/engine.master.ts index 7ca7f78828..20df90bac8 100644 --- a/packages/misskey-mahjong/src/engine.master.ts +++ b/packages/misskey-mahjong/src/engine.master.ts @@ -229,7 +229,7 @@ export class MasterGameEngine { ronTile: this.state.hoTiles[callee].at(-1)!, riichi: this.state.riichis[house], })); - console.log('yakus', yakus); + console.log('yakus', house, yakus); } this.endKyoku(); @@ -330,7 +330,7 @@ export class MasterGameEngine { this.state.turn = null; this.state.nextTurnAfterAsking = Utils.nextHouse(house); return { - asking: true, + asking: true as const, canRonHouses: canRonHouses, canKanHouse: canKanHouse, canPonHouse: canPonHouse, @@ -343,7 +343,7 @@ export class MasterGameEngine { const tsumoTile = this.tsumo(); return { - asking: false, + asking: false as const, tsumoTile: tsumoTile, }; } @@ -371,93 +371,91 @@ export class MasterGameEngine { }) { if (this.state.ponAsking == null && this.state.ciiAsking == null && this.state.kanAsking == null && this.state.ronAsking == null) throw new Error(); - const clearAsking = () => { - this.state.ponAsking = null; - this.state.ciiAsking = null; - this.state.kanAsking = null; - this.state.ronAsking = null; - }; + const pon = this.state.ponAsking; + const cii = this.state.ciiAsking; + const kan = this.state.kanAsking; + const ron = this.state.ronAsking; - if (this.state.ronAsking != null && answers.ron.length > 0) { - const callers = this.state.ronAsking.callers; - const callee = this.state.ronAsking.callee; + this.state.ponAsking = null; + this.state.ciiAsking = null; + this.state.kanAsking = null; + this.state.ronAsking = null; - this.ron(answers.ron, this.state.ronAsking.callee); + if (ron != null && answers.ron.length > 0) { + this.ron(answers.ron, ron.callee); return { - type: 'ronned', - callers, - callee, + type: 'ronned' as const, + callers: ron.callers, + callee: ron.callee, + turn: null, }; - } + } else if (kan != null && answers.kan) { + // 大明槓 - // 大明槓 - if (this.state.kanAsking != null && answers.kan) { - const caller = this.state.kanAsking.caller; - const callee = this.state.kanAsking.callee; - - const tile = this.state.hoTiles[callee].pop()!; - this.state.huros[caller].push({ type: 'minkan', tile, from: callee }); + const tile = this.state.hoTiles[kan.callee].pop()!; + this.state.huros[kan.caller].push({ type: 'minkan', tile, from: kan.callee }); const rinsyan = this.tsumo(); - clearAsking(); - this.state.turn = caller; + this.state.turn = kan.caller; return { - type: 'kanned', - caller, - callee, + type: 'kanned' as const, + caller: kan.caller, + callee: kan.callee, tile, rinsyan, + turn: this.state.turn, }; - } + } else if (pon != null && answers.pon) { + const tile = this.state.hoTiles[pon.callee].pop()!; + this.state.handTiles[pon.caller].splice(this.state.handTiles[pon.caller].indexOf(tile), 1); + this.state.handTiles[pon.caller].splice(this.state.handTiles[pon.caller].indexOf(tile), 1); + this.state.huros[pon.caller].push({ type: 'pon', tile, from: pon.callee }); - if (this.state.ponAsking != null && answers.pon) { - const caller = this.state.ponAsking.caller; - const callee = this.state.ponAsking.callee; - - const tile = this.state.hoTiles[callee].pop()!; - this.state.handTiles[caller].splice(this.state.handTiles[caller].indexOf(tile), 1); - this.state.handTiles[caller].splice(this.state.handTiles[caller].indexOf(tile), 1); - this.state.huros[caller].push({ type: 'pon', tile, from: callee }); - - clearAsking(); - this.state.turn = caller; + this.state.turn = pon.caller; return { - type: 'ponned', - caller, - callee, + type: 'ponned' as const, + caller: pon.caller, + callee: pon.callee, tile, + turn: this.state.turn, }; - } + } else if (cii != null && answers.cii) { + const tile = this.state.hoTiles[cii.callee].pop()!; + this.state.huros[cii.caller].push({ type: 'cii', tile, from: cii.callee }); - if (this.state.ciiAsking != null && answers.cii) { - const caller = this.state.ciiAsking.caller; - const callee = this.state.ciiAsking.callee; - - const tile = this.state.hoTiles[callee].pop()!; - this.state.huros[caller].push({ type: 'cii', tile, from: callee }); - - clearAsking(); - this.state.turn = caller; + this.state.turn = cii.caller; return { - type: 'ciied', - caller, - callee, + type: 'ciied' as const, + caller: cii.caller, + callee: cii.callee, tile, + turn: this.state.turn, + }; + } else if (this.state.tiles.length === 0) { + // 流局 + + this.state.turn = null; + this.state.nextTurnAfterAsking = null; + + this.endKyoku(); + + return { + type: 'ryukyoku' as const, + }; + } else { + this.state.turn = this.state.nextTurnAfterAsking!; + this.state.nextTurnAfterAsking = null; + + const tile = this.tsumo(); + + return { + type: 'tsumo' as const, + house: this.state.turn, + tile, + turn: this.state.turn, }; } - - clearAsking(); - this.state.turn = this.state.nextTurnAfterAsking; - this.state.nextTurnAfterAsking = null; - - const tile = this.tsumo(); - - return { - type: 'tsumo', - house: this.state.turn, - tile, - }; } public createPlayerState(index: 1 | 2 | 3 | 4): PlayerState { diff --git a/packages/misskey-mahjong/src/engine.player.ts b/packages/misskey-mahjong/src/engine.player.ts index ef19e13a9e..415c962928 100644 --- a/packages/misskey-mahjong/src/engine.player.ts +++ b/packages/misskey-mahjong/src/engine.player.ts @@ -151,7 +151,12 @@ export class PlayerGameEngine { * @param callers ロンした人 * @param callee 牌を捨てた人 */ - public commit_ron(callers: House[], callee: House) { + public commit_ron(callers: House[], callee: House, handTiles: { + e: Tile[]; + s: Tile[]; + w: Tile[]; + n: Tile[]; + }) { console.log('commit_ron', this.state.turn, callers, callee); this.state.canRonSource = null; @@ -161,13 +166,13 @@ export class PlayerGameEngine { for (const house of callers) { const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({ house: house, - handTiles: this.state.handTiles[house], + handTiles: handTiles[house], huros: this.state.huros[house], tsumoTile: null, ronTile: this.state.hoTiles[callee].at(-1)!, riichi: this.state.riichis[house], })); - console.log('yakus', yakus); + console.log('yakus', house, yakus); } }