445 lines
12 KiB
Vue
445 lines
12 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-gradient-to-br from-green-700 via-green-800 to-green-900">
|
|
<!-- Control Panel (Floating) -->
|
|
<div class="fixed top-4 left-4 bg-gray-900/95 rounded-lg p-4 max-w-xs z-50 shadow-2xl">
|
|
<h3 class="text-white font-bold mb-3 text-sm">Test Controls</h3>
|
|
<div class="space-y-2">
|
|
<button
|
|
@click="dealInitialCards"
|
|
:disabled="isDealing"
|
|
class="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 text-white rounded text-xs font-medium transition-colors"
|
|
>
|
|
Deal Cards
|
|
</button>
|
|
<button
|
|
@click="playRandomCards"
|
|
:disabled="playerHand.length === 0"
|
|
class="w-full px-3 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 text-white rounded text-xs font-medium transition-colors"
|
|
>
|
|
Play Both Cards
|
|
</button>
|
|
<button
|
|
@click="collectTrick"
|
|
:disabled="!currentTrick.playerCard || !currentTrick.opponentCard"
|
|
class="w-full px-3 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-600 text-white rounded text-xs font-medium transition-colors"
|
|
>
|
|
Collect Trick
|
|
</button>
|
|
<button
|
|
@click="revealTrump = !revealTrump"
|
|
class="w-full px-3 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded text-xs font-medium transition-colors"
|
|
>
|
|
{{ revealTrump ? 'Hide' : 'Reveal' }} Trump
|
|
</button>
|
|
<button
|
|
@click="showGameOver"
|
|
class="w-full px-3 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded text-xs font-medium transition-colors"
|
|
>
|
|
Show Game Over
|
|
</button>
|
|
<button
|
|
@click="resetGame"
|
|
class="w-full px-3 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded text-xs font-medium transition-colors"
|
|
>
|
|
Reset Game
|
|
</button>
|
|
<div class="pt-2 border-t border-gray-700 mt-2 text-gray-400 text-xs">
|
|
<p><strong>Turn:</strong> {{ currentTurn === 'player' ? 'You' : 'Bot' }}</p>
|
|
<p><strong>Deck:</strong> {{ cardsRemaining }} cards</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Game Board -->
|
|
<div
|
|
class="grid grid-rows-[auto_auto_1fr_auto] grid-cols-[1fr_3fr_1fr] gap-6 p-8 min-h-screen"
|
|
>
|
|
<!-- Score Display (Top Left) -->
|
|
<div class="col-start-1 col-end-4 row-start-1 flex justify-start items-start">
|
|
<ScoreDisplay
|
|
:player-score="playerScore"
|
|
:opponent-score="opponentScore"
|
|
:current-turn="currentTurn"
|
|
:round-number="1"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Opponent Hand -->
|
|
<div class="col-start-2 col-end-3 row-start-2 flex justify-center">
|
|
<PlayerHand
|
|
ref="opponentHandRef"
|
|
:cards="opponentHand"
|
|
:face-down="true"
|
|
:max-cards="3"
|
|
:playable-cards="[]"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Play Area -->
|
|
<div class="col-start-2 col-end-3 row-start-3 flex items-center justify-center">
|
|
<PlayArea
|
|
:player-card="currentTrick.playerCard"
|
|
:opponent-card="currentTrick.opponentCard"
|
|
:winner="currentTrick.winner"
|
|
:first-player="currentTrick.firstPlayer"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Deck Area -->
|
|
<div class="col-start-3 col-end-4 row-start-3 flex items-center justify-center">
|
|
<DeckArea
|
|
ref="deckRef"
|
|
:trump-card="trumpCard"
|
|
:cards-remaining="cardsRemaining"
|
|
:is-empty="cardsRemaining === 0"
|
|
:reveal-trump="revealTrump"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Player Hand -->
|
|
<div class="col-start-2 col-end-3 row-start-4 flex justify-center">
|
|
<PlayerHand
|
|
ref="playerHandRef"
|
|
:cards="playerHand"
|
|
:face-down="false"
|
|
:max-cards="3"
|
|
:playable-cards="playableCards"
|
|
@card-clicked="handleCardClick"
|
|
/>
|
|
</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)"
|
|
/>
|
|
|
|
<!-- Game Over Modal -->
|
|
<GameOver
|
|
:is-visible="gameOverVisible"
|
|
:winner="gameOverWinner"
|
|
:player-score="playerScore"
|
|
:opponent-score="opponentScore"
|
|
:stats="gameOverStats"
|
|
@close="gameOverVisible = false"
|
|
@play-again="resetGame"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, nextTick } from 'vue'
|
|
import GameBoard from '@/components/game/GameBoard.vue'
|
|
import ScoreDisplay from '@/components/game/ScoreDisplay.vue'
|
|
import PlayerHand from '@/components/game/PlayerHand.vue'
|
|
import PlayArea from '@/components/game/PlayArea.vue'
|
|
import DeckArea from '@/components/game/DeckArea.vue'
|
|
import FlyingCard from '@/components/game/FlyingCard.vue'
|
|
import GameOver from '@/components/game/GameOver.vue'
|
|
|
|
// Component refs
|
|
const playerHandRef = ref(null)
|
|
const opponentHandRef = ref(null)
|
|
const deckRef = ref(null)
|
|
|
|
// Game state
|
|
const playerHand = ref([])
|
|
const opponentHand = ref([])
|
|
const playerScore = ref(0)
|
|
const opponentScore = ref(0)
|
|
const currentTurn = ref('player')
|
|
const cardsRemaining = ref(40)
|
|
const isDealing = ref(false)
|
|
const revealTrump = ref(false)
|
|
|
|
// Trump card
|
|
const trumpCard = ref({ suit: 'o', rank: 7 })
|
|
|
|
// Current trick
|
|
const currentTrick = ref({
|
|
playerCard: null,
|
|
opponentCard: null,
|
|
winner: null,
|
|
firstPlayer: null,
|
|
})
|
|
|
|
// Flying cards for dealing animation
|
|
const flyingCards = ref([])
|
|
let flyingCardIdCounter = 0
|
|
|
|
// Game Over
|
|
const gameOverVisible = ref(false)
|
|
const gameOverWinner = ref('player')
|
|
const gameOverStats = ref({
|
|
playerTricks: 0,
|
|
opponentTricks: 0,
|
|
})
|
|
|
|
// 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 },
|
|
]
|
|
|
|
// Playable cards (all cards in hand for testing)
|
|
const playableCards = computed(() => {
|
|
return playerHand.value.map((_, index) => index)
|
|
})
|
|
|
|
// Get random card
|
|
const getRandomCard = () => {
|
|
return sampleCards[Math.floor(Math.random() * sampleCards.length)]
|
|
}
|
|
|
|
// Get element position
|
|
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,
|
|
y: rect.top + rect.height / 2 - 89,
|
|
}
|
|
}
|
|
|
|
// Deal a single card
|
|
const dealSingleCard = async (isPlayer) => {
|
|
if (cardsRemaining.value === 0) return
|
|
|
|
await nextTick()
|
|
|
|
const startPos = getElementPosition(deckRef.value)
|
|
const endPos = getElementPosition(isPlayer ? playerHandRef.value : opponentHandRef.value)
|
|
|
|
const card = getRandomCard()
|
|
const flyingCard = {
|
|
id: flyingCardIdCounter++,
|
|
card,
|
|
startPosition: startPos,
|
|
endPosition: endPos,
|
|
faceDown: !isPlayer,
|
|
delay: 0,
|
|
recipient: isPlayer ? 'player' : 'opponent',
|
|
}
|
|
|
|
flyingCards.value.push(flyingCard)
|
|
cardsRemaining.value--
|
|
}
|
|
|
|
// Flying card complete
|
|
const onFlyingCardComplete = (flyingCard) => {
|
|
if (flyingCard.recipient === 'player') {
|
|
playerHand.value.push(flyingCard.card)
|
|
} else {
|
|
opponentHand.value.push(flyingCard.card)
|
|
}
|
|
flyingCards.value = flyingCards.value.filter((fc) => fc.id !== flyingCard.id)
|
|
}
|
|
|
|
// Deal initial cards
|
|
const dealInitialCards = async () => {
|
|
if (isDealing.value) return
|
|
isDealing.value = true
|
|
|
|
// Reveal trump at start
|
|
revealTrump.value = true
|
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
revealTrump.value = false
|
|
|
|
// Deal 6 cards (3 to each player)
|
|
for (let i = 0; i < 6; i++) {
|
|
if (cardsRemaining.value === 0) break
|
|
const isPlayer = i % 2 === 0
|
|
await dealSingleCard(isPlayer)
|
|
await new Promise((resolve) => setTimeout(resolve, 700))
|
|
}
|
|
|
|
isDealing.value = false
|
|
}
|
|
|
|
// Handle card click from player hand
|
|
const handleCardClick = ({ card, index }) => {
|
|
if (currentTurn.value !== 'player') return
|
|
if (currentTrick.value.playerCard) return
|
|
|
|
// Set first player if no cards played yet
|
|
if (!currentTrick.value.opponentCard) {
|
|
currentTrick.value.firstPlayer = 'player'
|
|
}
|
|
|
|
// Play the card
|
|
currentTrick.value.playerCard = card
|
|
playerHand.value.splice(index, 1)
|
|
|
|
// Switch turn
|
|
currentTurn.value = 'opponent'
|
|
|
|
// Auto-play opponent card after delay
|
|
setTimeout(() => {
|
|
if (opponentHand.value.length > 0 && !currentTrick.value.opponentCard) {
|
|
playOpponentCard()
|
|
}
|
|
}, 1000)
|
|
}
|
|
|
|
// Play opponent card
|
|
const playOpponentCard = () => {
|
|
if (opponentHand.value.length === 0) return
|
|
|
|
// Set first player if no cards played yet
|
|
if (!currentTrick.value.playerCard) {
|
|
currentTrick.value.firstPlayer = 'opponent'
|
|
}
|
|
|
|
// Play random card
|
|
const randomIndex = Math.floor(Math.random() * opponentHand.value.length)
|
|
const card = opponentHand.value[randomIndex]
|
|
currentTrick.value.opponentCard = card
|
|
opponentHand.value.splice(randomIndex, 1)
|
|
|
|
// Switch turn
|
|
currentTurn.value = 'player'
|
|
}
|
|
|
|
// Play random cards from both players
|
|
const playRandomCards = () => {
|
|
if (playerHand.value.length === 0) return
|
|
|
|
// Player plays first
|
|
currentTrick.value.firstPlayer = 'player'
|
|
const playerCard = playerHand.value[0]
|
|
currentTrick.value.playerCard = playerCard
|
|
playerHand.value.shift()
|
|
|
|
// Opponent plays after delay
|
|
setTimeout(() => {
|
|
if (opponentHand.value.length > 0) {
|
|
const opponentCard = opponentHand.value[0]
|
|
currentTrick.value.opponentCard = opponentCard
|
|
opponentHand.value.shift()
|
|
}
|
|
}, 600)
|
|
}
|
|
|
|
// Collect trick
|
|
const collectTrick = async () => {
|
|
if (!currentTrick.value.playerCard || !currentTrick.value.opponentCard) return
|
|
|
|
// Randomly determine winner
|
|
const winner = Math.random() > 0.5 ? 'player' : 'opponent'
|
|
currentTrick.value.winner = winner
|
|
|
|
// Wait to show winner glow
|
|
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
|
|
// Update score (random points)
|
|
const points = Math.floor(Math.random() * 15) + 5
|
|
if (winner === 'player') {
|
|
playerScore.value += points
|
|
gameOverStats.value.playerTricks++
|
|
} else {
|
|
opponentScore.value += points
|
|
gameOverStats.value.opponentTricks++
|
|
}
|
|
|
|
// Clear cards (triggers collection animation)
|
|
currentTrick.value.playerCard = null
|
|
currentTrick.value.opponentCard = null
|
|
|
|
// Reset after animation
|
|
await new Promise((resolve) => setTimeout(resolve, 600))
|
|
currentTrick.value.winner = null
|
|
currentTrick.value.firstPlayer = null
|
|
|
|
// Deal new cards if deck has cards
|
|
if (cardsRemaining.value >= 2 && playerHand.value.length < 3) {
|
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
await dealSingleCard(winner === 'player')
|
|
await new Promise((resolve) => setTimeout(resolve, 700))
|
|
await dealSingleCard(winner === 'opponent')
|
|
}
|
|
|
|
// Switch turn to winner
|
|
currentTurn.value = winner
|
|
}
|
|
|
|
// Show game over
|
|
const showGameOver = () => {
|
|
// Make player win for demo
|
|
if (playerScore.value <= opponentScore.value) {
|
|
playerScore.value = opponentScore.value + 10
|
|
}
|
|
gameOverWinner.value =
|
|
playerScore.value > opponentScore.value
|
|
? 'player'
|
|
: opponentScore.value > playerScore.value
|
|
? 'opponent'
|
|
: 'draw'
|
|
gameOverVisible.value = true
|
|
}
|
|
|
|
// Reset game
|
|
const resetGame = () => {
|
|
playerHand.value = []
|
|
opponentHand.value = []
|
|
playerScore.value = 0
|
|
opponentScore.value = 0
|
|
currentTurn.value = 'player'
|
|
cardsRemaining.value = 40
|
|
currentTrick.value = {
|
|
playerCard: null,
|
|
opponentCard: null,
|
|
winner: null,
|
|
firstPlayer: null,
|
|
}
|
|
flyingCards.value = []
|
|
gameOverVisible.value = false
|
|
gameOverStats.value = {
|
|
playerTricks: 0,
|
|
opponentTricks: 0,
|
|
}
|
|
revealTrump.value = false
|
|
}
|
|
</script>
|