feat: implement startingLevel option and remove INITIAL_FPS constant

- Remove INITIAL_FPS constant entirely - no longer exists
- Add startingLevel to GameOptions (defaults to 1)
- FPS now equals currentLevel (1:1 ratio)
- Points per food always equals currentLevel
- Game can start at any level with appropriate speed/scoring
This commit is contained in:
2025-10-31 00:14:39 +00:00
parent 58622c64d1
commit 477bcdd9f3
3 changed files with 67 additions and 62 deletions
+1
View File
@@ -32,6 +32,7 @@ export type GameOptions = {
cols: number; cols: number;
rows: number; rows: number;
timeLimit?: number; timeLimit?: number;
startingLevel?: number;
}; };
// ============================================================================ // ============================================================================
+3 -1
View File
@@ -10,6 +10,7 @@ import { useScore } from "../useScore";
export function useGame(optionsOrCols: GameOptions | number, rows?: number, platformHook?: unknown): GameManager { export function useGame(optionsOrCols: GameOptions | number, rows?: number, platformHook?: unknown): GameManager {
// Support both old signature (cols, rows) and new signature (options) // Support both old signature (cols, rows) and new signature (options)
let cols: number; let cols: number;
let startingLevel: number | undefined;
let actualPlatformHook = platformHook; let actualPlatformHook = platformHook;
if (typeof optionsOrCols === 'object') { if (typeof optionsOrCols === 'object') {
@@ -17,6 +18,7 @@ export function useGame(optionsOrCols: GameOptions | number, rows?: number, plat
const options = optionsOrCols as GameOptions; const options = optionsOrCols as GameOptions;
cols = options.cols; cols = options.cols;
rows = options.rows; rows = options.rows;
startingLevel = options.startingLevel;
actualPlatformHook = rows as unknown as typeof platformHook; actualPlatformHook = rows as unknown as typeof platformHook;
} else { } else {
// Old signature: useGame(cols, rows, platformHook) // Old signature: useGame(cols, rows, platformHook)
@@ -32,7 +34,7 @@ export function useGame(optionsOrCols: GameOptions | number, rows?: number, plat
const foodManager = useFood(cols, rows, boardManager); const foodManager = useFood(cols, rows, boardManager);
const gameManager = useGameStatus(boardManager, snakeManager, foodManager); const gameManager = useGameStatus(boardManager, snakeManager, foodManager, { startingLevel });
const { gameStatus, setGameStatus, startGame, quitGame, togglePause, score, level } = gameManager; const { gameStatus, setGameStatus, startGame, quitGame, togglePause, score, level } = gameManager;
useEffect(() => { useEffect(() => {
+12 -10
View File
@@ -4,28 +4,32 @@ import type { UseBoardType } from "../useBoard";
import type { UseCursorType } from "../useCursor"; import type { UseCursorType } from "../useCursor";
import type { UseFoodType } from "../useGame/useFood"; import type { UseFoodType } from "../useGame/useFood";
const INITIAL_FPS = 3;
const FOODS_PER_LEVEL = 2; const FOODS_PER_LEVEL = 2;
export type UseGameStatusParams = {
startingLevel?: number;
};
export function useGameStatus( export function useGameStatus(
boardManager: UseBoardType, boardManager: UseBoardType,
snakeManager: UseCursorType, snakeManager: UseCursorType,
foodManager: UseFoodType foodManager: UseFoodType,
params?: UseGameStatusParams
) { ) {
const startingLevel = params?.startingLevel ?? 1;
const { containerRef } = boardManager; const { containerRef } = boardManager;
const { initSnake, clearSnake, snakeBodyRef } = snakeManager; const { initSnake, clearSnake, snakeBodyRef } = snakeManager;
const { spawnFood, clearFood } = foodManager; const { spawnFood, clearFood } = foodManager;
const [gameStatus, setGameStatus] = useState<GameStatus>("waiting"); const [gameStatus, setGameStatus] = useState<GameStatus>("waiting");
const [score, setScore] = useState(0); const [score, setScore] = useState(0);
const [level, setLevel] = useState(1); const [level, setLevel] = useState(startingLevel);
const [paused, setPaused] = useState(false); const [paused, setPaused] = useState(false);
const levelRef = useRef(1); const levelRef = useRef(startingLevel);
const foodsEatenRef = useRef(0); const foodsEatenRef = useRef(0);
const gameLoopRef = useRef<number | null>(null); const gameLoopRef = useRef<number | null>(null);
const lastUpdateRef = useRef<number>(0); const lastUpdateRef = useRef<number>(0);
const snakeManagerRef = useRef(snakeManager); const snakeManagerRef = useRef(snakeManager);
const foodManagerRef = useRef(foodManager); const foodManagerRef = useRef(foodManager);
const currentFpsRef = useRef(INITIAL_FPS);
snakeManagerRef.current = snakeManager; snakeManagerRef.current = snakeManager;
foodManagerRef.current = foodManager; foodManagerRef.current = foodManager;
@@ -34,10 +38,9 @@ export function useGameStatus(
initSnake(); initSnake();
spawnFood(snakeBodyRef.current, 3); spawnFood(snakeBodyRef.current, 3);
setScore(0); setScore(0);
setLevel(1); setLevel(startingLevel);
levelRef.current = 1; levelRef.current = startingLevel;
foodsEatenRef.current = 0; foodsEatenRef.current = 0;
currentFpsRef.current = INITIAL_FPS;
setGameStatus("started"); setGameStatus("started");
}; };
@@ -62,7 +65,7 @@ export function useGameStatus(
lastUpdateRef.current = performance.now(); lastUpdateRef.current = performance.now();
const gameLoop = (timestamp: number) => { const gameLoop = (timestamp: number) => {
const msPerFrame = 1000 / currentFpsRef.current; const msPerFrame = 1000 / levelRef.current;
const elapsed = timestamp - lastUpdateRef.current; const elapsed = timestamp - lastUpdateRef.current;
if (elapsed >= msPerFrame) { if (elapsed >= msPerFrame) {
@@ -100,7 +103,6 @@ export function useGameStatus(
const newLevel = levelRef.current + 1; const newLevel = levelRef.current + 1;
levelRef.current = newLevel; levelRef.current = newLevel;
setLevel(newLevel); setLevel(newLevel);
currentFpsRef.current = INITIAL_FPS + (newLevel - 1);
foodsEatenRef.current = 0; foodsEatenRef.current = 0;
} }