Skip to main content
added 134 characters in body
Source Link
FoxWise
  • 83
  • 1
  • 9
------------@------@@------@------------
---------@@-@------@@------@-@@---------
-----------@@-@---@@-@---@-@@-----------
-----@------@@--@@@@-@@@--@@------@-----
----@@-----@-@@----@-----@@-@----@@@----
---@-@@@-@-@---@----@---@---@-@-@@@-@---
-------@-@-@----@---@--@--@-@---@-------
-@------@@---@@---@-@-@--@----@@------@-
---@@-@--@------@-@@@@-@------@--@-@@---
--@@@-@-@@@-@-@-@-@@@@@-@-@-@-@-@@@@@-@-
-@------@@----@-@-@-@-@@-@----@@------@-
-------@---@-@---@--@--@--@-@---@-------
---@-@@@-@-@----@---@---@---@-@-@@@-@---
----@@@----@-@-@----@----@@-@----@@@----
-----@------@-@-@@@-@@@@--@@------@-----
------------@@@---@-@@---@-@@-----------
---------@@@@------@@------@-@@---------
------------@------@@------@------------
-----------------@--@-@-----------------
----------------------------------------
 
----------@@--@-@-@@@@-@-@--@@----------
-------@--@------@-@@-@------@--@-------
@---@---@@-@------@@@@------@-@@---@---@
@-----@-@-@-@----@-@--@----@-@-@-@-----@
-@-@-@-@-@@@--@-@-@@-@-@-@--@@@-@-@-@-@-
-@-------@--@--@----@---@--@--@-------@-
-@@-------@-@-@@----@---@@-@-@-------@@-
--@-@----@@------@--@-@------@@----@-@--
@-@@-@@@---@------@-@@------@---@@@-@@-@
@-@@@@@@@-@@@-@-@-@-@@@-@-@-@-@-@@@-@@-@
--@-@----@@------@--@-@------@@----@-@--
-@@-------@-@-@@----@---@@-@-@-------@@-
-@-------@--@--@----@---@--@--@-------@-
-@-@-@-@-@@@--@-@-@-@@-@-@--@@@-@-@-@-@-
@-----@-@-@-@----@--@-@----@-@-@-@-----@
@---@---@@-@------@@@@------@-@@---@---@
-------@--@------@-@@-@------@--@-------
----------@@--@-@-@@@@-@-@--@@----------
------@-@-@-@--@--@--@--@--@-@-@-@------
----------------------------------------
 

EDIT: I see some people already posted their solutions on Codepen, so will I, here it is: https://codepen.io/Bohdan-Dudar/full/qEdLVNR

------------@------@@------@------------
---------@@-@------@@------@-@@---------
-----------@@-@---@@-@---@-@@-----------
-----@------@@--@@@@-@@@--@@------@-----
----@@-----@-@@----@-----@@-@----@@@----
---@-@@@-@-@---@----@---@---@-@-@@@-@---
-------@-@-@----@---@--@--@-@---@-------
-@------@@---@@---@-@-@--@----@@------@-
---@@-@--@------@-@@@@-@------@--@-@@---
--@@@-@-@@@-@-@-@-@@@@@-@-@-@-@-@@@@@-@-
-@------@@----@-@-@-@-@@-@----@@------@-
-------@---@-@---@--@--@--@-@---@-------
---@-@@@-@-@----@---@---@---@-@-@@@-@---
----@@@----@-@-@----@----@@-@----@@@----
-----@------@-@-@@@-@@@@--@@------@-----
------------@@@---@-@@---@-@@-----------
---------@@@@------@@------@-@@---------
------------@------@@------@------------
-----------------@--@-@-----------------
----------------------------------------
 
----------@@--@-@-@@@@-@-@--@@----------
-------@--@------@-@@-@------@--@-------
@---@---@@-@------@@@@------@-@@---@---@
@-----@-@-@-@----@-@--@----@-@-@-@-----@
-@-@-@-@-@@@--@-@-@@-@-@-@--@@@-@-@-@-@-
-@-------@--@--@----@---@--@--@-------@-
-@@-------@-@-@@----@---@@-@-@-------@@-
--@-@----@@------@--@-@------@@----@-@--
@-@@-@@@---@------@-@@------@---@@@-@@-@
@-@@@@@@@-@@@-@-@-@-@@@-@-@-@-@-@@@-@@-@
--@-@----@@------@--@-@------@@----@-@--
-@@-------@-@-@@----@---@@-@-@-------@@-
-@-------@--@--@----@---@--@--@-------@-
-@-@-@-@-@@@--@-@-@-@@-@-@--@@@-@-@-@-@-
@-----@-@-@-@----@--@-@----@-@-@-@-----@
@---@---@@-@------@@@@------@-@@---@---@
-------@--@------@-@@-@------@--@-------
----------@@--@-@-@@@@-@-@--@@----------
------@-@-@-@--@--@--@--@--@-@-@-@------
----------------------------------------
 
