Spaces:
Paused
Paused
| 'use client'; | |
| import { useEffect, useRef, useCallback, useState } from 'react'; | |
| import { Game } from '@/lib/engine/Game'; | |
| import { GAME_CONFIG } from '@/lib/constants'; | |
| import TouchControls from './TouchControls'; | |
| interface GameCanvasProps { | |
| onGameOver: (isWin: boolean, score: number) => void; | |
| startLevel?: number; | |
| } | |
| interface Dimensions { | |
| width: number; | |
| height: number; | |
| } | |
| export default function GameCanvas({ onGameOver, startLevel = 0 }: GameCanvasProps) { | |
| const canvasRef = useRef<HTMLCanvasElement>(null); | |
| const containerRef = useRef<HTMLDivElement>(null); | |
| const gameRef = useRef<Game | null>(null); | |
| const [isMobile, setIsMobile] = useState(false); | |
| const [dimensions, setDimensions] = useState<Dimensions>({ | |
| width: GAME_CONFIG.CANVAS_WIDTH, | |
| height: GAME_CONFIG.CANVAS_HEIGHT | |
| }); | |
| const handleGameOver = useCallback((isWin: boolean, score: number) => { | |
| onGameOver(isWin, score); | |
| }, [onGameOver]); | |
| const handleControlChange = useCallback((controls: { left: boolean; right: boolean; jump: boolean }) => { | |
| if (gameRef.current) { | |
| gameRef.current.setControls(controls); | |
| } | |
| }, []); | |
| // Detect mobile and handle resize | |
| useEffect(() => { | |
| const checkMobile = () => { | |
| const mobile = window.matchMedia('(max-width: 1024px)').matches || | |
| 'ontouchstart' in window || | |
| navigator.maxTouchPoints > 0; | |
| setIsMobile(mobile); | |
| }; | |
| const handleResize = () => { | |
| checkMobile(); | |
| const viewportWidth = window.innerWidth; | |
| const viewportHeight = window.innerHeight; | |
| const aspectRatio = GAME_CONFIG.CANVAS_WIDTH / GAME_CONFIG.CANVAS_HEIGHT; | |
| // Reserve space for touch controls on mobile | |
| const controlsHeight = isMobile ? 140 : 0; | |
| const availableHeight = viewportHeight - controlsHeight - 20; | |
| const availableWidth = viewportWidth - 20; | |
| let newWidth, newHeight; | |
| if (availableWidth / availableHeight > aspectRatio) { | |
| // Height is limiting factor | |
| newHeight = Math.min(availableHeight, GAME_CONFIG.CANVAS_HEIGHT); | |
| newWidth = newHeight * aspectRatio; | |
| } else { | |
| // Width is limiting factor | |
| newWidth = Math.min(availableWidth, GAME_CONFIG.CANVAS_WIDTH); | |
| newHeight = newWidth / aspectRatio; | |
| } | |
| setDimensions({ | |
| width: Math.floor(newWidth), | |
| height: Math.floor(newHeight) | |
| }); | |
| }; | |
| handleResize(); | |
| window.addEventListener('resize', handleResize); | |
| window.addEventListener('orientationchange', handleResize); | |
| return () => { | |
| window.removeEventListener('resize', handleResize); | |
| window.removeEventListener('orientationchange', handleResize); | |
| }; | |
| }, [isMobile]); | |
| useEffect(() => { | |
| if (!canvasRef.current) return; | |
| const game = new Game(canvasRef.current, startLevel, isMobile); | |
| gameRef.current = game; | |
| game.setOnGameOver(handleGameOver); | |
| game.start(); | |
| return () => { | |
| game.stop(); | |
| }; | |
| }, [handleGameOver, startLevel, isMobile]); | |
| // Calculate scale for CSS transform | |
| const scale = dimensions.width / GAME_CONFIG.CANVAS_WIDTH; | |
| return ( | |
| <div className="game-wrapper" ref={containerRef}> | |
| <div | |
| className="canvas-container" | |
| style={{ | |
| width: dimensions.width, | |
| height: dimensions.height, | |
| }} | |
| > | |
| <canvas | |
| ref={canvasRef} | |
| width={GAME_CONFIG.CANVAS_WIDTH} | |
| height={GAME_CONFIG.CANVAS_HEIGHT} | |
| className="game-canvas" | |
| style={{ | |
| width: dimensions.width, | |
| height: dimensions.height, | |
| }} | |
| tabIndex={0} | |
| /> | |
| </div> | |
| {isMobile && <TouchControls onControlChange={handleControlChange} />} | |
| </div> | |
| ); | |
| } | |