ย
Visual perception of the 5 most important unsolved concepts in mathematics!
Prompt
Create a full-page, responsive, and visually appealing interactive web application to visualize the Collatz Conjecture. Use HTML, CSS (Tailwind CSS), and JavaScript. The page should have a dark theme. **1. UI Components:** - A main title: "The Collatz Conjecture Visualizer". - A brief explanation of the conjecture. - An input field for the user to enter a positive integer. It should only accept numbers. - A "Visualize Sequence" button. - A toggle switch or button to change between "Sequence View" and "Forest View". - A main canvas area where the visualization will be rendered (use p5.js or a similar library for drawing). - An area to display the sequence of numbers as text (e.g., "6 โ 3 โ 10 โ ... โ 1"). **2. "Sequence View" Functionality (Default):** - When the user enters a number (e.g., 6) and clicks "Visualize", clear the canvas. - Calculate the full sequence until it reaches 1. - Animate the drawing of the sequence. For each step, draw a point for the number and a line connecting it to the previous point. The y-position of the point should correspond to the number's value, creating a zig-zagging path downwards. - As each number is drawn, display the operation (e.g., "x3 + 1") next to the connecting line. - Display the full sequence as text below the canvas. - Handle invalid inputs (non-integers, numbers less than 1) with a user-friendly error message. **3. "Forest View" Functionality:** - When the user switches to this view, the input field is replaced by a slider to select a range (e.g., "Visualize numbers from 1 to N", where N is set by the slider). - When visualized, calculate the Collatz sequence for every number in the selected range. - Draw all these paths on the canvas simultaneously. The paths should be color-coded based on their starting number or length. - The final visualization should resemble a tree or a network of rivers, where all paths converge to the number 1. This view should be optimized for performance, as it can involve many calculations. The Collatz Conjecture Problem Description: Start with any positive integer n. If n is even, the next term is n/2. If n is odd, the next term is 3n + 1. The conjecture states that no matter what value of n you start with, the sequence will always eventually reach 1. Visualization Concept: The Hailstone Sequence Animator The most effective visualization is an interactive animator that shows the path of any given number. This directly illustrates the core process of the conjecture. Interaction: The user enters a number. The application then animates the sequence, showing each number and the operation performed (/2 or *3+1) to get to the next. The path is drawn as a series of connected points, creating a "hailstone" graph that zig-zags downwards until it hits 1. Advanced View: A "Forest View" button that shows the paths of many numbers (e.g., 1 to 100) simultaneously. This creates a beautiful, tree-like structure where all branches eventually merge and flow into the single root, which is 1. This powerfully visualizes the "all paths lead to one" idea.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>The Collatz Conjecture Visualizer</title> <!-- Tailwind (CDN build โ works without compilation) --> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"/> <!-- p5.js --> <script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.min.js"></script> </head> <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col"> <!-- HEADER --> <header class="text-center py-8"> <h1 class="text-3xl md:text-5xl font-extrabold tracking-wide mb-4"> The Collatz Conjecture Visualizer </h1> <p class="max-w-2xl mx-auto text-gray-400"> Start with any positive integer n. If n is even, divide it by 2; if odd, multiply by 3 and add 1. The conjecture claims every starting value eventually reaches 1. Explore the sequences below! </p> </header> <!-- CONTROLS --> <section class="w-full max-w-4xl mx-auto px-4 mb-4"> <!-- Toggle --> <div class="flex items-center justify-center space-x-4 mb-6"> <button id="viewToggle" class="px-4 py-2 rounded-md bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:ring transition"> Switch to Forest View </button> </div> <!-- Sequence-view inputs --> <div id="sequenceControls" class="flex flex-col md:flex-row items-center justify-center space-y-3 md:space-y-0 md:space-x-3"> <input id="numberInput" type="number" min="1" placeholder="Enter a positive integer" class="w-full md:w-64 px-3 py-2 rounded-md bg-gray-800 border border-gray-700 placeholder-gray-500 focus:outline-none focus:ring"/> <button id="visualizeBtn" class="px-4 py-2 w-full md:w-auto rounded-md bg-green-600 hover:bg-green-500 focus:outline-none focus:ring transition"> Visualize Sequence </button> </div> <!-- Forest-view inputs (hidden by default) --> <div id="forestControls" class="hidden flex flex-col md:flex-row items-center justify-center space-y-3 md:space-y-0 md:space-x-3"> <div class="w-full md:w-64"> <label for="rangeSlider" class="block mb-1 text-sm text-gray-400">Visualize numbers 1 โ <span id="rangeVal">50</span></label> <input id="rangeSlider" type="range" min="2" max="300" value="50" class="w-full"/> </div> <button id="forestBtn" class="px-4 py-2 w-full md:w-auto rounded-md bg-green-600 hover:bg-green-500 focus:outline-none focus:ring transition"> Visualize Forest </button> </div> <!-- Error msg --> <p id="errorMsg" class="text-center text-red-500 mt-3 hidden"></p> </section> <!-- CANVAS HOLDER --> <div id="sketch-holder" class="flex-1 mx-auto w-full max-w-7xl px-4"></div> <!-- SEQUENCE TEXT --> <section id="sequenceText" class="w-full max-w-4xl mx-auto px-4 py-6 text-center text-gray-300 whitespace-pre-wrap"></section> <script> // ------------------ Utility ------------------ function collatzSequence(n) { const seq = []; while (true) { seq.push(n); if (n === 1) break; n = (n % 2 === 0) ? n / 2 : 3 * n + 1; } return seq; } // ------------------ DOM Elements ------------- const viewToggle = document.getElementById('viewToggle'); const sequenceCtrls = document.getElementById('sequenceControls'); const forestCtrls = document.getElementById('forestControls'); const numberInput = document.getElementById('numberInput'); const visualizeBtn = document.getElementById('visualizeBtn'); const forestBtn = document.getElementById('forestBtn'); const errorMsg = document.getElementById('errorMsg'); const sequenceText = document.getElementById('sequenceText'); const rangeSlider = document.getElementById('rangeSlider'); const rangeVal = document.getElementById('rangeVal'); let viewMode = 'sequence'; // 'sequence' or 'forest' let p5Instance = null; // holds our sketch // ------------------ Mode Switching ---------- viewToggle.addEventListener('click', () => { if (viewMode === 'sequence') { viewMode = 'forest'; viewToggle.textContent = 'Switch to Sequence View'; sequenceCtrls.classList.add('hidden'); forestCtrls.classList.remove('hidden'); sequenceText.textContent = ''; clearError(); restartSketch(); } else { viewMode = 'sequence'; viewToggle.textContent = 'Switch to Forest View'; forestCtrls.classList.add('hidden'); sequenceCtrls.classList.remove('hidden'); sequenceText.textContent = ''; clearError(); restartSketch(); } }); // Slider label live update rangeSlider.addEventListener('input', () => rangeVal.textContent = rangeSlider.value); // ------------------ Validation -------------- function showError(msg) { errorMsg.textContent = msg; errorMsg.classList.remove('hidden'); } function clearError() { errorMsg.classList.add('hidden'); } // ------------------ p5 Sketch ---------------- function createSketch() { return function (p) { let seq = []; // current sequence (sequence mode) let stepIndex = 0; // animation pointer let animating = false; // For forest mode let forestData = []; // array of sequences let maxSteps = 0; let maxValue = 0; // Responsive canvas p.windowResized = () => { const h = Math.max(300, window.innerHeight * 0.55); p.resizeCanvas(p.select('#sketch-holder').width, h); if (viewMode === 'forest') drawForest(); // redraw after resize }; p.setup = () => { const holderWidth = p.select('#sketch-holder').width; const h = Math.max(300, window.innerHeight * 0.55); p.createCanvas(holderWidth, h).parent('sketch-holder'); p.colorMode(p.HSL, 360, 100, 100, 1); p.background('#1f2937'); // gray-800 }; p.draw = () => { if (viewMode === 'sequence' && animating && stepIndex < seq.length - 1) { const current = seq[stepIndex]; const next = seq[stepIndex + 1]; // scaling const maxVal = Math.max(...seq); const x1 = p.map(stepIndex, 0, seq.length - 1, 40, p.width - 40); const x2 = p.map(stepIndex + 1, 0, seq.length - 1, 40, p.width - 40); const y1 = mapY(current, maxVal); const y2 = mapY(next, maxVal); // draw segment p.stroke(171, 100, 50); p.strokeWeight(2); p.line(x1, y1, x2, y2); p.noStroke(); p.fill(171, 100, 70); p.circle(x1, y1, 6); // operation label p.fill(210, 20, 80); const op = (current % 2 === 0) ? 'รท2' : 'ร3+1'; p.textSize(12); p.textAlign(p.CENTER, p.CENTER); p.text(op, (x1 + x2) / 2, (y1 + y2) / 2 - 12); stepIndex++; } else if (viewMode === 'sequence' && animating && stepIndex >= seq.length - 1) { // draw final point (1) const maxVal = Math.max(...seq); const x = p.map(stepIndex, 0, seq.length - 1, 40, p.width - 40); const y = mapY(seq[stepIndex], maxVal); p.fill(171, 100, 70); p.circle(x, y, 6); animating = false; } }; // ---------- Helpers ---------- function mapY(value, maxVal) { // log scaling for tall numbers const logVal = Math.log2(value); const logMax = Math.log2(maxVal); return p.map(logVal, 0, logMax, p.height - 40, 40); } // ---------- External API ---------- p.visualizeSequence = (n) => { // reset p.clear(); p.background('#1f2937'); seq = collatzSequence(n); stepIndex = 0; animating = true; // sequence text const arrowSeq = seq.join(' โ '); sequenceText.textContent = arrowSeq; }; p.visualizeForest = (N) => { // precompute forestData = []; maxSteps = 0; maxValue = 0; for (let i = 1; i <= N; i++) { const s = collatzSequence(i); forestData.push(s); if (s.length > maxSteps) maxSteps = s.length; const localMax = Math.max(...s); if (localMax > maxValue) maxValue = localMax; } drawForest(); }; function drawForest() { p.clear(); p.background('#1f2937'); forestData.forEach((path, idx) => { const hue = p.map(idx + 1, 1, forestData.length, 0, 300); // 0 โ 300ยฐ hue p.stroke(hue, 80, 60, 0.8); p.noFill(); p.strokeWeight(1.2); p.beginShape(); path.forEach((val, step) => { const x = p.map(step, 0, maxSteps - 1, 40, p.width - 40); let y; if (val === 1) { // pull all "1" nodes to bottom for nicer root shape y = p.height - 40; } else { y = mapY(val, maxValue); } p.vertex(x, y); }); p.endShape(); }); } }; } // ------------------ Sketch Lifecycle ------- function restartSketch() { if (p5Instance) p5Instance.remove(); p5Instance = new p5(createSketch()); } restartSketch(); // initial load // ------------------ Button Actions ---------- visualizeBtn.addEventListener('click', () => { clearError(); const val = parseInt(numberInput.value, 10); if (Number.isNaN(val) || val < 1) { showError('Please enter a positive integer (1, 2, 3 โฆ).'); return; } p5Instance.visualizeSequence(val); }); forestBtn.addEventListener('click', () => { clearError(); const N = parseInt(rangeSlider.value, 10); p5Instance.visualizeForest(N); sequenceText.textContent = ''; }); </script> </body> </html> ```
Below is the full HTML code for a responsive, dark-themed web application to visualize the Collatz Conjecture using Tailwind CSS for styling and p5.js for canvas rendering. The code includes all necessary `<script>` and `<link>` tags in a single block. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.2/lib/p5.js"></script> <style> html, body { margin: 0; padding: 0; height: 100%; background-color: #1f2937; color: #e5e7eb; font-family: 'Inter', sans-serif; } canvas { display: block; } .canvas-container { position: relative; width: 100%; height: 60vh; min-height: 300px; background-color: #111827; border-radius: 0.5rem; overflow: hidden; } @media (max-width: 640px) { .canvas-container { height: 50vh; } } </style> </head> <body class="flex flex-col min-h-screen"> <div class="container mx-auto px-4 py-6 flex-grow flex flex-col"> <h1 class="text-3xl md:text-4xl font-bold text-center mb-6 text-white">The Collatz Conjecture Visualizer</h1> <div class="bg-gray-800 p-6 rounded-lg shadow-md mb-6"> <h2 class="text-xl font-semibold mb-2">What is the Collatz Conjecture?</h2> <p class="text-gray-300"> Start with any positive integer <em>n</em>. If <em>n</em> is even, the next term is <em>n/2</em>. If <em>n</em> is odd, the next term is <em>3n + 1</em>. The conjecture states that no matter what value of <em>n</em> you start with, the sequence will always eventually reach 1. </p> </div> <div class="bg-gray-800 p-6 rounded-lg shadow-md mb-6"> <div class="flex items-center space-x-4 mb-4"> <button id="sequenceBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">Sequence View</button> <button id="forestBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg">Forest View</button> </div> <div id="sequenceInput" class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4"> <input type="number" id="numberInput" placeholder="Enter a positive integer" class="bg-gray-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 w-full md:w-64" min="1" step="1"> <button id="visualizeBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg">Visualize Sequence</button> </div> <div id="forestInput" class="hidden flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-4"> <label for="rangeSlider" class="text-gray-300">Visualize numbers from 1 to <span id="rangeValue">50</span></label> <input type="range" id="rangeSlider" min="10" max="200" value="50" class="w-full md:w-64"> <button id="visualizeForestBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg">Visualize Forest</button> </div> </div> <div class="canvas-container mb-6" id="canvasContainer"></div> <div class="bg-gray-800 p-6 rounded-lg shadow-md"> <h2 class="text-xl font-semibold mb-2">Sequence Output</h2> <p id="sequenceOutput" class="text-gray-300 break-words">Enter a number and click "Visualize Sequence" to see the results.</p> <p id="errorMessage" class="text-red-400 hidden">Please enter a valid positive integer greater than 0.</p> </div> </div> <script> let mode = 'sequence'; // 'sequence' or 'forest' let sketch = function(p) { let canvas; let sequences = []; let currentStep = 0; let animationSpeed = 10; let maxValue = 0; let canvasWidth, canvasHeight; let hueOffset = 0; p.setup = function() { canvas = p.createCanvas(100, 100); canvas.parent('canvasContainer'); p.frameRate(60); resizeCanvas(); }; function resizeCanvas() { const container = document.getElementById('canvasContainer'); canvasWidth = container.clientWidth; canvasHeight = container.clientHeight; p.resizeCanvas(canvasWidth, canvasHeight); } window.addEventListener('resize', resizeCanvas); p.draw = function() { p.background(17, 24, 39); if (sequences.length === 0) return; if (mode === 'sequence') { drawSequenceMode(); } else { drawForestMode(); } }; function drawSequenceMode() { if (currentStep < sequences[0].length) { for (let f = 0; f < animationSpeed; f++) { if (currentStep < sequences[0].length) { currentStep++; } } } let seq = sequences[0]; let stepSize = canvasWidth / Math.max(10, seq.length * 0.8); let yScale = canvasHeight / (maxValue * 1.2); let points = []; p.stroke(96, 165, 250); p.strokeWeight(2); p.textSize(14); p.fill(229, 231, 235); p.textAlign(p.CENTER, p.CENTER); for (let i = 0; i < currentStep; i++) { let x = stepSize * i + stepSize; let y = canvasHeight - (seq[i] * yScale) + 20; points.push({ x, y, value: seq[i] }); } for (let i = 1; i < points.length; i++) { p.line(points[i-1].x, points[i-1].y, points[i].x, points[i].y); p.ellipse(points[i].x, points[i].y, 6, 6); let midX = (points[i-1].x + points[i].x) / 2; let midY = (points[i-1].y + points[i].y) / 2; let operation = (seq[i-1] % 2 === 0) ? '/2' : 'x3+1'; if (seq[i-1] === seq[i] * 2) operation = '/2'; p.text(operation, midX, midY - 10); } if (points.length > 0) { p.ellipse(points[0].x, points[0].y, 6, 6); } for (let i = 0; i < points.length; i++) { p.text(points[i].value, points[i].x, points[i].y - 15); } } function drawForestMode() { let stepSizeBase = canvasWidth / Math.max(10, sequences[0].length * 0.8); let yScale = canvasHeight / (maxValue * 1.2); p.textSize(12); p.textAlign(p.CENTER, p.CENTER); p.noFill(); for (let s = 0; s < sequences.length; s++) { let seq = sequences[s]; let hue = (s * 137.5 + hueOffset) % 360; p.stroke(`hsb(${hue}, 80%, 80%)`); p.strokeWeight(1); let xPrev, yPrev; for (let i = 0; i < seq.length; i++) { let x = stepSizeBase * i + stepSizeBase; let y = canvasHeight - (seq[i] * yScale) + 20; if (i > 0) { p.line(xPrev, yPrev, x, y); } if (i === 0 || i === seq.length - 1) { p.ellipse(x, y, 4, 4); } if (s === 0 && i === 0) { p.fill(`hsb(${hue}, 80%, 80%)`); p.text(seq[i], x, y - 10); p.noFill(); } xPrev = x; yPrev = y; } } hueOffset += 1; } function computeSequence(start) { let sequence = [start]; let current = start; while (current !== 1) { if (current % 2 === 0) { current = current / 2; } else { current = 3 * current + 1; } sequence.push(current); if (current > maxValue) maxValue = current; } return sequence; } window.visualizeSequence = function(number) { if (!Number.isInteger(number) || number < 1) { document.getElementById('errorMessage').classList.remove('hidden'); document.getElementById('sequenceOutput').textContent = ''; return; } document.getElementById('errorMessage').classList.add('hidden'); maxValue = number; sequences = [computeSequence(number)]; currentStep = 1; document.getElementById('sequenceOutput').textContent = sequences[0].join(' โ '); }; window.visualizeForest = function(maxN) { sequences = []; maxValue = 0; for (let i = 1; i <= maxN; i++) { sequences.push(computeSequence(i)); } currentStep = sequences[0].length; document.getElementById('sequenceOutput').textContent = `Showing sequences from 1 to ${maxN}.`; }; }; new p5(sketch); document.getElementById('visualizeBtn').addEventListener('click', function() { let input = document.getElementById('numberInput').value; let number = parseInt(input); window.visualizeSequence(number); }); document.getElementById('numberInput').addEventListener('keypress', function(e) { if (e.key === 'Enter') { let input = document.getElementById('numberInput').value; let number = parseInt(input); window.visualizeSequence(number); } }); document.getElementById('visualizeForestBtn').addEventListener('click', function() { let maxN = parseInt(document.getElementById('rangeSlider').value); window.visualizeForest(maxN); }); document.getElementById('rangeSlider').addEventListener('input', function() { document.getElementById('rangeValue').textContent = this.value; }); document.getElementById('sequenceBtn').addEventListener('click', function() { mode = 'sequence'; document.getElementById('sequenceBtn').classList.add('bg-blue-600'); document.getElementById('sequenceBtn').classList.remove('bg-gray-600'); document.getElementById('forestBtn').classList.add('bg-gray-600'); document.getElementById('forestBtn').classList.remove('bg-blue-600'); document.getElementById('sequenceInput').classList.remove('hidden'); document.getElementById('forestInput').classList.add('hidden'); document.getElementById('sequenceOutput').textContent = 'Enter a number and click "Visualize Sequence" to see the results.'; }); document.getElementById('forestBtn').addEventListener('click', function() { mode = 'forest'; document.getElementById('forestBtn').classList.add('bg-blue-600'); document.getElementById('forestBtn').classList.remove('bg-gray-600'); document.getElementById('sequenceBtn').classList.add('bg-gray-600'); document.getElementById('sequenceBtn').classList.remove('bg-blue-600'); document.getElementById('sequenceInput').classList.add('hidden'); document.getElementById('forestInput').classList.remove('hidden'); document.getElementById('sequenceOutput').textContent = 'Adjust the slider and click "Visualize Forest" to see the results.'; }); </script> </body> </html> ``` This code creates a fully functional, dark-themed web application for visualizing the Collatz Conjecture. It uses Tailwind CSS for responsive styling and p5.js for canvas animations. The application supports both "Sequence View" (animating a single number's path with operations displayed) and "Forest View" (showing multiple sequences as a converging tree with color-coded paths). All interactions, error handling, and responsive design elements are included.
Of course. Here is a full-page, interactive web application to visualize the Collatz Conjecture, built with HTML, Tailwind CSS, and p5.js. The application features two distinct visualization modes: "Sequence View" for animating a single number's path, and "Forest View" for visualizing the convergence of multiple number sequences into a tree-like structure. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script> <style> /* Custom styles for the toggle switch and slider */ .toggle-switch { position: relative; display: inline-block; width: 120px; height: 34px; } .toggle-switch input { display: none; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #374151; transition: .4s; border-radius: 34px; } .slider:before { position: absolute; content: ""; height: 26px; width: 55px; /* Half width */ left: 4px; bottom: 4px; background-color: #10B981; /* Emerald 500 */ transition: .4s; border-radius: 34px; } input:checked + .slider:before { transform: translateX(57px); /* Move to the right */ } .slider-label { position: absolute; width: 50%; text-align: center; line-height: 34px; font-size: 12px; font-weight: bold; color: white; z-index: 1; pointer-events: none; } .slider-label-left { left: 0; } .slider-label-right { right: 0; } /* Custom styles for range input */ input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; height: 8px; background: #4B5563; /* Gray-600 */ border-radius: 5px; outline: none; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; background: #10B981; cursor: pointer; border-radius: 50%; } input[type="range"]::-moz-range-thumb { width: 20px; height: 20px; background: #10B981; cursor: pointer; border-radius: 50%; } </style> </head> <body class="bg-gray-900 text-gray-300 font-sans antialiased"> <div id="app" class="flex flex-col lg:flex-row min-h-screen"> <!-- Controls Panel --> <div id="controls" class="w-full lg:w-[380px] lg:flex-shrink-0 p-6 bg-gray-800 shadow-2xl space-y-6 overflow-y-auto"> <div class="text-center lg:text-left"> <h1 class="text-3xl font-bold text-white">Collatz Conjecture</h1> <h2 class="text-xl font-light text-emerald-400">The Visualizer</h2> </div> <p class="text-sm text-gray-400 border-l-2 border-emerald-500 pl-3"> Start with a positive integer <span class="font-bold text-white">n</span>. If <span class="font-bold text-white">n</span> is even, divide it by 2. If it's odd, multiply by 3 and add 1. Repeat the process. The conjecture states this sequence will always eventually reach 1. </p> <!-- View Toggle --> <div class="flex items-center justify-between"> <span class="font-semibold text-white">View Mode:</span> <label class="toggle-switch"> <span class="slider-label slider-label-left">Sequence</span> <span class="slider-label slider-label-right">Forest</span> <input type="checkbox" id="viewToggle"> <span class="slider"></span> </label> </div> <!-- Sequence View UI --> <div id="sequence-controls" class="space-y-4"> <label for="numberInput" class="block font-medium text-white">Enter a positive integer:</label> <input type="number" id="numberInput" min="1" placeholder="e.g., 27" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-emerald-500"> <button id="visualizeBtn" class="w-full bg-emerald-600 text-white font-bold py-2 px-4 rounded-md hover:bg-emerald-700 transition duration-300 ease-in-out disabled:bg-gray-500 disabled:cursor-not-allowed"> Visualize Sequence </button> <div id="errorMessage" class="text-red-400 text-sm h-4"></div> </div> <!-- Forest View UI (Initially Hidden) --> <div id="forest-controls" class="space-y-4 hidden"> <label for="rangeSlider" class="block font-medium text-white">Visualize up to number:</label> <div class="flex items-center space-x-4"> <input type="range" id="rangeSlider" min="2" max="2000" value="100" class="flex-grow"> <span id="rangeValue" class="font-mono text-lg text-emerald-400 w-16 text-center bg-gray-700 py-1 rounded-md">100</span> </div> <div id="forestMessage" class="text-amber-400 text-sm h-4">Rendering may be slow for >1000 paths.</div> </div> <!-- Sequence Output --> <div class="space-y-2 pt-4"> <h3 class="font-semibold text-white">Sequence Path:</h3> <div id="sequenceOutput" class="p-3 bg-gray-900 rounded-md text-sm text-gray-400 h-32 overflow-y-auto break-words"> Enter a number to see its path... </div> </div> </div> <!-- Canvas Area --> <main class="flex-grow p-4 flex items-center justify-center bg-gray-900"> <div id="sketch-holder" class="w-full h-full max-w-full max-h-full aspect-video border border-gray-700 rounded-lg shadow-inner"> <!-- p5.js canvas will be injected here --> </div> </main> </div> <script> document.addEventListener('DOMContentLoaded', () => { // --- STATE MANAGEMENT --- let appState = { view: 'sequence', // 'sequence' or 'forest' p5Instance: null, isAnimating: false, isRenderingForest: false, }; let sequenceData = { path: [], animationIndex: 0, animationProgress: 0, maxValue: 0, }; let forestData = { paths: [], maxVal: 0, maxLen: 0, }; // --- DOM ELEMENTS --- const DOMElements = { viewToggle: document.getElementById('viewToggle'), sequenceControls: document.getElementById('sequence-controls'), forestControls: document.getElementById('forest-controls'), numberInput: document.getElementById('numberInput'), visualizeBtn: document.getElementById('visualizeBtn'), errorMessage: document.getElementById('errorMessage'), rangeSlider: document.getElementById('rangeSlider'), rangeValue: document.getElementById('rangeValue'), sequenceOutput: document.getElementById('sequenceOutput'), sketchHolder: document.getElementById('sketch-holder'), }; // --- COLLATZ LOGIC --- function getCollatzSequence(n) { if (n <= 0 || !Number.isInteger(n)) return []; const seq = [n]; while (n > 1) { if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } seq.push(n); } return seq; } // --- P5.JS SKETCH --- const sketch = (p) => { let canvas; const HORIZONTAL_STEP_FOREST = 5; p.setup = () => { const holderRect = DOMElements.sketchHolder.getBoundingClientRect(); canvas = p.createCanvas(holderRect.width, holderRect.height); canvas.parent('sketch-holder'); p.colorMode(p.HSB, 360, 100, 100, 100); p.textFont('monospace'); p.noLoop(); window.addEventListener('resize', () => p.windowResized()); drawWelcomeMessage(); }; p.windowResized = () => { const holderRect = DOMElements.sketchHolder.getBoundingClientRect(); p.resizeCanvas(holderRect.width, holderRect.height); p.redraw(); }; p.draw = () => { p.background(230, 15, 10); // Dark background: HSB(230, 15, 10) -> approx. bg-gray-900 if (appState.view === 'sequence') { if (sequenceData.path.length > 0) { drawAnimatedSequence(); } else { drawWelcomeMessage(); } } else { // 'forest' view drawStaticForest(); } }; function drawWelcomeMessage() { p.fill(200, 5, 80); p.noStroke(); p.textAlign(p.CENTER, p.CENTER); p.textSize(18); p.text('Visualization appears here', p.width / 2, p.height / 2); } // --- SEQUENCE VIEW DRAWING --- function drawAnimatedSequence() { p.strokeWeight(2); const path = sequenceData.path; const xStep = p.width / (path.length + 1); const yMargin = p.height * 0.1; // Draw completed segments for (let i = 0; i < sequenceData.animationIndex; i++) { drawSegment(i, path, xStep, yMargin, 1.0); } // Draw animating segment if (sequenceData.animationIndex < path.length - 1) { drawSegment(sequenceData.animationIndex, path, xStep, yMargin, sequenceData.animationProgress); // Update animation state sequenceData.animationProgress += 0.05; if (sequenceData.animationProgress >= 1.0) { sequenceData.animationProgress = 0; sequenceData.animationIndex++; updateSequenceTextOutput(); } } else { // Animation finished appState.isAnimating = false; DOMElements.visualizeBtn.disabled = false; p.noLoop(); } } function drawSegment(i, path, xStep, yMargin, progress) { const val1 = path[i]; const val2 = path[i+1]; const x1 = xStep * (i + 1); const y1 = p.map(Math.log(val1), 0, Math.log(sequenceData.maxValue), p.height - yMargin, yMargin); const x2_full = xStep * (i + 2); const y2_full = p.map(Math.log(val2), 0, Math.log(sequenceData.maxValue), p.height - yMargin, yMargin); const x2 = p.lerp(x1, x2_full, progress); const y2 = p.lerp(y1, y2_full, progress); // Line p.stroke(160, 80, 90); // Emerald color p. line(x1, y1, x2, y2); // Point 1 p.noStroke(); p.fill(160, 80, 90); p.ellipse(x1, y1, 8, 8); // Text for Point 1 p.fill(0, 0, 90); // White p.textAlign(p.CENTER, p.BOTTOM); p.textSize(12); p.text(val1, x1, y1 - 8); // Text for operation if (progress > 0.5) { const operation = val1 % 2 === 0 ? '/ 2' : 'x3 + 1'; p.fill(0, 0, 60); // Gray p.textAlign(p.CENTER, p.CENTER); p.text(operation, (x1 + x2_full) / 2, (y1 + y2_full) / 2 - 10); } // Final point of sequence if (i === path.length - 2 && progress >= 1.0) { p.noStroke(); p.fill(160, 80, 90); p.ellipse(x2_full, y2_full, 8, 8); p.fill(0, 0, 90); p.textAlign(p.CENTER, p.BOTTOM); p.text(val2, x2_full, y2_full - 8); } } function updateSequenceTextOutput() { const displayedPath = sequenceData.path.slice(0, sequenceData.animationIndex + 1); DOMElements.sequenceOutput.textContent = displayedPath.join(' โ '); } // --- FOREST VIEW DRAWING --- function drawStaticForest() { if (forestData.paths.length === 0) return; p.strokeWeight(1.5); const yMargin = 20; forestData.paths.forEach(pathData => { p.stroke(pathData.hue, 80, 95, 50); // Semi-transparent stroke p.noFill(); p.beginShape(); let current_x = p.width / 2; pathData.path.forEach((val, i) => { const y = p.map(Math.log(val), 0, Math.log(forestData.maxVal), p.height - yMargin, yMargin); p.vertex(current_x, y); // Determine next x step if (i < pathData.path.length - 1) { const next_val = pathData.path[i+1]; if (val % 2 === 0) { // from even current_x -= HORIZONTAL_STEP_FOREST * (Math.log(i + 2)); } else { // from odd current_x += HORIZONTAL_STEP_FOREST * (Math.log(i + 2)); } } }); p.endShape(); }); appState.isRenderingForest = false; } // --- PUBLIC METHODS FOR EVENT HANDLERS --- p.visualizeSequence = (path) => { sequenceData.path = path; sequenceData.animationIndex = 0; sequenceData.animationProgress = 0; sequenceData.maxValue = Math.max(...path); DOMElements.sequenceOutput.textContent = 'Animating...'; appState.isAnimating = true; p.loop(); }; p.visualizeForest = (paths, maxVal) => { forestData.paths = paths.map((path, i) => ({ path, hue: p.map(path[0], 1, paths.length, 0, 360) % 360 })); forestData.maxVal = maxVal; p.redraw(); }; p.clearCanvas = () => { sequenceData.path = []; forestData.paths = []; p.redraw(); }; }; // --- EVENT HANDLERS --- function handleViewToggle() { if (appState.isAnimating) return; // Prevent toggle during animation appState.view = DOMElements.viewToggle.checked ? 'forest' : 'sequence'; appState.p5Instance.clearCanvas(); DOMElements.sequenceOutput.textContent = '...'; if (appState.view === 'sequence') { DOMElements.sequenceControls.classList.remove('hidden'); DOMElements.forestControls.classList.add('hidden'); DOMElements.sequenceOutput.textContent = 'Enter a number to see its path...'; } else { DOMElements.sequenceControls.classList.add('hidden'); DOMElements.forestControls.classList.remove('hidden'); DOMElements.sequenceOutput.textContent = 'Adjust slider to visualize multiple paths.'; handleRangeChange(); // Trigger initial forest render } } function handleVisualizeClick() { if (appState.isAnimating) return; DOMElements.errorMessage.textContent = ''; const num = parseInt(DOMElements.numberInput.value, 10); if (isNaN(num) || num < 1 || !Number.isInteger(parseFloat(DOMElements.numberInput.value))) { DOMElements.errorMessage.textContent = 'Please enter a positive whole number.'; return; } DOMElements.visualizeBtn.disabled = true; const path = getCollatzSequence(num); appState.p5Instance.visualizeSequence(path); } let debounceTimer; function handleRangeChange() { if (appState.isRenderingForest) return; DOMElements.rangeValue.textContent = DOMElements.rangeSlider.value; clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { appState.isRenderingForest = true; const N = parseInt(DOMElements.rangeSlider.value, 10); DOMElements.sequenceOutput.textContent = `Calculating paths for 1 to ${N}...`; // Use setTimeout to avoid freezing the UI during calculation setTimeout(() => { let allPaths = []; let globalMax = 0; for (let i = 1; i <= N; i++) { const path = getCollatzSequence(i); allPaths.push(path); const localMax = Math.max(...path); if (localMax > globalMax) globalMax = localMax; } DOMElements.sequenceOutput.textContent = `Visualizing ${N} paths. All converge to 1.`; appState.p5Instance.visualizeForest(allPaths, globalMax); }, 50); }, 150); // Debounce to prevent rapid-fire calculations } // --- INITIALIZATION --- DOMElements.viewToggle.addEventListener('change', handleViewToggle); DOMElements.visualizeBtn.addEventListener('click', handleVisualizeClick); DOMElements.numberInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleVisualizeClick(); }); DOMElements.rangeSlider.addEventListener('input', handleRangeChange); appState.p5Instance = new p5(sketch); }); </script> </body> </html> ```
Here is a full-page, responsive, and visually appealing interactive web application to visualize the Collatz Conjecture using HTML, Tailwind CSS, and JavaScript with p5.js: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>The Collatz Conjecture Visualizer</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <style> body { background-color: #111827; color: #f3f4f6; } .canvas-container { width: 100%; height: 600px; } #error-message { color: #ef4444; } </style> </head> <body class="bg-gray-900 text-gray-100"> <div class="container mx-auto px-4 py-8"> <h1 class="text-4xl font-bold text-center mb-8">The Collatz Conjecture Visualizer</h1> <div class="bg-gray-800 p-6 rounded-lg mb-8"> <p class="text-lg mb-4"> The Collatz Conjecture is a mathematical conjecture concerning a sequence defined as follows: start with any positive integer n. Then each term is obtained from the previous term as follows: if the previous term is even, the next term is one half of the previous term. If the previous term is odd, the next term is 3 times the previous term plus 1. The conjecture states that this process will eventually reach the number 1, regardless of which positive integer is chosen initially. </p> </div> <div class="bg-gray-800 p-6 rounded-lg mb-8"> <div class="mb-4"> <label for="number-input" class="block text-sm font-medium mb-2">Enter a positive integer:</label> <input type="number" id="number-input" class="w-full p-2 border border-gray-600 rounded bg-gray-700 text-white" min="1"> <p id="error-message" class="text-red-500 text-sm mt-1 hidden"></p> </div> <div class="mb-4"> <button id="visualize-button" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Visualize Sequence </button> </div> <div class="mb-4"> <label for="view-toggle" class="block text-sm font-medium mb-2">View Mode:</label> <select id="view-toggle" class="w-full p-2 border border-gray-600 rounded bg-gray-700 text-white"> <option value="sequence">Sequence View</option> <option value="forest">Forest View</option> </select> </div> <div id="forest-controls" class="mb-4 hidden"> <label for="forest-range" class="block text-sm font-medium mb-2">Visualize numbers from 1 to:</label> <input type="range" id="forest-range" min="1" max="200" value="50" class="w-full"> <span id="forest-range-value" class="block text-center">50</span> </div> </div> <div class="canvas-container bg-gray-800 rounded-lg mb-8"> <div id="canvas"></div> </div> <div class="bg-gray-800 p-6 rounded-lg"> <h2 class="text-xl font-bold mb-4">Sequence</h2> <p id="sequence-text" class="text-lg"></p> </div> </div> <script> let sketch = function(p) { let sequence = []; let currentView = 'sequence'; let forestRange = 50; p.setup = function() { let canvas = p.createCanvas(p.windowWidth * 0.9, 600); canvas.parent('canvas'); p.background(17, 24, 39); }; p.draw = function() { if (currentView === 'sequence') { drawSequenceView(); } else { drawForestView(); } }; p.windowResized = function() { p.resizeCanvas(p.windowWidth * 0.9, 600); p.background(17, 24, 39); }; function drawSequenceView() { if (sequence.length === 0) return; p.background(17, 24, 39); p.stroke(255); p.noFill(); p.strokeWeight(2); let padding = 50; let xSpacing = p.width / (sequence.length - 1); let maxValue = p.max(sequence); let yScale = (p.height - padding) / maxValue; p.beginShape(); for (let i = 0; i < sequence.length; i++) { let x = i * xSpacing; let y = p.height - (sequence[i] * yScale) - padding/2; p.vertex(x, y); let operation = ''; if (i < sequence.length - 1) { if (sequence[i] % 2 === 0) { operation = '/ 2'; } else { operation = '* 3 + 1'; } p.text(operation, (x + (i+1)*xSpacing)/2, (p.height - (sequence[i+1] * yScale) - padding/2 + y)/2); } p.ellipse(x, y, 10, 10); } p.endShape(); } function drawForestView() { p.background(17, 24, 39); p.stroke(255); p.noFill(); p.strokeWeight(1); for (let n = 1; n <= forestRange; n++) { let sequence = generateSequence(n); let padding = 50; let xSpacing = p.width / (sequence.length - 1); let maxValue = p.max(sequence); let yScale = (p.height - padding) / maxValue; p.stroke(p.map(n, 1, forestRange, 0, 255), p.map(n, 1, forestRange, 255, 0), p.map(n, 1, forestRange, 255, 0)); p.beginShape(); for (let i = 0; i < sequence.length; i++) { let x = i * xSpacing; let y = p.height - (sequence[i] * yScale) - padding/2; p.vertex(x, y); } p.endShape(); } } function generateSequence(n) { let sequence = [n]; while (n !== 1) { if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } sequence.push(n); } return sequence; } document.getElementById('visualize-button').addEventListener('click', function() { let input = document.getElementById('number-input'); let errorMessage = document.getElementById('error-message'); let number = parseInt(input.value); if (isNaN(number) || number < 1) { errorMessage.textContent = 'Please enter a valid positive integer.'; errorMessage.classList.remove('hidden'); return; } else { errorMessage.classList.add('hidden'); } sequence = generateSequence(number); document.getElementById('sequence-text').textContent = sequence.join(' โ '); p.redraw(); }); document.getElementById('view-toggle').addEventListener('change', function() { currentView = this.value; if (currentView === 'forest') { document.getElementById('forest-controls').classList.remove('hidden'); document.getElementById('number-input').classList.add('hidden'); } else { document.getElementById('forest-controls').classList.add('hidden'); document.getElementById('number-input').classList.remove('hidden'); } p.redraw(); }); document.getElementById('forest-range').addEventListener('input', function() { forestRange = this.value; document.getElementById('forest-range-value').textContent = forestRange; p.redraw(); }); }; let myp5 = new p5(sketch); </script> </body> </html> ``` This code creates a responsive web application with a dark theme that visualizes the Collatz Conjecture. It includes an input field for entering a number, a button to visualize the sequence, a toggle switch to change between "Sequence View" and "Forest View", and a canvas area for rendering the visualization. The application uses p5.js for drawing and Tailwind CSS for styling. The "Sequence View" shows the path of a single number, while the "Forest View" shows the paths of multiple numbers simultaneously.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <style> body { background-color: #1a1a1a; color: #ffffff; } canvas { border: 1px solid #ffffff; } </style> </head> <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-start p-4"> <header class="w-full max-w-4xl text-center mb-8"> <h1 class="text-4xl font-bold mb-4">The Collatz Conjecture Visualizer</h1> <p class="text-lg mb-4"> The Collatz Conjecture states that for any positive integer n, repeatedly apply: if n is even, divide by 2; if odd, multiply by 3 and add 1. The sequence will always reach 1. </p> </header> <main class="w-full max-w-4xl flex flex-col items-center"> <!-- Toggle Switch --> <div class="mb-6 flex items-center"> <span class="mr-4">Sequence View</span> <label class="relative inline-flex items-center cursor-pointer"> <input type="checkbox" id="view-toggle" class="sr-only peer"> <div class="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div> </label> <span class="ml-4">Forest View</span> </div> <!-- Input Area --> <div id="input-area" class="mb-6 w-full max-w-md flex flex-col items-center"> <!-- Sequence View Input --> <div id="sequence-input" class="flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-4"> <input type="number" id="number-input" placeholder="Enter a positive integer" class="bg-gray-800 text-white px-4 py-2 rounded-md border border-gray-600 focus:outline-none focus:border-blue-500" min="1"> <button id="visualize-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md">Visualize Sequence</button> </div> <!-- Forest View Input --> <div id="forest-input" class="hidden flex flex-col items-center space-y-4"> <label for="range-slider" class="text-lg">Visualize numbers from 1 to N:</label> <input type="range" id="range-slider" min="1" max="500" value="100" class="w-full max-w-md"> <span id="range-value" class="text-lg">100</span> <button id="forest-visualize-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md">Visualize Forest</button> </div> </div> <!-- Error Message --> <div id="error-msg" class="hidden text-red-500 mb-4"></div> <!-- Canvas Container --> <div id="canvas-container" class="w-full h-96 mb-6"></div> <!-- Sequence Text --> <div id="sequence-text" class="w-full max-w-4xl text-center overflow-x-auto whitespace-pre-wrap bg-gray-800 p-4 rounded-md"></div> </main> <script> let sketch; let isForestView = false; let sequence = []; let operations = []; let forestSequences = []; let animationIndex = 0; let maxY = 0; function calculateCollatz(n) { const seq = [n]; const ops = []; while (n !== 1) { if (n % 2 === 0) { n = n / 2; ops.push('/2'); } else { n = 3 * n + 1; ops.push('*3+1'); } seq.push(n); } return { seq, ops }; } function setup() { const canvas = createCanvas(800, 400); canvas.parent('canvas-container'); background(30); } function drawSequenceView() { background(30); if (sequence.length === 0) return; const stepWidth = width / (sequence.length - 1); let prevX = 0; let prevY = map(sequence[0], 0, maxY, height, 0); for (let i = 1; i <= animationIndex; i++) { const x = i * stepWidth; const y = map(sequence[i], 0, maxY, height, 0); stroke(255); line(prevX, prevY, x, y); fill(255); ellipse(x, y, 5, 5); textAlign(CENTER); textSize(12); text(operations[i-1], (prevX + x)/2, (prevY + y)/2 - 10); prevX = x; prevY = y; } if (animationIndex < sequence.length - 1) { animationIndex++; } } function drawForestView() { background(30); if (forestSequences.length === 0) return; // Simple tree layout: x based on step, y based on value, but reversed for tree feel const stepWidth = width / 50; // Arbitrary, adjust for density forestSequences.forEach((seq, index) => { const hue = map(index, 0, forestSequences.length, 0, 360); stroke(color(hue, 100, 100, 50)); // Semi-transparent noFill(); let prevX = map(seq[0], 1, forestSequences.length, 0, width); // Start x based on number let prevY = height; // Start at bottom for (let i = 1; i < seq.length; i++) { let x = map(seq[i], 0, maxY, width / 2, width / 2) + (width / 2 - prevX) * 0.1; // Converge to center let y = map(i, 0, seq.length, height, 0); line(prevX, prevY, x, y); prevX = x; prevY = y; } }); } function draw() { if (isForestView) { drawForestView(); } else { drawSequenceView(); } } document.addEventListener('DOMContentLoaded', () => { const toggle = document.getElementById('view-toggle'); const sequenceInput = document.getElementById('sequence-input'); const forestInput = document.getElementById('forest-input'); const visualizeBtn = document.getElementById('visualize-btn'); const forestVisualizeBtn = document.getElementById('forest-visualize-btn'); const numberInput = document.getElementById('number-input'); const rangeSlider = document.getElementById('range-slider'); const rangeValue = document.getElementById('range-value'); const errorMsg = document.getElementById('error-msg'); const sequenceText = document.getElementById('sequence-text'); toggle.addEventListener('change', (e) => { isForestView = e.target.checked; sequence = []; forestSequences = []; animationIndex = 0; sequenceText.innerHTML = ''; background(30); if (isForestView) { sequenceInput.classList.add('hidden'); forestInput.classList.remove('hidden'); } else { sequenceInput.classList.remove('hidden'); forestInput.classList.add('hidden'); } }); visualizeBtn.addEventListener('click', () => { const n = parseInt(numberInput.value); if (isNaN(n) || n < 1) { errorMsg.textContent = 'Please enter a positive integer greater than or equal to 1.'; errorMsg.classList.remove('hidden'); return; } errorMsg.classList.add('hidden'); const { seq, ops } = calculateCollatz(n); sequence = seq; operations = ops; maxY = Math.max(...seq); animationIndex = 0; sequenceText.innerHTML = seq.join(' โ '); }); rangeSlider.addEventListener('input', (e) => { rangeValue.textContent = e.target.value; }); forestVisualizeBtn.addEventListener('click', () => { const N = parseInt(rangeSlider.value); forestSequences = []; maxY = 0; for (let i = 1; i <= N; i++) { const { seq } = calculateCollatz(i); forestSequences.push(seq); maxY = Math.max(maxY, ...seq); } sequenceText.innerHTML = `Showing paths for 1 to ${N}`; }); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>The Collatz Conjecture Visualizer</title> <!-- Tailwind CSS --> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <!-- p5.js --> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script> <style> /* Toggle Switch Styles */ .switch { position: relative; display: inline-block; width: 50px; height: 24px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #555; transition: .4s; border-radius: 24px; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #4f46e5; } input:checked + .slider:before { transform: translateX(26px); } </style> </head> <body class="bg-gray-900 text-white min-h-screen flex flex-col"> <div class="container mx-auto p-4 flex-grow"> <h1 class="text-4xl font-bold text-center mb-4">The Collatz Conjecture Visualizer</h1> <p class="text-center mb-6 max-w-2xl mx-auto"> Start with any positive integer n. If n is even, the next term is n/2. If n is odd, the next term is 3n + 1. The conjecture states that no matter what value of n you start with, the sequence will always eventually reach 1. </p> <!-- Controls --> <div id="controls" class="flex flex-col sm:flex-row items-center justify-center mb-6 space-y-4 sm:space-y-0 sm:space-x-4"> <!-- Sequence View Controls --> <div id="sequence-controls" class="flex items-center space-x-2"> <input id="numberInput" type="number" min="1" placeholder="Enter n" class="bg-gray-800 text-white p-2 rounded w-32" /> </div> <!-- Forest View Controls --> <div id="forest-controls" class="hidden items-center space-x-2"> <label for="rangeSlider">1 to <span id="rangeValue">50</span>:</label> <input id="rangeSlider" type="range" min="1" max="200" value="50" class="accent-blue-500" /> </div> <button id="visualizeBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded"> Visualize Sequence </button> <div class="ml-4 flex items-center space-x-2"> <span>Sequence View</span> <label class="switch"> <input type="checkbox" id="viewToggle" /> <span class="slider"></span> </label> <span>Forest View</span> </div> </div> <!-- Canvas Container --> <div id="canvas-container" class="border border-gray-700 rounded overflow-hidden mb-6"></div> <!-- Sequence Output --> <div id="sequenceOutput" class="mt-4 p-4 bg-gray-800 rounded overflow-auto max-h-40"></div> </div> <script> // Application state let mode = 'sequence'; let seq = [], animIndex = 0, animating = false; let sequences = [], maxForest = 50; // p5.js sketch const sketch = (p) => { let canvas; p.setup = () => { canvas = p.createCanvas(window.innerWidth * 0.95, window.innerHeight * 0.6); canvas.parent('canvas-container'); p.background(20); p.textSize(12); p.strokeWeight(2); }; p.windowResized = () => { p.resizeCanvas(window.innerWidth * 0.95, window.innerHeight * 0.6); p.background(20); if (mode === 'forest' && sequences.length) drawForest(); }; p.draw = () => { if (mode === 'sequence' && animating) { if (animIndex < seq.length) { const val = seq[animIndex]; const maxVal = Math.max(...seq); const x = p.map(animIndex, 0, seq.length - 1, 50, p.width - 50); const y = p.map(val, 1, maxVal, p.height - 50, 50); p.stroke(255); p.fill(255); p.ellipse(x, y, 4); if (animIndex > 0) { const prev = seq[animIndex - 1]; const x0 = p.map(animIndex - 1, 0, seq.length - 1, 50, p.width - 50); const y0 = p.map(prev, 1, maxVal, p.height - 50, 50); p.stroke(180); p.line(x0, y0, x, y); // operation label const op = (prev % 2 === 0) ? '/2' : '*3+1'; p.noStroke(); p.fill(200); p.text(op, (x0 + x) / 2 + 5, (y0 + y) / 2 - 5); } animIndex++; } else { animating = false; } } }; function drawForest() { p.background(20); // Color scale for (let i = 0; i < sequences.length; i++) { const arr = sequences[i]; const hue = p.map(i, 0, sequences.length - 1, 0, 360); p.stroke(p.color(`hsl(${hue}, 100%, 50%)`)); p.noFill(); p.beginShape(); const globalMax = Math.max(...sequences.flat()); for (let j = 0; j < arr.length; j++) { const x = p.map(j, 0, arr.length - 1, 50, p.width - 50); const y = p.map(arr[j], 1, globalMax, p.height - 50, 50); p.vertex(x, y); } p.endShape(); } } // Expose to global window.startSequence = (sequence) => { seq = sequence; animIndex = 0; animating = true; p.background(20); }; window.startForest = (seqs) => { sequences = seqs; mode = 'forest'; drawForest(); }; }; new p5(sketch); // UI logic const seqControls = document.getElementById('sequence-controls'); const forestControls = document.getElementById('forest-controls'); const viewToggle = document.getElementById('viewToggle'); const visualizeBtn = document.getElementById('visualizeBtn'); const rangeSlider = document.getElementById('rangeSlider'); const rangeValue = document.getElementById('rangeValue'); const output = document.getElementById('sequenceOutput'); viewToggle.addEventListener('change', () => { mode = viewToggle.checked ? 'forest' : 'sequence'; seqControls.classList.toggle('hidden', mode !== 'sequence'); forestControls.classList.toggle('hidden', mode !== 'forest'); visualizeBtn.textContent = mode === 'sequence' ? 'Visualize Sequence' : 'Visualize Forest'; output.textContent = ''; }); rangeSlider.addEventListener('input', () => { maxForest = +rangeSlider.value; rangeValue.textContent = maxForest; }); visualizeBtn.addEventListener('click', () => { if (mode === 'sequence') { const n = parseInt(document.getElementById('numberInput').value); if (!n || n < 1) { alert('Please enter a positive integer.'); return; } // compute single sequence let x = n, s = [x]; while (x !== 1) { x = (x % 2 === 0) ? x / 2 : 3 * x + 1; s.push(x); } output.textContent = s.join(' โ '); window.startSequence(s); } else { // forest const N = maxForest; const all = []; for (let i = 1; i <= N; i++) { let x = i, arr = [x]; while (x !== 1) { x = (x % 2 === 0) ? x / 2 : 3 * x + 1; arr.push(x); } all.push(arr); } output.textContent = ''; window.startForest(all); } }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <script src="https://cdn.jsdelivr.net/npm/p5@1.9.0/lib/p5.js"></script> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> </head> <body class="bg-gray-900 text-white"> <div class="container mx-auto p-4"> <h1 class="text-3xl font-bold mb-4">The Collatz Conjecture Visualizer</h1> <p class="mb-4"> The Collatz Conjecture states that for any positive integer, repeatedly applying the following rules will eventually lead to the number 1: <br> - If the number is even, divide it by 2. - If the number is odd, multiply it by 3 and add 1. </p> <div class="mb-4"> <label for="numberInput" class="block text-sm font-medium text-gray-300">Enter a positive integer:</label> <input type="number" id="numberInput" class="mt-1 block w-full rounded-md bg-gray-800 text-white border-gray-700 focus:ring-blue-500 focus:border-blue-500" placeholder="Enter a number"> <button id="visualizeButton" class="mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Visualize Sequence</button> <button id="toggleViewButton" class="mt-2 bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">Switch to Forest View</button> </div> <div id="sequenceView" class="mb-4"> <canvas id="collatzCanvas" width="800" height="600"></canvas> <div id="sequenceText" class="mt-2 text-sm"></div> </div> <div id="forestView" class="hidden"> <label for="rangeSlider" class="block text-sm font-medium text-gray-300">Visualize numbers from 1 to:</label> <input type="range" id="rangeSlider" min="1" max="500" value="100" class="mt-1 block w-full"> <span id="rangeValue" class="text-sm">100</span> <button id="visualizeForestButton" class="mt-2 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded">Visualize Forest</button> <canvas id="forestCanvas" width="800" height="600"></canvas> </div> </div> <script> const numberInput = document.getElementById('numberInput'); const visualizeButton = document.getElementById('visualizeButton'); const toggleViewButton = document.getElementById('toggleViewButton'); const collatzCanvas = document.getElementById('collatzCanvas'); const sequenceText = document.getElementById('sequenceText'); const sequenceView = document.getElementById('sequenceView'); const forestView = document.getElementById('forestView'); const rangeSlider = document.getElementById('rangeSlider'); const rangeValue = document.getElementById('rangeValue'); const visualizeForestButton = document.getElementById('visualizeForestButton'); const forestCanvas = document.getElementById('forestCanvas'); let currentView = 'sequence'; visualizeButton.addEventListener('click', visualizeSequence); toggleViewButton.addEventListener('click', toggleView); rangeSlider.addEventListener('input', updateRangeValue); visualizeForestButton.addEventListener('click', visualizeForest); function visualizeSequence() { const num = parseInt(numberInput.value); if (isNaN(num) || num < 1) { sequenceText.textContent = 'Invalid input. Please enter a positive integer.'; return; } let sequence = []; let n = num; while (n !== 1) { sequence.push(n); if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } } sequence.push(1); sequenceText.textContent = sequence.join(' โ '); p5.disableFriendlyErrors(); // Suppress p5.js errors in the console let p = new p5(sketch => { sketch.setup = () => { sketch.createCanvas(800, 600); sketch.background(50); sketch.noLoop(); // Draw once }; let x = 50; let yOffset = 600; let yScale = 0.1; sketch.draw = () => { sketch.stroke(255); for (let i = 0; i < sequence.length - 1; i++) { let y1 = yOffset - sequence[i] * yScale; let y2 = yOffset - sequence[i + 1] * yScale; sketch.line(x, y1, x + 20, y2); let operation = (sequence[i] % 2 === 0) ? ' / 2' : ' * 3 + 1'; sketch.textSize(10); sketch.fill(255); sketch.text(operation, x + 5, (y1 + y2) / 2); x += 20; } // Draw the final point sketch.ellipse(x, yOffset - sequence[sequence.length-1] * yScale, 10, 10); }; }); } function toggleView() { if (currentView === 'sequence') { sequenceView.classList.add('hidden'); forestView.classList.remove('hidden'); currentView = 'forest'; } else { forestView.classList.add('hidden'); sequenceView.classList.remove('hidden'); currentView = 'sequence'; } } function updateRangeValue() { rangeValue.textContent = rangeSlider.value; } function visualizeForest() { const maxNum = parseInt(rangeSlider.value); p5.disableFriendlyErrors(); let p = new p5(sketch => { sketch.setup = () => { sketch.createCanvas(800, 600); sketch.background(50); sketch.noLoop(); } sketch.draw = () => { const numColors = 20; const colorStep = 255 / numColors; for (let startNum = 1; startNum <= maxNum; startNum++) { let sequence = []; let n = startNum; while (n !== 1) { sequence.push(n); if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } } sequence.push(1); let x = 50; let yOffset = 600; let yScale = 0.1; let colorIndex = startNum % numColors; sketch.stroke(255, 255, 0); //Yellow for now sketch.fill(255, 255, 0, 50); // Yellow with transparency for (let i = 0; i < sequence.length - 1; i++) { let y1 = yOffset - sequence[i] * yScale; let y2 = yOffset - sequence[i + 1] * yScale; sketch.line(x, y1, x + 2, y2); x += 2; } } }; }); } </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en" class="bg-gray-950 text-gray-100"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>The Collatz Conjecture Visualizer</title> <!-- Tailwind CSS CDN --> <script src="https://cdn.tailwindcss.com"></script> <!-- p5.js CDN --> <script src="https://cdn.jsdelivr.net/npm/p5@1.9.0/lib/p5.min.js"></script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap" rel="stylesheet"> <style> body { font-family: 'Fira Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; } .collatz-btn { background-image: linear-gradient(90deg, #38bdf8, #a21caf); transition: box-shadow 0.2s; } .collatz-btn:active { box-shadow: 0 0 0 3px #a21caf88; } #error-message { min-height: 2rem; } .sequence-span { color: #38bdf8; margin-right: 0.25em; } .operation-span { font-size: 0.85em; margin-left: 0.25em; color: #a78bfa; } .collatz-scroll { overflow-x: auto; scrollbar-color: #a21caf #18181b; scrollbar-width: thin; padding-bottom: 0.25em; } </style> </head> <body class="min-h-screen flex flex-col bg-gray-950"> <div class="flex flex-col items-center w-full px-2 pb-8 min-h-screen"> <header class="pt-8 pb-2 w-full max-w-3xl flex flex-col items-center"> <h1 class="text-4xl sm:text-5xl font-bold mb-2 text-cyan-400 drop-shadow">The Collatz Conjecture Visualizer</h1> <p class="text-gray-300 mt-2 text-center max-w-2xl text-lg"> The Collatz Conjecture: Start with any positive integer <b>n</b>. If <b>n</b> is even, next is <b>n/2</b>. If <b>n</b> is odd, next is <b>3n + 1</b>. The conjecture says every sequence eventually reaches 1. </p> </header> <!-- Controls Panel --> <section class="w-full max-w-3xl flex flex-col md:flex-row items-center mt-6 mb-2 gap-4 md:gap-8"> <!-- Sequence View Controls --> <form id="sequence-controls" class="flex flex-col sm:flex-row items-start sm:items-center w-full md:w-auto gap-2 sm:gap-4"> <label for="input-number" class="text-gray-300">Start Number:</label> <input id="input-number" type="number" min="1" step="1" pattern="[0-9]*" autocomplete="off" inputmode="numeric" class="rounded bg-gray-800 border border-gray-700 text-lg px-2 py-1 text-gray-50 focus:ring-2 focus:ring-cyan-500 w-36" placeholder="Enter a positive integer" /> <button type="submit" id="visualize-btn" class="collatz-btn px-5 py-2 rounded font-bold text-gray-100 hover:brightness-110 focus:ring-2 focus:ring-cyan-600 shadow-md" > Visualize Sequence </button> </form> <!-- Forest View Controls (hidden by default) --> <form id="forest-controls" class="hidden flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-4 w-full md:w-auto"> <label for="max-range" class="text-gray-300">Visualize numbers from 1 to:</label> <input id="max-range" type="range" min="10" max="300" value="100" class="w-40" /> <span id="range-value" class="text-cyan-300 font-bold ml-2">100</span> <button id="forest-btn" type="submit" class="collatz-btn px-5 py-2 rounded font-bold text-gray-100 hover:brightness-110 focus:ring-2 focus:ring-cyan-600 shadow-md" > Visualize Forest </button> </form> <!-- View Toggle --> <button id="toggle-view" aria-label="Switch View" class="flex-shrink-0 px-4 py-2 rounded bg-gray-800 border border-gray-700 ml-0 md:ml-auto text-gray-100 hover:bg-gray-700 hover:text-cyan-300 font-semibold transition" > <span id="toggle-label">Forest View</span> </button> </section> <!-- Error Message --> <div id="error-message" class="w-full max-w-3xl text-rose-400 font-bold text-center my-1"></div> <!-- Visualization Area --> <div class="flex flex-col items-center w-full max-w-3xl mx-auto mb-2" > <div class="w-full h-[400px] md:h-[460px] shadow-lg rounded-lg bg-gray-900 flex items-center justify-center overflow-hidden relative"> <div id="p5-container" class="flex-1 w-full h-full z-0"></div> </div> <!-- Sequence display (below canvas) --> <div id="sequence-display" class="mt-4 bg-gray-900 p-3 rounded-lg w-full shadow border border-gray-800 font-mono text-base collatz-scroll" style="min-height: 2em;" ></div> </div> </div> <script> // --- Collatz Logic --- function collatzSequence(n) { const seq = [{num: n, op: null}]; while (n !== 1) { if (n % 2 === 0) { seq.push({num: n = n/2, op: '/2'}); } else { seq.push({num: n = 3*n + 1, op: 'x3+1'}); } } return seq; } // --- UI State --- let isForestView = false; let forestMax = 100; let currentN = null; let currentSeq = []; let allForestPaths = []; let animationStep = 0; let animating = false; let maxSeqValue = 0; let viewParams = {}; // --- DOM Elements --- const sequenceControls = document.getElementById('sequence-controls'); const forestControls = document.getElementById('forest-controls'); const inputNumber = document.getElementById('input-number'); const visualizeBtn = document.getElementById('visualize-btn'); const forestBtn = document.getElementById('forest-btn'); const toggleViewBtn = document.getElementById('toggle-view'); const toggleLabel = document.getElementById('toggle-label'); const errorMessage = document.getElementById('error-message'); const sequenceDisplay = document.getElementById('sequence-display'); const p5Container = document.getElementById('p5-container'); const maxRangeSlider = document.getElementById('max-range'); const rangeValueSpan = document.getElementById('range-value'); // --- Utility Functions --- function showError(msg) { errorMessage.textContent = msg; } function clearError() { errorMessage.textContent = ''; } function renderSequenceText(seq) { if (!Array.isArray(seq) || seq.length == 0) { sequenceDisplay.innerHTML = ''; return; } let html = ""; seq.forEach((item, idx) => { html += `<span class="sequence-span">${item.num}</span>`; if (idx < seq.length - 1) { html += `<span>โ</span>`; html += `<span class="operation-span">${item.op ? item.op : ''}</span>`; } }); sequenceDisplay.innerHTML = html; } function renderNothingText(text) { sequenceDisplay.innerHTML = `<span class="text-gray-500">${text}</span>`; } // --- View Switching --- toggleViewBtn.addEventListener('click', () => { isForestView = !isForestView; sequenceControls.classList.toggle('hidden', isForestView); forestControls.classList.toggle('hidden', !isForestView); toggleLabel.textContent = isForestView ? 'Sequence View' : 'Forest View'; clearError(); if (isForestView) { renderNothingText('Choose a range and visualize.'); window.setTimeout(() => p5redraw(), 0); } else { renderNothingText('Enter a number and visualize.'); window.setTimeout(() => p5redraw(), 0); } }); // --- Forest slider UI --- maxRangeSlider.addEventListener('input', () => { forestMax = parseInt(maxRangeSlider.value); rangeValueSpan.textContent = forestMax; }); // --- Sequence View (single number) --- sequenceControls.addEventListener('submit', function(e) { e.preventDefault(); let n = inputNumber.value.trim(); if (!n.match(/^\d+$/) || n === '' || parseInt(n) < 1) { showError("Please enter a valid positive integer (n โฅ 1)."); renderNothingText(""); return; } n = parseInt(n); if (n > 9000000) { // Arbitrary upper limit for animation showError("Please pick a number under 9,000,000."); renderNothingText(""); return; } clearError(); currentN = n; currentSeq = collatzSequence(n); maxSeqValue = Math.max(...currentSeq.map(x => x.num)); animationStep = 1; animating = true; renderSequenceText([]); // Clear previous p5redraw(); }); // --- Forest View (range) --- forestControls.addEventListener('submit', function(e) { e.preventDefault(); let N = parseInt(maxRangeSlider.value); if (N < 2) { showError("Pick a range of at least 2."); return; } clearError(); forestMax = N; allForestPaths = []; renderNothingText(""); setTimeout(() => { // Generate all paths allForestPaths = []; let globalMax = 1; let colorMap = {}; let lengths = []; for (let i = 1; i <= N; ++i) { let seq = collatzSequence(i); allForestPaths.push({ start: i, seq }); let localMax = Math.max(...seq.map(x=>x.num)); if (localMax > globalMax) globalMax = localMax; lengths.push(seq.length); } // Prepare color scaling viewParams = { globalMax, minLen: Math.min(...lengths), maxLen: Math.max(...lengths), }; animationStep = N + 2; // No animation: draw all. animating = false; p5redraw(); }, 10); }); // --- Initial --- renderNothingText('Enter a number and visualize.'); // --- p5.js --- let sketch = (p) => { let w, h, xoff, yoff, pad; let seqScaleY; // function let seqScaleX; // function let forestColors = []; // Utility: colorInterpolation between two colors function lerpColor(a, b, t) { const ar = parseInt(a.slice(1,3),16); const ag = parseInt(a.slice(3,5),16); const ab = parseInt(a.slice(5,7),16); const br = parseInt(b.slice(1,3),16); const bg = parseInt(b.slice(3,5),16); const bb = parseInt(b.slice(5,7),16); return p.color( Math.round(ar + (br-ar) * t), Math.round(ag + (bg-ag) * t), Math.round(ab + (bb-ab) * t) ); } p.setup = function() { w = p5Container.offsetWidth; h = p5Container.offsetHeight; pad = 44; xoff = 28; yoff = 20; p.createCanvas(w, h).parent('p5-container'); p.setAttributes('willReadFrequently', true); p.frameRate(60); p.background(24,24,32,255); }; p.windowResized = function() { w = p5Container.offsetWidth; h = p5Container.offsetHeight; p.resizeCanvas(w, h, false); p.background(24,24,32,255); p5redraw(); }; p.draw = function() { if (!p5Container.offsetWidth) return; if (p.width !== p5Container.offsetWidth || p.height !== p5Container.offsetHeight) { p.resizeCanvas(p5Container.offsetWidth, p5Container.offsetHeight, false); p.background(24,24,32,255); } p.background(24,24,32,240); if (isForestView) { drawForest(); } else { drawSequence(); } }; // --- Drawing routines --- function drawSequence() { if (!currentSeq || currentSeq.length === 0) return; let seq = currentSeq; const N = seq.length; // Axes scaling const yMin = 1; const yMax = maxSeqValue; const canvas_h = h - 2*yoff, canvas_w = w - 2*xoff; seqScaleX = i => xoff + (canvas_w) * (i/(N-1)); // Collatz can grow high! Use log for the y scaling, so the jumps are visible. const logYmax = Math.log2(yMax+3); seqScaleY = n => yoff + (1-(Math.log2(n+2)/logYmax)) * canvas_h; // Animation: Show up to animationStep points, animate fast for big sequences. let speed = 1 + Math.floor(N/36); animationStep = Math.min(animationStep + speed, N); // Draw axes p.stroke(60,60,65,96); p.strokeWeight(1.1); p.line(xoff, yoff, xoff, h-yoff); p.line(xoff, h-yoff, w-xoff, h-yoff); // Draw axis labels p.noStroke(); p.fill(160, 220, 255, 150); p.textSize(13); p.textAlign(p.LEFT, p.TOP); p.text('Start', xoff+2, h-yoff+6); p.textAlign(p.RIGHT, p.TOP); p.text('Step', w-xoff-2, h-yoff+6); p.textAlign(p.RIGHT, p.TOP); p.text('n', xoff-10, yoff); // Draw sequence graph for (let i = 0; i < Math.min(animationStep, N-1); i++) { const a = seq[i], b = seq[i+1]; let x1 = seqScaleX(i), y1 = seqScaleY(a.num); let x2 = seqScaleX(i+1), y2 = seqScaleY(b.num); // Edge p.strokeWeight(2.1); p.stroke(120+(i*7)%80,180,255,170); p.line(x1, y1, x2, y2); // Draw the operation above/below line if (b.op && (animationStep>=i+2)) { p.noStroke(); p.fill(172, 140, 255, 150); let opY = (y1+y2)/2 + ((b.op === 'x3+1') ? -15:12); p.textSize(13); p.textAlign(p.CENTER, p.BASELINE); p.text(b.op, (x1+x2)/2, opY); } } // Draw points (nodes) p.noStroke(); for (let i = 0; i < Math.min(animationStep, N); ++i) { let pointColor = seq[i].num === 1 ? p.color(34, 200, 100) : p.color(40,190,255,230); p.fill(pointColor); let x = seqScaleX(i), y = seqScaleY(seq[i].num); p.circle(x, y, 10); p.fill(22,36,42,240); p.textAlign(p.CENTER,p.CENTER); p.textSize(12); p.text(seq[i].num, x, y-16); } // Draw number "1" if (animationStep >= N) { p.fill(34, 200, 100,255); let x = seqScaleX(N-1), y = seqScaleY(1); p.textSize(16); p.textAlign(p.CENTER,p.BASELINE); p.text("1", x, y+21); } // Update text if (animationStep >= N) renderSequenceText(seq); else renderSequenceText(seq.slice(0, animationStep)); // Stop animating when done if (animationStep < N) p.redraw(); } function drawForest() { let N = forestMax; if (!allForestPaths || allForestPaths.length !== N) return; // Y axis: log2 scaling, max = viewParams.globalMax const yMin = 1, yMax = viewParams.globalMax ? viewParams.globalMax : 1; const canvas_h = h - 2*yoff, canvas_w = w - 2*xoff; const xStep = canvas_w / (N-1); // Colors by starting n let minLen = viewParams.minLen || 2; let maxLen = viewParams.maxLen || 10; const colors = []; for (let i=1; i<=N; ++i) { const t = (i-1)/(N-1); colors.push(lerpColor('#38bdf8','#a21caf', t)); } // Axes p.stroke(70,70,78,90); p.strokeWeight(1.1); p.line(xoff, yoff, xoff, h-yoff); p.line(xoff, h-yoff, w-xoff, h-yoff); p.noStroke(); p.fill(160, 220, 255, 140); p.textSize(12.5); p.textAlign(p.LEFT, p.TOP); p.text('Numbers 1 to '+N, xoff+2, h-yoff+6); // Y-axis log scale const logYmax = Math.log2(yMax+3); function scaleY(n) { return yoff + (1 - (Math.log2(n+2)/logYmax))*canvas_h; } // Draw every path p.strokeWeight(1.2); for (let idx=0; idx<N; ++idx) { let {start, seq} = allForestPaths[idx]; const t = (seq.length - minLen) / (maxLen - minLen + 1); p.stroke(colors[idx]); let prevX = xoff + (start-1)*xStep; let prevY = scaleY(seq[0].num); for (let i=1; i<seq.length; i++) { let f = (i/(seq.length-1)); let x = xoff + ((start-1) + f*0.4) * xStep; // For clarity, paths not always vertical let y = scaleY(seq[i].num); p.line(prevX, prevY, x, y); prevX = x; prevY = y; } } // Draw "1" as root p.noStroke(); p.fill(35,255,122, 220); let rootY = scaleY(1); p.ellipse(xoff+canvas_w/2, rootY, 16, 16); p.textAlign(p.CENTER,p.TOP) p.textSize(15); p.text('1', xoff+canvas_w/2, rootY+10); renderNothingText(`Forest for 1 to ${N}: All paths flow to 1. Drag slider to change range and re-visualize.`); } }; let p5inst = null; function p5redraw() { if (!p5inst) return; p5inst.redraw(); } window.addEventListener('DOMContentLoaded', () => { p5inst = new p5(sketch); p5inst.noLoop(); // We'll manually request redraws on each animate }); // Responsiveness: Redraw on resize window.addEventListener('resize', () => { p5redraw(); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en" class="dark"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script> <style> html.dark { color-scheme: dark; } .switch input:checked ~ .dot { transform: translateX(100%); } .dot { transition: 0.3s; } body { background-color: #1a202c !important; color: #cbd5e1 !important; } </style> </head> <body class="dark:bg-gray-900 dark:text-gray-100 min-h-screen p-6"> <div class="max-w-6xl mx-auto"> <h1 class="text-4xl font-bold text-center mb-4">The Collatz Conjecture Visualizer</h1> <p class="mb-6 text-center max-w-2xl mx-auto">Explore the Collatz Conjecture, where any positive integer eventually reaches 1 through two simple operations: divide by 2 if even, or triple and add 1 if odd.</p> <div class="flex flex-col md:flex-row items-center justify-center gap-4 mb-6"> <div id="inputGroup"> <input id="userInput" type="number" min="1" placeholder="Enter a number" class="px-3 py-2 bg-gray-800 border border-gray-600 rounded text-white focus:outline-none focus:ring-2 focus:ring-blue-500"> </div> <div id="sliderGroup" class="hidden"> <input id="rangeSlider" type="range" min="1" max="500" value="50" class="w-32 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> <span id="sliderValue" class="ml-2">50</span> </div> <button id="visualizeBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-white transition-colors"> Visualize </button> <div class="relative inline-block w-10 align-middle select-none"> <input type="checkbox" id="viewToggle" class="hidden"> <label for="viewToggle" class="flex items-center cursor-pointer"> <div class="relative w-10 h-6 bg-gray-600 rounded-full switch"> <div class="dot absolute w-5 h-5 bg-gray-900 rounded-full top-0.5 left-0.5 transition-transform duration-300"></div> </div> <span class="ml-2 text-sm font-medium" id="toggleLabel">Sequence View</span> </label> </div> </div> <!-- Error Area --> <div id="error" class="text-red-400 mb-4 text-center hidden"></div> <!-- Canvas --> <div class="relative"> <canvas id="visualizerCanvas" class="w-full bg-gray-800 rounded shadow-lg mb-6"></canvas> <!-- Overlay Messages --> <div id="overlay" class="absolute inset-0 flex items-center justify-center text-3xl font-bold opacity-70 hidden"> Select a number to visualize </div> </div> <!-- Sequence Display --> <div id="sequenceText" class="bg-gray-800 p-4 rounded-lg overflow-auto max-h-48 text-sm"></div> </div> <script> let currentInput = 'sequence'; let activeMode = 'sequence'; let currentSequence = []; let showOperations = true; let allSequences = []; let forestDrawn = false; let sequenceType = 'hailstone'; let colorMap = new Map(); // Helper functions function collatzSequence(n) { const sequence = [n]; while (n !== 1) { n = n % 2 === 0 ? n / 2 : 3 * n + 1; sequence.push(n); } return sequence; } function drawHailstoneSequence(p, sequence, color, operations = true, animate = true) { const { canvasHeight, padding } = this; const stepWidth = p.width / (sequence.length + 5); let drawnSteps = animate ? Math.min(Math.floor(p.millis()/100), sequence.length) : sequence.length; for (let i = 1; i < drawnSteps; i++) { const prev = sequence[i-1]; const next = sequence[i]; const x1 = (i-1) * stepWidth; const x2 = i * stepWidth; const y1 = p.map(prev, 1, Math.max(...sequence), 0, canvasHeight * 0.8); const y2 = p.map(next, 1, Math.max(...sequence), 0, canvasHeight * 0.8); // Draw line p.stroke(color); p.strokeWeight(2); p.line(x1, canvasHeight * 0.8 - y1, x2, canvasHeight * 0.8 - y2); // Draw circles p.fill(color); p.ellipse(x1, canvasHeight * 0.8 - y1, 6, 6); // Operation label if (operations) { p.fill(255); p.textSize(10); const op = prev % 2 === 0 ? "/2" : "x3+1"; const textX = x1 + (x2 - x1)/2; const textY = canvasHeight * 0.8 - y1 - 5; p.textAlign(p.CENTER, p.CENTER); p.text(op, textX, textY); } } } function drawForest(p, sequences, colors = [], spacing = 1) { // Draw all sequences as vertical paths converging to 1 const maxSteps = Math.max(...sequences.map(seq => seq.length)); const spacingStart = Math.min(5, p.width / Math.sqrt(sequences.length)); let paths = sequences.map((sequence, i) => { return { seq: sequence, index: 0, color: colors.length ? colors[i] : p.color(i * spd, 255, 255) }; }); while (paths.length) { let drawBatch = paths.splice(0, 5); drawBatch.forEach(path => { if (path.index < path.seq.length - 1) { const x = p.width - (path.seq[path.index] * spacingStart); const y = p.height - (path.index * spacing); p.point(x, y); path.index++; } }); } } // p5.js Sketch let sketch = (p) => { p.setup = function() { const canvasEl = document.getElementById('visualizerCanvas'); const cnv = p.createCanvas(canvasEl.offsetWidth, 400); cnv.parent(canvasEl); // Initialize canvas dimensions this.canvasWidth = p.width; this.canvasHeight = p.height; this.padding = 40; p.background(30); document.getElementById('overlay').classList.remove('hidden'); }; p.windowResized = function() { const canvasEl = document.getElementById('visualizerCanvas'); p.resizeCanvas(canvasEl.offsetWidth, 400); this.canvasWidth = p.width; this.canvasHeight = p.height; }; p.draw = function() { p.background(30); if (activeMode === 'sequence' && currentSequence.length > 0) { p.push(); drawHailstoneSequence(p, currentSequence, p.color('#4db5ff'), showOperations); p.pop(); } else if (activeMode === 'forest' && allSequences.length > 0) { p.push(); p.colorMode(p.HSB); // Draw forest view using different colors let colors = allSequences.map((_, i) => p.color(i * (100 / allSequences.length) % 360, 255, 255) ); drawForest(p, allSequences, colors); p.pop(); } }; }; new p5(sketch); // Setup event listeners document.getElementById('viewToggle').addEventListener('change', function() { currentInput = this.checked ? 'forest' : 'sequence'; document.getElementById('inputGroup').classList.toggle('hidden', this.checked); document.getElementById('sliderGroup').classList.toggle('hidden', !this.checked); document.getElementById('toggleLabel').textContent = this.checked ? 'Forest View' : 'Sequence View'; activeMode = this.checked ? 'forest' : 'sequence'; document.getElementById('overlay').classList.toggle('hidden', activeMode !== 'sequence' || currentSequence.length > 0); }); document.getElementById('rangeSlider').addEventListener('input', function() { document.getElementById('sliderValue').textContent = `N=${this.value}`; }); document.getElementById('visualizeBtn').addEventListener('click', () => { const userInput = document.getElementById('userInput'); const errorDiv = document.getElementById('error'); const sequenceText = document.getElementById('sequenceText'); errorDiv.classList.add('hidden'); sequenceText.innerHTML = ''; if (activeMode === 'sequence') { let value = parseInt(userInput.value); // Input validation if (isNaN(value) || value < 1) { errorDiv.textContent = 'Please enter a valid positive integer!'; errorDiv.classList.remove('hidden'); return; } // Generate Collatz sequence currentSequence = collatzSequence(value); // Draw textual sequence const steps = document.getElementById('sequenceText'); steps.innerHTML = currentSequence.map((num, idx) => { if (idx === currentSequence.length - 1) return num; return `${num} โ `; }).join(''); document.getElementById('overlay').classList.add('hidden'); } else { const range = parseInt(document.getElementById('rangeSlider').value); if (range < 1) { errorDiv.textContent = 'Please select a valid range!'; errorDiv.classList.remove('hidden'); return; } // Generate forest paths allSequences = []; for (let i = 1; i <= range; i++) { allSequences.push(collatzSequence(i)); } document.getElementById('overlay').classList.add('hidden'); } }); // Resize canvas with window window.addEventListener('resize', () => { if (typeof window.p5Instance !== 'undefined') { window.p5Instance._userNodeD = document.getElementById('visualizerCanvas'); window.p5Instance.resizeCanvas(window.p5Instance._userNodeD.offsetWidth, 400); } }); </script> </body> </html> ```
Certainly! Below is the complete HTML, CSS, and JavaScript code to create a full-page responsive and visually appealing Collatz Conjecture Visualizer. This example uses p5.js for rendering the visualization on the canvas. Tailwind CSS is used for styling. Make sure to include the necessary p5.js library directly in the HTML. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>The Collatz Conjecture Visualizer</title> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.1.2/dist/tailwind.min.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <style> body { display: flex; flex-direction: column; min-height: 100vh; background-color: #121212; color: #f5f5f5; } .main-container { flex: 1; padding: 20px; } </style> </head> <body> <div class="container mx-auto main-container"> <h1 class="text-4xl font-bold mb-4">The Collatz Conjecture Visualizer</h1> <p class="mb-4">The Collatz Conjecture states that if you start with any positive integer and repeatedly apply the following operation: If the number is even, divide it by 2. If the number is odd, multiply it by 3 and add 1. The sequence will always reach 1.</p> <div class="mb-4"> <input type="number" id="numberInput" placeholder="Enter a positive integer" class="w-full p-2 mb-2 text-black"> <button id="visualizeButton" class="p-2 bg-blue-500 text-white w-full">Visualize Sequence</button> </div> <div class="mb-4 flex justify-center"> <button id="toggleViewButton" class="p-2 bg-blue-500 text-white">Toggle Sequence View / Forest View</button> </div> <canvas id="visualizationCanvas" class="border"></canvas> <div id="sequenceText" class="mt-4"></div> </div> <script> let canvas; let sequenceView = true; let slider; const sequenceText = document.getElementById('sequenceText'); function setup() { canvas = createCanvas(windowWidth - 40, windowHeight - 200); canvas.parent('visualizationCanvas'); slider = createSlider(1, 100, 50); slider.style('width', '100%'); slider.parent('visualizationCanvas'); slider.hide(); } function draw() { if (!sequenceView) { background(255); stroke(0); for (let i = 1; i <= slider.value(); i++) { let sequence = collatzSequence(i); drawPath(sequence, color(map(i, 1, slider.value(), 0, 255), 0, 0)); } noLoop(); } } function collatzSequence(n) { let sequence = [n]; while (n > 1) { n = n % 2 === 0 ? n / 2 : n * 3 + 1; sequence.push(n); } return sequence; } function drawSequence(sequence) { background(255); stroke(0); for (let i = 0; i < sequence.length - 1; i++) { let x1 = map(i, 0, sequence.length - 1, 0, width); let y1 = map(sequence[i], 0, max(sequence), height, 0); let x2 = map(i + 1, 0, sequence.length - 1, 0, width); let y2 = map(sequence[i + 1], 0, max(sequence), height, 0); line(x1, y1, x2, y2); text(sequence[i] + ' ' + (sequence[i] % 2 === 0 ? '/2' : '*3+1'), x1, y1 - 10); } noLoop(); } function drawPath(sequence, col) { stroke(col); for (let i = 0; i < sequence.length - 1; i++) { let x1 = map(i, 0, sequence.length - 1, 0, width); let y1 = map(sequence[i], 0, max(sequence), height, 0); let x2 = map(i + 1, 0, sequence.length - 1, 0, width); let y2 = map(sequence[i + 1], 0, max(sequence), height, 0); line(x1, y1, x2, y2); } } function toggleView() { sequenceView = !sequenceView; if (sequenceView) { slider.hide(); document.getElementById('numberInput').type = 'number'; } else { slider.show(); document.getElementById('numberInput').type = 'hidden'; } } function visualize() { let input = document.getElementById('numberInput').value; if (sequenceView) { if (input < 1 || !Number.isInteger(+input)) { sequenceText.innerText = 'Please enter a valid positive integer.'; return; } let sequence = collatzSequence(+input); drawSequence(sequence); sequenceText.innerText = sequence.join(' โ '); } else { loop(); } } document.getElementById('visualizeButton').addEventListener('click', visualize); document.getElementById('toggleViewButton').addEventListener('click', toggleView); </script> </body> </html> ``` This code sets up a web page with the specified functionalities. The visualization uses p5.js to draw sequences on a canvas. The UI components include input for numbers, a button to visualize sequences, a toggle button to switch between views, and a canvas area to render the visualization. Tailwind CSS is used for styling, and all necessary libraries are included directly in the HTML.
Here's a complete implementation of the Collatz Conjecture Visualizer with all the specified functionality: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>The Collatz Conjecture Visualizer</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> <style> body { overflow-x: hidden; } .toggle-checkbox:checked { right: 0; border-color: #68D391; } .toggle-checkbox:checked + .toggle-label { background-color: #68D391; } canvas { display: block; margin: 0 auto; } </style> </head> <body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col"> <div class="container mx-auto px-4 py-8 flex-grow"> <header class="text-center mb-8"> <h1 class="text-4xl md:text-5xl font-bold text-green-400 mb-4">The Collatz Conjecture Visualizer</h1> <p class="text-lg max-w-3xl mx-auto"> Start with any positive integer. If it's even, divide by 2. If it's odd, multiply by 3 and add 1. The conjecture states that no matter what number you start with, you'll always reach 1 eventually. </p> </header> <div class="flex flex-col md:flex-row justify-between items-center mb-8 gap-4"> <div class="flex-grow w-full md:w-auto" id="input-container"> <div class="flex flex-col md:flex-row gap-4 items-center"> <input type="number" id="starting-number" min="1" placeholder="Enter a positive integer" class="bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-green-400 w-full md:w-64" > <button id="visualize-btn" class="bg-green-600 hover:bg-green-500 text-white font-bold px-6 py-2 rounded-lg transition-colors w-full md:w-auto" > Visualize Sequence </button> </div> </div> <div class="flex items-center gap-4"> <span>Sequence View</span> <div class="relative inline-block w-16 mr-2 align-middle select-none"> <input type="checkbox" id="view-toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" > <label for="view-toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer" ></label> </div> <span>Forest View</span> </div> </div> <div class="bg-gray-800 rounded-lg p-4 mb-8"> <div id="p5-canvas" class="w-full"></div> </div> <div class="bg-gray-800 rounded-lg p-4 mb-8" id="sequence-output"> <h2 class="text-xl font-semibold mb-2">Sequence:</h2> <div id="sequence-text" class="text-gray-300 overflow-x-auto"></div> </div> <div class="bg-gray-800 rounded-lg p-4 hidden" id="forest-controls"> <div class="flex flex-col gap-2"> <label for="range-slider" class="text-lg">Numbers from 1 to <span id="range-value">50</span></label> <input type="range" id="range-slider" min="2" max="200" value="50" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer" > </div> </div> </div> <footer class="bg-gray-800 py-4 text-center text-gray-400"> <p>The Collatz Conjecture Visualizer - All paths lead to 1</p> </footer> <script> let collatzSketch = function(p) { let sequence = []; let animationStep = 0; let maxWidth = 0; let maxValue = 0; let minValue = Infinity; let scaleX = 1; let scaleY = 1; let textPositions = []; let forestSequences = []; let isForestView = false; let forestRange = 50; p.setup = function() { let canvas = p.createCanvas(p.select('#p5-canvas').width, 400); canvas.parent('p5-canvas'); p.noLoop(); }; p.draw = function() { p.background(31, 41, 55); // bg-gray-800 if (isForestView && forestSequences.length > 0) { drawForest(); } else if (sequence.length > 0 && animationStep > 0) { drawSequence(); } }; function drawSequence() { p.stroke(110, 231, 183); // green-400 p.strokeWeight(1.5); p.noFill(); // Draw up to the current animation step let currentSequence = sequence.slice(0, animationStep + 1); // Calculate path p.beginShape(); for (let i = 0; i < currentSequence.length; i++) { let x = i * scaleX; let y = p.height - currentSequence[i] * scaleY; p.vertex(x, y); // Store positions for operation text if (i > 0) { textPositions.push({ x: (x + ((i-1) * scaleX)) / 2, y: (y + (p.height - sequence[i-1] * scaleY)) / 2, op: sequence[i-1] % 2 === 0 ? 'รท 2' : 'ร 3 + 1' }); } } p.endShape(); // Draw points p.fill(110, 231, 183); p.noStroke(); for (let i = 0; i < currentSequence.length; i++) { let x = i * scaleX; let y = p.height - currentSequence[i] * scaleY; p.circle(x, y, 6); } // Draw operation text p.textSize(12); p.fill(255); for (let i = 0; i < textPositions.length && i < animationStep; i++) { p.text(textPositions[i].op, textPositions[i].x, textPositions[i].y); } // If animation not complete, increment step if (animationStep < sequence.length - 1) { animationStep++; setTimeout(() => p.redraw(), 100); } } function drawForest() { p.textSize(10); p.textAlign(p.LEFT, p.CENTER); for (let seq of forestSequences) { // Random color hue based on starting number let hue = (seq[0] * 137.508) % 360; // Golden angle for color distribution p.stroke(hue, 80, 90); p.fill(hue, 80, 90); p.strokeWeight(1); p.beginShape(); for (let i = 0; i < seq.length; i++) { let x = i * (p.width / forestSequences.reduce((max, s) => Math.max(max, s.length), 0)); let y = p.height - seq[i] * (p.height / maxValue * 0.9); p.vertex(x, y); // Draw starting number label if (i === 0) { p.text(seq[0], x + 3, y); } } p.endShape(); } // Draw 1 at the bottom p.fill(255); p.noStroke(); p.textSize(14); p.text('1', 5, p.height - 5); } p.windowResized = function() { p.resizeCanvas(p.select('#p5-canvas').width, 400); rescaleCanvas(); p.redraw(); }; function rescaleCanvas() { if (sequence.length === 0) return; maxWidth = sequence.length; maxValue = Math.max(...sequence); minValue = Math.min(...sequence); scaleX = p.width / maxWidth; scaleY = p.height / (maxValue * 1.1); textPositions = []; } function calculateSequence(n) { let seq = [n]; while (n !== 1) { if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } seq.push(n); } return seq; } function visualizeSequence(n) { sequence = calculateSequence(n); animationStep = 0; rescaleCanvas(); // Update sequence text let seqText = sequence.join(' โ '); document.getElementById('sequence-text').textContent = seqText; p.redraw(); } function visualizeForest(range) { forestRange = range; forestSequences = []; // Calculate sequences for the range for (let i = 1; i <= range; i++) { forestSequences.push(calculateSequence(i)); } // Find maximum values for scaling maxValue = forestSequences.reduce((max, seq) => Math.max(max, ...seq), 0); p.redraw(); } return { visualizeSequence, visualizeForest, setForestView: (value) => { isForestView = value; if (!value && sequence.length > 0) { p.redraw(); } else if (value && forestSequences.length > 0) { p.redraw(); } } }; }; document.addEventListener('DOMContentLoaded', function() { let myp5 = new p5(collatzSketch); // Visualize button click handler document.getElementById('visualize-btn').addEventListener('click', function() { let input = document.getElementById('starting-number'); let num = parseInt(input.value); if (isNaN(num) || num < 1) { alert('Please enter a valid positive integer'); return; } myp5.visualizeSequence(num); }); // View toggle handler document.getElementById('view-toggle').addEventListener('change', function() { let isForestView = this.checked; myp5.setForestView(isForestView); document.getElementById('input-container').classList.toggle('hidden', isForestView); document.getElementById('forest-controls').classList.toggle('hidden', !isForestView); document.getElementById('sequence-output').classList.toggle('hidden', isForestView); if (isForestView) { let range = parseInt(document.getElementById('range-slider').value); myp5.visualizeForest(range); } }); // Range slider handler document.getElementById('range-slider').addEventListener('input', function() { let value = parseInt(this.value); document.getElementById('range-value').textContent = value; if (document.getElementById('view-toggle').checked) { myp5.visualizeForest(value); } }); // Initialize with example setTimeout(() => { document.getElementById('starting-number').value = 27; document.getElementById('visualize-btn').click(); }, 500); }); </script> </body> </html> ``` This implementation includes: 1. **Dark Theme UI** with Tailwind CSS styling 2. **Responsive design** that works on mobile and desktop 3. **Interactive Visualization** with p5.js: - Sequence View: Animates the path from input number to 1 - Forest View: Shows multiple sequences converging to 1 4. **User Controls**: - Input field for single numbers (Sequence View) - Range slider for Forest View - Toggle to switch between views 5. **Sequence Display** that shows the full sequence as text 6. **Automatic scaling** of the visualization based on sequence length and values 7. **Initial Example** (number 27) to demonstrate immediately The visualization handles both odd and even operations, showing the intermediate calculations, and efficiently renders even large forests of sequences. The color coding helps distinguish different starting numbers in the Forest View.
**Collatz Conjecture Visualizer** ================================ ### HTML ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.1/dist/p5.js"></script> <style> body { background-color: #1a202c; color: #fff; } </style> </head> <body> <div class="container mx-auto p-4 mt-4"> <h1 class="text-3xl font-bold mb-4">The Collatz Conjecture Visualizer</h1> <p class="mb-4">The Collatz Conjecture is a mathematical conjecture that concerns a sequence defined as follows: start with any positive integer n. If n is even, the next term is n/2. If n is odd, the next term is 3n + 1. The conjecture states that no matter what value of n you start with, the sequence will always eventually reach 1.</p> <div id="input-container" class="flex flex-col mb-4"> <input id="number-input" type="number" placeholder="Enter a positive integer" class="p-2 rounded bg-gray-700 text-white"> <button id="visualize-button" class="p-2 bg-blue-500 hover:bg-blue-700 rounded mt-2">Visualize Sequence</button> <div id="error-message" class="text-red-500"></div> </div> <div id="slider-container" class="hidden flex flex-col mb-4"> <label for="range-slider">Visualize numbers from 1 to:</label> <input id="range-slider" type="range" min="1" max="100" value="10" class="p-2 rounded"> <span id="range-value">10</span> </div> <div class="flex mb-4"> <button id="sequence-view-button" class="p-2 bg-blue-500 hover:bg-blue-700 rounded mr-2">Sequence View</button> <button id="forest-view-button" class="p-2 bg-gray-500 hover:bg-gray-700 rounded">Forest View</button> </div> <div id="canvas-container" class="mb-4"> <div id="sequence-text" class="mb-2"></div> </div> </div> <script src="script.js"></script> </body> </html> ``` ### JavaScript ```javascript let sequenceView = true; let currentNumber = null; let sequence = []; let index = 0; let forestSequences = []; let forestIndex = 0; const numberInput = document.getElementById('number-input'); const visualizeButton = document.getElementById('visualize-button'); const errorMessage = document.getElementById('error-message'); const sequenceViewButton = document.getElementById('sequence-view-button'); const forestViewButton = document.getElementById('forest-view-button'); const sliderContainer = document.getElementById('slider-container'); const inputContainer = document.getElementById('input-container'); const rangeSlider = document.getElementById('range-slider'); const rangeValue = document.getElementById('range-value'); const sequenceText = document.getElementById('sequence-text'); sequenceViewButton.addEventListener('click', () => { sequenceView = true; inputContainer.classList.remove('hidden'); sliderContainer.classList.add('hidden'); sequenceViewButton.classList.remove('bg-gray-500'); sequenceViewButton.classList.add('bg-blue-500'); forestViewButton.classList.remove('bg-blue-500'); forestViewButton.classList.add('bg-gray-500'); }); forestViewButton.addEventListener('click', () => { sequenceView = false; inputContainer.classList.add('hidden'); sliderContainer.classList.remove('hidden'); sequenceViewButton.classList.remove('bg-blue-500'); sequenceViewButton.classList.add('bg-gray-500'); forestViewButton.classList.remove('bg-gray-500'); forestViewButton.classList.add('bg-blue-500'); }); rangeSlider.addEventListener('input', () => { rangeValue.textContent = rangeSlider.value; }); visualizeButton.addEventListener('click', () => { const num = parseInt(numberInput.value); if (isNaN(num) || num <= 0) { errorMessage.textContent = 'Please enter a positive integer.'; return; } errorMessage.textContent = ''; currentNumber = num; sequence = collatzSequence(num); index = 0; forestSequences = []; forestIndex = 0; sequenceText.textContent = sequence.join(' โ '); }); function collatzSequence(n) { const sequence = [n]; while (n !== 1) { if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } sequence.push(n); } return sequence; } function setup() { const canvas = createCanvas(windowWidth - 100, 600); canvas.parent('canvas-container'); background(26, 32, 44); frameRate(10); } function draw() { background(26, 32, 44); if (sequenceView && currentNumber !== null) { drawSequence(); } else if (!sequenceView) { drawForest(); } } function drawSequence() { const sequenceLength = sequence.length; const scaleX = width / sequenceLength; const scaleY = height / Math.max(...sequence); noFill(); stroke(255); beginShape(); for (let i = 0; i <= index; i++) { const x = i * scaleX; const y = height - sequence[i] * scaleY; vertex(x, y); if (i > 0) { const operation = sequence[i] === sequence[i - 1] / 2 ? '/2' : '*3+1'; fill(255); noStroke(); text(operation, x - 20, y + 10); stroke(255); noFill(); } } endShape(); index++; if (index >= sequenceLength) { noLoop(); } } function drawForest() { const maxN = parseInt(rangeSlider.value); if (forestIndex < maxN) { const num = forestIndex + 1; const seq = collatzSequence(num); forestSequences.push(seq); forestIndex++; } const scaleX = width / maxN; const scaleY = height / Math.max(...forestSequences.flat()); noFill(); for (let i = 0; i < forestSequences.length; i++) { const seq = forestSequences[i]; const hue = map(i, 0, forestSequences.length, 0, 360); stroke(`hsl(${hue}, 100%, 50%)`); beginShape(); for (let j = 0; j < seq.length; j++) { const x = i * scaleX + scaleX / 2; const y = height - seq[j] * scaleY; vertex(x, y); } endShape(); } } ``` ### Usage 1. Open the HTML file in a web browser. 2. Enter a positive integer in the input field and click "Visualize Sequence" to visualize the Collatz sequence. 3. Click "Forest View" to visualize the Collatz sequences for a range of numbers. 4. Adjust the slider to change the range of numbers visualized in "Forest View".
Here's a simple implementation of the Collatz Conjecture Visualizer using HTML, CSS (Tailwind CSS), and JavaScript with the p5.js library for drawing: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>The Collatz Conjecture Visualizer</title> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.2/dist/p5.js"></script> </head> <body class="h-screen bg-gray-900 text-white p-4"> <h1 class="text-4xl mb-4">The Collatz Conjecture Visualizer</h1> <p class="text-lg mb-4">Start with any positive integer n. If n is even, the next term is n/2. If n is odd, the next term is 3n + 1. The conjecture states that no matter what value of n you start with, the sequence will always eventually reach 1.</p> <div class="mb-4"> <input id="number-input" type="number" class="w-full p-2 pl-10 text-sm text-gray-700" placeholder="Enter a positive integer"> <button id="visualize-button" class="bg-orange-500 hover:bg-orange-700 text-white font-bold py-2 px-4 rounded">Visualize Sequence</button> </div> <div class="mb-4"> <label class="inline-block mb-2">View:</label> <input id="sequence-view" type="radio" name="view" value="sequence" checked> <label class="inline-block mb-2">Sequence View</label> <input id="forest-view" type="radio" name="view" value="forest"> <label class="inline-block mb-2">Forest View</label> <input id="forest-range" type="range" min="1" max="100" value="10" class="hidden"> <label id="forest-range-label" class="hidden">Visualize numbers from 1 to <span id="forest-range-value">10</span></label> </div> <div id="canvas-container" class="mb-4"></div> <div id="sequence-text" class="text-lg"></div> <script> letCOVERY = { started: false, sequence: [], currentStep: 0, view: 'sequence' }; const numberInput = document.getElementById('number-input'); const visualizeButton = document.getElementById('visualize-button'); const sequenceView = document.getElementById('sequence-view'); const forestView = document.getElementById('forest-view'); const forestRange = document.getElementById('forest-range'); const forestRangeLabel = document.getElementById('forest-range-label'); const forestRangeValue = document.getElementById('forest-range-value'); const canvasContainer = document.getElementById('canvas-container'); const sequenceText = document.getElementById('sequence-text'); forestView.addEventListener('click', () => { if (!COVERY.started) { COVERY.view = 'forest'; numberInput.classList.add('hidden'); forestRange.classList.remove('hidden'); forestRangeLabel.classList.remove('hidden'); visualizeButton.textContent = 'Visualize Forest'; } }); sequenceView.addEventListener('click', () => { if (!COVERY.started) { COVERY.view = 'sequence'; numberInput.classList.remove('hidden'); forestRange.classList.add('hidden'); forestRangeLabel.classList.add('hidden'); visualizeButton.textContent = 'Visualize Sequence'; } }); forestRange.addEventListener('input', () => { forestRangeValue.textContent = forestRange.value; }); visualizeButton.addEventListener('click', () => { if (!COVERY.started) { COVERY.started = true; if (COVERY.view === 'sequence') { const number = parseInt(numberInput.value); if (isNaN(number) || number < 1) { sequenceText.textContent = 'Please enter a positive integer.'; COVERY.started = false; return; } COVERY.sequence = [number]; generateSequence(number); drawSequence(); } else { const max = parseInt(forestRange.value); for (let i = 1; i <= max; i++) { COVERY.sequence = [i]; generateSequence(i); drawSequence(); } } } }); function generateSequence(n) { while (n > 1) { if (n % 2 === 0) { COVERY.sequence.push(n / 2); n = n / 2; } else { COVERY.sequence.push(3 * n + 1); n = 3 * n + 1; } } } function drawSequence() { if (COVERY.view === 'sequence') { const canvas = createCanvas(800, 600); canvas.parent('canvas-container'); background(220); noFill(); stroke(0); strokeWeight(2); beginShape(); for (let i = 0; i < COVERY.sequence.length; i++) { const x = map(i, 0, COVERY.sequence.length - 1, 0, width); const y = height - map(COVERY.sequence[i], 1, Math.max(...COVERY.sequence), 0, height); vertex(x, y); if (i > 0) { const prevX = map(i - 1, 0, COVERY.sequence.length - 1, 0, width); const prevY = height - map(COVERY.sequence[i - 1], 1, Math.max(...COVERY.sequence), 0, height); strokeWeight(1); line(prevX, prevY, x, y); fill(0); textSize(12); textAlign(CENTER, CENTER); text(`${COVERY.sequence[i - 1]} โ ${COVERY.sequence[i]}`, (prevX + x) / 2, (prevY + y) / 2); noFill(); strokeWeight(2); } } endShape(); sequenceText.textContent = COVERY.sequence.join(' โ '); } else { const canvas = createCanvas(800, 600); canvas.parent('canvas-container'); background(220); noFill(); strokeWeight(2); for (let i = 0; i < COVERY.sequence.length; i++) { const x = map(i, 0, COVERY.sequence.length - 1, 0, width); const y = height - map(COVERY.sequence[i], 1, Math.max(...COVERY.sequence), 0, height); stroke(map(i, 0, COVERY.sequence.length - 1, 0, 255), 0, 0); beginShape(); vertex(x, y); if (i > 0) { const prevX = map(i - 1, 0, COVERY.sequence.length - 1, 0, width); const prevY = height - map(COVERY.sequence[i - 1], 1, Math.max(...COVERY.sequence), 0, height); line(prevX, prevY, x, y); } endShape(); } } } </script> </body> </html> ``` This code creates a page with a title, explanation, input field, visualize button, and a toggle switch to change between "Sequence View" and "Forest View". When the user enters a number and clicks "Visualize", it generates the Collatz sequence and draws it on a canvas. The sequence is displayed as a series of connected points, creating a "hailstone" graph that zig-zags downwards until it hits 1. The user can switch to "Forest View" to visualize the paths of many numbers simultaneously, creating a tree-like structure where all branches eventually merge and flow into the single root, which is 1.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Collatz Conjecture Visualizer</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/min/p5.min.js"></script> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); body { font-family: 'Inter', sans-serif; } .toggle-switch { position: relative; display: inline-block; width: 60px; height: 34px; } .toggle-switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #4B5563; transition: .4s; border-radius: 34px; } .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #3B82F6; } input:checked + .slider:before { transform: translateX(26px); } </style> </head> <body class="bg-gray-900 text-white min-h-screen"> <div class="container mx-auto px-4 py-8"> <!-- Header --> <div class="text-center mb-8"> <h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent"> The Collatz Conjecture Visualizer </h1> <div class="bg-gray-800 rounded-lg p-6 max-w-4xl mx-auto"> <p class="text-lg text-gray-300 leading-relaxed"> Start with any positive integer <span class="text-blue-400 font-semibold">n</span>. If <span class="text-green-400 font-semibold">n is even</span>, the next term is <span class="text-green-400 font-semibold">n/2</span>. If <span class="text-orange-400 font-semibold">n is odd</span>, the next term is <span class="text-orange-400 font-semibold">3n + 1</span>. The conjecture states that no matter what value you start with, the sequence will always eventually reach <span class="text-purple-400 font-semibold">1</span>. </p> </div> </div> <!-- Controls --> <div class="bg-gray-800 rounded-lg p-6 mb-8"> <div class="flex flex-wrap items-center justify-center gap-6"> <!-- View Toggle --> <div class="flex items-center gap-3"> <span class="text-gray-300">Sequence View</span> <label class="toggle-switch"> <input type="checkbox" id="viewToggle"> <span class="slider"></span> </label> <span class="text-gray-300">Forest View</span> </div> <!-- Input Controls --> <div id="sequenceControls" class="flex items-center gap-4"> <label for="numberInput" class="text-gray-300 font-medium">Enter a number:</label> <input type="number" id="numberInput" min="1" max="10000" placeholder="e.g., 27" class="bg-gray-700 text-white px-4 py-2 rounded-lg border border-gray-600 focus:border-blue-500 focus:outline-none w-32" > <button id="visualizeBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-medium transition-colors" > Visualize Sequence </button> </div> <div id="forestControls" class="hidden flex items-center gap-4"> <label for="rangeSlider" class="text-gray-300 font-medium">Range (1 to N):</label> <input type="range" id="rangeSlider" min="10" max="200" value="50" class="w-32" > <span id="rangeDisplay" class="text-blue-400 font-semibold w-8">50</span> <button id="forestBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-lg font-medium transition-colors" > Visualize Forest </button> </div> </div> <!-- Error Message --> <div id="errorMessage" class="hidden mt-4 p-4 bg-red-900 border border-red-600 rounded-lg text-red-200 text-center"></div> </div> <!-- Canvas Container --> <div class="bg-gray-800 rounded-lg p-4 mb-8"> <div id="canvasContainer" class="w-full h-96"></div> </div> <!-- Sequence Display --> <div id="sequenceDisplay" class="bg-gray-800 rounded-lg p-6"> <h3 class="text-xl font-semibold mb-4 text-center">Sequence</h3> <div id="sequenceText" class="text-center text-gray-300 text-lg"> Enter a number and click "Visualize Sequence" to see the Collatz sequence </div> </div> </div> <script> let canvas; let isSequenceView = true; let currentSequence = []; let animationIndex = 0; let isAnimating = false; let paths = []; // UI Elements const viewToggle = document.getElementById('viewToggle'); const sequenceControls = document.getElementById('sequenceControls'); const forestControls = document.getElementById('forestControls'); const numberInput = document.getElementById('numberInput'); const visualizeBtn = document.getElementById('visualizeBtn'); const forestBtn = document.getElementById('forestBtn'); const rangeSlider = document.getElementById('rangeSlider'); const rangeDisplay = document.getElementById('rangeDisplay'); const errorMessage = document.getElementById('errorMessage'); const sequenceText = document.getElementById('sequenceText'); // Collatz sequence calculation function collatzSequence(n) { const sequence = [n]; while (n !== 1) { if (n % 2 === 0) { n = n / 2; } else { n = 3 * n + 1; } sequence.push(n); } return sequence; } // Error handling function showError(message) { errorMessage.textContent = message; errorMessage.classList.remove('hidden'); setTimeout(() => { errorMessage.classList.add('hidden'); }, 3000); } // Toggle view viewToggle.addEventListener('change', (e) => { isSequenceView = !e.target.checked; if (isSequenceView) { sequenceControls.classList.remove('hidden'); forestControls.classList.add('hidden'); } else { sequenceControls.classList.add('hidden'); forestControls.classList.remove('hidden'); } clear(); }); // Range slider update rangeSlider.addEventListener('input', (e) => { rangeDisplay.textContent = e.target.value; }); // Visualize sequence button visualizeBtn.addEventListener('click', () => { const num = parseInt(numberInput.value); if (isNaN(num) || num < 1) { showError('Please enter a positive integer greater than 0'); return; } if (num > 10000) { showError('Please enter a number less than or equal to 10000'); return; } visualizeSequence(num); }); // Visualize forest button forestBtn.addEventListener('click', () => { const range = parseInt(rangeSlider.value); visualizeForest(range); }); function visualizeSequence(startNum) { currentSequence = collatzSequence(startNum); animationIndex = 0; isAnimating = true; // Display sequence as text sequenceText.textContent = currentSequence.join(' โ '); redraw(); } function visualizeForest(maxNum) { paths = []; for (let i = 1; i <= maxNum; i++) { const sequence = collatzSequence(i); const color = `hsl(${(i * 137.5) % 360}, 70%, 60%)`; paths.push({ sequence, color, startNum: i }); } sequenceText.textContent = `Forest view: Numbers 1 to ${maxNum} (${paths.length} paths)`; redraw(); } function setup() { const container = document.getElementById('canvasContainer'); canvas = createCanvas(container.offsetWidth, 400); canvas.parent('canvasContainer'); background(31, 41, 55); } function draw() { background(31, 41, 55); if (isSequenceView && currentSequence.length > 0) { drawSequenceView(); } else if (!isSequenceView && paths.length > 0) { drawForestView(); } } function drawSequenceView() { if (currentSequence.length === 0) return; const maxVal = Math.max(...currentSequence); const minVal = Math.min(...currentSequence); const margin = 50; const usableHeight = height - 2 * margin; const usableWidth = width - 2 * margin; stroke(100); strokeWeight(1); // Draw grid for (let i = 0; i <= 10; i++) { const y = margin + (i / 10) * usableHeight; line(margin, y, width - margin, y); } const endIndex = isAnimating ? Math.min(animationIndex + 1, currentSequence.length) : currentSequence.length; for (let i = 0; i < endIndex - 1; i++) { const x1 = margin + (i / (currentSequence.length - 1)) * usableWidth; const y1 = height - margin - ((currentSequence[i] - minVal) / (maxVal - minVal)) * usableHeight; const x2 = margin + ((i + 1) / (currentSequence.length - 1)) * usableWidth; const y2 = height - margin - ((currentSequence[i + 1] - minVal) / (maxVal - minVal)) * usableHeight; // Draw line const isOdd = currentSequence[i] % 2 === 1; stroke(isOdd ? color(255, 165, 0) : color(100, 200, 255)); strokeWeight(2); line(x1, y1, x2, y2); // Draw points fill(255); noStroke(); circle(x1, y1, 8); // Draw operation label const operation = isOdd ? 'ร3+1' : 'รท2'; fill(isOdd ? color(255, 165, 0) : color(100, 200, 255)); textAlign(CENTER); textSize(12); text(operation, (x1 + x2) / 2, (y1 + y2) / 2 - 10); // Draw number fill(255); text(currentSequence[i], x1, y1 - 15); } // Draw final point if (endIndex > 0) { const finalX = margin + ((endIndex - 1) / (currentSequence.length - 1)) * usableWidth; const finalY = height - margin - ((currentSequence[endIndex - 1] - minVal) / (maxVal - minVal)) * usableHeight; fill(255); noStroke(); circle(finalX, finalY, 8); text(currentSequence[endIndex - 1], finalX, finalY - 15); } if (isAnimating && animationIndex < currentSequence.length - 1) { animationIndex += 0.5; } else { isAnimating = false; } } function drawForestView() { const margin = 50; for (const path of paths) { const { sequence, color: pathColor, startNum } = path; stroke(pathColor); strokeWeight(1); fill(pathColor); const maxVal = Math.max(...sequence); const minVal = 1; const usableHeight = height - 2 * margin; const usableWidth = width - 2 * margin; for (let i = 0; i < sequence.length - 1; i++) { const x1 = margin + (i / (sequence.length - 1)) * (usableWidth * 0.8) + (startNum % 20) * 2; const y1 = height - margin - ((sequence[i] - minVal) / (maxVal - minVal)) * usableHeight; const x2 = margin + ((i + 1) / (sequence.length - 1)) * (usableWidth * 0.8) + (startNum % 20) * 2; const y2 = height - margin - ((sequence[i + 1] - minVal) / (maxVal - minVal)) * usableHeight; line(x1, y1, x2, y2); } } } function windowResized() { const container = document.getElementById('canvasContainer'); resizeCanvas(container.offsetWidth, 400); } </script> </body> </html> ```