------------@------@@------@------------
---------@@-@------@@------@-@@---------
-----------@@-@---@@-@---@-@@-----------
-----@------@@--@@@@-@@@--@@------@-----
----@@-----@-@@----@-----@@-@----@@@----
---@-@@@-@-@---@----@---@---@-@-@@@-@---
-------@-@-@----@---@--@--@-@---@-------
-@------@@---@@---@-@-@--@----@@------@-
---@@-@--@------@-@@@@-@------@--@-@@---
--@@@-@-@@@-@-@-@-@@@@@-@-@-@-@-@@@@@-@-
-@------@@----@-@-@-@-@@-@----@@------@-
-------@---@-@---@--@--@--@-@---@-------
---@-@@@-@-@----@---@---@---@-@-@@@-@---
----@@@----@-@-@----@----@@-@----@@@----
-----@------@-@-@@@-@@@@--@@------@-----
------------@@@---@-@@---@-@@-----------
---------@@@@------@@------@-@@---------
------------@------@@------@------------
-----------------@--@-@-----------------
----------------------------------------
----------@@--@-@-@@@@-@-@--@@----------
-------@--@------@-@@-@------@--@-------
@---@---@@-@------@@@@------@-@@---@---@
@-----@-@-@-@----@-@--@----@-@-@-@-----@
-@-@-@-@-@@@--@-@-@@-@-@-@--@@@-@-@-@-@-
-@-------@--@--@----@---@--@--@-------@-
-@@-------@-@-@@----@---@@-@-@-------@@-
--@-@----@@------@--@-@------@@----@-@--
@-@@-@@@---@------@-@@------@---@@@-@@-@
@-@@@@@@@-@@@-@-@-@-@@@-@-@-@-@-@@@-@@-@
--@-@----@@------@--@-@------@@----@-@--
-@@-------@-@-@@----@---@@-@-@-------@@-
-@-------@--@--@----@---@--@--@-------@-
-@-@-@-@-@@@--@-@-@-@@-@-@--@@@-@-@-@-@-
@-----@-@-@-@----@--@-@----@-@-@-@-----@
@---@---@@-@------@@@@------@-@@---@---@
-------@--@------@-@@-@------@--@-------
----------@@--@-@-@@@@-@-@--@@----------
------@-@-@-@--@--@--@--@--@-@-@-@------
----------------------------------------

EDIT: I see some people already posted their solutions on Codepen, so will I, here it is: https://codepen.io/Bohdan-Dudar/full/qEdLVNR

Source Link
FoxWise
  • 83
  • 1
  • 9

Snowflake generator

Explanation

After analysing all possible different snowflakes from Microphysics of Clouds and Precipitation I figured only the planar type looks nice as a 2D ASCII art, so my focus is on those.

By looking at the snowflakes, I decided to parameterise each snowflake by the symmetry, length of primary branches, and length, period and angle of secondary (tertiary) branches that grow out of primary (secondary) branches. The lengths define the length of each branch. The period of a secondary (tertiary) branch defines the space between the two consecutive secondary (tertiary) branches along a primary (secondary) branch. The angle defines the angle between the secondary (tertiary) branch direction and the primary (secondary) branch direction on which it grows. So, in total, the snowflake is parametrised by:

  • Symmetry

  • Length of primary branches

  • Length of secondary branches

  • Period of secondary branches

  • Angle of secondary branches

  • Length of tertiary branches

  • Period of tertiary branches

  • Angle of tertiary branches

  • Background ASCII symbol

  • Snowflake ASCII symbol

The snowflakes are generated on a grid of background ASCII characters governed by the lengths and angles of all branches defined above.

The initially intended size of the grid is 120x60 (120 characters and 60 lines). This makes approximately 800x800 px and makes a nice resolution for the snowflakes.
However, after I discovered that the challenge entry does not allow screenshots and 120x60 is too big to be nicely formatted directly here, now this parameter is adjustable in the code...

