Skip to content

Commit

Permalink
refactor(game-victory): change helper to service (#572)
Browse files Browse the repository at this point in the history
Closes #541
  • Loading branch information
antoinezanardi committed Oct 15, 2023
1 parent d5a7d79 commit 8ee0a57
Show file tree
Hide file tree
Showing 7 changed files with 53,660 additions and 53,926 deletions.
2 changes: 2 additions & 0 deletions src/modules/game/game.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";

import { GameVictoryService } from "@/modules/game/providers/services/game-victory/game-victory.service";
import { DatabaseModule } from "@/modules/config/database/database.module";
import { GameController } from "@/modules/game/controllers/game.controller";
import { GameHistoryRecordRepository } from "@/modules/game/providers/repositories/game-history-record.repository";
Expand Down Expand Up @@ -35,6 +36,7 @@ import { Game, GAME_SCHEMA } from "@/modules/game/schemas/game.schema";
GamePlayMakerService,
GamePlayVoteService,
GamePhaseService,
GameVictoryService,
GameRepository,
GameHistoryRecordService,
GameHistoryRecordRepository,
Expand Down
99 changes: 0 additions & 99 deletions src/modules/game/helpers/game-victory/game-victory.helper.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Injectable } from "@nestjs/common";

import { createAngelGameVictory, createLoversGameVictory, createNoneGameVictory, createPiedPiperGameVictory, createVillagersGameVictory, createWerewolvesGameVictory, createWhiteWerewolfGameVictory } from "@/modules/game/helpers/game-victory/game-victory.factory";
import { GamePlayActions } from "@/modules/game/enums/game-play.enum";
import { GamePhases } from "@/modules/game/enums/game.enum";
import { PlayerAttributeNames, PlayerDeathCauses } from "@/modules/game/enums/player.enum";
import { areAllPlayersDead, getLeftToCharmByPiedPiperPlayers, getPlayersWithActiveAttributeName, getPlayersWithCurrentSide, getPlayerWithCurrentRole } from "@/modules/game/helpers/game.helper";
import { doesPlayerHaveActiveAttributeWithName } from "@/modules/game/helpers/player/player-attribute/player-attribute.helper";
import { isPlayerAliveAndPowerful, isPlayerPowerful } from "@/modules/game/helpers/player/player.helper";
import type { GameVictory } from "@/modules/game/schemas/game-victory/game-victory.schema";
import type { Game } from "@/modules/game/schemas/game.schema";
import { RoleNames, RoleSides } from "@/modules/role/enums/role.enum";

import { createNoCurrentGamePlayUnexpectedException } from "@/shared/exception/helpers/unexpected-exception.factory";

