diff --git a/apps/server/src/domains/game/GameHandler.ts b/apps/server/src/domains/game/GameHandler.ts index 46f5057..d1409db 100644 --- a/apps/server/src/domains/game/GameHandler.ts +++ b/apps/server/src/domains/game/GameHandler.ts @@ -1,99 +1,34 @@ import { Server, Socket } from "socket.io"; import { GameManager } from "./GameManager"; import { RoomManager } from "@server/domains/room/RoomManager"; -import { protocol, roomConsts } from "@repo/shared"; +import { protocol } from "@repo/shared"; import type { playerTypes } from "@repo/shared"; +import { onPing } from "./handlers/onPing"; +import { onStartGame } from "./handlers/onStartGame"; +import { onReadyForGame } from "./handlers/onReadyForGame"; +import { onMove } from "./handlers/onMove"; +import { onDisconnect } from "./handlers/onDisconnect"; export const registerGameHandlers = (io: Server, socket: Socket, gameManager: GameManager, roomManager: RoomManager) => { // クライアントから送られてきた時刻をそのまま返しつつ、サーバーの現在時刻も添える socket.on(protocol.SocketEvents.PING, (clientTime: number) => { - socket.emit(protocol.SocketEvents.PONG, { clientTime, serverTime: Date.now() }); + onPing(socket, clientTime); }); // ゲーム開始要求処理 socket.on(protocol.SocketEvents.START_GAME, () => { - const room = roomManager.getRoomByOwnerId(socket.id); - if (!room) { - console.log("[GameHandler] START_GAME ignored (no room)", { socketId: socket.id }); - return; - } - - if (room.status === roomConsts.RoomPhase.PLAYING) { - console.log("[GameHandler] START_GAME ignored (already playing)", { roomId: room.roomId }); - return; - } - - console.log("[GameHandler] START_GAME accepted", { - roomId: room.roomId, - ownerId: socket.id, - totalPlayers: room.players.length - }); - - if (room) { - room.status = roomConsts.RoomPhase.PLAYING; - - const playerIds = room.players.map((p: { id: string }) => p.id); - - // 同ルーム全プレイヤーのゲーム管理登録 - room.players.forEach((p: { id: string }) => { - gameManager.addPlayer(p.id); - }); - - // 20Hzのゲームループを開始し、毎フレームの送信処理を定義 - gameManager.startGameLoop( - room.roomId, - playerIds, - (tickData) => { - // 1. 各プレイヤーの最新座標をクライアントに送信 - tickData.players.forEach((playerData) => { - io.to(room.roomId).emit(protocol.SocketEvents.UPDATE_PLAYER, playerData); - }); - - // 2. 差分があれば、ルーム内の全員に一斉送信 - if (tickData.cellUpdates.length > 0) { - io.to(room.roomId).emit(protocol.SocketEvents.UPDATE_MAP_CELLS, tickData.cellUpdates); - } - }, - - () => { - // 3分経過時に GameLoop から呼ばれる処理 - console.log(`[GameHandler] ルーム ${room.roomId} のゲームが終了しました (3分経過)`); - io.to(room.roomId).emit(protocol.SocketEvents.GAME_END); // クライアントへ終了通知 - room.status = roomConsts.RoomPhase.WAITING; // ルーム状態を待機に戻す - } - ); - - // GameManagerから開始時刻を取得し、GAME_STARTイベントにデータを乗せて送る - const startTime = gameManager.getRoomStartTime(room.roomId) || Date.now(); - io.to(room.roomId).emit(protocol.SocketEvents.GAME_START, { startTime }); - } + onStartGame(io, gameManager, roomManager, socket.id); }); // 画面準備完了通知受信時初期データ返却 socket.on(protocol.SocketEvents.READY_FOR_GAME, () => { - const allPlayers = gameManager.getAllPlayers(); - socket.emit(protocol.SocketEvents.CURRENT_PLAYERS, allPlayers); - console.log("[GameHandler] READY_FOR_GAME received", { socketId: socket.id, totalPlayers: allPlayers.length }); - - // 準備が完了したクライアントに対して、改めて開始時刻を個別に教える - // Socket.ioの仕様上、socket.roomsには自身のIDと参加中のルームIDが含まれるため、そこからルームIDを特定する - const roomId = Array.from(socket.rooms).find(room => room !== socket.id); - if (roomId) { - const startTime = gameManager.getRoomStartTime(roomId); - if (startTime) { - // io.to() による全員への一斉送信ではなく、socket.emit() でこの本人にだけ送る - socket.emit(protocol.SocketEvents.GAME_START, { startTime }); - console.log("[GameHandler] GAME_START sent to ready client", { socketId: socket.id, roomId, startTime }); - } - } else { - console.log("[GameHandler] READY_FOR_GAME missing roomId", { socketId: socket.id }); - } + onReadyForGame(socket, gameManager); }); // ゲームプレイ中イベント群 socket.on(protocol.SocketEvents.MOVE, (data: playerTypes.MovePayload) => { - gameManager.movePlayer(socket.id, data.x, data.y); + onMove(gameManager, socket.id, data); }); }; @@ -102,9 +37,5 @@ * 切断時のゲームクリーンアップ処理 */ export const handleGameDisconnect = (io: Server, gameManager: GameManager, playerId: string) => { - // ゲームからの除外処理 - gameManager.removePlayer(playerId); - // 全体にプレイヤー削除を通知 - io.emit(protocol.SocketEvents.REMOVE_PLAYER, playerId); - console.log("[GameHandler] player removed", { playerId }); + onDisconnect(io, gameManager, playerId); }; \ No newline at end of file diff --git a/apps/server/src/domains/game/handlers/onDisconnect.ts b/apps/server/src/domains/game/handlers/onDisconnect.ts new file mode 100644 index 0000000..4cc4b5f --- /dev/null +++ b/apps/server/src/domains/game/handlers/onDisconnect.ts @@ -0,0 +1,13 @@ +import { Server } from "socket.io"; +import { GameManager } from "@server/domains/game/GameManager"; +import { protocol } from "@repo/shared"; + +export const onDisconnect = ( + io: Server, + gameManager: GameManager, + playerId: string +) => { + gameManager.removePlayer(playerId); + io.emit(protocol.SocketEvents.REMOVE_PLAYER, playerId); + console.log("[GameHandler] player removed", { playerId }); +}; diff --git a/apps/server/src/domains/game/handlers/onMove.ts b/apps/server/src/domains/game/handlers/onMove.ts new file mode 100644 index 0000000..7288ce3 --- /dev/null +++ b/apps/server/src/domains/game/handlers/onMove.ts @@ -0,0 +1,10 @@ +import { GameManager } from "@server/domains/game/GameManager"; +import type { playerTypes } from "@repo/shared"; + +export const onMove = ( + gameManager: GameManager, + playerId: string, + data: playerTypes.MovePayload +) => { + gameManager.movePlayer(playerId, data.x, data.y); +}; diff --git a/apps/server/src/domains/game/handlers/onPing.ts b/apps/server/src/domains/game/handlers/onPing.ts new file mode 100644 index 0000000..2045d39 --- /dev/null +++ b/apps/server/src/domains/game/handlers/onPing.ts @@ -0,0 +1,6 @@ +import { Socket } from "socket.io"; +import { protocol } from "@repo/shared"; + +export const onPing = (socket: Socket, clientTime: number) => { + socket.emit(protocol.SocketEvents.PONG, { clientTime, serverTime: Date.now() }); +}; diff --git a/apps/server/src/domains/game/handlers/onReadyForGame.ts b/apps/server/src/domains/game/handlers/onReadyForGame.ts new file mode 100644 index 0000000..f7c845b --- /dev/null +++ b/apps/server/src/domains/game/handlers/onReadyForGame.ts @@ -0,0 +1,30 @@ +import { Socket } from "socket.io"; +import { GameManager } from "@server/domains/game/GameManager"; +import { protocol } from "@repo/shared"; + +export const onReadyForGame = ( + socket: Socket, + gameManager: GameManager +) => { + const allPlayers = gameManager.getAllPlayers(); + socket.emit(protocol.SocketEvents.CURRENT_PLAYERS, allPlayers); + console.log("[GameHandler] READY_FOR_GAME received", { + socketId: socket.id, + totalPlayers: allPlayers.length, + }); + + const roomId = Array.from(socket.rooms).find((room) => room !== socket.id); + if (roomId) { + const startTime = gameManager.getRoomStartTime(roomId); + if (startTime) { + socket.emit(protocol.SocketEvents.GAME_START, { startTime }); + console.log("[GameHandler] GAME_START sent to ready client", { + socketId: socket.id, + roomId, + startTime, + }); + } + } else { + console.log("[GameHandler] READY_FOR_GAME missing roomId", { socketId: socket.id }); + } +}; diff --git a/apps/server/src/domains/game/handlers/onStartGame.ts b/apps/server/src/domains/game/handlers/onStartGame.ts new file mode 100644 index 0000000..e27d612 --- /dev/null +++ b/apps/server/src/domains/game/handlers/onStartGame.ts @@ -0,0 +1,58 @@ +import { Server } from "socket.io"; +import { GameManager } from "@server/domains/game/GameManager"; +import { RoomManager } from "@server/domains/room/RoomManager"; +import { protocol, roomConsts } from "@repo/shared"; + +export const onStartGame = ( + io: Server, + gameManager: GameManager, + roomManager: RoomManager, + ownerId: string +) => { + const room = roomManager.getRoomByOwnerId(ownerId); + if (!room) { + console.log("[GameHandler] START_GAME ignored (no room)", { socketId: ownerId }); + return; + } + + if (room.status === roomConsts.RoomPhase.PLAYING) { + console.log("[GameHandler] START_GAME ignored (already playing)", { roomId: room.roomId }); + return; + } + + console.log("[GameHandler] START_GAME accepted", { + roomId: room.roomId, + ownerId, + totalPlayers: room.players.length, + }); + + room.status = roomConsts.RoomPhase.PLAYING; + + const playerIds = room.players.map((p: { id: string }) => p.id); + + room.players.forEach((p: { id: string }) => { + gameManager.addPlayer(p.id); + }); + + gameManager.startGameLoop( + room.roomId, + playerIds, + (tickData) => { + tickData.players.forEach((playerData) => { + io.to(room.roomId).emit(protocol.SocketEvents.UPDATE_PLAYER, playerData); + }); + + if (tickData.cellUpdates.length > 0) { + io.to(room.roomId).emit(protocol.SocketEvents.UPDATE_MAP_CELLS, tickData.cellUpdates); + } + }, + () => { + console.log(`[GameHandler] ルーム ${room.roomId} のゲームが終了しました (3分経過)`); + io.to(room.roomId).emit(protocol.SocketEvents.GAME_END); + room.status = roomConsts.RoomPhase.WAITING; + } + ); + + const startTime = gameManager.getRoomStartTime(room.roomId) || Date.now(); + io.to(room.roomId).emit(protocol.SocketEvents.GAME_START, { startTime }); +};