feat: gameboard ui
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user