From 7cb80aa409ead0b651ee18401fd4ae228f565bcd Mon Sep 17 00:00:00 2001 From: ootsby Date: Mon, 16 Jan 2017 11:33:48 +0000 Subject: [PATCH 1/6] Rearranging and adding buttons to control execution. --- sketch.js | 465 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 268 insertions(+), 197 deletions(-) diff --git a/sketch.js b/sketch.js index 05fd61f..ce69c52 100644 --- a/sketch.js +++ b/sketch.js @@ -3,11 +3,11 @@ var allowDiagonals = false; function removeFromArray(arr, elt) { - for (var i = arr.length - 1; i >= 0; i--) { - if (arr[i] == elt) { - arr.splice(i, 1); + for (var i = arr.length - 1; i >= 0; i--) { + if (arr[i] == elt) { + arr.splice(i, 1); + } } - } } //This function returns a measure of aesthetic preference for @@ -16,250 +16,321 @@ function removeFromArray(arr, elt) { //be anything you like without affecting the ability to find //a minimum cost path. function visualDist(a, b) { - return dist(a.i, a.j, b.i, b.j); + return dist(a.i, a.j, b.i, b.j); } function heuristic(a, b) { - var d; - if (allowDiagonals) { - d = dist(a.i, a.j, b.i, b.j); - } else { - d = abs(a.i - b.i) + abs(a.j - b.j); - } - return d; -} - -var cols = 50; -var rows = 50; -var grid = new Array(cols); - -var openSet = []; -var closedSet = []; -var start; -var end; -var w, - h; -var path = []; - -function Spot(i, j) { - this.i = i; - this.j = j; - this.f = 0; - this.g = 0; - this.h = 0; - this.vh = 0; //visual heuristic for tie-breaking - this.neighbors = []; - this.previous = undefined; - this.wall = false; - - if (random(1) < 0.3) { - this.wall = true; - } - - - this.show = function(col) { - fill(col); - if (this.wall) { - fill(0); - noStroke(); - ellipse(this.i * w + w / 2, this.j * h + h / 2, w / 2, h / 2); - } else { - rect(this.i * w, this.j * h, w - 1, h - 1); - } - } - - 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]); - } + var d; if (allowDiagonals) { - 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]); - } + d = dist(a.i, a.j, b.i, b.j); + } else { + d = abs(a.i - b.i) + abs(a.j - b.j); } - } - - - - + return d; } -function setup() { - createCanvas(400, 400); - console.log('A*'); +function TerrainMap(cols, rows, x, y, w, h){ + this.cols = cols; + this.rows = rows; + this.grid = []; + this.path = []; - w = width / cols; - h = height / rows; + this.x = x; + this.y = y; + this.w = w; + this.h = h; // Making a 2D array for (var i = 0; i < cols; i++) { - grid[i] = new Array(rows); + this.grid[i] = []; } for (var i = 0; i < cols; i++) { - for (var j = 0; j < rows; j++) { - grid[i][j] = new Spot(i, j); - } + for (var j = 0; j < rows; j++) { + this.grid[i][j] = new Spot(i, j, x+i*w/cols, y+j*h/rows, w/cols, h/rows); + } } for (var i = 0; i < cols; i++) { - for (var j = 0; j < rows; j++) { - grid[i][j].addNeighbors(grid); - } + for (var j = 0; j < rows; j++) { + this.grid[i][j].addNeighbors(this.grid); + } } +} +function Spot(i, j, x, y ,width, height) { + this.i = i; + this.j = j; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + this.f = 0; + this.g = 0; + this.h = 0; + this.vh = 0; //visual heuristic for tie-breaking + this.neighbors = []; + this.previous = undefined; + this.wall = false; + + if (random(1) < 0.3) { + this.wall = true; + } + + + this.show = function(color) { + fill(color); + if (this.wall) { + fill(0); + noStroke(); + ellipse(this.x + this.width / 2, this.y + this.height / 2, this.width / 2, this.height / 2); + } else { + rect(this.x, this.y, this.width - 1, this.height - 1); + } + } + this.addNeighbors = function(grid) { + var rows = grid[0].length; + var cols = grid.length; + 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 (allowDiagonals) { + 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]); + } + } + } +} - start = grid[0][0]; - end = grid[cols - 1][rows - 1]; - start.wall = false; - end.wall = false; - openSet.push(start); +function Button(label, x, y, w, h, callback) { + this.label = label; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.callback = callback; + this.show = function() { + stroke(0) + fill(255); + rect(x, y, w, h); + text(label, x, y, w, h); + } + this.mouseClick = function(x, y) { + if (callback != null && + x > this.x && x <= this.x + this.w && + y > this.y && y <= this.y + this.h) { + callback(); + } + } +} +function runpause() { + paused = !paused; } -function draw() { +function mouseClicked() { + for (var i = 0; i < buttons.length; i++) { + buttons[i].mouseClick(mouseX, mouseY); + } - if (openSet.length > 0) { +} - var winner = 0; - for (var i = 1; i < openSet.length; i++) { - if (openSet[i].f < openSet[winner].f) { - winner = i; - } - //if we have a tie according to the standard heuristic - if (openSet[i].f == openSet[winner].f) { - //Prefer to explore options with longer known paths (closer to goal) - if (openSet[i].g > openSet[winner].g) { - winner = i; - } - //if we're using Manhattan distances then also break ties - //of the known distance measure by using the visual heuristic. - //This ensures that the search concentrates on routes that look - //more direct. This makes no difference to the actual path distance - //but improves the look for things like games or more closely - //approximates the real shortest path if using grid sampled data for - //planning natural paths. - if (!allowDiagonals) { - if (openSet[i].g == openSet[winner].g && openSet[i].vh < openSet[winner].vh) { - winner = i; - } - } - } +function doGUI() { + for (var i = 0; i < buttons.length; i++) { + buttons[i].show(); } - var current = openSet[winner]; +} - if (current === end) { - noLoop(); - console.log("DONE!"); +function PathFinder(map, start, end) { + + this.map = map; + this.currentBestNode = start; + this.openSet = []; + this.openSet.push(start); + this.closedSet = []; + this.start = start; + this.end = end; + + this.step = function() { + + if (this.openSet.length > 0) { + + var winner = 0; + for (var i = 1; i < this.openSet.length; i++) { + if (this.openSet[i].f < this.openSet[winner].f) { + winner = i; + } + //if we have a tie according to the standard heuristic + if (this.openSet[i].f == this.openSet[winner].f) { + //Prefer to explore options with longer known paths (closer to goal) + if (this.openSet[i].g > this.openSet[winner].g) { + winner = i; + } + //if we're using Manhattan distances then also break ties + //of the known distance measure by using the visual heuristic. + //This ensures that the search concentrates on routes that look + //more direct. This makes no difference to the actual path distance + //but improves the look for things like games or more closely + //approximates the real shortest path if using grid sampled data for + //planning natural paths. + if (!allowDiagonals) { + if (this.openSet[i].g == this.openSet[winner].g && + this.openSet[i].vh < this.openSet[winner].vh) { + winner = i; + } + } + } + } + var current = this.openSet[winner]; + + if (current === this.end) { + noLoop(); + console.log("DONE!"); + } + + + removeFromArray(this.openSet, current); + this.closedSet.push(current); + + var neighbors = current.neighbors; + for (var i = 0; i < neighbors.length; i++) { + var neighbor = neighbors[i]; + + if (!this.closedSet.includes(neighbor) && !neighbor.wall) { + var tempG = current.g + heuristic(neighbor, current); + + var newPath = false; + if (this.openSet.includes(neighbor)) { + if (tempG < neighbor.g) { + neighbor.g = tempG; + newPath = true; + } + } else { + neighbor.g = tempG; + newPath = true; + this.openSet.push(neighbor); + } + + if (newPath) { + neighbor.h = heuristic(neighbor, this.end); + if (allowDiagonals) { + neighbor.vh = visualDist(neighbor, this.end); + } + neighbor.f = neighbor.g + neighbor.h; + neighbor.previous = current; + } + } + + } + this.currentBestNode = current; + // we can keep going + } else { + console.log('no solution'); + noLoop(); + return; + // no solution + } } +} +var gamemap; +var buttons = []; +var paused = false; +var pathfinder; - removeFromArray(openSet, current); - closedSet.push(current); +function setup() { + createCanvas(500, 500); + console.log('A*'); - var neighbors = current.neighbors; - for (var i = 0; i < neighbors.length; i++) { - var neighbor = neighbors[i]; + var rows = 50; + var cols = 50; + gamemap = new TerrainMap(cols,rows,10,10,410,410); + start = gamemap.grid[0][0]; + end = gamemap.grid[cols-1][rows-1]; + start.wall = false; + end.wall = false; - if (!closedSet.includes(neighbor) && !neighbor.wall) { - var tempG = current.g + heuristic(neighbor, current); + pathfinder = new PathFinder(gamemap,start,end); - 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); - } + buttons.push(new Button("run/pause", 420, 20, 60, 30, runpause)); + buttons.push(new Button("step", 420, 70, 60, 30, runpause)); + buttons.push(new Button("reset", 420, 120, 60, 30, runpause)); - if (newPath) { - neighbor.h = heuristic(neighbor, end); - if (allowDiagonals) { - neighbor.vh = visualDist(neighbor, end); - } - neighbor.f = neighbor.g + neighbor.h; - neighbor.previous = current; - } - } +} - } - // we can keep going - } else { - console.log('no solution'); - noLoop(); - return; +function draw() { - // no solution - } + background(255); - background(255); + doGUI(); - for (var i = 0; i < cols; i++) { - for (var j = 0; j < rows; j++) { - grid[i][j].show(color(255)); + if (!paused) { + pathfinder.step(); } - } + for (var i = 0; i < gamemap.cols; i++) { + for (var j = 0; j < gamemap.rows; j++) { + gamemap.grid[i][j].show(color(255)); + } + } - for (var i = 0; i < closedSet.length; i++) { - closedSet[i].show(color(255, 0, 0)); - } - for (var i = 0; i < openSet.length; i++) { - openSet[i].show(color(0, 255, 0)); - } + for (var i = 0; i < pathfinder.closedSet.length; i++) { + pathfinder.closedSet[i].show(color(255, 0, 0)); + } + for (var i = 0; i < pathfinder.openSet.length; i++) { + pathfinder.openSet[i].show(color(0, 255, 0)); + } - // Find the path - path = []; - var temp = current; - path.push(temp); - while (temp.previous) { - path.push(temp.previous); - temp = temp.previous; - } + + // Find the path + path = []; + var temp = pathfinder.currentBestNode; + path.push(temp); + while (temp.previous) { + path.push(temp.previous); + temp = temp.previous; + } - for (var i = 0; i < path.length; i++) { - //path[i].show(color(0, 0, 255)); - } + for (var i = 0; i < path.length; i++) { + //path[i].show(color(0, 0, 255)); + } - noFill(); - stroke(255, 0, 200); - strokeWeight(w / 2); - beginShape(); - for (var i = 0; i < path.length; i++) { - vertex(path[i].i * w + w / 2, path[i].j * h + h / 2); - } - endShape(); + noFill(); + stroke(255, 0, 200); + strokeWeight(gamemap.w/gamemap.cols / 2); + beginShape(); + for (var i = 0; i < path.length; i++) { + vertex(path[i].x + path[i].width / 2, path[i].y + path[i].height / 2); + } + endShape(); } From c658c7a6dd0ab18d54c81e72fe1127bfcec641fc Mon Sep 17 00:00:00 2001 From: ootsby Date: Mon, 16 Jan 2017 23:45:07 +0000 Subject: [PATCH 2/6] Fixed rendering glitch. --- spot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spot.js b/spot.js index 6035e0c..d38ef23 100644 --- a/spot.js +++ b/spot.js @@ -23,10 +23,10 @@ function Spot(i, j, x, y ,width, height, isWall) { this.wall = isWall; this.show = function(color) { + noStroke(); fill(color); if (this.wall) { fill(0); - noStroke(); ellipse(this.x + this.width / 2, this.y + this.height / 2, this.width / 2, this.height / 2); } else { rect(this.x, this.y, this.width - 1, this.height - 1); From 5d6e6202bbb079bfd521174fc12f5e11d9292135 Mon Sep 17 00:00:00 2001 From: ootsby Date: Mon, 16 Jan 2017 23:47:14 +0000 Subject: [PATCH 3/6] Added diagonal switching via GUI. Fixed bug where the visual heuristic wasn't being run for 4-way movement. Added printing of open node info on mouseover. --- astarpathfinder.js | 9 +++--- searchmap.js | 4 +-- sketch.js | 75 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/astarpathfinder.js b/astarpathfinder.js index 05b45b3..34ed28a 100644 --- a/astarpathfinder.js +++ b/astarpathfinder.js @@ -1,12 +1,13 @@ -function AStarPathFinder(map, start, end) { +function AStarPathFinder(map, start, end, allowDiagonals) { this.map = map; this.lastCheckedNode = start; this.openSet = []; - // openSet starts with eginning only + // openSet starts with beginning node only this.openSet.push(start); this.closedSet = []; this.start = start; this.end = end; + this.allowDiagonals = allowDiagonals; //Run one finding step. //returns 0 if search ongoing @@ -35,7 +36,7 @@ function AStarPathFinder(map, start, end) { //but improves the look for things like games or more closely //approximates the real shortest path if using grid sampled data for //planning natural paths. - if (!allowDiagonals) { + if (!this.allowDiagonals) { if (this.openSet[i].g == this.openSet[winner].g && this.openSet[i].vh < this.openSet[winner].vh) { winner = i; @@ -81,7 +82,7 @@ function AStarPathFinder(map, start, end) { // Yes, it's a better path if (newPath) { neighbor.h = heuristic(neighbor, this.end); - if (allowDiagonals) { + if (!this.allowDiagonals) { neighbor.vh = visualDist(neighbor, this.end); } neighbor.f = neighbor.g + neighbor.h; diff --git a/searchmap.js b/searchmap.js index d0ade4b..afd9506 100644 --- a/searchmap.js +++ b/searchmap.js @@ -1,4 +1,4 @@ -function SearchMap(cols, rows, x, y, w, h) { +function SearchMap(cols, rows, x, y, w, h, allowDiagonals) { // How many columns and rows? this.cols = cols; this.rows = rows; @@ -11,7 +11,7 @@ function SearchMap(cols, rows, x, y, w, h) { this.y = y; this.w = w; this.h = h; - // Start and end + // Making a 2D array for (var i = 0; i < cols; i++) { this.grid[i] = []; diff --git a/sketch.js b/sketch.js index 6c7f61d..d23e449 100644 --- a/sketch.js +++ b/sketch.js @@ -44,6 +44,33 @@ function heuristic(a, b) { return d; } +function SettingBox(label, x, y, isSet, callback){ + this.label = label; + this.x = x; + this.y = y; + this.isSet = isSet; + this.callback = callback; + + this.show = function(){ + //noFill(); + ellipse(this.x+10,this.y+10,20,20); + //fill(0); + if( this.isSet ){ + ellipse(this.x+10,this.y+10,3,3); + } + text(label, this.x + 25, this.y+15); + } + + this.mouseClick = function(x, y) { + if (x > this.x && x <= this.x + 20 && + y > this.y && y <= this.y + 20) { + this.isSet = !this.isSet; + if( this.callback != null) + this.callback(this); + } + } +} + function Button(label, x, y, w, h, callback) { this.label = label; this.x = x; @@ -60,10 +87,10 @@ function Button(label, x, y, w, h, callback) { } this.mouseClick = function(x, y) { - if (callback != null && + if (this.callback != null && x > this.x && x <= this.x + this.w && y > this.y && y <= this.y + this.h) { - callback(this); + this.callback(this); } } } @@ -87,22 +114,26 @@ function restart(button) { pauseUnpause(true); } +function toggleDiagonals(){ + allowDiagonals = !allowDiagonals; +} + function mouseClicked() { - for (var i = 0; i < buttons.length; i++) { - buttons[i].mouseClick(mouseX, mouseY); + for (var i = 0; i < uiElements.length; i++) { + uiElements[i].mouseClick(mouseX, mouseY); } } function doGUI() { - for (var i = 0; i < buttons.length; i++) { - buttons[i].show(); + for (var i = 0; i < uiElements.length; i++) { + uiElements[i].show(); } } var gamemap; -var buttons = []; +var uiElements = []; var paused = true; var pathfinder; var status = ""; @@ -110,25 +141,26 @@ var stepsAllowed = 0; var runPauseButton; function initaliseSearchExample(rows, cols) { - gamemap = new SearchMap(cols, rows, 10, 10, 410, 410); + gamemap = new SearchMap(cols, rows, 10, 10, 410, 410, allowDiagonals); start = gamemap.grid[0][0]; end = gamemap.grid[cols - 1][rows - 1]; start.wall = false; end.wall = false; - pathfinder = new AStarPathFinder(gamemap, start, end); + pathfinder = new AStarPathFinder(gamemap, start, end, allowDiagonals); } function setup() { - createCanvas(500, 500); + createCanvas(600, 600); console.log('A*'); initaliseSearchExample(cols, rows); runPauseButton = new Button("run", 430, 20, 50, 30, runpause); - buttons.push(runPauseButton); - buttons.push(new Button("step", 430, 70, 50, 30, step)); - buttons.push(new Button("restart", 430, 120, 50, 30, restart)); + uiElements.push(runPauseButton); + uiElements.push(new Button("step", 430, 70, 50, 30, step)); + uiElements.push(new Button("restart", 430, 120, 50, 30, restart)); + uiElements.push(new SettingBox("AllowDiag", 430, 180, allowDiagonals, toggleDiagonals)); } @@ -169,10 +201,25 @@ function draw() { pathfinder.closedSet[i].show(color(255, 0, 0, 50)); } + var infoNode = null; + for (var i = 0; i < pathfinder.openSet.length; i++) { - pathfinder.openSet[i].show(color(0, 255, 0, 50)); + var node = pathfinder.openSet[i]; + node.show(color(0, 255, 0, 50)); + if( mouseX > node.x && mouseX < node.x+node.width && + mouseY > node.y && mouseY < node.y + node.height ){ + infoNode = node; + } } + fill(0); + if( infoNode != null ){ + text("f = " + infoNode.f, 430, 230); + text("g = " + infoNode.g, 430, 250); + text("h = " + infoNode.f, 430, 270); + text("vh = " + infoNode.vh, 430, 290); + + } // Find the path by working backwards path = []; From d94ba5ef3b67a1d9a92ff66355de95e2f48823a1 Mon Sep 17 00:00:00 2001 From: ootsby Date: Tue, 17 Jan 2017 00:22:23 +0000 Subject: [PATCH 4/6] Moved heuristics into pathfinder. corrected mistake in node info printing. --- astarpathfinder.js | 29 ++++++++++++++++++++++++++--- sketch.js | 23 +---------------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/astarpathfinder.js b/astarpathfinder.js index 34ed28a..35e46f3 100644 --- a/astarpathfinder.js +++ b/astarpathfinder.js @@ -9,6 +9,29 @@ function AStarPathFinder(map, start, end, allowDiagonals) { this.end = end; this.allowDiagonals = allowDiagonals; + //This function returns a measure of aesthetic preference for + //use when ordering the openSet. It is used to prioritise + //between equal standard heuristic scores. It can therefore + //be anything you like without affecting the ability to find + //a minimum cost path. + + this.visualDist = function(a, b) { + return dist(a.i, a.j, b.i, b.j); + } + + // An educated guess of how far it is between two points + + this.heuristic = function(a, b) { + var d; + if (allowDiagonals) { + d = dist(a.i, a.j, b.i, b.j); + } else { + d = abs(a.i - b.i) + abs(a.j - b.j); + } + return d; + } + + //Run one finding step. //returns 0 if search ongoing //returns 1 if goal reached @@ -64,7 +87,7 @@ function AStarPathFinder(map, start, end, allowDiagonals) { // Valid next spot? if (!this.closedSet.includes(neighbor) && !neighbor.wall) { - var tempG = current.g + heuristic(neighbor, current); + var tempG = current.g + this.heuristic(neighbor, current); // Is this a better path than before? var newPath = false; @@ -81,9 +104,9 @@ function AStarPathFinder(map, start, end, allowDiagonals) { // Yes, it's a better path if (newPath) { - neighbor.h = heuristic(neighbor, this.end); + neighbor.h = this.heuristic(neighbor, this.end); if (!this.allowDiagonals) { - neighbor.vh = visualDist(neighbor, this.end); + neighbor.vh = this.visualDist(neighbor, this.end); } neighbor.f = neighbor.g + neighbor.h; neighbor.previous = current; diff --git a/sketch.js b/sketch.js index d23e449..cc7e7ce 100644 --- a/sketch.js +++ b/sketch.js @@ -22,27 +22,6 @@ function removeFromArray(arr, elt) { } } -//This function returns a measure of aesthetic preference for -//use when ordering the openSet. It is used to prioritise -//between equal standard heuristic scores. It can therefore -//be anything you like without affecting the ability to find -//a minimum cost path. - -function visualDist(a, b) { - return dist(a.i, a.j, b.i, b.j); -} - -// An educated guess of how far it is between two points - -function heuristic(a, b) { - var d; - if (allowDiagonals) { - d = dist(a.i, a.j, b.i, b.j); - } else { - d = abs(a.i - b.i) + abs(a.j - b.j); - } - return d; -} function SettingBox(label, x, y, isSet, callback){ this.label = label; @@ -216,7 +195,7 @@ function draw() { if( infoNode != null ){ text("f = " + infoNode.f, 430, 230); text("g = " + infoNode.g, 430, 250); - text("h = " + infoNode.f, 430, 270); + text("h = " + infoNode.h, 430, 270); text("vh = " + infoNode.vh, 430, 290); } From 91404998358a3c48d7979ca0e0e517389d0cd0d6 Mon Sep 17 00:00:00 2001 From: ootsby Date: Wed, 18 Jan 2017 03:16:31 +0000 Subject: [PATCH 5/6] Buffered rendering of map walls by copying to image. Clearing of timings on restart. --- sketch.js | 201 ++++++++++++++----------- spot.js | 428 +++++++++++++++++++++++++++--------------------------- 2 files changed, 328 insertions(+), 301 deletions(-) diff --git a/sketch.js b/sketch.js index 5cdb778..27e5fa7 100644 --- a/sketch.js +++ b/sketch.js @@ -30,28 +30,32 @@ var percentWalls = (allowDiagonals ? (canPassThroughCorners ? 0.4 : 0.3) : 0.2); var t; var timings = {}; +function clearTimings() { + timings = {}; +} + function startTime() { - t = millis(); + t = millis(); } function recordTime(n) { - if (!timings[n]) { - timings[n] = { - sum: millis() - t, - count: 1 - }; - } else { - timings[n].sum = timings[n].sum + millis() - t; - timings[n].count = timings[n].count + 1; - } + if (!timings[n]) { + timings[n] = { + sum: millis() - t, + count: 1 + }; + } else { + timings[n].sum = timings[n].sum + millis() - t; + timings[n].count = timings[n].count + 1; + } } function logTimings() { - for (var prop in timings) { - if(timings.hasOwnProperty(prop)) { - console.log(prop + " = " + (timings[prop].sum / timings[prop].count).toString() + " ms"); + for (var prop in timings) { + if (timings.hasOwnProperty(prop)) { + console.log(prop + " = " + (timings[prop].sum / timings[prop].count).toString() + " ms"); + } } - } } @@ -66,31 +70,31 @@ function removeFromArray(arr, elt) { } -function SettingBox(label, x, y, isSet, callback){ - this.label = label; - this.x = x; - this.y = y; - this.isSet = isSet; - this.callback = callback; +function SettingBox(label, x, y, isSet, callback) { + this.label = label; + this.x = x; + this.y = y; + this.isSet = isSet; + this.callback = callback; - this.show = function(){ - //noFill(); - ellipse(this.x+10,this.y+10,20,20); - //fill(0); - if( this.isSet ){ - ellipse(this.x+10,this.y+10,3,3); + this.show = function() { + //noFill(); + ellipse(this.x + 10, this.y + 10, 20, 20); + //fill(0); + if (this.isSet) { + ellipse(this.x + 10, this.y + 10, 3, 3); + } + text(label, this.x + 25, this.y + 15); + } + + this.mouseClick = function(x, y) { + if (x > this.x && x <= this.x + 20 && + y > this.y && y <= this.y + 20) { + this.isSet = !this.isSet; + if (this.callback != null) + this.callback(this); + } } - text(label, this.x + 25, this.y+15); - } - - this.mouseClick = function(x, y) { - if (x > this.x && x <= this.x + 20 && - y > this.y && y <= this.y + 20) { - this.isSet = !this.isSet; - if( this.callback != null) - this.callback(this); - } - } } function Button(label, x, y, w, h, callback) { @@ -118,13 +122,13 @@ function Button(label, x, y, w, h, callback) { } function step(button) { - pauseUnpause(true); - stepsAllowed = 1; + pauseUnpause(true); + stepsAllowed = 1; } -function pauseUnpause( pause ){ - paused = pause; - runPauseButton.label = paused ? "run" : "pause"; +function pauseUnpause(pause) { + paused = pause; + runPauseButton.label = paused ? "run" : "pause"; } function runpause(button) { @@ -132,12 +136,14 @@ function runpause(button) { } function restart(button) { + logTimings(); + clearTimings(); initaliseSearchExample(cols, rows); pauseUnpause(true); } -function toggleDiagonals(){ - allowDiagonals = !allowDiagonals; +function toggleDiagonals() { + allowDiagonals = !allowDiagonals; } function mouseClicked() { @@ -163,6 +169,7 @@ var stepsAllowed = 0; var runPauseButton; function initaliseSearchExample(rows, cols) { + mapGraphic = null; gamemap = new SearchMap(cols, rows, 10, 10, 410, 410, allowDiagonals, percentWalls); start = gamemap.grid[0][0]; end = gamemap.grid[cols - 1][rows - 1]; @@ -173,14 +180,14 @@ function initaliseSearchExample(rows, cols) { } function setup() { - startTime(); - - if (getURL().toLowerCase().indexOf("fullscreen") === -1) { - createCanvas(600, 600); - } else { - var sz = min(windowWidth, windowHeight); - createCanvas(sz, sz); - } + startTime(); + + if (getURL().toLowerCase().indexOf("fullscreen") === -1) { + createCanvas(600, 600); + } else { + var sz = min(windowWidth, windowHeight); + createCanvas(sz, sz); + } console.log('A*'); initaliseSearchExample(cols, rows); @@ -194,44 +201,62 @@ function setup() { recordTime("Setup"); } -function draw() { - - // Draw current state of everything - background(255); - - doGUI(); - +function searchStep() { if (!paused || stepsAllowed > 0) { - startTime(); + startTime(); var result = pathfinder.step(); - recordTime("AStar Search"); + recordTime("AStar Iteration"); stepsAllowed--; - switch(result){ - case -1: - status = "No Solution"; - logTimings(); - pauseUnpause(true); - break; - case 1: - status = "Goal Reached!"; - logTimings(); - pauseUnpause(true); - break; - case 0: - status = "Still Searching" - break; + switch (result) { + case -1: + status = "No Solution"; + logTimings(); + pauseUnpause(true); + break; + case 1: + status = "Goal Reached!"; + logTimings(); + pauseUnpause(true); + break; + case 0: + status = "Still Searching" + break; } } +} + +var mapGraphic = null; + +function drawMap() { + if( mapGraphic == null ) { + for (var i = 0; i < gamemap.cols; i++) { + for (var j = 0; j < gamemap.rows; j++) { + if( gamemap.grid[i][j].wall ) { + gamemap.grid[i][j].show(color(255)); + } + } + } + mapGraphic = get(gamemap.x,gamemap.y,gamemap.w,gamemap.h); + } + + image(mapGraphic, gamemap.x, gamemap.y); +} + +function draw() { + + searchStep(); + + // Draw current state of everything + background(255); + + doGUI(); text("Search status - " + status, 10, 450); startTime(); - for (var i = 0; i < gamemap.cols; i++) { - for (var j = 0; j < gamemap.rows; j++) { - gamemap.grid[i][j].show(color(255)); - } - } + + drawMap(); for (var i = 0; i < pathfinder.closedSet.length; i++) { pathfinder.closedSet[i].show(color(255, 0, 0, 50)); @@ -242,19 +267,19 @@ function draw() { for (var i = 0; i < pathfinder.openSet.length; i++) { var node = pathfinder.openSet[i]; node.show(color(0, 255, 0, 50)); - if( mouseX > node.x && mouseX < node.x+node.width && - mouseY > node.y && mouseY < node.y + node.height ){ + if (mouseX > node.x && mouseX < node.x + node.width && + mouseY > node.y && mouseY < node.y + node.height) { infoNode = node; - } + } } recordTime("Draw Grid"); fill(0); - if( infoNode != null ){ - text("f = " + infoNode.f, 430, 230); - text("g = " + infoNode.g, 430, 250); - text("h = " + infoNode.h, 430, 270); - text("vh = " + infoNode.vh, 430, 290); + if (infoNode != null) { + text("f = " + infoNode.f, 430, 230); + text("g = " + infoNode.g, 430, 250); + text("h = " + infoNode.h, 430, 270); + text("vh = " + infoNode.vh, 430, 290); } diff --git a/spot.js b/spot.js index 76e74bb..8333ffd 100644 --- a/spot.js +++ b/spot.js @@ -7,242 +7,244 @@ // An object to describe a spot in the grid function Spot(i, j, x, y, width, height, isWall, grid) { - this.grid = grid; - // Location - this.i = i; - this.j = j; + + this.grid = grid; + + // Location + this.i = i; + this.j = j; this.x = x; this.y = y; this.width = width; this.height = height; - // f, g, and h values for A* - this.f = 0; - this.g = 0; - this.h = 0; - this.vh = 0; //visual heuristic for prioritising path options - // Neighbors - this.neighbors = undefined; - this.neighboringWalls = undefined; - // Where did I come from? - this.previous = undefined; - // Am I an wall? + // f, g, and h values for A* + this.f = 0; + this.g = 0; + this.h = 0; + this.vh = 0; //visual heuristic for prioritising path options + // Neighbors + this.neighbors = undefined; + this.neighboringWalls = undefined; + // Where did I come from? + this.previous = undefined; + // Am I an wall? this.wall = isWall; - // Display me + // Display me this.show = function(color) { - if (this.wall) { - fill(0); - noStroke(); + if (this.wall) { + fill(0); + noStroke(); - if (drawingOption === 0) { - ellipse(this.x,this.y, this.width*0.5,this.height*0.5); - } else { - rect(this.x,this.y, this.width, this.height); - } + if (drawingOption === 0) { + ellipse(this.x, this.y, this.width * 0.5, this.height * 0.5); + } else { + rect(this.x, this.y, this.width, this.height); + } - stroke(0); + stroke(0); strokeWeight(this.width / 2); - var nWalls = this.getNeighboringWalls(); - for (var i = 0; i < nWalls.length; i++) { - var nw = nWalls[i]; + var nWalls = this.getNeighboringWalls(this.grid); + for (var i = 0; i < nWalls.length; i++) { + var nw = nWalls[i]; - // Draw line between this and bottom/right neighbor walls - if ((nw.i > this.i && nw.j == this.j) - || (nw.i == this.i && nw.j > this.j)) { - line(this.x + this.width / 2, - this.y + this.height / 2, - nw.x + nw.width / 2, - nw.y + nw.height / 2); - } + // Draw line between this and bottom/right neighbor walls + if ((nw.i > this.i && nw.j == this.j) || + (nw.i == this.i && nw.j > this.j)) { + line(this.x + this.width / 2, + this.y + this.height / 2, + nw.x + nw.width / 2, + nw.y + nw.height / 2); + } - // Draw line between this and bottom-left/bottom-right neighbor walls - if (!canPassThroughCorners && (nw.j > this.j) - && (nw.i < this.i || nw.i > this.i)) { - line(this.x + this.width / 2, - this.y + this.height / 2, - nw.x + nw.width / 2, - nw.y + nw.height / 2); - } - } + // Draw line between this and bottom-left/bottom-right neighbor walls + if (!canPassThroughCorners && (nw.j > this.j) && + (nw.i < this.i || nw.i > this.i)) { + line(this.x + this.width / 2, + this.y + this.height / 2, + nw.x + nw.width / 2, + nw.y + nw.height / 2); + } + } } else if (color) { fill(color); - noStroke(); + noStroke(); rect(this.x, this.y, this.width, this.height); + } } - } - this.getNeighbors = function() { - if (!this.neighbors) { - this.addNeighbors(); - } - return this.neighbors; - } - - // Figure out who my neighbors are - this.addNeighbors = function() { - this.neighbors = []; - var i = this.i; - var j = this.j; - - // right - if (i < cols - 1) { - var n = this.grid[i + 1][j]; - if (!n.wall) { - this.neighbors.push(n); - } - } - // left - if (i > 0) { - var n = this.grid[i - 1][j]; - if (!n.wall) { - this.neighbors.push(n); - } - } - // bottom - if (j < rows - 1) { - var n = this.grid[i][j + 1]; - if (!n.wall) { - this.neighbors.push(n); - } - } - // top - if (j > 0) { - var n = this.grid[i][j - 1]; - if (!n.wall) { - this.neighbors.push(n); - } - } - if (allowDiagonals) { - // top-left - if (i > 0 && j > 0) { - var n = this.grid[i - 1][j - 1]; - if (!n.wall) { - if (canPassThroughCorners) { - this.neighbors.push(n); - } else { - var top = this.grid[i][j - 1]; - var left = this.grid[i - 1][j]; - if (!(top.wall && left.wall)) { - this.neighbors.push(n); - } - } - } - } - // top-right - if (i < cols - 1 && j > 0) { - var n = this.grid[i + 1][j - 1]; - if (!n.wall) { - if (canPassThroughCorners) { - this.neighbors.push(n); - } else { - var top = this.grid[i][j - 1]; - var right = this.grid[i + 1][j]; - if (!(top.wall && right.wall)) { - this.neighbors.push(n); - } - } - } - } - // bottom-left - if (i > 0 && j < rows - 1) { - var n = this.grid[i - 1][j + 1]; - if (!n.wall) { - if (canPassThroughCorners) { - this.neighbors.push(n); - } else { - var bottom = this.grid[i][j + 1]; - var left = this.grid[i - 1][j]; - if (!(bottom.wall && left.wall)) { - this.neighbors.push(n); - } - } - } - } - // bottom-right - if (i < cols - 1 && j < rows - 1) { - var n = this.grid[i + 1][j + 1]; - if (!n.wall) { - if (canPassThroughCorners) { - this.neighbors.push(n); - } else { - var bottom = this.grid[i][j + 1]; - var right = this.grid[i + 1][j]; - if (!(bottom.wall && right.wall)) { - this.neighbors.push(n); - } - } - } - } + this.getNeighbors = function() { + if (!this.neighbors) { + this.addNeighbors(this.grid); + } + return this.neighbors; } - } - this.getNeighboringWalls = function(grid) { - if (this.neighboringWalls) { - return this.neighboringWalls; + // Figure out who my neighbors are + this.addNeighbors = function(grid) { + this.neighbors = []; + var i = this.i; + var j = this.j; + + // right + if (i < cols - 1) { + var n = grid[i + 1][j]; + if (!n.wall) { + this.neighbors.push(n); + } + } + // left + if (i > 0) { + var n = grid[i - 1][j]; + if (!n.wall) { + this.neighbors.push(n); + } + } + // bottom + if (j < rows - 1) { + var n = grid[i][j + 1]; + if (!n.wall) { + this.neighbors.push(n); + } + } + // top + if (j > 0) { + var n = grid[i][j - 1]; + if (!n.wall) { + this.neighbors.push(n); + } + } + if (allowDiagonals) { + // top-left + if (i > 0 && j > 0) { + var n = grid[i - 1][j - 1]; + if (!n.wall) { + if (canPassThroughCorners) { + this.neighbors.push(n); + } else { + var top = grid[i][j - 1]; + var left = grid[i - 1][j]; + if (!(top.wall && left.wall)) { + this.neighbors.push(n); + } + } + } + } + // top-right + if (i < cols - 1 && j > 0) { + var n = grid[i + 1][j - 1]; + if (!n.wall) { + if (canPassThroughCorners) { + this.neighbors.push(n); + } else { + var top = grid[i][j - 1]; + var right = grid[i + 1][j]; + if (!(top.wall && right.wall)) { + this.neighbors.push(n); + } + } + } + } + // bottom-left + if (i > 0 && j < rows - 1) { + var n = grid[i - 1][j + 1]; + if (!n.wall) { + if (canPassThroughCorners) { + this.neighbors.push(n); + } else { + var bottom = grid[i][j + 1]; + var left = grid[i - 1][j]; + if (!(bottom.wall && left.wall)) { + this.neighbors.push(n); + } + } + } + } + // bottom-right + if (i < cols - 1 && j < rows - 1) { + var n = grid[i + 1][j + 1]; + if (!n.wall) { + if (canPassThroughCorners) { + this.neighbors.push(n); + } else { + var bottom = grid[i][j + 1]; + var right = grid[i + 1][j]; + if (!(bottom.wall && right.wall)) { + this.neighbors.push(n); + } + } + } + } + } } - this.neighboringWalls = []; - var i = this.i; - var j = this.j; + this.getNeighboringWalls = function(grid) { + if (this.neighboringWalls) { + return this.neighboringWalls; + } - // right - if (i < cols - 1) { - var n = this.grid[i + 1][j]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // left - if (i > 0) { - var n = this.grid[i - 1][j]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // bottom - if (j < rows - 1) { - var n = this.grid[i][j + 1]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // top - if (j > 0) { - var n = this.grid[i][j - 1]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // top-left - if (i > 0 && j > 0) { - var n = this.grid[i - 1][j - 1]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // top-right - if (i < cols - 1 && j > 0) { - var n = this.grid[i + 1][j - 1]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // bottom-left - if (i > 0 && j < rows - 1) { - var n = this.grid[i - 1][j + 1]; - if (n.wall) { - this.neighboringWalls.push(n); - } - } - // bottom-right - if (i < cols - 1 && j < rows - 1) { - var n = this.grid[i + 1][j + 1]; - if (n.wall) { - this.neighboringWalls.push(n); - } + this.neighboringWalls = []; + var i = this.i; + var j = this.j; + + // right + if (i < cols - 1) { + var n = grid[i + 1][j]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // left + if (i > 0) { + var n = grid[i - 1][j]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // bottom + if (j < rows - 1) { + var n = grid[i][j + 1]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // top + if (j > 0) { + var n = grid[i][j - 1]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // top-left + if (i > 0 && j > 0) { + var n = grid[i - 1][j - 1]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // top-right + if (i < cols - 1 && j > 0) { + var n = grid[i + 1][j - 1]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // bottom-left + if (i > 0 && j < rows - 1) { + var n = grid[i - 1][j + 1]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + // bottom-right + if (i < cols - 1 && j < rows - 1) { + var n = grid[i + 1][j + 1]; + if (n.wall) { + this.neighboringWalls.push(n); + } + } + return this.neighboringWalls; } - return this.neighboringWalls; - } } From eade7b36ba38bbe42f789b58d349cb6f63002367 Mon Sep 17 00:00:00 2001 From: ootsby Date: Wed, 18 Jan 2017 03:34:37 +0000 Subject: [PATCH 6/6] Moved path extraction and drawing into functions as per upstream. Moved removeFromArray function to Pathfinder. --- astarpathfinder.js | 20 +++++++++++++++----- sketch.js | 40 ++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/astarpathfinder.js b/astarpathfinder.js index 4a4999b..cb5160d 100644 --- a/astarpathfinder.js +++ b/astarpathfinder.js @@ -1,3 +1,4 @@ + function AStarPathFinder(map, start, end, allowDiagonals) { this.map = map; this.lastCheckedNode = start; @@ -31,6 +32,15 @@ function AStarPathFinder(map, start, end, allowDiagonals) { return d; } + // Function to delete element from the array + this.removeFromArray = function(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); + } + } + } //Run one finding step. //returns 0 if search ongoing @@ -77,7 +87,7 @@ function AStarPathFinder(map, start, end, allowDiagonals) { } // Best option moves from openSet to closedSet - removeFromArray(this.openSet, current); + this.removeFromArray(this.openSet, current); this.closedSet.push(current); // Check all the neighbors @@ -95,16 +105,16 @@ function AStarPathFinder(map, start, end, allowDiagonals) { // Is this a better path than before? if (!this.openSet.includes(neighbor)) { - this.openSet.push(neighbor); + this.openSet.push(neighbor); } else if (tempG >= neighbor.g) { - // No, it's not a better path - continue; + // No, it's not a better path + continue; } neighbor.g = tempG; neighbor.h = this.heuristic(neighbor, end); if (allowDiagonals) { - neighbor.vh = this.visualDist(neighbor, end); + neighbor.vh = this.visualDist(neighbor, end); } neighbor.f = neighbor.g + neighbor.h; neighbor.previous = current; diff --git a/sketch.js b/sketch.js index 27e5fa7..d4483ee 100644 --- a/sketch.js +++ b/sketch.js @@ -59,17 +59,6 @@ function logTimings() { } -// 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 SettingBox(label, x, y, isSet, callback) { this.label = label; this.x = x; @@ -229,15 +218,15 @@ function searchStep() { var mapGraphic = null; function drawMap() { - if( mapGraphic == null ) { - for (var i = 0; i < gamemap.cols; i++) { - for (var j = 0; j < gamemap.rows; j++) { - if( gamemap.grid[i][j].wall ) { - gamemap.grid[i][j].show(color(255)); - } - } - } - mapGraphic = get(gamemap.x,gamemap.y,gamemap.w,gamemap.h); + if (mapGraphic == null) { + for (var i = 0; i < gamemap.cols; i++) { + for (var j = 0; j < gamemap.rows; j++) { + if (gamemap.grid[i][j].wall) { + gamemap.grid[i][j].show(color(255)); + } + } + } + mapGraphic = get(gamemap.x, gamemap.y, gamemap.w, gamemap.h); } image(mapGraphic, gamemap.x, gamemap.y); @@ -283,17 +272,25 @@ function draw() { } + var path = calcPath(pathfinder.lastCheckedNode); + drawPath(path); +} + +function calcPath(endNode) { startTime(); // Find the path by working backwards path = []; - var temp = pathfinder.lastCheckedNode; + var temp = endNode; path.push(temp); while (temp.previous) { path.push(temp.previous); temp = temp.previous; } recordTime("Calc Path"); + return path +} +function drawPath(path) { // Drawing path as continuous line noFill(); stroke(255, 0, 200); @@ -303,5 +300,4 @@ function draw() { vertex(path[i].x + path[i].width / 2, path[i].y + path[i].height / 2); } endShape(); - }