Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 5x 4x 4x 4x 16x 16x 16x 1x 1x 1x 1x 1x 16x 16x 16x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 12x 12x 4x 4x 4x 4x 4x 4x 4x 2x 2x 4x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x | 'use client'
import { useCallback, useEffect, useRef, useState } from 'react'
export interface UseGameBreakTimerOptions {
maxDurationMinutes: number
onTimeout: () => void
enabled?: boolean
}
export interface UseGameBreakTimerResult {
startTime: number | null
elapsedMs: number
remainingMs: number
remainingMinutes: number
remainingSeconds: number
percentRemaining: number
isActive: boolean
start: () => void
stop: () => void
reset: () => void
}
export function useGameBreakTimer({
maxDurationMinutes,
onTimeout,
enabled = true,
}: UseGameBreakTimerOptions): UseGameBreakTimerResult {
const [startTime, setStartTime] = useState<number | null>(null)
const [elapsedMs, setElapsedMs] = useState(0)
const animationFrameRef = useRef<number | null>(null)
const hasTimedOutRef = useRef(false)
const maxDurationMs = maxDurationMinutes * 60 * 1000
const start = useCallback(() => {
if (!enabled) return
hasTimedOutRef.current = false
setStartTime(Date.now())
setElapsedMs(0)
}, [enabled])
const stop = useCallback(() => {
setStartTime(null)
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = null
}
}, [])
const reset = useCallback(() => {
hasTimedOutRef.current = false
setStartTime(null)
setElapsedMs(0)
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
animationFrameRef.current = null
}
}, [])
useEffect(() => {
if (!startTime || !enabled) {
return
}
const updateTimer = () => {
const now = Date.now()
const elapsed = now - startTime
setElapsedMs(elapsed)
if (elapsed >= maxDurationMs) {
if (!hasTimedOutRef.current) {
hasTimedOutRef.current = true
onTimeout()
}
} else {
animationFrameRef.current = requestAnimationFrame(updateTimer)
}
}
animationFrameRef.current = requestAnimationFrame(updateTimer)
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current)
}
}
}, [startTime, maxDurationMs, onTimeout, enabled])
const remainingMs = Math.max(0, maxDurationMs - elapsedMs)
const remainingMinutes = Math.floor(remainingMs / 60000)
const remainingSeconds = Math.floor((remainingMs % 60000) / 1000)
const percentRemaining = maxDurationMs > 0 ? (remainingMs / maxDurationMs) * 100 : 100
return {
startTime,
elapsedMs,
remainingMs,
remainingMinutes,
remainingSeconds,
percentRemaining,
isActive: startTime !== null && !hasTimedOutRef.current,
start,
stop,
reset,
}
}
|