This commit is contained in:
syuilo 2024-01-30 11:27:08 +09:00
parent 547b74c9b2
commit d7337e5f81
3 changed files with 126 additions and 111 deletions

View file

@ -77,6 +77,10 @@ type NextKyokuConfirmation = {
user4: boolean; 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() @Injectable()
export class MahjongService implements OnApplicationShutdown, OnModuleInit { export class MahjongService implements OnApplicationShutdown, OnModuleInit {
private notificationService: NotificationService; private notificationService: NotificationService;
@ -302,62 +306,71 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
pon: answers.pon ?? false, pon: answers.pon ?? false,
cii: answers.cii ?? false, cii: answers.cii ?? false,
kan: answers.kan ?? false, kan: answers.kan ?? false,
ron: [...(answers.ron.e ? ['e'] : []), ...(answers.ron.s ? ['s'] : []), ...(answers.ron.w ? ['w'] : []), ...(answers.ron.n ? ['n'] : [])], 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; room.gameState = engine.state;
await this.saveRoom(room); await this.saveRoom(room);
if (res.type === 'tsumo') { switch (res.type) {
this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile }); case 'tsumo':
this.next(room, engine); this.globalEventService.publishMahjongRoomStream(room.id, 'tsumo', { house: res.house, tile: res.tile });
} else if (res.type === 'ponned') { this.next(room, engine);
this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { caller: res.caller, callee: res.callee, tile: res.tile }); break;
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; case 'ponned':
this.waitForTurn(room, userId, engine); this.globalEventService.publishMahjongRoomStream(room.id, 'ponned', { caller: res.caller, callee: res.callee, tile: res.tile });
} else if (res.type === 'kanned') { this.waitForTurn(room, res.turn, engine);
this.globalEventService.publishMahjongRoomStream(room.id, 'kanned', { caller: res.caller, callee: res.callee, tile: res.tile, rinsyan: res.rinsyan }); break;
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; case 'kanned':
this.waitForTurn(room, userId, engine); this.globalEventService.publishMahjongRoomStream(room.id, 'kanned', { caller: res.caller, callee: res.callee, tile: res.tile, rinsyan: res.rinsyan });
} else if (res.type === 'ronned') { this.waitForTurn(room, res.turn, engine);
this.globalEventService.publishMahjongRoomStream(room.id, 'ronned', { break;
callers: res.callers, case 'ronned':
callee: res.callee, this.globalEventService.publishMahjongRoomStream(room.id, 'ronned', {
handTiles: { callers: res.callers,
e: engine.state.handTiles.e, callee: res.callee,
s: engine.state.handTiles.s, handTiles: {
w: engine.state.handTiles.w, e: engine.state.handTiles.e,
n: engine.state.handTiles.n, s: engine.state.handTiles.s,
}, w: engine.state.handTiles.w,
}); n: engine.state.handTiles.n,
this.endKyoku(room, engine); },
});
this.endKyoku(room, engine);
break;
case 'ryukyoku':
this.globalEventService.publishMahjongRoomStream(room.id, 'ryukyoku', {
});
this.endKyoku(room, engine);
break;
} }
} }
@bindThis @bindThis
private async next(room: Room, engine: Mahjong.MasterGameEngine) { 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 turn = engine.state.turn;
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; 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: ちゃんと思考するようにする // TODO: ちゃんと思考するようにする
setTimeout(() => { setTimeout(() => {
const house = engine.state.turn; this.dahai(room, engine, turn, engine.state.handTiles[turn].at(-1));
this.dahai(room, engine, engine.state.turn, engine.state.handTiles[house].at(-1));
}, 500); }, 500);
} else { } 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); const horaSets = Mahjong.Utils.getHoraSets(handTiles);
if (horaSets.length === 0) { if (horaSets.length === 0) {
setTimeout(() => { setTimeout(() => {
this.dahai(room, engine, engine.state.turn, handTiles.at(-1)); this.dahai(room, engine, turn, handTiles.at(-1));
}, 500); }, 500);
} else { } else {
this.waitForTurn(room, userId, engine); this.waitForTurn(room, turn, engine);
} }
} else { } 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を格納する構造としている * NOTE: 時間切れチェックが行われたときにタイミングによっては次のwaitingが始まっている場合があることを考慮しSetに一意のIDを格納する構造としている
* @param room * @param room
* @param userId * @param house
* @param engine * @param engine
*/ */
@bindThis @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); 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); this.redisClient.sadd(`mahjong:gameTurnWaiting:${room.id}`, id);
const waitingStartedAt = Date.now(); const waitingStartedAt = Date.now();
const interval = setInterval(async () => { const interval = setInterval(async () => {
@ -624,9 +637,8 @@ export class MahjongService implements OnApplicationShutdown, OnModuleInit {
} }
if (Date.now() - waitingStartedAt > TURN_TIMEOUT_MS) { if (Date.now() - waitingStartedAt > TURN_TIMEOUT_MS) {
await this.redisClient.srem(`mahjong:gameTurnWaiting:${room.id}`, id); await this.redisClient.srem(`mahjong:gameTurnWaiting:${room.id}`, id);
console.log('turn timeout', userId, id); console.log('turn timeout', house, 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 handTiles = engine.state.handTiles[house]; const handTiles = engine.state.handTiles[house];
await this.dahai(room, engine, house, handTiles.at(-1)); await this.dahai(room, engine, house, handTiles.at(-1));
return; return;

View file

@ -229,7 +229,7 @@ export class MasterGameEngine {
ronTile: this.state.hoTiles[callee].at(-1)!, ronTile: this.state.hoTiles[callee].at(-1)!,
riichi: this.state.riichis[house], riichi: this.state.riichis[house],
})); }));
console.log('yakus', yakus); console.log('yakus', house, yakus);
} }
this.endKyoku(); this.endKyoku();
@ -330,7 +330,7 @@ export class MasterGameEngine {
this.state.turn = null; this.state.turn = null;
this.state.nextTurnAfterAsking = Utils.nextHouse(house); this.state.nextTurnAfterAsking = Utils.nextHouse(house);
return { return {
asking: true, asking: true as const,
canRonHouses: canRonHouses, canRonHouses: canRonHouses,
canKanHouse: canKanHouse, canKanHouse: canKanHouse,
canPonHouse: canPonHouse, canPonHouse: canPonHouse,
@ -343,7 +343,7 @@ export class MasterGameEngine {
const tsumoTile = this.tsumo(); const tsumoTile = this.tsumo();
return { return {
asking: false, asking: false as const,
tsumoTile: tsumoTile, 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(); if (this.state.ponAsking == null && this.state.ciiAsking == null && this.state.kanAsking == null && this.state.ronAsking == null) throw new Error();
const clearAsking = () => { const pon = this.state.ponAsking;
this.state.ponAsking = null; const cii = this.state.ciiAsking;
this.state.ciiAsking = null; const kan = this.state.kanAsking;
this.state.kanAsking = null; const ron = this.state.ronAsking;
this.state.ronAsking = null;
};
if (this.state.ronAsking != null && answers.ron.length > 0) { this.state.ponAsking = null;
const callers = this.state.ronAsking.callers; this.state.ciiAsking = null;
const callee = this.state.ronAsking.callee; 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 { return {
type: 'ronned', type: 'ronned' as const,
callers, callers: ron.callers,
callee, callee: ron.callee,
turn: null,
}; };
} } else if (kan != null && answers.kan) {
// 大明槓
// 大明槓 const tile = this.state.hoTiles[kan.callee].pop()!;
if (this.state.kanAsking != null && answers.kan) { this.state.huros[kan.caller].push({ type: 'minkan', tile, from: kan.callee });
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 rinsyan = this.tsumo(); const rinsyan = this.tsumo();
clearAsking(); this.state.turn = kan.caller;
this.state.turn = caller;
return { return {
type: 'kanned', type: 'kanned' as const,
caller, caller: kan.caller,
callee, callee: kan.callee,
tile, tile,
rinsyan, 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) { this.state.turn = pon.caller;
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;
return { return {
type: 'ponned', type: 'ponned' as const,
caller, caller: pon.caller,
callee, callee: pon.callee,
tile, 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) { this.state.turn = cii.caller;
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;
return { return {
type: 'ciied', type: 'ciied' as const,
caller, caller: cii.caller,
callee, callee: cii.callee,
tile, 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 { public createPlayerState(index: 1 | 2 | 3 | 4): PlayerState {

View file

@ -151,7 +151,12 @@ export class PlayerGameEngine {
* @param callers * @param callers
* @param callee * @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); console.log('commit_ron', this.state.turn, callers, callee);
this.state.canRonSource = null; this.state.canRonSource = null;
@ -161,13 +166,13 @@ export class PlayerGameEngine {
for (const house of callers) { for (const house of callers) {
const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({ const yakus = YAKU_DEFINITIONS.filter(yaku => yaku.calc({
house: house, house: house,
handTiles: this.state.handTiles[house], handTiles: handTiles[house],
huros: this.state.huros[house], huros: this.state.huros[house],
tsumoTile: null, tsumoTile: null,
ronTile: this.state.hoTiles[callee].at(-1)!, ronTile: this.state.hoTiles[callee].at(-1)!,
riichi: this.state.riichis[house], riichi: this.state.riichis[house],
})); }));
console.log('yakus', yakus); console.log('yakus', house, yakus);
} }
} }