For randomising these properties from a random seed, I used xorshift32amx pseudo-random number generator. The range of the properties was manually adjusted by looking what looks "nice".

The code

I used javascript, as it is the easiest to share with others and to practice my mediocre JS skills:

HTML code

<!-- index.html -->
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Snowflake Generator</title>
    <link type="text/css" rel="stylesheet" href="styles.css"
</head>

<body>
    <div style="display:flex;">
        <div class="menu">
            <div>
                <form id="generate-form">         
                    <h4>Customize:</h4>

                    <div class="menu-item">
                        <label for="symmetry" id="symmetry-label"> Symmetry: </label><br>
                        <input type="range" id="symmetry-input" name="symmetry" min="4" max="12" step="2" list="symmetry-steplist">
                        <datalist id="symmetry-steplist">
                            <option value="4" label="4"></option>
                            <option value="6" label="6"></option>
                            <option value="8" label="8"></option>
                            <option value="10" label="10"></option>
                            <option value="12" label="12"></option>
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="primary-branch-length" id="primary-branch-length-label"> Primary Branch Length: </label><br>
                        <input type="range" id="primary-branch-length-input" name="primary-branch-length" step="1" list="primary-branch-length-steplist">
                        <datalist id="primary-branch-length-steplist">
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="secondary-branch-length" id="secondary-branch-length-label"> Secondary Branch Length: </label><br>
                        <input type="range" id="secondary-branch-length-input" name="secondary-branch-length" step="1" list="secondary-branch-length-steplist">
                        <datalist id="secondary-branch-length-steplist">
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="secondary-branch-period" id="secondary-branch-period-label"> Secondary Branch Period: </label><br>
                        <input type="range" id="secondary-branch-period-input" name="secondary-branch-period" step="1" list="secondary-branch-period-steplist">
                        <datalist id="secondary-branch-period-steplist">
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="secondary-branch-angle" id="secondary-branch-angle-label"> Secondary Branch Angle: </label><br>
                        <input type="range" id="secondary-branch-angle-input" name="secondary-branch-angle" min="0" max="180" step="1" list="secondary-branch-angle-steplist">
                        <datalist id="secondary-branch-angle-steplist">
                            <option value="0" label="0"></option>
                            <option value="45" label="45"></option>
                            <option value="90" label="90"></option>
                            <option value="135" label="135"></option>
                            <option value="180" label="180"></option>
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="tertiary-branch-length" id="tertiary-branch-length-label"> Tertiary Branch Length: </label><br>
                        <input type="range" id="tertiary-branch-length-input" name="tertiary-branch-length" step="1" list="tertiary-branch-length-steplist">
                        <datalist id="tertiary-branch-length-steplist">
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="tertiary-branch-period" id="tertiary-branch-period-label"> Tertiary Branch Period: </label><br>
                        <input type="range" id="tertiary-branch-period-input" name="tertiary-branch-period" step="1" list="tertiary-branch-period-steplist">
                        <datalist id="tertiary-branch-period-steplist">
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="tertiary-branch-angle" id="tertiary-branch-angle-label"> Tertiary Branch Angle: </label><br>
                        <input type="range" id="tertiary-branch-angle-input" name="tertiary-branch-angle" min="0" max="180" step="1" list="tertiary-branch-angle-steplist">
                        <datalist id="tertiary-branch-angle-steplist">
                            <option value="0" label="0"></option>
                            <option value="45" label="45"></option>
                            <option value="90" label="90"></option>
                            <option value="135" label="135"></option>
                            <option value="180" label="180"></option>
                        </datalist>
                    </div>

                    <div class="menu-item">
                        <label for="background-symbol" id="background-symbol-label"> Background Symbol: </label><br>
                        <input type="text" id="background-symbol-input" name="background-symbol" value="-" maxlength="1">
                    </div>

                    <div class="menu-item">
                        <label for="snowflake-symbol" id="snowflake-symbol-label"> Snowflake Symbol: </label><br>
                        <input type="text" id="snowflake-symbol-input" name="snowflake-symbol" value="@" maxlength="1">
                    </div>
                    
                    <h4>Generate:</h4>
                    <div class="menu-item">
                        <label for="seed" id="seed-label"> Seed: </label><br>
                        <input type="text" id="seed-input" name="seed" value="12345">
                        <input type="button" id="generate-seed" value="Generate with Seed">
                    </div>

                    <div class="menu-item">
                        <input type="button" id="generate-random" value="I'm feeling Lucky">
                    </div>

                </form>
            </div>
        </div>
        

        <pre id="canvas" style="height: 1000px; width:1000px;"></pre>
    
    </div>
    
    <script type="text/javascript" src="main.js"></script>
    
