about summary refs log blame commit diff stats
path: root/html/isometric-bounce/js/game.js
blob: a1849c8a00b60b786536da09a55ff880127c1f90 (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                      
                         
                 




                       
                     





                                  


                         







                                              
          
     



                                                  
        

                                                                                   
        
                                                      
     



                                                 
        

                                                
        




                                                                               
     





















                                                                                                        

         













                                                                         

             
     

                                                   

                        


                                                                                         
                                            


             


                                                                                         
                                          




                    
 


                                               
 


                                                               
            



                                                                                            
                                            


























                                                                                                                                          
             


         
















                                                                                                                                                    



             


                                                            
            


                                                                              
            
                                  





                                                                 
                                                                          
             
                                  
            

                                                                                                 
            





                                                                
                                                                              










                                                                                      
         




































                                                                                       
     
 
                                  
 
                                        
        










                                                                                       










                                                          











                                                                  

















                                                            
                                



                    

 
                       

                              
 
function createGame() {
    const state = {
        canvas: document.getElementById('gameCanvas'),
        ctx: null,
        gridSize: 10,
        tileWidth: 50,
        tileHeight: 25,
        offsetX: 0,
        offsetY: 0,
        particles: [],
        lastFrameTime: 0,
        player: {
            x: 0,
            y: 0,
            targetX: 0,
            targetY: 0,
            size: 20,
            path: [],
            currentWaypoint: null,
            jumpHeight: 0,
            jumpProgress: 0,
            isJumping: false,
            startX: 0,
            startY: 0
        },
        isHopping: false,
        hopProgress: 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.isHopping) {
            state.hopProgress += jumpDuration;
            state.hopProgress = Math.min(state.hopProgress, 1);
            
            state.player.jumpHeight = Math.sin(state.hopProgress * Math.PI) * maxJumpHeight;

            if (state.hopProgress >= 1) {
                state.isHopping = false;
                state.player.jumpHeight = 0;
            }
        } else {
            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 || state.player.jumpHeight;
        
        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(timestamp) {

        const frameInterval = 1000 / 60;
        
        if (!state.lastFrameTime || timestamp - state.lastFrameTime >= frameInterval) {
            state.ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
            
            drawGrid();
            updateParticles();
            drawParticles();
            updatePlayer();
            drawPlayer();
            
            state.lastFrameTime = timestamp;
        }
        
        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);
        
        const iso = toIsometric(state.player.x, state.player.y);
        const playerRadius = state.player.size;
        const distanceToPlayer = Math.sqrt(
            Math.pow(clickX - (iso.x + state.offsetX), 2) +
            Math.pow(clickY - (iso.y + state.offsetY), 2)
        );

        if (distanceToPlayer < playerRadius) {
            state.isHopping = true;
            state.hopProgress = 0;
        } else 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);
        state.lastFrameTime = 0;
        gameLoop();
    }

    return { init };
}

window.onload = () => {
    const game = createGame();
    game.init();
};