diff --git a/apps/server/src/domains/game/application/services/GameRoomSession.ts b/apps/server/src/domains/game/application/services/GameRoomSession.ts index bc77b27..02fdc48 100644 --- a/apps/server/src/domains/game/application/services/GameRoomSession.ts +++ b/apps/server/src/domains/game/application/services/GameRoomSession.ts @@ -3,7 +3,11 @@ * 1ルーム分のゲーム進行状態とゲームループ実行を管理する */ import { logEvent } from "@server/logging/logger"; -import { gameDomainLogEvents, logResults, logScopes } from "@server/logging/index"; +import { + gameDomainLogEvents, + logResults, + logScopes, +} from "@server/logging/index"; import type { gameTypes } from "@repo/shared"; import { GameLoop } from "../../loop/GameLoop"; import { Player } from "../../entities/player/Player.js"; @@ -14,6 +18,9 @@ setPlayerPosition, } from "../../entities/player/playerMovement.js"; +// 💡 追加: チーム割り当てサービスをインポート +import { TeamAssignmentService } from "../services/TeamAssignmentService.js"; + /** ルーム単位のゲーム状態とループ進行を保持するセッションクラス */ export class GameRoomSession { private players: Map; @@ -21,12 +28,22 @@ private gameLoop: GameLoop | null = null; private startTime: number | undefined; - constructor(private roomId: string, playerIds: string[]) { + constructor( + private roomId: string, + playerIds: string[], + ) { this.players = new Map(); this.mapStore = new MapStore(); playerIds.forEach((playerId) => { - const player = createSpawnedPlayer(playerId); + // 💡 追加: 現在の this.players (生成済みのプレイヤー達) を見て、一番人数の少ないチームを算出する + const assignedTeamId = TeamAssignmentService.getBalancedTeamId( + this.players, + ); + + // 💡 修正: バランス良く割り当てられたチームIDを渡してプレイヤーを生成する + const player = createSpawnedPlayer(playerId, assignedTeamId); + this.players.set(playerId, player); }); } @@ -34,7 +51,7 @@ public start( tickRate: number, onTick: (data: gameTypes.TickData) => void, - onGameEnd: () => void + onGameEnd: () => void, ): void { if (this.gameLoop) { return; @@ -50,7 +67,7 @@ () => { this.dispose(); onGameEnd(); - } + }, ); this.gameLoop.start(); @@ -97,17 +114,11 @@ return this.players.has(id); } - public getPlayerIds(): string[] { - return Array.from(this.players.keys()); - } - - public isEmpty(): boolean { - return this.players.size === 0; - } - public dispose(): void { - this.gameLoop?.stop(); - this.gameLoop = null; - this.startTime = undefined; + if (this.gameLoop) { + this.gameLoop.stop(); + this.gameLoop = null; + } + this.players.clear(); } -} \ No newline at end of file +} diff --git a/apps/server/src/domains/game/application/services/TeamAssignmentService.ts b/apps/server/src/domains/game/application/services/TeamAssignmentService.ts new file mode 100644 index 0000000..8108586 --- /dev/null +++ b/apps/server/src/domains/game/application/services/TeamAssignmentService.ts @@ -0,0 +1,36 @@ +/** + * TeamAssignmentService + * プレイヤーのチーム割り当てロジックを提供するサービス + */ +import { config } from "@repo/shared"; +import type { Player } from "../../entities/player/Player.js"; + +export class TeamAssignmentService { + /** + * 現在のプレイヤー状況から、最も人数が少ないチームIDを算出する + */ + public static getBalancedTeamId(currentPlayers: Map): number { + const teamCount = config.GAME_CONFIG.TEAM_COLORS.length; + const teamPopulations = new Array(teamCount).fill(0); + + // 現在の各チームの所属人数を数え上げる + for (const player of currentPlayers.values()) { + if (player.teamId >= 0 && player.teamId < teamCount) { + teamPopulations[player.teamId]++; + } + } + + // 最も人数が少ないチームを探す + let minPopulation = Infinity; + let targetTeamId = 0; + + for (let i = 0; i < teamCount; i++) { + if (teamPopulations[i] < minPopulation) { + minPopulation = teamPopulations[i]; + targetTeamId = i; // 人数が一番少ないチームIDを記録 + } + } + + return targetTeamId; + } +} diff --git a/apps/server/src/domains/game/entities/player/Player.ts b/apps/server/src/domains/game/entities/player/Player.ts index 68a033e..d0f0467 100644 --- a/apps/server/src/domains/game/entities/player/Player.ts +++ b/apps/server/src/domains/game/entities/player/Player.ts @@ -3,21 +3,17 @@ * サーバー側で保持するプレイヤー状態モデルを定義する */ import type { playerTypes } from "@repo/shared"; -import { config } from "@repo/shared"; +// configのimportは不要になります -// サーバー側保持プレイヤー状態モデル -/** サーバー側プレイヤー座標と所属チームを保持するエンティティ */ export class Player implements playerTypes.PlayerData { public id: string; public x: number = 0; public y: number = 0; public teamId: number; - constructor(id: string) { + // 💡 コンストラクタで teamId を受け取るように変更 + constructor(id: string, teamId: number) { this.id = id; - - // GAME_CONFIGからチーム数を動的に取得して割り当て - const teamCount = config.GAME_CONFIG.TEAM_COLORS.length; - this.teamId = Math.floor(Math.random() * teamCount); + this.teamId = teamId; } -} \ No newline at end of file +} diff --git a/apps/server/src/domains/game/entities/player/playerSpawn.ts b/apps/server/src/domains/game/entities/player/playerSpawn.ts index b17e88c..f368069 100644 --- a/apps/server/src/domains/game/entities/player/playerSpawn.ts +++ b/apps/server/src/domains/game/entities/player/playerSpawn.ts @@ -6,47 +6,39 @@ import { Player } from "./Player.js"; /** プレイヤーを生成し,初期スポーン座標を設定して返す */ -// 💡 引数は元通り `id` だけでOKです! -export const createSpawnedPlayer = (id: string): Player => { - // ここで Player が作られ、同時に player.teamId も自動で決まります! - const player = new Player(id); +// 💡 引数に teamId を追加 +export const createSpawnedPlayer = (id: string, teamId: number): Player => { + const player = new Player(id, teamId); // ここにteamIdを渡す! const { GRID_COLS, GRID_ROWS } = config.GAME_CONFIG; - // 1. チームごとの基準点を決める let baseX = GRID_COLS / 2; let baseY = GRID_ROWS / 2; - // 💡 自動で決まった teamId を使って、四隅に振り分ける switch (player.teamId % 4) { - case 0: // チーム0: 左上 + case 0: // 左上 baseX = 2; baseY = 2; break; - case 1: // チーム1: 右下 + case 1: // 右下 baseX = GRID_COLS - 2; baseY = GRID_ROWS - 2; break; - case 2: // チーム2: 右上 + case 2: // 右上 baseX = GRID_COLS - 2; baseY = 2; break; - case 3: // チーム3: 左下 + case 3: // 左下 baseX = 2; baseY = GRID_ROWS - 2; break; } - // 2. 完全に同じ座標に重ならないように、少しランダムに散らす(±1マスの範囲) const scatterX = (Math.random() - 0.5) * 2; const scatterY = (Math.random() - 0.5) * 2; - player.x = baseX + scatterX; - player.y = baseY + scatterY; - - // マップ外(壁の中)にはみ出さないように制限(クランプ) - player.x = Math.max(1, Math.min(GRID_COLS - 1, player.x)); - player.y = Math.max(1, Math.min(GRID_ROWS - 1, player.y)); + player.x = Math.max(1, Math.min(GRID_COLS - 1, baseX + scatterX)); + player.y = Math.max(1, Math.min(GRID_ROWS - 1, baseY + scatterY)); return player; };