Skip to content

Commit

Permalink
feat(game-playground): bury dead bodies playground (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinezanardi committed Mar 7, 2024
1 parent fbe47b1 commit 4dce309
Show file tree
Hide file tree
Showing 30 changed files with 22,474 additions and 19,429 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<template>
<div
id="game-bury-dead-bodies-playground"
class="flex items-center justify-center"
>
<GameDevotedServantStealsRolePlayground
v-if="eligibleStolenRoleByDevotedServantPlayer"
id="game-devoted-servant-steals-role-playground"
/>

<NoActionNeeded
v-else
id="no-action-needed"
/>
</div>
</template>

<script setup lang="ts">
import { storeToRefs } from "pinia";
import GameDevotedServantStealsRolePlayground from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameBuryDeadBodiesPlayground/GameDevotedServantStealsRolePlayground/GameDevotedServantStealsRolePlayground.vue";
import NoActionNeeded from "~/components/shared/game/game-play/NoNeededAction/NoActionNeeded.vue";
import { useGamePlay } from "~/composables/api/game/game-play/useGamePlay";
import { PlayerInteraction } from "~/composables/api/game/types/game-play/game-play-eligible-targets/interactable-player/player-interaction/player-interaction.class";
import type { Player } from "~/composables/api/game/types/players/player.class";
import { RoleNames } from "~/composables/api/role/enums/role.enums";
import { useGameStore } from "~/stores/game/useGameStore";
const gameStore = useGameStore();
const { game } = storeToRefs(gameStore);
const { getPlayerWithInteractionInCurrentGamePlay } = useGamePlay(game);
const eligibleStolenRoleByDevotedServantPlayer = computed<Player | undefined>(() => {
const stolenRoleByDevotedServantInteraction = PlayerInteraction.create({
source: RoleNames.DEVOTED_SERVANT,
type: "steal-role",
});
return getPlayerWithInteractionInCurrentGamePlay(stolenRoleByDevotedServantInteraction);
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<div
id="game-devoted-servant-steal-role-playground"
class="flex flex-col items-center"
>
<GlowCapture>
<GlowElement>
<RoleImage
class="glow:border-gray-400"
definition="normal"
:role-name="RoleNames.DEVOTED_SERVANT"
sizes="175px"
/>
</GlowElement>
</GlowCapture>

<h3
id="devoted-servant-steals-role-question"
class="my-4"
>
{{ devotedServantStealsRoleQuestion }}
</h3>

<VuePrimeToggleButton
id="does-devoted-servant-steal-role-button"
v-model="doesDevotedServantStealRole"
class="w-9rem"
off-icon="fa fa-thumbs-down me-4"
:off-label="$t('components.GameDevotedServantStealsRolePlayground.sheDoesntStealRole')"
on-icon="fa fa-thumbs-up me-4 !text-white"
:on-label="$t('components.GameDevotedServantStealsRolePlayground.sheStealsRole')"
@change="handleDoesDevotedServantStealRoleChange"
/>
</div>
</template>

<script setup lang="ts">
import { storeToRefs } from "pinia";
import RoleImage from "~/components/shared/role/RoleImage/RoleImage.vue";
import { useGamePlay } from "~/composables/api/game/game-play/useGamePlay";
import { PlayerInteraction } from "~/composables/api/game/types/game-play/game-play-eligible-targets/interactable-player/player-interaction/player-interaction.class";
import type { Player } from "~/composables/api/game/types/players/player.class";
import { RoleNames } from "~/composables/api/role/enums/role.enums";
import { useMakeGamePlayDtoStore } from "~/stores/game/make-game-play-dto/useMakeGamePlayDtoStore";
import { useGameStore } from "~/stores/game/useGameStore";
const { t } = useI18n();
const gameStore = useGameStore();
const { game } = storeToRefs(gameStore);
const makeGamePlayDtoStore = useMakeGamePlayDtoStore();
const {
addMakeGamePlayTargetDto,
removeMakeGamePlayTargetDto,
} = makeGamePlayDtoStore;
const { getPlayerWithInteractionInCurrentGamePlay } = useGamePlay(game);
const doesDevotedServantStealRole = ref<boolean>(false);
const eliminatedPlayer = computed<Player | undefined>(() => getPlayerWithInteractionInCurrentGamePlay(PlayerInteraction.create({
source: RoleNames.DEVOTED_SERVANT,
type: "steal-role",
})));
const devotedServantStealsRoleQuestion = computed<string>(() => {
const playerName = eliminatedPlayer.value?.name;
return t("components.GameDevotedServantStealsRolePlayground.doesDevotedServantStealRole", { playerName });
});
function handleDoesDevotedServantStealRoleChange(): void {
if (eliminatedPlayer.value?._id === undefined) {
throw createError("Eliminated player is not found.");
}
if (doesDevotedServantStealRole.value) {
addMakeGamePlayTargetDto({ playerId: eliminatedPlayer.value._id });
return;
}
removeMakeGamePlayTargetDto(eliminatedPlayer.value._id);
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<script lang="ts" setup>
import { storeToRefs } from "pinia";
import GameBuryDeadBodiesPlayground from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameBuryDeadBodiesPlayground/GameBuryDeadBodiesPlayground.vue";
import GameChooseCardPlayground from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameChooseCardPlayground/GameChooseCardPlayground.vue";
import GameChooseSidePlayground from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameChooseSidePlayground/GameChooseSidePlayground.vue";
import GameNoActionPlayground from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameNoActionPlayground/GameNoActionPlayground.vue";
Expand All @@ -24,6 +25,7 @@ import type { GamePlayType } from "~/composables/api/game/types/game-play/game-p
import { useGameStore } from "~/stores/game/useGameStore";
type GamePlaygroundTypeComponent =
| typeof GameBuryDeadBodiesPlayground
| typeof GameChooseCardPlayground
| typeof GameChooseSidePlayground
| typeof GameNoActionPlayground
Expand All @@ -43,6 +45,7 @@ const gamePlaygroundTypeComponentToRender = computed<GamePlaygroundTypeComponent
return GameUsePotionsPlayground;
}
const currentGamePlayTypeComponents: Record<GamePlayType, GamePlaygroundTypeComponent> = {
"bury-dead-bodies": GameBuryDeadBodiesPlayground,
"choose-card": GameChooseCardPlayground,
"choose-side": GameChooseSidePlayground,
"no-action": GameNoActionPlayground,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
v-if="player.side.current === RoleSides.WEREWOLVES"
id="player-werewolf-role-image"
:alt="roleImageAlt"
class="!border-2"
:class="playerSideGlowClass"
definition="small"
height="50"
:role-name="player.role.current"
Expand All @@ -49,6 +51,8 @@
v-if="player.side.current === RoleSides.VILLAGERS"
id="player-villager-role-image"
:alt="roleImageAlt"
class="!border-2"
:class="playerSideGlowClass"
definition="small"
height="50"
:role-name="player.role.current"
Expand Down
14 changes: 14 additions & 0 deletions components/shared/game/game-play/NoNeededAction/NoActionNeeded.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<div id="no-action-needed">
<h2
id="no-action-needed-message"
class="flex flex-col items-center justify-center text-gray-300"
>
<i class="fa fa-2x fa-thumbs-up mb-3 text-success"/>

<span>
{{ $t("components.NoActionNeeded.noActionNeeded") }}
</span>
</h2>
</div>
</template>
25 changes: 15 additions & 10 deletions components/shared/role/RoleImage/RoleImage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<NuxtImg
:alt="alt"
class="role-image"
:alt="imageAlt"
class="border-4 border-gray-800 role-image rounded-lg"
:class="imageClasses"
:height="sizes"
placeholder="/svg/misc/infinite-spinner.svg"
Expand All @@ -19,6 +19,8 @@ const props = withDefaults(defineProps<RoleImageProps>(), {
definition: "normal",
});
const { t } = useI18n();
const runtimeConfig = useRuntimeConfig();
const backImageSrc = "/img/role/back.jpeg";
Expand All @@ -35,11 +37,14 @@ const roleImageSrc = computed<string>(() => {
});
const imageClasses = computed<string>(() => `h-[${props.sizes}px] w-[${props.sizes}px]`);
</script>

<style lang="scss" scoped>
.role-image {
border-radius: 10%;
border: 3px solid #1c1c1c;
}
</style>
const imageAlt = computed<string>(() => {
if (props.alt !== undefined) {
return props.alt;
}
if (props.roleName !== undefined) {
return t(`shared.role.name.${props.roleName}`);
}
return t("components.RoleImage.back");
});
</script>
2 changes: 1 addition & 1 deletion components/shared/role/RoleImage/role-image.types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RoleNames } from "~/composables/api/role/enums/role.enums";

type RoleImageProps = {
alt: string,
alt?: string,
roleName?: RoleNames,
sizes?: string,
definition?: "normal" | "small";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const GAME_PLAY_TYPES = [
"choose-card",
"choose-side",
"request-another-vote",
"bury-dead-bodies",
] as const;

const GAME_PLAY_ACTIONS = [
Expand Down
23 changes: 22 additions & 1 deletion composables/api/game/game-play/useGamePlay.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { ComputedRef, Ref } from "vue";

import type { PlayerInteraction } from "~/composables/api/game/types/game-play/game-play-eligible-targets/interactable-player/player-interaction/player-interaction.class";
import type { GamePlayAction, GamePlayType } from "~/composables/api/game/types/game-play/game-play.types";
import type { Game } from "~/composables/api/game/types/game.class";
import type { Player } from "~/composables/api/game/types/players/player.class";

type UseGamePlay = {
currentPlayType: ComputedRef<GamePlayType | undefined>;
getPlayerWithInteractionInCurrentGamePlay: (interaction: PlayerInteraction) => Player | undefined;
};

function useGamePlay(game: Ref<Game>): UseGamePlay {
Expand Down Expand Up @@ -39,6 +42,7 @@ function useGamePlay(game: Ref<Game>): UseGamePlay {
"choose-card": ["choose-card"],
"choose-side": ["choose-side"],
"request-another-vote": ["request-another-vote"],
"bury-dead-bodies": ["bury-dead-bodies"],
};
for (const key of Object.keys(gamePlayTypesActions)) {
const gamePlayType = key as GamePlayType;
Expand All @@ -49,7 +53,24 @@ function useGamePlay(game: Ref<Game>): UseGamePlay {
return undefined;
});

return { currentPlayType };
function getPlayerWithInteractionInCurrentGamePlay(interaction: PlayerInteraction): Player | undefined {
if (!game.value.currentPlay?.eligibleTargets?.interactablePlayers) {
return undefined;
}
const { interactablePlayers } = game.value.currentPlay.eligibleTargets;
for (const interactablePlayer of interactablePlayers) {
const { interactions, player } = interactablePlayer;
const playerInteraction = interactions.find(({ type, source }) => type === interaction.type && source === interaction.source);
if (playerInteraction) {
return player;
}
}
return undefined;
}
return {
currentPlayType,
getPlayerWithInteractionInCurrentGamePlay,
};
}

export { useGamePlay };
8 changes: 8 additions & 0 deletions modules/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,14 @@
"sheriffBySurvivors": "The survivors elected this player as the Sheriff.",
"stolenRoleByDevotedServant": "The Devoted Servant stole the role of this player.",
"worshipedByWildChild": "The Wild Child chose this player as his model."
},
"NoActionNeeded": {
"noActionNeeded": "No action needed, the game can proceed"
},
"GameDevotedServantStealsRolePlayground": {
"doesDevotedServantStealRole": "Does the Devoted Servant wants to steal the role of the eliminated player {playerName} ?",
"sheStealsRole": "She steals the role",
"sheDoesntStealRole": "She doesn't steal the role"
}
}
}
8 changes: 8 additions & 0 deletions modules/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,14 @@
"sheriffBySurvivors": "Les survivants ont élu ce joueur comme Maire.",
"stolenRoleByDevotedServant": "La Servante Dévouée a volé le rôle de ce joueur.",
"worshipedByWildChild": "L'Enfant Sauvage a choisi ce joueur comme modèle."
},
"NoActionNeeded": {
"noActionNeeded": "Aucune action nécessaire, la partie peut continuer"
},
"GameDevotedServantStealsRolePlayground": {
"doesDevotedServantStealRole": "Est-ce que la Servante Dévouée souhaite voler le rôle du joueur éliminé {playerName} ?",
"sheStealsRole": "Elle vole le rôle",
"sheDoesntStealRole": "Elle ne vole pas le rôle"
}
}
}
1 change: 1 addition & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default defineNuxtConfig({
"FloatLabel",
"AutoComplete",
"Toast",
"ToggleButton",
],
},
directives: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ async function saveFullPageScreenshot(page: Page, screenshotPath: string): Promi
}

