feat(ui): FRO-26 Tie selection
Added a nice tie selection ui
This commit is contained in:
@@ -4,7 +4,7 @@ import {useIngame} from "@/composables/useIngame.ts";
|
|||||||
import type {GameInfo, LobbyInfo, TieInfo, TrumpInfo} from "@/types/GameTypes.ts";
|
import type {GameInfo, LobbyInfo, TieInfo, TrumpInfo} from "@/types/GameTypes.ts";
|
||||||
import {useQuasar} from "quasar";
|
import {useQuasar} from "quasar";
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { computed } from 'vue'
|
import { computed, nextTick } from 'vue'
|
||||||
|
|
||||||
const wb = useWebSocket()
|
const wb = useWebSocket()
|
||||||
const wi = useIngame()
|
const wi = useIngame()
|
||||||
@@ -21,38 +21,36 @@ function getPlayerName(playerId: string) {
|
|||||||
return tieInf.value.tiedPlayers.find(p => p.id === playerId)?.name || 'Player';
|
return tieInf.value.tiedPlayers.find(p => p.id === playerId)?.name || 'Player';
|
||||||
}
|
}
|
||||||
const myRevealedCard = computed(() => {
|
const myRevealedCard = computed(() => {
|
||||||
// 1. Get your own ID from the 'self' object in the DTO
|
|
||||||
const myId = tieInf.value.self?.id;
|
const myId = tieInf.value.self?.id;
|
||||||
|
|
||||||
// 2. Safety check: ensure we have an ID and the map exists
|
|
||||||
if (!myId || !tieInf.value.selectedCards) return null;
|
if (!myId || !tieInf.value.selectedCards) return null;
|
||||||
|
|
||||||
// 3. Look up the CardDTO using your ID as the key
|
|
||||||
const card = tieInf.value.selectedCards[myId];
|
const card = tieInf.value.selectedCards[myId];
|
||||||
|
|
||||||
// 4. If found, convert the relative path to a full URL
|
|
||||||
return card ? getCardImagePath(card.path) : null;
|
return card ? getCardImagePath(card.path) : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFlipping = ref(false);
|
const isFlipping = ref(false);
|
||||||
|
|
||||||
// This replaces your v-if check.
|
|
||||||
// It keeps the "Pick" screen visible while flipping.
|
|
||||||
const showPickScreen = computed(() => {
|
const showPickScreen = computed(() => {
|
||||||
const isMyTurn = tieInf.value.self?.id === tieInf.value.currentPlayer?.id;
|
const isMyTurn = tieInf.value.self?.id === tieInf.value.currentPlayer?.id;
|
||||||
return isMyTurn || isFlipping.value;
|
return isFlipping.value || (isMyTurn && !showResultScreen.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showResultScreen = computed(() => {
|
||||||
|
const allPicked = Object.keys(tieInf.value.selectedCards).length === tieInf.value.tiedPlayers.length;
|
||||||
|
return allPicked && !isFlipping.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
function selectTie(tieIndex: number) {
|
function selectTie(tieIndex: number) {
|
||||||
// Use model.value because it's a ref
|
isFlipping.value = true;
|
||||||
|
nextTick(() => {
|
||||||
wb.sendAndWait("PickTie", { cardIndex: tieIndex })
|
wb.sendAndWait("PickTie", { cardIndex: tieIndex })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Server accepted pick, starting animation...");
|
console.log("Server accepted pick, starting animation...");
|
||||||
|
console.log("CARD SELECTED: " + Object.keys(tieInf.value.selectedCards).length)
|
||||||
|
|
||||||
// 1. Trigger the animation state
|
|
||||||
isFlipping.value = true;
|
|
||||||
|
|
||||||
// 2. Optional Notification
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
message: "Card revealed!",
|
message: "Card revealed!",
|
||||||
color: "positive",
|
color: "positive",
|
||||||
@@ -60,13 +58,13 @@ function selectTie(tieIndex: number) {
|
|||||||
timeout: 1000
|
timeout: 1000
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3. Wait for animation to finish before switching to Waiting Screen
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isFlipping.value = false;
|
isFlipping.value = false;
|
||||||
model.value = null;
|
model.value = null;
|
||||||
}, 2500);
|
}, 2500);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
isFlipping.value = false;
|
||||||
console.error("Pick failed:", error);
|
console.error("Pick failed:", error);
|
||||||
$q.notify({
|
$q.notify({
|
||||||
message: error.message || "Failed to pick card",
|
message: error.message || "Failed to pick card",
|
||||||
@@ -74,6 +72,8 @@ function selectTie(tieIndex: number) {
|
|||||||
position: "top"
|
position: "top"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
const model = ref<number | null>(null)
|
const model = ref<number | null>(null)
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
@@ -115,7 +115,6 @@ const options = computed(() => {
|
|||||||
>
|
>
|
||||||
<div class="card-inner">
|
<div class="card-inner">
|
||||||
<q-img
|
<q-img
|
||||||
:key="myRevealedCard || 'hidden'"
|
|
||||||
:src="(model === n && myRevealedCard) ? myRevealedCard : getCardImagePath(tieBlankCard)"
|
:src="(model === n && myRevealedCard) ? myRevealedCard : getCardImagePath(tieBlankCard)"
|
||||||
class="card-image shadow-24"
|
class="card-image shadow-24"
|
||||||
:class="{ 'animate-reveal': isFlipping && model === n }"
|
:class="{ 'animate-reveal': isFlipping && model === n }"
|
||||||
@@ -147,7 +146,7 @@ const options = computed(() => {
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
<q-card v-else class="game-container text-center overflow-hidden">
|
<q-card v-else-if="!showResultScreen" class="game-container text-center overflow-hidden">
|
||||||
<q-card-section class="content-layer q-py-xl">
|
<q-card-section class="content-layer q-py-xl">
|
||||||
<div class="text-overline text-primary letter-spacing-2">Tie-Break Round</div>
|
<div class="text-overline text-primary letter-spacing-2">Tie-Break Round</div>
|
||||||
|
|
||||||
@@ -174,7 +173,7 @@ const options = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="q-mt-xl">
|
<div class="q-mt-xl">
|
||||||
<q-spinner-hourglass color="primary" size="4em" />
|
<q-spinner-hourglass color="white" size="4em" />
|
||||||
<div class="text-subtitle1 text-grey-5 q-mt-sm italic">
|
<div class="text-subtitle1 text-grey-5 q-mt-sm italic">
|
||||||
Waiting for <span class="text-white">{{ tieInf.currentPlayer?.name }}</span> to pick a card...
|
Waiting for <span class="text-white">{{ tieInf.currentPlayer?.name }}</span> to pick a card...
|
||||||
</div>
|
</div>
|
||||||
@@ -182,6 +181,61 @@ const options = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
|
</q-card>
|
||||||
|
<q-card v-else class="game-container tie-break-card text-center overflow-hidden">
|
||||||
|
<div class="absolute-full bg-gradient-dark opacity-80"></div>
|
||||||
|
|
||||||
|
<q-card-section class="content-layer q-py-xl">
|
||||||
|
<div class="column items-center q-mb-xl">
|
||||||
|
<q-icon
|
||||||
|
:name="(tieInf.winners?.length || 0) > 1 ? 'auto_renew' : 'workspace_premium'"
|
||||||
|
:color="(tieInf.winners?.length || 0) > 1 ? 'info' : 'warning'"
|
||||||
|
size="64px"
|
||||||
|
class="q-mb-sm"
|
||||||
|
/>
|
||||||
|
<div class="text-h3 text-weight-bolder text-white text-uppercase tracking-widest">
|
||||||
|
{{ (tieInf.winners?.length || 0) > 1 ? 'Another Tie!' : 'Tie Broken' }}
|
||||||
|
</div>
|
||||||
|
<p class="text-subtitle1 text-grey-4">
|
||||||
|
{{ (tieInf.winners?.length || 0) > 1 ? 'The high cards matched. Draw again!' : 'We have a winner.' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-center items-end q-gutter-lg">
|
||||||
|
<div
|
||||||
|
v-for="(card, playerId) in tieInf.selectedCards"
|
||||||
|
:key="playerId"
|
||||||
|
class="revealed-card-wrapper"
|
||||||
|
:class="{
|
||||||
|
'winner-glow': tieInf.winners?.map(player => player.id).includes(playerId),
|
||||||
|
'opacity-40': !tieInf.winners?.map(player => player.id).includes(playerId) && (tieInf.winners?.length || 0) > 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="player-tag q-mb-md">
|
||||||
|
<div class="text-overline text-white">{{ getPlayerName(playerId) }}</div>
|
||||||
|
<q-badge
|
||||||
|
v-if="tieInf.winners?.map(player => player.id).includes(playerId)"
|
||||||
|
:color="tieInf.winners.length > 1 ? 'info' : 'positive'"
|
||||||
|
:label="tieInf.winners.length > 1 ? 'Still Tied' : 'Winner'"
|
||||||
|
rounded
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-stack">
|
||||||
|
<q-img
|
||||||
|
:src="getCardImagePath(card.path)"
|
||||||
|
class="card-image-reveal shadow-24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column items-center q-mt-lg">
|
||||||
|
<q-spinner-hourglass color="white" size="4em" />
|
||||||
|
<div class="text-subtitle1 text-white q-mt-sm italic">
|
||||||
|
Preparing trumpsuit selection...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
@@ -397,4 +451,35 @@ const options = computed(() => {
|
|||||||
50% { transform: scale(1.45); box-shadow: 0 0 40px rgba(25, 118, 210, 0.6); }
|
50% { transform: scale(1.45); box-shadow: 0 0 40px rgba(25, 118, 210, 0.6); }
|
||||||
100% { transform: scale(1.4); }
|
100% { transform: scale(1.4); }
|
||||||
}
|
}
|
||||||
|
/* Add to previous styles */
|
||||||
|
|
||||||
|
.winner-glow .card-image {
|
||||||
|
border-color: #31ccec; /* Default 'info' blue for ongoing ties */
|
||||||
|
transform: scale(1.05) translateY(-10px);
|
||||||
|
box-shadow: 0 10px 40px rgba(49, 204, 236, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there is only one winner, change glow to Gold */
|
||||||
|
.winner-glow:only-child .card-image,
|
||||||
|
.tie-break-card .winner-glow:has(.bg-positive) .card-image {
|
||||||
|
border-color: #f2c037;
|
||||||
|
box-shadow: 0 10px 40px rgba(242, 192, 55, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-40 {
|
||||||
|
opacity: 0.4;
|
||||||
|
filter: grayscale(0.8);
|
||||||
|
transform: scale(0.9);
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stack {
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image-reveal {
|
||||||
|
width: 130px;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type TieInfo = {
|
|||||||
tiedPlayers: Player[]
|
tiedPlayers: Player[]
|
||||||
highestAmount: number
|
highestAmount: number
|
||||||
selectedCards: Record<string, Card>
|
selectedCards: Record<string, Card>
|
||||||
|
winners: Player[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrumpInfo = {
|
type TrumpInfo = {
|
||||||
|
|||||||
Reference in New Issue
Block a user