Skip to content
LAM
Read Home Blog
Make Projects HTML Tools Games
Touch grass Notes Resume Links
Home Blog HTML Projects
Tools Games Notes Resume Links
Back CALC 2: The Game - Interactive Grapher Math
Download Open
Show description 327 chars · Math

CALC 2: The Game - Interactive Grapher

CALC 2: The Game - Interactive Grapher









CALC 2: THE GAME

PLAYER: LALO











LEVEL SELECT


Custom Input
Level 1: Limaçon's Loop
Level 2: Clash of Curves
Level 3: Infinite Spiral
Level 4: Heart of the Matter
Level 5: Rose Petal Puzzle


Curve 1 (r=)


Curve 2 (r=)


START LEVEL

CALC 2: The Game - Interactive Grapher

9,942 bytes · HTML source
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CALC 2: The Game - Interactive Grapher</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Press Start 2P', cursive;
            background-color: #1a1a1a;
            color: #ffffff;
            image-rendering: pixelated; /* Ensures sharp pixels for the 8-bit feel */
        }
        .game-container {
            display: grid;
            grid-template-columns: 1fr;
            lg:grid-template-columns: 2fr 1fr;
            gap: 2rem;
            max-width: 1200px;
            margin: auto;
            padding: 1rem;
        }
        #graph-container {
            border: 4px solid #fff;
            background-color: #000;
            box-shadow: 8px 8px 0 #5d64f5;
            position: relative;
            aspect-ratio: 1/1;
        }
        #graph-canvas {
            width: 100%;
            height: 100%;
            background-image:
                linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
                linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
            background-size: 20px 20px;
        }
        .controls-container {
            background-color: #2c2c2c;
            border: 4px solid #fff;
            padding: 1.5rem;
            box-shadow: 8px 8px 0 #f5a623;
        }
        .input-8bit, .select-8bit {
            background-color: #1a1a1a;
            border: 2px solid #fff;
            padding: 10px;
            color: #fff;
            font-family: 'Press Start 2P', cursive;
            width: 100%;
            margin-bottom: 1rem;
        }
        .btn-8bit {
            background-color: #5d64f5;
            border: none;
            padding: 12px 24px;
            color: white;
            text-align: center;
            font-size: 16px;
            cursor: pointer;
            position: relative;
            box-shadow: inset -4px -4px 0px 0px #3a3d99;
            transition: all 0.1s ease-in-out;
            width: 100%;
        }
        .btn-8bit:hover, .btn-8bit:focus {
            background-color: #7178ff;
            box-shadow: inset -6px -6px 0px 0px #3a3d99;
        }
        .btn-8bit:active {
            box-shadow: inset 4px 4px 0px 0px #3a3d99;
        }
    </style>
