diff --git a/apps/server/src/application/coordinators/disconnectCoordinator.ts b/apps/server/src/application/coordinators/disconnectCoordinator.ts index f6c899f..a830dda 100644 --- a/apps/server/src/application/coordinators/disconnectCoordinator.ts +++ b/apps/server/src/application/coordinators/disconnectCoordinator.ts @@ -3,7 +3,6 @@ * DISCONNECTイベントの調停を行い,ゲーム離脱処理とルーム離脱処理を順序実行する */ import { - type BombCleanupPort, type DisconnectPlayerPort, type GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; @@ -20,7 +19,6 @@ export type DisconnectCoordinatorParams = { socketId: string; gameManager: DisconnectPlayerPort; - bombState: BombCleanupPort; roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort; gameOutput: Pick; roomOutput: Pick; @@ -30,7 +28,6 @@ export const disconnectCoordinator = ({ socketId, gameManager, - bombState, roomManager, gameOutput, roomOutput, @@ -49,8 +46,4 @@ socketId, output: roomOutput, }); - - if (roomId && !roomManager.getRoomById(roomId)) { - bombState.clearBombRoomState(roomId, "room-deleted"); - } }; diff --git a/apps/server/src/application/coordinators/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index 049dd23..c69f378 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -3,7 +3,6 @@ * START_GAMEイベントの調停を行い,ルーム状態更新とゲーム開始処理を橋渡しする */ import { - type BombCleanupPort, type GameOutputPort, type StartGamePort, } from "@server/domains/game/application/ports/gameUseCasePorts"; @@ -19,7 +18,6 @@ type StartGameCoordinatorParams = { ownerId: string; gameManager: StartGamePort; - bombState: BombCleanupPort; roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort; output: Pick< GameOutputPort, @@ -35,7 +33,6 @@ export const startGameCoordinator = ({ ownerId, gameManager, - bombState, roomManager, output, }: StartGameCoordinatorParams) => { @@ -86,7 +83,6 @@ gameManager, onGameEnd: () => { roomManager.markRoomWaiting(updatedRoom.roomId); - bombState.clearBombRoomState(updatedRoom.roomId, "game-ended"); }, output, }); diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index 389f85c..277878a 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -59,4 +59,14 @@ getRoomPlayers(roomId: string): Player[] { return this.lifecycleService.getRoomPlayers(roomId); } + + // 爆弾設置イベントを配信すべきか判定し,配信時は重複排除状態を更新する + shouldBroadcastBombPlaced(roomId: string, dedupeKey: string, nowMs: number): boolean { + return this.lifecycleService.shouldBroadcastBombPlaced(roomId, dedupeKey, nowMs); + } + + // ルーム単位の連番からサーバー採番の爆弾IDを生成する + issueServerBombId(roomId: string): string { + return this.lifecycleService.issueServerBombId(roomId); + } } \ No newline at end of file diff --git a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts index 7b21bed..d5850f5 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -2,7 +2,6 @@ * gameUseCasePorts * ゲーム系ユースケースが利用する入力ポートと出力ポートの契約を定義する */ -import type { BombRoomStateClearReason } from "@server/domains/game/entities/bomb/BombRoomStateStore"; import type { BombPlacedPayload, gameTypes, @@ -71,14 +70,6 @@ issueServerBombId(roomId: string): string; } -/** 爆弾状態破棄ユースケースが利用する入力ポート */ -export interface BombCleanupPort { - clearBombRoomState(roomId: string, reason: BombRoomStateClearReason): void; -} - -/** 爆弾状態の参照更新と破棄を扱う統合入力ポート */ -export interface BombStatePort extends BombPlacementPort, BombCleanupPort {} - /** 爆弾設置ユースケースの入力値 */ export type PlaceBombInput = { socketId: string; diff --git a/apps/server/src/domains/game/application/services/GameRoomSession.ts b/apps/server/src/domains/game/application/services/GameRoomSession.ts index 75d1b06..959fe41 100644 --- a/apps/server/src/domains/game/application/services/GameRoomSession.ts +++ b/apps/server/src/domains/game/application/services/GameRoomSession.ts @@ -12,6 +12,7 @@ import { GameLoop } from "../../loop/GameLoop"; import { Player } from "../../entities/player/Player.js"; import { MapStore } from "../../entities/map/MapStore"; +import { BombRoomStateStore } from "../../entities/bomb/BombRoomStateStore"; import { createSpawnedPlayer } from "../../entities/player/playerSpawn.js"; import { isValidPosition, @@ -24,6 +25,7 @@ export class GameRoomSession { private players: Map; private mapStore: MapStore; + private bombStateStore: BombRoomStateStore; private gameLoop: GameLoop | null = null; private startTime: number | undefined; @@ -33,6 +35,7 @@ ) { this.players = new Map(); this.mapStore = new MapStore(); + this.bombStateStore = new BombRoomStateStore(); playerIds.forEach((playerId) => { // 現在のプレイヤー構成から人数が最も少ないチームを算出する @@ -114,6 +117,14 @@ return this.players.has(id); } + public shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean { + return this.bombStateStore.shouldBroadcastBombPlaced(dedupeKey, nowMs); + } + + public issueServerBombId(): string { + return this.bombStateStore.issueServerBombId(this.roomId); + } + public dispose(): void { if (this.gameLoop) { this.gameLoop.stop(); diff --git a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts index 748d349..0d4517a 100644 --- a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts +++ b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts @@ -28,6 +28,19 @@ return this.sessions.get(roomId)?.getPlayers() ?? []; } + public shouldBroadcastBombPlaced(roomId: string, dedupeKey: string, nowMs: number): boolean { + return this.sessions.get(roomId)?.shouldBroadcastBombPlaced(dedupeKey, nowMs) ?? false; + } + + public issueServerBombId(roomId: string): string { + const session = this.sessions.get(roomId); + if (!session) { + throw new Error(`Game session not found for roomId: ${roomId}`); + } + + return session.issueServerBombId(); + } + public startRoomSession( roomId: string, playerIds: string[], diff --git a/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts b/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts index cd4e006..d217a95 100644 --- a/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts +++ b/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts @@ -4,65 +4,35 @@ */ import { config } from "@repo/shared"; -/** 爆弾状態破棄理由の識別子 */ -export type BombRoomStateClearReason = "game-ended" | "room-deleted"; - /** ルーム単位の爆弾重複排除状態と採番状態を保持するストア */ export class BombRoomStateStore { - private roomBombDedupTable = new Map>(); - private roomBombSerialTable = new Map(); - private isBombRoomStateDebugEnabled = process.env.NODE_ENV !== "production"; + private bombDedupTable = new Map(); + private bombSerial = 0; /** 爆弾設置イベントを配信すべきか判定し,配信時は重複排除状態を更新する */ - public shouldBroadcastBombPlaced(roomId: string, dedupeKey: string, nowMs: number): boolean { - this.cleanupExpiredBombDedup(roomId, nowMs); + public shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean { + this.cleanupExpiredBombDedup(nowMs); - const roomTable = this.roomBombDedupTable.get(roomId) ?? new Map(); - if (roomTable.has(dedupeKey)) { + if (this.bombDedupTable.has(dedupeKey)) { return false; } const ttlMs = config.GAME_CONFIG.BOMB_FUSE_MS + config.GAME_CONFIG.BOMB_DEDUP_EXTRA_TTL_MS; - roomTable.set(dedupeKey, nowMs + ttlMs); - this.roomBombDedupTable.set(roomId, roomTable); + this.bombDedupTable.set(dedupeKey, nowMs + ttlMs); return true; } - /** ルーム単位の連番からサーバー採番の爆弾IDを生成する */ + /** セッション単位の連番からサーバー採番の爆弾IDを生成する */ public issueServerBombId(roomId: string): string { - const serial = (this.roomBombSerialTable.get(roomId) ?? 0) + 1; - this.roomBombSerialTable.set(roomId, serial); - return `${roomId}:${serial}`; + this.bombSerial += 1; + return `${roomId}:${this.bombSerial}`; } - /** 指定ルームの爆弾採番状態と重複排除状態を破棄する */ - public clearBombRoomState(roomId: string, reason: BombRoomStateClearReason): void { - const hadDedupState = this.roomBombDedupTable.delete(roomId); - const hadSerialState = this.roomBombSerialTable.delete(roomId); - - if (!this.isBombRoomStateDebugEnabled) { - return; - } - - console.debug( - `[BombState] cleared room=${roomId} reason=${reason} dedup=${hadDedupState} serial=${hadSerialState}` - ); - } - - private cleanupExpiredBombDedup(roomId: string, nowMs: number): void { - const roomTable = this.roomBombDedupTable.get(roomId); - if (!roomTable) { - return; - } - - roomTable.forEach((expiresAtMs, dedupeKey) => { + private cleanupExpiredBombDedup(nowMs: number): void { + this.bombDedupTable.forEach((expiresAtMs, dedupeKey) => { if (expiresAtMs <= nowMs) { - roomTable.delete(dedupeKey); + this.bombDedupTable.delete(dedupeKey); } }); - - if (roomTable.size === 0) { - this.roomBombDedupTable.delete(roomId); - } } } diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 6ba5c30..279a30c 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -4,7 +4,6 @@ */ import { Server } from "socket.io"; import type { - SocketConnectionBombPort, SocketConnectionManagerBundle, SocketConnectionGamePort, SocketConnectionRoomPort, @@ -19,13 +18,11 @@ constructor( io: Server, gameManager: SocketConnectionGamePort, - bombState: SocketConnectionBombPort, roomManager: SocketConnectionRoomPort ) { this.io = io; this.managers = { gameManager, - bombState, roomManager, }; } diff --git a/apps/server/src/network/bootstrap/boot.ts b/apps/server/src/network/bootstrap/boot.ts index 414e6eb..29ba1ff 100644 --- a/apps/server/src/network/bootstrap/boot.ts +++ b/apps/server/src/network/bootstrap/boot.ts @@ -4,7 +4,6 @@ */ import type { Server as HttpServer } from "http"; import { GameManager } from "@server/domains/game/GameManager"; -import { BombRoomStateStore } from "@server/domains/game/entities/bomb/BombRoomStateStore"; import { RoomManager } from "@server/domains/room/RoomManager"; import { SocketManager } from "../SocketManager"; import { createIo } from "./createIo"; @@ -14,9 +13,8 @@ // ネットワーク層とドメイン層の依存を構築する const io = createIo(httpServer); const gameManager = new GameManager(); - const bombState = new BombRoomStateStore(); const roomManager = new RoomManager(); - const socketManager = new SocketManager(io, gameManager, bombState, roomManager); + const socketManager = new SocketManager(io, gameManager, roomManager); socketManager.initialize(); }; diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index d1e0fa6..525b6d9 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -13,7 +13,6 @@ } from "@server/domains/room/application/ports/roomUseCasePorts"; import type { BombPlacementPort, - BombCleanupPort, MovePlayerPort, ReadyForGamePort, StartGamePort, @@ -38,8 +37,7 @@ export const registerGameHandlers = ( io: Server, socket: Socket, - gameManager: StartGamePort & ReadyForGamePort & MovePlayerPort, - bombState: BombPlacementPort & BombCleanupPort, + gameManager: StartGamePort & ReadyForGamePort & MovePlayerPort & BombPlacementPort, roomManager: FindRoomByOwnerPort & FindRoomByPlayerPort & RoomPhaseTransitionPort ) => { const common = createCommonHandlerContext(io, socket); @@ -75,7 +73,6 @@ startGameCoordinator({ ownerId: socket.id, gameManager, - bombState, roomManager, output: gameOutputAdapter, }); @@ -112,7 +109,7 @@ placeBombUseCase({ roomResolver: roomManager, - bombStore: bombState, + bombStore: gameManager, input: { socketId: socket.id, payload: data, diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index 5701b69..66850a8 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -19,7 +19,6 @@ export const registerConnectionHandlers = ({ io, gameManager, - bombState, roomManager, }: RegisterConnectionHandlersParams) => { const gameDisconnectOutputAdapter = createGameDisconnectOutputAdapter(io); @@ -34,7 +33,7 @@ }); registerRoomHandlers(io, socket, roomManager); - registerGameHandlers(io, socket, gameManager, bombState, roomManager); + registerGameHandlers(io, socket, gameManager, roomManager); socket.on(protocol.SocketEvents.DISCONNECT, () => { // 切断ログ記録後にドメイン別の後処理を実行する @@ -47,7 +46,6 @@ disconnectCoordinator({ socketId: socket.id, gameManager, - bombState, roomManager, gameOutput: gameDisconnectOutputAdapter, roomOutput: roomDisconnectOutputAdapter, diff --git a/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index 7e872b7..c3713a4 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -4,12 +4,12 @@ */ import type { Server } from "socket.io"; import type { + BombPlacementPort, DisconnectPlayerPort, MovePlayerPort, ReadyForGamePort, StartGamePort, } from "@server/domains/game/application/ports/gameUseCasePorts"; -import type { BombStatePort } from "@server/domains/game/application/ports/gameUseCasePorts"; import type { DisconnectRoomPort, FindRoomByOwnerPort, @@ -24,7 +24,8 @@ export type ConnectionGamePort = & StartGamePort & ReadyForGamePort - & MovePlayerPort; + & MovePlayerPort + & BombPlacementPort; /** 接続時のルーム処理で利用する入力ポート集合 */ export type ConnectionRoomPort = @@ -38,9 +39,6 @@ & ConnectionGamePort & DisconnectPlayerPort; -/** ソケット接続全体で利用する爆弾状態ポート集合 */ -export type SocketConnectionBombPort = BombStatePort; - /** ソケット接続全体で利用するルーム管理ポート集合 */ export type SocketConnectionRoomPort = & ConnectionRoomPort @@ -50,7 +48,6 @@ /** ソケット接続ハンドラで受け取るマネージャ依存の束 */ export type SocketConnectionManagerBundle = { gameManager: SocketConnectionGamePort; - bombState: SocketConnectionBombPort; roomManager: SocketConnectionRoomPort; };