From e655e3efe242c28ba22b19eb110020474033069f Mon Sep 17 00:00:00 2001 From: dnviti Date: Tue, 23 Dec 2025 01:23:43 +0100 Subject: [PATCH] feat: Implement smart auto-pass for non-active players and add a UI button to suspend it. --- src/client/src/modules/game/GameView.tsx | 27 +++++++++++++- src/client/src/modules/game/PhaseStrip.tsx | 43 +++++++++++++++------- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/client/src/modules/game/GameView.tsx b/src/client/src/modules/game/GameView.tsx index 9525368..f908774 100644 --- a/src/client/src/modules/game/GameView.tsx +++ b/src/client/src/modules/game/GameView.tsx @@ -78,6 +78,7 @@ export const GameView: React.FC = ({ gameState, currentPlayerId } const [hoveredCard, setHoveredCard] = useState(null); const [dragAnimationMode, setDragAnimationMode] = useState<'start' | 'end'>('end'); const [previewTappedIds, setPreviewTappedIds] = useState>(new Set()); + const [stopRequested, setStopRequested] = useState(false); // Auto-Pass Priority if Yielding useEffect(() => { @@ -162,7 +163,31 @@ export const GameView: React.FC = ({ gameState, currentPlayerId } // If I manually enable it, isBotTurn is false. We simply don't interfere. // This allows accurate Manual Yielding! } - }, [gameState.activePlayerId, gameState.step, isBotTurn]); // Removed isYielding dependency to avoid loops? + }, [gameState.activePlayerId, gameState.step, isBotTurn]); + + // --- Smart Auto-Pass (Suspend Logic) --- + useEffect(() => { + setStopRequested(false); + }, [gameState.step, gameState.turn]); + + useEffect(() => { + // Smart Auto-Pass Logic for NAP + if (!gameState.activePlayerId) return; + const amActivePlayer = gameState.activePlayerId === currentPlayerId; + const amPriorityPlayer = gameState.priorityPlayerId === currentPlayerId; + + // Condition: I am NAP, I have Priority, and I have NOT requested a stop. + // Logic: Auto-Pass. + if (!amActivePlayer && amPriorityPlayer && !stopRequested) { + if (gameState.step === 'declare_blockers') return; // Explicit wait for blockers + + console.log("[Smart Auto-Pass] Auto-passing priority as NAP (No Suspend requested)."); + const timer = setTimeout(() => { + socketService.socket.emit('game_strict_action', { action: { type: 'PASS_PRIORITY' } }); + }, 800); + return () => clearTimeout(timer); + } + }, [gameState.activePlayerId, gameState.priorityPlayerId, stopRequested, currentPlayerId, gameState.step]); // If I access `isYielding` inside `setIsYielding`, I don't need it in dependency. // But wait, the `if (['declare_...'].includes)` logic needs to potentially set it false. // setIsYielding(false) is fine. diff --git a/src/client/src/modules/game/PhaseStrip.tsx b/src/client/src/modules/game/PhaseStrip.tsx index bb0e96a..f52e589 100644 --- a/src/client/src/modules/game/PhaseStrip.tsx +++ b/src/client/src/modules/game/PhaseStrip.tsx @@ -10,6 +10,8 @@ interface PhaseStripProps { contextData?: any; isYielding?: boolean; onYieldToggle?: () => void; + stopRequested?: boolean; + onToggleSuspend?: () => void; } export const PhaseStrip: React.FC = ({ @@ -18,7 +20,9 @@ export const PhaseStrip: React.FC = ({ onAction, contextData, isYielding, - onYieldToggle + onYieldToggle, + stopRequested, + onToggleSuspend }) => { const currentPhase = gameState.phase as Phase; const currentStep = gameState.step as Step; @@ -63,12 +67,6 @@ export const PhaseStrip: React.FC = ({ } } } else if (currentStep === 'declare_blockers') { - // If it's MY turn (Active Player), I should NEVER verify blocks myself? - // Actually Rules say AP gets priority after blocks. - // So if I have priority, it MUST mean blocks are done (or I'm waiting for them, but then I wouldn't have priority?) - // Wait, if I am AP, and I have priority in this step, it means blocks are implicitly done (flag should be true). - // Fallback: If I am Active Player, always show "To Damage". - const showToDamage = gameState.blockersDeclared || isMyTurn; // UI Safety for AP if (showToDamage) { @@ -88,19 +86,36 @@ export const PhaseStrip: React.FC = ({ else if (gameState.phase === 'main2') actionLabel = "End Turn"; else actionLabel = "Pass"; } else { - // Resolve - // const topItem = gameState.stack![gameState.stack!.length - 1]; // Unused + // Resolve Logic actionLabel = "Resolve"; actionType = 'PASS_PRIORITY'; ActionIcon = Zap; actionColor = "bg-amber-600 hover:bg-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.4)]"; } } else { - // Waiting - actionLabel = "Waiting..."; - ActionIcon = Hand; - actionColor = "bg-white/5 text-slate-500 cursor-not-allowed"; - isActionEnabled = false; + // NOT PRIORITY (Waiting) + // Suspend Button Logic for NAP + if (!isMyTurn) { + isActionEnabled = true; + + if (stopRequested) { + actionLabel = "Stop Set"; + ActionIcon = Hand; + actionColor = "bg-red-600 hover:bg-red-500 animate-pulse font-bold border border-red-400"; + actionType = 'TOGGLE_SUSPEND'; + } else { + actionLabel = "Suspend"; + ActionIcon = Hand; + actionColor = "bg-yellow-600/80 hover:bg-yellow-500 text-yellow-50 border border-yellow-500/50"; + actionType = 'TOGGLE_SUSPEND'; + } + } else { + // I am AP but don't have priority? (Maybe waiting for server?) + actionLabel = "Waiting..."; + ActionIcon = Hourglass; + actionColor = "bg-white/5 text-slate-500 cursor-not-allowed"; + isActionEnabled = false; + } } const handleAction = (e: React.MouseEvent) => {