Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(thief): thief playground #723

Merged
merged 5 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/pages/about/AboutHowToContribute.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
id="about-how-to-contribute-title"
class="flex items-center"
>
<i class="fa fa-2x fa-handshake-angle me-3 text-green-500"/>
<i class="fa fa-2x fa-handshake-angle me-3 text-emerald-500"/>

<span>
{{ $t('components.AboutHowToContribute.howToContribute') }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<GameOptionInputGroup
id="game-lobby-options-hub-composition-tab-is-hidden-input-group"
:option-description="isCompositionHiddenDescription"
option-icon-class="fa fa-eye-slash text-green-400"
option-icon-class="fa fa-eye-slash text-emerald-400"
:option-label="$t('components.GameLobbyOptionsHubCompositionTab.options.isHidden.label')"
>
<AffirmativeToggleButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<GameOptionInputGroup
id="game-lobby-options-hub-roles-tab-wolf-hound-is-side-randomly-chosen-input-group"
:option-description="isWolfHoundSideRandomlyChosenDescription"
option-icon-class="fa fa-random text-green-500"
option-icon-class="fa fa-random text-emerald-500"
:option-label="$t('components.GameLobbyOptionsHubRolesTabWolfHound.options.isSideRandomlyChosen.label')"
>
<AffirmativeToggleButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,81 @@
<template>
<div id="game-thief-may-have-chosen-card-event"/>
</template>
<GameEventWithTexts
id="game-thief-may-have-chosen-card-event"
:texts="gameThiefMayHaveChosenCardEventTexts"
>
<div class="flex h-full items-center justify-center">
<GameEventFlippingPlayersCard
id="game-event-flipping-last-play-source-card"
:players="event.players"
:svg-icon-path="svgIconPath"
/>
</div>
</GameEventWithTexts>
</template>

<script lang="ts" setup>
import { storeToRefs } from "pinia";
import type { CurrentGameEventProps } from "~/components/pages/game/GamePlaying/GameEventsMonitor/GameEventsMonitorCurrentEvent/game-events-monitor-current-event.types.js";
import GameEventFlippingPlayersCard from "~/components/shared/game/game-event/GameEventFlippingPlayersCard/GameEventFlippingPlayersCard.vue";
import GameEventWithTexts from "~/components/shared/game/game-event/GameEventWithTexts/GameEventWithTexts.vue";
import type { GameAdditionalCard } from "~/composables/api/game/types/game-additional-card/game-additional-card.class";
import type { Player } from "~/composables/api/game/types/players/player.class";
import { useRoleName } from "~/composables/api/role/useRoleName";
import { useAudioStore } from "~/stores/audio/useAudioStore";
import { useGameStore } from "~/stores/game/useGameStore";

const props = defineProps<CurrentGameEventProps>();

const gameStore = useGameStore();
const { game } = storeToRefs(gameStore);

const { getDefiniteRoleNameLabel } = useRoleName();

const { t } = useI18n();

const audioStore = useAudioStore();
const { playSoundEffect } = audioStore;

const thiefPlayer = computed<Player | undefined>(() => props.event.players?.[0]);

const chosenCard = computed<GameAdditionalCard | undefined>(() => game.value.lastGameHistoryRecord?.play.chosenCard);

const isThiefChosenCardRevealed = computed<boolean>(() => game.value.options.roles.thief.isChosenCardRevealed);

const gameThiefMayHaveChosenCardEventTexts = computed<string[]>(() => {
if (!thiefPlayer.value) {
return [t("components.GameThiefMayHaveChosenCardEvent.cantFindThief")];
}
if (isThiefChosenCardRevealed.value) {
return gameThiefRevealedChosenCardTexts.value;
}
return [
t("components.GameThiefMayHaveChosenCardEvent.thiefMayHaveChosenCard"),
t("components.GameThiefMayHaveChosenCardEvent.gameMasterWillSwitchCardsIfThiefChoseCard"),
];
});

const gameThiefRevealedChosenCardTexts = computed<string[]>(() => {
if (chosenCard.value === undefined) {
return [
t("components.GameThiefMayHaveChosenCardEvent.thiefHasNotChosenCard"),
t("components.GameThiefMayHaveChosenCardEvent.gameMasterCanTakeAwayRemainingCards"),
];
}
const chosenCardDefiniteRoleName = getDefiniteRoleNameLabel(chosenCard.value.roleName, 1);

return [
t("components.GameThiefMayHaveChosenCardEvent.thiefHasChosenCard", { roleName: chosenCardDefiniteRoleName }),
t("components.GameThiefMayHaveChosenCardEvent.gameMasterCanTakeAwayRemainingCards"),
];
});

const svgIconPath = computed<string | undefined>(() => {
if (chosenCard.value === undefined) {
return undefined;
}
return "/svg/role/thief.svg";
});

playSoundEffect("evil-laugh");
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
<template>
<div id="game-choose-card-playground">
{{ $t("ChooseCard") }}
<GlowCapture class="flex gap-2 h-full items-center justify-center w-full">
<GameChooseCardPlaygroundAdditionalCard
v-for="additionalCard in eligibleAdditionalCardsToChoose"
:key="additionalCard._id"
:additional-card="additionalCard"
class="flex game-additional-card justify-center w-1/5"
@click-additional-card="onClickFromAdditionalCardButton"
/>
</GlowCapture>
</div>
</template>
</template>

<script setup lang="ts">
import { storeToRefs } from "pinia";
import GameChooseCardPlaygroundAdditionalCard from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameChooseCardPlayground/GameChooseCardPlaygroundAdditionalCard/GameChooseCardPlaygroundAdditionalCard.vue";
import { useCurrentGamePlay } from "~/composables/api/game/game-play/useCurrentGamePlay";
import type { GameAdditionalCard } from "~/composables/api/game/types/game-additional-card/game-additional-card.class";
import { useMakeGamePlayDtoStore } from "~/stores/game/make-game-play-dto/useMakeGamePlayDtoStore";
import { useGameStore } from "~/stores/game/useGameStore";

const gameStore = useGameStore();
const { game } = storeToRefs(gameStore);

const makeGamePlayDtoStore = useMakeGamePlayDtoStore();
const { makeGamePlayDto } = storeToRefs(makeGamePlayDtoStore);

const { getEligibleAdditionalCardsToChooseInCurrentGamePlay } = useCurrentGamePlay(game);

const eligibleAdditionalCardsToChoose = computed<GameAdditionalCard[]>(() => getEligibleAdditionalCardsToChooseInCurrentGamePlay());

function onClickFromAdditionalCardButton(additionalCard: GameAdditionalCard) {

Check failure on line 33 in app/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameChooseCardPlayground/GameChooseCardPlayground.vue

View workflow job for this annotation

GitHub Actions / Lint 🔍

Missing return type on function
if (makeGamePlayDto.value.chosenCardId === additionalCard._id) {
makeGamePlayDto.value.chosenCardId = undefined;

return;
}
makeGamePlayDto.value.chosenCardId = additionalCard._id;
}
antoinezanardi marked this conversation as resolved.
Show resolved Hide resolved
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<GlowElement
id="game-choose-card-playground-additional-card"
>
<button
id="additional-card-button"
:aria-label="additionalCardButtonAriaLabel"
:class="additionalCardButtonClasses"
type="button"
@click.prevent="onClickFromAdditionalCardButton"
>
<RoleImage
id="additional-card-image"
:class="additionalCardsRoleImageClasses"
definition="normal"
:role-name="additionalCard.roleName"
sizes="125"
/>

<span
id="additional-card-role-label"
class="flex flex-col font-semibold grow justify-center"
>
{{ additionalCardRoleNameLabel }}
</span>
</button>
</GlowElement>
</template>

<script setup lang="ts">
import type { GameChooseCardPlaygroundAdditionalCardEmits, GameChooseCardPlaygroundAdditionalCardProps } from "~/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameChooseCardPlayground/GameChooseCardPlaygroundAdditionalCard/game-choose-card-playground-additional-card.types";
import RoleImage from "~/components/shared/role/RoleImage/RoleImage.vue";
import { useRoleName } from "~/composables/api/role/useRoleName";
import { useStrings } from "~/composables/misc/useStrings";
import { useMakeGamePlayDtoStore } from "~/stores/game/make-game-play-dto/useMakeGamePlayDtoStore";
import { useRolesStore } from "~/stores/role/useRolesStore";

const props = defineProps<GameChooseCardPlaygroundAdditionalCardProps>();

const emit = defineEmits<GameChooseCardPlaygroundAdditionalCardEmits>();

const { makeGamePlayDto } = useMakeGamePlayDtoStore();

const rolesStore = useRolesStore();
const { getRoleSideForRoleName } = rolesStore;

const { getRoleNameLabel, getDefiniteRoleNameLabel } = useRoleName();

const { t } = useI18n();

const { lowerCaseFirstLetter } = useStrings();

const additionalCardRoleNameLabel = computed<string>(() => getRoleNameLabel(props.additionalCard.roleName));

const additionalCardButtonAriaLabel = computed<string>(() => {
const lowerRecipientDefiniteRoleNameLabel = lowerCaseFirstLetter(getDefiniteRoleNameLabel(props.additionalCard.roleName, 1));

return t("components.GameChooseCardPlaygroundAdditionalCard.chooseCardForRecipient", {
roleName: additionalCardRoleNameLabel.value,
recipientDefiniteName: lowerRecipientDefiniteRoleNameLabel,
});
});

const additionalCardColor = computed<string>(() => {
const roleSide = getRoleSideForRoleName(props.additionalCard.roleName);

return roleSide === "villagers" ? "emerald" : "red";
});

const isAdditionalCardChosen = computed<boolean>(() => makeGamePlayDto.chosenCardId === props.additionalCard._id);

const additionalCardButtonClasses = computed<string>(() => {
const baseClasses = `border-4 border-transparent flex flex-col glow:border-${additionalCardColor.value}-500 h-52 items-center me-2 p-3 rounded-lg`;
if (isAdditionalCardChosen.value) {
return `${baseClasses} !border-${additionalCardColor.value}-500`;
}
return baseClasses;
});
antoinezanardi marked this conversation as resolved.
Show resolved Hide resolved

const additionalCardsRoleImageClasses = computed<string>(() => {
const baseClasses = `glow:border-${additionalCardColor.value}-500 mb-1`;
if (isAdditionalCardChosen.value) {
return `${baseClasses} !border-${additionalCardColor.value}-500`;
}
return baseClasses;
});

function onClickFromAdditionalCardButton() {

Check failure on line 88 in app/components/pages/game/GamePlaying/GamePlayground/GamePlaygroundContent/GameChooseCardPlayground/GameChooseCardPlaygroundAdditionalCard/GameChooseCardPlaygroundAdditionalCard.vue

View workflow job for this annotation

GitHub Actions / Lint 🔍

Missing return type on function
emit("clickAdditionalCard", props.additionalCard);
}
antoinezanardi marked this conversation as resolved.
Show resolved Hide resolved
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { GameAdditionalCard } from "~/composables/api/game/types/game-additional-card/game-additional-card.class";

type GameChooseCardPlaygroundAdditionalCardProps = {
additionalCard: GameAdditionalCard;
};

type GameChooseCardPlaygroundAdditionalCardEmits = {
clickAdditionalCard: [GameAdditionalCard];
};

export type {
GameChooseCardPlaygroundAdditionalCardProps,
GameChooseCardPlaygroundAdditionalCardEmits,
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
<button
id="choose-villagers-side-button"
:aria-label="$t('components.GameChooseSidePlayground.villagersSide')"
class="border-4 border-transparent glow:border-green-500 me-2 p-2 rounded-lg"
:class="{ '!border-green-500': makeGamePlayDto.chosenSide === 'villagers' }"
class="border-4 border-transparent glow:border-emerald-500 me-2 p-2 rounded-lg"
:class="{ '!border-emerald-500': makeGamePlayDto.chosenSide === 'villagers' }"
type="button"
@click.prevent="onClickFromChooseVillagersSideButton"
>
<RoleImage
id="villagers-side-image"
class="glow:border-green-500 mb-1"
:class="{ 'border-green-500': makeGamePlayDto.chosenSide === 'villagers' }"
class="glow:border-emerald-500 mb-1"
:class="{ 'border-emerald-500': makeGamePlayDto.chosenSide === 'villagers' }"
definition="normal"
role-name="villager"
sizes="200"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const playerSideGlowClass = computed<string>(() => {
if (!player.isAlive) {
return "";
}
return player.side.current === "werewolves" ? "glow:border-red-500" : "glow:border-green-500";
return player.side.current === "werewolves" ? "glow:border-red-500" : "glow:border-emerald-500";
});

const roleImageClasses = computed<string>(() => {
Expand Down
13 changes: 13 additions & 0 deletions app/composables/api/game/game-play/useCurrentGamePlay.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ComputedRef, MaybeRef } from "vue";
import type { GameAdditionalCard } from "~/composables/api/game/types/game-additional-card/game-additional-card.class";
import type { GameOptions } from "~/composables/api/game/types/game-options/game-options.class";
import type { GamePlay } from "~/composables/api/game/types/game-play/game-play.class";
import type { GamePlayCause } from "~/composables/api/game/types/game-play/game-play.types";
Expand All @@ -11,10 +12,12 @@ type UseCurrentGamePlay = {
mustCurrentGamePlayBeSkipped: ComputedRef<boolean>;
priorityCauseInCurrentGamePlay: ComputedRef<GamePlayCause | undefined>;
getEligibleTargetsWithInteractionInCurrentGamePlay: (interaction: PlayerInteractionType) => Player[];
getEligibleAdditionalCardsToChooseInCurrentGamePlay: () => GameAdditionalCard[];
};

function useCurrentGamePlay(game: MaybeRef<Game>): UseCurrentGamePlay {
const currentPlay = computed<GamePlay | null>(() => (isRef(game) ? game.value.currentPlay : game.currentPlay));
const additionalCards = computed<GameAdditionalCard[] | undefined>(() => (isRef(game) ? game.value.additionalCards : game.additionalCards));
const gameOptions = computed<GameOptions>(() => (isRef(game) ? game.value.options : game.options));

const mustCurrentGamePlayBeSkipped = computed<boolean>(() => {
Expand Down Expand Up @@ -47,10 +50,20 @@ function useCurrentGamePlay(game: MaybeRef<Game>): UseCurrentGamePlay {
}
return interaction.eligibleTargets;
}

function getEligibleAdditionalCardsToChooseInCurrentGamePlay(): GameAdditionalCard[] {
if (currentPlay.value?.action !== "choose-card" || additionalCards.value === undefined) {
return [];
}
const { source } = currentPlay.value;

return additionalCards.value.filter(({ recipient, isUsed }) => recipient === source.name && !isUsed);
}
return {
mustCurrentGamePlayBeSkipped,
priorityCauseInCurrentGamePlay,
getEligibleTargetsWithInteractionInCurrentGamePlay,
getEligibleAdditionalCardsToChooseInCurrentGamePlay,
};
}

Expand Down
5 changes: 5 additions & 0 deletions app/composables/api/game/types/game.class.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Expose, instanceToPlain, plainToInstance, Type } from "class-transformer";
import { GameEvent } from "~/composables/api/game/game-event/game-event.class";
import { GameAdditionalCard } from "~/composables/api/game/types/game-additional-card/game-additional-card.class";

import { GameOptions } from "~/composables/api/game/types/game-options/game-options.class";
import { GamePhase } from "~/composables/api/game/types/game-phase/game-phase.class";
Expand Down Expand Up @@ -40,6 +41,10 @@ class Game {
@Expose()
public upcomingPlays: GamePlay[];

@Type(() => GameAdditionalCard)
@Expose()
public additionalCards?: GameAdditionalCard[];

@Type(() => GameHistoryRecord)
@Expose()
public lastGameHistoryRecord: GameHistoryRecord | null;
Expand Down
7 changes: 6 additions & 1 deletion app/stores/role/useRolesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ref } from "vue";
import type { GameAdditionalCardRecipientRoleName } from "~/composables/api/game/types/game-additional-card/game-additional-card.types";

import type { Role } from "~/composables/api/role/types/role.class";
import type { RoleName } from "~/composables/api/role/types/role.types";
import type { RoleName, RoleSide } from "~/composables/api/role/types/role.types";
import { useFetchRoles } from "~/composables/api/role/useFetchRoles";
import { StoreIds } from "~/stores/enums/store.enum";

Expand All @@ -27,12 +27,17 @@ const useRolesStore = defineStore(StoreIds.ROLES, () => {
function getRolesForRecipientRoleName(recipientRoleName: GameAdditionalCardRecipientRoleName): Role[] {
return roles.value?.filter(role => role.additionalCardsEligibleRecipients?.includes(recipientRoleName)) ?? [];
}

function getRoleSideForRoleName(roleName: RoleName): RoleSide | undefined {
return getRoleWithNameInRoles(roleName)?.side;
}
return {
roles,
fetchingRoleStatus,
fetchAndSetRoles,
getRoleWithNameInRoles,
getRolesForRecipientRoleName,
getRoleSideForRoleName,
};
});

Expand Down
Loading
Loading