// Forked from "Packed in for a Good Wander" by ProjectGrantwood
// https://turtletoy.net/turtle/04187a129d
//
// Forked from "Beadpackpathwander" by ProjectGrantwood
// https://turtletoy.net/turtle/5f76422d98


Canvas.setpenopacity(1);


// ============================================================
// TEXT SETTINGS
// ============================================================

const TEXT = "SALAM";

// اندازه کلی نوشته
const TEXT_SCALE = 1.0;

// فاصله بین حروف
const LETTER_SPACING = 6;

// فاصله بین کلمات
const WORD_SPACING = 18;

// ضخامت / فاصله نقاط حروف
const LETTER_UNIT_SIZE = 4;


// ============================================================
// LETTER SIZE
// ============================================================

const LETTER_WIDTH = 5 * LETTER_UNIT_SIZE;
const LETTER_HEIGHT = 7 * LETTER_UNIT_SIZE;


// ============================================================
// TURTLE SETTINGS
// ============================================================

let shrinkageAcceleration = 50.0;
let shrinkageAcceleration_FineAdjust = 0.0;

let AngleIncrementA =-32; // min=-180, max=180, step=1
let AngleIncrementA_FineAdjust = 0.75; // min=0, max=1, step=0.01

let AngleIncrementB = 57; // min=-180, max=180, step=1
let AngleIncrementB_FineAdjust = 0.67; // min=0, max=1, step=0.01

let minimumRadius = 0.88; // min=0.05, max=4, step=0.01

let initialRadius = LETTER_UNIT_SIZE;

let maxFailures = 26; // min=0, max=180, step=1


shrinkageAcceleration =
    (101 - (shrinkageAcceleration + shrinkageAcceleration_FineAdjust)) / 1000;

initialRadius =
    minimumRadius > initialRadius
        ? minimumRadius
        : initialRadius;


const angleIncrement1 =
    (AngleIncrementA + AngleIncrementA_FineAdjust) * Math.PI / 180;

const angleIncrement2 =
    (AngleIncrementB + AngleIncrementB_FineAdjust) * Math.PI / 180;


const t = new Turtle();


// ============================================================
// 5 x 7 FONT
// ============================================================
const FONT = {

    A: [
        "10001",
        "10001",
        "10001",
        "11111",
        "10001",
        "10001",
        "01110"
    ],

    B: [
        "11110",
        "10001",
        "10001",
        "11110",
        "10001",
        "10001",
        "11110"
    ],

    C: [
        "01111",
        "10000",
        "10000",
        "10000",
        "10000",
        "10000",
        "01111"
    ],

    D: [
        "11110",
        "10001",
        "10001",
        "10001",
        "10001",
        "10001",
        "11110"
    ],

    E: [
        "11111",
        "10000",
        "10000",
        "11110",
        "10000",
        "10000",
        "11111"
    ],

    F: [
        "10000",
        "10000",
        "10000",
        "11110",
        "10000",
        "10000",
        "11111"
    ],

    G: [
        "01111",
        "10001",
        "10001",
        "10111",
        "10000",
        "10000",
        "01111"
    ],

    H: [
        "10001",
        "10001",
        "10001",
        "11111",
        "10001",
        "10001",
        "10001"
    ],

    I: [
        "11111",
        "00100",
        "00100",
        "00100",
        "00100",
        "00100",
        "11111"
    ],

    J: [
        "01100",
        "10010",
        "00010",
        "00010",
        "00010",
        "00010",
        "00111"
    ],

    K: [
        "10001",
        "10010",
        "10100",
        "11000",
        "10100",
        "10010",
        "10001"
    ],

    L: [
        "11111",
        "10000",
        "10000",
        "10000",
        "10000",
        "10000",
        "10000"
    ],

    M: [
        "10001",
        "10001",
        "10001",
        "10101",
        "10101",
        "11011",
        "10001"
    ],

    N: [
        "10001",
        "10011",
        "10011",
        "10101",
        "11001",
        "11001",
        "10001"
    ],

    O: [
        "01110",
        "10001",
        "10001",
        "10001",
        "10001",
        "10001",
        "01110"
    ],

    P: [
        "10000",
        "10000",
        "10000",
        "11110",
        "10001",
        "10001",
        "11110"
    ],

    Q: [
        "01101",
        "10010",
        "10101",
        "10001",
        "10001",
        "10001",
        "01110"
    ],

    R: [
        "10001",
        "10010",
        "10100",
        "11110",
        "10001",
        "10001",
        "11110"
    ],

    S: [
        "11110",
        "00001",
        "00001",
        "01110",
        "10000",
        "10000",
        "01111"
    ],

    T: [
        "00100",
        "00100",
        "00100",
        "00100",
        "00100",
        "00100",
        "11111"
    ],

    U: [
        "01110",
        "10001",
        "10001",
        "10001",
        "10001",
        "10001",
        "10001"
    ],

    V: [
        "00100",
        "01010",
        "10001",
        "10001",
        "10001",
        "10001",
        "10001"
    ],

    W: [
        "10001",
        "11011",
        "10101",
        "10101",
        "10001",
        "10001",
        "10001"
    ],

    X: [
        "10001",
        "10001",
        "01010",
        "00100",
        "01010",
        "10001",
        "10001"
    ],

    Y: [
        "00100",
        "00100",
        "00100",
        "00100",
        "01010",
        "10001",
        "10001"
    ],

    Z: [
        "11111",
        "10000",
        "01000",
        "00100",
        "00010",
        "00001",
        "11111"
    ],


    // ========================================================
    // NUMBERS
    // ========================================================

    "0": [
        "01110",
        "10001",
        "11001",
        "10101",
        "10011",
        "10001",
        "01110"
    ],

    "1": [
        "01110",
        "00100",
        "00100",
        "00100",
        "00100",
        "01100",
        "00100"
    ],

    "2": [
        "11111",
        "01000",
        "00100",
        "00010",
        "00001",
        "10001",
        "01110"
    ],

    "3": [
        "11110",
        "00001",
        "00001",
        "01110",
        "00001",
        "00001",
        "11110"
    ],

    "4": [
        "00010",
        "00010",
        "11111",
        "10010",
        "01010",
        "00110",
        "00010"
    ],

    "5": [
        "11110",
        "00001",
        "00001",
        "11110",
        "10000",
        "10000",
        "11111"
    ],

    "6": [
        "01110",
        "10001",
        "10001",
        "11110",
        "10000",
        "10000",
        "01110"
    ],

    "7": [
        "01000",
        "01000",
        "00100",
        "00010",
        "00001",
        "00001",
        "11111"
    ],

    "8": [
        "01110",
        "10001",
        "10001",
        "01110",
        "10001",
        "10001",
        "01110"
    ],

    "9": [
        "01110",
        "00001",
        "00001",
        "01111",
        "10001",
        "10001",
        "01110"
    ],


    // ========================================================
    // SPECIAL CHARACTERS
    // ========================================================

    "!": [
        "00100",
        "00000",
        "00100",
        "00100",
        "00100",
        "00100",
        "00100"
    ],

    "?": [
        "00100",
        "00000",
        "00100",
        "00010",
        "00001",
        "10001",
        "01110"
    ],

    "-": [
        "00000",
        "00000",
        "00000",
        "11111",
        "00000",
        "00000",
        "00000"
    ],

    "_": [
        "11111",
        "00000",
        "00000",
        "00000",
        "00000",
        "00000",
        "00000"
    ],

    ".": [
        "00110",
        "00110",
        "00000",
        "00000",
        "00000",
        "00000",
        "00000"
    ]

};