async function tryScreenshotWithCorrectDimensions(page: Page, baseScreenshot: PNG): Promise<PNG> {
const maxRetries = 5;
const maxRetries = 10;
const timeoutMs = 500;
const screenshots: PNG[] = [];
for (let i = 0; i < maxRetries; i++) {
const screenshot = PNG.sync.read(await page.screenshot(DEFAULT_PLAYWRIGHT_PAGE_SCREENSHOT_OPTIONS));
Expand All @@ -22,7 +23,13 @@ async function tryScreenshotWithCorrectDimensions(page: Page, baseScreenshot: PN

return screenshot;
}
console.info(`The screenshot does not have the correct dimensions (${screenshot.width}x${screenshot.height}). Retrying in ${timeoutMs}ms...`);
await new Promise(resolve => {
setTimeout(resolve, timeoutMs);
});
}
console.error(`The screenshot does not have the correct dimensions after ${maxRetries} retries`);

return screenshots[screenshots.length - 1];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { saveFullPageScreenshot, throwErrorIfBrokenThreshold, tryScreenshotWithC
import { ACCEPTANCE_TESTS_PATH_SCREENSHOTS_PATH } from "~/tests/acceptance/shared/constants/acceptance.constants";
import type { CustomWorld } from "~/tests/acceptance/shared/types/word.types";

Then(/^the page should match the snapshot with name "(?<name>.+)"$/u, async function(this: CustomWorld, name: string): Promise<void> {
const screenshotStepTimeout = 30000;

Then(/^the page should match the snapshot with name "(?<name>.+)"$/u, { timeout: screenshotStepTimeout }, async function(this: CustomWorld, name: string): Promise<void> {
await Promise.all([
this.page.waitForLoadState("load"),
this.page.waitForLoadState("networkidle"),
Expand Down
Binary file modified tests/acceptance/screenshots/darwin/About Page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed tests/acceptance/screenshots/linux/About Page.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4dce309

Please sign in to comment.