Code viewer for World: A* with Different Terrains

// Cloned by Chen Zhenshuo on 20 Oct 2020 from World "A star" by Chen Zhenshuo 
// Please leave this clone trail here.
 


// Cloned by Chen Zhenshuo on 19 Oct 2020 from World "A star" by "Coding Train" project 
// Please leave this clone trail here.


// Port of 
// https://github.com/nature-of-code/NOC-S17-2-Intelligence-Learning/tree/master/week1-graphs/05_astar


// Daniel Shiffman
// Nature of Code: Intelligence and Learning
// https://github.com/shiffman/NOC-S17-2-Intelligence-Learning

// Part 1: https://youtu.be/aKYlikFAV4k
// Part 2: https://youtu.be/EaZxUCWAjb0
// Part 3: https://youtu.be/jwRT4PCT6RU

// is diagonal move allowed 
const diagonal = false;

const MUD = 1;
const mudCost = 10;
const mudAmount = 0.3;

// canvas size 
const cw = 900;
const ch = 600;

// How many columns and rows 
// different each time
var rando = AB.randomIntAtoB(1, 5);
var cols = 9 * rando;
var rows = 6 * rando;

// how many walls to make, from 0 to 1 
// different each time
const wallAmount = 0.2;

const backcolor = 'white';
const wallcolor = 'black';
const mudcolor = 'orange';
const pathcolor = 'darkred';


// 2D array
var grid = new Array(cols);

// Open and closed set
var openSet = [];
var closedSet = [];

// Start and end
var start;
var end;

var startPos = {x: 0, y: randomInt(0, rows)};
var endPos= {x: cols - 1, y: randomInt(0, rows)};


// Width and height of each cell of grid
var w, h;

// The road taken
var path = [];



//=== heuristic ===========================
// this must be always optimistic - real time will be this or longer 
    
function heuristic(src, dest) {
    if (diagonal) {
        return dist(src.i, src.j, dest.i, dest.j);
    } else {
        return Math.abs(src.i - dest.i) + Math.abs(src.j - dest.j);
    }
}

function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}


// Function to delete element from the array
function removeFromArray(arr, elt) {
    // Could use indexOf here instead to be more efficient
    for (var i = arr.length - 1; i >= 0; i--)
        if (arr[i] == elt)
            arr.splice(i, 1);
}

function showPoint(x, y) {
    stroke('blue');
    strokeWeight(w / 1.5);
    point(x * w + w / 2, y * w + w / 2);
}


// Daniel Shiffman
// Nature of Code: Intelligence and Learning
// https://github.com/shiffman/NOC-S17-2-Intelligence-Learning

// Part 1: https://youtu.be/aKYlikFAV4k
// Part 2: https://youtu.be/EaZxUCWAjb0
// Part 3: https://youtu.be/jwRT4PCT6RU

// An object to describe a spot in the grid
function Spot(i, j) {

    // Location
    this.i = i;
    this.j = j;

    // f, g, and h values for A*
    this.f = 0;
    this.g = 0;
    this.h = 0;
    
    if (random(1) < mudAmount) this.type = MUD;
    else this.type = 0;
    
    // Neighbors
    this.neighbors = [];

    // Where did I come from?
    this.previous = undefined;

    // Am I an wall?
    if (random(1) < wallAmount) this.wall = true;
    else this.wall = false;
    

    
    // Display me
    this.show = function () {
        if (this.wall) {
            fill(wallcolor);
            // wall fills square 
            rect(this.i * w, this.j * h, w, h);

            // wall only partially fills square 
            //    ellipse (   this.i * w + w / 2,     this.j * h + h / 2,     w * 0.7,    h * 0.7     );

        } else if (this.type === MUD) {
            fill(mudcolor);
            rect(this.i * w, this.j * h, w, h);
        }
    };


    // Figure out who my neighbors are
    this.addNeighbors = function (grid) {
        var i = this.i;
        var j = this.j;

        if (i < cols - 1) this.neighbors.push(grid[i + 1][j]);
        if (i > 0) this.neighbors.push(grid[i - 1][j]);
        if (j < rows - 1) this.neighbors.push(grid[i][j + 1]);
        if (j > 0) this.neighbors.push(grid[i][j - 1]);

        if (diagonal)
        // diagonals are also neighbours:
        {
            if (i > 0 && j > 0) this.neighbors.push(grid[i - 1][j - 1]);
            if (i < cols - 1 && j > 0) this.neighbors.push(grid[i + 1][j - 1]);
            if (i > 0 && j < rows - 1) this.neighbors.push(grid[i - 1][j + 1]);
            if (i < cols - 1 && j < rows - 1) this.neighbors.push(grid[i + 1][j + 1]);
        }
    }

}


