Controlled lines 🎛️

Well, it turned out a bit more than 6 lines eventually...

Concept by u/mecobi at Reddit: reddit.com/r/generat…kpg/6_lines_of_code/

Log in to post a comment.

const n = 2000; //min=100 max=10000 step=100
const a = 5; //min=0 max=20 step=.01
const b = 3; //min=0 max=20 step=.01
const c = 3; //min=0 max=20 step=.01
const d = 5; //min=0 max=20 step=.01
const res = 10; //min=.1 max=100 step=.1
const xFormula = 'Math.cos((a*y/w)*res)+Math.cos((b*y/w)*res)'; //type=string
const yFormula = 'Math.sin((c*x/w)*res)-Math.cos((d*x/w)*res)'; //type=string
const remapAfterX = 1; //min=0 max=1 step=1 (No, Yes)

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(-.7);

// Global code will be evaluated once.
turtlelib_init();
const turtle = new Turtle();
const w = 200;
const ww = w/2;
const w8 = w/8;

const xFn = new fn(xFormula);
const yFn = new fn(yFormula);

// The walk function will be called until it returns false.
function walk(i) {
    turtle.jump((x = rnd(-w8, w+w8))-ww, (y = rnd(-w8, w+w8))-ww);
    for(let j = 0; j < w * 2; j++) {
        const params = {w: w, x: x, y: y, a: a, b: b, c: c, d: d, res: res};
        turtle.goto(
            (x += xFn.solve(params))-ww,
            (y += yFn.solve(remapAfterX == 0? params: {w: w, x: x, y: y, a: a, b: b, c: c, d: d, res: res}))-ww
        );
    }
    return i < n;
}

function rnd(lower, upper) {
    return lower + Math.random() * (upper - lower);
}

