feat: gameboard ui

This commit is contained in:
2025-12-20 17:03:18 +00:00
parent 8a6e0d0255
commit 2b161e2099
59 changed files with 2564 additions and 17 deletions
+444
View File
@@ -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>