</body>
</html>

CSS code

/* styles.css */
html, body{
    height:100%;
    margin:0;
    padding:0;
}

.menu{
    display:flex;
    justify-content: column;
    padding: 5px;
    width:250px;
}

datalist {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    writing-mode: horizontal-tb;
    width: 200px;
}

option {
  padding: 0;
}

input[type="range"] {
  width: 200px;
  margin: 0;
}

input[type="range"] {
  width: 200px;
  margin: 0;
}


.menu-item{
    display: flex;
    flex-direction: column;
    align-items: center;
    margin:12px 8px;
}

JS code

// main.js
const canvas = document.getElementById("canvas");
const size = 60 // adjustible number of lines in canvas to use for drawing
const halfSize = size/2;

function getSymmetry(rand){ return 4 + 2*Math.floor(5*rand); }
function getPrimaryBranchLength(rand){ return Math.round(halfSize*rand) }
function getSecondaryBranchLength(rand){ return Math.round(halfSize*rand) }
function getTertiaryBranchLength(rand){ return Math.round(halfSize/5*rand) }
function getSecondaryBranchPeriod(rand){ return Math.round(3 + halfSize/5*rand) }
function getTertiaryBranchPeriod(rand){ return Math.round(3 + halfSize/5*rand) }
function getAngle(rand){ return Math.PI*rand }


//make length-dependent controls relative to the size of the canvas:
document.getElementById('primary-branch-length-input').min = getPrimaryBranchLength(0);
document.getElementById('primary-branch-length-input').max = getPrimaryBranchLength(1);
for (let i = 0; i < 5; i++) {
    const option = document.createElement('option');
    option.value = getPrimaryBranchLength(i/4);
    option.label = `${i*25}`;
    document.getElementById('primary-branch-length-steplist').appendChild(option);
}

document.getElementById('secondary-branch-length-input').min = getSecondaryBranchLength(0);
document.getElementById('secondary-branch-length-input').max = getSecondaryBranchLength(1);
for (let i = 0; i < 5; i++) {
    const option = document.createElement('option');
    option.value = getSecondaryBranchLength(i/4);
    option.label = `${i*25}`;
    document.getElementById('secondary-branch-length-steplist').appendChild(option);
}

document.getElementById('tertiary-branch-length-input').min = getTertiaryBranchLength(0);
document.getElementById('tertiary-branch-length-input').max = getTertiaryBranchLength(1);
for (let i = 0; i < 5; i++) {
    const option = document.createElement('option');
    option.value = getTertiaryBranchLength(i/4);
    option.label = `${i*25}`;
    document.getElementById('tertiary-branch-length-steplist').appendChild(option);
}

document.getElementById('secondary-branch-period-input').min = getSecondaryBranchPeriod(0);
document.getElementById('secondary-branch-period-input').max = getSecondaryBranchPeriod(1);
const secondaryBranchPeriodOption0 = document.createElement('option');
secondaryBranchPeriodOption0.value = getSecondaryBranchPeriod(0);
secondaryBranchPeriodOption0.label = `Frequent`;
document.getElementById('secondary-branch-period-steplist').appendChild(secondaryBranchPeriodOption0);
const secondaryBranchPeriodOption100 = document.createElement('option');
secondaryBranchPeriodOption100.value = getSecondaryBranchPeriod(1);
secondaryBranchPeriodOption100.label = `Sparse`;
document.getElementById('secondary-branch-period-steplist').appendChild(secondaryBranchPeriodOption100);


document.getElementById('tertiary-branch-period-input').min = getTertiaryBranchPeriod(0);
document.getElementById('tertiary-branch-period-input').max = getTertiaryBranchPeriod(1);
const tertiaryBranchPeriodOption0 = document.createElement('option');
tertiaryBranchPeriodOption0.value = getTertiaryBranchPeriod(0);
tertiaryBranchPeriodOption0.label = `Frequent`;
document.getElementById('tertiary-branch-period-steplist').appendChild(tertiaryBranchPeriodOption0);
const tertiaryBranchPeriodOption100 = document.createElement('option');
tertiaryBranchPeriodOption100.value = getTertiaryBranchPeriod(1);
tertiaryBranchPeriodOption100.label = `Sparse`;
document.getElementById('tertiary-branch-period-steplist').appendChild(tertiaryBranchPeriodOption100);


