const generations = 180; //min=0 max=300 step=1
const zoom = 1; //min=.5 max=5 step=.25
// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);

const VERTICAL = 0;
const HORIZONTAL = 1;
const ORIENTATIONS = [
    [[0, -1], [0, 1]],
    [[-1, 0], [1, 0]]
];

// Global code will be evaluated once.
init();
const turtle = new Turtle();

const gridSize = 1000;
const grid = Array.from({length: gridSize}).map((v, column) => Array.from({length: gridSize}).map((v, row) => 0));//[]));
const picks = [[createPick([gridSize/2,gridSize/2], HORIZONTAL)],[]];

let generation = 0;

// The walk function will be called until it returns false.
function walk(i) {
    const pick = picks[0].shift();

    drawPick(pick);
    
    ORIENTATIONS[pick[1]].map(e => V.add(pick[0], e)).forEach(l => {
        if(grid[l[0]][l[1]] < 2) picks[1].push(createPick(l, (pick[1]+1)%2));
    });
    
    if(picks[0].length == 0) {
        generation++;
        picks.shift();
        picks.push([]);
    }

    return generation < generations;
}

function createPick (location, orientation) {
    ORIENTATIONS[orientation].forEach(end => ((l) => grid[l[0]][l[1]]++)(V.add(location, end)));
    return [location, orientation];
}

function drawPick(pick) {
    turtle.jump(V.scale(V.add([-gridSize/2, -gridSize/2], V.add(pick[0], ORIENTATIONS[pick[1]][0])), zoom));
    turtle.goto(V.scale(V.add([-gridSize/2, -gridSize/2], V.add(pick[0], ORIENTATIONS[pick[1]][1])), zoom));
}

function init() {
    ///////////////////////////////////////////////////////
    // Vector functions - Created by Jurgen Westerhof 2024
    // https://turtletoy.net/turtle/d068ad6040
    ///////////////////////////////////////////////////////
    class Vector {
        static add  (a,b) { return a.map((v,i)=>v+b[i]); }
        static sub  (a,b) { return a.map((v,i)=>v-b[i]); }
        static mul  (a,b) { return a.map((v,i)=>v*b[i]); }
        static div  (a,b) { return a.map((v,i)=>v/b[i]); }
        static scale(a,s) { return a.map(v=>v*s); }
    
        static det(m)                { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); }
        static angle(a)              { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading
        static rot2d(angle)          { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; }
        static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; }
        static trans(matrix,a)       { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); }
        //Mirror vector a in a ray through [0,0] with direction mirror
        static mirror2d(a,mirror)    { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); }

        static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); }
        static norm  (a)     { return this.scale(a,1/this.len(a)); }
        static len   (a)     { return Math.hypot(...a); }
        static lenSq (a)     { return a.reduce((a,c)=>a+c**2,0); }
        static lerp  (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); }
        static dist  (a,b)   { return Math.hypot(...this.sub(a,b)); }
        
        static dot  (a,b)   { return a.reduce((a,c,i) => a+c*b[i], 0); }
        static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); }
    }
    this.V = Vector;
    
    class Intersection2D {
        //a-start, a-direction, b-start, b-direction
        //returns false on no intersection or [[intersection:x,y], scalar a-direction, scalar b-direction
        static info(as, ad, bs, bd) {
            const d = V.sub(bs, as), det = -V.det([bd, ad]);
            if(det === 0) return false;
            const res = [V.det([d, bd]) / det, V.det([d, ad]) / det];
            return [V.add(as, V.scale(ad, res[0])), ...res];
        }
        static ray(a, b, c, d) {
            return this.info(a, b, c, d);
        }
        static segment(a,b,c,d, inclusiveStart = true, inclusiveEnd = true) {
            const i = this.info(a, V.sub(b, a), c, V.sub(d, c));
            return i === false? false: (
                (inclusiveStart? 0<=i[1] && 0<=i[2]: 0<i[1] && 0<i[2])
             && (inclusiveEnd?   i[1]<=1 && i[2]<=1: i[1]<1 && i[2]<1)
            )?i[0]:false;
        }
        static tour(tour, segmentStart, segmentDirection) {
            return tour.map((e, i, a) => [i, this.info(e, V.sub(a[(i+1)%a.length], e), segmentStart, segmentDirection)])
                .filter(e => e[1] !== false && 0 <= e[1][1] && e[1][1] <= 1)
                .filter(e => 0 <= e[1][2])// && e[1][2] <= 1)
                .map(e => ({
                    position: e[1][0],
                    tourIndex: e[0],
                    tourSegmentPortion: e[1][1],
                    segmentPortion: e[1][2],
                })
            );
        }
        static inside(tour, pt) {
            return tour.map((e,i,a) => this.segment(e, a[(i+1)%a.length], pt, [Number.MAX_SAFE_INTEGER, 0], true, false)).filter(e => e !== false).length % 2 == 1
        }
    }
    this.Intersection = Intersection2D;
    
    
    class PathTools {
        static bezier(p1, cp1, cp2, p2, steps = null) {steps = (steps === null? (V.len(V.sub(cp1, p1)) + V.len(V.sub(cp2, cp1)) + V.len(V.sub(p2, cp2))) | 0: steps) - 1;return Array.from({length: steps + 1}).map((v, i, a, f = i/steps) => [[V.lerp(p1, cp1, f),V.lerp(cp1, cp2, f),V.lerp(cp2, p2, f)]].map(v => V.lerp(V.lerp(v[0], v[1], f), V.lerp(v[1], v[2], f), f))[0]);}
        // https://stackoverflow.com/questions/18655135/divide-bezier-curve-into-two-equal-halves#18681336
        static splitBezier(p1, cp1, cp2, p2, t=.5) {const e = V.lerp(p1, cp1, t);const f = V.lerp(cp1, cp2, t);const g = V.lerp(cp2, p2, t);const h = V.lerp(e, f, t);const j = V.lerp(f, g, t);const k = V.lerp(h, j, t);return [[p1, e, h, k], [k, j, g, p2]];}
        static circular(radius,verticeCount,rotation=0) {return Array.from({length: verticeCount}).map((e,i,a,f=i*2*Math.PI/verticeCount+rotation) => [radius*Math.cos(f),radius*Math.sin(f)])}
        static circle(r){return this.circular(r,Math.max(12, r*2*Math.PI|0));}
        static draw(turtle, path) {path.forEach((pt, i) => turtle[i==0?'jump':'goto'](pt));}
        static drawTour(turtle, path) {this.draw(turtle, path.concat([path[0]]));}
        static drawPoint(turtle, pt, r = .1) {this.drawTour(turtle, this.circle(r).map(e => V.add(e, pt)));}
        static drawArrow(turtle, s, d, width = 6, length = 3) {
            turtle.jump(s);
            const arrowHeadBase = V.add(s,d);
            turtle.goto(arrowHeadBase);
            turtle.goto(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, width/2])));
            turtle.jump(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, -width/2])));
            turtle.goto(arrowHeadBase);
        }
    }
    
    this.PT = PathTools;
}