Source: sketch.js

/*******************************************************************************
 *
 *	@file sketch.js Generate a random walk within a grid based on the work of
 *                  Dan from the coding train
 *
 *	@author Omar Essilfie-Quaye <omareq08@gmail.com>
 *	@version 1.0
 *	@date 11-June-2021
 *  @link https://omareq.github.io/random-walk/
 *
 *****************************************************************************/

/**
 * Monitors how often the reset button is pushed.  This helps provide debouncing
 * of the reset button when it is used on mobile devices.
 *
 * @type       {Float}
 */
let lastResetTime = 0;

/**
 * The number of lattice nodes on the vertical axis of the grid.
 *
 * @type       {Integer}
 */
let verticalGridPoints = 10;

/**
 * Handler for the number of grid points slider.
 *
 * @type       {p5.element}
 */
let verticalGridPointsSlider = undefined;

/**
 * Handler for the paragraphs that displays the current grid size.
 *
 * @type       {p5.element}
 */
let verticalGridPointsDisplay = undefined;

/**
 * The number of seconds to wait before auto reset.
 *
 * @type       {Integer}
 */
let refreshTime = 5;

/**
 * Handler for the refresh time slider.
 *
 * @type       {p5.element}
 */
let refreshTimeSlider = undefined;

/**
 * Handler for the paragraphs that displays the current refresh time.
 *
 * @type       {p5.element}
 */
let refreshTimeDisplay = undefined;

/**
 * The pixel spacing between lattice nodes on the canvas.
 *
 * @type       {Integer}
 */
let spacing = undefined;

/**
 * The generated lattice nodes
 *
 * @type       {Array<GraphNodes>}
 */
let nodes = [];

/**
 * The generated random self avoiding walk
 *
 * @type       {Array<GraphNode>}
 */
let walk = [];

/**
 * Handler for the reset button
 *
 * @type       {p5.element}
 */
let resetButton = undefined;

/**
 * Checks to see if a GraphNode list contains a given node.
 *
 * @param      {Array<GraphNode>}  nodesList     The list of nodes
 * @param      {Array<Integer>}  nodeIndecies  The lattice indecies.  Should not
 *                               have more than 2 elements representing the
 *                               components for each lattice vector.
 * @return     {boolean}  Boolean true if the node is in the list.
 */
function contains(nodesList, nodeIndecies) {
    for(let nodeIndex = 0; nodeIndex < nodesList.length; nodeIndex++) {
        const node = nodesList[nodeIndex];
        if(node.i == nodeIndecies[0] && node.j == nodeIndecies[1]) {
            return true;
        }
    }
    return false;
}

/**
 * Gets the node specified by the lattice indecies from the list.
 *
 * @param      {Array<GraphNode>}  nodesList     The list of nodes
 * @param      {Array<Integer>}  nodeIndecies  The lattice indecies.  Should not
 *                               have more than 2 elements representing the
 *                               components for each lattice vector.
 * @return     {GraphNode}  The node from list.
 */
function getNodeFromList(nodesList, nodeIndecies) {
    for(let nodeIndex = 0; nodeIndex < nodesList.length; nodeIndex++) {
        const node = nodesList[nodeIndex];
        if(node.i == nodeIndecies[0] && node.j == nodeIndecies[1]) {
            return node;
        }
    }
}

/**
 * Generate a graph of nodes arranged in a triangular pattern
 */
function generateTriangleGrid() {
    const r1 = createVector(spacing * sqrt(3) / 2, -spacing / 2);
    const r2 = createVector(spacing * sqrt(3) / 2 , spacing / 2);
    console.log("R1: " + r1.x + " " + r1.y);
    console.log("R2: " + r2.x + " " + r2.y);

    for(let i = -25; i < 25; i++) {
        for(let j = 0; j < 60; j++) {
            let pnt = r1.copy().mult(i).add(r2.copy().mult(j));
            pnt.x = pnt.x + 0.5 * spacing;
            pnt.y = pnt.y + 0.5 * spacing;
            if(pnt.x <= 0.5 || pnt.y <= 0.5 || pnt.x >= width-0.5 || pnt.y >= height-0.5) {
                continue;
            }
            nodes.push(new GraphNode(i, j, pnt.x, pnt.y));
        }
    }

    for(let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
        const currentNode = nodes[nodeIndex];
        const i = currentNode.i;
        const j = currentNode.j;
        const possibleNeighbors = [
            [i, j + 1],
            [i, j - 1],
            [i + 1, j],
            [i - 1, j]
        ];

        for(let n = possibleNeighbors.length - 2; n >= 0; n--) {
            if(!contains(nodes, possibleNeighbors[n])) {
                possibleNeighbors.splice(1, n);
            }
        }
        nodes[nodeIndex].setNeighbours(possibleNeighbors);
    }
}


