Code viewer for World: Breadth-first search (Kevi...

// Cloned by Andrew Merrigan on 19 Oct 2019 from World "Breadth-first search (Kevin Bacon)" by "Coding Train" project 
// Please leave this clone trail here.


// Breadth-first search

// Port from:
// https://github.com/nature-of-code/NOC-S17-2-Intelligence-Learning/tree/master/week1-graphs/P2_six_degrees_kevin_bacon
// https://www.youtube.com/watch?v=piBq7VD0ZSo

// Hard to visualise graph 


AB.headerRHS();

AB.newDiv("selectdiv");

$("#selectdiv").css({
    "margin": "30",
});

$("#selectdiv").html(" Pick an actor: <span id='actorlist'></span> ");



// Daniel Shiffman
// Nature of Code: Intelligence and Learning
// https://github.com/shiffman/NOC-S17-2-Intelligence-Learning
// Graph object
class Graph {
    constructor() {
        // store nodes by key and also in an array
        this.graph = {};
        this.nodes = [];
        // Start and end
        this.start = null;
        this.end = null;
    }
    // Set the start
    setStart(node) {
        this.start = node;
    }
    // Set the end
    setEnd(node) {
        this.end = node;
    }
    // Add a node
    addNode(label, type) {
        var n = new Node(label, type);
        // Add to both the graph object and the nodes array
        this.graph[label] = n;
        this.nodes.push(n);
        return n;
    }
    // Clear all searched and parent values
    clear() {
        for (var i = 0; i < this.nodes.length; i++) {
            this.nodes[i].searched = false;
            this.nodes[i].parent = null;
        }
    }
}


// Daniel Shiffman
// Nature of Code: Intelligence and Learning
// https://github.com/shiffman/NOC-S17-2-Intelligence-Learning
class Node {
    constructor(label, type) {
        this.label = label;
        this.type = type;
        this.edges = [];
        this.parent = null;
        this.searched = false;
    }

    // Connect one or more neighbor nodes
    connect(...nodes) {
        if (nodes) {
            nodes.forEach(node => {
                this.edges.push(node); // increase the size of the array 
                node.edges.push(this);
            });
        }
    }
}



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

// The data
var data;

// The graph
var graph;
// A lookup table for actors
// (redundant, the graph could handle this)
var actors;
// Dropdown menu for actors
var actorlist;

function preload() {
    data = loadJSON('/uploads/codingtrain/bacon.json');
}

function setup() {
    noCanvas();

    // Create the graph
    graph = new Graph();
    // A separate lookup table for actors
    actors = {};

    // For all movies
    var movies = data.movies;
    if (movies) {
        movies.forEach(movie => {
            var movieNode = graph.addNode(movie.title, "MOVIE");
            if (movie.cast) {
                movie.cast.forEach(name => {
                    var actorNode;

                    // Does the actor exist already?
                    if (actors[name]) {
                        actorNode = actors[name];

                    } else {
                        actorNode = graph.addNode(name, "ACTOR");        // Node person 
                        actors[name] = actorNode;
                    }

                    // Connect movie and actor
                    // movie connects to person 
                    movieNode.connect(actorNode);
                });
            }
        });
    }
    // Create dropdown
    actorlist = createSelect();           // https://p5js.org/reference/#/p5/createSelect 
    actorlist.parent('actorlist');
    var allActors = Object.keys(actors);
    // Add all the actors
    for (var i = 0; i < allActors.length; i++) {
        actorlist.option(allActors[i]);
    }
    // Set up an event
    actorlist.changed(findbacon);
}


function findbacon() {
    // Clear everyone from having been searched
    graph.clear();
    // Start and end
    var start = actors[actorlist.value()];
    var end = actors['Kevin Bacon'];
    graph.setStart(start);
    graph.setEnd(end);
    // Run the search!
    bfs();
}


function bfs() {
    // Create a queue ad path
    var queue = [];
    var path = [];

    // Get started
    queue.push(graph.start);

    // inside the loop we keep adding things to queue
    while (queue.length > 0) {
        var node = queue.shift();               // remove first item
        console.log("======");
        console.log("start of search of " + node.label);
        var edges;
        
        // Are we done?
        if (node == graph.end) {
            console.log("found goal node");
            // Figure out the path
            path.push(node);
            edges = node.parent;
            while (edges) {
                path.push(edges);
                edges = edges.parent;
            }
            break;

        } else {
            console.log("checking neighbours of " + node.label);
            // Check all neighbors
            edges = node.edges;
            // console.log ("checking " + next.length + " neighbours");
            for (var i = 0; i < edges.length; i++) {
                var neighbor = edges[i];
                // Any neighbor not already searched add to queue
                if (!neighbor.searched && !queue.includes(neighbor)) {
                    console.log("adding unsearched neighbour " + neighbor.label + " to queue");
                    queue.push(neighbor);
                    node.searched = true;
                    neighbor.parent = node;
                }
            }

            console.log("end of search of " + node.label);
            // console.log ("======" );
        }
    }         // end of while loop


    // now output path 

    console.log('finished search for path');

    // path = bacon + movie + x + movie + x ... + movie + x

    var s = '<h1> Path length ' + path.length + '</h1>';

    for (var nodeIndex = path.length-1; nodeIndex >= 0; nodeIndex--) {
        var node = path[nodeIndex];
        if(node.type === 'MOVIE'){
            s = s + '<b>' + node.label + '</b><br>';
        }else{
            s = s + node.label + '</b><br>';
        }
    }


    AB.newDiv("theoutput");

    $("#theoutput").css({
        "margin": "30",
        "padding": "30",
        "display": "inline-block",
        "border": "1px solid black"
    });

    $("#theoutput").html(s);

}