function setup() {
    // slower frame rate to see how it is working 
    // frameRate (2);


    createCanvas(cw, ch);

    // Grid cell size
    w = width / cols;
    h = height / rows;

    // Making a 2D array
    for (var i = 0; i < cols; i++)
        grid[i] = new Array(rows);

    for (var i = 0; i < cols; i++)
        for (var j = 0; j < rows; j++)
            grid[i][j] = new Spot(i, j);

    // All the neighbors
    for (var i = 0; i < cols; i++)
        for (var j = 0; j < rows; j++)
            grid[i][j].addNeighbors(grid);


    // Start and end
    
    start = grid[startPos.x][startPos.y];
    end = grid[endPos.x][endPos.y];
    start.wall = false;
    end.wall = false;

    // openSet starts with beginning only
    openSet.push(start);

    console.log('start search');

}



function draw()
// the search goes on over many timesteps 
// each timestep, check one more square and draw current partial solution 
{

    // --- begin still searching -----------------------------
    if (openSet.length > 0) {

        // Best next option
        var winner = 0;

        for (var i = 0; i < openSet.length; i++)
            if (openSet[i].f < openSet[winner].f)
                winner = i;

        var current = openSet[winner];

        // Did I finish?
        if (current === end) {
            noLoop();
            console.log("success - found path");
        }

        // Best option moves from openSet to closedSet
        removeFromArray(openSet, current);
        closedSet.push(current);

        // Check all the neighbors
        var neighbors = current.neighbors;

        //--- start of for loop -----------
        for (var i = 0; i < neighbors.length; i++) {
            var neighbor = neighbors[i];

            // Valid next spot?
            if (!closedSet.includes(neighbor) && !neighbor.wall) {
                var tempG = current.g + heuristic(neighbor, current);
                if (neighbor.type === MUD) {
                    tempG += mudCost;
                }

                // Is this a better path than before?
                var newPath = false;
                if (openSet.includes(neighbor)) {
                    if (tempG < neighbor.g) {
                        neighbor.g = tempG;
                        newPath = true;
                    }
                } else {
                    neighbor.g = tempG;
                    newPath = true;
                    openSet.push(neighbor);
                }

                // Yes, it's a better path
                if (newPath) {
                    neighbor.h = heuristic(neighbor, end);
                    neighbor.f = neighbor.g + neighbor.h;
                    neighbor.previous = current;
                }
            }
        }
        //--- end of for loop -----------

    }
    // --- end still searching -----------------------------


    else {
        console.log('fail - no path exists');
        noLoop();
        return;
    }


    // Draw current state of everything
    background(backcolor);
    stroke('white');
    strokeWeight(1);
        
    for (var i = 0; i < cols; i++)
        for (var j = 0; j < rows; j++)
            grid[i][j].show();

    showPoint(startPos.x, startPos.y);
    showPoint(endPos.x, endPos.y);

    // Find the path by working backwards
    path = [];
    var temp = current;
    path.push(temp);
    while (temp.previous) {
        path.push(temp.previous);
        temp = temp.previous;
    }
    
    noFill();
    stroke(pathcolor);
    strokeWeight(w / 8);
    beginShape();
    for (var i = 0; i < path.length; i++)
        vertex((path[i].i * w) + w / 2, (path[i].j * h) + h / 2);
    endShape();
}