mejoras de UX
This commit is contained in:
@@ -12,6 +12,17 @@
|
|||||||
<span class="points">Puntos: {{ s.points }}</span>
|
<span class="points">Puntos: {{ s.points }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button @click="changeToPreviousVariant" class="btn btn-prev-variant">
|
||||||
|
⏪ {{ getPreviousVariant() }}
|
||||||
|
</button>
|
||||||
|
<button @click="restartCurrentVariant" class="btn btn-restart-variant">
|
||||||
|
🔄 {{ currentVariant }}
|
||||||
|
</button>
|
||||||
|
<button @click="changeToNextVariant" class="btn btn-next-variant">
|
||||||
|
{{ getNextVariant() }} ⏩
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="hint">Se cerrará en {{ remainingSeconds }}s</div>
|
<div class="hint">Se cerrará en {{ remainingSeconds }}s</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +127,7 @@ const variants = ['G1','G2','G3','G4','G5'];
|
|||||||
|
|
||||||
// End-of-game modal state and helpers
|
// End-of-game modal state and helpers
|
||||||
const endModal = ref<{ visible: boolean }>({ visible: false });
|
const endModal = ref<{ visible: boolean }>({ visible: false });
|
||||||
const remainingSeconds = ref(10);
|
const remainingSeconds = ref(20);
|
||||||
let endTimerTimeout: any = null;
|
let endTimerTimeout: any = null;
|
||||||
let endTimerInterval: any = null;
|
let endTimerInterval: any = null;
|
||||||
|
|
||||||
@@ -124,7 +135,7 @@ function showEndModal() {
|
|||||||
// Prevent multiple timers
|
// Prevent multiple timers
|
||||||
if (endModal.value.visible) return;
|
if (endModal.value.visible) return;
|
||||||
endModal.value.visible = true;
|
endModal.value.visible = true;
|
||||||
remainingSeconds.value = 10;
|
remainingSeconds.value = 20;
|
||||||
if (endTimerInterval) clearInterval(endTimerInterval);
|
if (endTimerInterval) clearInterval(endTimerInterval);
|
||||||
if (endTimerTimeout) clearTimeout(endTimerTimeout);
|
if (endTimerTimeout) clearTimeout(endTimerTimeout);
|
||||||
endTimerInterval = setInterval(() => {
|
endTimerInterval = setInterval(() => {
|
||||||
@@ -132,7 +143,7 @@ function showEndModal() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
endTimerTimeout = setTimeout(() => {
|
endTimerTimeout = setTimeout(() => {
|
||||||
dismissEndModal();
|
dismissEndModal();
|
||||||
}, 10000);
|
}, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dismissEndModal() {
|
function dismissEndModal() {
|
||||||
@@ -141,6 +152,40 @@ function dismissEndModal() {
|
|||||||
if (endTimerTimeout) { clearTimeout(endTimerTimeout); endTimerTimeout = null; }
|
if (endTimerTimeout) { clearTimeout(endTimerTimeout); endTimerTimeout = null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to get next variant in sequence
|
||||||
|
function getNextVariant(): string {
|
||||||
|
const currentIndex = variants.indexOf(currentVariant.value);
|
||||||
|
const nextIndex = (currentIndex + 1) % variants.length;
|
||||||
|
return variants[nextIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get previous variant in sequence
|
||||||
|
function getPreviousVariant(): string {
|
||||||
|
const currentIndex = variants.indexOf(currentVariant.value);
|
||||||
|
const previousIndex = (currentIndex - 1 + variants.length) % variants.length;
|
||||||
|
return variants[previousIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to change to next variant and dismiss modal
|
||||||
|
function changeToNextVariant() {
|
||||||
|
const nextVariant = getNextVariant();
|
||||||
|
setVariant(nextVariant);
|
||||||
|
dismissEndModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to change to previous variant and dismiss modal
|
||||||
|
function changeToPreviousVariant() {
|
||||||
|
const previousVariant = getPreviousVariant();
|
||||||
|
setVariant(previousVariant);
|
||||||
|
dismissEndModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to restart the same variant and dismiss modal
|
||||||
|
function restartCurrentVariant() {
|
||||||
|
setVariant(currentVariant.value);
|
||||||
|
dismissEndModal();
|
||||||
|
}
|
||||||
|
|
||||||
const finalScores = computed(() => {
|
const finalScores = computed(() => {
|
||||||
return players.value.map(p => {
|
return players.value.map(p => {
|
||||||
const points = (p.role === 'P2')
|
const points = (p.role === 'P2')
|
||||||
@@ -271,6 +316,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
room.onMessage("variantChanged", (data: { variant: string }) => {
|
room.onMessage("variantChanged", (data: { variant: string }) => {
|
||||||
currentVariant.value = data.variant as any;
|
currentVariant.value = data.variant as any;
|
||||||
|
// Close end modal if it's open when variant changes
|
||||||
|
if (endModal.value.visible) {
|
||||||
|
dismissEndModal();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// No round transition banners
|
// No round transition banners
|
||||||
@@ -378,6 +427,46 @@ async function leaveGame() {
|
|||||||
.end-modal .score-row .name { font-weight:800; color:#1f2937; }
|
.end-modal .score-row .name { font-weight:800; color:#1f2937; }
|
||||||
.end-modal .score-row .tokens { font-weight:700; color:#374151; }
|
.end-modal .score-row .tokens { font-weight:700; color:#374151; }
|
||||||
.end-modal .score-row .points { font-weight:900; color:#111827; }
|
.end-modal .score-row .points { font-weight:900; color:#111827; }
|
||||||
|
.end-modal .modal-actions {
|
||||||
|
margin: 16px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.end-modal .btn-next-variant, .end-modal .btn-prev-variant, .end-modal .btn-restart-variant {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||||
|
min-width: 85px;
|
||||||
|
}
|
||||||
|
.end-modal .btn-prev-variant {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
box-shadow: 0 4px 12px rgba(240, 147, 251, 0.3);
|
||||||
|
}
|
||||||
|
.end-modal .btn-restart-variant {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.3);
|
||||||
|
}
|
||||||
|
.end-modal .btn-next-variant:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
.end-modal .btn-prev-variant:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(240, 147, 251, 0.4);
|
||||||
|
}
|
||||||
|
.end-modal .btn-restart-variant:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(79, 172, 254, 0.4);
|
||||||
|
}
|
||||||
.end-modal .hint { font-size: 12px; color:#6b7280; text-align:right; }
|
.end-modal .hint { font-size: 12px; color:#6b7280; text-align:right; }
|
||||||
.game-container { background: white; border-radius: 20px; padding: 24px; max-width: 1000px; width: 100%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
|
.game-container { background: white; border-radius: 20px; padding: 24px; max-width: 1000px; width: 100%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
|
||||||
.game-header { display:flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
|
.game-header { display:flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||||
|
|||||||
@@ -115,15 +115,20 @@ const requestElote = ref(0);
|
|||||||
const advancedMode = ref(false); // Start in basic mode
|
const advancedMode = ref(false); // Start in basic mode
|
||||||
|
|
||||||
const room = computed(() => colyseusService.gameRoom.value as any);
|
const room = computed(() => colyseusService.gameRoom.value as any);
|
||||||
const p1 = computed(() => room.value?.state ? room.value.state.players.get(room.value.state.p1Id) : null);
|
|
||||||
const p2 = computed(() => room.value?.state ? room.value.state.players.get(room.value.state.p2Id) : null);
|
|
||||||
const isFinished = ref(false);
|
const isFinished = ref(false);
|
||||||
const maxOfferPavo = computed(() => (p1.value?.pavoTokens ?? Infinity));
|
|
||||||
const maxOfferElote = computed(() => (p1.value?.eloteTokens ?? Infinity));
|
// Reactive refs for player tokens
|
||||||
const maxRequestPavo = computed(() => (p2.value?.pavoTokens ?? Infinity));
|
const p1PavoTokens = ref(0);
|
||||||
const maxRequestElote = computed(() => (p2.value?.eloteTokens ?? Infinity));
|
const p1EloteTokens = ref(0);
|
||||||
|
const p2PavoTokens = ref(0);
|
||||||
|
const p2EloteTokens = ref(0);
|
||||||
|
|
||||||
|
const maxOfferPavo = computed(() => p1PavoTokens.value);
|
||||||
|
const maxOfferElote = computed(() => p1EloteTokens.value);
|
||||||
|
const maxRequestPavo = computed(() => p2PavoTokens.value);
|
||||||
|
const maxRequestElote = computed(() => p2EloteTokens.value);
|
||||||
const isNonsense = computed(() => (offerPavo.value|0) === (requestPavo.value|0) && (offerElote.value|0) === (requestElote.value|0));
|
const isNonsense = computed(() => (offerPavo.value|0) === (requestPavo.value|0) && (offerElote.value|0) === (requestElote.value|0));
|
||||||
const canMakeBasicOffer = computed(() => (p1.value?.pavoTokens ?? 0) >= 3);
|
const canMakeBasicOffer = computed(() => p1PavoTokens.value >= 3);
|
||||||
|
|
||||||
function clampAll() {
|
function clampAll() {
|
||||||
offerPavo.value = Math.max(0, Math.min(offerPavo.value | 0, maxOfferPavo.value));
|
offerPavo.value = Math.max(0, Math.min(offerPavo.value | 0, maxOfferPavo.value));
|
||||||
@@ -138,9 +143,64 @@ onMounted(() => {
|
|||||||
if (r?.state) {
|
if (r?.state) {
|
||||||
isFinished.value = ((r.state.gameStatus || '').toLowerCase() === 'finished');
|
isFinished.value = ((r.state.gameStatus || '').toLowerCase() === 'finished');
|
||||||
const $ = getStateCallbacks(r);
|
const $ = getStateCallbacks(r);
|
||||||
|
|
||||||
|
// Initialize token values
|
||||||
|
const p1 = r.state.players.get(r.state.p1Id);
|
||||||
|
const p2 = r.state.players.get(r.state.p2Id);
|
||||||
|
if (p1) {
|
||||||
|
p1PavoTokens.value = p1.pavoTokens || 0;
|
||||||
|
p1EloteTokens.value = p1.eloteTokens || 0;
|
||||||
|
}
|
||||||
|
if (p2) {
|
||||||
|
p2PavoTokens.value = p2.pavoTokens || 0;
|
||||||
|
p2EloteTokens.value = p2.eloteTokens || 0;
|
||||||
|
}
|
||||||
|
|
||||||
$(r.state).listen('gameStatus', (v: string) => {
|
$(r.state).listen('gameStatus', (v: string) => {
|
||||||
isFinished.value = (v || '').toLowerCase() === 'finished';
|
isFinished.value = (v || '').toLowerCase() === 'finished';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Reset inputs when round goes back to 1 (game restart)
|
||||||
|
$(r.state).listen('currentRound', (round: number) => {
|
||||||
|
if (round === 1) {
|
||||||
|
offerPavo.value = 0;
|
||||||
|
offerElote.value = 0;
|
||||||
|
requestPavo.value = 0;
|
||||||
|
requestElote.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update token refs when player tokens change
|
||||||
|
$(r.state).players.onAdd((player: any, sessionId: string) => {
|
||||||
|
const isP1 = sessionId === r.state.p1Id;
|
||||||
|
const isP2 = sessionId === r.state.p2Id;
|
||||||
|
|
||||||
|
// Set initial values
|
||||||
|
if (isP1) {
|
||||||
|
p1PavoTokens.value = player.pavoTokens || 0;
|
||||||
|
p1EloteTokens.value = player.eloteTokens || 0;
|
||||||
|
} else if (isP2) {
|
||||||
|
p2PavoTokens.value = player.pavoTokens || 0;
|
||||||
|
p2EloteTokens.value = player.eloteTokens || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(player).listen('pavoTokens', (tokens: number) => {
|
||||||
|
if (isP1) {
|
||||||
|
p1PavoTokens.value = tokens || 0;
|
||||||
|
} else if (isP2) {
|
||||||
|
p2PavoTokens.value = tokens || 0;
|
||||||
|
}
|
||||||
|
clampAll();
|
||||||
|
});
|
||||||
|
$(player).listen('eloteTokens', (tokens: number) => {
|
||||||
|
if (isP1) {
|
||||||
|
p1EloteTokens.value = tokens || 0;
|
||||||
|
} else if (isP2) {
|
||||||
|
p2EloteTokens.value = tokens || 0;
|
||||||
|
}
|
||||||
|
clampAll();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -77,18 +77,45 @@ export class GameRoom extends Room<GameState> {
|
|||||||
// Reset to round 1 and clear decisions when variant changes
|
// Reset to round 1 and clear decisions when variant changes
|
||||||
this.state.currentRound = 1;
|
this.state.currentRound = 1;
|
||||||
this.state.resetRound();
|
this.state.resetRound();
|
||||||
// Update metadata with new variant and round (don't special-case FINISHED)
|
|
||||||
|
// Reset player tokens while preserving shame tokens
|
||||||
|
this.state.players.forEach((player, sessionId) => {
|
||||||
|
const currentShameTokens = player.shameTokens || 0;
|
||||||
|
|
||||||
|
if (player.role === 'P1') {
|
||||||
|
player.pavoTokens = 10;
|
||||||
|
player.eloteTokens = 0;
|
||||||
|
} else if (player.role === 'P2') {
|
||||||
|
player.pavoTokens = 0;
|
||||||
|
player.eloteTokens = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve shame tokens
|
||||||
|
player.shameTokens = currentShameTokens;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If game was finished, restart it
|
||||||
|
if (this.state.gameStatus === GameStatus.FINISHED) {
|
||||||
|
this.state.gameStatus = GameStatus.PLAYING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update metadata with new status
|
||||||
|
const statusString = this.state.gameStatus === GameStatus.WAITING ? 'waiting' :
|
||||||
|
(this.state.gameStatus === GameStatus.PAUSED ? 'paused' :
|
||||||
|
(this.state.gameStatus === GameStatus.FINISHED ? 'finished' : 'playing'));
|
||||||
|
|
||||||
this.setMetadata({
|
this.setMetadata({
|
||||||
gameStatus: this.state.gameStatus === GameStatus.WAITING ? 'waiting' : (this.state.gameStatus === GameStatus.PAUSED ? 'paused' : (this.state.gameStatus === GameStatus.FINISHED ? 'finished' : 'playing')),
|
gameStatus: statusString,
|
||||||
currentRound: this.state.currentRound,
|
currentRound: this.state.currentRound,
|
||||||
currentVariant: this.state.currentVariant
|
currentVariant: this.state.currentVariant
|
||||||
});
|
});
|
||||||
|
|
||||||
// G2: Force offer by default
|
// G2: Force offer by default
|
||||||
if (variant === 'G2') {
|
if (variant === 'G2') {
|
||||||
this.state.forcedByP2 = true;
|
this.state.forcedByP2 = true;
|
||||||
}
|
}
|
||||||
this.broadcast("variantChanged", { variant });
|
this.broadcast("variantChanged", { variant });
|
||||||
this.sysChat(`🔄 Variante cambiada a ${variant}`, 'variant_change');
|
this.sysChat(`🔄 Variante cambiada a ${variant} - Juego reiniciado`, 'variant_change');
|
||||||
});
|
});
|
||||||
|
|
||||||
// P1 proposes a variable offer (offer -> P2, request <- from P2)
|
// P1 proposes a variable offer (offer -> P2, request <- from P2)
|
||||||
|
|||||||
Reference in New Issue
Block a user