diff --git a/apps/client/src/app.tsx b/apps/client/src/app.tsx index 5c6272a..08b1b54 100644 --- a/apps/client/src/app.tsx +++ b/apps/client/src/app.tsx @@ -9,11 +9,16 @@ import { appConsts } from "@repo/shared"; export default function App() { - const { scenePhase, room, myId } = useAppFlow(); + const { scenePhase, room, myId, joinErrorMessage } = useAppFlow(); // タイトル画面分岐 if (scenePhase === appConsts.ScenePhase.TITLE) { - return socketManager.title.joinRoom(payload)} />; + return ( + socketManager.title.joinRoom(payload)} + joinErrorMessage={joinErrorMessage} + /> + ); } // ロビー画面分岐 diff --git a/apps/client/src/hooks/useAppFlow.ts b/apps/client/src/hooks/useAppFlow.ts index b68228b..6fe6ae8 100644 --- a/apps/client/src/hooks/useAppFlow.ts +++ b/apps/client/src/hooks/useAppFlow.ts @@ -7,12 +7,14 @@ scenePhase: appTypes.ScenePhase; room: roomTypes.Room | null; myId: string | null; + joinErrorMessage: string | null; }; export const useAppFlow = (): AppFlowState => { const [scenePhase, setScenePhase] = useState(appConsts.ScenePhase.TITLE); const [room, setRoom] = useState(null); const [myId, setMyId] = useState(null); + const [joinErrorMessage, setJoinErrorMessage] = useState(null); useEffect(() => { const handleConnect = (id: string) => { @@ -20,22 +22,30 @@ }; const handleRoomUpdate = (updatedRoom: roomTypes.Room) => { setRoom(updatedRoom); + setJoinErrorMessage(null); setScenePhase(appConsts.ScenePhase.LOBBY); }; + const handleJoinRejected = (payload: roomTypes.JoinRoomRejectedPayload) => { + if (payload.reason === "full") { + setJoinErrorMessage(`ルーム ${payload.roomId} は満員です`); + } + }; const handleGameStart = () => { setScenePhase(appConsts.ScenePhase.PLAYING); }; socketManager.common.onConnect(handleConnect); + socketManager.title.onJoinRejected(handleJoinRejected); socketManager.lobby.onRoomUpdate(handleRoomUpdate); socketManager.game.onGameStart(handleGameStart); return () => { socketManager.common.offConnect(handleConnect); + socketManager.title.offJoinRejected(handleJoinRejected); socketManager.lobby.offRoomUpdate(handleRoomUpdate); socketManager.game.offGameStart(handleGameStart); }; }, []); - return { scenePhase, room, myId }; + return { scenePhase, room, myId, joinErrorMessage }; }; diff --git a/apps/client/src/network/handlers/TitleHandler.ts b/apps/client/src/network/handlers/TitleHandler.ts index 32248a8..3246817 100644 --- a/apps/client/src/network/handlers/TitleHandler.ts +++ b/apps/client/src/network/handlers/TitleHandler.ts @@ -4,12 +4,20 @@ type TitleHandler = { joinRoom: (payload: roomTypes.JoinRoomPayload) => void; + onJoinRejected: (callback: (payload: roomTypes.JoinRoomRejectedPayload) => void) => void; + offJoinRejected: (callback: (payload: roomTypes.JoinRoomRejectedPayload) => void) => void; }; export const createTitleHandler = (socket: Socket): TitleHandler => { return { joinRoom: (payload: roomTypes.JoinRoomPayload) => { socket.emit(protocol.SocketEvents.JOIN_ROOM, payload); + }, + onJoinRejected: (callback: (payload: roomTypes.JoinRoomRejectedPayload) => void) => { + socket.on(protocol.SocketEvents.ROOM_JOIN_REJECTED, callback); + }, + offJoinRejected: (callback: (payload: roomTypes.JoinRoomRejectedPayload) => void) => { + socket.off(protocol.SocketEvents.ROOM_JOIN_REJECTED, callback); } }; }; diff --git a/apps/client/src/scenes/title/TitleScene.tsx b/apps/client/src/scenes/title/TitleScene.tsx index d475c08..cec2fdc 100644 --- a/apps/client/src/scenes/title/TitleScene.tsx +++ b/apps/client/src/scenes/title/TitleScene.tsx @@ -5,9 +5,11 @@ type Props = { // 入室実行時呼び出しコールバック onJoin: (payload: roomTypes.JoinRoomPayload) => void; + // 入室失敗時の表示メッセージ + joinErrorMessage: string | null; }; -export const TitleScene = ({ onJoin }: Props) => { +export const TitleScene = ({ onJoin, joinErrorMessage }: Props) => { // プレイヤー名入力値 const [playerName, setPlayerName] = useState(""); // ルームID入力値 @@ -59,6 +61,10 @@ > ルームに入る / 作る + + {joinErrorMessage && ( +

{joinErrorMessage}

+ )} ); }; \ No newline at end of file diff --git a/apps/server/src/network/handlers/room/createRoomEventPublisher.ts b/apps/server/src/network/handlers/room/createRoomEventPublisher.ts index 3184664..5e2bf4d 100644 --- a/apps/server/src/network/handlers/room/createRoomEventPublisher.ts +++ b/apps/server/src/network/handlers/room/createRoomEventPublisher.ts @@ -14,6 +14,7 @@ /** ルーム更新イベントの送信インターフェース */ export type RoomEventPublisher = { publishRoomUpdate: (roomId: RoomId, room: RoomUpdatePayload) => void; + publishJoinRejected: (payload: roomTypes.JoinRoomRejectedPayload) => void; }; /** 共通送信コンテキストからルームイベント送信関数を生成する */ @@ -24,6 +25,9 @@ publishRoomUpdate: (roomId: RoomId, room: RoomUpdatePayload) => { common.emitToRoom(roomId, protocol.SocketEvents.ROOM_UPDATE, room); }, + publishJoinRejected: (payload: roomTypes.JoinRoomRejectedPayload) => { + common.emitToSocket(protocol.SocketEvents.ROOM_JOIN_REJECTED, payload); + }, }; }; @@ -35,5 +39,8 @@ publishRoomUpdate: (roomId: RoomId, room: RoomUpdatePayload) => { emitToRoom(roomId, protocol.SocketEvents.ROOM_UPDATE, room); }, + publishJoinRejected: () => { + return; + }, }; }; diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index 7336761..33dd81c 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -46,6 +46,10 @@ // 満員拒否時はソケットのルーム参加状態を巻き戻す if (joinResult.status === "full") { socket.leave(roomId); + roomPublisher.publishJoinRejected({ + roomId, + reason: "full", + }); logEvent("Network", { event: "JOIN_ROOM", result: "rejected_room_full", diff --git a/packages/shared/src/domains/room/room.type.ts b/packages/shared/src/domains/room/room.type.ts index 4c1ac67..6226444 100644 --- a/packages/shared/src/domains/room/room.type.ts +++ b/packages/shared/src/domains/room/room.type.ts @@ -22,4 +22,13 @@ export interface JoinRoomPayload { roomId: string; playerName: string; +} + +// ルーム参加拒否理由型 +export type JoinRoomRejectedReason = "full"; + +// ルーム参加拒否通知ペイロード型 +export interface JoinRoomRejectedPayload { + roomId: string; + reason: JoinRoomRejectedReason; } \ No newline at end of file diff --git a/packages/shared/src/protocol/events.ts b/packages/shared/src/protocol/events.ts index 5e90344..39a1c4a 100644 --- a/packages/shared/src/protocol/events.ts +++ b/packages/shared/src/protocol/events.ts @@ -5,6 +5,7 @@ // ロビー・ルーム関連イベント名 JOIN_ROOM: "join-room", + ROOM_JOIN_REJECTED: "room-join-rejected", ROOM_UPDATE: "room-update", START_GAME: "start-game", GAME_START: "game-start",