@Injectable()
export class GameVictoryService {
public isGameOver(game: Game): boolean {
const { upcomingPlays, currentPlay } = game;
if (!currentPlay) {
throw createNoCurrentGamePlayUnexpectedException("isGameOver", { gameId: game._id });
}
const isShootPlayIncoming = !!upcomingPlays.find(({ action, source }) => action === GamePlayActions.SHOOT && source.name === RoleNames.HUNTER);
const gameVictoryData = this.generateGameVictoryData(game);
return areAllPlayersDead(game) || currentPlay.action !== GamePlayActions.SHOOT && !isShootPlayIncoming && !!gameVictoryData;
}

public generateGameVictoryData(game: Game): GameVictory | undefined {
if (areAllPlayersDead(game)) {
return createNoneGameVictory();
}
const victoryWinConditions: { winCondition: (game: Game) => boolean; victoryFactoryMethod: (game: Game) => GameVictory }[] = [
{ winCondition: this.doesAngelWin, victoryFactoryMethod: createAngelGameVictory },
{ winCondition: this.doLoversWin, victoryFactoryMethod: createLoversGameVictory },
{ winCondition: this.doesPiedPiperWin, victoryFactoryMethod: createPiedPiperGameVictory },
{ winCondition: this.doesWhiteWerewolfWin, victoryFactoryMethod: createWhiteWerewolfGameVictory },
{ winCondition: this.doWerewolvesWin, victoryFactoryMethod: createWerewolvesGameVictory },
{ winCondition: this.doVillagersWin, victoryFactoryMethod: createVillagersGameVictory },
];
const victoryCondition = victoryWinConditions.find(({ winCondition }) => winCondition(game));
return victoryCondition?.victoryFactoryMethod(game);
}

private doWerewolvesWin(game: Game): boolean {
const werewolvesSidedPlayers = getPlayersWithCurrentSide(game, RoleSides.WEREWOLVES);
return werewolvesSidedPlayers.length > 0 && !game.players.some(({ side, isAlive }) => side.current === RoleSides.VILLAGERS && isAlive);
}

private doVillagersWin(game: Game): boolean {
const villagersSidedPlayers = getPlayersWithCurrentSide(game, RoleSides.VILLAGERS);
return villagersSidedPlayers.length > 0 && !game.players.some(({ side, isAlive }) => side.current === RoleSides.WEREWOLVES && isAlive);
}

private doLoversWin(game: Game): boolean {
const lovers = getPlayersWithActiveAttributeName(game, PlayerAttributeNames.IN_LOVE);
return lovers.length > 0 && game.players.every(player => {
const isPlayerInLove = doesPlayerHaveActiveAttributeWithName(player, PlayerAttributeNames.IN_LOVE, game);
return isPlayerInLove && player.isAlive || !isPlayerInLove && !player.isAlive;
});
}

private doesWhiteWerewolfWin(game: Game): boolean {
const whiteWerewolfPlayer = getPlayerWithCurrentRole(game, RoleNames.WHITE_WEREWOLF);
return !!whiteWerewolfPlayer && game.players.every(({ role, isAlive }) =>
role.current === RoleNames.WHITE_WEREWOLF && isAlive || role.current !== RoleNames.WHITE_WEREWOLF && !isAlive);
}

private doesPiedPiperWin(game: Game): boolean {
const { isPowerlessIfInfected } = game.options.roles.piedPiper;
const piedPiperPlayer = getPlayerWithCurrentRole(game, RoleNames.PIED_PIPER);
const leftToCharmPlayers = getLeftToCharmByPiedPiperPlayers(game);
return !!piedPiperPlayer && isPlayerAliveAndPowerful(piedPiperPlayer, game) && !leftToCharmPlayers.length &&
(!isPowerlessIfInfected || piedPiperPlayer.side.current === RoleSides.VILLAGERS);
}

private doesAngelWin(game: Game): boolean {
const angelPlayer = getPlayerWithCurrentRole(game, RoleNames.ANGEL);
const { turn, phase } = game;
if (!angelPlayer?.death || angelPlayer.isAlive || !isPlayerPowerful(angelPlayer, game) || turn > 1) {
return false;
}
const { cause: deathCause } = angelPlayer.death;
return deathCause === PlayerDeathCauses.EATEN || deathCause === PlayerDeathCauses.VOTE && phase === GamePhases.NIGHT;
}
}
7 changes: 4 additions & 3 deletions src/modules/game/providers/services/game.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { MakeGamePlayDto } from "@/modules/game/dto/make-game-play/make-gam
import { GameStatuses } from "@/modules/game/enums/game.enum";
import { isGamePhaseOver } from "@/modules/game/helpers/game-phase/game-phase.helper";
import { createMakeGamePlayDtoWithRelations } from "@/modules/game/helpers/game-play/game-play.helper";
import { generateGameVictoryData, isGameOver } from "@/modules/game/helpers/game-victory/game-victory.helper";
import { GameVictoryService } from "@/modules/game/providers/services/game-victory/game-victory.service";
import { createGame as createGameFromFactory } from "@/modules/game/helpers/game.factory";
import { getExpectedPlayersToPlay } from "@/modules/game/helpers/game.helper";
import { GameRepository } from "@/modules/game/providers/repositories/game.repository";
Expand All @@ -33,6 +33,7 @@ export class GameService {
private readonly gamePlayValidatorService: GamePlayValidatorService,
private readonly gamePlayMakerService: GamePlayMakerService,
private readonly gamePhaseService: GamePhaseService,
private readonly gameVictoryService: GameVictoryService,
private readonly gameRepository: GameRepository,
private readonly playerAttributeService: PlayerAttributeService,
private readonly gameHistoryRecordService: GameHistoryRecordService,
Expand Down Expand Up @@ -82,7 +83,7 @@ export class GameService {
}
const gameHistoryRecordToInsert = this.gameHistoryRecordService.generateCurrentGameHistoryRecordToInsert(game, clonedGame, play);
await this.gameHistoryRecordService.createGameHistoryRecord(gameHistoryRecordToInsert);
if (isGameOver(clonedGame)) {
if (this.gameVictoryService.isGameOver(clonedGame)) {
clonedGame = this.setGameAsOver(clonedGame);
}
return this.updateGame(clonedGame._id, clonedGame);
Expand Down Expand Up @@ -112,7 +113,7 @@ export class GameService {
private setGameAsOver(game: Game): Game {
const clonedGame = createGameFromFactory(game);
clonedGame.status = GameStatuses.OVER;
clonedGame.victory = generateGameVictoryData(clonedGame);
clonedGame.victory = this.gameVictoryService.generateGameVictoryData(clonedGame);
return clonedGame;
}
}

0 comments on commit 8ee0a57

Please sign in to comment.