127.0.0.1:8000 personal-website / master src / js / cat.js
master

Tree @master (Download .tar.gz)

cat.js @masterraw · history · blame

const imgOffset = -32;
const speed = 4;

const catMessage = document.getElementById("cat-message");
const wrapper = document.getElementById("cat-wrapper");

let mouse = {
    x: 0,
    y: 0
};

let sitActionTimeout;
let sleepActionTimeout;
let frameInterval;

let cat = {
    img: document.getElementById("cat"),
    frame: false,
    backgroundX: 1,
    backgroundY: 1,
    setBackground: function() {
        let x = cat.backgroundX;
        let y = cat.backgroundY;
        if (arguments.length) {
            cat.backgroundX = x = arguments[0];
            cat.backgroundY = y = arguments[1];
        }
        // Abusing JS math with bool types
        cat.img.style.backgroundPositionX = px((x + cat.frame) * imgOffset);
        cat.img.style.backgroundPositionY = px(y * imgOffset);
    },
    getOffset: function() {
        return {
            x: (cat.img.offsetLeft - imgOffset / 2) - mouse.x,
            y: (cat.img.offsetTop - imgOffset) - mouse.y
        };
    },
    run: function() {
        let offset = cat.getOffset();
        let distance = findDistance(offset.x, offset.y);
        let differenceThreshold = distance * 0.5;

        if (Math.abs(offset.x) - Math.abs(offset.y) > differenceThreshold) {
            // horizontal
            if (offset.x > 0) { // left
                cat.setBackground(0, 3);
            } else { // right
                cat.setBackground(2, 3);
            }
        } else if (Math.abs(offset.y) - Math.abs(offset.x) > differenceThreshold) {
            // vertical
            if (offset.y > 0) { // up
                cat.setBackground(0, 2);
            } else { // down
                cat.setBackground(2, 2);
            }
        } else if (offset.x > 0) {
            // diagonal left
            if (offset.y > 0) { // diagonal up
                cat.setBackground(0, 4);
            } else { // diagonal down
                cat.setBackground(0, 5);
            }
        } else {
            // diagonal right
            if (offset.y > 0) { // diagonal up
                cat.setBackground(2, 4);
            } else { // diagonal down
                cat.setBackground(2, 5);
            }
        }
    },
    alert: function() {
        stopFrameInterval();
        cat.setBackground(3, 1);
    },
    isAlert: function() {
        return cat.backgroundX === 3 && cat.backgroundY === 1;
    },
    sit: function() {
        stopFrameInterval();
        cat.setBackground(0, 0);
        sitActionTimeout = setTimeout(function() {
            if (Math.random() > 0.5) {
                cat.lick();
            } else {
                cat.scratch();
            }
        }, Math.floor(Math.random() * 10) * 500 + 1500);
        if (!sleepActionTimeout) {
            sleepActionTimeout = setTimeout(function() {
                clearTimeout(sitActionTimeout);
                cat.sleep();
            }, 20000);
        }
    },
    lick: function() {
        cat.setBackground(0, 0);
        startFrameInterval(500);
        sitActionTimeout = setTimeout(function() {
            stopFrameInterval();
            cat.sit();
        }, 2000);
    },
    scratch: function() {
        cat.setBackground(2, 0);
        startFrameInterval(500);
        sitActionTimeout = setTimeout(function() {
            stopFrameInterval();
            cat.sit();
        }, 4000);
    },
    sleep: function() {
        stopFrameInterval();
        cat.setBackground(0, 1);
        sleepActionTimeout = setTimeout(function() {
            sleepActionTimeout = undefined;
            cat.setBackground(1, 1);
            startFrameInterval(1000);
        }, 1000);
    },
    isSleep: function() {
        return (cat.backgroundX === 1 || cat.backgroundX === 2) && cat.backgroundY === 1;
    }
};

function startFrameInterval(interval) {
    if (frameInterval) {
        clearInterval(frameInterval);
    }

    frameInterval = setInterval(function() {
        cat.frame = !cat.frame;
        cat.setBackground();
    }, interval);
}

function stopFrameInterval() {
    cat.frame = false;
    clearInterval(frameInterval);
    frameInterval = undefined;
}

// Cat initializes in sleep mode, so just start the animation
startFrameInterval(1000);

window.addEventListener("mousemove", function(event) {
    mouse.x = event.x - wrapper.offsetLeft;
    mouse.y = event.y - wrapper.offsetTop;
});

function startMoveCatEvent() {
    cat.img.removeEventListener("click", startMoveCatEvent);
    cat.img.style.cursor = "default";
    catMessage.style.opacity = 0;
    // catMessage.innerHTML = "Click here to<br>put me back.";
    // catMessage.style.cursor = "pointer";
    // catMessage.addEventListener("click", messageClickEvent);
    window.addEventListener("mousemove", mouseMoveEvent);
    startMoveCat();
}

function messageClickEvent() {
    window.removeEventListener("mousemove", mouseMoveEvent);
    stopCat = true;
    cat.img.style.left = "42px";
    cat.img.style.top = "37px";
    clearTimeout(sitActionTimeout);
    clearTimeout(sleepActionTimeout);
    sleepActionTimeout = undefined;
    cat.sleep();
    cat.img.style.cursor = "pointer";
    catMessage.innerHTML = "Hey there, click<br>me to wake me up!";
    catMessage.style.cursor = "default";
    catMessage.removeEventListener("click", messageClickEvent);
    cat.img.addEventListener("click", startMoveCatEvent);
}

function mouseMoveEvent() {
    if (!cat.isAlert()) {
        cat.run();
    }
    if (stopCat) {
        stopCat = false;
        startMoveCat();
    }
}

cat.img.addEventListener("click", startMoveCatEvent);

function startMoveCat() {
    nextFrameTime = Date.now();
    clearTimeout(sitActionTimeout);
    clearTimeout(sleepActionTimeout);
    sleepActionTimeout = undefined;
    let wasSleep = cat.isSleep();
    if (wasSleep) {
        cat.alert();
    }
    setTimeout(function() {
        cat.run();
        startFrameInterval(250);
        moveCat();
    }, wasSleep ? 1000 : 100);
}

const fpsInterval = 1000 / 30;
let stopCat = false;
let currFrameTime;
let nextFrameTime;

function moveCat() {
    if (stopCat) {
        return;
    }

    requestAnimationFrame(moveCat);

    currFrameTime = Date.now();
    let elapsedTime = currFrameTime - nextFrameTime;
    if (elapsedTime > fpsInterval) {
        nextFrameTime = currFrameTime - (elapsedTime % fpsInterval);

        let offset = cat.getOffset();
        let distance = findDistance(offset.x, offset.y);
        if (distance > 0) {
            let sumOffset = Math.abs(offset.x) + Math.abs(offset.y);
            let delta = -Math.min(speed, distance);
            let xRatio = offset.x / sumOffset;
            let yRatio = offset.y / sumOffset;
            // Cat runs slower diagonally bc pixel math is bullshit
            let diagonalScale = Math.max(1.5 / (Math.abs(Math.abs(xRatio) - Math.abs(yRatio)) + 1), 1);
            cat.img.style.left = px(cat.img.offsetLeft + (diagonalScale * delta * xRatio));
            cat.img.style.top = px(cat.img.offsetTop + (diagonalScale * delta * yRatio));
        } else {
            stopCat = true;
            cat.sit();
        }
    }
}

function findDistance(dx, dy) {
    return Math.sqrt(dx * dx + dy * dy);
}

function px(val) {
    return `${val}px`;
}