const FPS = 60; const FRAME_TIME = 1000 / FPS; let lastFrameTime = 0; const state = { canvas: null, ctx: null }; async function init() { state.canvas = document.getElementById('gameCanvas'); state.ctx = state.canvas.getContext('2d'); await player.init(); Debug.init(); function resize() { state.canvas.width = window.innerWidth; state.canvas.height = window.innerHeight; Camera.centerOn(player.position); } window.addEventListener('resize', resize); resize(); state.canvas.addEventListener('click', handleClick); requestAnimationFrame(gameLoop); FogOfWar.init(); Items.init(); } function drawHex(ctx, x, y, hex) { const screen = HexGrid.toScreenCoordinates(hex, Camera); // Only draw if hex is visible on screen (with padding) if (!HexGrid.isInViewport(screen.x, screen.y, state.canvas)) { return; } // Skip drawing completely if hex hasn't been revealed if (!FogOfWar.isRevealed(hex)) { return; } // Draw the hex fill HexGrid.drawHexPath(ctx, screen.x, screen.y); ctx.fillStyle = Config.colors.HEX_FILL; ctx.fill(); // Only draw grid lines for currently visible hexes // (fog of war will handle the grid lines for revealed but not visible hexes) if (FogOfWar.isVisible(hex)) { ctx.strokeStyle = Config.colors.GRID; ctx.lineWidth = 1; ctx.stroke(); } } function gameLoop(currentTime) { requestAnimationFrame(gameLoop); if (currentTime - lastFrameTime < Config.game.FRAME_TIME) { return; } const deltaTime = Math.min(currentTime - lastFrameTime, Config.game.FRAME_TIME * 2); lastFrameTime = currentTime; // Update debug information Debug.update(currentTime); // Clear with background state.ctx.fillStyle = Config.colors.BACKGROUND; state.ctx.fillRect(0, 0, state.canvas.width, state.canvas.height); // Round camera position to prevent sub-pixel rendering Camera.x = Math.round(Camera.x); Camera.y = Math.round(Camera.y); player.update(); Camera.smoothFollow(player.getCurrentPosition()); if (player.hasMoved) { FogOfWar.updateVisibility(player.position); player.hasMoved = false; } // Draw everything in one pass to prevent flicker HexGrid.getViewportHexes().forEach(hex => { const pixel = HexGrid.toPixel(hex); drawHex(state.ctx, Math.round(pixel.x), Math.round(pixel.y), hex); }); Items.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE); player.draw(state.ctx, HexGrid.toPixel.bind(HexGrid), Camera, HexGrid.SIZE); FogOfWar.draw(state.ctx); InventoryUI.draw(state.ctx); Debug.draw(state.ctx); } function handleClick(event) { if (InventoryUI.isOpen) { InventoryUI.toggleInventory(); return; } const rect = state.canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const hexCoord = HexGrid.fromPixel(x + Camera.x, y + Camera.y); // Check if clicking on player's position if (hexCoord.q === player.position.q && hexCoord.r === player.position.r) { InventoryUI.toggleInventory(); } else { player.moveTo(hexCoord); } } init().catch(console.error);