wip: add give up feature (client-side UI and state)

This commit is contained in:
Thomas Hallock 2025-11-25 10:10:15 -06:00
parent 996c973774
commit 33faccdf60
7 changed files with 104 additions and 1 deletions

View File

@ -23,6 +23,7 @@ interface KnowYourWorldContextValue {
clickRegion: (regionId: string, regionName: string) => void
nextRound: () => void
endGame: () => void
giveUp: () => void
endStudy: () => void
returnToSetup: () => void
@ -97,6 +98,8 @@ export function KnowYourWorldProvider({ children }: { children: React.ReactNode
startTime: 0,
activePlayers: [],
playerMetadata: {},
giveUpRegionId: null,
giveUpTimestamp: 0,
}
}, [roomData])
@ -181,6 +184,16 @@ export function KnowYourWorldProvider({ children }: { children: React.ReactNode
})
}, [viewerId, sendMove, state.currentPlayer, activePlayers])
// Action: Give Up (show current region and advance)
const giveUp = useCallback(() => {
sendMove({
type: 'GIVE_UP',
playerId: state.currentPlayer || activePlayers[0] || '',
userId: viewerId || '',
data: {},
})
}, [viewerId, sendMove, state.currentPlayer, activePlayers])
// Setup Action: Set Map
const setMap = useCallback(
(selectedMap: 'world' | 'usa') => {
@ -362,6 +375,7 @@ export function KnowYourWorldProvider({ children }: { children: React.ReactNode
clickRegion,
nextRound,
endGame,
giveUp,
endStudy,
returnToSetup,
setMap,

View File

@ -421,6 +421,8 @@ export class KnowYourWorldValidator
startTime: 0,
activePlayers: [],
playerMetadata: {},
giveUpRegionId: null,
giveUpTimestamp: 0,
}
}

View File

@ -23,7 +23,7 @@ export function GameInfoPanel({
}: GameInfoPanelProps) {
const { resolvedTheme } = useTheme()
const isDark = resolvedTheme === 'dark'
const { state, lastError, clearError } = useKnowYourWorld()
const { state, lastError, clearError, giveUp } = useKnowYourWorld()
// Auto-dismiss errors after 3 seconds
useEffect(() => {
@ -67,6 +67,9 @@ export function GameInfoPanel({
border: '2px solid',
borderColor: 'blue.500',
minWidth: 0, // Allow shrinking
display: 'flex',
flexDirection: 'column',
gap: '1',
})}
>
<div
@ -90,6 +93,28 @@ export function GameInfoPanel({
>
{currentRegionName || '...'}
</div>
<button
onClick={giveUp}
data-action="give-up"
className={css({
padding: '1',
fontSize: '2xs',
cursor: 'pointer',
bg: isDark ? 'yellow.800' : 'yellow.100',
color: isDark ? 'yellow.200' : 'yellow.800',
rounded: 'sm',
border: '1px solid',
borderColor: isDark ? 'yellow.600' : 'yellow.400',
fontWeight: 'bold',
transition: 'all 0.2s',
_hover: {
bg: isDark ? 'yellow.700' : 'yellow.200',
transform: 'scale(1.02)',
},
})}
>
Give Up
</button>
</div>
{/* Progress - compact */}

View File

@ -112,6 +112,8 @@ const Template = (args: StoryArgs) => {
onRegionClick={(id, name) => console.log('Clicked:', id, name)}
guessHistory={guessHistory}
playerMetadata={mockPlayerMetadata}
giveUpRegionId={null}
giveUpTimestamp={0}
forceTuning={{
showArrows: args.showArrows,
centeringStrength: args.centeringStrength,

View File

@ -68,6 +68,9 @@ interface MapRendererProps {
color: string
}
>
// Give up animation
giveUpRegionId: string | null
giveUpTimestamp: number
// Force simulation tuning parameters
forceTuning?: {
showArrows?: boolean
@ -130,6 +133,8 @@ export function MapRenderer({
onRegionClick,
guessHistory,
playerMetadata,
giveUpRegionId,
giveUpTimestamp,
forceTuning = {},
showDebugBoundingBoxes = SHOW_DEBUG_BOUNDING_BOXES,
}: MapRendererProps) {
@ -257,6 +262,10 @@ export function MapRenderer({
// Track whether current target region needs magnification
const [targetNeedsMagnification, setTargetNeedsMagnification] = useState(false)
// Track give up animation state
const [isGivingUpAnimation, setIsGivingUpAnimation] = useState(false)
const [giveUpAnimationProgress, setGiveUpAnimationProgress] = useState(0) // 0-1 for flash animation
// Debug: Track bounding boxes for visualization
const [debugBoundingBoxes, setDebugBoundingBoxes] = useState<DebugBoundingBox[]>([])
// Debug: Track full zoom search result for detailed panel
@ -461,6 +470,44 @@ export function MapRenderer({
})
}, [currentPrompt, svgDimensions]) // Re-check when prompt or SVG size changes
// Handle give up animation
useEffect(() => {
if (!giveUpRegionId) {
setIsGivingUpAnimation(false)
setGiveUpAnimationProgress(0)
return
}
console.log('[GiveUp] Starting animation for region:', giveUpRegionId)
setIsGivingUpAnimation(true)
setShowMagnifier(true)
setTargetOpacity(1)
// Animate flash over 3 seconds (3 pulses)
const duration = 3000 // 3 seconds total
const startTime = Date.now()
const animate = () => {
const elapsed = Date.now() - startTime
const progress = Math.min(elapsed / duration, 1)
// Create pulsing effect: 0 -> 1 -> 0 -> 1 -> 0 -> 1 -> 0 (3 full pulses)
const pulseProgress = Math.sin(progress * Math.PI * 3) ** 2
setGiveUpAnimationProgress(pulseProgress)
if (progress < 1) {
requestAnimationFrame(animate)
} else {
setIsGivingUpAnimation(false)
setGiveUpAnimationProgress(0)
console.log('[GiveUp] Animation complete')
}
}
requestAnimationFrame(animate)
}, [giveUpRegionId, giveUpTimestamp])
const [labelPositions, setLabelPositions] = useState<RegionLabelPosition[]>([])
const [smallRegionLabelPositions, setSmallRegionLabelPositions] = useState<
Array<{

View File

@ -112,6 +112,8 @@ export function PlayingPhase() {
onRegionClick={clickRegion}
guessHistory={state.guessHistory}
playerMetadata={state.playerMetadata}
giveUpRegionId={state.giveUpRegionId}
giveUpTimestamp={state.giveUpTimestamp}
/>
</div>
</Panel>

View File

@ -70,6 +70,10 @@ export interface KnowYourWorldState extends GameState {
// Multiplayer
activePlayers: string[]
playerMetadata: Record<string, any>
// Give up animation state
giveUpRegionId: string | null // Region ID to show/flash when user gives up
giveUpTimestamp: number // When the give up was triggered (for animation timing)
}
// Move types
@ -170,3 +174,10 @@ export type KnowYourWorldMove =
selectedContinent: ContinentId | 'all'
}
}
| {
type: 'GIVE_UP'
playerId: string
userId: string
timestamp: number
data: {}
}