Skip to content

Commit

Permalink
feat(game-options): actor additional cards count game option (#747)
Browse files Browse the repository at this point in the history
Closes #735
  • Loading branch information
antoinezanardi authored Dec 13, 2023
1 parent 9423611 commit 5c6b062
Show file tree
Hide file tree
Showing 11 changed files with 23,939 additions and 23,177 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ const DEFAULT_GAME_OPTIONS: ReadonlyDeep<GameOptions> = {
scandalmonger: { markPenalty: 2 },
witch: { doesKnowWerewolvesTargets: true },
prejudicedManipulator: { isPowerlessOnWerewolvesSide: true },
actor: { isPowerlessOnWerewolvesSide: true },
actor: {
isPowerlessOnWerewolvesSide: true,
additionalCardsCount: 3,
},
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import type { CreateGameDto } from "@/modules/game/dto/create-game/create-game.d
import { RoleNames } from "@/modules/role/enums/role.enum";

function isAdditionalCardsForActorSizeRespected(value: unknown, validationArguments: ValidationArguments): boolean {
const { players } = validationArguments.object as CreateGameDto;
const actorAdditionalCardsExpectedSize = 3;
const { players, options } = validationArguments.object as CreateGameDto;
const { additionalCardsCount } = options.roles.actor;
if (value === undefined || !players.some(player => player.role.name === RoleNames.ACTOR)) {
return true;
}
Expand All @@ -16,11 +16,11 @@ function isAdditionalCardsForActorSizeRespected(value: unknown, validationArgume
}
const cards = value as { recipient: string }[];
const actorAdditionalCards = cards.filter(card => card.recipient === RoleNames.ACTOR);
return actorAdditionalCards.length === actorAdditionalCardsExpectedSize;
return actorAdditionalCards.length === additionalCardsCount;
}

function getAdditionalCardsForActorSizeDefaultMessage(): string {
return "additionalCards length for actor must be equal to 3";
return "additionalCards length for actor must be equal to options.roles.actor.additionalCardsCount";
}

function AdditionalCardsForActorSize(validationOptions?: ValidationOptions) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ApiPropertyOptions } from "@nestjs/swagger";
import { ApiProperty } from "@nestjs/swagger";
import { IsBoolean, IsOptional } from "class-validator";
import { IsBoolean, IsInt, IsOptional, Max, Min } from "class-validator";

import { ACTOR_GAME_OPTIONS_API_PROPERTIES, ACTOR_GAME_OPTIONS_FIELDS_SPECS } from "@/modules/game/schemas/game-options/roles-game-options/actor-game-options/actor-game-options.schema.constant";

Expand All @@ -12,6 +12,16 @@ class CreateActorGameOptionsDto {
@IsOptional()
@IsBoolean()
public isPowerlessOnWerewolvesSide: boolean = ACTOR_GAME_OPTIONS_FIELDS_SPECS.isPowerlessOnWerewolvesSide.default;

@ApiProperty({
...ACTOR_GAME_OPTIONS_API_PROPERTIES.additionalCardsCount,
required: false,
} as ApiPropertyOptions)
@IsOptional()
@IsInt()
@Min(ACTOR_GAME_OPTIONS_FIELDS_SPECS.additionalCardsCount.min)
@Max(ACTOR_GAME_OPTIONS_FIELDS_SPECS.additionalCardsCount.max)
public additionalCardsCount: number = ACTOR_GAME_OPTIONS_FIELDS_SPECS.additionalCardsCount.default;
}

export { CreateActorGameOptionsDto };
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ const ACTOR_GAME_OPTIONS_FIELDS_SPECS = {
required: true,
default: DEFAULT_GAME_OPTIONS.roles.actor.isPowerlessOnWerewolvesSide,
},
additionalCardsCount: {
required: true,
default: DEFAULT_GAME_OPTIONS.roles.actor.additionalCardsCount,
min: 1,
max: 5,
},
} as const satisfies Record<keyof ActorGameOptions, MongoosePropOptions>;

