diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts index ff3bb62..7dc2152 100644 --- a/apps/server/src/domains/room/RoomManager.ts +++ b/apps/server/src/domains/room/RoomManager.ts @@ -6,6 +6,7 @@ import { RoomJoinService } from "./application/services/RoomJoinService"; import { RoomExitService } from "./application/services/RoomExitService"; import { RoomQueryService } from "./application/services/RoomQueryService"; +import type { JoinRoomResult } from "./application/ports/roomUseCasePorts"; /** ルーム操作の公開インターフェースを提供するマネージャ */ export class RoomManager { @@ -21,7 +22,7 @@ } // ルームにプレイヤーを追加する,ルームが未作成なら新規作成する - public addPlayerToRoom(roomId: string, socketId: string, playerName: string): roomTypes.Room { + public addPlayerToRoom(roomId: string, socketId: string, playerName: string): JoinRoomResult { return this.roomJoinService.addPlayerToRoom(roomId, socketId, playerName); } diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index c3e9cb8..b00b3c8 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -4,9 +4,15 @@ */ import type { roomTypes } from "@repo/shared"; +/** ルーム参加処理の実行結果 */ +export type JoinRoomResult = { + room: roomTypes.Room; + status: "joined" | "duplicate" | "full"; +}; + /** ルーム参加ユースケースが利用する参加操作ポート */ export interface JoinRoomPort { - addPlayerToRoom(roomId: string, socketId: string, playerName: string): roomTypes.Room; + addPlayerToRoom(roomId: string, socketId: string, playerName: string): JoinRoomResult; } /** ルーム切断ユースケースが利用する退出操作ポート */ diff --git a/apps/server/src/domains/room/application/services/RoomJoinService.ts b/apps/server/src/domains/room/application/services/RoomJoinService.ts index 4af2108..d096dcc 100644 --- a/apps/server/src/domains/room/application/services/RoomJoinService.ts +++ b/apps/server/src/domains/room/application/services/RoomJoinService.ts @@ -5,12 +5,13 @@ import { config, roomConsts } from "@repo/shared"; import type { roomTypes } from "@repo/shared"; import { logEvent } from "@server/logging/logEvent"; +import type { JoinRoomResult } from "../ports/roomUseCasePorts"; /** 参加要求に応じてルーム作成と参加者追加を行うサービス */ export class RoomJoinService { constructor(private rooms: Map) {} - public addPlayerToRoom(roomId: string, socketId: string, playerName: string): roomTypes.Room { + public addPlayerToRoom(roomId: string, socketId: string, playerName: string): JoinRoomResult { let room = this.rooms.get(roomId); if (!room) { room = { @@ -30,6 +31,32 @@ }); } + // 同一ソケットの重複参加を防止する + const alreadyJoined = room.players.some((player) => player.id === socketId); + if (alreadyJoined) { + logEvent("RoomJoinService", { + event: "PLAYER_JOIN", + result: "ignored_duplicate", + roomId, + socketId, + totalPlayers: room.players.length, + }); + return { room, status: "duplicate" }; + } + + // ルーム満員時の参加を拒否する + if (room.players.length >= room.maxPlayers) { + logEvent("RoomJoinService", { + event: "PLAYER_JOIN", + result: "ignored_room_full", + roomId, + socketId, + maxPlayers: room.maxPlayers, + totalPlayers: room.players.length, + }); + return { room, status: "full" }; + } + const newPlayer: roomTypes.RoomMember = { id: socketId, name: playerName, @@ -47,6 +74,6 @@ totalPlayers: room.players.length, }); - return room; + return { room, status: "joined" }; } } diff --git a/apps/server/src/domains/room/application/useCases/joinRoomUseCase.ts b/apps/server/src/domains/room/application/useCases/joinRoomUseCase.ts index 661618c..e432db4 100644 --- a/apps/server/src/domains/room/application/useCases/joinRoomUseCase.ts +++ b/apps/server/src/domains/room/application/useCases/joinRoomUseCase.ts @@ -3,7 +3,7 @@ * ルーム参加要求を処理し,状態更新を配信するユースケース */ import type { roomTypes } from "@repo/shared"; -import type { JoinRoomPort } from "../ports/roomUseCasePorts"; +import type { JoinRoomPort, JoinRoomResult } from "../ports/roomUseCasePorts"; import { logEvent } from "@server/logging/logEvent"; type JoinRoomUseCaseParams = { @@ -19,7 +19,7 @@ socketId, data, publishRoomUpdate, -}: JoinRoomUseCaseParams) => { +}: JoinRoomUseCaseParams): JoinRoomResult => { const { roomId, playerName } = data; logEvent("RoomUseCase", { event: "JOIN_ROOM", @@ -29,15 +29,27 @@ playerName, }); - const room = roomManager.addPlayerToRoom(roomId, socketId, playerName); + const joinResult = roomManager.addPlayerToRoom(roomId, socketId, playerName); + if (joinResult.status !== "joined") { + logEvent("RoomUseCase", { + event: "JOIN_ROOM", + result: "rejected", + reason: joinResult.status, + roomId, + socketId, + }); + return joinResult; + } - publishRoomUpdate(roomId, room); + publishRoomUpdate(roomId, joinResult.room); logEvent("RoomUseCase", { event: "ROOM_UPDATE", result: "emitted", roomId, socketId, - ownerId: room.ownerId, - totalPlayers: room.players.length, + ownerId: joinResult.room.ownerId, + totalPlayers: joinResult.room.players.length, }); + + return joinResult; }; diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index 0c3dd03..7336761 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -36,11 +36,22 @@ socket.join(roomId); - joinRoomUseCase({ + const joinResult = joinRoomUseCase({ roomManager, socketId: socket.id, data, publishRoomUpdate: roomPublisher.publishRoomUpdate, }); + + // 満員拒否時はソケットのルーム参加状態を巻き戻す + if (joinResult.status === "full") { + socket.leave(roomId); + logEvent("Network", { + event: "JOIN_ROOM", + result: "rejected_room_full", + roomId, + socketId: socket.id, + }); + } }); }; diff --git a/packages/shared/src/config/gameConfig.ts b/packages/shared/src/config/gameConfig.ts index 32b91e2..bc2a755 100644 --- a/packages/shared/src/config/gameConfig.ts +++ b/packages/shared/src/config/gameConfig.ts @@ -1,6 +1,6 @@ export const GAME_CONFIG = { // ゲーム設定 - MAX_PLAYERS_PER_ROOM: 4, // ルーム収容人数設定 + MAX_PLAYERS_PER_ROOM: 100, // ルーム収容人数設定 GAME_DURATION_SEC: 180, // 1ゲームの制限時間(3分 = 180秒) // UI表示更新設定