/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { savedFractals: [], lastFractalType: "mandelbrot", lastColorScheme: "rainbow" }); /**** * Classes ****/ var Button = Container.expand(function (label, width, height) { var self = Container.call(this); var buttonGraphics = self.attachAsset('controlButton', { anchorX: 0.5, anchorY: 0.5, width: width || 120, height: height || 60 }); var buttonText = new Text2(label, { size: 24, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.setLabel = function (newLabel) { buttonText.setText(newLabel); }; self.setSelected = function (selected) { if (selected) { buttonGraphics.tint = 0x88AAFF; } else { buttonGraphics.tint = 0xFFFFFF; } }; self.down = function () { LK.getSound('click').play(); buttonGraphics.tint = 0x88AAFF; }; self.up = function () { if (self.onButtonClick) { self.onButtonClick(); } buttonGraphics.tint = 0xFFFFFF; }; return self; }); var ColorPicker = Container.expand(function () { var self = Container.call(this); var colorSchemes = { rainbow: [0xFF0000, 0xFF7F00, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x9400D3], fire: [0xFF0000, 0xFF5500, 0xFF8800, 0xFFAA00, 0xFFCC00, 0xFFFF00, 0xFFFFAA], ocean: [0x000033, 0x000066, 0x000099, 0x0000CC, 0x0000FF, 0x3399FF, 0x66CCFF], grayscale: [0x000000, 0x333333, 0x666666, 0x999999, 0xCCCCCC, 0xFFFFFF, 0xFFFFFF], neon: [0xFF00FF, 0xFF33FF, 0xFF66FF, 0xFF99FF, 0xFFCCFF, 0x00FFFF, 0x33FFFF] }; var currentScheme = storage.lastColorScheme || 'rainbow'; var background = self.attachAsset('controlPanel', { anchorX: 0, anchorY: 0, width: 180, height: 240 }); var title = new Text2("COLOR SCHEME", { size: 20, fill: 0xFFFFFF }); title.anchor.set(0.5, 0); title.x = 90; title.y = 10; self.addChild(title); var buttons = {}; var yPos = 40; Object.keys(colorSchemes).forEach(function (scheme, index) { var button = new Button(scheme, 140, 30); button.x = 90; button.y = yPos + index * 40; button.onButtonClick = function () { setColorScheme(scheme); }; buttons[scheme] = button; self.addChild(button); // Preview colors for (var i = 0; i < 5; i++) { var preview = self.attachAsset('colorPreview', { anchorX: 0.5, anchorY: 0.5, width: 15, height: 15 }); preview.tint = colorSchemes[scheme][i]; preview.x = 30 + i * 20; preview.y = button.y + 15; self.addChild(preview); } }); function setColorScheme(scheme) { if (currentScheme !== scheme) { buttons[currentScheme].setSelected(false); currentScheme = scheme; buttons[currentScheme].setSelected(true); storage.lastColorScheme = scheme; if (self.onSchemeChange) { self.onSchemeChange(scheme); } } } self.getColorScheme = function () { return currentScheme; }; self.getColor = function (intensity, maxIntensity) { var colors = colorSchemes[currentScheme]; if (!maxIntensity) { maxIntensity = 100; } if (intensity >= maxIntensity) { return 0x000000; // Black for values outside the set } var colorPos = intensity / maxIntensity * (colors.length - 1); var index1 = Math.floor(colorPos); var index2 = Math.min(colors.length - 1, index1 + 1); var fraction = colorPos - index1; return lerpColor(colors[index1], colors[index2], fraction); }; // Set initial selected scheme buttons[currentScheme].setSelected(true); return self; }); var FractalRenderer = Container.expand(function () { var self = Container.call(this); // Fractal renderer size - will be adjusted based on screen var rendererWidth = 1024; var rendererHeight = 1024; var resolution = 4; // Downscale for performance, will render at 256x256 var pixelSize = resolution; var pixels = []; var fractalContainer = new Container(); self.addChild(fractalContainer); self.generateFractal = function (type, params, colorScheme) { // Clear previous fractal for (var i = 0; i < pixels.length; i++) { pixels[i].destroy(); } pixels = []; fractalContainer.removeChildren(); // Calculate center of fractal var centerX = rendererWidth / 2; var centerY = rendererHeight / 2; // Get width and height in steps of resolution var stepsX = Math.ceil(rendererWidth / resolution); var stepsY = Math.ceil(rendererHeight / resolution); // Rendering functions for different fractal types var renderFunctions = { mandelbrot: renderMandelbrot, julia: renderJulia, burning_ship: renderBurningShip, sierpinski: renderSierpinski }; if (renderFunctions[type]) { renderFunctions[type](stepsX, stepsY, params, colorScheme); } else { console.log("Unknown fractal type:", type); } }; function renderMandelbrot(stepsX, stepsY, params, colorScheme) { var iterations = params.iterations; var zoom = params.zoom; var offsetX = params.offsetx; var offsetY = params.offsety; for (var y = 0; y < stepsY; y++) { for (var x = 0; x < stepsX; x++) { // Map pixel position to complex plane var zx = (x - stepsX / 2) / (stepsX / 4) / zoom + offsetX; var zy = (y - stepsY / 2) / (stepsY / 4) / zoom + offsetY; // Initial values for the calculation var cx = zx; var cy = zy; var i = 0; // Calculate mandelbrot value for this pixel while (i < iterations) { var tmp = zx * zx - zy * zy + cx; zy = 2 * zx * zy + cy; zx = tmp; // Check if point is outside circle of radius 2 if (zx * zx + zy * zy > 4) { break; } i++; } // Determine color based on iterations var color = colorScheme.getColor(i, iterations); // Create pixel createPixel(x * pixelSize, y * pixelSize, color); } } } function renderJulia(stepsX, stepsY, params, colorScheme) { var iterations = params.iterations; var zoom = params.zoom; var offsetX = params.offsetx; var offsetY = params.offsety; var cReal = params.juliaconstantreal; var cImag = params.juliaconstantimag; for (var y = 0; y < stepsY; y++) { for (var x = 0; x < stepsX; x++) { // Map pixel position to complex plane var zx = (x - stepsX / 2) / (stepsX / 4) / zoom + offsetX; var zy = (y - stepsY / 2) / (stepsY / 4) / zoom + offsetY; var i = 0; // Calculate julia value for this pixel while (i < iterations) { var tmp = zx * zx - zy * zy + cReal; zy = 2 * zx * zy + cImag; zx = tmp; // Check if point is outside circle of radius 2 if (zx * zx + zy * zy > 4) { break; } i++; } // Determine color based on iterations var color = colorScheme.getColor(i, iterations); // Create pixel createPixel(x * pixelSize, y * pixelSize, color); } } } function renderBurningShip(stepsX, stepsY, params, colorScheme) { var iterations = params.iterations; var zoom = params.zoom; var offsetX = params.offsetx; var offsetY = params.offsety; for (var y = 0; y < stepsY; y++) { for (var x = 0; x < stepsX; x++) { // Map pixel position to complex plane var zx = (x - stepsX / 2) / (stepsX / 4) / zoom + offsetX; var zy = (y - stepsY / 2) / (stepsY / 4) / zoom + offsetY; // Initial values for the calculation var cx = zx; var cy = zy; var i = 0; // Calculate burning ship value for this pixel while (i < iterations) { // The only difference from Mandelbrot is taking absolute values var tmp = zx * zx - zy * zy + cx; zy = Math.abs(2 * zx * zy) + cy; zx = tmp; // Check if point is outside circle of radius 2 if (zx * zx + zy * zy > 4) { break; } i++; } // Determine color based on iterations var color = colorScheme.getColor(i, iterations); // Create pixel createPixel(x * pixelSize, y * pixelSize, color); } } } function renderSierpinski(stepsX, stepsY, params, colorScheme) { var iterations = Math.min(10, params.iterations / 10); // Limit iterations for performance var zoom = params.zoom; var offsetX = params.offsetx * stepsX; var offsetY = params.offsety * stepsY; // Draw the entire canvas black first for (var y = 0; y < stepsY; y++) { for (var x = 0; x < stepsX; x++) { createPixel(x * pixelSize, y * pixelSize, 0x000000); } } // Initialize with a triangle var points = [{ x: stepsX / 2, y: 0 }, { x: 0, y: stepsY }, { x: stepsX, y: stepsY }]; function drawTriangle(p1, p2, p3, iteration) { if (iteration <= 0) { // Draw the triangle var triangle = [p1, p2, p3]; fillTriangle(triangle, colorScheme.getColor(iteration, iterations)); return; } // Calculate midpoints var mid1 = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 }; var mid2 = { x: (p2.x + p3.x) / 2, y: (p2.y + p3.y) / 2 }; var mid3 = { x: (p3.x + p1.x) / 2, y: (p3.y + p1.y) / 2 }; // Recursive calls for the three corners drawTriangle(p1, mid1, mid3, iteration - 1); drawTriangle(mid1, p2, mid2, iteration - 1); drawTriangle(mid3, mid2, p3, iteration - 1); } function fillTriangle(points, color) { // Basic triangle filling algorithm var minX = Math.min(points[0].x, points[1].x, points[2].x); var maxX = Math.max(points[0].x, points[1].x, points[2].x); var minY = Math.min(points[0].y, points[1].y, points[2].y); var maxY = Math.max(points[0].y, points[1].y, points[2].y); for (var y = minY; y <= maxY; y++) { for (var x = minX; x <= maxX; x++) { if (pointInTriangle(x, y, points[0], points[1], points[2])) { // Ensure we're inside the canvas bounds if (x >= 0 && x < stepsX && y >= 0 && y < stepsY) { setPixelColor(x, y, color); } } } } } function pointInTriangle(x, y, p1, p2, p3) { var denominator = (p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y); var a = ((p2.y - p3.y) * (x - p3.x) + (p3.x - p2.x) * (y - p3.y)) / denominator; var b = ((p3.y - p1.y) * (x - p3.x) + (p1.x - p3.x) * (y - p3.y)) / denominator; var c = 1 - a - b; return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1; } function setPixelColor(x, y, color) { var index = y * stepsX + x; if (pixels[index]) { pixels[index].setColor(color); } } // Scale and offset the triangle points.forEach(function (point) { point.x = point.x / zoom + offsetX; point.y = point.y / zoom + offsetY; }); // Draw the Sierpinski triangle drawTriangle(points[0], points[1], points[2], iterations); } function createPixel(x, y, color) { var pixel = new Pixel(); pixel.x = x; pixel.y = y; pixel.setColor(color); pixel.width = pixelSize; pixel.height = pixelSize; fractalContainer.addChild(pixel); pixels.push(pixel); } self.setSize = function (width, height) { rendererWidth = width; rendererHeight = height; }; return self; }); // Helper function to interpolate between two colors var FractalSelector = Container.expand(function () { var self = Container.call(this); var fractalTypes = ['mandelbrot', 'julia', 'burning_ship', 'sierpinski']; var currentType = storage.lastFractalType || 'mandelbrot'; var background = self.attachAsset('controlPanel', { anchorX: 0, anchorY: 0, width: 180, height: 240 }); var title = new Text2("FRACTAL TYPE", { size: 20, fill: 0xFFFFFF }); title.anchor.set(0.5, 0); title.x = 90; title.y = 10; self.addChild(title); var buttons = {}; var yPos = 40; fractalTypes.forEach(function (type, index) { var button = new Button(type, 140, 30); button.x = 90; button.y = yPos + index * 40; button.onButtonClick = function () { setFractalType(type); }; buttons[type] = button; self.addChild(button); }); function setFractalType(type) { if (currentType !== type) { buttons[currentType].setSelected(false); currentType = type; buttons[currentType].setSelected(true); storage.lastFractalType = type; if (self.onTypeChange) { self.onTypeChange(type); } } } self.getFractalType = function () { return currentType; }; // Set initial selected type buttons[currentType].setSelected(true); return self; }); var ParameterControl = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('controlPanel', { anchorX: 0, anchorY: 0, width: 180, height: 320 }); var title = new Text2("PARAMETERS", { size: 20, fill: 0xFFFFFF }); title.anchor.set(0.5, 0); title.x = 90; title.y = 10; self.addChild(title); // Default parameters for fractals var parameters = { iterations: 100, zoom: 1.0, offsetX: 0, offsetY: 0, juliaConstantReal: -0.7, juliaConstantImag: 0.27 }; var paramTexts = {}; var buttons = {}; var yPos = 40; // Create controls for iterations createParameterControl("iterations", 40, 100, 10); createParameterControl("zoom", 90, 1.0, 0.5); createParameterControl("offset X", 140, 0, 0.1); createParameterControl("offset Y", 190, 0, 0.1); createParameterControl("Julia Real", 240, -0.7, 0.05, "julia"); createParameterControl("Julia Imag", 290, 0.27, 0.05, "julia"); function createParameterControl(name, y, defaultValue, step, visibleFor) { var paramName = name.toLowerCase().replace(/\s+/g, ""); var label = new Text2(name + ":", { size: 16, fill: 0xFFFFFF }); label.anchor.set(0, 0.5); label.x = 10; label.y = y; self.addChild(label); var value = new Text2(defaultValue.toString(), { size: 16, fill: 0xFFFFFF }); value.anchor.set(0.5, 0.5); value.x = 100; value.y = y; self.addChild(value); paramTexts[paramName] = value; // Decrease button var minusButton = new Button("-", 30, 30); minusButton.x = 140; minusButton.y = y; minusButton.onButtonClick = function () { updateParameter(paramName, -step); }; self.addChild(minusButton); // Increase button var plusButton = new Button("+", 30, 30); plusButton.x = 175; plusButton.y = y; plusButton.onButtonClick = function () { updateParameter(paramName, step); }; self.addChild(plusButton); if (visibleFor) { label.visible = false; value.visible = false; minusButton.visible = false; plusButton.visible = false; // Store the buttons and visibility condition buttons[paramName] = { label: label, value: value, minus: minusButton, plus: plusButton, visibleFor: visibleFor }; } // Set the default value parameters[paramName] = defaultValue; } function updateParameter(name, delta) { var newValue = parameters[name] + delta; // Apply constraints based on parameter if (name === "iterations") { newValue = Math.max(10, Math.min(500, newValue)); } else if (name === "zoom") { newValue = Math.max(0.1, Math.min(10, newValue)); } parameters[name] = newValue; paramTexts[name].setText(newValue.toFixed(2).replace(/\.00$/, "")); if (self.onParameterChange) { self.onParameterChange(parameters); } } self.updateVisibility = function (fractalType) { // Show/hide parameters based on fractal type Object.keys(buttons).forEach(function (key) { var control = buttons[key]; var shouldBeVisible = control.visibleFor === fractalType; control.label.visible = shouldBeVisible; control.value.visible = shouldBeVisible; control.minus.visible = shouldBeVisible; control.plus.visible = shouldBeVisible; }); }; self.getParameters = function () { return parameters; }; return self; }); var Pixel = Container.expand(function () { var self = Container.call(this); var pixelGraphics = self.attachAsset('pixel', { anchorX: 0, anchorY: 0 }); self.setColor = function (color) { pixelGraphics.tint = color; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Helper function to interpolate between two colors // Main title function lerpColor(color1, color2, amount) { var r1 = color1 >> 16 & 0xFF; var g1 = color1 >> 8 & 0xFF; var b1 = color1 & 0xFF; var r2 = color2 >> 16 & 0xFF; var g2 = color2 >> 8 & 0xFF; var b2 = color2 & 0xFF; var r = Math.round(r1 + (r2 - r1) * amount); var g = Math.round(g1 + (g2 - g1) * amount); var b = Math.round(b1 + (b1 - b1) * amount); return r << 16 | g << 8 | b; } var title = new Text2("Fractal Explorer", { size: 60, fill: 0xFFFFFF }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 60; game.addChild(title); // Create fractal renderer var fractalRenderer = new FractalRenderer(); fractalRenderer.x = 250; // Offset to allow space for the controls fractalRenderer.y = 200; game.addChild(fractalRenderer); // Calculate renderer size based on screen var rendererSize = Math.min(2048 - 500, 2732 - 400); fractalRenderer.setSize(rendererSize, rendererSize); // Create controls var fractalSelector = new FractalSelector(); fractalSelector.x = 30; fractalSelector.y = 200; game.addChild(fractalSelector); var colorPicker = new ColorPicker(); colorPicker.x = 30; colorPicker.y = 450; game.addChild(colorPicker); var parameterControl = new ParameterControl(); parameterControl.x = 1848; // Right side of screen parameterControl.y = 200; game.addChild(parameterControl); // Create generate button var generateButton = new Button("GENERATE", 200, 60); generateButton.x = 2048 / 2; generateButton.y = 2732 - 100; generateButton.onButtonClick = generateFractal; game.addChild(generateButton); // Start background music LK.playMusic('ambient', { loop: true }); // Show initial fractal visibility parameterControl.updateVisibility(fractalSelector.getFractalType()); // Handle events when fractal type changes fractalSelector.onTypeChange = function (type) { parameterControl.updateVisibility(type); generateFractal(); }; // Handle events when parameters change parameterControl.onParameterChange = function (params) { // Debounce for performance if (generateTimer) { LK.clearTimeout(generateTimer); } generateTimer = LK.setTimeout(generateFractal, 300); }; // Handle events when color scheme changes colorPicker.onSchemeChange = function (scheme) { generateFractal(); }; var generateTimer = null; // Main function to generate the fractal function generateFractal() { var type = fractalSelector.getFractalType(); var params = parameterControl.getParameters(); // Show "Generating..." message generateButton.setLabel("GENERATING..."); // Use a timeout to allow the button text to update LK.setTimeout(function () { fractalRenderer.generateFractal(type, params, colorPicker); generateButton.setLabel("GENERATE"); }, 100); } // Generate initial fractal generateFractal(); // Pan and zoom variables var isDragging = false; var lastX = 0; var lastY = 0; // Handle input events for panning and zooming game.down = function (x, y, obj) { isDragging = true; lastX = x; lastY = y; }; game.move = function (x, y, obj) { if (isDragging) { // Convert screen coordinates to parameter offsets var params = parameterControl.getParameters(); var deltaX = (x - lastX) / (1000 * params.zoom); var deltaY = (y - lastY) / (1000 * params.zoom); // Update parameters params.offsetx -= deltaX; params.offsety -= deltaY; // Update UI and regenerate parameterControl.onParameterChange(params); // Update last position lastX = x; lastY = y; } }; game.up = function (x, y, obj) { isDragging = false; }; game.update = function () { // Main update loop - nothing needed here as the rendering is event-driven };
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,670 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1", {
+ savedFractals: [],
+ lastFractalType: "mandelbrot",
+ lastColorScheme: "rainbow"
+});
+
+/****
+* Classes
+****/
+var Button = Container.expand(function (label, width, height) {
+ var self = Container.call(this);
+ var buttonGraphics = self.attachAsset('controlButton', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: width || 120,
+ height: height || 60
+ });
+ var buttonText = new Text2(label, {
+ size: 24,
+ fill: 0xFFFFFF
+ });
+ buttonText.anchor.set(0.5, 0.5);
+ self.addChild(buttonText);
+ self.setLabel = function (newLabel) {
+ buttonText.setText(newLabel);
+ };
+ self.setSelected = function (selected) {
+ if (selected) {
+ buttonGraphics.tint = 0x88AAFF;
+ } else {
+ buttonGraphics.tint = 0xFFFFFF;
+ }
+ };
+ self.down = function () {
+ LK.getSound('click').play();
+ buttonGraphics.tint = 0x88AAFF;
+ };
+ self.up = function () {
+ if (self.onButtonClick) {
+ self.onButtonClick();
+ }
+ buttonGraphics.tint = 0xFFFFFF;
+ };
+ return self;
+});
+var ColorPicker = Container.expand(function () {
+ var self = Container.call(this);
+ var colorSchemes = {
+ rainbow: [0xFF0000, 0xFF7F00, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x9400D3],
+ fire: [0xFF0000, 0xFF5500, 0xFF8800, 0xFFAA00, 0xFFCC00, 0xFFFF00, 0xFFFFAA],
+ ocean: [0x000033, 0x000066, 0x000099, 0x0000CC, 0x0000FF, 0x3399FF, 0x66CCFF],
+ grayscale: [0x000000, 0x333333, 0x666666, 0x999999, 0xCCCCCC, 0xFFFFFF, 0xFFFFFF],
+ neon: [0xFF00FF, 0xFF33FF, 0xFF66FF, 0xFF99FF, 0xFFCCFF, 0x00FFFF, 0x33FFFF]
+ };
+ var currentScheme = storage.lastColorScheme || 'rainbow';
+ var background = self.attachAsset('controlPanel', {
+ anchorX: 0,
+ anchorY: 0,
+ width: 180,
+ height: 240
+ });
+ var title = new Text2("COLOR SCHEME", {
+ size: 20,
+ fill: 0xFFFFFF
+ });
+ title.anchor.set(0.5, 0);
+ title.x = 90;
+ title.y = 10;
+ self.addChild(title);
+ var buttons = {};
+ var yPos = 40;
+ Object.keys(colorSchemes).forEach(function (scheme, index) {
+ var button = new Button(scheme, 140, 30);
+ button.x = 90;
+ button.y = yPos + index * 40;
+ button.onButtonClick = function () {
+ setColorScheme(scheme);
+ };
+ buttons[scheme] = button;
+ self.addChild(button);
+ // Preview colors
+ for (var i = 0; i < 5; i++) {
+ var preview = self.attachAsset('colorPreview', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 15,
+ height: 15
+ });
+ preview.tint = colorSchemes[scheme][i];
+ preview.x = 30 + i * 20;
+ preview.y = button.y + 15;
+ self.addChild(preview);
+ }
+ });
+ function setColorScheme(scheme) {
+ if (currentScheme !== scheme) {
+ buttons[currentScheme].setSelected(false);
+ currentScheme = scheme;
+ buttons[currentScheme].setSelected(true);
+ storage.lastColorScheme = scheme;
+ if (self.onSchemeChange) {
+ self.onSchemeChange(scheme);
+ }
+ }
+ }
+ self.getColorScheme = function () {
+ return currentScheme;
+ };
+ self.getColor = function (intensity, maxIntensity) {
+ var colors = colorSchemes[currentScheme];
+ if (!maxIntensity) {
+ maxIntensity = 100;
+ }
+ if (intensity >= maxIntensity) {
+ return 0x000000; // Black for values outside the set
+ }
+ var colorPos = intensity / maxIntensity * (colors.length - 1);
+ var index1 = Math.floor(colorPos);
+ var index2 = Math.min(colors.length - 1, index1 + 1);
+ var fraction = colorPos - index1;
+ return lerpColor(colors[index1], colors[index2], fraction);
+ };
+ // Set initial selected scheme
+ buttons[currentScheme].setSelected(true);
+ return self;
+});
+var FractalRenderer = Container.expand(function () {
+ var self = Container.call(this);
+ // Fractal renderer size - will be adjusted based on screen
+ var rendererWidth = 1024;
+ var rendererHeight = 1024;
+ var resolution = 4; // Downscale for performance, will render at 256x256
+ var pixelSize = resolution;
+ var pixels = [];
+ var fractalContainer = new Container();
+ self.addChild(fractalContainer);
+ self.generateFractal = function (type, params, colorScheme) {
+ // Clear previous fractal
+ for (var i = 0; i < pixels.length; i++) {
+ pixels[i].destroy();
+ }
+ pixels = [];
+ fractalContainer.removeChildren();
+ // Calculate center of fractal
+ var centerX = rendererWidth / 2;
+ var centerY = rendererHeight / 2;
+ // Get width and height in steps of resolution
+ var stepsX = Math.ceil(rendererWidth / resolution);
+ var stepsY = Math.ceil(rendererHeight / resolution);
+ // Rendering functions for different fractal types
+ var renderFunctions = {
+ mandelbrot: renderMandelbrot,
+ julia: renderJulia,
+ burning_ship: renderBurningShip,
+ sierpinski: renderSierpinski
+ };
+ if (renderFunctions[type]) {
+ renderFunctions[type](stepsX, stepsY, params, colorScheme);
+ } else {
+ console.log("Unknown fractal type:", type);
+ }
+ };
+ function renderMandelbrot(stepsX, stepsY, params, colorScheme) {
+ var iterations = params.iterations;
+ var zoom = params.zoom;
+ var offsetX = params.offsetx;
+ var offsetY = params.offsety;
+ for (var y = 0; y < stepsY; y++) {
+ for (var x = 0; x < stepsX; x++) {
+ // Map pixel position to complex plane
+ var zx = (x - stepsX / 2) / (stepsX / 4) / zoom + offsetX;
+ var zy = (y - stepsY / 2) / (stepsY / 4) / zoom + offsetY;
+ // Initial values for the calculation
+ var cx = zx;
+ var cy = zy;
+ var i = 0;
+ // Calculate mandelbrot value for this pixel
+ while (i < iterations) {
+ var tmp = zx * zx - zy * zy + cx;
+ zy = 2 * zx * zy + cy;
+ zx = tmp;
+ // Check if point is outside circle of radius 2
+ if (zx * zx + zy * zy > 4) {
+ break;
+ }
+ i++;
+ }
+ // Determine color based on iterations
+ var color = colorScheme.getColor(i, iterations);
+ // Create pixel
+ createPixel(x * pixelSize, y * pixelSize, color);
+ }
+ }
+ }
+ function renderJulia(stepsX, stepsY, params, colorScheme) {
+ var iterations = params.iterations;
+ var zoom = params.zoom;
+ var offsetX = params.offsetx;
+ var offsetY = params.offsety;
+ var cReal = params.juliaconstantreal;
+ var cImag = params.juliaconstantimag;
+ for (var y = 0; y < stepsY; y++) {
+ for (var x = 0; x < stepsX; x++) {
+ // Map pixel position to complex plane
+ var zx = (x - stepsX / 2) / (stepsX / 4) / zoom + offsetX;
+ var zy = (y - stepsY / 2) / (stepsY / 4) / zoom + offsetY;
+ var i = 0;
+ // Calculate julia value for this pixel
+ while (i < iterations) {
+ var tmp = zx * zx - zy * zy + cReal;
+ zy = 2 * zx * zy + cImag;
+ zx = tmp;
+ // Check if point is outside circle of radius 2
+ if (zx * zx + zy * zy > 4) {
+ break;
+ }
+ i++;
+ }
+ // Determine color based on iterations
+ var color = colorScheme.getColor(i, iterations);
+ // Create pixel
+ createPixel(x * pixelSize, y * pixelSize, color);
+ }
+ }
+ }
+ function renderBurningShip(stepsX, stepsY, params, colorScheme) {
+ var iterations = params.iterations;
+ var zoom = params.zoom;
+ var offsetX = params.offsetx;
+ var offsetY = params.offsety;
+ for (var y = 0; y < stepsY; y++) {
+ for (var x = 0; x < stepsX; x++) {
+ // Map pixel position to complex plane
+ var zx = (x - stepsX / 2) / (stepsX / 4) / zoom + offsetX;
+ var zy = (y - stepsY / 2) / (stepsY / 4) / zoom + offsetY;
+ // Initial values for the calculation
+ var cx = zx;
+ var cy = zy;
+ var i = 0;
+ // Calculate burning ship value for this pixel
+ while (i < iterations) {
+ // The only difference from Mandelbrot is taking absolute values
+ var tmp = zx * zx - zy * zy + cx;
+ zy = Math.abs(2 * zx * zy) + cy;
+ zx = tmp;
+ // Check if point is outside circle of radius 2
+ if (zx * zx + zy * zy > 4) {
+ break;
+ }
+ i++;
+ }
+ // Determine color based on iterations
+ var color = colorScheme.getColor(i, iterations);
+ // Create pixel
+ createPixel(x * pixelSize, y * pixelSize, color);
+ }
+ }
+ }
+ function renderSierpinski(stepsX, stepsY, params, colorScheme) {
+ var iterations = Math.min(10, params.iterations / 10); // Limit iterations for performance
+ var zoom = params.zoom;
+ var offsetX = params.offsetx * stepsX;
+ var offsetY = params.offsety * stepsY;
+ // Draw the entire canvas black first
+ for (var y = 0; y < stepsY; y++) {
+ for (var x = 0; x < stepsX; x++) {
+ createPixel(x * pixelSize, y * pixelSize, 0x000000);
+ }
+ }
+ // Initialize with a triangle
+ var points = [{
+ x: stepsX / 2,
+ y: 0
+ }, {
+ x: 0,
+ y: stepsY
+ }, {
+ x: stepsX,
+ y: stepsY
+ }];
+ function drawTriangle(p1, p2, p3, iteration) {
+ if (iteration <= 0) {
+ // Draw the triangle
+ var triangle = [p1, p2, p3];
+ fillTriangle(triangle, colorScheme.getColor(iteration, iterations));
+ return;
+ }
+ // Calculate midpoints
+ var mid1 = {
+ x: (p1.x + p2.x) / 2,
+ y: (p1.y + p2.y) / 2
+ };
+ var mid2 = {
+ x: (p2.x + p3.x) / 2,
+ y: (p2.y + p3.y) / 2
+ };
+ var mid3 = {
+ x: (p3.x + p1.x) / 2,
+ y: (p3.y + p1.y) / 2
+ };
+ // Recursive calls for the three corners
+ drawTriangle(p1, mid1, mid3, iteration - 1);
+ drawTriangle(mid1, p2, mid2, iteration - 1);
+ drawTriangle(mid3, mid2, p3, iteration - 1);
+ }
+ function fillTriangle(points, color) {
+ // Basic triangle filling algorithm
+ var minX = Math.min(points[0].x, points[1].x, points[2].x);
+ var maxX = Math.max(points[0].x, points[1].x, points[2].x);
+ var minY = Math.min(points[0].y, points[1].y, points[2].y);
+ var maxY = Math.max(points[0].y, points[1].y, points[2].y);
+ for (var y = minY; y <= maxY; y++) {
+ for (var x = minX; x <= maxX; x++) {
+ if (pointInTriangle(x, y, points[0], points[1], points[2])) {
+ // Ensure we're inside the canvas bounds
+ if (x >= 0 && x < stepsX && y >= 0 && y < stepsY) {
+ setPixelColor(x, y, color);
+ }
+ }
+ }
+ }
+ }
+ function pointInTriangle(x, y, p1, p2, p3) {
+ var denominator = (p2.y - p3.y) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y);
+ var a = ((p2.y - p3.y) * (x - p3.x) + (p3.x - p2.x) * (y - p3.y)) / denominator;
+ var b = ((p3.y - p1.y) * (x - p3.x) + (p1.x - p3.x) * (y - p3.y)) / denominator;
+ var c = 1 - a - b;
+ return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1;
+ }
+ function setPixelColor(x, y, color) {
+ var index = y * stepsX + x;
+ if (pixels[index]) {
+ pixels[index].setColor(color);
+ }
+ }
+ // Scale and offset the triangle
+ points.forEach(function (point) {
+ point.x = point.x / zoom + offsetX;
+ point.y = point.y / zoom + offsetY;
+ });
+ // Draw the Sierpinski triangle
+ drawTriangle(points[0], points[1], points[2], iterations);
+ }
+ function createPixel(x, y, color) {
+ var pixel = new Pixel();
+ pixel.x = x;
+ pixel.y = y;
+ pixel.setColor(color);
+ pixel.width = pixelSize;
+ pixel.height = pixelSize;
+ fractalContainer.addChild(pixel);
+ pixels.push(pixel);
+ }
+ self.setSize = function (width, height) {
+ rendererWidth = width;
+ rendererHeight = height;
+ };
+ return self;
+});
+// Helper function to interpolate between two colors
+var FractalSelector = Container.expand(function () {
+ var self = Container.call(this);
+ var fractalTypes = ['mandelbrot', 'julia', 'burning_ship', 'sierpinski'];
+ var currentType = storage.lastFractalType || 'mandelbrot';
+ var background = self.attachAsset('controlPanel', {
+ anchorX: 0,
+ anchorY: 0,
+ width: 180,
+ height: 240
+ });
+ var title = new Text2("FRACTAL TYPE", {
+ size: 20,
+ fill: 0xFFFFFF
+ });
+ title.anchor.set(0.5, 0);
+ title.x = 90;
+ title.y = 10;
+ self.addChild(title);
+ var buttons = {};
+ var yPos = 40;
+ fractalTypes.forEach(function (type, index) {
+ var button = new Button(type, 140, 30);
+ button.x = 90;
+ button.y = yPos + index * 40;
+ button.onButtonClick = function () {
+ setFractalType(type);
+ };
+ buttons[type] = button;
+ self.addChild(button);
+ });
+ function setFractalType(type) {
+ if (currentType !== type) {
+ buttons[currentType].setSelected(false);
+ currentType = type;
+ buttons[currentType].setSelected(true);
+ storage.lastFractalType = type;
+ if (self.onTypeChange) {
+ self.onTypeChange(type);
+ }
+ }
+ }
+ self.getFractalType = function () {
+ return currentType;
+ };
+ // Set initial selected type
+ buttons[currentType].setSelected(true);
+ return self;
+});
+var ParameterControl = Container.expand(function () {
+ var self = Container.call(this);
+ var background = self.attachAsset('controlPanel', {
+ anchorX: 0,
+ anchorY: 0,
+ width: 180,
+ height: 320
+ });
+ var title = new Text2("PARAMETERS", {
+ size: 20,
+ fill: 0xFFFFFF
+ });
+ title.anchor.set(0.5, 0);
+ title.x = 90;
+ title.y = 10;
+ self.addChild(title);
+ // Default parameters for fractals
+ var parameters = {
+ iterations: 100,
+ zoom: 1.0,
+ offsetX: 0,
+ offsetY: 0,
+ juliaConstantReal: -0.7,
+ juliaConstantImag: 0.27
+ };
+ var paramTexts = {};
+ var buttons = {};
+ var yPos = 40;
+ // Create controls for iterations
+ createParameterControl("iterations", 40, 100, 10);
+ createParameterControl("zoom", 90, 1.0, 0.5);
+ createParameterControl("offset X", 140, 0, 0.1);
+ createParameterControl("offset Y", 190, 0, 0.1);
+ createParameterControl("Julia Real", 240, -0.7, 0.05, "julia");
+ createParameterControl("Julia Imag", 290, 0.27, 0.05, "julia");
+ function createParameterControl(name, y, defaultValue, step, visibleFor) {
+ var paramName = name.toLowerCase().replace(/\s+/g, "");
+ var label = new Text2(name + ":", {
+ size: 16,
+ fill: 0xFFFFFF
+ });
+ label.anchor.set(0, 0.5);
+ label.x = 10;
+ label.y = y;
+ self.addChild(label);
+ var value = new Text2(defaultValue.toString(), {
+ size: 16,
+ fill: 0xFFFFFF
+ });
+ value.anchor.set(0.5, 0.5);
+ value.x = 100;
+ value.y = y;
+ self.addChild(value);
+ paramTexts[paramName] = value;
+ // Decrease button
+ var minusButton = new Button("-", 30, 30);
+ minusButton.x = 140;
+ minusButton.y = y;
+ minusButton.onButtonClick = function () {
+ updateParameter(paramName, -step);
+ };
+ self.addChild(minusButton);
+ // Increase button
+ var plusButton = new Button("+", 30, 30);
+ plusButton.x = 175;
+ plusButton.y = y;
+ plusButton.onButtonClick = function () {
+ updateParameter(paramName, step);
+ };
+ self.addChild(plusButton);
+ if (visibleFor) {
+ label.visible = false;
+ value.visible = false;
+ minusButton.visible = false;
+ plusButton.visible = false;
+ // Store the buttons and visibility condition
+ buttons[paramName] = {
+ label: label,
+ value: value,
+ minus: minusButton,
+ plus: plusButton,
+ visibleFor: visibleFor
+ };
+ }
+ // Set the default value
+ parameters[paramName] = defaultValue;
+ }
+ function updateParameter(name, delta) {
+ var newValue = parameters[name] + delta;
+ // Apply constraints based on parameter
+ if (name === "iterations") {
+ newValue = Math.max(10, Math.min(500, newValue));
+ } else if (name === "zoom") {
+ newValue = Math.max(0.1, Math.min(10, newValue));
+ }
+ parameters[name] = newValue;
+ paramTexts[name].setText(newValue.toFixed(2).replace(/\.00$/, ""));
+ if (self.onParameterChange) {
+ self.onParameterChange(parameters);
+ }
+ }
+ self.updateVisibility = function (fractalType) {
+ // Show/hide parameters based on fractal type
+ Object.keys(buttons).forEach(function (key) {
+ var control = buttons[key];
+ var shouldBeVisible = control.visibleFor === fractalType;
+ control.label.visible = shouldBeVisible;
+ control.value.visible = shouldBeVisible;
+ control.minus.visible = shouldBeVisible;
+ control.plus.visible = shouldBeVisible;
+ });
+ };
+ self.getParameters = function () {
+ return parameters;
+ };
+ return self;
+});
+var Pixel = Container.expand(function () {
+ var self = Container.call(this);
+ var pixelGraphics = self.attachAsset('pixel', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ self.setColor = function (color) {
+ pixelGraphics.tint = color;
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
backgroundColor: 0x000000
-});
\ No newline at end of file
+});
+
+/****
+* Game Code
+****/
+// Helper function to interpolate between two colors
+// Main title
+function lerpColor(color1, color2, amount) {
+ var r1 = color1 >> 16 & 0xFF;
+ var g1 = color1 >> 8 & 0xFF;
+ var b1 = color1 & 0xFF;
+ var r2 = color2 >> 16 & 0xFF;
+ var g2 = color2 >> 8 & 0xFF;
+ var b2 = color2 & 0xFF;
+ var r = Math.round(r1 + (r2 - r1) * amount);
+ var g = Math.round(g1 + (g2 - g1) * amount);
+ var b = Math.round(b1 + (b1 - b1) * amount);
+ return r << 16 | g << 8 | b;
+}
+var title = new Text2("Fractal Explorer", {
+ size: 60,
+ fill: 0xFFFFFF
+});
+title.anchor.set(0.5, 0);
+title.x = 2048 / 2;
+title.y = 60;
+game.addChild(title);
+// Create fractal renderer
+var fractalRenderer = new FractalRenderer();
+fractalRenderer.x = 250; // Offset to allow space for the controls
+fractalRenderer.y = 200;
+game.addChild(fractalRenderer);
+// Calculate renderer size based on screen
+var rendererSize = Math.min(2048 - 500, 2732 - 400);
+fractalRenderer.setSize(rendererSize, rendererSize);
+// Create controls
+var fractalSelector = new FractalSelector();
+fractalSelector.x = 30;
+fractalSelector.y = 200;
+game.addChild(fractalSelector);
+var colorPicker = new ColorPicker();
+colorPicker.x = 30;
+colorPicker.y = 450;
+game.addChild(colorPicker);
+var parameterControl = new ParameterControl();
+parameterControl.x = 1848; // Right side of screen
+parameterControl.y = 200;
+game.addChild(parameterControl);
+// Create generate button
+var generateButton = new Button("GENERATE", 200, 60);
+generateButton.x = 2048 / 2;
+generateButton.y = 2732 - 100;
+generateButton.onButtonClick = generateFractal;
+game.addChild(generateButton);
+// Start background music
+LK.playMusic('ambient', {
+ loop: true
+});
+// Show initial fractal visibility
+parameterControl.updateVisibility(fractalSelector.getFractalType());
+// Handle events when fractal type changes
+fractalSelector.onTypeChange = function (type) {
+ parameterControl.updateVisibility(type);
+ generateFractal();
+};
+// Handle events when parameters change
+parameterControl.onParameterChange = function (params) {
+ // Debounce for performance
+ if (generateTimer) {
+ LK.clearTimeout(generateTimer);
+ }
+ generateTimer = LK.setTimeout(generateFractal, 300);
+};
+// Handle events when color scheme changes
+colorPicker.onSchemeChange = function (scheme) {
+ generateFractal();
+};
+var generateTimer = null;
+// Main function to generate the fractal
+function generateFractal() {
+ var type = fractalSelector.getFractalType();
+ var params = parameterControl.getParameters();
+ // Show "Generating..." message
+ generateButton.setLabel("GENERATING...");
+ // Use a timeout to allow the button text to update
+ LK.setTimeout(function () {
+ fractalRenderer.generateFractal(type, params, colorPicker);
+ generateButton.setLabel("GENERATE");
+ }, 100);
+}
+// Generate initial fractal
+generateFractal();
+// Pan and zoom variables
+var isDragging = false;
+var lastX = 0;
+var lastY = 0;
+// Handle input events for panning and zooming
+game.down = function (x, y, obj) {
+ isDragging = true;
+ lastX = x;
+ lastY = y;
+};
+game.move = function (x, y, obj) {
+ if (isDragging) {
+ // Convert screen coordinates to parameter offsets
+ var params = parameterControl.getParameters();
+ var deltaX = (x - lastX) / (1000 * params.zoom);
+ var deltaY = (y - lastY) / (1000 * params.zoom);
+ // Update parameters
+ params.offsetx -= deltaX;
+ params.offsety -= deltaY;
+ // Update UI and regenerate
+ parameterControl.onParameterChange(params);
+ // Update last position
+ lastX = x;
+ lastY = y;
+ }
+};
+game.up = function (x, y, obj) {
+ isDragging = false;
+};
+game.update = function () {
+ // Main update loop - nothing needed here as the rendering is event-driven
+};
\ No newline at end of file