feat: gameboard ui
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gradient-to-br from-green-700 via-green-800 to-green-900 p-8">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<!-- Title -->
|
||||
<h1 class="text-white text-3xl font-bold mb-8 text-center">Card Dealing Test Page</h1>
|
||||
|
||||
<!-- Control Panel -->
|
||||
<div class="bg-gray-900/80 rounded-lg p-6 mb-8">
|
||||
<h2 class="text-white text-xl font-semibold mb-4">Controls</h2>
|
||||
<div class="flex flex-wrap gap-3 mb-4">
|
||||
<button
|
||||
@click="startDealing"
|
||||
:disabled="isDealing"
|
||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
Start Dealing
|
||||
</button>
|
||||
<button
|
||||
@click="dealSingleCard"
|
||||
:disabled="isDealing || cardsRemaining === 0"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
Deal One Card
|
||||
</button>
|
||||
<button
|
||||
@click="reset"
|
||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg font-medium transition-colors"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Current State Display -->
|
||||
<div class="grid grid-cols-3 gap-4 text-gray-300 text-sm">
|
||||
<div>
|
||||
<p><strong>Player Hand:</strong> {{ playerHand.length }} cards</p>
|
||||
<p><strong>Opponent Hand:</strong> {{ opponentHand.length }} cards</p>
|
||||
</div>
|
||||
<div>
|
||||
<p><strong>Cards Remaining:</strong> {{ cardsRemaining }}</p>
|
||||
<p><strong>Dealing:</strong> {{ isDealing ? 'Yes' : 'No' }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p><strong>Next Recipient:</strong> {{ nextRecipient }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Game Board Layout -->
|
||||
<div class="grid grid-rows-[auto_1fr_auto] gap-8">
|
||||
<!-- Opponent Hand (Top) -->
|
||||
<div class="flex justify-center">
|
||||
<div class="bg-gray-800/30 rounded-lg p-4">
|
||||
<h3 class="text-white text-sm font-semibold mb-2 text-center">Opponent Hand</h3>
|
||||
<PlayerHand
|
||||
ref="opponentHandRef"
|
||||
:cards="opponentHand"
|
||||
:face-down="true"
|
||||
:max-cards="3"
|
||||
:playable-cards="[]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Center Area (Deck) -->
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="bg-gray-800/30 rounded-lg p-6">
|
||||
<h3 class="text-white text-sm font-semibold mb-4 text-center">Deck</h3>
|
||||
<DeckArea
|
||||
ref="deckRef"
|
||||
:trump-card="trumpCard"
|
||||
:cards-remaining="cardsRemaining"
|
||||
:is-empty="cardsRemaining === 0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player Hand (Bottom) -->
|
||||
<div class="flex justify-center">
|
||||
<div class="bg-gray-800/30 rounded-lg p-4">
|
||||
<h3 class="text-white text-sm font-semibold mb-2 text-center">Your Hand</h3>
|
||||
<PlayerHand
|
||||
ref="playerHandRef"
|
||||
:cards="playerHand"
|
||||
:face-down="false"
|
||||
:max-cards="3"
|
||||
:playable-cards="[]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Instructions -->
|
||||
<div class="bg-gray-900/80 rounded-lg p-6 mt-8 text-gray-300">
|
||||
<h3 class="text-white font-semibold mb-2">Instructions:</h3>
|
||||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||||
<li><strong>Start Dealing:</strong> Automatically deals 6 cards (3 to each player) with 200ms delay</li>
|
||||
<li><strong>Deal One Card:</strong> Manually deal a single card to the next recipient</li>
|
||||
<li><strong>Reset:</strong> Clear all hands and reset the deck to 40 cards</li>
|
||||
<li>Watch the FlyingCard animation as cards move from deck to hands</li>
|
||||
<li>Notice the deck counter decreasing and the pulse effect</li>
|
||||
<li>Cards appear in hands with a fade-in animation</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Flying Cards Container -->
|
||||
<FlyingCard
|
||||
v-for="flyingCard in flyingCards"
|
||||
:key="flyingCard.id"
|
||||
:card="flyingCard.card"
|
||||
:start-position="flyingCard.startPosition"
|
||||
:end-position="flyingCard.endPosition"
|
||||
:duration="500"
|
||||
:face-down="flyingCard.faceDown"
|
||||
:delay="flyingCard.delay"
|
||||
@complete="onFlyingCardComplete(flyingCard)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import PlayerHand from '@/components/game/PlayerHand.vue'
|
||||
import DeckArea from '@/components/game/DeckArea.vue'
|
||||
import FlyingCard from '@/components/game/FlyingCard.vue'
|
||||
|
||||
// Refs for components
|
||||
const playerHandRef = ref(null)
|
||||
const opponentHandRef = ref(null)
|
||||
const deckRef = ref(null)
|
||||
|
||||
// Game state
|
||||
const playerHand = ref([])
|
||||
const opponentHand = ref([])
|
||||
const cardsRemaining = ref(40)
|
||||
const isDealing = ref(false)
|
||||
const flyingCards = ref([])
|
||||
let flyingCardIdCounter = 0
|
||||
|
||||
// Trump card (fixed for testing)
|
||||
const trumpCard = ref({ suit: 'o', rank: 7 })
|
||||
|
||||
// Sample deck
|
||||
const sampleCards = [
|
||||
{ suit: 'c', rank: 1 },
|
||||
{ suit: 'c', rank: 2 },
|
||||
{ suit: 'c', rank: 3 },
|
||||
{ suit: 'c', rank: 4 },
|
||||
{ suit: 'c', rank: 5 },
|
||||
{ suit: 'c', rank: 6 },
|
||||
{ suit: 'c', rank: 7 },
|
||||
{ suit: 'c', rank: 11 },
|
||||
{ suit: 'c', rank: 12 },
|
||||
{ suit: 'c', rank: 13 },
|
||||
{ suit: 'e', rank: 1 },
|
||||
{ suit: 'e', rank: 2 },
|
||||
{ suit: 'e', rank: 3 },
|
||||
{ suit: 'e', rank: 4 },
|
||||
{ suit: 'e', rank: 5 },
|
||||
{ suit: 'e', rank: 6 },
|
||||
{ suit: 'e', rank: 7 },
|
||||
{ suit: 'e', rank: 11 },
|
||||
{ suit: 'e', rank: 12 },
|
||||
{ suit: 'e', rank: 13 },
|
||||
{ suit: 'o', rank: 1 },
|
||||
{ suit: 'o', rank: 2 },
|
||||
{ suit: 'o', rank: 3 },
|
||||
{ suit: 'o', rank: 4 },
|
||||
{ suit: 'o', rank: 5 },
|
||||
{ suit: 'o', rank: 6 },
|
||||
{ suit: 'p', rank: 1 },
|
||||
{ suit: 'p', rank: 2 },
|
||||
{ suit: 'p', rank: 3 },
|
||||
{ suit: 'p', rank: 4 },
|
||||
{ suit: 'p', rank: 5 },
|
||||
{ suit: 'p', rank: 6 },
|
||||
{ suit: 'p', rank: 7 },
|
||||
{ suit: 'p', rank: 11 },
|
||||
{ suit: 'p', rank: 12 },
|
||||
{ suit: 'p', rank: 13 },
|
||||
]
|
||||
|
||||
// Computed
|
||||
const nextRecipient = computed(() => {
|
||||
const totalDealt = playerHand.value.length + opponentHand.value.length
|
||||
return totalDealt % 2 === 0 ? 'Player' : 'Opponent'
|
||||
})
|
||||
|
||||
// Get random card
|
||||
const getRandomCard = () => {
|
||||
return sampleCards[Math.floor(Math.random() * sampleCards.length)]
|
||||
}
|
||||
|
||||
// Get position of an element
|
||||
const getElementPosition = (el) => {
|
||||
if (!el) return { x: 0, y: 0 }
|
||||
const rect = el.$el?.getBoundingClientRect() || el.getBoundingClientRect()
|
||||
return {
|
||||
x: rect.left + rect.width / 2 - 64, // Center, minus half card width
|
||||
y: rect.top + rect.height / 2 - 89, // Center, minus half card height
|
||||
}
|
||||
}
|
||||
|
||||
// Deal a single card
|
||||
const dealSingleCard = async () => {
|
||||
if (cardsRemaining.value === 0) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
// Determine recipient
|
||||
const totalDealt = playerHand.value.length + opponentHand.value.length
|
||||
const isPlayer = totalDealt % 2 === 0
|
||||
|
||||
// Get positions
|
||||
const startPos = getElementPosition(deckRef.value)
|
||||
const endPos = getElementPosition(isPlayer ? playerHandRef.value : opponentHandRef.value)
|
||||
|
||||
// Create flying card
|
||||
const card = getRandomCard()
|
||||
const flyingCard = {
|
||||
id: flyingCardIdCounter++,
|
||||
card,
|
||||
startPosition: startPos,
|
||||
endPosition: endPos,
|
||||
faceDown: !isPlayer, // Face down for opponent
|
||||
delay: 0,
|
||||
recipient: isPlayer ? 'player' : 'opponent',
|
||||
}
|
||||
|
||||
flyingCards.value.push(flyingCard)
|
||||
cardsRemaining.value--
|
||||
}
|
||||
|
||||
// Flying card animation complete
|
||||
const onFlyingCardComplete = (flyingCard) => {
|
||||
// Add card to appropriate hand
|
||||
if (flyingCard.recipient === 'player') {
|
||||
playerHand.value.push(flyingCard.card)
|
||||
} else {
|
||||
opponentHand.value.push(flyingCard.card)
|
||||
}
|
||||
|
||||
// Remove flying card
|
||||
flyingCards.value = flyingCards.value.filter((fc) => fc.id !== flyingCard.id)
|
||||
}
|
||||
|
||||
// Start automatic dealing sequence
|
||||
const startDealing = async () => {
|
||||
if (isDealing.value) return
|
||||
|
||||
isDealing.value = true
|
||||
|
||||
// Deal 6 cards (3 to each player)
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (cardsRemaining.value === 0) break
|
||||
|
||||
await dealSingleCard()
|
||||
|
||||
// Wait 200ms before next card
|
||||
await new Promise((resolve) => setTimeout(resolve, 700))
|
||||
}
|
||||
|
||||
isDealing.value = false
|
||||
}
|
||||
|
||||
// Reset everything
|
||||
const reset = () => {
|
||||
playerHand.value = []
|
||||
opponentHand.value = []
|
||||
cardsRemaining.value = 40
|
||||
flyingCards.value = []
|
||||
isDealing.value = false
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user