Files
snatchgame/nuxt-snatchgame/app/components/GameScreen.vue
josedario87 62226ab5d4
Some checks failed
build-and-deploy / filter (push) Successful in 3s
build-and-deploy / build (push) Failing after 6s
build-and-deploy / deploy (push) Has been skipped
feat: Migración completa de SnatchGame a Nuxt 4
- Creado nuevo proyecto Nuxt 4 con estructura app/
- Servidor Colyseus separado para evitar problemas con decoradores
- Migrado GameRoom y toda la lógica del juego
- Implementado cliente con composables useGameClient
- Panel de administración funcional
- Componentes Vue migrados (HomeScreen, GameScreen, PlayerCard, etc)
- Configuración para ejecutar ambos servidores (npm run dev:all)
2025-08-05 16:05:51 -06:00

328 lines
7.6 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="game-container">
<div v-if="!gameState" class="loading">
<h2>Esperando estado del juego...</h2>
</div>
<div v-else class="game-content">
<!-- Game Header -->
<div class="game-header">
<div class="game-info">
<h2>Snatch Game</h2>
<div class="game-status">
<span>Ronda: {{ gameState.round }}</span>
<span>Fase: {{ gamePhaseDisplay }}</span>
<span>Jugadores: {{ playerCount }}/{{ gameState.maxPlayers }}</span>
</div>
</div>
</div>
<!-- Waiting Screen -->
<div v-if="gameState.gamePhase === 'waiting'" class="waiting-room">
<h3>Esperando jugadores...</h3>
<p>Se necesitan {{ gameState.minPlayers }} jugadores para comenzar</p>
<div class="players-waiting">
<div v-for="[id, player] in gameState.players" :key="id" class="player-waiting">
{{ player.name }}
</div>
</div>
</div>
<!-- Trading Phase -->
<div v-else-if="gameState.gamePhase === 'trading'" class="trading-phase">
<div class="my-info">
<h3>Tu información</h3>
<div class="role">Productor de: {{ currentPlayer?.producerRole }}</div>
<div class="tokens">
<div>🦃 Pavos: {{ currentPlayer?.tokens.turkey || 0 }}</div>
<div> Café: {{ currentPlayer?.tokens.coffee || 0 }}</div>
<div>🌽 Maíz: {{ currentPlayer?.tokens.corn || 0 }}</div>
</div>
<div class="points">Puntos: {{ currentPlayer?.points || 0 }}</div>
</div>
<!-- Other Players -->
<div class="other-players">
<h3>Otros jugadores</h3>
<div class="players-grid">
<div v-for="[id, player] in otherPlayers" :key="id" class="player-card">
<PlayerCard :player="player" :is-current="false" @make-offer="onMakeOffer(id)" />
</div>
</div>
</div>
<!-- Active Trade Offers -->
<div v-if="activeOffers.length > 0" class="trade-offers">
<h3>Ofertas de intercambio</h3>
<div v-for="offer in activeOffers" :key="offer.id" class="offer-card">
<TradeOfferCard
:offer="offer"
:current-player-id="currentPlayerId"
@respond="onRespondToOffer"
@cancel="onCancelOffer"
/>
</div>
</div>
</div>
<!-- Paused Phase -->
<div v-else-if="gameState.gamePhase === 'paused'" class="paused-phase">
<h2> Juego pausado</h2>
<p>El administrador ha pausado el juego</p>
</div>
<!-- Results Phase -->
<div v-else-if="gameState.gamePhase === 'results'" class="results-phase">
<h2>Resultados de la ronda {{ gameState.round }}</h2>
<div class="results-grid">
<div v-for="[id, player] in gameState.players" :key="id" class="result-card">
<h4>{{ player.name }}</h4>
<p>Puntos: {{ player.points }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch } from 'vue'
const props = defineProps<{
gameClient: any
}>()
const gameState = computed(() => props.gameClient?.gameState.value)
const currentPlayerId = computed(() => props.gameClient?.currentPlayerId.value)
const currentPlayer = computed(() => {
if (!gameState.value || !currentPlayerId.value) return null
return gameState.value.players.get(currentPlayerId.value)
})
const otherPlayers = computed(() => {
if (!gameState.value) return new Map()
const others = new Map()
gameState.value.players.forEach((player, id) => {
if (id !== currentPlayerId.value) {
others.set(id, player)
}
})
return others
})
const playerCount = computed(() => gameState.value?.players.size || 0)
const gamePhaseDisplay = computed(() => {
const phase = gameState.value?.gamePhase
switch (phase) {
case 'waiting': return 'Esperando jugadores'
case 'trading': return 'Intercambio'
case 'paused': return 'Pausado'
case 'results': return 'Resultados'
default: return phase
}
})
const activeOffers = computed(() => {
if (!gameState.value) return []
return gameState.value.activeTradeOffers.filter(offer =>
offer.status === 'pending' &&
(offer.offererId === currentPlayerId.value || offer.targetId === currentPlayerId.value)
)
})
const onMakeOffer = (targetId: string) => {
// This would open a modal or form to create an offer
console.log('Make offer to:', targetId)
// For now, create a simple offer
props.gameClient.makeOffer(targetId,
{ turkey: 1, coffee: 0, corn: 0 },
{ turkey: 0, coffee: 1, corn: 0 }
)
}
const onRespondToOffer = (offerId: string, response: string) => {
props.gameClient.respondToOffer(offerId, response)
}
const onCancelOffer = (offerId: string) => {
props.gameClient.cancelOffer(offerId)
}
</script>
<style scoped>
.game-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.game-content {
max-width: 1200px;
margin: 0 auto;
}
.game-header {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
backdrop-filter: blur(10px);
}
.game-info h2 {
margin: 0 0 1rem 0;
font-size: 2rem;
}
.game-status {
display: flex;
gap: 2rem;
font-size: 1.1rem;
}
.waiting-room {
text-align: center;
padding: 3rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
backdrop-filter: blur(10px);
}
.players-waiting {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 2rem;
}
.player-waiting {
padding: 0.75rem 1.5rem;
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
}
.trading-phase {
display: grid;
gap: 2rem;
}
.my-info {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1.5rem;
backdrop-filter: blur(10px);
}
.my-info h3 {
margin: 0 0 1rem 0;
}
.role {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 1rem;
text-transform: capitalize;
}
.tokens {
display: flex;
gap: 2rem;
margin-bottom: 1rem;
font-size: 1.1rem;
}
.points {
font-size: 1.3rem;
font-weight: 700;
color: #ffd700;
}
.other-players {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1.5rem;
backdrop-filter: blur(10px);
}
.other-players h3 {
margin: 0 0 1rem 0;
}
.players-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
.player-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 1rem;
}
.trade-offers {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1.5rem;
backdrop-filter: blur(10px);
}
.trade-offers h3 {
margin: 0 0 1rem 0;
}
.offer-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.paused-phase {
text-align: center;
padding: 3rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
backdrop-filter: blur(10px);
}
.results-phase {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 2rem;
backdrop-filter: blur(10px);
}
.results-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 2rem;
}
.result-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 1rem;
text-align: center;
}
.result-card h4 {
margin: 0 0 0.5rem 0;
}
.result-card p {
font-size: 1.2rem;
font-weight: 700;
color: #ffd700;
}
</style>