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..7c46ac3 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, @@ -61,24 +60,20 @@ publishGameStartToRoom(roomId: roomTypes.Room["roomId"], payload: GameStartPayload): void; publishCurrentPlayersToSocket(players: CurrentPlayersPayload): void; publishGameStartToSocket(payload: GameStartPayload): void; - publishBombPlacedToRoom(roomId: roomTypes.Room["roomId"], payload: BombPlacedPayload): void; publishPlayerRemovedToRoom(roomId: roomTypes.Room["roomId"], removedPlayerId: RemovePlayerPayload): void; } +/** 爆弾ユースケースが利用する送信出力ポート */ +export interface BombOutputPort { + publishBombPlacedToRoom(roomId: roomTypes.Room["roomId"], payload: BombPlacedPayload): void; +} + /** 爆弾設置ユースケースが利用する爆弾状態入力ポート */ export interface BombPlacementPort { shouldBroadcastBombPlaced(roomId: string, dedupeKey: string, nowMs: number): boolean; 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..54a5abf 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 { BombStateStore } from "../../entities/bomb/BombStateStore"; import { createSpawnedPlayer } from "../../entities/player/playerSpawn.js"; import { isValidPosition, @@ -24,6 +25,7 @@ export class GameRoomSession { private players: Map; private mapStore: MapStore; + private bombStateStore: BombStateStore; private gameLoop: GameLoop | null = null; private startTime: number | undefined; @@ -33,6 +35,7 @@ ) { this.players = new Map(); this.mapStore = new MapStore(); + this.bombStateStore = new BombStateStore(); 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/application/useCases/placeBombUseCase.ts b/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts index 0e9fde1..adbf947 100644 --- a/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts @@ -4,16 +4,21 @@ */ import type { BombPlacementPort, - GameOutputPort, + BombOutputPort, PlaceBombInput, } from "../ports/gameUseCasePorts"; +import { + createBombDedupeKey, + createBombPlacedPayload, +} from "@server/domains/game/entities/bomb/bombPlacement"; +import { resolveRoomIdBySocketId } from "./useCaseRoomResolver"; import type { FindRoomByPlayerPort } from "@server/domains/room/application/ports/roomUseCasePorts"; type PlaceBombUseCaseParams = { roomResolver: FindRoomByPlayerPort; bombStore: BombPlacementPort; input: PlaceBombInput; - output: Pick; + output: BombOutputPort; }; /** 爆弾設置入力を重複排除と採番付きでルームへ配信する */ @@ -23,19 +28,22 @@ input, output, }: PlaceBombUseCaseParams): void => { - const roomId = roomResolver.getRoomByPlayerId(input.socketId)?.roomId; + const roomId = resolveRoomIdBySocketId(roomResolver, input.socketId); if (!roomId) { return; } - const dedupeKey = `${input.socketId}:${input.payload.requestId}`; + const dedupeKey = createBombDedupeKey(input.socketId, input.payload.requestId); if (!bombStore.shouldBroadcastBombPlaced(roomId, dedupeKey, input.nowMs)) { return; } - output.publishBombPlacedToRoom(roomId, { - ...input.payload, - bombId: bombStore.issueServerBombId(roomId), - ownerId: input.socketId, - }); + output.publishBombPlacedToRoom( + roomId, + createBombPlacedPayload({ + payload: input.payload, + bombId: bombStore.issueServerBombId(roomId), + ownerId: input.socketId, + }) + ); }; diff --git a/apps/server/src/domains/game/application/useCases/useCaseRoomResolver.ts b/apps/server/src/domains/game/application/useCases/useCaseRoomResolver.ts new file mode 100644 index 0000000..9096941 --- /dev/null +++ b/apps/server/src/domains/game/application/useCases/useCaseRoomResolver.ts @@ -0,0 +1,13 @@ +/** + * useCaseRoomResolver + * ゲーム系ユースケースで利用するルーム解決処理を提供する + */ +import type { FindRoomByPlayerPort } from "@server/domains/room/application/ports/roomUseCasePorts"; + +/** ソケットIDから所属ルームIDを解決して返す */ +export const resolveRoomIdBySocketId = ( + roomResolver: FindRoomByPlayerPort, + socketId: string +): string | undefined => { + return roomResolver.getRoomByPlayerId(socketId)?.roomId; +}; diff --git a/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts b/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts deleted file mode 100644 index cd4e006..0000000 --- a/apps/server/src/domains/game/entities/bomb/BombRoomStateStore.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * BombRoomStateStore - * ルーム単位の爆弾重複排除状態と採番状態を管理する - */ -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"; - - /** 爆弾設置イベントを配信すべきか判定し,配信時は重複排除状態を更新する */ - public shouldBroadcastBombPlaced(roomId: string, dedupeKey: string, nowMs: number): boolean { - this.cleanupExpiredBombDedup(roomId, nowMs); - - const roomTable = this.roomBombDedupTable.get(roomId) ?? new Map(); - if (roomTable.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); - return true; - } - - /** ルーム単位の連番からサーバー採番の爆弾IDを生成する */ - public issueServerBombId(roomId: string): string { - const serial = (this.roomBombSerialTable.get(roomId) ?? 0) + 1; - this.roomBombSerialTable.set(roomId, serial); - return `${roomId}:${serial}`; - } - - /** 指定ルームの爆弾採番状態と重複排除状態を破棄する */ - 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) => { - if (expiresAtMs <= nowMs) { - roomTable.delete(dedupeKey); - } - }); - - if (roomTable.size === 0) { - this.roomBombDedupTable.delete(roomId); - } - } -} diff --git a/apps/server/src/domains/game/entities/bomb/BombStateStore.ts b/apps/server/src/domains/game/entities/bomb/BombStateStore.ts new file mode 100644 index 0000000..28a0719 --- /dev/null +++ b/apps/server/src/domains/game/entities/bomb/BombStateStore.ts @@ -0,0 +1,32 @@ +/** + * BombStateStore + * セッション単位の爆弾重複排除状態と採番状態を管理する + */ +import { issueServerBombId } from "./bombIdentity.js"; +import { shouldBroadcastBombPlaced } from "./bombDedup.js"; + +/** セッション単位の爆弾重複排除状態と採番状態を保持するストア */ +export class BombStateStore { + private bombDedupTable = new Map(); + private bombSerial = 0; + + /** 爆弾設置イベントを配信すべきか判定し,配信時は重複排除状態を更新する */ + public shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean { + return shouldBroadcastBombPlaced({ + dedupTable: this.bombDedupTable, + dedupeKey, + nowMs, + }); + } + + /** セッション単位の連番からサーバー採番の爆弾IDを生成する */ + public issueServerBombId(roomId: string): string { + // roomId はセッションを外部参照するためのID名前空間として利用する + const { bombId, nextSerial } = issueServerBombId({ + roomId, + currentSerial: this.bombSerial, + }); + this.bombSerial = nextSerial; + return bombId; + } +} diff --git a/apps/server/src/domains/game/entities/bomb/bombDedup.ts b/apps/server/src/domains/game/entities/bomb/bombDedup.ts new file mode 100644 index 0000000..ff57c39 --- /dev/null +++ b/apps/server/src/domains/game/entities/bomb/bombDedup.ts @@ -0,0 +1,40 @@ +/** + * bombDedup + * 爆弾設置要求の重複排除テーブル操作を提供する + */ +import { config } from "@repo/shared"; + +type ShouldBroadcastBombPlacedParams = { + dedupTable: Map; + dedupeKey: string; + nowMs: number; +}; + +/** 重複排除テーブルの期限切れエントリを削除する */ +export const cleanupExpiredBombDedup = ( + dedupTable: Map, + nowMs: number +): void => { + dedupTable.forEach((expiresAtMs, key) => { + if (expiresAtMs <= nowMs) { + dedupTable.delete(key); + } + }); +}; + +/** 爆弾設置イベントを配信すべきか判定し,配信時は重複排除状態を更新する */ +export const shouldBroadcastBombPlaced = ({ + dedupTable, + dedupeKey, + nowMs, +}: ShouldBroadcastBombPlacedParams): boolean => { + cleanupExpiredBombDedup(dedupTable, nowMs); + + if (dedupTable.has(dedupeKey)) { + return false; + } + + const ttlMs = config.GAME_CONFIG.BOMB_FUSE_MS + config.GAME_CONFIG.BOMB_DEDUP_EXTRA_TTL_MS; + dedupTable.set(dedupeKey, nowMs + ttlMs); + return true; +}; diff --git a/apps/server/src/domains/game/entities/bomb/bombIdentity.ts b/apps/server/src/domains/game/entities/bomb/bombIdentity.ts new file mode 100644 index 0000000..eaa9ba2 --- /dev/null +++ b/apps/server/src/domains/game/entities/bomb/bombIdentity.ts @@ -0,0 +1,22 @@ +/** + * bombIdentity + * 爆弾ID採番ロジックを提供する + */ + +type IssueServerBombIdParams = { + // セッションに紐づく外部公開用のID名前空間 + roomId: string; + currentSerial: number; +}; + +/** 次のサーバー採番爆弾IDと更新後シリアルを返す */ +export const issueServerBombId = ({ + roomId, + currentSerial, +}: IssueServerBombIdParams): { bombId: string; nextSerial: number } => { + const nextSerial = currentSerial + 1; + return { + bombId: `${roomId}:${nextSerial}`, + nextSerial, + }; +}; diff --git a/apps/server/src/domains/game/entities/bomb/bombPayloadValidation.ts b/apps/server/src/domains/game/entities/bomb/bombPayloadValidation.ts new file mode 100644 index 0000000..02f7209 --- /dev/null +++ b/apps/server/src/domains/game/entities/bomb/bombPayloadValidation.ts @@ -0,0 +1,28 @@ +/** + * bombPayloadValidation + * 爆弾設置ペイロードの妥当性検証ロジックを提供する + */ +import type { PlaceBombPayload } from "@repo/shared"; + +const isFiniteNumber = (value: unknown): value is number => { + return typeof value === "number" && Number.isFinite(value); +}; + +const isNonEmptyString = (value: unknown): value is string => { + return typeof value === "string" && value.trim().length > 0; +}; + +/** PLACE_BOMBイベントのペイロードが爆弾設置要求であるか判定する */ +export const isPlaceBombPayload = (value: unknown): value is PlaceBombPayload => { + if (typeof value !== "object" || value === null) { + return false; + } + + const candidate = value as Record; + return ( + isNonEmptyString(candidate.requestId) + && isFiniteNumber(candidate.x) + && isFiniteNumber(candidate.y) + && isFiniteNumber(candidate.explodeAtElapsedMs) + ); +}; diff --git a/apps/server/src/domains/game/entities/bomb/bombPlacement.ts b/apps/server/src/domains/game/entities/bomb/bombPlacement.ts new file mode 100644 index 0000000..98492a0 --- /dev/null +++ b/apps/server/src/domains/game/entities/bomb/bombPlacement.ts @@ -0,0 +1,29 @@ +/** + * bombPlacement + * 爆弾設置時の識別キー生成と確定ペイロード組み立てを提供する + */ +import type { BombPlacedPayload, PlaceBombPayload } from "@repo/shared"; + +/** 重複排除に利用する爆弾設置要求キーを生成する */ +export const createBombDedupeKey = (ownerId: string, requestId: string): string => { + return `${ownerId}:${requestId}`; +}; + +type CreateBombPlacedPayloadParams = { + payload: PlaceBombPayload; + bombId: string; + ownerId: string; +}; + +/** 爆弾確定通知で配信するペイロードを生成する */ +export const createBombPlacedPayload = ({ + payload, + bombId, + ownerId, +}: CreateBombPlacedPayloadParams): BombPlacedPayload => { + return { + ...payload, + bombId, + ownerId, + }; +}; 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/createGameOutputAdapter.ts b/apps/server/src/network/handlers/game/createGameOutputAdapter.ts index 3594a06..1e3e779 100644 --- a/apps/server/src/network/handlers/game/createGameOutputAdapter.ts +++ b/apps/server/src/network/handlers/game/createGameOutputAdapter.ts @@ -15,14 +15,17 @@ UpdateMapCellsPayload, UpdatePlayersPayload, } from "@repo/shared"; -import type { GameOutputPort } from "@server/domains/game/application/ports/gameUseCasePorts"; +import type { + BombOutputPort, + GameOutputPort, +} from "@server/domains/game/application/ports/gameUseCasePorts"; import { createEmitToRoom } from "@server/network/adapters/socketEmitters"; import type { CommonHandlerContext } from "../CommonHandler"; type RoomId = roomTypes.Room["roomId"]; /** ゲーム出力アダプターのインターフェース */ -export type GameOutputAdapter = Omit; +export type GameOutputAdapter = Omit & BombOutputPort; /** ゲーム切断時の出力アダプターのインターフェース */ export type GameDisconnectOutputAdapter = Pick; 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; }; diff --git a/apps/server/src/network/validation/socketPayloadValidators.ts b/apps/server/src/network/validation/socketPayloadValidators.ts index 1f950cc..5b62965 100644 --- a/apps/server/src/network/validation/socketPayloadValidators.ts +++ b/apps/server/src/network/validation/socketPayloadValidators.ts @@ -4,6 +4,7 @@ */ import type { playerTypes, roomTypes, PlaceBombPayload } from "@repo/shared"; import type { PingPayload } from "@repo/shared"; +import { isPlaceBombPayload as isValidPlaceBombPayload } from "@server/domains/game/entities/bomb/bombPayloadValidation"; const isFiniteNumber = (value: unknown): value is number => { return typeof value === "number" && Number.isFinite(value); @@ -30,18 +31,7 @@ /** PLACE_BOMBイベントのペイロードが爆弾設置要求であるか判定する */ export const isPlaceBombPayload = (value: unknown): value is PlaceBombPayload => { - if (typeof value !== "object" || value === null) { - return false; - } - - const candidate = value as Record; - return ( - isNonEmptyString(candidate.requestId) - && - isFiniteNumber(candidate.x) - && isFiniteNumber(candidate.y) - && isFiniteNumber(candidate.explodeAtElapsedMs) - ); + return isValidPlaceBombPayload(value); }; /** JOIN_ROOMイベントのペイロードが参加情報であるか判定する */