const ACTOR_GAME_OPTIONS_API_PROPERTIES: ReadonlyDeep<Record<keyof ActorGameOptions, ApiPropertyOptions>> = {
isPowerlessOnWerewolvesSide: {
description: "If set to `true`, the actor becomes powerless if he joins the werewolves side.",
...convertMongoosePropOptionsToApiPropertyOptions(ACTOR_GAME_OPTIONS_FIELDS_SPECS.isPowerlessOnWerewolvesSide),
},
additionalCardsCount: {
description: "Number of additional cards for the `actor` at the beginning of the game.",
...convertMongoosePropOptionsToApiPropertyOptions(ACTOR_GAME_OPTIONS_FIELDS_SPECS.additionalCardsCount),
},
};

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ class ActorGameOptions {
@Prop(ACTOR_GAME_OPTIONS_FIELDS_SPECS.isPowerlessOnWerewolvesSide)
@Expose()
public isPowerlessOnWerewolvesSide: boolean;

@ApiProperty(ACTOR_GAME_OPTIONS_API_PROPERTIES.additionalCardsCount as ApiPropertyOptions)
@Prop(ACTOR_GAME_OPTIONS_FIELDS_SPECS.additionalCardsCount)
@Expose()
public additionalCardsCount: number;
}

const ACTOR_GAME_OPTIONS_SCHEMA = SchemaFactory.createForClass(ActorGameOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,8 +720,8 @@ Feature: 🎲 Game Creation
And the request exception status code should be 400
And the request exception error should be "Bad Request"
And the request exception messages should be
| message |
| additionalCards length for actor must be equal to 3 |
| message |
| additionalCards length for actor must be equal to options.roles.actor.additionalCardsCount |

Scenario: 🎲 Game can't be created if there are too much additional cards for actor

Expand All @@ -735,8 +735,8 @@ Feature: 🎲 Game Creation
And the request exception status code should be 400
And the request exception error should be "Bad Request"
And the request exception messages should be
| message |
| additionalCards length for actor must be equal to 3 |
| message |
| additionalCards length for actor must be equal to options.roles.actor.additionalCardsCount |

Scenario: 🎲 Game can't be created if one additional card can't be given to actor

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { truncateAllCollections } from "@tests/e2e/helpers/mongoose.helper";
import { initNestApp } from "@tests/e2e/helpers/nest-app.helper";
import { createFakeCreateGameAdditionalCardDto } from "@tests/factories/game/dto/create-game/create-game-additional-card/create-game-additional-card.dto.factory";
import { createFakeGameOptionsDto } from "@tests/factories/game/dto/create-game/create-game-options/create-game-options.dto.factory";
import { createFakeCreateThiefGameOptionsDto } from "@tests/factories/game/dto/create-game/create-game-options/create-roles-game-options/create-roles-game-options.dto.factory";
import { createFakeCreateActorGameOptionsDto, createFakeCreateThiefGameOptionsDto, createFakeRolesGameOptionsDto } from "@tests/factories/game/dto/create-game/create-game-options/create-roles-game-options/create-roles-game-options.dto.factory";
import { bulkCreateFakeCreateGamePlayerDto, createFakeCreateGamePlayerDto } from "@tests/factories/game/dto/create-game/create-game-player/create-game-player.dto.factory";
import { createFakeCreateGameDto, createFakeCreateGameWithPlayersDto } from "@tests/factories/game/dto/create-game/create-game.dto.factory";
import { createFakeGetGameHistoryDto } from "@tests/factories/game/dto/get-game-history/get-game-history.dto.factory";
Expand Down Expand Up @@ -704,7 +704,26 @@ describe("Game Controller", () => {
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.WEREWOLF, recipient: RoleNames.ACTOR }),
],
}),
errorMessage: "additionalCards length for actor must be equal to 3",
errorMessage: "additionalCards length for actor must be equal to options.roles.actor.additionalCardsCount",
},
{
test: "should not allow game creation when actor additional cards are more than the expected changed limit set in options.",
payload: createFakeCreateGameDto({
players: [
createFakeCreateGamePlayerDto({ role: { name: RoleNames.WEREWOLF } }),
createFakeCreateGamePlayerDto({ role: { name: RoleNames.PIED_PIPER } }),
createFakeCreateGamePlayerDto({ role: { name: RoleNames.WITCH } }),
createFakeCreateGamePlayerDto({ role: { name: RoleNames.ACTOR } }),
],
additionalCards: [
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.SEER, recipient: RoleNames.ACTOR }),
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.HUNTER, recipient: RoleNames.ACTOR }),
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.IDIOT, recipient: RoleNames.ACTOR }),
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.ELDER, recipient: RoleNames.ACTOR }),
],
options: createFakeGameOptionsDto({ roles: createFakeRolesGameOptionsDto({ actor: createFakeCreateActorGameOptionsDto({ additionalCardsCount: 1 }) }) }),
}),
errorMessage: "additionalCards length for actor must be equal to options.roles.actor.additionalCardsCount",
},
{
test: "should not allow game creation when one actor additional card (werewolf role) is is not available for actor.",
Expand Down Expand Up @@ -774,6 +793,22 @@ describe("Game Controller", () => {
}),
errorMessage: `additionalCards.roleName for actor must be one of the following values: ${ELIGIBLE_ACTOR_ADDITIONAL_CARDS_ROLE_NAMES.toString()}`,
},
{
test: "should not allow game creation when one actor additional role card exceeds the maximum occurrences in game possible because another player has it.",
payload: createFakeCreateGameDto({
players: [
createFakeCreateGamePlayerDto({ role: { name: RoleNames.WEREWOLF } }),
createFakeCreateGamePlayerDto({ role: { name: RoleNames.PIED_PIPER } }),
createFakeCreateGamePlayerDto({ role: { name: RoleNames.WITCH } }),
createFakeCreateGamePlayerDto({ role: { name: RoleNames.ACTOR } }),
],
additionalCards: [
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.WITCH, recipient: RoleNames.ACTOR }),
createFakeCreateGameAdditionalCardDto({ roleName: RoleNames.SEER, recipient: RoleNames.ACTOR }),
],
}),
errorMessage: "additionalCards.roleName can't exceed role maximum occurrences in game. Please check `maxInGame` property of roles",
},
])("$test", async({
payload,
errorMessage,
Expand Down Expand Up @@ -1044,7 +1079,10 @@ describe("Game Controller", () => {
scandalmonger: { markPenalty: 5 },
witch: { doesKnowWerewolvesTargets: false },
prejudicedManipulator: { isPowerlessOnWerewolvesSide: false },
actor: { isPowerlessOnWerewolvesSide: false },
actor: {
isPowerlessOnWerewolvesSide: false,
additionalCardsCount: 5,
},
},
};
const payload = createFakeCreateGameWithPlayersDto({}, { options });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ import { GamePhases } from "@/modules/game/enums/game.enum";

import { DEFAULT_PLAIN_TO_INSTANCE_OPTIONS } from "@/shared/validation/constants/validation.constant";

function createFakeActorGameOptionsDto(actorGameOptions: Partial<CreateActorGameOptionsDto> = {}, override: object = {}): CreateActorGameOptionsDto {
function createFakeCreateActorGameOptionsDto(actorGameOptions: Partial<CreateActorGameOptionsDto> = {}, override: object = {}): CreateActorGameOptionsDto {
return plainToInstance(CreateActorGameOptionsDto, {
isPowerlessOnWerewolvesSide: actorGameOptions.isPowerlessOnWerewolvesSide ?? faker.datatype.boolean(),
additionalCardsCount: actorGameOptions.additionalCardsCount ?? faker.number.int({ min: 1, max: 5 }),
...override,
}, DEFAULT_PLAIN_TO_INSTANCE_OPTIONS);
}
Expand Down Expand Up @@ -251,13 +252,13 @@ function createFakeRolesGameOptionsDto(rolesGameOptions: Partial<CreateRolesGame
scandalmonger: createFakeCreateScandalmongerGameOptionsDto(rolesGameOptions.scandalmonger),
witch: createFakeCreateWitchGameOptionsDto(rolesGameOptions.witch),
prejudicedManipulator: createFakeCreatePrejudicedManipulatorGameOptionsDto(rolesGameOptions.prejudicedManipulator),
actor: createFakeActorGameOptionsDto(rolesGameOptions.actor),
actor: createFakeCreateActorGameOptionsDto(rolesGameOptions.actor),
...override,
}, DEFAULT_PLAIN_TO_INSTANCE_OPTIONS);
}

export {
createFakeActorGameOptionsDto,
createFakeCreateActorGameOptionsDto,
createFakeCreatePrejudicedManipulatorGameOptionsDto,
createFakeCreateWitchGameOptionsDto,
createFakeCreateScandalmongerGameOptionsDto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { DEFAULT_PLAIN_TO_INSTANCE_OPTIONS } from "@/shared/validation/constants
function createFakeActorGameOptions(actorGameOptions: Partial<ActorGameOptions> = {}, override: object = {}): ActorGameOptions {
return plainToInstance(ActorGameOptions, {
isPowerlessOnWerewolvesSide: actorGameOptions.isPowerlessOnWerewolvesSide ?? faker.datatype.boolean(),
additionalCardsCount: actorGameOptions.additionalCardsCount ?? faker.number.int({ min: 1, max: 5 }),
...override,
}, DEFAULT_PLAIN_TO_INSTANCE_OPTIONS);
}
Expand Down
Loading

0 comments on commit 5c6b062

Please sign in to comment.