diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index 99066d8..a12a820 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -6,6 +6,7 @@ import { Application, Container, Ticker } from "pixi.js"; import type { BombPlacedAckPayload, BombPlacedPayload } from "@repo/shared"; import { socketManager } from "@client/network/SocketManager"; +import { AppearanceResolver } from "./application/AppearanceResolver"; import { GameMapController } from "./entities/map/GameMapController"; import { BombManager } from "./entities/bomb/BombManager"; import { GameTimer } from "./application/GameTimer"; @@ -22,6 +23,7 @@ private container: HTMLDivElement; private gameMap!: GameMapController; private timer = new GameTimer(); + private appearanceResolver = new AppearanceResolver(); private bombManager: BombManager | null = null; private networkSync: GameNetworkSync | null = null; private gameLoop: GameLoop | null = null; @@ -88,7 +90,7 @@ this.container.appendChild(this.app.canvas); // 背景マップの配置 - const gameMap = new GameMapController(); + const gameMap = new GameMapController(this.appearanceResolver); this.gameMap = gameMap; this.worldContainer.addChild(gameMap.getDisplayObject()); this.app.stage.addChild(this.worldContainer); @@ -98,6 +100,7 @@ players: this.players, myId: this.myId, gameMap: this.gameMap, + appearanceResolver: this.appearanceResolver, onGameStart: this.setGameStart.bind(this), onGameEnd: this.lockInput.bind(this), onBombPlacedFromOthers: (payload) => { @@ -122,6 +125,7 @@ players: this.players, myId: this.myId, getElapsedMs: () => this.timer.getElapsedMs(), + appearanceResolver: this.appearanceResolver, }); // サーバーへゲーム準備完了を通知 diff --git a/apps/client/src/scenes/game/application/AppearanceResolver.ts b/apps/client/src/scenes/game/application/AppearanceResolver.ts new file mode 100644 index 0000000..806fb77 --- /dev/null +++ b/apps/client/src/scenes/game/application/AppearanceResolver.ts @@ -0,0 +1,85 @@ +/** + * AppearanceResolver + * teamId から見た目リソースを解決する責務を集約する + * プレイヤー画像,塗り色,unknown のフォールバック規約を統一する + */ +import { config as sharedConfig } from "@repo/shared"; +import { config } from "@client/config"; + +/** チーム由来の見た目解決を提供するリゾルバー */ +export class AppearanceResolver { + private static readonly TEAM_CHARACTER_IMAGE_FILES = [ + "red.svg", + "blue.svg", + "green.svg", + "yellow.svg", + ] as const; + + private readonly cachedTeamColors: number[]; + + constructor() { + this.cachedTeamColors = config.GAME_CONFIG.TEAM_COLORS.map((colorCode) => this.parseColorCode(colorCode)); + } + + /** teamId からプレイヤー画像ファイル名を解決する */ + public resolvePlayerImageFile(teamId: number): string { + const { TEAM_COUNT } = config.GAME_CONFIG; + const imageFiles = AppearanceResolver.TEAM_CHARACTER_IMAGE_FILES; + + config.validateTeamConfig(); + + if (imageFiles.length !== TEAM_COUNT) { + throw new Error( + `GAME_CONFIG mismatch: imageFiles length (${imageFiles.length}) must equal TEAM_COUNT (${TEAM_COUNT})`, + ); + } + + if (!this.isKnownTeamId(teamId)) { + return imageFiles[0]; + } + + return imageFiles[teamId] ?? imageFiles[0]; + } + + /** teamId からチーム色を解決する */ + public resolveTeamColor(teamId: number): number { + if (!this.isKnownTeamId(teamId)) { + return config.GAME_CONFIG.MAP_GRID_COLOR; + } + + const teamColor = this.cachedTeamColors[teamId]; + if (!Number.isInteger(teamColor)) { + return config.GAME_CONFIG.MAP_GRID_COLOR; + } + + return teamColor; + } + + /** teamId からマップセル色を解決する,unknown は未塗り扱いにする */ + public resolveMapCellColor(teamId: number): number | null { + if (teamId === sharedConfig.UNKNOWN_TEAM_ID) { + return null; + } + + return this.resolveTeamColor(teamId); + } + + private isKnownTeamId(teamId: number): boolean { + return Number.isInteger(teamId) + && teamId >= 0 + && teamId < config.GAME_CONFIG.TEAM_COUNT; + } + + private parseColorCode(colorCode: string): number { + const normalizedColorCode = colorCode.startsWith("#") + ? colorCode.slice(1) + : colorCode; + + const parsedColor = Number.parseInt(normalizedColorCode, 16); + if (Number.isNaN(parsedColor)) { + return config.GAME_CONFIG.MAP_GRID_COLOR; + } + + return parsedColor; + } +} diff --git a/apps/client/src/scenes/game/application/GameNetworkSync.ts b/apps/client/src/scenes/game/application/GameNetworkSync.ts index c4bebb1..5d1f28a 100644 --- a/apps/client/src/scenes/game/application/GameNetworkSync.ts +++ b/apps/client/src/scenes/game/application/GameNetworkSync.ts @@ -15,6 +15,7 @@ UpdatePlayersPayload, } from "@repo/shared"; import { socketManager } from "@client/network/SocketManager"; +import { AppearanceResolver } from "@client/scenes/game/application/AppearanceResolver"; import { LocalPlayerController, RemotePlayerController } from "@client/scenes/game/entities/player/PlayerController"; import { GameMapController } from "@client/scenes/game/entities/map/GameMapController"; import type { GamePlayers } from "./game.types"; @@ -26,6 +27,7 @@ players: GamePlayers; myId: string; gameMap: GameMapController; + appearanceResolver: AppearanceResolver; onGameStart: (startTime: number) => void; onGameEnd: () => void; onBombPlacedFromOthers: (payload: BombPlacedPayload) => void; @@ -38,6 +40,7 @@ private players: GamePlayers; private myId: string; private gameMap: GameMapController; + private appearanceResolver: AppearanceResolver; private onGameStart: (startTime: number) => void; private onGameEnd: () => void; private onBombPlacedFromOthers: (payload: BombPlacedPayload) => void; @@ -54,14 +57,16 @@ private handleCurrentPlayers = (serverPlayers: CurrentPlayersPayload) => { serverPlayers.forEach((p) => { - const playerController = p.id === this.myId ? new LocalPlayerController(p) : new RemotePlayerController(p); + const playerController = p.id === this.myId + ? new LocalPlayerController(p, this.appearanceResolver) + : new RemotePlayerController(p, this.appearanceResolver); this.worldContainer.addChild(playerController.getDisplayObject()); this.players[p.id] = playerController; }); }; private handleNewPlayer = (p: NewPlayerPayload) => { - const playerController = new RemotePlayerController(p); + const playerController = new RemotePlayerController(p, this.appearanceResolver); this.worldContainer.addChild(playerController.getDisplayObject()); this.players[p.id] = playerController; }; @@ -113,6 +118,7 @@ players, myId, gameMap, + appearanceResolver, onGameStart, onGameEnd, onBombPlacedFromOthers, @@ -122,6 +128,7 @@ this.players = players; this.myId = myId; this.gameMap = gameMap; + this.appearanceResolver = appearanceResolver; this.onGameStart = onGameStart; this.onGameEnd = onGameEnd; this.onBombPlacedFromOthers = onBombPlacedFromOthers; diff --git a/apps/client/src/scenes/game/entities/bomb/BombManager.ts b/apps/client/src/scenes/game/entities/bomb/BombManager.ts index ab4efcd..3cdf04f 100644 --- a/apps/client/src/scenes/game/entities/bomb/BombManager.ts +++ b/apps/client/src/scenes/game/entities/bomb/BombManager.ts @@ -12,7 +12,9 @@ } from "@repo/shared"; import { config as sharedConfig } from "@repo/shared"; import { LocalPlayerController } from "@client/scenes/game/entities/player/PlayerController"; +import { AppearanceResolver } from "@client/scenes/game/application/AppearanceResolver"; import { BombController } from "./BombController"; +import { PendingBombRequestStore } from "./PendingBombRequestStore"; import type { GamePlayers } from "@client/scenes/game/application/game.types"; /** 経過時間ミリ秒を返す関数型 */ @@ -39,6 +41,7 @@ players: GamePlayers; myId: string; getElapsedMs: ElapsedMsProvider; + appearanceResolver: AppearanceResolver; }; /** 爆弾エンティティのライフサイクルを管理する */ @@ -47,20 +50,19 @@ private players: GamePlayers; private myId: string; private getElapsedMs: ElapsedMsProvider; - private readonly cachedTeamColors: number[]; + private appearanceResolver: AppearanceResolver; private bombs = new Map(); private bombRenderPayloadById = new Map(); - private pendingOwnRequestToTempBombId = new Map(); - private pendingTempBombIdToOwnRequest = new Map(); + private pendingBombRequestStore = new PendingBombRequestStore(); private lastBombPlacedElapsedMs = Number.NEGATIVE_INFINITY; private requestSerial = 0; - constructor({ worldContainer, players, myId, getElapsedMs }: BombManagerOptions) { + constructor({ worldContainer, players, myId, getElapsedMs, appearanceResolver }: BombManagerOptions) { this.worldContainer = worldContainer; this.players = players; this.myId = myId; this.getElapsedMs = getElapsedMs; - this.cachedTeamColors = config.GAME_CONFIG.TEAM_COLORS.map((colorCode) => this.parseColorCode(colorCode)); + this.appearanceResolver = appearanceResolver; } /** 自プレイヤー位置に爆弾を仮IDで設置し,設置要求を返す */ @@ -86,7 +88,7 @@ // 自分の爆弾は設置時点で teamId を確定して保持する const ownTeamId = this.resolveTeamIdBySocketId(this.myId); - this.registerPendingOwnRequest(requestId, tempBombId); + this.pendingBombRequestStore.register(requestId, tempBombId); this.upsertBomb(tempBombId, this.toRenderPayload(payload, ownTeamId)); this.lastBombPlacedElapsedMs = elapsedMs; return { @@ -104,13 +106,13 @@ /** 設置者本人向けACKを反映し,仮IDから正式IDへ置換する */ public applyPlacedBombAck(payload: BombPlacedAckPayload): void { - const tempBombId = this.getPendingTempBombId(payload.requestId); + const tempBombId = this.pendingBombRequestStore.getTempBombIdByRequestId(payload.requestId); if (!tempBombId) { return; } const tempPayload = this.bombRenderPayloadById.get(tempBombId); - this.removePendingRequestByRequestId(payload.requestId); + this.pendingBombRequestStore.removeByRequestId(payload.requestId); if (!tempPayload || tempBombId === payload.bombId) { return; } @@ -147,7 +149,7 @@ bomb.destroy(); this.bombs.delete(bombId); this.bombRenderPayloadById.delete(bombId); - this.removePendingRequestByTempBombId(bombId); + this.pendingBombRequestStore.removeByTempBombId(bombId); } /** 爆弾状態を更新し終了済みを破棄する */ @@ -169,8 +171,7 @@ this.bombs.forEach((bomb) => bomb.destroy()); this.bombs.clear(); this.bombRenderPayloadById.clear(); - this.pendingOwnRequestToTempBombId.clear(); - this.pendingTempBombIdToOwnRequest.clear(); + this.pendingBombRequestStore.clear(); } private isSameRenderPayload(a: BombRenderPayload, b: BombRenderPayload): boolean { @@ -192,7 +193,7 @@ explodeAtElapsedMs: payload.explodeAtElapsedMs, radiusGrid: config.GAME_CONFIG.BOMB_RADIUS_GRID, teamId, - color: this.resolveTeamColorByTeamId(teamId), + color: this.appearanceResolver.resolveTeamColor(teamId), }; } @@ -206,28 +207,6 @@ return playerController.getSnapshot().teamId; } - private resolveTeamColorByTeamId(teamId: number): number { - const teamColor = this.cachedTeamColors[teamId]; - if (!Number.isInteger(teamColor)) { - return config.GAME_CONFIG.MAP_GRID_COLOR; - } - - return teamColor; - } - - private parseColorCode(colorCode: string): number { - const normalizedColorCode = colorCode.startsWith("#") - ? colorCode.slice(1) - : colorCode; - - const parsedColor = Number.parseInt(normalizedColorCode, 16); - if (Number.isNaN(parsedColor)) { - return config.GAME_CONFIG.MAP_GRID_COLOR; - } - - return parsedColor; - } - private createRequestId(_elapsedMs: number): string { this.requestSerial += 1; return `${this.requestSerial}`; @@ -236,33 +215,4 @@ private createTempBombId(requestId: string): string { return `temp:${requestId}`; } - - private registerPendingOwnRequest(requestId: string, tempBombId: string): void { - this.pendingOwnRequestToTempBombId.set(requestId, tempBombId); - this.pendingTempBombIdToOwnRequest.set(tempBombId, requestId); - } - - private getPendingTempBombId(requestId: string): string | undefined { - return this.pendingOwnRequestToTempBombId.get(requestId); - } - - private removePendingRequestByRequestId(requestId: string): void { - const tempBombId = this.pendingOwnRequestToTempBombId.get(requestId); - if (!tempBombId) { - return; - } - - this.pendingOwnRequestToTempBombId.delete(requestId); - this.pendingTempBombIdToOwnRequest.delete(tempBombId); - } - - private removePendingRequestByTempBombId(tempBombId: string): void { - const requestId = this.pendingTempBombIdToOwnRequest.get(tempBombId); - if (!requestId) { - return; - } - - this.pendingTempBombIdToOwnRequest.delete(tempBombId); - this.pendingOwnRequestToTempBombId.delete(requestId); - } } \ No newline at end of file diff --git a/apps/client/src/scenes/game/entities/bomb/PendingBombRequestStore.ts b/apps/client/src/scenes/game/entities/bomb/PendingBombRequestStore.ts new file mode 100644 index 0000000..3cd48d9 --- /dev/null +++ b/apps/client/src/scenes/game/entities/bomb/PendingBombRequestStore.ts @@ -0,0 +1,50 @@ +/** + * PendingBombRequestStore + * 爆弾設置要求の requestId と tempBombId の対応を管理する + * ACK 到着時の置換処理で参照する対応表を一元管理する + */ + +/** 爆弾設置の pending 対応表を管理するストア */ +export class PendingBombRequestStore { + private pendingOwnRequestToTempBombId = new Map(); + private pendingTempBombIdToOwnRequest = new Map(); + + /** requestId と tempBombId の対応を登録する */ + public register(requestId: string, tempBombId: string): void { + this.pendingOwnRequestToTempBombId.set(requestId, tempBombId); + this.pendingTempBombIdToOwnRequest.set(tempBombId, requestId); + } + + /** requestId に対応する tempBombId を取得する */ + public getTempBombIdByRequestId(requestId: string): string | undefined { + return this.pendingOwnRequestToTempBombId.get(requestId); + } + + /** requestId 起点で対応を削除する */ + public removeByRequestId(requestId: string): void { + const tempBombId = this.pendingOwnRequestToTempBombId.get(requestId); + if (!tempBombId) { + return; + } + + this.pendingOwnRequestToTempBombId.delete(requestId); + this.pendingTempBombIdToOwnRequest.delete(tempBombId); + } + + /** tempBombId 起点で対応を削除する */ + public removeByTempBombId(tempBombId: string): void { + const requestId = this.pendingTempBombIdToOwnRequest.get(tempBombId); + if (!requestId) { + return; + } + + this.pendingTempBombIdToOwnRequest.delete(tempBombId); + this.pendingOwnRequestToTempBombId.delete(requestId); + } + + /** すべての pending 対応を削除する */ + public clear(): void { + this.pendingOwnRequestToTempBombId.clear(); + this.pendingTempBombIdToOwnRequest.clear(); + } +} diff --git a/apps/client/src/scenes/game/entities/map/GameMapController.ts b/apps/client/src/scenes/game/entities/map/GameMapController.ts index 41ca4ef..6213174 100644 --- a/apps/client/src/scenes/game/entities/map/GameMapController.ts +++ b/apps/client/src/scenes/game/entities/map/GameMapController.ts @@ -5,19 +5,22 @@ */ import type { gridMapTypes } from '@repo/shared'; import type { Container } from 'pixi.js'; +import { AppearanceResolver } from '@client/scenes/game/application/AppearanceResolver'; import { GameMapModel } from './GameMapModel'; import { GameMapView } from './GameMapView'; /** マップ更新の仲介責務を担うコントローラー */ export class GameMapController { + private readonly appearanceResolver: AppearanceResolver; private readonly model: GameMapModel; private readonly view: GameMapView; /** モデルとビューを生成し初期同期する */ - constructor() { + constructor(appearanceResolver: AppearanceResolver) { + this.appearanceResolver = appearanceResolver; this.model = new GameMapModel(); this.view = new GameMapView(); - this.view.renderAll(this.model.getAllTeamIds()); + this.view.renderAll(this.resolveAllCellColors(this.model.getAllTeamIds())); } /** 描画オブジェクトを取得する */ @@ -28,7 +31,7 @@ /** 全体マップ状態を反映する */ public updateMapState(state: gridMapTypes.MapState): void { this.model.applyMapState(state); - this.view.renderAll(this.model.getAllTeamIds()); + this.view.renderAll(this.resolveAllCellColors(this.model.getAllTeamIds())); } /** 差分セル更新を反映する */ @@ -38,7 +41,7 @@ updates.forEach(({ index }) => { const teamId = this.model.getTeamId(index); if (teamId === undefined) return; - this.view.renderCell(index, teamId); + this.view.renderCell(index, this.resolveCellColor(teamId)); }); } @@ -46,4 +49,14 @@ public destroy(): void { this.view.destroy(); } + + /** すべてのセルteamIdを描画色へ変換する */ + private resolveAllCellColors(teamIds: number[]): Array { + return teamIds.map((teamId) => this.resolveCellColor(teamId)); + } + + /** セルteamIdを描画色へ変換する */ + private resolveCellColor(teamId: number): number | null { + return this.appearanceResolver.resolveMapCellColor(teamId); + } } \ No newline at end of file diff --git a/apps/client/src/scenes/game/entities/map/GameMapView.ts b/apps/client/src/scenes/game/entities/map/GameMapView.ts index 16ae70b..5bdfb57 100644 --- a/apps/client/src/scenes/game/entities/map/GameMapView.ts +++ b/apps/client/src/scenes/game/entities/map/GameMapView.ts @@ -27,15 +27,15 @@ } /** 全セル状態をまとめて描画へ反映する */ - public renderAll(teamIds: number[]): void { - const maxLength = Math.min(this.cells.length, teamIds.length); + public renderAll(cellColors: Array): void { + const maxLength = Math.min(this.cells.length, cellColors.length); for (let index = 0; index < maxLength; index++) { - this.renderCell(index, teamIds[index]); + this.renderCell(index, cellColors[index]); } } /** 指定セルの状態を描画へ反映する */ - public renderCell(index: number, teamId: number): void { + public renderCell(index: number, color: number | null): void { const cell = this.cells[index]; if (!cell) return; @@ -43,10 +43,9 @@ // 対象セルをクリアしてから必要に応じて再塗布する cell.clear(); - if (teamId === -1) return; + if (color === null) return; - const hexColor = this.toHexColor(teamId); - cell.rect(0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE).fill(hexColor); + cell.rect(0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE).fill(color); } /** 描画リソースを破棄する */ @@ -95,12 +94,4 @@ this.gridGraphics.rect(0, 0, MAP_WIDTH_PX, MAP_HEIGHT_PX).stroke({ width: 5, color: MAP_BORDER_COLOR }); } - - /** チームIDから塗り色の16進数カラー値を取得する */ - private toHexColor(teamId: number): number { - config.assertValidTeamId(teamId); - const { TEAM_COLORS } = config.GAME_CONFIG; - const colorString = TEAM_COLORS[teamId]; - return parseInt(colorString.replace('#', '0x'), 16); - } } \ No newline at end of file diff --git a/apps/client/src/scenes/game/entities/player/PlayerController.ts b/apps/client/src/scenes/game/entities/player/PlayerController.ts index 409794a..c2c51de 100644 --- a/apps/client/src/scenes/game/entities/player/PlayerController.ts +++ b/apps/client/src/scenes/game/entities/player/PlayerController.ts @@ -4,6 +4,7 @@ * ローカル入力適用,リモート更新適用,描画同期を分離して扱う */ import type { playerTypes } from '@repo/shared'; +import { AppearanceResolver } from '@client/scenes/game/application/AppearanceResolver'; import { PlayerModel } from './PlayerModel'; import { PlayerView } from './PlayerView'; @@ -25,9 +26,9 @@ protected readonly view: PlayerView; /** 共通初期化としてModelとViewを生成する */ - protected constructor(data: playerTypes.PlayerData, isLocal: boolean) { + protected constructor(data: playerTypes.PlayerData, isLocal: boolean, appearanceResolver: AppearanceResolver) { this.model = new PlayerModel(data); - this.view = new PlayerView(data.teamId, isLocal); + this.view = new PlayerView(appearanceResolver.resolvePlayerImageFile(data.teamId), isLocal); const pos = this.model.getPosition(); this.view.syncPosition(pos.x, pos.y); @@ -57,8 +58,8 @@ /** ローカルプレイヤーの入力適用と描画同期を担うコントローラー */ export class LocalPlayerController extends BasePlayerController { /** ローカルプレイヤー用コントローラーを初期化する */ - constructor(data: playerTypes.PlayerData) { - super(data, true); + constructor(data: playerTypes.PlayerData, appearanceResolver: AppearanceResolver) { + super(data, true, appearanceResolver); } /** ローカル入力を座標計算へ適用する */ @@ -76,8 +77,8 @@ /** リモートプレイヤーの更新適用と補間同期を担うコントローラー */ export class RemotePlayerController extends BasePlayerController { /** リモートプレイヤー用コントローラーを初期化する */ - constructor(data: playerTypes.PlayerData) { - super(data, false); + constructor(data: playerTypes.PlayerData, appearanceResolver: AppearanceResolver) { + super(data, false, appearanceResolver); } /** ネットワーク更新を目標座標へ反映する */ diff --git a/apps/client/src/scenes/game/entities/player/PlayerView.ts b/apps/client/src/scenes/game/entities/player/PlayerView.ts index bad10fd..915be8f 100644 --- a/apps/client/src/scenes/game/entities/player/PlayerView.ts +++ b/apps/client/src/scenes/game/entities/player/PlayerView.ts @@ -11,31 +11,10 @@ export class PlayerView { public readonly displayObject: Sprite; - constructor(teamId: number, isLocal: boolean) { - const { PLAYER_RADIUS_PX, TEAM_COUNT, PLAYER_RENDER_SCALE } = + constructor(imageFileName: string, isLocal: boolean) { + const { PLAYER_RADIUS_PX, PLAYER_RENDER_SCALE } = config.GAME_CONFIG; - // 🌟 1. チームIDと画像ファイル名の紐づけ(すべて .svg に変更しました!) - const characterImages = [ - "/red.svg", // teamId: 0 のときの画像 - "/blue.svg", // teamId: 1 のときの画像 - "/green.svg", // teamId: 2 のときの画像 - "/yellow.svg", // teamId: 3 のときの画像 - ]; - - config.validateTeamConfig(); - - if (characterImages.length !== TEAM_COUNT) { - throw new Error( - `GAME_CONFIG mismatch: characterImages length (${characterImages.length}) must equal TEAM_COUNT (${TEAM_COUNT})`, - ); - } - - config.assertValidTeamId(teamId); - - // 配列から対応する画像ファイル名を取得(デフォルトは red.svg) - const imageFileName = characterImages[teamId].replace(/^\//, ""); - // 🌟 2. スプライト(画像)の生成(初期は1x1テクスチャ) this.displayObject = new Sprite(Texture.WHITE); diff --git a/packages/shared/src/config/gameConfig.ts b/packages/shared/src/config/gameConfig.ts index 6d503a3..97a5515 100644 --- a/packages/shared/src/config/gameConfig.ts +++ b/packages/shared/src/config/gameConfig.ts @@ -41,6 +41,18 @@ /** プレイヤー情報から teamId を解決できない場合に利用する既定値 */ export const UNKNOWN_TEAM_ID = -1; +/** teamId が unknown を表す値か判定する */ +export const isUnknownTeamId = (teamId: number): boolean => { + return teamId === UNKNOWN_TEAM_ID; +}; + +/** teamId が有効範囲内かを真偽値で判定する */ +export const isKnownTeamId = (teamId: number): boolean => { + return Number.isInteger(teamId) + && teamId >= 0 + && teamId < GAME_CONFIG.TEAM_COUNT; +}; + /** TEAM_COUNT と TEAM_NAMES の整合性を検証する */ export const validateTeamConfig = (): void => { const { TEAM_COUNT } = GAME_CONFIG; diff --git a/packages/shared/src/config/index.ts b/packages/shared/src/config/index.ts index ca34ae3..27ba7e8 100644 --- a/packages/shared/src/config/index.ts +++ b/packages/shared/src/config/index.ts @@ -1,5 +1,5 @@ export { GAME_CONFIG } from "./gameConfig"; export { TEAM_NAMES } from "./gameConfig"; export { UNKNOWN_TEAM_ID } from "./gameConfig"; -export { validateTeamConfig, assertValidTeamId } from "./gameConfig"; +export { validateTeamConfig, assertValidTeamId, isUnknownTeamId, isKnownTeamId } from "./gameConfig"; export { NETWORK_CONFIG } from "./networkConfig";