fix: prevent multiple P2 actions on same offer and improve UI consistency

- Add server validation to prevent P2 from taking multiple actions on the same offer
- Hide P2 action buttons after an action is taken (using \!state.p2Action condition)
- Add offer details display to G3, G4, and G5 (was missing)
- Fix G2 to show offer details instead of just 'Oferta activa'
- Reset game status to PLAYING when changing variant from FINISHED state
- Ensure P2 can only make one decision per offer in all variants
This commit is contained in:
2025-08-08 00:10:10 -06:00
parent 305f19c7b0
commit 9673c6ce9e
6 changed files with 17 additions and 7 deletions

View File

@@ -2,7 +2,7 @@
<div class="g">
<h3>G1 Sin derechos de propiedad</h3>
<OfferControls v-if="myRole==='P1' && !state.offer?.active" @propose="onPropose" @no-offer="onNoOffer"/>
<div v-if="state.offer?.active" class="controls">
<div v-if="state.offer?.active && !state.p2Action" class="controls">
<div class="offer-view">Oferta: 🦃 {{ state.offer.offerPavo }} / 🌽 {{ state.offer.offerElote }} | Pedido: 🦃 {{ state.offer.requestPavo }} / 🌽 {{ state.offer.requestElote }}</div>
<div v-if="myRole === 'P2'">
<button class="btn" @click="$emit('p2Action', 'accept')">P2: Aceptar</button>

View File

@@ -5,8 +5,8 @@
<label><input type="checkbox" :checked="state.forcedByP2" @change="$emit('p2Force', ($event.target as HTMLInputElement).checked)"/> Forzar oferta</label>
</div>
<OfferControls v-if="myRole==='P1' && !state.offer?.active" :disable-no-offer="state.forcedByP2" @propose="onPropose" @no-offer="onNoOffer"/>
<div v-if="state.offer?.active" class="note">Oferta activa</div>
<div v-if="state.offer?.active" class="controls">
<div v-if="state.offer?.active && !state.p2Action" class="controls">
<div class="offer-view">Oferta: 🦃 {{ state.offer.offerPavo }} / 🌽 {{ state.offer.offerElote }} | Pedido: 🦃 {{ state.offer.requestPavo }} / 🌽 {{ state.offer.requestElote }}</div>
<div v-if="myRole === 'P2'">
<button class="btn" @click="$emit('p2Action', 'accept')">P2: Aceptar</button>
<button class="btn" @click="$emit('p2Action', 'reject')">P2: Rechazar</button>
@@ -35,6 +35,6 @@ function onNoOffer() {
.g { background:#fff; padding:12px; border-radius:8px; }
.controls { display:flex; gap:8px; margin:8px 0; }
.btn { padding:8px 12px; border:none; border-radius:6px; background:#e3f2fd; color:#1565c0; cursor:pointer; }
.note { color:#1565c0; font-weight:600; }
.offer-view { font-size: 14px; color:#333; }
.hint { color:#666; }
</style>

View File

@@ -2,7 +2,8 @@
<div class="g">
<h3>G3 Token de repudio (vergüenza)</h3>
<OfferControls v-if="myRole==='P1' && !state.offer?.active" @propose="onPropose" @no-offer="onNoOffer"/>
<div v-if="state.offer?.active" class="controls">
<div v-if="state.offer?.active && !state.p2Action" class="controls">
<div class="offer-view">Oferta: 🦃 {{ state.offer.offerPavo }} / 🌽 {{ state.offer.offerElote }} | Pedido: 🦃 {{ state.offer.requestPavo }} / 🌽 {{ state.offer.requestElote }}</div>
<div v-if="myRole === 'P2'">
<button class="btn" @click="$emit('p2Action', 'accept')">P2: Aceptar</button>
<button class="btn" @click="$emit('p2Action', 'reject')">P2: Rechazar</button>
@@ -34,5 +35,6 @@ function onNoOffer() {
.controls { display:flex; gap:8px; margin:8px 0; }
.btn { padding:8px 12px; border:none; border-radius:6px; background:#e3f2fd; color:#1565c0; cursor:pointer; }
.btn.warn { background:#ffecb3; color:#8d6e63; }
.offer-view { font-size: 14px; color:#333; }
.hint { color:#666; }
</style>

View File

@@ -2,7 +2,8 @@
<div class="g">
<h3>G4 Derechos mínimos de propiedad (juez)</h3>
<OfferControls v-if="myRole==='P1' && !state.offer?.active" @propose="onPropose" @no-offer="onNoOffer"/>
<div v-if="state.offer?.active" class="controls">
<div v-if="state.offer?.active && !state.p2Action" class="controls">
<div class="offer-view">Oferta: 🦃 {{ state.offer.offerPavo }} / 🌽 {{ state.offer.offerElote }} | Pedido: 🦃 {{ state.offer.requestPavo }} / 🌽 {{ state.offer.requestElote }}</div>
<div v-if="myRole === 'P2'">
<button class="btn" @click="$emit('p2Action', 'accept')">P2: Aceptar</button>
<button class="btn" @click="$emit('p2Action', 'reject')">P2: Rechazar</button>
@@ -34,5 +35,6 @@ function onNoOffer() {
.controls { display:flex; gap:8px; margin:8px 0; }
.btn { padding:8px 12px; border:none; border-radius:6px; background:#e3f2fd; color:#1565c0; cursor:pointer; }
.btn.warn { background:#ffe0e0; color:#b71c1c; }
.offer-view { font-size: 14px; color:#333; }
.hint { color:#666; }
</style>

View File

@@ -6,7 +6,8 @@
<button class="btn" @click="send">Enviar</button>
</div>
<OfferControls v-if="myRole==='P1' && !state.offer?.active" @propose="onPropose" @no-offer="onNoOffer"/>
<div v-if="state.offer?.active" class="controls">
<div v-if="state.offer?.active && !state.p2Action" class="controls">
<div class="offer-view">Oferta: 🦃 {{ state.offer.offerPavo }} / 🌽 {{ state.offer.offerElote }} | Pedido: 🦃 {{ state.offer.requestPavo }} / 🌽 {{ state.offer.requestElote }}</div>
<div v-if="myRole === 'P2'">
<button class="btn" @click="$emit('p2Action', 'accept')">P2: Aceptar</button>
<button class="btn" @click="$emit('p2Action', 'reject')">P2: Rechazar</button>
@@ -46,5 +47,6 @@ function onNoOffer() {
.chat { display:flex; gap:8px; margin:8px 0; }
.chat input { flex:1; padding:8px; border-radius:6px; border:1px solid #ddd; }
.btn { padding:8px 12px; border:none; border-radius:6px; background:#e3f2fd; color:#1565c0; cursor:pointer; }
.offer-view { font-size: 14px; color:#333; }
.hint { color:#666; }
</style>

View File

@@ -85,6 +85,10 @@ export class GameRoom extends Room<GameState> {
const player = this.state.players.get(client.sessionId);
if (!player) return;
if (player.role !== "P2") return;
// Prevent multiple actions on the same offer
if (this.state.p2Action) return;
this.state.p2Action = action; // accept | reject | snatch
this.resolveP2Action();