From 3debf51df49980b6ef348f44a95dbfec87c45111 Mon Sep 17 00:00:00 2001 From: bender Date: Tue, 10 Mar 2026 14:11:02 +0000 Subject: [PATCH 1/4] Update src/app/page.tsx --- src/app/page.tsx | 292 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 222 insertions(+), 70 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index f40c7b6..157c48a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -17,6 +17,192 @@ import { Trophy, Gamepad2, } from "lucide-react"; +import { useState, useEffect, useRef } from "react"; + +type GameState = "idle" | "playing" | "paused" | "gameover"; + +interface GameInstance { + id: string; + state: GameState; + score: number; + level: number; +} + +const GameContainer = ({ gameId }: { gameId: string }) => { + const [gameState, setGameState] = useState("idle"); + const [score, setScore] = useState(0); + const [level, setLevel] = useState(1); + const canvasRef = useRef(null); + const gameLoopRef = useRef(null); + const keysPressed = useRef<{ [key: string]: boolean }>({}); + + // Keyboard event handlers + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + keysPressed.current[e.key.toLowerCase()] = true; + + // Space to start/pause/resume + if (e.key === " ") { + e.preventDefault(); + if (gameState === "idle" || gameState === "gameover") { + setGameState("playing"); + } else if (gameState === "playing") { + setGameState("paused"); + } else if (gameState === "paused") { + setGameState("playing"); + } + } + }; + + const handleKeyUp = (e: KeyboardEvent) => { + keysPressed.current[e.key.toLowerCase()] = false; + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }, [gameState]); + + // Mouse event handler + const handleCanvasClick = (e: React.MouseEvent) => { + const canvas = canvasRef.current; + if (!canvas) return; + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + // Simple click-based interaction + if (gameState === "playing") { + setScore((prev) => prev + 10); + } + }; + + // Game loop + useEffect(() => { + if (gameState !== "playing") return; + + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + let animationId: number; + + const gameLoop = () => { + // Clear canvas + ctx.fillStyle = "rgba(15, 23, 42, 0.8)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw game elements + ctx.fillStyle = "#00ff88"; + ctx.font = "24px Arial"; + ctx.fillText(`Score: ${score}`, 20, 40); + ctx.fillText(`Level: ${level}`, 20, 80); + + // Draw controls hint + if (gameState === "playing") { + ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; + ctx.font = "14px Arial"; + ctx.fillText("Arrow keys: Move | Space: Pause | Click: Action", 20, canvas.height - 20); + } + + // Update score every frame + if (gameState === "playing") { + setScore((prev) => prev + 1); + } + + animationId = requestAnimationFrame(gameLoop); + }; + + animationId = requestAnimationFrame(gameLoop); + + return () => cancelAnimationFrame(animationId); + }, [gameState, score, level]); + + const handlePauseResume = () => { + if (gameState === "playing") { + setGameState("paused"); + } else if (gameState === "paused") { + setGameState("playing"); + } + }; + + const handleGameOver = () => { + setGameState("gameover"); + }; + + const handleReset = () => { + setScore(0); + setLevel(1); + setGameState("idle"); + }; + + return ( +
+ +
+ {gameState === "idle" && ( + + )} + {gameState === "playing" && ( + + )} + {gameState === "paused" && ( + + )} + {(gameState === "playing" || gameState === "paused") && ( + + )} + {gameState === "gameover" && ( + + )} +
+
+ {gameState === "idle" && "Press Space or click Start to begin"} + {gameState === "playing" && "Game Running - Press Space to pause"} + {gameState === "paused" && "Game Paused - Press Space to resume"} + {gameState === "gameover" && `Game Over! Final Score: ${score}`} +
+
+ ); +}; export default function HomePage() { const navItems = [ @@ -111,18 +297,15 @@ export default function HomePage() { { id: "featured-1", brand: "PlayHub", name: "Neon Blasters Pro", price: "FREE", rating: 5, reviewCount: "12.5k", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-vibrant-action-game-thumbnail-with-exp-1773147106304-b4d00265.jpg", imageAlt: "Neon Blasters Pro action game" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-vibrant-action-game-thumbnail-with-exp-1773147106304-b4d00265.jpg", imageAlt: "Neon Blasters Pro action game"}, { id: "featured-2", brand: "PlayHub", name: "Speed Zone Racing", price: "FREE", rating: 5, reviewCount: "9.8k", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-racing-game-screenshot-showing-high-sp-1773147106485-71fc80aa.png", imageAlt: "Speed Zone Racing game" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-racing-game-screenshot-showing-high-sp-1773147106485-71fc80aa.png", imageAlt: "Speed Zone Racing game"}, { id: "featured-3", brand: "PlayHub", name: "Puzzle Master", price: "FREE", rating: 5, reviewCount: "15.2k", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-puzzle-game-interface-with-colorful-ma-1773147105895-ad36efba.jpg", imageAlt: "Puzzle Master game" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-puzzle-game-interface-with-colorful-ma-1773147105895-ad36efba.jpg", imageAlt: "Puzzle Master game"}, ]} gridVariant="uniform-all-items-equal" animationType="slide-up" @@ -141,43 +324,37 @@ export default function HomePage() { icon: Zap, title: "Action", description: "High-octane gaming with intense combat and explosive gameplay. Perfect for adrenaline seekers.", button: { - text: "Play Action Games", href: "/games" - }, + text: "Play Action Games", href: "/games"}, }, { icon: Gauge, title: "Racing", description: "Speed through tracks with realistic vehicles and competitive racing experiences.", button: { - text: "Play Racing Games", href: "/games" - }, + text: "Play Racing Games", href: "/games"}, }, { icon: Brain, title: "Puzzle", description: "Challenge your mind with logic puzzles, match-3 games, and brain teasers.", button: { - text: "Play Puzzle Games", href: "/games" - }, + text: "Play Puzzle Games", href: "/games"}, }, { icon: Compass, title: "Adventure", description: "Embark on epic quests and explore immersive worlds filled with mystery and wonder.", button: { - text: "Play Adventure Games", href: "/games" - }, + text: "Play Adventure Games", href: "/games"}, }, { icon: Trophy, title: "Sports", description: "Compete in various sports simulations from football to basketball to esports.", button: { - text: "Play Sports Games", href: "/games" - }, + text: "Play Sports Games", href: "/games"}, }, { icon: Gamepad2, title: "Arcade", description: "Classic arcade thrills with retro gameplay, pixel art, and nostalgic gaming joy.", button: { - text: "Play Arcade Games", href: "/games" - }, + text: "Play Arcade Games", href: "/games"}, }, ]} animationType="slide-up" @@ -187,38 +364,23 @@ export default function HomePage() {
@@ -230,33 +392,27 @@ export default function HomePage() { { id: "test-1", title: "Best free gaming platform ever", quote: "PlayHub changed my gaming life! I can play so many games without installing anything. The variety and quality are incredible. Highly recommend!", name: "Alex Chen", role: "Gaming Enthusiast", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-professional-gaming-enthusiast-portrai-1773147108470-29bb2604.png", imageAlt: "Alex Chen testimonial" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-professional-gaming-enthusiast-portrai-1773147108470-29bb2604.png", imageAlt: "Alex Chen testimonial"}, { id: "test-2", title: "Perfect for casual gaming", quote: "I love how easy it is to jump into a game on PlayHub. Whether I have 5 minutes or an hour, there's something perfect for me. Best discovery ever!", name: "Sarah Williams", role: "Casual Player", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-young-casual-gamer-portrait-smiling-ho-1773147107760-3f1647bf.jpg", imageAlt: "Sarah Williams testimonial" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-young-casual-gamer-portrait-smiling-ho-1773147107760-3f1647bf.jpg", imageAlt: "Sarah Williams testimonial"}, { id: "test-3", title: "Serious gamer's paradise", quote: "The competitive games on PlayHub are insane! Great balance, smooth gameplay, and an amazing community. This is where I spend most of my gaming time.", name: "Marcus Johnson", role: "Competitive Player", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-competitive-gamer-portrait-intense-foc-1773147108094-9100416c.jpg", imageAlt: "Marcus Johnson testimonial" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-competitive-gamer-portrait-intense-foc-1773147108094-9100416c.jpg", imageAlt: "Marcus Johnson testimonial"}, { id: "test-4", title: "Something for everyone", quote: "My whole family plays different games on PlayHub. From puzzle games to action, everyone finds what they love. It's a game-changer for households!", name: "Emma Rodriguez", role: "Family Gamer", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-diverse-gamer-portrait-friendly-approa-1773147107832-c70ea8c8.png", imageAlt: "Emma Rodriguez testimonial" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-diverse-gamer-portrait-friendly-approa-1773147107832-c70ea8c8.png", imageAlt: "Emma Rodriguez testimonial"}, { id: "test-5", title: "Mobile gaming done right", quote: "I play on my phone during commutes. PlayHub's mobile experience is smooth, responsive, and packed with amazing games. No lags, pure gaming joy!", name: "David Park", role: "Mobile Gamer", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-mobile-gamer-portrait-casual-comfortab-1773147108245-b78865a8.jpg", imageAlt: "David Park testimonial" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-mobile-gamer-portrait-casual-comfortab-1773147108245-b78865a8.jpg", imageAlt: "David Park testimonial"}, { id: "test-6", title: "The ultimate gaming hub", quote: "As a hardcore gamer, I was skeptical, but PlayHub delivers. The game selection is vast, servers are reliable, and the community is fantastic. Respect!", name: "Lisa Anderson", role: "Hardcore Gamer", imageSrc: - "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-hardcore-gamer-portrait-with-gaming-se-1773147108492-803fc87c.jpg", imageAlt: "Lisa Anderson testimonial" - }, + "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AkizvW9pyMSIEgho4Reud2UqvC/a-hardcore-gamer-portrait-with-gaming-se-1773147108492-803fc87c.jpg", imageAlt: "Lisa Anderson testimonial"}, ]} textboxLayout="default" useInvertedBackground={false} @@ -270,17 +426,13 @@ export default function HomePage() { tag="Global Platform" metrics={[ { - id: "metric-1", value: "100+", description: "Free Games Available" - }, + id: "metric-1", value: "100+", description: "Free Games Available"}, { - id: "metric-2", value: "50M+", description: "Active Players Monthly" - }, + id: "metric-2", value: "50M+", description: "Active Players Monthly"}, { - id: "metric-3", value: "150K+", description: "Games Played Daily" - }, + id: "metric-3", value: "150K+", description: "Games Played Daily"}, { - id: "metric-4", value: "98%", description: "Player Satisfaction" - }, + id: "metric-4", value: "98%", description: "Player Satisfaction"}, ]} gridVariant="uniform-all-items-equal" animationType="slide-up" -- 2.49.1 From 801766b33876e996f89030089e447687f70cbcf1 Mon Sep 17 00:00:00 2001 From: bender Date: Tue, 10 Mar 2026 14:11:03 +0000 Subject: [PATCH 2/4] Add src/components/game/GameCanvas.tsx --- src/components/game/GameCanvas.tsx | 102 +++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/components/game/GameCanvas.tsx diff --git a/src/components/game/GameCanvas.tsx b/src/components/game/GameCanvas.tsx new file mode 100644 index 0000000..9d1eeea --- /dev/null +++ b/src/components/game/GameCanvas.tsx @@ -0,0 +1,102 @@ +"use client"; + +import React, { useEffect, useRef } from "react"; +import { GameEngine, InputManager, SceneManager, GameConfig } from "@/utils/gameEngine"; + +export interface GameCanvasProps { + config: GameConfig; + onEngineReady?: (engine: GameEngine, inputManager: InputManager, sceneManager: SceneManager) => void; + className?: string; + ariaLabel?: string; +} + +/** + * Reusable game canvas component with engine initialization + * Handles canvas rendering, game loop, and input management + */ +export const GameCanvas = React.forwardRef( + ({ config, onEngineReady, className = "", ariaLabel = "Game canvas" }, ref) => { + const canvasRef = useRef(null); + const engineRef = useRef(null); + const inputManagerRef = useRef(null); + const sceneManagerRef = useRef(null); + + // Expose canvas ref + useEffect(() => { + if (ref) { + if (typeof ref === "function") { + ref(canvasRef.current); + } else { + ref.current = canvasRef.current; + } + } + }, [ref]); + + // Initialize game engine + useEffect(() => { + if (!canvasRef.current) return; + + try { + // Create engine + const engine = new GameEngine(config); + const initialized = engine.initialize(canvasRef.current); + + if (!initialized) { + console.error("Failed to initialize game engine"); + return; + } + + // Create input and scene managers + const inputManager = new InputManager(canvasRef.current); + const sceneManager = new SceneManager(); + + // Store references + engineRef.current = engine; + inputManagerRef.current = inputManager; + sceneManagerRef.current = sceneManager; + + // Initialize scene + sceneManager.createScene("main"); + sceneManager.setActiveScene("main"); + + // Call callback if provided + if (onEngineReady) { + onEngineReady(engine, inputManager, sceneManager); + } + + // Start game loop + engine.start(); + + // Handle window resize + const handleResize = () => { + if (canvasRef.current) { + engine.resize(canvasRef.current.offsetWidth, canvasRef.current.offsetHeight); + } + }; + + window.addEventListener("resize", handleResize); + + // Cleanup + return () => { + window.removeEventListener("resize", handleResize); + engine.stop(); + }; + } catch (error) { + console.error("Game engine initialization error:", error); + } + }, [config, onEngineReady]); + + return ( + + ); + } +); + +GameCanvas.displayName = "GameCanvas"; + +export default GameCanvas; -- 2.49.1 From 6a1cb1dfcdd72989ae8d1cf0c0fe60a6dafcf7d3 Mon Sep 17 00:00:00 2001 From: bender Date: Tue, 10 Mar 2026 14:11:04 +0000 Subject: [PATCH 3/4] Add src/components/game/GameComponent.tsx --- src/components/game/GameComponent.tsx | 160 ++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/components/game/GameComponent.tsx diff --git a/src/components/game/GameComponent.tsx b/src/components/game/GameComponent.tsx new file mode 100644 index 0000000..9248a7c --- /dev/null +++ b/src/components/game/GameComponent.tsx @@ -0,0 +1,160 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { GameEngine, InputManager, SceneManager, GameObject, GameConfig } from "@/utils/gameEngine"; +import GameCanvas from "./GameCanvas"; + +export interface GameComponentProps { + gameConfig: GameConfig; + onInitialize?: (engine: GameEngine, input: InputManager, scene: SceneManager) => void; + onFrame?: (engine: GameEngine, input: InputManager) => void; + onCleanup?: () => void; + title?: string; + description?: string; + showStats?: boolean; + className?: string; + canvasClassName?: string; +} + +/** + * Reusable game component wrapper + * Provides game loop integration, state management, and UI overlay + */ +export const GameComponent: React.FC = ({ + gameConfig, + onInitialize, + onFrame, + onCleanup, + title, + description, + showStats = false, + className = "", canvasClassName = ""}) => { + const [stats, setStats] = useState({ + fps: 0, + deltaTime: 0, + elapsedTime: 0, + frameCount: 0, + }); + const [isPaused, setIsPaused] = useState(false); + const engineRef = React.useRef(null); + const inputRef = React.useRef(null); + const sceneRef = React.useRef(null); + const statsIntervalRef = React.useRef(null); + + const handleEngineReady = (engine: GameEngine, input: InputManager, scene: SceneManager) => { + engineRef.current = engine; + inputRef.current = input; + sceneRef.current = scene; + + // Setup game update loop + engine.onUpdate((deltaTime: number) => { + if (onFrame && inputRef.current) { + onFrame(engine, inputRef.current); + } + + // Update scene objects + const objects = scene.getActiveObjects(); + objects.forEach((obj) => { + if (obj.active) { + obj.update(deltaTime); + } + }); + }); + + // Setup rendering + engine.onRender((ctx) => { + // Render scene objects + const objects = scene.getActiveObjects(); + objects.forEach((obj) => { + if (obj.visible) { + obj.render(ctx); + } + }); + }); + + // Call user initialization callback + if (onInitialize) { + onInitialize(engine, input, scene); + } + + // Setup stats updates + if (showStats) { + statsIntervalRef.current = setInterval(() => { + setStats({ + fps: engine.state.fps, + deltaTime: engine.state.deltaTime, + elapsedTime: engine.state.elapsedTime, + frameCount: engine.state.frameCount, + }); + }, 100); + } + }; + + const togglePause = () => { + if (!engineRef.current) return; + + if (isPaused) { + engineRef.current.resume(); + setIsPaused(false); + } else { + engineRef.current.pause(); + setIsPaused(true); + } + }; + + useEffect(() => { + return () => { + if (statsIntervalRef.current) { + clearInterval(statsIntervalRef.current); + } + if (onCleanup) { + onCleanup(); + } + }; + }, [onCleanup]); + + return ( +
+ + + {/* Game overlay UI */} + {(title || description || showStats || true) && ( +
+ {/* Header */} + {(title || description) && ( +
+ {title &&

{title}

} + {description &&

{description}

} +
+ )} + + {/* Controls */} +
+ +
+ + {/* Stats Display */} + {showStats && ( +
+
FPS: {stats.fps}
+
Frame: {stats.frameCount}
+
Time: {stats.elapsedTime.toFixed(2)}s
+
+ )} +
+ )} +
+ ); +}; + +export default GameComponent; -- 2.49.1 From 2699d47b0db5f895aac47b1463e253b3710da3d9 Mon Sep 17 00:00:00 2001 From: bender Date: Tue, 10 Mar 2026 14:11:05 +0000 Subject: [PATCH 4/4] Add src/utils/gameEngine.ts --- src/utils/gameEngine.ts | 388 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 src/utils/gameEngine.ts diff --git a/src/utils/gameEngine.ts b/src/utils/gameEngine.ts new file mode 100644 index 0000000..468bd54 --- /dev/null +++ b/src/utils/gameEngine.ts @@ -0,0 +1,388 @@ +/** + * Game Engine Infrastructure + * Provides core game loop, state management, and canvas/WebGL rendering utilities + */ + +export interface GameState { + isPaused: boolean; + isRunning: boolean; + deltaTime: number; + elapsedTime: number; + frameCount: number; + fps: number; +} + +export interface GameConfig { + width: number; + height: number; + fps?: number; + useWebGL?: boolean; +} + +export class GameEngine { + private canvas: HTMLCanvasElement | null = null; + private ctx: CanvasRenderingContext2D | WebGLRenderingContext | null = null; + private isWebGL: boolean = false; + private animationFrameId: number | null = null; + private lastFrameTime: number = 0; + private frameCount: number = 0; + private targetFPS: number = 60; + private accumulatedTime: number = 0; + + state: GameState = { + isPaused: false, + isRunning: false, + deltaTime: 0, + elapsedTime: 0, + frameCount: 0, + fps: 0, + }; + + private updateCallbacks: Array<(deltaTime: number) => void> = []; + private renderCallbacks: Array<(ctx: CanvasRenderingContext2D | WebGLRenderingContext) => void> = []; + + constructor(config: GameConfig) { + this.targetFPS = config.fps || 60; + this.isWebGL = config.useWebGL || false; + } + + /** + * Initialize the game engine with a canvas element + */ + initialize(canvas: HTMLCanvasElement): boolean { + if (!canvas) return false; + + this.canvas = canvas; + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + + if (this.isWebGL) { + this.ctx = canvas.getContext('webgl') || canvas.getContext('webgl2'); + if (!this.ctx) { + console.warn('WebGL not supported, falling back to 2D'); + this.ctx = canvas.getContext('2d'); + this.isWebGL = false; + } + } else { + this.ctx = canvas.getContext('2d'); + } + + return this.ctx !== null; + } + + /** + * Register an update callback to be called each frame + */ + onUpdate(callback: (deltaTime: number) => void): void { + this.updateCallbacks.push(callback); + } + + /** + * Register a render callback to be called each frame + */ + onRender(callback: (ctx: CanvasRenderingContext2D | WebGLRenderingContext) => void): void { + this.renderCallbacks.push(callback); + } + + /** + * Start the game loop + */ + start(): void { + if (this.state.isRunning) return; + + this.state.isRunning = true; + this.state.isPaused = false; + this.lastFrameTime = performance.now(); + this.accumulatedTime = 0; + + this.gameLoop(); + } + + /** + * Pause the game + */ + pause(): void { + this.state.isPaused = true; + } + + /** + * Resume the game + */ + resume(): void { + this.state.isPaused = false; + this.lastFrameTime = performance.now(); + } + + /** + * Stop the game loop + */ + stop(): void { + this.state.isRunning = false; + this.state.isPaused = false; + + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + this.animationFrameId = null; + } + } + + /** + * Main game loop using requestAnimationFrame + */ + private gameLoop = (): void => { + if (!this.state.isRunning) return; + + const currentTime = performance.now(); + const frameDeltaTime = (currentTime - this.lastFrameTime) / 1000; + this.lastFrameTime = currentTime; + + if (!this.state.isPaused) { + this.accumulatedTime += frameDeltaTime; + const frameTime = 1 / this.targetFPS; + + // Update game state + while (this.accumulatedTime >= frameTime) { + this.state.deltaTime = frameTime; + this.state.elapsedTime += frameTime; + this.state.frameCount++; + + // Call update callbacks + this.updateCallbacks.forEach(callback => callback(frameTime)); + + this.accumulatedTime -= frameTime; + } + + // Calculate FPS + this.frameCount++; + if (currentTime - (this.lastFrameTime - frameDeltaTime) >= 1000) { + this.state.fps = this.frameCount; + this.frameCount = 0; + } + + // Clear canvas + this.clearCanvas(); + + // Call render callbacks + if (this.ctx) { + this.renderCallbacks.forEach(callback => callback(this.ctx!)); + } + } + + this.animationFrameId = requestAnimationFrame(this.gameLoop); + }; + + /** + * Clear the canvas + */ + private clearCanvas(): void { + if (!this.canvas || !this.ctx) return; + + if (this.isWebGL) { + const gl = this.ctx as WebGLRenderingContext; + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + } else { + const ctx = this.ctx as CanvasRenderingContext2D; + ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + } + + /** + * Get canvas dimensions + */ + getDimensions(): { width: number; height: number } { + return { + width: this.canvas?.width || 0, + height: this.canvas?.height || 0, + }; + } + + /** + * Resize canvas + */ + resize(width: number, height: number): void { + if (!this.canvas) return; + + this.canvas.width = width; + this.canvas.height = height; + } +} + +/** + * Base class for reusable game components + */ +export class GameObject { + x: number = 0; + y: number = 0; + width: number = 0; + height: number = 0; + rotation: number = 0; + scaleX: number = 1; + scaleY: number = 1; + visible: boolean = true; + active: boolean = true; + + constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Update game object state + */ + update(deltaTime: number): void { + // Override in subclasses + } + + /** + * Render game object + */ + render(ctx: CanvasRenderingContext2D | WebGLRenderingContext): void { + // Override in subclasses + } + + /** + * Check if this object collides with another + */ + collidsWith(other: GameObject): boolean { + return ( + this.x < other.x + other.width && + this.x + this.width > other.x && + this.y < other.y + other.height && + this.y + this.height > other.y + ); + } + + /** + * Get bounds + */ + getBounds(): { x: number; y: number; width: number; height: number } { + return { + x: this.x, + y: this.y, + width: this.width, + height: this.height, + }; + } +} + +/** + * Input manager for handling keyboard and mouse input + */ +export class InputManager { + private keysPressed: Map = new Map(); + private mousePosition: { x: number; y: number } = { x: 0, y: 0 }; + private mouseButtons: Map = new Map(); + + constructor(canvas: HTMLCanvasElement) { + this.attachEventListeners(canvas); + } + + private attachEventListeners(canvas: HTMLCanvasElement): void { + // Keyboard events + document.addEventListener('keydown', (e) => { + this.keysPressed.set(e.key.toLowerCase(), true); + }); + + document.addEventListener('keyup', (e) => { + this.keysPressed.set(e.key.toLowerCase(), false); + }); + + // Mouse events + canvas.addEventListener('mousemove', (e) => { + const rect = canvas.getBoundingClientRect(); + this.mousePosition = { + x: e.clientX - rect.left, + y: e.clientY - rect.top, + }; + }); + + canvas.addEventListener('mousedown', (e) => { + this.mouseButtons.set(e.button, true); + }); + + canvas.addEventListener('mouseup', (e) => { + this.mouseButtons.set(e.button, false); + }); + } + + /** + * Check if a key is pressed + */ + isKeyPressed(key: string): boolean { + return this.keysPressed.get(key.toLowerCase()) || false; + } + + /** + * Get mouse position + */ + getMousePosition(): { x: number; y: number } { + return this.mousePosition; + } + + /** + * Check if a mouse button is pressed + */ + isMouseButtonPressed(button: number = 0): boolean { + return this.mouseButtons.get(button) || false; + } +} + +/** + * Scene manager for managing game scenes + */ +export class SceneManager { + private scenes: Map = new Map(); + private activeScene: string | null = null; + + /** + * Create a new scene + */ + createScene(name: string): void { + this.scenes.set(name, []); + } + + /** + * Set active scene + */ + setActiveScene(name: string): boolean { + if (this.scenes.has(name)) { + this.activeScene = name; + return true; + } + return false; + } + + /** + * Add object to active scene + */ + addObject(obj: GameObject): void { + if (this.activeScene) { + const scene = this.scenes.get(this.activeScene); + if (scene) { + scene.push(obj); + } + } + } + + /** + * Get all active objects + */ + getActiveObjects(): GameObject[] { + if (this.activeScene) { + return this.scenes.get(this.activeScene) || []; + } + return []; + } + + /** + * Clear active scene + */ + clearActiveScene(): void { + if (this.activeScene) { + this.scenes.set(this.activeScene, []); + } + } +} -- 2.49.1