// Below is automatically maintained by Turtlelib 1.0
// Changes below this comment might interfere with its correct functioning.
function turtlelib_init() {
	turtlelib_ns_ed7b692d39_Jurgen_Formula();
}
// Turtlelib Jurgen Formula v 3 - start - {"id":"ed7b692d39","package":"Jurgen","name":"Formula","version":"3"}
function turtlelib_ns_ed7b692d39_Jurgen_Formula() {
/////////////////////////////////////////////////////////////////
// Formula parser and solver - Created by Jurgen Westerhof 2024
// https://turtletoy.net/turtle/187a81ec7d
/////////////////////////////////////////////////////////////////
function Formula(string) {
    const types = {
        'Function': 'Function',
        'Literal': 'Literal',
        'Variable': 'Variable',
        'Arithmetic': 'Arithmetic',
        'Unary': 'Unary',
    };
    const operators = [ //ordered by operator precedence (PEMDAS)
        ['**', (a, b) => a**b],
        ['*', (a, b) => a*b],
        ['/', (a, b) => a/b],
        ['%', (a, b) => a%b],
        ['+', (a, b) => a+b],
        ['-', (a, b) => a-b],
        ['<<', (a, b) => a<<b],
        ['>>', (a, b) => a>>b],
        ['|', (a, b) => a|b],
        ['^', (a, b) => a^b],
        ['&', (a, b) => a&b],
    ];
    class Formula {
        #variables;
        #parsed;
        #raw;
        #ready;
        constructor(string) {
            this.#raw = string;
            this.#variables = [];
            this.#parsed = this.tokenize(string);
        }
        getVariables() {
            return this.#variables.map(e => e);
        }
        getParsed() {
            const clone = (v) => (typeof v == 'object')? v.map(vv => clone(vv)): v;
            return this.#parsed.map(v => clone(v));
        }
        tokenize(str) {
            const tokens = [];
            let m;

            nextToken: for(let i = 0; i < str.length; i++) {
                //Skip whitespace
                if(/\s/.test(str[i])) continue;
                //Parse Math namespace
                if(str.substr(i, 5) == 'Math.') {
                    i += 5;
                    m = new RegExp(`^.{${i}}(?<payload>(?<const>[A-Z][A-Z0-9]*)|(?<fn>[a-z][a-z0-9]*)).*?`).exec(str);
                    if(Math[m.groups.payload] === undefined) {
                        console.error(`Math.${m.groups.payload} is undefined`);
                    }
                    if(m.groups.const) {
                        tokens.push([types.Literal, Math[m.groups.payload]]);
                    } else {
                        tokens.push([types.Function, m.groups.payload]);
                    }
                    i+=m.groups.payload.length-1;
                    continue nextToken;
                }
                //Parse variable
                // taking a shortcut here: unicode in variable names not accepted: https://stackoverflow.com/questions/1661197/what-characters-are-valid-for-javascript-variable-names
                m = new RegExp('^' + '\.'.repeat(i) + '(?<payload>[a-zA-Z_$][0-9a-zA-Z_$]*).*').exec(str);
                if(m !== null) {
                    tokens.push([types.Variable, m.groups.payload]);
                    if(!this.#variables.includes(m.groups.payload)) {
                        this.#variables.push(m.groups.payload);
                    }
                    i+= m.groups.payload.length - 1;
                    continue nextToken;
                }
                //Parse unary
                if((tokens.length == 0 || tokens[tokens.length - 1][0] == types.Arithmetic) && (str[i] == '-' || str[i] == '+' || str[i] == '~')) {
                    tokens.push([types.Unary, str[i]]);
                    continue nextToken;
                }
                //Parse (group) (including function parameters)
                if(str[i] == '(') {
                    const isFunction = (tokens.length > 0 && tokens[tokens.length - 1][0] == types.Function);
                    let cnt = 1;
                    let k = i + 1;
                    let j = 0;
                    let fnArgs = [];
                    for(; 0 < cnt && k+j < str.length; j++) {
                        if(str[k+j] == '(') cnt++;
                        if(str[k+j] == ')') cnt--;
                        if(str[k+j] == ',' && cnt == 1) {
                            fnArgs.push(this.tokenize(str.substr(i+1, j)));
                            i += j+1;
                            k += j+1;
                            j=0;
                        }
                    }
                    if(cnt == 0) {
                        if(isFunction) {
                            fnArgs.push(this.tokenize(str.substr(i+1, j-1)));
                            tokens[tokens.length - 1].push(fnArgs)
                        } else {
                            tokens.push(this.tokenize(str.substr(i+1, j-1)));
                        }
                        i += j;
                        continue nextToken;
                    }
                    console.error(`Opened bracket at character ${i} not closed: ${str.substr(i)}`);
                    throw new Error(`Opened bracket at character ${i} not closed: ${str.substr(i)}`);
                }
                //Parse literal
                m = new RegExp(`^.{${i}}(?<payload>\\d+(\\.\\d+)?|\\.\\d+).*?`).exec(str);
                if(m !== null) {
                    tokens.push([types.Literal, +m.groups.payload]);
                    i+=m.groups.payload.length-1;
                    continue nextToken;
                }
                //Parse operator
                m = new RegExp(`^.{${i}}(?<payload>\\${operators.map(o => o[0].split('').join('\\')).join('|\\')})`).exec(str);
                if(m !== null) {
                    tokens.push([types.Arithmetic, m.groups.payload]);
                    i+=m.groups.payload.length-1;
                    continue nextToken;
                }
                //Something I didn't think of occured
                console.error(`Unable to parse '${str}' because a character at ${i}: ${str[i]}`);
                throw new Error(`Unable to parse '${str}' because a character at ${i}: ${str[i]}`);
            }
            return tokens;
        }
        solve(variableMap = {}) {
            return this.solveInt(
                this.#parsed,
                //Map required variables to values assuming 0 if not set
                this.#variables.reduce((a, c) => {
                    let val = 0;
                    if(variableMap[c] === undefined) {
                        console.warn(`Variable ${c} not set in argument to solve() of ${this.#raw}.`);
                        throw new Error(`Variable ${c} not set in argument to solve() of ${this.#raw}.`);
                    } else {
                        val = variableMap[c];
                    }
                    return {...a, [c]: val};
                }, {})
            );
        }
        solveInt(tokenss, variableMap) {
            if(tokenss.length == 0) return 0;
            
            const clone = (v) => (typeof v == 'object')? v.map(vv => clone(vv)): v;
            const tokens = tokenss.map(v => clone(v));
            
            //Resolve functions
            for(let i = 0; i < tokens.length; i++) {
                if(tokens[i][0] == types.Function) {
                    const literals = tokens[i][2].map(v => this.solveInt(v, variableMap));
                    tokens[i] = [types.Literal, Math[tokens[i][1]].apply(null, literals)];
                }
            }
            //Resolve (group)s
            for(let i = 0; i < tokens.length; i++) {
                if(typeof tokens[i][0] == 'object') {
                    tokens[i] = [types.Literal, this.solveInt(tokens[i], variableMap)];
                }
                if(tokens[i][0] == types.Variable) {
                    tokens[i] = [types.Literal, typeof variableMap[tokens[i][1]] == 'function'? variableMap[tokens[i][1]](variableMap): variableMap[tokens[i][1]]];
                }
            }
            //Resolve unary
            for(let i = 0; i < tokens.length; i++) {
                if(tokens[i][0] == types.Unary) {
                    switch(tokens[i][1]) {
                        case '-':
                        case '+':
                            tokens[i+1][1] = tokens[i+1][1]*(tokens[i][1]=='-'?-1:1);
                            break;
                        case '~':
                            tokens[i+1][1] = ~tokens[i+1][1];
                    }
                    tokens.splice(i, 1);
                    i--;
                }
            }
            //Resolve operators
            operators.forEach(op => {
                for(let i = 0; i < tokens.length; i++) {
                    if(tokens[i][0] == types.Arithmetic && tokens[i][1] == op[0]) {
                        tokens[i-1][1] = op[1](tokens[i-1][1], tokens[i+1][1]);
                        tokens.splice(i, 2);
                        i-=2;
                    }
                }
            });
            //Get solution
            if(tokens.length == 1 && tokens[0][0] == types.Literal) {
                return tokens[0][1];
            }
            //Something I didn't think of occured
            console.error('Something went wrong solving token ' + i, tokens);
            throw new Error('Something went wrong solving token ' + i);
        }
    }
    return new Formula(string);
}
this.fn = Formula;
}
// Turtlelib Jurgen Formula v 3 - end