diff --git a/3-Solving-Problems-By-Searching/c_breadthFirstSearch.js b/3-Solving-Problems-By-Searching/c_breadthFirstSearch.js index 2ca3968..15b5e78 100644 --- a/3-Solving-Problems-By-Searching/c_breadthFirstSearch.js +++ b/3-Solving-Problems-By-Searching/c_breadthFirstSearch.js @@ -1,38 +1,48 @@ $(document).ready(function() { var w = 600, h = 350; - var DELAY = 2000; var intervalFunction = null; function init() { - clearInterval(intervalFunction, DELAY); - var graph = new DefaultGraph(); - var graphProblem = new GraphProblem(graph.nodes, graph.edges, 'A', 'A'); - var graphAgent = new GraphAgent(graphProblem); var options = new DefaultOptions(); options.nodes.next.fill = 'hsla(126, 100%, 69%, 1)'; - var graphDrawAgent = new GraphDrawAgent(graphProblem, 'breadthFirstSearchCanvas', options, h, w); - var queueDrawAgent = new QueueDrawAgent('fifoQueueCanvas', h, w, graphProblem, options); - //Update handler is the function that would be executed every DELAY ms. - var updateFunction = function() { - if (graphProblem.frontier.length > 0) { - var nextNode = breadthFirstSearch(graphProblem); - graphAgent.expand(nextNode); - //If frontier is still present, find the next node to be expanded so it - //could be colored differently + + var drawState = function(n) { + var graph = new DefaultGraph(); + var graphProblem = new GraphProblem(graph.nodes, graph.edges, 'A', 'A'); + var graphAgent = new GraphAgent(graphProblem); + + var graphDrawAgent = new GraphDrawAgent(graphProblem, 'breadthFirstSearchCanvas', options, h, w); + var queueDrawAgent = new QueueDrawAgent('fifoQueueCanvas', h, w, graphProblem, options); + + while (n--) { if (graphProblem.frontier.length > 0) { - graphProblem.nextToExpand = breadthFirstSearch(graphProblem); + var nextNode = breadthFirstSearch(graphProblem); + graphAgent.expand(nextNode); + //If frontier is still present, find the next node to be expanded so it + //could be colored differently + if (graphProblem.frontier.length > 0) { + graphProblem.nextToExpand = breadthFirstSearch(graphProblem); + } else { + graphProblem.nextToExpand = null; + } } else { - graphProblem.nextToExpand = null; + break; } - graphDrawAgent.iterate(); - queueDrawAgent.iterate(); - } else { - clearInterval(intervalFunction, DELAY); } + graphDrawAgent.iterate(); + queueDrawAgent.iterate(); } - intervalFunction = setInterval(updateFunction, DELAY); + + let ac = new AnimationController({ + selector: '#bfsAC', + min: 0, + max: 15, + renderer: drawState + }); + ac.renderFirst(); }; + $('#bfsRestartButton').click(init); $('#fifoWaiting').css('background-color', 'hsl(0,50%,75%)'); $('#fifoNextNode').css('background-color', 'hsl(126, 100%, 69%)'); diff --git a/3-Solving-Problems-By-Searching/c_depthFirstSearch.js b/3-Solving-Problems-By-Searching/c_depthFirstSearch.js index 858bf3d..d50dc81 100644 --- a/3-Solving-Problems-By-Searching/c_depthFirstSearch.js +++ b/3-Solving-Problems-By-Searching/c_depthFirstSearch.js @@ -5,32 +5,43 @@ $(document).ready(function() { var intervalFunction = null; function init() { - clearInterval(intervalFunction, DELAY); - var graph = new DefaultGraph(); - var graphProblem = new GraphProblem(graph.nodes, graph.edges, 'A', 'A'); - var graphAgent = new GraphAgent(graphProblem); var options = new DefaultOptions(); options.nodes.next.fill = 'hsla(126, 100%, 69%, 1)'; - var graphDrawAgent = new GraphDrawAgent(graphProblem, 'depthFirstSearchCanvas', options, h, w); - var queueDrawAgent = new QueueDrawAgent('lifoQueueCanvas', h, w, graphProblem, options); - var updateFunction = function() { - if (graphProblem.frontier.length > 0) { - var nextNode = depthFirstSearch(graphProblem); - graphAgent.expand(nextNode); - //If frontier is still present, find the next node to be expanded so it - //could be colored differently + + var drawState = function(n) { + var graph = new DefaultGraph(); + var graphProblem = new GraphProblem(graph.nodes, graph.edges, 'A', 'A'); + var graphAgent = new GraphAgent(graphProblem); + + var graphDrawAgent = new GraphDrawAgent(graphProblem, 'depthFirstSearchCanvas', options, h, w); + var queueDrawAgent = new QueueDrawAgent('lifoQueueCanvas', h, w, graphProblem, options); + + while (n--) { if (graphProblem.frontier.length > 0) { - graphProblem.nextToExpand = depthFirstSearch(graphProblem); + var nextNode = depthFirstSearch(graphProblem); + graphAgent.expand(nextNode); + //If frontier is still present, find the next node to be expanded so it + //could be colored differently + if (graphProblem.frontier.length > 0) { + graphProblem.nextToExpand = depthFirstSearch(graphProblem); + } else { + graphProblem.nextToExpand = null; + } } else { - graphProblem.nextToExpand = null; + break; } - graphDrawAgent.iterate(); - queueDrawAgent.iterate(); - } else { - clearInterval(intervalFunction, DELAY); } + graphDrawAgent.iterate(); + queueDrawAgent.iterate(); } - intervalFunction = setInterval(updateFunction, DELAY); + + let ac = new AnimationController({ + selector: '#dfsAC', + min: 0, + max: 15, + renderer: drawState + }); + ac.renderFirst(); }; $('#dfsRestartButton').click(init); $('#lifoWaiting').css('background-color', 'hsl(0,50%,75%)'); diff --git a/3-Solving-Problems-By-Searching/c_uniformCostSearch.js b/3-Solving-Problems-By-Searching/c_uniformCostSearch.js index 3c3519c..d3585ae 100644 --- a/3-Solving-Problems-By-Searching/c_uniformCostSearch.js +++ b/3-Solving-Problems-By-Searching/c_uniformCostSearch.js @@ -20,45 +20,51 @@ $(document).ready(function() { }).appendTo(exploredQueueCanvas); //Intial value for separation is 0 $('.ucsSeparation').html(0); - var graph = new DefaultGraph(); - //Precompute costs of all nodes from the initial node var costMap = precomputedCosts(); - var graphProblem = new GraphProblem(graph.nodes, graph.edges, 'A', 'A'); - //Change the text of all the nodes to its cost - for (key in graphProblem.nodes) { - graphProblem.nodes[key].text = costMap[graphProblem.nodes[key].id]; - } - var graphAgent = new GraphAgent(graphProblem, 'ucs'); + var options = new DefaultOptions(); options.nodes.next.fill = 'hsla(126, 100%, 69%, 1)'; options.edges.showCost = true; - var graphDrawAgent = new GraphDrawAgent(graphProblem, 'uniformCostSearchCanvas', options, h, w); - drawList(priorityTwo, graphProblem.frontier, graphProblem, options, costMap); - drawList(exploredTwo, graphProblem.explored, graphProblem, options, costMap); - var updateFunction = function() { - if (graphProblem.frontier.length > 0) { - var nextNode = uniformCostSearch(graphProblem); - graphAgent.expand(nextNode); + + var drawState = function(n) { + var graph = new DefaultGraph(); + var graphProblem = new GraphProblem(graph.nodes, graph.edges, 'A', 'A'); + var graphAgent = new GraphAgent(graphProblem); + for (key in graphProblem.nodes) { + graphProblem.nodes[key].text = costMap[graphProblem.nodes[key].id]; + } + + var graphDrawAgent = new GraphDrawAgent(graphProblem, 'uniformCostSearchCanvas', options, h, w); + var maxCost; + while (n--) { if (graphProblem.frontier.length > 0) { - graphProblem.nextToExpand = uniformCostSearch(graphProblem); + var nextNode = uniformCostSearch(graphProblem); + graphAgent.expand(nextNode); + //If frontier is still present, find the next node to be expanded so it + //could be colored differently + if (graphProblem.frontier.length > 0) { + graphProblem.nextToExpand = uniformCostSearch(graphProblem); + maxCost = graphProblem.nodes[graphProblem.nextToExpand].cost; + } else { + graphProblem.nextToExpand = null; + } } else { - graphProblem.nextToExpand = null; - } - graphDrawAgent.iterate(); - drawList(priorityTwo, graphProblem.frontier, graphProblem, options, costMap); - drawList(exploredTwo, graphProblem.explored, graphProblem, options, costMap); - let maxCost = 0; - //Find the max cost which separates the explored from frontier nodes - if (graphProblem.nextToExpand) { - maxCost = graphProblem.nodes[graphProblem.nextToExpand].cost; + break; } - //Draw it in the front end - $('.ucsSeparation').html(maxCost); - } else { - clearInterval(intervalFunction, DELAY); } + graphDrawAgent.iterate(); + drawList(priorityTwo, graphProblem.frontier, graphProblem, options, costMap); + drawList(exploredTwo, graphProblem.explored, graphProblem, options, costMap); + //Draw it in the front end + $('.ucsSeparation').html(maxCost); } - intervalFunction = setInterval(updateFunction, DELAY); + let ac = new AnimationController({ + selector: '#ucsAC', + min: 0, + max: 15, + renderer: drawState + }); + ac.renderFirst(); }; $('#ucsRestartButton').click(init); $('#ucsWaiting').css('background-color', 'hsl(0,50%,75%)'); @@ -81,7 +87,6 @@ function drawList(two, list, problem, options, costMap) { let text = two.makeText(costMap[node.id], x, y); let fillColor = options.nodes[state].fill; circle.fill = fillColor; - } two.update(); } diff --git a/3-Solving-Problems-By-Searching/index.html b/3-Solving-Problems-By-Searching/index.html index c02d2cb..4c948b8 100644 --- a/3-Solving-Problems-By-Searching/index.html +++ b/3-Solving-Problems-By-Searching/index.html @@ -10,6 +10,7 @@ + @@ -111,10 +112,11 @@

Breadth First Search

-
+
-
Restart
-
+
+
+
@@ -135,6 +137,7 @@

FIFO Queue

+

Depth First Search

@@ -145,10 +148,11 @@

Depth First Search

-
+
-
Restart
-
+
+
+
@@ -238,10 +242,11 @@

Uniform Cost Search

-
+
-
Restart
-
+
+
+
@@ -378,12 +383,12 @@

Bi-directional BFS

Bi-directional BFS

-

+

Standard BFS

-

+

diff --git a/6-Constraint-Satisfaction-Problems/c_backtracking.js b/6-Constraint-Satisfaction-Problems/c_backtracking.js index d1c7d92..4b3675a 100644 --- a/6-Constraint-Satisfaction-Problems/c_backtracking.js +++ b/6-Constraint-Satisfaction-Problems/c_backtracking.js @@ -97,30 +97,14 @@ class BacktrackingDiagram { //Run the algorithm and record frames this.recordFrames(heuristics); this.addAssignmentTable(this.backtracker.variables); - //Attach listener to the sliders and buttons - let sliderElement = this.selector.select('#backtrackSlider'); - this.selector.select('.prevButton').on('mousedown', () => { - let value = parseInt(sliderElement.property('value')); - if (value > 0) { - value -= 1; - } - sliderElement.property('value', value).dispatch('input'); - }); - this.selector.select('.nextButton').on('mousedown', () => { - let value = parseInt(sliderElement.property('value')); - if (value < this.frames.length - 1) { - value += 1; - } - sliderElement.property('value', value).dispatch('input'); + //Create AnimationController + let ac = new AnimationController({ + selector: '#backtrackingAC', + min: 0, + max: this.frames.length - 1, + renderer: (n) => this.loadFrame(n) }); - sliderElement.attr('max', this.frames.length - 1) - .property('value', 0) - .on('input', () => { - let frameIndex = parseInt(sliderElement.property('value')); - this.loadFrame(this.frames[frameIndex], frameIndex); - }); - //Load the first frame - this.loadFrame(this.frames[0], 0); + ac.renderFirst(); } addNewMap(assignment) { @@ -268,7 +252,8 @@ class BacktrackingDiagram { } //Given a frame, load it into the diagram. - loadFrame(frame, frameIndex) { + loadFrame(frameIndex) { + let frame = this.frames[frameIndex]; this.selector.selectAll('.map').remove(); this.maps = []; this.mapElements = []; diff --git a/6-Constraint-Satisfaction-Problems/index.html b/6-Constraint-Satisfaction-Problems/index.html index 4012b49..8a4ef5b 100644 --- a/6-Constraint-Satisfaction-Problems/index.html +++ b/6-Constraint-Satisfaction-Problems/index.html @@ -10,6 +10,7 @@ + @@ -110,14 +111,10 @@

Backtracking Search

Restart
-
-
Previous
-
-
- -
-
-
Next
+
+
+ +
diff --git a/globalHelpers.js b/globalHelpers.js new file mode 100644 index 0000000..fea74e8 --- /dev/null +++ b/globalHelpers.js @@ -0,0 +1,81 @@ +/** + * Class to create an animation controller. + * It creates the 'previous' button, 'next' button, slider and also attaches + event listeners to them. + * @example How to use AnimationController + *
+ * +*/ +class AnimationController { + /** + * Create Animation Controller. + * @param {Object} options - Options for the controller + * @param {String} options.selector - A selector to target the wrapper div for the controller. + * @param {Number} options.min - Minimum value for the slider. Defaults to 0 + * @param {Number} options.max - Maximum value for the slider. + * @param {Function} options.renderer - A function that can take the slider value and render the frame. + */ + constructor(options) { + //Initializing options + this.selector = options.selector; + this.min = options.min || 0; + this.max = options.max; + this.renderer = options.renderer; + + //Create Elements + this.root = d3.select(this.selector); + //Empty the root element + this.root.selectAll('*').remove(); + this.prevButton = this.root.append('div') + .attr('class', 'btn btn-default ACPrev col-md-2') + .text('Previous'); + + this.slider = this.root.append('div') + .attr('class', 'form-group col-md-6') + .style('margin-top', '1%') + .append('input') + .attr('type', 'range') + .attr('name', 'rangeInput') + .attr('step', 1) + .attr('min', this.min) + .attr('max', this.max); + this.slider.property('value', this.min); + + this.nextButton = this.root.append('div') + .attr('class', 'btn btn-default ACNext col-md-2') + .text('Next'); + + //Bind Event Listeners + this.prevButton.on('click', () => { + let value = parseInt(this.slider.property('value')); + value = (value - 1 < 0) ? value : value - 1; + this.slider.property('value', value); + this.renderer(value); + }); + this.slider.on('change input', () => { + let value = parseInt(this.slider.property('value')); + this.renderer(value); + }); + this.nextButton.on('click', () => { + let value = parseInt(this.slider.property('value')); + value = (value + 1 > this.max) ? value : value + 1; + this.slider.property('value', value); + this.renderer(value); + }); + } + /** + * Renders the first frame + */ + renderFirst() { + this.renderer(this.min); + } +}