/**
 * Generates a random walk given a list of nodes in a grid
 */
function generateWalk() {
    randomNodeIndex = floor(random(nodes.length));
    let currentNode = nodes[randomNodeIndex];
    currentNode.visited = true;
    walk = [];
    walk.push(currentNode);
    let watchdog = nodes.length;
    console.log("watchdog: " + watchdog);
    while(watchdog > 0) {
        watchdog--;

        orderedNeighbours = currentNode.neighbours;
        neighbours = shuffle(orderedNeighbours);
        let foundUnvisitedNeighbour = false;

        for(let n = 0; n < neighbours.length; n++) {
            if(!contains(nodes, neighbours[n])) {
                continue;
            }
            currentNeighbour = getNodeFromList(nodes, neighbours[n]);
            if(!currentNeighbour.visited) {
                currentNode = currentNeighbour;
                currentNode.visited = true;
                foundUnvisitedNeighbour = true;
                walk.push(currentNode);
                break;
            }
        }

        if(!foundUnvisitedNeighbour) {
            console.log("No more unvisited neighbours");
            console.log("watchdog: " + watchdog);
            break;
        }
    }
}

/**
 * Resets the canvas and draws a new random walk.
 */
function reset() {
    if(millis() - lastResetTime < 275) {
        return;
    }

    for(let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
        let currentNode = nodes[nodeIndex];
        currentNode.visited = false;
    }
    spacing = height / verticalGridPoints;
    walk = [];
    nodes = [];
    generateTriangleGrid();
    generateWalk();

    lastResetTime = millis();
}

/**
 * p5.js setup function, creates canvas.
 */
function setup() {
	let cnvSize;
	if(windowWidth > windowHeight) {
		cnvSize = windowHeight;
	} else {
		cnvSize = windowWidth;
	}
	let cnv = createCanvas(0.9 * cnvSize, 0.9 * cnvSize);
	cnv.parent('sketch');

    spacing = height / verticalGridPoints;
    generateTriangleGrid();
    generateWalk();
    console.log("Generated Walk:");
    console.log(walk);

    verticalGridPointsDisplay = createP("No. of Vertical Grid Points: " +
        str(verticalGridPoints));
    verticalGridPointsDisplay.parent("vertical-grid-points-val");

    verticalGridPointsSlider = createSlider(4, 25, verticalGridPoints, 1);
    verticalGridPointsSlider.parent("vertical-grid-points");

    refreshTimeDisplay = createP("Refresh Time (s): " +
        str(refreshTime));
    refreshTimeDisplay.parent("refresh-time-val");

    refreshTimeSlider = createSlider(1, 60, refreshTime, 1);
    refreshTimeSlider.parent("refresh-time");

    resetButton = createButton("Reset", "value");
    resetButton.parent("reset-button");
    resetButton.mousePressed(reset);
}

/**
 * p5.js draw function, is run every frame to create the desired animation
 */
function draw() {
	background(0);
    stroke(255);
    fill(255);
    for(let i = 0; i < nodes.length; i++) {
        ellipse(nodes[i].x, nodes[i].y, spacing / 8, spacing / 8);
    }

    fill(255, 0, 0);
    stroke(255, 0, 0);
    ellipse(walk[0].x, walk[0].y, spacing / 8, spacing / 8);
    for(let i = 1; i < walk.length; i++) {
        noStroke();
        ellipse(walk[i].x, walk[i].y, spacing / 8, spacing / 8);
        stroke(0, 255, 0);
        strokeWeight(2);
        line(walk[i].x, walk[i].y, walk[i-1].x, walk[i-1].y);
    }
        stroke(0, 0, 255);
        fill(0, 0, 255);
        ellipse(walk[walk.length - 1].x, walk[walk.length - 1].y,
            spacing / 8, spacing / 8);

    let sliderVal = verticalGridPointsSlider.value();
    if(sliderVal != verticalGridPoints) {
        verticalGridPoints = sliderVal;
        verticalGridPointsDisplay.elt.innerText = "No. of Vertical Grid Points: " +
            str(verticalGridPoints);
        reset();
    }

    sliderVal = refreshTimeSlider.value();
    if(sliderVal != refreshTime) {
        refreshTime = sliderVal;
        refreshTimeDisplay.elt.innerText = "Refresh Time (s): " +
            str(refreshTime);
        reset();
    }

    if(millis() - lastResetTime > refreshTime * 1000) {
        reset();
    }
}


Documentation generated by JSDoc 3.6.3 on Sun Jun 05 2022 19:55:55 GMT+0100 (BST)