</head>
<body class="p-4 md:p-8">

    <header class="text-center mb-8">
        <h1 class="text-3xl md:text-4xl text-yellow-400 tracking-wider">CALC 2: THE GAME</h1>
        <p class="text-green-400 mt-2">PLAYER: LALO</p>
    </header>

    <div class="game-container">
        <div id="graph-container">
            <canvas id="graph-canvas"></canvas>
        </div>

        <div class="controls-container">
            <h2 class="text-2xl text-center mb-4 text-orange-400">LEVEL SELECT</h2>
            <select id="level-select" class="select-8bit">
                <option value="custom">Custom Input</option>
                <option value="level1">Level 1: Limaçon's Loop</option>
                <option value="level2">Level 2: Clash of Curves</option>
                <option value="level3">Level 3: Infinite Spiral</option>
                <option value="level4">Level 4: Heart of the Matter</option>
                <option value="level5">Level 5: Rose Petal Puzzle</option>
            </select>

            <label for="r1-input" class="mt-4 block">Curve 1 (r=)</label>
            <input type="text" id="r1-input" class="input-8bit" value="5-5*sin(theta)">
            
            <label for="r2-input" class="mt-4 block">Curve 2 (r=)</label>
            <input type="text" id="r2-input" class="input-8bit" value="">

            <button id="plot-button" class="btn-8bit mt-4">START LEVEL</button>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('graph-canvas');
        const ctx = canvas.getContext('2d');
        const container = document.getElementById('graph-container');

        const r1Input = document.getElementById('r1-input');
        const r2Input = document.getElementById('r2-input');
        const plotButton = document.getElementById('plot-button');
        const levelSelect = document.getElementById('level-select');

        // --- Level Data ---
        const levels = {
            level1: { r1: '11*cos(theta)-8', r2: '' },
            level2: { r1: '6*sin(theta)', r2: '4' },
            level3: { r1: '9*exp(-theta)', r2: '' },
            level4: { r1: '5-5*sin(theta)', r2: '' },
            level5: { r1: '7*sin(2*theta)', r2: '7*sin(theta)' },
            custom: { r1: '5-5*sin(theta)', r2: ''}
        };

        // --- Event Listeners ---
        plotButton.addEventListener('click', draw);
        levelSelect.addEventListener('change', (e) => {
            const level = levels[e.target.value];
            r1Input.value = level.r1;
            r2Input.value = level.r2;
            draw();
        });
        
        window.addEventListener('resize', draw);

        // --- Drawing Logic ---
        function draw() {
            // Resize canvas to fit container
            const size = container.clientWidth;
            canvas.width = size;
            canvas.height = size;

            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            
            // Find max r to scale the graph
            let maxR = 0;
            try {
                 // We need to evaluate the functions to find a good scale.
                 // This is a simplified approach. A more robust solution would parse the function.
                 // For now, we'll use a fixed scale or a simple heuristic.
                 const r1Func = new Function('theta', `with(Math) { return ${r1Input.value || 0}; }`);
                 const r2Func = new Function('theta', `with(Math) { return ${r2Input.value || 0}; }`);

                 for (let i = 0; i < 360; i++) {
                     const theta = i * Math.PI / 180;
                     if (r1Input.value) maxR = Math.max(maxR, Math.abs(r1Func(theta)));
                     if (r2Input.value) maxR = Math.max(maxR, Math.abs(r2Func(theta)));
                 }
            } catch (e) {
                console.error("Error parsing function:", e);
                maxR = 10; // Default scale on error
            }
            
            if (maxR === 0) maxR = 10; // Default scale if no input
            const scale = size / (maxR * 2.2);

            // Clear canvas and draw axes
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawAxes(centerX, centerY, scale, maxR);

            // Plot curves
            if (r1Input.value) {
                plot(r1Input.value, '#ff6347', centerX, centerY, scale); // Tomato Red
            }
            if (r2Input.value) {
                plot(r2Input.value, '#1e90ff', centerX, centerY, scale); // Dodger Blue
            }
        }

        function drawAxes(cx, cy, scale, maxR) {
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
            ctx.lineWidth = 2;

            // X and Y axes
            ctx.beginPath();
            ctx.moveTo(0, cy);
            ctx.lineTo(canvas.width, cy);
            ctx.moveTo(cx, 0);
            ctx.lineTo(cx, canvas.height);
            ctx.stroke();

            // Axis labels
            ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
            ctx.font = '12px "Press Start 2P"';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            
            const numTicks = 5;
            for(let i = 1; i <= numTicks; i++) {
                const tickVal = (maxR / numTicks * i).toFixed(1);
                // Positive X
                ctx.fillText(tickVal, cx + i * (maxR * scale / numTicks), cy + 15);
                // Positive Y
                ctx.fillText(tickVal, cx + 15, cy - i * (maxR * scale / numTicks));
            }
        }

        function plot(funcStr, color, cx, cy, scale) {
            ctx.strokeStyle = color;
            ctx.lineWidth = 4;
            ctx.beginPath();

            let func;
            try {
                // Using 'with(Math)' to allow direct use of Math functions like sin, cos, etc.
                func = new Function('theta', `with(Math) { return ${funcStr}; }`);
            } catch (e) {
                console.error("Could not plot function:", funcStr, e);
                return; // Exit if function is invalid
            }
            
            let firstPoint = true;

            for (let i = 0; i <= 360 * 4; i++) { // Increased resolution
                const theta = (i / 4) * (Math.PI / 180);
                let r;
                try {
                    r = func(theta);
                } catch(e) {
                    break; // Stop plotting if function fails
                }

                if (typeof r !== 'number' || !isFinite(r)) continue;

                const x = cx + r * Math.cos(theta) * scale;
                const y = cy - r * Math.sin(theta) * scale; // Y is inverted in canvas

                if (firstPoint) {
                    ctx.moveTo(x, y);
                    firstPoint = false;
                } else {
                    ctx.lineTo(x, y);
                }
            }
            ctx.stroke();
        }

        // Initial draw on page load
        draw();
    </script>

</body>
</html>