// ============================================================
// CREATE CIRCLE
// ============================================================

function createCircle(
    x,
    y,
    rad,
    heading,
    connectRadii = "00"
) {

    return [
        x,
        y,
        rad,
        heading,
        connectRadii
    ];
}


// ============================================================
// TEXT → CIRCLES
// ============================================================

function textToCircles(text) {

    let circles = [];

    text = text.toUpperCase();

    const charWidth =
        LETTER_WIDTH + LETTER_SPACING;

    const totalWidth =
        getTextWidth(text);

    let startX =
        -totalWidth / 2;

    for (let charIndex = 0; charIndex < text.length; charIndex++) {

        const char = text[charIndex];

        // فاصله
        if (char === " ") {

            startX += WORD_SPACING;

            continue;
        }


        const pattern = FONT[char];

        // اگر کاراکتر ناشناخته بود
        if (!pattern) {

            startX += charWidth;

            continue;
        }


        for (let row = 0; row < pattern.length; row++) {

            for (let col = 0; col < pattern[row].length; col++) {

                if (pattern[row][col] !== "1") {
                    continue;
                }


                const x =
                    startX +
                    col * LETTER_UNIT_SIZE;

                const y =
                    LETTER_HEIGHT / 2 -
                    row * LETTER_UNIT_SIZE;


                circles.push(
                    createCircle(
                        x,
                        y,
                        LETTER_UNIT_SIZE / 2,
                        -Math.PI / 2,
                        "00"
                    )
                );

            }

        }


        startX += charWidth;
    }


    return circles;
}


// ============================================================
// TEXT WIDTH
// ============================================================

function getTextWidth(text) {

    let width = 0;

    for (let i = 0; i < text.length; i++) {

        if (text[i] === " ") {

            width += WORD_SPACING;

        } else {

            width += LETTER_WIDTH + LETTER_SPACING;

        }

    }

    return width - LETTER_SPACING;
}


// ============================================================
// CREATE TEXT
// ============================================================

let circleArray = textToCircles(TEXT);


// ============================================================
// SCALE TEXT
// ============================================================

if (TEXT_SCALE !== 1) {

    for (let c of circleArray) {

        c[0] *= TEXT_SCALE;
        c[1] *= TEXT_SCALE;
        c[2] *= TEXT_SCALE;

    }

}


// ============================================================
// FIND TEXT BOUNDS
// ============================================================

