From 060c8abb2474d7b93d7b6c4ae14c8d519efe1f5a Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Sun, 2 Nov 2025 17:01:53 +0000 Subject: [PATCH] feat: add custom gameOver condition to GameOptions - Add optional gameOver field to GameOptions - Can be a boolean value or a function that receives scoreManager - Function receives current score metrics to make dynamic decisions - Enables flexible game-over conditions beyond just time limit - Example: gameOver: (sm) => sm.efficiency > 150 for efficiency threshold - Checked every frame during 'started' and 'has-key' states --- src/types.ts | 1 + src/useGame.ts | 4 ++-- src/useScore/useScore.ts | 28 +++++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/types.ts b/src/types.ts index 0e5f4ad..49ae5cc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,6 +12,7 @@ export type GameOptions = { rows: number; cols: number; timeLimit?: number; + gameOver?: boolean | ((scoreManager: ScoreManager) => boolean); }; export type KeyLogEntry = { key: string; timestamp: number }; diff --git a/src/useGame.ts b/src/useGame.ts index c93cb45..b3008fd 100644 --- a/src/useGame.ts +++ b/src/useGame.ts @@ -6,7 +6,7 @@ import { useHeroRender } from './useCursor'; import { useScore } from './useScore'; export function useGame(options: GameOptions, platformHook?: unknown): GameManager { - const { cols, rows, timeLimit = 600 } = options; + const { cols, rows, timeLimit = 600, gameOver } = options; const board = useBoard(cols, rows); const { containerRef, renderBoard } = board; const gameStatusManager = useGameStatus(); @@ -14,7 +14,7 @@ export function useGame(options: GameOptions, platformHook?: unknown): GameManag const hero = cursor.hero; const renderer = useHeroRender({ gameStatus: gameStatusManager.gameStatus, board, hero }); - const scoreManager = useScore({ board, hero, gameStatusManager, keyManager, rows, cols, timeLimit }); + const scoreManager = useScore({ board, hero, gameStatusManager, keyManager, rows, cols, timeLimit, gameOver }); const gameManager: GameManager = { containerRef, diff --git a/src/useScore/useScore.ts b/src/useScore/useScore.ts index 7d18606..155950b 100644 --- a/src/useScore/useScore.ts +++ b/src/useScore/useScore.ts @@ -5,6 +5,7 @@ import { useScorePaths } from "./useScorePaths"; import type { BoardManager } from "../useBoard/types"; import type { GameStatusManager } from "../useGameStatus/types"; import type { UseHeroType } from "../useCursor/types"; +import type { ScoreManager } from "./types"; type UseScoreParams = { board: BoardManager; @@ -14,9 +15,10 @@ type UseScoreParams = { rows: number; cols: number; timeLimit: number; + gameOver?: boolean | ((scoreManager: ScoreManager) => boolean); }; -export function useScore({ board, hero, gameStatusManager, keyManager, rows, cols, timeLimit }: UseScoreParams) { +export function useScore({ board, hero, gameStatusManager, keyManager, rows, cols, timeLimit, gameOver }: UseScoreParams) { const timer = useTimer(); const { gameStatus, setGameStatus } = gameStatusManager; const { timeValue, startTimer, stopTimer, resetTimer } = timer; @@ -64,6 +66,30 @@ export function useScore({ board, hero, gameStatusManager, keyManager, rows, col } }, [gameStatus, timeValue, timeLimit, setGameStatus]); + // Check custom gameOver condition if provided + useEffect(() => { + if (gameStatus !== "started" && gameStatus !== "has-key") return; + if (!gameOver) return; + + const scoreManager = { + timeValue, + startTimer, + stopTimer, + resetTimer, + distToKey, + distToExit, + keystrokes, + optimalSteps, + efficiency, + finalScore, + }; + + const shouldGameOver = typeof gameOver === "function" ? gameOver(scoreManager) : gameOver; + if (shouldGameOver) { + setGameStatus("game-over"); + } + }, [gameStatus, gameOver, timeValue, keystrokes, optimalSteps, efficiency, distToKey, distToExit, startTimer, stopTimer, resetTimer, finalScore, setGameStatus]); + useEffect(() => { if (gameStatus !== "game-won") return;