function generateRandomSnowflakeProperties(seed){
    const getRand = xorshift32amx(seed);

    const symmetry = getSymmetry(getRand());
    const primaryBranchLength = getPrimaryBranchLength(getRand());
    const secondaryBranchLength = getSecondaryBranchLength(getRand());
    const tertiaryBranchLength = getTertiaryBranchLength(getRand());
    const secondaryBranchPeriod = getSecondaryBranchPeriod(getRand());
    const tertiaryBranchPeriod = getTertiaryBranchPeriod(getRand());
    const secondaryBranchAngle = getAngle(getRand());
    const tertiaryBranchAngle = getAngle(getRand());
    const properties = [symmetry, primaryBranchLength, secondaryBranchLength, secondaryBranchPeriod, secondaryBranchAngle, tertiaryBranchLength, tertiaryBranchPeriod, tertiaryBranchAngle];
    return properties;
}


//random generator from https://github.com/bryc/code/blob/master/jshash/PRNGs.md
function xorshift32amx(a) {
    return function() {
        var t = Math.imul(a, 1597334677);
        t = t>>>24 | t>>>8&65280 | t<<8&16711680 | t<<24; // reverse byte order
        a ^= a << 13; a ^= a >>> 17; a ^= a << 5;
        return (a + t >>> 0) / 4294967296;
    }
}



class Snowflake {
    constructor(symmetry, primaryBranchLength, secondaryBranchLength, secondaryBranchPeriod, secondaryBranchAngle, tertiaryBranchLength, tertiaryBranchPeriod, tertiaryBranchAngle){

        this.symmetry = symmetry;
        this.primaryBranchLength = primaryBranchLength;
        this.secondaryBranchLength = secondaryBranchLength;
        this.secondaryBranchPeriod = secondaryBranchPeriod;
        this.secondaryBranchAngle = secondaryBranchAngle;
        this.tertiaryBranchLength = tertiaryBranchLength;
        this.tertiaryBranchPeriod = tertiaryBranchPeriod;
        this.tertiaryBranchAngle = tertiaryBranchAngle;
        
        this.asciArt = "";
        this.snowflakeSymbol = "@";
        this.backgroundSymbol = "-";

        this.xmax = 2*size;
        this.ymax = size;
        this.xPrimaryCenter = Math.floor(this.xmax/2);
        this.yPrimaryCenter = Math.floor(this.ymax/2);
    }

    backgroundFillAsciText(){
        this.asciArt = "";
        for (let y = 0; y < this.ymax; y++) {
            for (let x = 0; x < this.xmax; x++) {
                this.asciArt += this.backgroundSymbol;
            }
            this.asciArt += '\n';
        }
    }


    getAsciTextPos(x, y){
        if (x < 0 || x >= this.xmax) return -1;
        if (y < 0 || y >= this.ymax) return -1;
        return x+1-1 + (y-1)*(this.xmax+1);
    }

    updateInputFields(){
        document.getElementById("symmetry-input").value = this.symmetry;
        document.getElementById("primary-branch-length-input").value = this.primaryBranchLength;
        document.getElementById("secondary-branch-length-input").value = this.secondaryBranchLength;
        document.getElementById("secondary-branch-period-input").value = this.secondaryBranchPeriod;
        document.getElementById("secondary-branch-angle-input").value = Math.round(this.secondaryBranchAngle*180/Math.PI);
        document.getElementById("tertiary-branch-length-input").value = this.tertiaryBranchLength;
        document.getElementById("tertiary-branch-period-input").value = this.tertiaryBranchPeriod;
        document.getElementById("tertiary-branch-angle-input").value = Math.round(this.tertiaryBranchAngle*180/Math.PI);
        document.getElementById("background-symbol-input").value = this.backgroundSymbol;
        document.getElementById("snowflake-symbol-input").value = this.snowflakeSymbol;
    }

