about summary refs log tree commit diff stats
path: root/html/isometric-bounce/js/game.js
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2024-12-29 06:20:10 -0500
committerelioat <elioat@tilde.institute>2024-12-29 06:20:10 -0500
commit56375df183c97b1f5b5121af82aa024b918af88a (patch)
treec6c21d661b240277aafaf10bd718277bbc585c1c /html/isometric-bounce/js/game.js
parent326e29526606bf7a3d7623705e3baabf5302a071 (diff)
downloadtour-56375df183c97b1f5b5121af82aa024b918af88a.tar.gz
*
Diffstat (limited to 'html/isometric-bounce/js/game.js')
-rw-r--r--html/isometric-bounce/js/game.js303
1 files changed, 303 insertions, 0 deletions
diff --git a/html/isometric-bounce/js/game.js b/html/isometric-bounce/js/game.js
new file mode 100644
index 0000000..fb6530d
--- /dev/null
+++ b/html/isometric-bounce/js/game.js
@@ -0,0 +1,303 @@
+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: [],
+            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
+        };
+    }
+
+    function fromIsometric(screenX, screenY) {
+        const adjustedX = screenX - state.offsetX;
+        const adjustedY = screenY - state.offsetY;
+        
+        const x = (adjustedX / state.tileWidth + adjustedY / state.tileHeight) / 1;
+        const y = (adjustedY / state.tileHeight - adjustedX / state.tileWidth) / 1;
+        
+        return { x: Math.round(x), y: Math.round(y) };
+    }
+
+    function resizeCanvas() {
+        state.canvas.width = window.innerWidth;
+        state.canvas.height = window.innerHeight;
+        
+        state.offsetX = state.canvas.width / 2;
+        state.offsetY = state.canvas.height / 3;
+        
+        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;
+    }
+
+    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
+            });
+        }
+    }
+
+    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);
+            }
+        }
+    }
+
+    function findPath(startX, startY, endX, endY) {
+        const path = [];
+        
+        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, y: startY });
+            }
+        }
+        
+        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 });
+            }
+        }
+
+        return path;
+    }
+
+    function updatePlayer() {
+        const jumpDuration = 0.1;
+        const maxJumpHeight = state.tileHeight;
+
+        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;
+        }
+
+        if (state.player.currentWaypoint && state.player.isJumping) {
+            state.player.jumpProgress += jumpDuration;
+            state.player.jumpProgress = Math.min(state.player.jumpProgress, 1);
+            
+            state.player.jumpHeight = Math.sin(state.player.jumpProgress * Math.PI) * maxJumpHeight;
+            
+            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;
+            
+            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;
+            }
+        }
+    }
+
+    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();
+            }
+        }
+    }
+
+    function drawParticles() {
+        state.particles.forEach(particle => {
+            const iso = toIsometric(particle.x, particle.y);
+            
+            state.ctx.save();
+            state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY);
+            state.ctx.rotate(particle.rotation);
+            
+            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;
+                i === 0 ? state.ctx.moveTo(x, y) : state.ctx.lineTo(x, y);
+            }
+            state.ctx.closePath();
+            
+            state.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`;
+            state.ctx.fill();
+            
+            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 };
+}
+
+window.onload = () => {
+    const game = createGame();
+    game.init();
+};
\ No newline at end of file