implementado funcionmiento turn-based g2 flujo de forzar o no forzar

This commit is contained in:
2025-08-27 17:54:51 -06:00
parent 48046a794e
commit 80af7461a4
5 changed files with 60 additions and 41 deletions

View File

@@ -218,11 +218,15 @@ const myRole = computed(() => {
});
const roundState = computed(() => ({
// tick to recompute when non-ref state fields change via forceUpdate()
_tick: refreshTick.value,
isFinished: (gameStatus.value || '').toLowerCase() === 'finished',
currentVariant: currentVariant.value,
currentRound: currentRound.value,
p1Action: p1Action.value,
p2Action: p2Action.value,
forcedByP2: forcedByP2.value,
g2ForcePending: roomOffer('g2ForcePending'),
reported: reported.value,
shameAssigned: shameAssigned.value,
offer: {
@@ -270,6 +274,7 @@ onMounted(() => {
$(room.state).listen("p1Action", (value: string) => { p1Action.value = value; });
$(room.state).listen("p2Action", (value: string) => { p2Action.value = value; });
$(room.state).listen("forcedByP2", (value: boolean) => { forcedByP2.value = value; });
$(room.state).listen("g2ForcePending", () => forceUpdate());
$(room.state).listen("reported", (value: boolean) => { reported.value = value; });
$(room.state).listen("shameAssigned", (value: boolean) => { shameAssigned.value = value; });
// Offer fields

View File

@@ -1,7 +1,13 @@
<template>
<div class="g">
<h3>G2 Regla contraproductiva (P2 puede forzar)</h3>
<OfferControls v-if="myRole==='P1' && !state.offer?.active" :my-role="myRole" :disable-no-offer="state.forcedByP2" @propose="onPropose" @no-offer="onNoOffer"/>
<OfferControls
v-if="myRole==='P1' && !state.offer?.active && !state.g2ForcePending"
:my-role="myRole"
:disable-no-offer="state.forcedByP2"
@propose="onPropose"
@no-offer="onNoOffer"
/>
<OfferActions
:state="state"
:my-role="myRole"

View File

@@ -4,16 +4,19 @@
class="offer-actions-card"
:style="({ '--p1': p1Color, '--p2': p2Color } as any)"
>
<!-- G2: Force option for P2 -->
<div v-if="myRole === 'P2' && currentVariant === 'G2' && !state.offer?.active" class="force-section">
<label class="force-checkbox">
<input
type="checkbox"
:checked="state.forcedByP2"
@change="$emit('p2Force', ($event.target as HTMLInputElement).checked)"
/>
<span class="checkbox-label">Forzar oferta</span>
</label>
<!-- G2: P2 must decide whether to force P1 before the offer UI appears -->
<div v-if="currentVariant === 'G2' && !state.offer?.active && state.g2ForcePending" class="force-section">
<div v-if="myRole === 'P2'" class="force-decision">
<div class="force-title">Forzar a P1 a realizar una oferta?</div>
<div class="action-buttons">
<button class="btn accept" :disabled="isFinished" @click="$emit('p2Force', true)"><span class="btn-icon"></span> Forzar</button>
<button class="btn no-action" :disabled="isFinished" @click="$emit('p2Force', false)">No forzar</button>
</div>
</div>
<div v-else-if="myRole === 'P1'" class="waiting-state">
<div class="spinner spinner--p2"></div>
<span class="waiting-text">Esperando decisión de P2...</span>
</div>
</div>
<!-- Offer display when active -->
@@ -124,7 +127,10 @@
</div>
<!-- Waiting for offer from P1 (P2 perspective) -->
<div v-else-if="myRole === 'P2' && !state.offer?.active && currentVariant !== 'G2'" class="waiting-state">
<div
v-else-if="myRole === 'P2' && !state.offer?.active && !(currentVariant === 'G2' && state.g2ForcePending)"
class="waiting-state"
>
<div class="spinner spinner--p1"></div>
<span class="waiting-text">Esperando oferta de P1...</span>
</div>
@@ -154,8 +160,8 @@ const showP1PostActions = computed(() => {
});
const shouldShowComponent = computed(() => {
// Show for P2 in G2 when forcing offer (even without active offer)
if (props.myRole === 'P2' && props.currentVariant === 'G2' && !props.state.offer?.active) {
// In G2 while decision pending, show component for both roles
if (props.currentVariant === 'G2' && !props.state.offer?.active && props.state.g2ForcePending) {
return true;
}
@@ -169,8 +175,10 @@ const shouldShowComponent = computed(() => {
return true;
}
// Show waiting state for P2 (except in G2 which is handled above)
if (props.myRole === 'P2' && !props.state.offer?.active && props.currentVariant !== 'G2') {
// Show waiting state for P2
// - In non-G2 when there's no active offer
// - In G2 after P2 has decided (g2ForcePending == false) and there's no active offer
if (props.myRole === 'P2' && !props.state.offer?.active && (props.currentVariant !== 'G2' || (props.currentVariant === 'G2' && !props.state.g2ForcePending))) {
return true;
}
@@ -178,6 +186,11 @@ const shouldShowComponent = computed(() => {
return false;
});
const isFinished = computed(() => {
const v = (props.state?.isFinished ?? false) as boolean;
return !!v;
});
defineEmits(['p2Action', 'p2Force', 'assignShame', 'report']);
</script>
@@ -197,25 +210,8 @@ defineEmits(['p2Action', 'p2Force', 'assignShame', 'report']);
border-bottom: 1px solid #e5e9f0;
}
.force-checkbox {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.force-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 10px;
cursor: pointer;
}
.checkbox-label {
font-weight: 600;
color: #334155;
font-size: 14px;
}
.force-decision { display:flex; flex-direction:column; gap:10px; }
.force-title { font-weight:700; color:#334155; font-size:14px; }
.offer-display {
display: flex;