    calculateAsciArtPositions(){
        this.asciArtPositions = [];
        for(let i=0; i < this.symmetry; i++){
            const xPrimaryDir = Math.cos(i*2*Math.PI/this.symmetry);
            const yPrimaryDir = Math.sin(i*2*Math.PI/this.symmetry);
            for(let i2=0; i2 < this.primaryBranchLength; i2++){
                const xToDraw = Math.floor(this.xPrimaryCenter + 2*i2*xPrimaryDir);
                const yToDraw = Math.floor(this.yPrimaryCenter + i2*yPrimaryDir);
                const globIdx = this.getAsciTextPos(xToDraw, yToDraw);
                if (globIdx != -1) this.asciArtPositions.push(globIdx);
            }

            const xSecondaryLeftDir = Math.cos(i*2*Math.PI/this.symmetry - this.secondaryBranchAngle);
            const ySecondaryLeftDir = Math.sin(i*2*Math.PI/this.symmetry - this.secondaryBranchAngle);
            const xSecondaryRightDir = Math.cos(i*2*Math.PI/this.symmetry + this.secondaryBranchAngle);
            const ySecondaryRightDir = Math.sin(i*2*Math.PI/this.symmetry + this.secondaryBranchAngle);

            for(let i2=this.secondaryBranchPeriod; i2 < this.primaryBranchLength; i2+=this.secondaryBranchPeriod){
                const xSecondaryCenter = this.xPrimaryCenter + 2*i2*xPrimaryDir;
                const ySecondaryCenter = this.yPrimaryCenter + i2*yPrimaryDir;

                for(let i3=0; i3 < this.secondaryBranchLength; i3++){
                    const xLeftToDraw = Math.floor(xSecondaryCenter + 2*i3*xSecondaryLeftDir);
                    const yLeftToDraw = Math.floor(ySecondaryCenter + i3*ySecondaryLeftDir);
                    const globIdxLeft = this.getAsciTextPos(xLeftToDraw, yLeftToDraw);
                    if (globIdxLeft != -1) this.asciArtPositions.push(globIdxLeft);

                    const xRightToDraw = Math.floor(xSecondaryCenter + 2*i3*xSecondaryRightDir);
                    const yRightToDraw = Math.floor(ySecondaryCenter + i3*ySecondaryRightDir);
                    const globIdxRight = this.getAsciTextPos(xRightToDraw, yRightToDraw);
                    if (globIdxRight != -1) this.asciArtPositions.push(globIdxRight);
                }
    
                const xTertiaryLLDir = Math.cos(i*2*Math.PI/this.symmetry - this.secondaryBranchAngle - this.tertiaryBranchAngle);
                const yTertiaryLLDir = Math.sin(i*2*Math.PI/this.symmetry - this.secondaryBranchAngle - this.tertiaryBranchAngle);
                const xTertiaryLRDir = Math.cos(i*2*Math.PI/this.symmetry - this.secondaryBranchAngle + this.tertiaryBranchAngle);
                const yTertiaryLRDir = Math.sin(i*2*Math.PI/this.symmetry - this.secondaryBranchAngle + this.tertiaryBranchAngle);
    
                const xTertiaryRLDir = Math.cos(i*2*Math.PI/this.symmetry + this.secondaryBranchAngle - this.tertiaryBranchAngle);
                const yTertiaryRLDir = Math.sin(i*2*Math.PI/this.symmetry + this.secondaryBranchAngle - this.tertiaryBranchAngle);
                const xTertiaryRRDir = Math.cos(i*2*Math.PI/this.symmetry + this.secondaryBranchAngle + this.tertiaryBranchAngle);
                const yTertiaryRRDir = Math.sin(i*2*Math.PI/this.symmetry + this.secondaryBranchAngle + this.tertiaryBranchAngle);
    
                for(let i3=this.tertiaryBranchPeriod; i3 < this.secondaryBranchLength; i3+=this.tertiaryBranchPeriod){
                    const xTertiaryCenterLeft = xSecondaryCenter + 2*i3*xSecondaryLeftDir;
                    const yTertiaryCenterLeft = ySecondaryCenter + i3*ySecondaryLeftDir;
                    const xTertiaryCenterRight = xSecondaryCenter + 2*i3*xSecondaryRightDir;
                    const yTertiaryCenterRight = ySecondaryCenter + i3*ySecondaryRightDir;

                    for(let i4=0; i4 < this.tertiaryBranchLength; i4++){
                        const xLLToDraw = Math.floor(xTertiaryCenterLeft + 2*i4*xTertiaryLLDir);
                        const yLLToDraw = Math.floor(yTertiaryCenterLeft + i4*yTertiaryLLDir);
                        const globIdxLL = this.getAsciTextPos(xLLToDraw, yLLToDraw);
                        if (globIdxLL != -1) this.asciArtPositions.push(globIdxLL);

                        const xLRToDraw = Math.floor(xTertiaryCenterLeft + 2*i4*xTertiaryLRDir);
                        const yLRToDraw = Math.floor(yTertiaryCenterLeft + i4*yTertiaryLRDir);
                        const globIdxLR = this.getAsciTextPos(xLRToDraw, yLRToDraw);
                        if (globIdxLR != -1) this.asciArtPositions.push(globIdxLR);

                        const xRLToDraw = Math.floor(xTertiaryCenterRight + 2*i4*xTertiaryRLDir);
                        const yRLToDraw = Math.floor(yTertiaryCenterRight + i4*yTertiaryRLDir);
                        const globIdxRL = this.getAsciTextPos(xRLToDraw, yRLToDraw);
                        if (globIdxRL != -1) this.asciArtPositions.push(globIdxRL);

                        const xRRToDraw = Math.floor(xTertiaryCenterRight + 2*i4*xTertiaryRRDir);
                        const yRRToDraw = Math.floor(yTertiaryCenterRight + i4*yTertiaryRRDir);
                        const globIdxRR = this.getAsciTextPos(xRRToDraw, yRRToDraw);
                        if (globIdxRR != -1) this.asciArtPositions.push(globIdxRR);
                    }
                }
            }
        }

    }

