Newer
Older
PixelPaintWar / apps / server / src / domains / game / application / useCases / startGameUseCase.ts
/**
 * startGameUseCase
 * ルーム内プレイヤーでゲームセッションを開始し,進行イベントを通知する
 */
import type {
  BombOutputPort,
  GameOutputPort,
  StartGamePort,
} from "../ports/gameUseCasePorts";
import { logEvent } from "@server/logging/logger";
import {
  gameUseCaseLogEvents,
  logResults,
  logScopes,
} from "@server/logging/index";
import { placeBombUseCase } from "./placeBombUseCase";

const excludeRecipientFromPlayerUpdates = <
  TPlayerUpdate extends { id: string },
>(
  playerUpdates: TPlayerUpdate[],
  recipientId: string,
): TPlayerUpdate[] => {
  return playerUpdates.filter(
    (playerUpdate) => playerUpdate.id !== recipientId,
  );
};

type StartGameUseCaseParams = {
  roomId: string;
  playerIds: string[];
  recipientPlayerIds?: string[];
  gameManager: StartGamePort;
  onGameEnd: () => void;
  output: Pick<
    GameOutputPort,
    | "publishUpdatePlayersToSocket"
    | "publishMapCellUpdatesToRoom"
    | "publishGameEndToRoom"
    | "publishGameResultToRoom"
    | "publishGameStartToRoom"
  > &
    Pick<
      BombOutputPort,
      "publishBombPlacedToOthersInRoom" | "publishBombPlacedAckToSocket"
    >;
};

/** ゲームセッション開始とティック通知,終了通知を実行する */
export const startGameUseCase = ({
  roomId,
  playerIds,
  recipientPlayerIds,
  gameManager,
  onGameEnd,
  output,
}: StartGameUseCaseParams) => {
  const updateRecipients = recipientPlayerIds ?? playerIds;

  gameManager.startRoomSession(
    playerIds,
    (tickData) => {
      if (tickData.playerUpdates.length > 0) {
        updateRecipients.forEach((playerId) => {
          const updatesForPlayer = excludeRecipientFromPlayerUpdates(
            tickData.playerUpdates,
            playerId,
          );

          if (updatesForPlayer.length === 0) {
            return;
          }

          output.publishUpdatePlayersToSocket(playerId, updatesForPlayer);
        });
      }

      if (tickData.cellUpdates.length > 0) {
        output.publishMapCellUpdatesToRoom(roomId, tickData.cellUpdates);
      }
    },
    (resultPayload) => {
      logEvent(logScopes.GAME_USE_CASE, {
        event: gameUseCaseLogEvents.GAME_END,
        result: logResults.EMITTED,
        roomId,
        reason: "duration_elapsed",
      });
      output.publishGameEndToRoom(roomId);
      output.publishGameResultToRoom(roomId, resultPayload);
      onGameEnd();
    },
    (ownerId, payload) => {
      placeBombUseCase({
        roomId,
        bombStore: gameManager,
        input: {
          socketId: ownerId,
          payload,
          nowMs: Date.now(),
        },
        output,
      });
    },
  );

  const startTime = gameManager.getRoomStartTime() || Date.now();
  output.publishGameStartToRoom(roomId, { startTime });
};