wip
This commit is contained in:
parent
547b74c9b2
commit
d7337e5f81
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue