about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-12-24 15:53:19 -0500
committerelioat <elioat@tilde.institute>2024-12-24 15:53:19 -0500
commit8d4ebce0ef9eb7495e2b27a9a4df5f6001f7e65f (patch)
treed5a394ab15985715a70c1fb4ed8c4c09dcb65da4
parentbb977cc4e502e3d35f3c82e1fca1a3535fda5e19 (diff)
downloadtour-8d4ebce0ef9eb7495e2b27a9a4df5f6001f7e65f.tar.gz
*
-rw-r--r--html/rogue/index.html13
-rw-r--r--html/rogue/js/input.js19
-rw-r--r--html/rogue/js/renderer.js93
-rw-r--r--html/rogue/js/rogue.js134
4 files changed, 160 insertions, 99 deletions
diff --git a/html/rogue/index.html b/html/rogue/index.html
index e564517..2920841 100644
--- a/html/rogue/index.html
+++ b/html/rogue/index.html
@@ -1,15 +1,10 @@
 <!DOCTYPE html>
-<html lang="en">
+<html>
 <head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Rogue</title>
     <style>
-        body, html {
+        body {
             margin: 0;
-            padding: 0;
-            width: 100%;
-            height: 100%;
             overflow: hidden;
         }
         canvas {
@@ -19,9 +14,13 @@
 </head>
 <body>
     <canvas id="gameCanvas"></canvas>
+    <!-- Load renderer.js first since other files depend on its functions -->
+    <script src="js/renderer.js"></script>
+    <script src="js/input.js"></script>
     <script src="js/world.js"></script>
     <script src="js/player.js"></script>
     <script src="js/camera.js"></script>
+    <!-- Load rogue.js last as it uses functions from all other files -->
     <script src="js/rogue.js"></script>
 </body>
 </html>
diff --git a/html/rogue/js/input.js b/html/rogue/js/input.js
new file mode 100644
index 0000000..047321c
--- /dev/null
+++ b/html/rogue/js/input.js
@@ -0,0 +1,19 @@
+const setupInputHandlers = (canvas, gameState) => {
+    let mouseMoveThrottle;
+    
+    canvas.addEventListener('mousemove', (e) => {
+        if (!mouseMoveThrottle) {
+            mouseMoveThrottle = setTimeout(() => {
+                gameState.debug.mouseX = e.clientX + gameState.camera.x;
+                gameState.debug.mouseY = e.clientY + gameState.camera.y;
+                mouseMoveThrottle = null;
+            }, 16);
+        }
+    });
+
+    window.addEventListener('keydown', (e) => {
+        if (e.key === 'd') {
+            gameState.debug.enabled = !gameState.debug.enabled;
+        }
+    });
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/renderer.js b/html/rogue/js/renderer.js
new file mode 100644
index 0000000..e0fd489
--- /dev/null
+++ b/html/rogue/js/renderer.js
@@ -0,0 +1,93 @@
+// Rendering constants
+const RENDER_CONSTANTS = {
+    VIEWPORT_BUFFER: 100,
+    SKY_COLORS: {
+        TOP: '#1a1a2e',
+        UPPER_MID: '#2d1b3d',
+        LOWER_MID: '#462639',
+        BOTTOM: '#1f1f2f'
+    },
+    GROUND_COLOR: '#4a4',
+    DEBUG_FONT: '14px monospace',
+    DEBUG_COLOR: '#ffffff'
+};
+
+// Helper functions for rendering
+const createSkyGradient = (ctx, groundY) => {
+    const gradient = ctx.createLinearGradient(0, 0, 0, groundY);
+    gradient.addColorStop(0, RENDER_CONSTANTS.SKY_COLORS.TOP);
+    gradient.addColorStop(0.4, RENDER_CONSTANTS.SKY_COLORS.UPPER_MID);
+    gradient.addColorStop(0.7, RENDER_CONSTANTS.SKY_COLORS.LOWER_MID);
+    gradient.addColorStop(1, RENDER_CONSTANTS.SKY_COLORS.BOTTOM);
+    return gradient;
+};
+
+const getViewBounds = (camera) => ({
+    left: camera.x - RENDER_CONSTANTS.VIEWPORT_BUFFER,
+    right: camera.x + camera.width + RENDER_CONSTANTS.VIEWPORT_BUFFER,
+    top: camera.y - RENDER_CONSTANTS.VIEWPORT_BUFFER,
+    bottom: camera.y + camera.height + RENDER_CONSTANTS.VIEWPORT_BUFFER
+});
+
+const isInView = (x, viewBounds) => 
+    x > viewBounds.left && x < viewBounds.right;
+
+// Layer rendering functions
+const renderBackground = (ctx, state, groundY, viewBounds) => {
+    // Save the current transform
+    ctx.save();
+    
+    // Reset transform for viewport-fixed sky
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+    
+    // Sky (fixed to viewport)
+    ctx.fillStyle = state.cachedGradient;
+    ctx.fillRect(0, 0, ctx.canvas.width, groundY);
+
+    // Restore transform for underground
+    ctx.restore();
+    
+    // Underground (follows camera)
+    ctx.fillStyle = '#000000';
+    ctx.fillRect(
+        viewBounds.left,
+        groundY,
+        viewBounds.right - viewBounds.left,
+        ctx.canvas.height
+    );
+};
+
+const renderBackgroundObjects = (ctx, state, groundY, viewBounds) => {
+    state.world.backgroundTrees
+        .filter(tree => isInView(tree.x, viewBounds))
+        .forEach(tree => renderTree(ctx, tree, groundY));
+    
+    state.world.backgroundRocks
+        .filter(rock => isInView(rock.x, viewBounds))
+        .forEach(rock => renderRock(ctx, rock, groundY));
+    
+    state.world.backgroundGrass
+        .filter(grass => isInView(grass.x, viewBounds))
+        .forEach(grass => renderGrass(ctx, grass, groundY, state.time));
+};
+
+const renderForegroundObjects = (ctx, state, groundY, viewBounds) => {
+    state.world.foregroundTrees
+        .filter(tree => isInView(tree.x, viewBounds))
+        .forEach(tree => renderTree(ctx, tree, groundY));
+    
+    state.world.foregroundRocks
+        .filter(rock => isInView(rock.x, viewBounds))
+        .forEach(rock => renderRock(ctx, rock, groundY));
+    
+    state.world.foregroundGrass
+        .filter(grass => isInView(grass.x, viewBounds))
+        .forEach(grass => renderGrass(ctx, grass, groundY, state.time));
+};
+
+const renderDebugInfo = (ctx, state) => {
+    ctx.fillStyle = RENDER_CONSTANTS.DEBUG_COLOR;
+    ctx.font = RENDER_CONSTANTS.DEBUG_FONT;
+    const text = `x: ${Math.round(state.debug.mouseX)}, y: ${Math.round(state.debug.mouseY)}`;
+    ctx.fillText(text, 10, ctx.canvas.height - 20);
+}; 
\ No newline at end of file
diff --git a/html/rogue/js/rogue.js b/html/rogue/js/rogue.js
index b4379ea..75a4c86 100644
--- a/html/rogue/js/rogue.js
+++ b/html/rogue/js/rogue.js
@@ -3,11 +3,20 @@ window.gameState = null;
 const initGame = () => {
     const canvas = document.getElementById('gameCanvas');
     const ctx = canvas.getContext('2d');
+    
+    // Target frame rate
+    const FPS = 60;
+    const FRAME_TIME = 1000 / FPS;
+    let lastFrameTime = 0;
 
     // Set canvas to full viewport size
     const resizeCanvas = () => {
         canvas.width = window.innerWidth;
         canvas.height = window.innerHeight;
+        // Clear any cached gradients when resizing
+        if (window.gameState) {
+            window.gameState.cachedGradient = null;
+        }
     };
 
     window.addEventListener('resize', resizeCanvas);
@@ -16,7 +25,7 @@ const initGame = () => {
     // Game state
     let gameState = {
         time: 0,
-        player: createPlayer(100, 100), // Starting position
+        player: createPlayer(100, 100),
         camera: createCamera(0, 0),
         world: createWorld(),
         debug: {
@@ -29,42 +38,37 @@ const initGame = () => {
     // Make gameState globally accessible
     window.gameState = gameState;
 
-    // Throttle mouse move updates
-    let mouseMoveThrottle;
-    canvas.addEventListener('mousemove', (e) => {
-        if (!mouseMoveThrottle) {
-            mouseMoveThrottle = setTimeout(() => {
-                gameState.debug.mouseX = e.clientX + gameState.camera.x;
-                gameState.debug.mouseY = e.clientY + gameState.camera.y;
-                mouseMoveThrottle = null;
-            }, 16); // ~60fps
-        }
-    });
-
-    // Add debug toggle listener
-    window.addEventListener('keydown', (e) => {
-        if (e.key === 'd') {
-            gameState.debug.enabled = !gameState.debug.enabled;
-        }
-    });
+    // Set up input handlers
+    setupInputHandlers(canvas, gameState);
 
     // Game loop
     const gameLoop = (timestamp) => {
-        // Calculate delta time
-        const deltaTime = timestamp - gameState.time;
+        // Check if enough time has passed since last frame
+        if (timestamp - lastFrameTime < FRAME_TIME) {
+            requestAnimationFrame(gameLoop);
+            return;
+        }
+
+        // Calculate delta time (capped at 1 second to prevent huge jumps)
+        const deltaTime = Math.min(timestamp - lastFrameTime, 1000);
+        lastFrameTime = timestamp;
         gameState.time = timestamp;
 
+        // Clear the entire canvas
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+
         // Update
         gameState = updateGame(gameState, deltaTime);
 
         // Render
         render(ctx, gameState);
 
-        // Next frame
+        // Schedule next frame
         requestAnimationFrame(gameLoop);
     };
 
     // Start the game loop
+    lastFrameTime = performance.now();
     requestAnimationFrame(gameLoop);
 };
 
@@ -80,107 +84,53 @@ const updateGame = (state, deltaTime) => {
 };
 
 const render = (ctx, state) => {
-    // Calculate groundY once at the start
     const groundY = ctx.canvas.height - state.world.groundHeight;
 
-    // Create gradient for sky - cache this instead of recreating every frame
+    // Cache sky gradient
     if (!state.cachedGradient) {
-        const gradient = ctx.createLinearGradient(0, 0, 0, groundY);
-        gradient.addColorStop(0, '#1a1a2e');
-        gradient.addColorStop(0.4, '#2d1b3d');
-        gradient.addColorStop(0.7, '#462639');
-        gradient.addColorStop(1, '#1f1f2f');
-        state.cachedGradient = gradient;
+        state.cachedGradient = createSkyGradient(ctx, groundY);
     }
 
-    // Fill sky using cached gradient
-    ctx.fillStyle = state.cachedGradient;
-    ctx.fillRect(0, 0, ctx.canvas.width, groundY);
-    
-    // Implement view frustum culling - only render visible objects
-    const viewBounds = {
-        left: state.camera.x - 100, // Add small buffer
-        right: state.camera.x + state.camera.width + 100,
-        top: state.camera.y - 100,
-        bottom: state.camera.y + state.camera.height + 100
-    };
+    const viewBounds = getViewBounds(state.camera);
 
     // Apply camera transform
     ctx.save();
     ctx.translate(-state.camera.x, -state.camera.y);
 
-    // Fill black background
-    ctx.fillStyle = '#000000';
-    ctx.fillRect(
+    // Clear the transformed canvas area
+    ctx.clearRect(
         viewBounds.left,
-        groundY,
+        0,
         viewBounds.right - viewBounds.left,
         ctx.canvas.height
     );
 
-    // 1. Render only visible background objects
-    state.world.backgroundTrees.forEach(tree => {
-        if (tree.x > viewBounds.left && tree.x < viewBounds.right) {
-            renderTree(ctx, tree, groundY);
-        }
-    });
+    // Render layers
+    renderBackground(ctx, state, groundY, viewBounds);
+    renderBackgroundObjects(ctx, state, groundY, viewBounds);
     
-    state.world.backgroundRocks.forEach(rock => {
-        if (rock.x > viewBounds.left && rock.x < viewBounds.right) {
-            renderRock(ctx, rock, groundY);
-        }
-    });
-
-    state.world.backgroundGrass.forEach(grass => {
-        if (grass.x > viewBounds.left && grass.x < viewBounds.right) {
-            renderGrass(ctx, grass, groundY, state.time);
-        }
-    });
-    
-    // 2. Render platforms
+    // Render platforms
     state.world.platforms.forEach(platform => {
         ctx.fillStyle = platform.color;
         ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
     });
 
-    // 3. Render player
     renderPlayer(ctx, state.player);
+    renderForegroundObjects(ctx, state, groundY, viewBounds);
 
-    // 4. Render foreground objects
-    state.world.foregroundTrees.forEach(tree => {
-        if (tree.x > viewBounds.left && tree.x < viewBounds.right) {
-            renderTree(ctx, tree, groundY);
-        }
-    });
-    
-    state.world.foregroundRocks.forEach(rock => {
-        if (rock.x > viewBounds.left && rock.x < viewBounds.right) {
-            renderRock(ctx, rock, groundY);
-        }
-    });
-
-    state.world.foregroundGrass.forEach(grass => {
-        if (grass.x > viewBounds.left && grass.x < viewBounds.right) {
-            renderGrass(ctx, grass, groundY, state.time);
-        }
-    });
-
-    // 5. Render ground (just the grass top)
-    ctx.fillStyle = '#4a4';
+    // Render ground line
+    ctx.fillStyle = RENDER_CONSTANTS.GROUND_COLOR;
     ctx.fillRect(
         state.camera.x - 1000,
         groundY,
         ctx.canvas.width + 2000,
-        1  // Only render the grass line
+        1
     );
 
-    // Render debug information if enabled
+    // Handle debug rendering
     if (state.debug.enabled) {
         ctx.restore();
-        ctx.fillStyle = '#ffffff';
-        ctx.font = '14px monospace';
-        const text = `x: ${Math.round(state.debug.mouseX)}, y: ${Math.round(state.debug.mouseY)}`;
-        ctx.fillText(text, 10, ctx.canvas.height - 20);
+        renderDebugInfo(ctx, state);
     } else {
         ctx.restore();
     }