function centerCircles(circles) {

    if (circles.length === 0) {
        return;
    }

    let minX = Infinity;
    let maxX = -Infinity;

    let minY = Infinity;
    let maxY = -Infinity;


    for (let c of circles) {

        minX = Math.min(minX, c[0]);
        maxX = Math.max(maxX, c[0]);

        minY = Math.min(minY, c[1]);
        maxY = Math.max(maxY, c[1]);

    }


    const centerX =
        (minX + maxX) / 2;

    const centerY =
        (minY + maxY) / 2;


    for (let c of circles) {

        c[0] -= centerX;
        c[1] -= centerY;

    }

}


centerCircles(circleArray);


// ============================================================
// PACKING SETTINGS
// ============================================================

let ratio = 0.8;

let ratio2 = 0.999;

const initialRatio = ratio;


let currentCircle =
    circleArray[0];

let failures = 0;


// ============================================================
// TURTLE
// ============================================================

t.radians();
t.pd();


let time = 0;


// ============================================================
// WALK
// ============================================================

function walk() {

    if (time === 0) {

        for (let z = 0; z < circleArray.length; z++) {

            draw(
                circleArray[z],
                t,
                "10"
            );

        }

        time += 1;

    } else {

        for (let j = 0; j < 200; j++) {

            currentCircle =
                getAndCheck(
                    currentCircle,
                    circleArray,
                    time
                );

        }

        time += 1;

    }


    return time < 20000;
}


// ============================================================
// DRAW
// ============================================================

function draw(
    c,
    turtle,
    i,
    flags = c[4]
) {

    turtle.jmp(
        c[0],
        c[1] - c[2]
    );


    // Circle

    if (flags[0] === "1") {

        turtle.jmp(
            c[0],
            c[1]
        );

        turtle.circle(
            c[2]
        );

    }


    // Connecting line

    if (flags[1] === "1") {

        turtle.jmp(
            c[0],
            c[1]
        );

        turtle.seth(
            c[3] - Math.PI
        );


        let travelDistance =
            c[2];


        travelDistance =
            c[2] === minimumRadius
                ? c[2] + c[2] + minimumRadius
                : c[2] + c[2] / ratio + minimumRadius;


        turtle.forward(
            travelDistance
        );

    }

}


// ============================================================
// GET NEXT CIRCLE
// ============================================================

function getNextCircle(c) {

    let radiusOld =
        c[2];


    let radiusNew =
        radiusOld * ratio;


    radiusNew =
        radiusNew < minimumRadius
            ? minimumRadius
            : radiusNew;


    let headingNew =
        c[3] + angleIncrement1;


    c[3] =
        headingNew;


    let d =
        (radiusOld + radiusNew) +
        minimumRadius;


    let xOld =
        c[0];

    let yOld =
        c[1];


    let xNew =
        xOld +
        d * Math.cos(headingNew);


    let yNew =
        yOld +
        d * Math.sin(headingNew);


    return createCircle(
        xNew,
        yNew,
        radiusNew,
        headingNew,
        "01"
    );

}


// ============================================================
// BOUNDS
// ============================================================

function checkBounds(c) {

    let add = true;


    add &=
        c[0] <
        100 - MARGIN - c[2];


    add &=
        c[0] >
        -100 + MARGIN + c[2];


    add &=
        c[1] <
        100 - MARGIN - c[2];


    add &=
        c[1] >
        -100 + MARGIN + c[2];


    return add;

}


// ============================================================
// COLLISION + WALK
// ============================================================

function getAndCheck(
    c1,
    circleArray,
    i
) {

    let c2 =
        getNextCircle(
            currentCircle
        );


    let add = 1;

    add *=
        checkBounds(c2);


    let newC;


    if (add) {

        for (let c of circleArray) {

            add *=
                checkCircleOverlap(
                    c,
                    c2
                );


            if (!add) {

                newC = c;

                break;

            }

        }

    }


    if (!add) {

        c1[3] +=
            angleIncrement2;


        failures++;


        if (failures >= maxFailures) {

            failures = 0;


            c1[3] +=
                angleIncrement2;


            ratio *=
                initialRatio +
                shrinkageAcceleration;


            let radius =
                initialRadius;


            let index =
                circleArray.indexOf(c1) - 1;


            index =
                index < 0 ||
                Math.random() < 0.04
                    ? Math.floor(
                        Math.random() *
                        circleArray.length
                    )
                    : index;


            return circleArray[index];

        }


        return currentCircle;

    } else {

        draw(
            c2,
            t,
            i
        );


        circleArray.push(
            c2
        );


        return c2;

    }

}


// ============================================================
// OVERLAP CHECK
// ============================================================

function checkCircleOverlap(
    c1,
    c2
) {

    const xd =
        (c2[0] - c1[0]) ** 2;


    const yd =
        (c2[1] - c1[1]) ** 2;


    const dsq =
        xd + yd;


    return (
        dsq >=
        (c1[2] + c2[2]) ** 2
    );

}


// ============================================================
// MAP
// ============================================================

function map(
    val,
    hi,
    lo,
    newhi,
    newlo
) {

    return (
        (val - lo) *
        (newhi - newlo)
    ) / (hi - lo) + newlo;

}


// ============================================================
// MARGIN
// ============================================================

const MARGIN =
    LETTER_UNIT_SIZE * 3.5;