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(() => ({ 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, currentVariant: currentVariant.value,
currentRound: currentRound.value, currentRound: currentRound.value,
p1Action: p1Action.value, p1Action: p1Action.value,
p2Action: p2Action.value, p2Action: p2Action.value,
forcedByP2: forcedByP2.value, forcedByP2: forcedByP2.value,
g2ForcePending: roomOffer('g2ForcePending'),
reported: reported.value, reported: reported.value,
shameAssigned: shameAssigned.value, shameAssigned: shameAssigned.value,
offer: { offer: {
@@ -270,6 +274,7 @@ onMounted(() => {
$(room.state).listen("p1Action", (value: string) => { p1Action.value = value; }); $(room.state).listen("p1Action", (value: string) => { p1Action.value = value; });
$(room.state).listen("p2Action", (value: string) => { p2Action.value = value; }); $(room.state).listen("p2Action", (value: string) => { p2Action.value = value; });
$(room.state).listen("forcedByP2", (value: boolean) => { forcedByP2.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("reported", (value: boolean) => { reported.value = value; });
$(room.state).listen("shameAssigned", (value: boolean) => { shameAssigned.value = value; }); $(room.state).listen("shameAssigned", (value: boolean) => { shameAssigned.value = value; });
// Offer fields // Offer fields

View File

@@ -1,7 +1,13 @@
<template> <template>
<div class="g"> <div class="g">
<h3>G2 Regla contraproductiva (P2 puede forzar)</h3> <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 <OfferActions
:state="state" :state="state"
:my-role="myRole" :my-role="myRole"

View File

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

View File

@@ -110,9 +110,10 @@ export class GameRoom extends Room<GameState> {
currentVariant: this.state.currentVariant currentVariant: this.state.currentVariant
}); });
// G2: Force offer by default // G2: start next round awaiting P2 decision, not forced by default
if (variant === 'G2') { if (variant === 'G2') {
this.state.forcedByP2 = true; this.state.g2ForcePending = true;
this.state.forcedByP2 = false;
} }
this.broadcast("variantChanged", { variant }); this.broadcast("variantChanged", { variant });
this.sysChat(`🔄 Variante cambiada a ${variant} - Juego reiniciado`, 'variant_change'); this.sysChat(`🔄 Variante cambiada a ${variant} - Juego reiniciado`, 'variant_change');
@@ -171,7 +172,13 @@ export class GameRoom extends Room<GameState> {
if (!player) return; if (!player) return;
if (player.role !== "P2") return; if (player.role !== "P2") return;
this.state.forcedByP2 = !!force; this.state.forcedByP2 = !!force;
this.state.g2ForcePending = false;
// When forced, P1 must propose an offer; nothing automatic here. // When forced, P1 must propose an offer; nothing automatic here.
// System chat feedback and dashboard update
if (this.state.currentVariant === 'G2') {
if (force) this.sysChat('📌 P2 decidió forzar a P1 a ofrecer', 'p2_force');
else this.sysChat('🕊️ P2 decidió no forzar a P1', 'p2_no_force');
}
}); });
// System chat helper moved to class method this.sysChat // System chat helper moved to class method this.sysChat
@@ -481,9 +488,10 @@ export class GameRoom extends Room<GameState> {
currentRound: this.state.currentRound, currentRound: this.state.currentRound,
currentVariant: this.state.currentVariant currentVariant: this.state.currentVariant
}); });
// G2: Force offer by default when starting game // G2: awaiting P2 decision at round start (not forced by default)
if (this.state.currentVariant === 'G2') { if (this.state.currentVariant === 'G2') {
this.state.forcedByP2 = true; this.state.g2ForcePending = true;
this.state.forcedByP2 = false;
} }
this.broadcast("gameStart"); this.broadcast("gameStart");
// System chat: start at round 1 // System chat: start at round 1
@@ -651,7 +659,8 @@ export class GameRoom extends Room<GameState> {
}); });
if (variant === 'G2') { if (variant === 'G2') {
this.state.forcedByP2 = true; this.state.g2ForcePending = true;
this.state.forcedByP2 = false;
} }
this.broadcast("variantChanged", { variant }); this.broadcast("variantChanged", { variant });

View File

@@ -22,6 +22,7 @@ export class GameState extends Schema {
@type("string") p1Action: string = ""; // no_offer|"" (variable offers handled via fields below) @type("string") p1Action: string = ""; // no_offer|"" (variable offers handled via fields below)
@type("string") p2Action: string = ""; // accept|reject|snatch @type("string") p2Action: string = ""; // accept|reject|snatch
@type("boolean") forcedByP2: boolean = false; // G2 @type("boolean") forcedByP2: boolean = false; // G2
@type("boolean") g2ForcePending: boolean = false; // G2: awaiting P2 decision at round start
@type("boolean") reported: boolean = false; // G4 @type("boolean") reported: boolean = false; // G4
@type("boolean") shameAssigned: boolean = false; // G3 @type("boolean") shameAssigned: boolean = false; // G3
@@ -120,7 +121,9 @@ export class GameState extends Schema {
resetRound(): void { resetRound(): void {
this.p1Action = ""; this.p1Action = "";
this.p2Action = ""; this.p2Action = "";
this.forcedByP2 = (this.currentVariant === "G2"); // In G2, start each round awaiting P2's force decision; do not force by default
this.g2ForcePending = (this.currentVariant === "G2");
this.forcedByP2 = false;
this.reported = false; this.reported = false;
this.shameAssigned = false; this.shameAssigned = false;
this.offerPavo = this.offerElote = 0; this.offerPavo = this.offerElote = 0;