    renderAsci(){
        this.backgroundFillAsciText();
        let newAsciArt = "";
        for(let i=0; i < this.asciArt.length; i++){
            if ( this.asciArtPositions.includes(i) && this.asciArt[i] != '\n' ) newAsciArt += this.snowflakeSymbol;
            else newAsciArt += this.asciArt[i];
        }
        this.asciArt = newAsciArt;
        canvas.textContent = this.asciArt;
    }


    draw(canvas){
        this.updateInputFields();
        this.calculateAsciArtPositions();
        this.renderAsci(this.asciArtPositions, canvas);
    }

}

function createFromSeed(seed){
    const randomProperties = generateRandomSnowflakeProperties(seed);
    snowflake = new Snowflake(...randomProperties);
    snowflake.draw(canvas);
}


function generate(event){
    event.preventDefault();
    const seed = parseInt( document.getElementById("seed-input").value );
    createFromSeed(seed);
}

function generateRandom(event){
    event.preventDefault();
    const seed = Math.floor(Math.random()*100000);
    document.getElementById("seed-input").value = seed;
    createFromSeed(seed);
}


document.getElementById("generate-seed").addEventListener('click', generate);
document.getElementById("generate-random").addEventListener('click', generateRandom);


//Controls
document.getElementById("symmetry-input").oninput = function() {
    snowflake.symmetry = parseInt(this.value);
    snowflake.draw(canvas);
}
document.getElementById("primary-branch-length-input").oninput = function() {
    snowflake.primaryBranchLength = parseInt(this.value);
    snowflake.draw(canvas);
}
document.getElementById("secondary-branch-length-input").oninput = function() {
    snowflake.secondaryBranchLength = parseInt(this.value);
    snowflake.draw(canvas);
}
document.getElementById("secondary-branch-period-input").oninput = function() {
    snowflake.secondaryBranchPeriod = parseInt(this.value);
    snowflake.draw(canvas);
}
document.getElementById("secondary-branch-angle-input").oninput = function() {
    snowflake.secondaryBranchAngle = parseFloat(this.value*Math.PI/180.);
    snowflake.draw(canvas);
}
document.getElementById("tertiary-branch-length-input").oninput = function() {
    snowflake.tertiaryBranchLength = parseInt(this.value);
    snowflake.draw(canvas);
}
document.getElementById("tertiary-branch-period-input").oninput = function() {
    snowflake.tertiaryBranchPeriod = parseInt(this.value);
    snowflake.draw(canvas);
}
document.getElementById("tertiary-branch-angle-input").oninput = function() {
    snowflake.tertiaryBranchAngle = parseFloat(this.value*Math.PI/180.);
    snowflake.draw(canvas);
}
document.getElementById("background-symbol-input").oninput = function() {
    snowflake.backgroundSymbol = this.value;
    snowflake.draw(canvas);
}
document.getElementById("snowflake-symbol-input").oninput = function() {
    snowflake.snowflakeSymbol = this.value;
    snowflake.draw(canvas);
}
const defaultRandomProperties = generateRandomSnowflakeProperties(12345);
let snowflake = new Snowflake(...defaultRandomProperties);
snowflake.draw(canvas);

