diff --git a/apps/server/src/domains/game/GameHandler.ts b/apps/server/src/domains/game/GameHandler.ts new file mode 100644 index 0000000..3d67bd4 --- /dev/null +++ b/apps/server/src/domains/game/GameHandler.ts @@ -0,0 +1,65 @@ +import { Server, Socket } from "socket.io"; +import { GameManager } from "./GameManager"; +import { RoomManager } from "../room/RoomManager"; +import { SocketEvents } from "@repo/shared/src/protocol/events"; +import { RoomStatus } from "@repo/shared/src/domains/room/room.type"; +import type { MovePayload } from "@repo/shared/src/domains/player/player.type"; + +export const registerGameHandlers = (io: Server, socket: Socket, gameManager: GameManager, roomManager: RoomManager) => { + + // ゲーム開始要求処理 + socket.on(SocketEvents.START_GAME, () => { + const room = roomManager.getRoomByOwnerId(socket.id); + + if (room) { + room.status = RoomStatus.PLAYING; + + const playerIds = room.players.map((p: { id: string }) => p.id); + + // 同ルーム全プレイヤーのゲーム管理登録 + room.players.forEach((p: { id: string }) => { + gameManager.addPlayer(p.id); + }); + + // ルーム全員向けゲーム開始通知 + io.to(room.roomId).emit(SocketEvents.GAME_START); + + // 20Hzのゲームループを開始し、毎フレームの送信処理を定義 + gameManager.startGameLoop(room.roomId, playerIds, (tickData) => { + + // 1. 各プレイヤーの最新座標をクライアントに送信 + tickData.players.forEach((playerData) => { + io.to(room.roomId).emit(SocketEvents.UPDATE_PLAYER, playerData); + }); + + // 2. 差分があれば、ルーム内の全員に一斉送信 + if (tickData.cellUpdates.length > 0) { + io.to(room.roomId).emit(SocketEvents.UPDATE_MAP_CELLS, tickData.cellUpdates); + } + + }); + } + }); + + // 画面準備完了通知受信時初期データ返却 + socket.on(SocketEvents.READY_FOR_GAME, () => { + const allPlayers = gameManager.getAllPlayers(); + socket.emit(SocketEvents.CURRENT_PLAYERS, allPlayers); + }); + + // ゲームプレイ中イベント群 + socket.on(SocketEvents.MOVE, (data: MovePayload) => { + gameManager.movePlayer(socket.id, data.x, data.y); + }); + +}; + +/** + * 切断時のゲームクリーンアップ処理 + */ +export const handleGameDisconnect = (io: Server, gameManager: GameManager, playerId: string) => { + // ゲームからの除外処理 + gameManager.removePlayer(playerId); + // 全体にプレイヤー削除を通知 + io.emit(SocketEvents.REMOVE_PLAYER, playerId); +}; \ No newline at end of file diff --git a/apps/server/src/domains/game/GameLoop.ts b/apps/server/src/domains/game/GameLoop.ts new file mode 100644 index 0000000..50c81f8 --- /dev/null +++ b/apps/server/src/domains/game/GameLoop.ts @@ -0,0 +1,76 @@ +import { Player } from "./entities/Player.js"; +import { MapStore } from "./states/MapStore"; +import { getGridIndexFromPosition } from "@repo/shared/src/domains/gridMap/gridMap.logic"; +import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; + +// コールバックで渡すデータの型定義 +export interface TickData { + players: { + id: string; + x: number; + y: number; + teamId: number; + }[]; + cellUpdates: CellUpdate[]; +} + +export class GameLoop { + private loopId: NodeJS.Timeout | null = null; + + constructor( + private roomId: string, + private tickRate: number, + private playerIds: string[], + private players: Map, + private mapStore: MapStore, + private onTick: (data: TickData) => void + ) {} + + start() { + // 既にループが回っている場合は何もしない + if (this.loopId) return; + + this.loopId = setInterval(() => { + const playersData: TickData["players"] = []; + + // 1. 各プレイヤーの座標処理とマス塗りの判定 + this.playerIds.forEach(id => { + const player = this.players.get(id); + if (!player) return; + + const gridIndex = getGridIndexFromPosition(player.x, player.y); + if (gridIndex !== null) { + this.mapStore.paintCell(gridIndex, player.teamId); + } + + // 送信用のプレイヤーデータを構築 + playersData.push({ + id: player.id, + x: player.x, + y: player.y, + teamId: player.teamId, + }); + }); + + // 2. マスの差分(Diff)を取得 + const cellUpdates = this.mapStore.getAndClearUpdates(); + + // 3. 通信層(GameHandler)へデータを渡す + this.onTick({ + players: playersData, + cellUpdates: cellUpdates, + }); + + }, this.tickRate); + + console.log(`[GameLoop] Started for room: ${this.roomId} at ${this.tickRate}ms`); + } + + stop() { + if (this.loopId) { + clearInterval(this.loopId); + this.loopId = null; + console.log(`[GameLoop] Stopped for room: ${this.roomId}`); + } + } +} \ No newline at end of file diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts new file mode 100644 index 0000000..7b010e1 --- /dev/null +++ b/apps/server/src/domains/game/GameManager.ts @@ -0,0 +1,106 @@ +import { Player } from "./entities/Player.js"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; +import { MapStore } from "./states/MapStore"; +import { getGridIndexFromPosition } from "@repo/shared/src/domains/gridMap/gridMap.logic"; +import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; +import { GameLoop, type TickData } from "./GameLoop"; + +// プレイヤー集合の生成・更新・参照管理クラス +export class GameManager { + private players: Map; + private mapStore: MapStore; + private gameLoops: Map; // NodeJS.Timeout から変更 + + constructor() { + this.players = new Map(); + this.mapStore = new MapStore(); + this.gameLoops = new Map(); + } + + // 新規プレイヤー登録と初期位置設定処理 + addPlayer(id: string): Player { + const player = new Player(id); + player.x = GAME_CONFIG.MAP_WIDTH / 2; + player.y = GAME_CONFIG.MAP_HEIGHT / 2; + this.players.set(id, player); + return player; + } + + // プレイヤー登録解除処理 + removePlayer(id: string) { + this.players.delete(id); + } + + // 指定IDプレイヤー参照取得 + getPlayer(id: string) { + return this.players.get(id); + } + + // 指定プレイヤー座標更新処理 + movePlayer(id: string, x: number, y: number) { + const player = this.players.get(id); + if (player) { + console.log(`Move Request -> ID:${id.slice(0,4)} x:${Math.round(x)} y:${Math.round(y)}`); + if (typeof x !== "number" || typeof y !== "number" || isNaN(x) || isNaN(y)) { + console.log("⚠️ 無効なデータなので無視しました"); + return; + } + player.x = x; + player.y = y; + } + } + + /** + * 20Hz固定のゲームループを開始する + * @param roomId ルームID + * @param playerIds このルームに参加しているプレイヤーのIDリスト + * @param onTick 毎フレーム実行される送信用のコールバック関数 + */ + startGameLoop(roomId: string, playerIds: string[], onTick: (data: TickData) => void) { + if (this.gameLoops.has(roomId)) return; + + const tickRate = GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; + + // GameLoopインスタンスを生成し、参照を渡す + const loop = new GameLoop( + roomId, + tickRate, + playerIds, + this.players, + this.mapStore, + onTick + ); + + loop.start(); + this.gameLoops.set(roomId, loop); + } + + /** + * ゲームループを停止する + */ + stopGameLoop(roomId: string) { + const loop = this.gameLoops.get(roomId); + if (loop) { + loop.stop(); + this.gameLoops.delete(roomId); + } + } + + // 登録中全プレイヤー配列取得 + getAllPlayers() { + return Array.from(this.players.values()); + } + + // 【一時的】移動したプレイヤーの足元を塗り、差分を返すメソッド + public paintAndGetUpdates(playerId: string): CellUpdate[] { + const player = this.players.get(playerId); + if (!player) return []; + + const gridIndex = getGridIndexFromPosition(player.x, player.y); + if (gridIndex !== null) { + this.mapStore.paintCell(gridIndex, player.teamId); + } + + return this.mapStore.getAndClearUpdates(); + } +} \ No newline at end of file diff --git a/apps/server/src/domains/game/entities/Player.ts b/apps/server/src/domains/game/entities/Player.ts new file mode 100644 index 0000000..edc7976 --- /dev/null +++ b/apps/server/src/domains/game/entities/Player.ts @@ -0,0 +1,18 @@ +import type { PlayerData } from "@repo/shared/src/domains/player/player.type"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; + +// サーバー側保持プレイヤー状態モデル +export class Player implements PlayerData { + public id: string; + public x: number = 0; + public y: number = 0; + public teamId: number; + + constructor(id: string) { + this.id = id; + + // GAME_CONFIGからチーム数を動的に取得して割り当て + const teamCount = GAME_CONFIG.TEAM_COLORS.length; + this.teamId = Math.floor(Math.random() * teamCount); + } +} \ No newline at end of file diff --git a/apps/server/src/domains/game/states/MapStore.ts b/apps/server/src/domains/game/states/MapStore.ts new file mode 100644 index 0000000..4f6f2be --- /dev/null +++ b/apps/server/src/domains/game/states/MapStore.ts @@ -0,0 +1,36 @@ +// apps/server/src/states/MapStore.ts +import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; + +export class MapStore { + // 全マスの現在の色(teamId)を保持 + private gridColors: number[]; + // 次回の送信ループで送る差分リスト + private pendingUpdates: CellUpdate[]; + + constructor() { + // 初期状態は -1 (無色) などで初期化 + const totalCells = GAME_CONFIG.GRID_COLS * GAME_CONFIG.GRID_ROWS; + this.gridColors = new Array(totalCells).fill(-1); + this.pendingUpdates = []; + } + + /** + * マスを塗り、色が変化した場合のみ差分キューに追加する + */ + public paintCell(index: number, teamId: number): void { + if (this.gridColors[index] !== teamId) { + this.gridColors[index] = teamId; + this.pendingUpdates.push({ index, teamId }); + } + } + + /** + * 溜まっている差分を取得し、キューをクリアする(ループ送信時に使用) + */ + public getAndClearUpdates(): CellUpdate[] { + const updates = [...this.pendingUpdates]; + this.pendingUpdates = []; + return updates; + } +} \ No newline at end of file diff --git a/apps/server/src/domains/room/RoomHandler.ts b/apps/server/src/domains/room/RoomHandler.ts new file mode 100644 index 0000000..d210e01 --- /dev/null +++ b/apps/server/src/domains/room/RoomHandler.ts @@ -0,0 +1,33 @@ +import { Server, Socket } from "socket.io"; +import { RoomManager } from "./RoomManager"; +import { SocketEvents } from "@repo/shared/src/protocol/events"; +import type { JoinRoomPayload } from "@repo/shared/src/domains/room/room.type"; + +export const registerRoomHandlers = (io: Server, socket: Socket, roomManager: RoomManager) => { + + socket.on(SocketEvents.JOIN_ROOM, (data: JoinRoomPayload) => { + const { roomId, playerName } = data; + + socket.join(roomId); + + // RoomManagerにデータ操作を依頼 + const room = roomManager.addPlayerToRoom(roomId, socket.id, playerName); + + // ルーム内全員向け最新状態配信 + io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); + }); + +}; + +/** + * 切断時のルームクリーンアップ処理 + */ +export const handleRoomDisconnect = (io: Server, socket: Socket, roomManager: RoomManager) => { + // ルームからの除外処理 + const updatedRooms = roomManager.removePlayer(socket.id); + + // 更新があったルーム(オーナー変更など)にのみ通知を飛ばす + updatedRooms.forEach(room => { + io.to(room.roomId).emit(SocketEvents.ROOM_UPDATE, room); + }); +}; \ No newline at end of file diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts new file mode 100644 index 0000000..3be6e62 --- /dev/null +++ b/apps/server/src/domains/room/RoomManager.ts @@ -0,0 +1,66 @@ +import { Room, RoomStatus, RoomMember } from "@repo/shared/src/domains/room/room.type"; +import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; + +export class RoomManager { + private rooms: Map = new Map(); + + // ルームにプレイヤーを追加(なければ作成) + public addPlayerToRoom(roomId: string, socketId: string, playerName: string): Room { + let room = this.rooms.get(roomId); + if (!room) { + room = { + roomId: roomId, + ownerId: socketId, + players: [], + status: RoomStatus.WAITING, + maxPlayers: GAME_CONFIG.MAX_PLAYERS_PER_ROOM + }; + this.rooms.set(roomId, room); + } + + const newPlayer: RoomMember = { + id: socketId, + name: playerName, + isOwner: room.ownerId === socketId, + isReady: false + }; + room.players.push(newPlayer); + + return room; + } + + // プレイヤーをルームから削除し、更新があったルームの配列を返す + public removePlayer(socketId: string): Room[] { + const updatedRooms: Room[] = []; + + for (const [roomId, room] of this.rooms.entries()) { + const playerIndex = room.players.findIndex(p => p.id === socketId); + if (playerIndex !== -1) { + room.players.splice(playerIndex, 1); + + if (room.players.length === 0) { + // 空ルーム削除 + this.rooms.delete(roomId); + } else { + // オーナー切断時所有権移譲処理 + if (room.ownerId === socketId) { + room.ownerId = room.players[0].id; + room.players[0].isOwner = true; + } + updatedRooms.push(room); + } + } + } + return updatedRooms; + } + + // オーナーIDからルームを取得 + public getRoomByOwnerId(ownerId: string): Room | undefined { + for (const room of this.rooms.values()) { + if (room.ownerId === ownerId) { + return room; + } + } + return undefined; + } +} \ No newline at end of file diff --git a/apps/server/src/entities/Player.ts b/apps/server/src/entities/Player.ts deleted file mode 100644 index edc7976..0000000 --- a/apps/server/src/entities/Player.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { PlayerData } from "@repo/shared/src/domains/player/player.type"; -import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; - -// サーバー側保持プレイヤー状態モデル -export class Player implements PlayerData { - public id: string; - public x: number = 0; - public y: number = 0; - public teamId: number; - - constructor(id: string) { - this.id = id; - - // GAME_CONFIGからチーム数を動的に取得して割り当て - const teamCount = GAME_CONFIG.TEAM_COLORS.length; - this.teamId = Math.floor(Math.random() * teamCount); - } -} \ No newline at end of file diff --git a/apps/server/src/handlers/GameHandler.ts b/apps/server/src/handlers/GameHandler.ts deleted file mode 100644 index c26cf65..0000000 --- a/apps/server/src/handlers/GameHandler.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Server, Socket } from "socket.io"; -import { GameManager } from "../managers/GameManager.js"; -import { RoomManager } from "../managers/RoomManager.js"; -import { SocketEvents } from "@repo/shared/src/protocol/events"; -import { RoomStatus } from "@repo/shared/src/domains/room/room.type"; -import type { MovePayload } from "@repo/shared/src/domains/player/player.type"; - -export const registerGameHandlers = (io: Server, socket: Socket, gameManager: GameManager, roomManager: RoomManager) => { - - // ゲーム開始要求処理 - socket.on(SocketEvents.START_GAME, () => { - const room = roomManager.getRoomByOwnerId(socket.id); - - if (room) { - room.status = RoomStatus.PLAYING; - - // 同ルーム全プレイヤーのゲーム管理登録 - room.players.forEach((p: { id: string }) => { - gameManager.addPlayer(p.id); - }); - - // ルーム全員向けゲーム開始通知 - io.to(room.roomId).emit(SocketEvents.GAME_START); - } - }); - - // 画面準備完了通知受信時初期データ返却 - socket.on(SocketEvents.READY_FOR_GAME, () => { - const allPlayers = gameManager.getAllPlayers(); - socket.emit(SocketEvents.CURRENT_PLAYERS, allPlayers); - }); - - // ゲームプレイ中イベント群 - socket.on(SocketEvents.MOVE, (data: MovePayload) => { - gameManager.movePlayer(socket.id, data.x, data.y); - - const updatedPlayer = gameManager.getPlayer(socket.id); - if (updatedPlayer) { - const myRooms = Array.from(socket.rooms).filter(r => r !== socket.id); - const targetRoom = myRooms.length > 0 ? myRooms[0] : null; - - if (targetRoom) { - io.to(targetRoom).emit(SocketEvents.UPDATE_PLAYER, { - id: socket.id, - x: updatedPlayer.x, - y: updatedPlayer.y - }); - - // ② 【新規】マス目の更新と差分送信のテスト - const cellUpdates = gameManager.paintAndGetUpdates(socket.id); - if (cellUpdates.length > 0) { - io.to(targetRoom).emit(SocketEvents.UPDATE_MAP_CELLS, cellUpdates); - // ログを出してサーバー側で送信されているか確認すると便利です - console.log(`[MAP_UPDATE] Sent ${cellUpdates.length} updates to room ${targetRoom}`); - } - } - } - }); - -}; - -/** - * 切断時のゲームクリーンアップ処理 - */ -export const handleGameDisconnect = (io: Server, gameManager: GameManager, playerId: string) => { - // ゲームからの除外処理 - gameManager.removePlayer(playerId); - // 全体にプレイヤー削除を通知 - io.emit(SocketEvents.REMOVE_PLAYER, playerId); -}; \ No newline at end of file diff --git a/apps/server/src/handlers/RoomHandler.ts b/apps/server/src/handlers/RoomHandler.ts deleted file mode 100644 index e3f0e23..0000000 --- a/apps/server/src/handlers/RoomHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Server, Socket } from "socket.io"; -import { RoomManager } from "../managers/RoomManager.js"; -import { SocketEvents } from "@repo/shared/src/protocol/events"; -import type { JoinRoomPayload } from "@repo/shared/src/domains/room/room.type"; - -export const registerRoomHandlers = (io: Server, socket: Socket, roomManager: RoomManager) => { - - socket.on(SocketEvents.JOIN_ROOM, (data: JoinRoomPayload) => { - const { roomId, playerName } = data; - - socket.join(roomId); - - // RoomManagerにデータ操作を依頼 - const room = roomManager.addPlayerToRoom(roomId, socket.id, playerName); - - // ルーム内全員向け最新状態配信 - io.to(roomId).emit(SocketEvents.ROOM_UPDATE, room); - }); - -}; - -/** - * 切断時のルームクリーンアップ処理 - */ -export const handleRoomDisconnect = (io: Server, socket: Socket, roomManager: RoomManager) => { - // ルームからの除外処理 - const updatedRooms = roomManager.removePlayer(socket.id); - - // 更新があったルーム(オーナー変更など)にのみ通知を飛ばす - updatedRooms.forEach(room => { - io.to(room.roomId).emit(SocketEvents.ROOM_UPDATE, room); - }); -}; \ No newline at end of file diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 314f10e..dc4676b 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,7 +1,8 @@ import { Server } from "socket.io"; import { createServer } from "http"; -import { GameManager } from "./managers/GameManager.js"; -import { SocketManager } from "./network/SocketManager.js"; +import { GameManager } from "./domains/game/GameManager"; +import { RoomManager } from "./domains/room/RoomManager"; +import { SocketManager } from "./network/SocketManager"; // サーバー待受ポート const PORT = 3000; @@ -18,7 +19,8 @@ // ゲーム管理・通信管理クラス初期化 const gameManager = new GameManager(); -const socketManager = new SocketManager(io, gameManager); +const roomManager = new RoomManager(); +const socketManager = new SocketManager(io, gameManager, roomManager); socketManager.initialize(); diff --git a/apps/server/src/managers/GameManager.ts b/apps/server/src/managers/GameManager.ts deleted file mode 100644 index 152cbe0..0000000 --- a/apps/server/src/managers/GameManager.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Player } from "../entities/Player.js"; -import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; -import { MapStore } from "../states/MapStore"; -import { getGridIndexFromPosition } from "@repo/shared/src/domains/gridMap/gridMap.logic"; -import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; - -// プレイヤー集合の生成・更新・参照管理クラス -export class GameManager { - private players: Map; - private mapStore: MapStore; - - constructor() { - this.players = new Map(); - this.mapStore = new MapStore(); - } - - // 新規プレイヤー登録と初期位置設定処理 - addPlayer(id: string): Player { - const player = new Player(id); - - // 初期スポーン位置 - player.x = GAME_CONFIG.MAP_WIDTH / 2; - player.y = GAME_CONFIG.MAP_HEIGHT / 2; - - this.players.set(id, player); - return player; - } - - // プレイヤー登録解除処理 - removePlayer(id: string) { - this.players.delete(id); - } - - // 指定IDプレイヤー参照取得 - getPlayer(id: string) { - return this.players.get(id); - } - - // 指定プレイヤー座標更新処理 - movePlayer(id: string, x: number, y: number) { - const player = this.players.get(id); - if (player) { - - // 受信移動要求ログ - console.log(`Move Request -> ID:${id.slice(0,4)} x:${Math.round(x)} y:${Math.round(y)}`); - - // 無効座標データ防御チェック - if (typeof x !== "number" || typeof y !== "number" || isNaN(x) || isNaN(y)) { - console.log("⚠️ 無効なデータなので無視しました"); - return; - } - - // クライアント送信絶対座標の直接反映 - player.x = x; - player.y = y; - } - } - - // 登録中全プレイヤー配列取得 - getAllPlayers() { - return Array.from(this.players.values()); - } - - // 【一時的】移動したプレイヤーの足元を塗り、差分を返すメソッド - public paintAndGetUpdates(playerId: string): CellUpdate[] { - const player = this.players.get(playerId); - if (!player) return []; - - const gridIndex = getGridIndexFromPosition(player.x, player.y); - if (gridIndex !== null) { - this.mapStore.paintCell(gridIndex, player.teamId); - } - - return this.mapStore.getAndClearUpdates(); - } -} \ No newline at end of file diff --git a/apps/server/src/managers/RoomManager.ts b/apps/server/src/managers/RoomManager.ts deleted file mode 100644 index 3be6e62..0000000 --- a/apps/server/src/managers/RoomManager.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Room, RoomStatus, RoomMember } from "@repo/shared/src/domains/room/room.type"; -import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; - -export class RoomManager { - private rooms: Map = new Map(); - - // ルームにプレイヤーを追加(なければ作成) - public addPlayerToRoom(roomId: string, socketId: string, playerName: string): Room { - let room = this.rooms.get(roomId); - if (!room) { - room = { - roomId: roomId, - ownerId: socketId, - players: [], - status: RoomStatus.WAITING, - maxPlayers: GAME_CONFIG.MAX_PLAYERS_PER_ROOM - }; - this.rooms.set(roomId, room); - } - - const newPlayer: RoomMember = { - id: socketId, - name: playerName, - isOwner: room.ownerId === socketId, - isReady: false - }; - room.players.push(newPlayer); - - return room; - } - - // プレイヤーをルームから削除し、更新があったルームの配列を返す - public removePlayer(socketId: string): Room[] { - const updatedRooms: Room[] = []; - - for (const [roomId, room] of this.rooms.entries()) { - const playerIndex = room.players.findIndex(p => p.id === socketId); - if (playerIndex !== -1) { - room.players.splice(playerIndex, 1); - - if (room.players.length === 0) { - // 空ルーム削除 - this.rooms.delete(roomId); - } else { - // オーナー切断時所有権移譲処理 - if (room.ownerId === socketId) { - room.ownerId = room.players[0].id; - room.players[0].isOwner = true; - } - updatedRooms.push(room); - } - } - } - return updatedRooms; - } - - // オーナーIDからルームを取得 - public getRoomByOwnerId(ownerId: string): Room | undefined { - for (const room of this.rooms.values()) { - if (room.ownerId === ownerId) { - return room; - } - } - return undefined; - } -} \ No newline at end of file diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 791be78..8ad1602 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -1,19 +1,19 @@ import { Server, Socket } from "socket.io"; -import { GameManager } from "../managers/GameManager.js"; -import { RoomManager } from "../managers/RoomManager.js"; +import { GameManager } from "../domains/game/GameManager"; +import { RoomManager } from "../domains/room/RoomManager"; import { SocketEvents } from "@repo/shared/src/protocol/events"; -import { registerRoomHandlers, handleRoomDisconnect } from "../handlers/RoomHandler.js"; -import { registerGameHandlers, handleGameDisconnect } from "../handlers/GameHandler.js"; +import { registerRoomHandlers, handleRoomDisconnect } from "../domains/room/RoomHandler"; +import { registerGameHandlers, handleGameDisconnect } from "../domains/game/GameHandler"; export class SocketManager { private io: Server; private gameManager: GameManager; private roomManager: RoomManager; - constructor(io: Server, gameManager: GameManager) { + constructor(io: Server, gameManager: GameManager, roomManager: RoomManager) { this.io = io; this.gameManager = gameManager; - this.roomManager = new RoomManager(); + this.roomManager = roomManager; } public initialize() { diff --git a/apps/server/src/states/MapStore.ts b/apps/server/src/states/MapStore.ts deleted file mode 100644 index 4f6f2be..0000000 --- a/apps/server/src/states/MapStore.ts +++ /dev/null @@ -1,36 +0,0 @@ -// apps/server/src/states/MapStore.ts -import type { CellUpdate } from "@repo/shared/src/domains/gridMap/gridMap.type"; -import { GAME_CONFIG } from "@repo/shared/src/config/gameConfig"; - -export class MapStore { - // 全マスの現在の色(teamId)を保持 - private gridColors: number[]; - // 次回の送信ループで送る差分リスト - private pendingUpdates: CellUpdate[]; - - constructor() { - // 初期状態は -1 (無色) などで初期化 - const totalCells = GAME_CONFIG.GRID_COLS * GAME_CONFIG.GRID_ROWS; - this.gridColors = new Array(totalCells).fill(-1); - this.pendingUpdates = []; - } - - /** - * マスを塗り、色が変化した場合のみ差分キューに追加する - */ - public paintCell(index: number, teamId: number): void { - if (this.gridColors[index] !== teamId) { - this.gridColors[index] = teamId; - this.pendingUpdates.push({ index, teamId }); - } - } - - /** - * 溜まっている差分を取得し、キューをクリアする(ループ送信時に使用) - */ - public getAndClearUpdates(): CellUpdate[] { - const updates = [...this.pendingUpdates]; - this.pendingUpdates = []; - return updates; - } -} \ No newline at end of file diff --git "a/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" "b/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" index ef06a45..cf5888b 100644 --- "a/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" +++ "b/docs/01_Env/ENV_01_\347\222\260\345\242\203\346\247\213\347\257\211\343\203\273\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.txt" @@ -55,11 +55,13 @@ │ │ │ └── server/ # 【権限】バックエンド (Node.js) │ ├── src/ - │ │ ├── entities/ # サーバー側エンティティ (Player) - │ │ ├── handlers/ # 処理ハンドラ (GameHandler, RoomHandler) - │ │ ├── managers/ # 管理クラス (GameManager, RoomManager) - │ │ ├── network/ # WebSocket処理 (SocketManager) - │ │ └── index.ts # エントリーポイント + │ │ ├── domains/ # ドメイン別実装 + │ │ │ ├── game/ # ゲーム進行 (GameHandler, GameLoop, GameManager) + │ │ │ │ ├── entities/ # ゲーム内エンティティ (Player) + │ │ │ │ └── states/ # ゲーム状態ストア (MapStore) + │ │ │ └── room/ # ルーム管理 (RoomHandler, RoomManager) + │ │ ├── network/ # WebSocket処理 (SocketManager) + │ │ └── index.ts # エントリーポイント │ ├── tsconfig.json │ └── package.json │