about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-12-29 06:19:41 -0500
committerelioat <elioat@tilde.institute>2024-12-29 06:19:41 -0500
commit326e29526606bf7a3d7623705e3baabf5302a071 (patch)
treebffedeef80abafa59fbbe4f43176cb65a80e90b6
parent1efe21139c8f76541e37918de31e80fcb33e6e07 (diff)
downloadtour-326e29526606bf7a3d7623705e3baabf5302a071.tar.gz
*
-rw-r--r--html/voxels/index.html2
-rw-r--r--html/voxels/js/game.js523
2 files changed, 233 insertions, 292 deletions
diff --git a/html/voxels/index.html b/html/voxels/index.html
index fda7eba..570f247 100644
--- a/html/voxels/index.html
+++ b/html/voxels/index.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <title>Isometric Game</title>
+    <title>Bouncy Isometric Guy</title>
     <style>
         body {
             margin: 0;
diff --git a/html/voxels/js/game.js b/html/voxels/js/game.js
index 2ced6fd..fb6530d 100644
--- a/html/voxels/js/game.js
+++ b/html/voxels/js/game.js
@@ -1,362 +1,303 @@
-class IsometricGame {
-    constructor() {
-        this.canvas = document.getElementById('gameCanvas');
-        this.ctx = this.canvas.getContext('2d');
-        
-        // Grid properties
-        this.gridSize = 10;
-        this.tileWidth = 50;
-        this.tileHeight = 25;
-        
-        // Player properties
-        this.player = {
+function createGame() {
+    const state = {
+        canvas: document.getElementById('gameCanvas'),
+        ctx: null,
+        gridSize: 10,
+        tileWidth: 50,
+        tileHeight: 25,
+        offsetX: 0,
+        offsetY: 0,
+        particles: [],
+        player: {
             x: 0,
             y: 0,
             targetX: 0,
             targetY: 0,
             size: 20,
-            path: [], // Array to store waypoints
+            path: [],
             currentWaypoint: null,
             jumpHeight: 0,
             jumpProgress: 0,
             isJumping: false,
             startX: 0,
             startY: 0
+        }
+    };
+
+    state.ctx = state.canvas.getContext('2d');
+
+    function toIsometric(x, y) {
+        return {
+            x: (x - y) * state.tileWidth / 2,
+            y: (x + y) * state.tileHeight / 2
         };
-        
-        // Add particle system
-        this.particles = [];
-        
-        // Handle window resize
-        this.resizeCanvas();
-        window.addEventListener('resize', () => this.resizeCanvas());
-        
-        this.setupEventListeners();
-        this.gameLoop();
     }
-    
-    resizeCanvas() {
-        this.canvas.width = window.innerWidth;
-        this.canvas.height = window.innerHeight;
+
+    function fromIsometric(screenX, screenY) {
+        const adjustedX = screenX - state.offsetX;
+        const adjustedY = screenY - state.offsetY;
         
-        // Recalculate grid offset to center it
-        this.offsetX = this.canvas.width / 2;
-        this.offsetY = this.canvas.height / 3;
+        const x = (adjustedX / state.tileWidth + adjustedY / state.tileHeight) / 1;
+        const y = (adjustedY / state.tileHeight - adjustedX / state.tileWidth) / 1;
         
-        // Scale tile size based on screen size
-        const minDimension = Math.min(this.canvas.width, this.canvas.height);
-        const scaleFactor = minDimension / 800; // 800 is our reference size
-        this.tileWidth = 50 * scaleFactor;
-        this.tileHeight = 25 * scaleFactor;
-        this.player.size = 20 * scaleFactor;
-    }
-    
-    toIsometric(x, y) {
-        return {
-            x: (x - y) * this.tileWidth / 2,
-            y: (x + y) * this.tileHeight / 2
-        };
+        return { x: Math.round(x), y: Math.round(y) };
     }
-    
-    fromIsometric(screenX, screenY) {
-        // Convert screen coordinates back to grid coordinates
-        screenX -= this.offsetX;
-        screenY -= this.offsetY;
+
+    function resizeCanvas() {
+        state.canvas.width = window.innerWidth;
+        state.canvas.height = window.innerHeight;
         
-        const x = (screenX / this.tileWidth + screenY / this.tileHeight) / 1;
-        const y = (screenY / this.tileHeight - screenX / this.tileWidth) / 1;
+        state.offsetX = state.canvas.width / 2;
+        state.offsetY = state.canvas.height / 3;
         
-        return { x: Math.round(x), y: Math.round(y) };
+        const minDimension = Math.min(state.canvas.width, state.canvas.height);
+        const scaleFactor = minDimension / 800;
+        state.tileWidth = 50 * scaleFactor;
+        state.tileHeight = 25 * scaleFactor;
+        state.player.size = 20 * scaleFactor;
     }
-    
-    drawGrid() {
-        for (let x = 0; x < this.gridSize; x++) {
-            for (let y = 0; y < this.gridSize; y++) {
-                const iso = this.toIsometric(x, y);
-                
-                // Draw tile
-                this.ctx.beginPath();
-                this.ctx.moveTo(iso.x + this.offsetX, iso.y + this.offsetY - this.tileHeight/2);
-                this.ctx.lineTo(iso.x + this.offsetX + this.tileWidth/2, iso.y + this.offsetY);
-                this.ctx.lineTo(iso.x + this.offsetX, iso.y + this.offsetY + this.tileHeight/2);
-                this.ctx.lineTo(iso.x + this.offsetX - this.tileWidth/2, iso.y + this.offsetY);
-                this.ctx.closePath();
-                
-                this.ctx.strokeStyle = '#666';
-                this.ctx.stroke();
-                this.ctx.fillStyle = '#fff';
-                this.ctx.fill();
-            }
+
+    function dustyParticles(x, y) {
+        const particleCount = 12;
+        for (let i = 0; i < particleCount; i++) {
+            const baseAngle = (Math.PI * 2 * i) / particleCount;
+            const randomAngle = baseAngle + (Math.random() - 0.5) * 0.5;
+            
+            const speed = 0.3 + Math.random() * 0.4;
+            const initialSize = (state.player.size * 0.15) + (Math.random() * state.player.size * 0.15);
+            const greyValue = 220 + Math.floor(Math.random() * 35);
+            
+            state.particles.push({
+                x, y,
+                dx: Math.cos(randomAngle) * speed,
+                dy: Math.sin(randomAngle) * speed,
+                life: 0.8 + Math.random() * 0.4,
+                size: initialSize,
+                color: `rgb(${greyValue}, ${greyValue}, ${greyValue})`,
+                initialSize,
+                rotationSpeed: (Math.random() - 0.5) * 0.2,
+                rotation: Math.random() * Math.PI * 2
+            });
         }
     }
-    
-    drawPlayer() {
-        // Convert player grid position to isometric coordinates
-        const iso = this.toIsometric(this.player.x, this.player.y);
-        
-        // Apply jump height offset
-        const jumpOffset = this.player.jumpHeight || 0;
-        
-        // Calculate squash and stretch based on jump progress
-        let squashStretch = 1;
-        if (this.player.isJumping) {
-            // Stretch at the start and middle of jump, squash at landing
-            const jumpPhase = Math.sin(this.player.jumpProgress * Math.PI);
-            if (this.player.jumpProgress < 0.2) {
-                // Initial stretch when jumping
-                squashStretch = 1 + (0.3 * (1 - this.player.jumpProgress / 0.2));
-            } else if (this.player.jumpProgress > 0.8) {
-                // Squash when landing
-                squashStretch = 1 - (0.3 * ((this.player.jumpProgress - 0.8) / 0.2));
-            } else {
-                // Slight stretch at peak of jump
-                squashStretch = 1 + (0.1 * jumpPhase);
+
+    function updateParticles() {
+        for (let i = state.particles.length - 1; i >= 0; i--) {
+            const particle = state.particles[i];
+            
+            particle.x += particle.dx;
+            particle.y += particle.dy;
+            particle.dy += 0.01;
+            particle.rotation += particle.rotationSpeed;
+            particle.life -= 0.03;
+            particle.size = particle.initialSize * (particle.life * 1.5);
+            
+            if (particle.life <= 0) {
+                state.particles.splice(i, 1);
             }
         }
-        
-        // Draw player shadow (gets smaller when jumping)
-        const shadowScale = Math.max(0.2, 1 - (jumpOffset / this.tileHeight));
-        this.ctx.beginPath();
-        this.ctx.ellipse(
-            iso.x + this.offsetX,
-            iso.y + this.offsetY + 2,
-            this.player.size * 0.8 * shadowScale,
-            this.player.size * 0.3 * shadowScale,
-            0,
-            0,
-            Math.PI * 2
-        );
-        this.ctx.fillStyle = `rgba(0,0,0,${0.2 * shadowScale})`;
-        this.ctx.fill();
-
-        // Draw player body with jump offset and squash/stretch
-        this.ctx.beginPath();
-        this.ctx.fillStyle = '#ff4444';
-        this.ctx.strokeStyle = '#aa0000';
-        
-        const bodyHeight = this.player.size * 2 * squashStretch;
-        const bodyWidth = this.player.size * 0.8 * (1 / squashStretch); // Inverse stretch for width
-        
-        this.ctx.save();
-        this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY - jumpOffset);
-        this.ctx.scale(1, 0.5); // Apply isometric perspective
-        this.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
-        this.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
-        this.ctx.restore();
-
-        // Draw player head with jump offset and squash/stretch
-        this.ctx.beginPath();
-        this.ctx.ellipse(
-            iso.x + this.offsetX,
-            iso.y + this.offsetY - this.player.size * squashStretch - jumpOffset,
-            this.player.size * (1 / squashStretch),
-            this.player.size * 0.5 * squashStretch,
-            0,
-            0,
-            Math.PI * 2
-        );
-        this.ctx.fillStyle = '#ff4444';
-        this.ctx.fill();
-        this.ctx.strokeStyle = '#aa0000';
-        this.ctx.stroke();
     }
-    
-    findPath(startX, startY, endX, endY) {
-        // Simple pathfinding that follows grid edges
+
+    function findPath(startX, startY, endX, endY) {
         const path = [];
         
-        // First move along X axis
         if (startX !== endX) {
             const stepX = startX < endX ? 1 : -1;
             for (let x = startX + stepX; stepX > 0 ? x <= endX : x >= endX; x += stepX) {
-                path.push({ x: x, y: startY });
+                path.push({ x, y: startY });
             }
         }
         
-        // Then move along Y axis
         if (startY !== endY) {
             const stepY = startY < endY ? 1 : -1;
             for (let y = startY + stepY; stepY > 0 ? y <= endY : y >= endY; y += stepY) {
-                path.push({ x: endX, y: y });
+                path.push({ x: endX, y });
             }
         }
 
         return path;
     }
-    
-    updatePlayer() {
-        const jumpDuration = 0.1; // Faster jump for snappier movement
-        const maxJumpHeight = this.tileHeight;
 
-        // If we don't have a current waypoint but have a path, get next waypoint
-        if (!this.player.currentWaypoint && this.player.path.length > 0) {
-            this.player.currentWaypoint = this.player.path.shift();
-            this.player.isJumping = true;
-            this.player.jumpProgress = 0;
-            
-            // Store starting position for interpolation
-            this.player.startX = this.player.x;
-            this.player.startY = this.player.y;
-        }
+    function updatePlayer() {
+        const jumpDuration = 0.1;
+        const maxJumpHeight = state.tileHeight;
 
-        // Move towards current waypoint
-        if (this.player.currentWaypoint) {
-            // Update jump animation
-            if (this.player.isJumping) {
-                this.player.jumpProgress += jumpDuration;
-                
-                // Clamp progress to 1
-                if (this.player.jumpProgress > 1) this.player.jumpProgress = 1;
-                
-                // Parabolic jump arc
-                this.player.jumpHeight = Math.sin(this.player.jumpProgress * Math.PI) * maxJumpHeight;
-                
-                // Precise interpolation between points
-                this.player.x = this.player.startX + (this.player.currentWaypoint.x - this.player.startX) * this.player.jumpProgress;
-                this.player.y = this.player.startY + (this.player.currentWaypoint.y - this.player.startY) * this.player.jumpProgress;
-                
-                // Landing
-                if (this.player.jumpProgress >= 1) {
-                    this.player.isJumping = false;
-                    this.player.jumpHeight = 0;
-                    this.player.x = this.player.currentWaypoint.x;
-                    this.player.y = this.player.currentWaypoint.y;
-                    this.createDustParticles(this.player.x, this.player.y);
-                    this.player.currentWaypoint = null;
-                }
-            }
+        if (!state.player.currentWaypoint && state.player.path.length > 0) {
+            state.player.currentWaypoint = state.player.path.shift();
+            state.player.isJumping = true;
+            state.player.jumpProgress = 0;
+            state.player.startX = state.player.x;
+            state.player.startY = state.player.y;
         }
-    }
-    
-    setupEventListeners() {
-        this.canvas.addEventListener('click', (e) => {
-            const rect = this.canvas.getBoundingClientRect();
-            const clickX = e.clientX - rect.left;
-            const clickY = e.clientY - rect.top;
-            
-            const gridPos = this.fromIsometric(clickX, clickY);
-            
-            // Only move if within grid bounds
-            if (gridPos.x >= 0 && gridPos.x < this.gridSize &&
-                gridPos.y >= 0 && gridPos.y < this.gridSize) {
-                
-                // Set target and calculate path
-                this.player.targetX = Math.round(gridPos.x);
-                this.player.targetY = Math.round(gridPos.y);
-                
-                // Calculate new path
-                this.player.path = this.findPath(
-                    Math.round(this.player.x),
-                    Math.round(this.player.y),
-                    this.player.targetX,
-                    this.player.targetY
-                );
-                
-                // Clear current waypoint to start new path
-                this.player.currentWaypoint = null;
-            }
-        });
-    }
-    
-    gameLoop() {
-        // Clear canvas
-        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
-        
-        // Draw game elements
-        this.drawGrid();
-        this.updateParticles();
-        this.drawParticles();
-        this.updatePlayer();
-        this.drawPlayer();
-        
-        // Continue game loop
-        requestAnimationFrame(() => this.gameLoop());
-    }
 
-    // Add new particle system methods
-    createDustParticles(x, y) {
-        const particleCount = 12; // Increased for more particles
-        for (let i = 0; i < particleCount; i++) {
-            // Randomize the angle slightly
-            const baseAngle = (Math.PI * 2 * i) / particleCount;
-            const randomAngle = baseAngle + (Math.random() - 0.5) * 0.5;
+        if (state.player.currentWaypoint && state.player.isJumping) {
+            state.player.jumpProgress += jumpDuration;
+            state.player.jumpProgress = Math.min(state.player.jumpProgress, 1);
             
-            // Random speed and size variations
-            const speed = 0.3 + Math.random() * 0.4;
-            const initialSize = (this.player.size * 0.15) + (Math.random() * this.player.size * 0.15);
+            state.player.jumpHeight = Math.sin(state.player.jumpProgress * Math.PI) * maxJumpHeight;
             
-            // Random grey color
-            const greyValue = 220 + Math.floor(Math.random() * 35);
-            const color = `rgb(${greyValue}, ${greyValue}, ${greyValue})`;
+            state.player.x = state.player.startX + (state.player.currentWaypoint.x - state.player.startX) * state.player.jumpProgress;
+            state.player.y = state.player.startY + (state.player.currentWaypoint.y - state.player.startY) * state.player.jumpProgress;
             
-            this.particles.push({
-                x: x,
-                y: y,
-                dx: Math.cos(randomAngle) * speed,
-                dy: Math.sin(randomAngle) * speed,
-                life: 0.8 + Math.random() * 0.4, // Random initial life
-                size: initialSize,
-                color: color,
-                initialSize: initialSize,
-                rotationSpeed: (Math.random() - 0.5) * 0.2,
-                rotation: Math.random() * Math.PI * 2
-            });
+            if (state.player.jumpProgress >= 1) {
+                state.player.isJumping = false;
+                state.player.jumpHeight = 0;
+                state.player.x = state.player.currentWaypoint.x;
+                state.player.y = state.player.currentWaypoint.y;
+                dustyParticles(state.player.x, state.player.y);
+                state.player.currentWaypoint = null;
+            }
         }
     }
 
-    updateParticles() {
-        for (let i = this.particles.length - 1; i >= 0; i--) {
-            const particle = this.particles[i];
-            
-            // Update position with slight gravity effect
-            particle.x += particle.dx;
-            particle.y += particle.dy;
-            particle.dy += 0.01; // Slight upward drift
-            
-            // Update rotation
-            particle.rotation += particle.rotationSpeed;
-            
-            // Non-linear fade out
-            particle.life -= 0.03;
-            particle.size = particle.initialSize * (particle.life * 1.5); // Grow slightly as they fade
-            
-            // Remove dead particles
-            if (particle.life <= 0) {
-                this.particles.splice(i, 1);
+    function drawGrid() {
+        for (let x = 0; x < state.gridSize; x++) {
+            for (let y = 0; y < state.gridSize; y++) {
+                const iso = toIsometric(x, y);
+                
+                // Diamonds!
+                state.ctx.beginPath(); // Start a new path
+                state.ctx.moveTo(iso.x + state.offsetX, iso.y + state.offsetY - state.tileHeight/2); // Move to the top point of the diamond
+                state.ctx.lineTo(iso.x + state.offsetX + state.tileWidth/2, iso.y + state.offsetY); // Draw line to the right point of the diamond
+                state.ctx.lineTo(iso.x + state.offsetX, iso.y + state.offsetY + state.tileHeight/2); // Draw line to the bottom point of the diamond
+                state.ctx.lineTo(iso.x + state.offsetX - state.tileWidth/2, iso.y + state.offsetY); // Draw line to the left point of the diamond
+                state.ctx.closePath(); // Close the path to complete the diamond
+                
+                state.ctx.strokeStyle = '#666';
+                state.ctx.stroke();
+                state.ctx.fillStyle = '#fff';
+                state.ctx.fill();
             }
         }
     }
 
-    drawParticles() {
-        for (const particle of this.particles) {
-            const iso = this.toIsometric(particle.x, particle.y);
+    function drawParticles() {
+        state.particles.forEach(particle => {
+            const iso = toIsometric(particle.x, particle.y);
             
-            this.ctx.save();
-            this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY);
-            this.ctx.rotate(particle.rotation);
+            state.ctx.save();
+            state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY);
+            state.ctx.rotate(particle.rotation);
             
-            // Draw a slightly irregular dust puff
-            this.ctx.beginPath();
+            state.ctx.beginPath();
             const points = 5;
             for (let i = 0; i < points * 2; i++) {
                 const angle = (i * Math.PI) / points;
                 const radius = particle.size * (i % 2 ? 0.7 : 1);
                 const x = Math.cos(angle) * radius;
                 const y = Math.sin(angle) * radius;
-                if (i === 0) this.ctx.moveTo(x, y);
-                else this.ctx.lineTo(x, y);
+                i === 0 ? state.ctx.moveTo(x, y) : state.ctx.lineTo(x, y);
             }
-            this.ctx.closePath();
+            state.ctx.closePath();
             
-            this.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`;
-            this.ctx.fill();
+            state.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`;
+            state.ctx.fill();
             
-            this.ctx.restore();
+            state.ctx.restore();
+        });
+    }
+
+    function drawPlayer() {
+        const iso = toIsometric(state.player.x, state.player.y);
+        const jumpOffset = state.player.jumpHeight || 0;
+        
+        let squashStretch = 1;
+        if (state.player.isJumping) {
+            const jumpPhase = Math.sin(state.player.jumpProgress * Math.PI);
+            if (state.player.jumpProgress < 0.2) {
+                squashStretch = 1 + (0.3 * (1 - state.player.jumpProgress / 0.2));
+            } else if (state.player.jumpProgress > 0.8) {
+                squashStretch = 1 - (0.3 * ((state.player.jumpProgress - 0.8) / 0.2));
+            } else {
+                squashStretch = 1 + (0.1 * jumpPhase);
+            }
         }
+        
+        const shadowScale = Math.max(0.2, 1 - (jumpOffset / state.tileHeight));
+        state.ctx.beginPath();
+        state.ctx.ellipse(
+            iso.x + state.offsetX,
+            iso.y + state.offsetY + 2,
+            state.player.size * 0.8 * shadowScale,
+            state.player.size * 0.3 * shadowScale,
+            0, 0, Math.PI * 2
+        );
+        state.ctx.fillStyle = `rgba(0,0,0,${0.2 * shadowScale})`;
+        state.ctx.fill();
+
+        const bodyHeight = state.player.size * 2 * squashStretch;
+        const bodyWidth = state.player.size * 0.8 * (1 / squashStretch);
+        
+        state.ctx.save();
+        state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY - jumpOffset);
+        state.ctx.scale(1, 0.5);
+        state.ctx.fillStyle = '#ff4444';
+        state.ctx.strokeStyle = '#aa0000';
+        state.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
+        state.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
+        state.ctx.restore();
+
+        state.ctx.beginPath();
+        state.ctx.ellipse(
+            iso.x + state.offsetX,
+            iso.y + state.offsetY - state.player.size * squashStretch - jumpOffset,
+            state.player.size * (1 / squashStretch),
+            state.player.size * 0.5 * squashStretch,
+            0, 0, Math.PI * 2
+        );
+        state.ctx.fillStyle = '#ff4444';
+        state.ctx.fill();
+        state.ctx.strokeStyle = '#aa0000';
+        state.ctx.stroke();
     }
+
+    function gameLoop() {
+        state.ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
+        
+        drawGrid();
+        updateParticles();
+        drawParticles();
+        updatePlayer();
+        drawPlayer();
+        
+        requestAnimationFrame(gameLoop);
+    }
+
+    function handleClick(e) {
+        const rect = state.canvas.getBoundingClientRect();
+        const clickX = e.clientX - rect.left;
+        const clickY = e.clientY - rect.top;
+        
+        const gridPos = fromIsometric(clickX, clickY);
+        
+        if (gridPos.x >= 0 && gridPos.x < state.gridSize &&
+            gridPos.y >= 0 && gridPos.y < state.gridSize) {
+            
+            state.player.targetX = Math.round(gridPos.x);
+            state.player.targetY = Math.round(gridPos.y);
+            
+            state.player.path = findPath(
+                Math.round(state.player.x),
+                Math.round(state.player.y),
+                state.player.targetX,
+                state.player.targetY
+            );
+            
+            state.player.currentWaypoint = null;
+        }
+    }
+
+    function init() {
+        resizeCanvas();
+        window.addEventListener('resize', resizeCanvas);
+        state.canvas.addEventListener('click', handleClick);
+        gameLoop();
+    }
+
+    return { init };
 }
 
-// Start the game when the page loads
 window.onload = () => {
-    new IsometricGame();
+    const game = createGame();
+    game.init();
 };
\ No newline at end of file