Examples

A few examples randomly generated with the size 20.

-------------------@@-------------------
------------------@--@------------------
-----------------@----@-----------------
----------------@--@---@----------------
---------------@---@----@---------------
---------------@@-@-@@-@@---------------
----------@-@-@-----@----@-@-@----------
-------@@----@------@-----@----@@-------
---@-@------@-------@------@------@-@---
---@-@--@-@-@-@-@-@-@-@-@-@@@-@-@-@-@---
-------@@----@------@-----@----@@-------
----------@-@-@-----@----@-@-@----------
---------------@@-@-@@-@@---------------
---------------@----@---@---------------
----------------@---@--@----------------
-----------------@--@-@-----------------
------------------@--@------------------
-------------------@@-------------------
----------------------------------------
----------------------------------------
------------@------@@------@------------
---------@@-@------@@------@-@@---------
-----------@@-@---@@-@---@-@@-----------
-----@------@@--@@@@-@@@--@@------@-----
----@@-----@-@@----@-----@@-@----@@@----
---@-@@@-@-@---@----@---@---@-@-@@@-@---
-------@-@-@----@---@--@--@-@---@-------
-@------@@---@@---@-@-@--@----@@------@-
---@@-@--@------@-@@@@-@------@--@-@@---
--@@@-@-@@@-@-@-@-@@@@@-@-@-@-@-@@@@@-@-
-@------@@----@-@-@-@-@@-@----@@------@-
-------@---@-@---@--@--@--@-@---@-------
---@-@@@-@-@----@---@---@---@-@-@@@-@---
----@@@----@-@-@----@----@@-@----@@@----
-----@------@-@-@@@-@@@@--@@------@-----
------------@@@---@-@@---@-@@-----------
---------@@@@------@@------@-@@---------
------------@------@@------@------------
-----------------@--@-@-----------------
----------------------------------------

----------@@--@-@-@@@@-@-@--@@----------
-------@--@------@-@@-@------@--@-------
@---@---@@-@------@@@@------@-@@---@---@
@-----@-@-@-@----@-@--@----@-@-@-@-----@
-@-@-@-@-@@@--@-@-@@-@-@-@--@@@-@-@-@-@-
-@-------@--@--@----@---@--@--@-------@-
-@@-------@-@-@@----@---@@-@-@-------@@-
--@-@----@@------@--@-@------@@----@-@--
@-@@-@@@---@------@-@@------@---@@@-@@-@
@-@@@@@@@-@@@-@-@-@-@@@-@-@-@-@-@@@-@@-@
--@-@----@@------@--@-@------@@----@-@--
-@@-------@-@-@@----@---@@-@-@-------@@-
-@-------@--@--@----@---@--@--@-------@-
-@-@-@-@-@@@--@-@-@-@@-@-@--@@@-@-@-@-@-
@-----@-@-@-@----@--@-@----@-@-@-@-----@
@---@---@@-@------@@@@------@-@@---@---@
-------@--@------@-@@-@------@--@-------
----------@@--@-@-@@@@-@-@--@@----------
------@-@-@-@--@--@--@--@--@-@-@-@------
----------------------------------------

The bigger the size of the canvas, the better the resolution, that allows for a more exquisite snowflakes that I unfortunately cannot share, because screenshots are fobidden and limited size kills the formatting. The default intended size that nicely fits on modern screens is 60 (vertical lines and 120 horizontal characters).

AI usage

AI has not been used entirely. Not for assistance, not for code generation.

How to run

  1. Create index.html, styles.css, and main.js files and copy the code content in them.
  2. Open index.html with your favourite browser. Tested with Firefox 128.11 only.

When the answers become public I probably will upload the code on Codepen and on Github, so it can be even more accesible.

Besides the canvas there is a menu with controls for customisation. And, as the challange requests a button to generate snowflakes randomly from a given seed.

What I learned

It was a nice brain teaser and practice exercise to improve my js and math skills. I am eager to learn something new from other solutions :)