I am creating an algorithm to find the shortest path between two points in a maze but my current solution is too slow.
This is what I did:
Helper classes:
import { Coord } from "./Coord";
export class MazeResult {
position: Coord;
path: Array<Coord>;
constructor (_position: Coord, _path: Array<Coord>) {
this.position = _position;
this.path = _path;
}
}
export class Coord {
coordX: number;
coordY: number;
isFree: boolean;
element: Element;
distance: number;
constructor (xpos: number, ypos: number) {
this.coordX = xpos;
this.coordY = ypos;
this.distance = 0;
}
}
function isValid(visited: Array<Coord>, position: Coord)
{
let checkPosition = mapPositions.find(_p => _p.coordX == position.coordX &&
_p.coordY == position.coordY);
let isVisited = false;
for (var j = 0; j < visited.length; j ++) {
if ((visited[j].coordX == position.coordX && visited[j].coordY == position.coordY)) {
isVisited = true;
break;
}
}
return (position.coordY >= 0) &&
(position.coordY < lines.length) &&
(position.coordX >= 0) &&
(position.coordX < lines[0].length) &&
(checkPosition != undefined && checkPosition.element.elementType == ElementType.FIELD) &&
!isVisited;
}
function findPath(origin: Coord, target: Coord, minDistance: number) {
let queue = Array<MazeResult>();
let validpaths = Array<Array<Coord>>();
// New points, where we did not check the surroundings:
// remember the position and how we got there
// initially our starting point and a path containing only this point
let tmpElement = new MazeResult(origin, [origin]);
queue.push(tmpElement);
while (queue.length > 0) {
// get next position to check viable directions
let pointToReach = queue.shift();
let position = new Coord(0, 0);
let path = new Array<Coord>();
if(pointToReach != undefined){
position = pointToReach.position;
path = pointToReach.path;
}
// all points in each direction
let direction = [
[ position.coordX, position.coordY - 1 ],
[ position.coordX, position.coordY + 1 ],
[ position.coordX - 1, position.coordY ],
[ position.coordX + 1, position.coordY ]
];
for(var i = 0; i < direction.length; i++) {
let newTarget = new Coord(direction[i][0], direction[i][1]);
// is valid is just a function that checks whether the point is free.
if (isValid(path, newTarget)) {
//
let newPath = path.slice(0);
newPath.push(newTarget);
if ((validpaths.length > 0 && validpaths.sort(_p => _p.length)[0].length < newPath.length) ||
(minDistance > 0 && newPath.length > minDistance))
continue;
// check if we are at end
if (newTarget.coordX != target.coordX || newTarget.coordY != target.coordY) {
// remember position and the path to it
tmpElement = new MazeResult(newTarget, newPath);
queue.push(tmpElement);
} else {
// remember this path from start to end
validpaths.push(newPath);
// break here if you want only one shortest path
}
}
}
}
validpaths = validpaths.sort(sortByArrayPosition);
let result = validpaths.shift();
return result;
}
I have added a third paramether minDistance so I can compare with previous paths calculations to different points but this should not be relevant here.
How could I improve the performance of this algorithm?
If you are asking about path finding alghortims. Dijkstra is way to go. Look up this great open source library : Javascript-alghoritms . Essentialy what you want to do, is to create weighted graph representation. (You need to know basics of "Graph theory".)
Your weight between points will be in this case. Euclidian distance between points. And what keyword you should use for each point. (I used Mac Adress in my scenario). In your case it should be the unique id of each point.
You might want read up on Dijkstra's algorithm. It seems like what you have implemented is kind of similar but more complicated. You don't need to keep track of the entire path to each point, just the minimum distance to each position. You get the shortest path simply by going to to the neighboring cell with the lowest distance value.
Edit: looks like somebody beat me to it!
Thanks for the recommendations.
I ended with this solution:
let resultPath: Array<MazePoint>;
let visistedMazePoints: Array<Coord>;
function findBFSPath(origin: Coord, target: Coord) {
resultPath = new Array<MazePoint>();
visistedMazePoints = new Array<Coord>();
availablePaths = new Array<MazePoint>();
let tmpMazePoint = new MazePoint(origin, null);
resultPath.push(tmpMazePoint);
while(resultPath.length > 0) {
let currentPoint = resultPath.shift();
if (currentPoint != undefined &&
currentPoint.position.isEqual(target)) {
return currentPoint;
}
if (currentPoint != undefined &&
visistedMazePoints.find(_v => _v.isEqual(currentPoint.position)) == undefined) {
let neighbourMazePoint: MazePoint;
let xCord: Array<number>;
let yCord: Array<number>;
xCord = [0, -1, 1, 0];
yCord = [-1, 0, 0, 1];
for (let idx = 0; idx < 4; idx++) {
neighbourMazePoint = new MazePoint(new Coord(currentPoint.position.coordX + xCord[idx], currentPoint.position.coordY + yCord[idx]), currentPoint);
if (isValid(visistedMazePoints, neighbourMazePoint.position)) {
if (visistedMazePoints.find(_v => _v.isEqual(currentPoint.position)) == undefined) {
visistedMazePoints.push(currentPoint.position);
}
resultPath.push(neighbourMazePoint);
}
}
}
}
return null;
}
Related
For some reason, my implementation of bidirectional A* isn't returning the shortest path in very specific initializations of the graph.
I'm running two A* searches, one from the source to the destination, and one from the destination to the source. From what I've read, when the closed sets for both of these searches intersect, then we've connected the shortest paths from both searches and have found the shortest path.
The problem is, in very specific situations, the closed sets for both searches are intersecting before the searches can actually discover the nodes which should be included in their respective shortest paths. This means A* doesn't get to explore enough nodes to find the shortest path.
Is this intersection condition the right way to go about things, or is there a different condition I should be using to figure out when to stop both searches?
You can run my code here: https://jasperhuangg.github.io/pathfinding-visualizer.
The cases where this problem occurs are certain (not all) situations when both walls and weights have been placed on the grid.
Here is the code if it helps, sorry if it is very messy!:
async function bidirectionalAStar(graph, startNode, finishNode) {
recolorGrid();
searching = true;
const infinity = Number.MAX_VALUE;
var openSource = [];
var openDest = [];
var closedSource = [];
var closedDest = [];
var numSteps = -3; // -2 for both start and finish nodes + -1 for overlapping connecting node
$("#steps-taken").html("Cells Examined: " + numSteps);
const startX = startNode.x;
const startY = startNode.y;
const finishX = finishNode.x;
const finishY = finishNode.y;
var bidirectionalAStarGraph = shallowCopyGraph(graph, []);
// initialize all nodes to dist infinity from the startNode
for (let i = 0; i < bidirectionalAStarGraph.length; i++) {
for (let j = 0; j < bidirectionalAStarGraph[i].length; j++) {
bidirectionalAStarGraph[i][j].fSrc = infinity;
bidirectionalAStarGraph[i][j].gSrc = infinity;
bidirectionalAStarGraph[i][j].hSrc = infinity;
bidirectionalAStarGraph[i][j].fDest = infinity;
bidirectionalAStarGraph[i][j].gDest = infinity;
bidirectionalAStarGraph[i][j].hDest = infinity;
bidirectionalAStarGraph[i][j].setSource = "neither";
bidirectionalAStarGraph[i][j].setDest = "neither";
}
}
// initialize start/finish node distance from start/finish to 0
bidirectionalAStarGraph[startX][startY].fSrc = 0;
bidirectionalAStarGraph[startX][startY].gSrc = 0;
bidirectionalAStarGraph[startX][startY].hSrc = 0;
bidirectionalAStarGraph[startX][startY].setSource = "open";
openSource.push(bidirectionalAStarGraph[startX][startY]);
bidirectionalAStarGraph[finishX][finishY].fDest = 0;
bidirectionalAStarGraph[finishX][finishY].gDest = 0;
bidirectionalAStarGraph[finishX][finishY].hDest = 0;
bidirectionalAStarGraph[finishX][finishY].setDest = "open";
openDest.push(bidirectionalAStarGraph[finishX][finishY]);
var lastNodeSource;
var lastNodeDest;
while (openSource.length > 0 && openDest.length > 0) {
openSource.sort((a, b) => {
if (a.fSrc !== b.fSrc) return a.fSrc - b.fSrc;
else return a.hSrc - b.hSrc;
});
openDest.sort((a, b) => {
if (a.fDest !== b.fDest) return a.fDest - b.fDest;
else return a.hDest - b.hDest;
});
var currNodeSource = openSource.shift();
var currNodeDest = openDest.shift();
$(".currentNodeGray").removeClass("currentNodeGray");
$(".currentNodeSunset").removeClass("currentNodeSunset");
$(".currentNodeOcean").removeClass("currentNodeOcean");
$(".currentNodeChaos").removeClass("currentNodeChaos");
$(".currentNodeGreen").removeClass("currentNodeGreen");
$(".currentNodeCottonCandy").removeClass("currentNodeCottonCandy");
if (checkIntersection(closedSource, closedDest)) {
break; // the paths have reached each other
}
numSteps += 2;
$("#steps-taken").html("Cells Examined: " + numSteps);
currNodeSource.setSource = "closed";
currNodeDest.setDest = "closed";
closedSource.push(currNodeSource);
closedDest.push(currNodeDest);
colorNode(currNodeSource, "currentNode");
colorNode(currNodeDest, "currentNode");
if (lastNodeSource !== undefined && currentSpeed !== "instantaneous")
colorNode(lastNodeSource, "visited");
if (lastNodeDest !== undefined && currentSpeed !== "instantaneous")
colorNode(lastNodeDest, "visited");
if (currentSpeed === "fast") await sleep(20);
else if (currentSpeed === "medium") await sleep(180);
else if (currentSpeed === "slow") await sleep(500);
var validNeighborsSource = [];
var validNeighborsDest = [];
var left = currNodeSource.x - 1;
var right = currNodeSource.x + 1;
var up = currNodeSource.y - 1;
var down = currNodeSource.y + 1;
// consider all of the current node's (from source) valid neighbors
if (left >= 0 && !bidirectionalAStarGraph[left][currNodeSource.y].blocked) {
validNeighborsSource.push(
bidirectionalAStarGraph[left][currNodeSource.y]
);
}
if (
right < grid_width &&
!bidirectionalAStarGraph[right][currNodeSource.y].blocked
) {
validNeighborsSource.push(
bidirectionalAStarGraph[right][currNodeSource.y]
);
}
if (up >= 0 && !bidirectionalAStarGraph[currNodeSource.x][up].blocked) {
validNeighborsSource.push(bidirectionalAStarGraph[currNodeSource.x][up]);
}
if (
down < grid_height &&
!bidirectionalAStarGraph[currNodeSource.x][down].blocked
) {
validNeighborsSource.push(
bidirectionalAStarGraph[currNodeSource.x][down]
);
}
left = currNodeDest.x - 1;
right = currNodeDest.x + 1;
up = currNodeDest.y - 1;
down = currNodeDest.y + 1;
// consider all of the current node's (from dest) valid neighbors
if (left >= 0 && !bidirectionalAStarGraph[left][currNodeDest.y].blocked) {
validNeighborsDest.push(bidirectionalAStarGraph[left][currNodeDest.y]);
}
if (
right < grid_width &&
!bidirectionalAStarGraph[right][currNodeDest.y].blocked
) {
validNeighborsDest.push(bidirectionalAStarGraph[right][currNodeDest.y]);
}
if (up >= 0 && !bidirectionalAStarGraph[currNodeDest.x][up].blocked) {
validNeighborsDest.push(bidirectionalAStarGraph[currNodeDest.x][up]);
}
if (
down < grid_height &&
!bidirectionalAStarGraph[currNodeDest.x][down].blocked
) {
validNeighborsDest.push(bidirectionalAStarGraph[currNodeDest.x][down]);
}
// UPDATE NEIGHBORS FROM SOURCE
for (let i = 0; i < validNeighborsSource.length; i++) {
let neighbor = validNeighborsSource[i];
if (neighbor.setSource === "closed") continue;
let cost = 0;
if (currNodeSource.weighted === true || neighbor.weighted === true)
cost = currNodeSource.gSrc + 10;
else cost = currNodeSource.gSrc + 1;
if (neighbor.setSource === "open" && cost < neighbor.gSrc) {
neighbor.setSource = "neither";
neighbor.gSrc = cost;
neighbor.fSrc = neighbor.gSrc + neighbor.hSrc;
openSource.remove(neighbor);
}
if (neighbor.setSource === "neither") {
openSource.push(neighbor);
neighbor.setSource = "open";
neighbor.gSrc = cost;
neighbor.hSrc = calculateHeuristic(neighbor, finishNode);
neighbor.fSrc = neighbor.gSrc + neighbor.hSrc;
neighbor.predecessorSource = currNodeSource;
}
}
lastNodeSource = currNodeSource;
// UPDATE NEIGHBORS FROM DEST
for (let i = 0; i < validNeighborsDest.length; i++) {
let neighbor = validNeighborsDest[i];
if (neighbor.setDest === "closed") continue;
let cost = 0;
if (currNodeDest.weighted === true || neighbor.weighted === true)
cost = currNodeDest.gDest + 10;
else cost = currNodeDest.gDest + 1;
if (neighbor.setDest === "open" && cost < neighbor.gDest) {
neighbor.setDest = "neither";
neighbor.gDest = cost;
neighbor.fDest = neighbor.gDest + neighbor.hDest;
openDest.remove(neighbor);
}
if (neighbor.setDest === "neither") {
openDest.push(neighbor);
neighbor.setDest = "open";
neighbor.gDest = cost;
neighbor.hDest = calculateHeuristic(neighbor, startNode);
neighbor.fDest = neighbor.gDest + neighbor.hDest;
neighbor.predecessorDest = currNodeDest;
}
}
lastNodeDest = currNodeDest;
}
Without any code I can't identify any specific bugs in your algorithm but the thing about Bidirectional A* is that it is only as good as your A*.
A* is flexible in that it is capable of acting just like a dumb breadth first search and just like a dumb depth first search - usually it's somewhere in the middle, and that "middle" is defined by the quality of your heuristic.
Adding a second A* on the other side is a good way to "speed up" an A* heuristic that leans towards breadth, but, it won't "fix" a heuristic that leans towards depth.
If you want a guarantee that your Bidirectional A* search will always find the shortest possible path, then your heuristic needs to lean towards breadth. (Usually this is done by estimating the heuristic - the imagined cost of a node to explore as the Manhattan distance to the target plus the distance traveled to that node. Then sort the nodes and toss nodes more than 1.5x the lowest node -- 1.5 being a variable you can play with, too high and you'll do a traditional breadth first and too low and you might toss the actual lowest path, if it's a complicated one.)
Sorry for the vagueness, some code snippets might help give more direction!
I have to do a binary search for a names directory in typescript, if the name is in the array the code works properly but if the name it's not in the array it became in an infinite loop.
Can somebody help me? Please!
This is the code:
var initialArray = ['Diego', 'David','Mauricio']
var sortedArray = initialArray.sort()
function search(find) {
var leftLimit = initialArray[0]
var leftLimitIndex = initialArray.indexOf(leftLimit)
var rightLimit = initialArray[initialArray.length - 1]
var rightLimitIndex = initialArray.indexOf(rightLimit)
var pivotIndex = 0
var index = -1
while (compare(leftLimit, rightLimit)) {
pivotIndex = Math.floor((leftLimitIndex + rightLimitIndex)/2)
console.log(pivotIndex)
if (initialArray[pivotIndex] == find) {
index = pivotIndex
break
}
else {
if (compare(initialArray[pivotIndex], find)) {
leftLimitIndex = pivotIndex + 1
}
else {
rightLimitIndex = pivotIndex - 1
}
}
console.log(initialArray[pivotIndex])
}
console.log("Result: "+initialArray[index])
return initialArray[index]
}
function compare(leftLimit, rightLimit) {
var r = (leftLimit < rightLimit ? -1 : 1)
if (r < 0) {
return true
}
else {
return false
}
}
Youare not taking in account the limits index which change and at the end you dont know if you have not more options to search or if the findTerm doesnt exits at all.
You only need to change the follow line
while (compare(leftLimit, rightLimit) && leftLimitIndex <= rightLimitIndex) {
Regards,
i was trying to implement the A* algorithm and followed the wikipedia pseudo code to make this.
when i pass a predefined object pixel to the a funtion getG() it says that the object is null. I'm sorry if i am not pointing to a specific problem but i am not even sure how to really specify the problem by name. i have tried commenting out the code to increase readability.
git repository link of the whole project - https://github.com/NirobNabil/WhirlWind
(things are a little messy here because i didn't use github at first and i uploaded it just a little ago for posting the problem)
[ i'm actually making this to use a* to find path for my bot which is powered by arduino. thats why i'm using involt. ]
here goes the code,
$(function() {
// define the height, width and bot size in centemeter
total_width = 200;
total_height = 200;
bot_size = 20;
total_box = (total_height / bot_size) * (total_width / bot_size);
box_in_x = total_width / bot_size;
box_in_y = total_height / bot_size;
//populating the pixels array
populate(total_width / bot_size, total_height / bot_size, "UND");
pathfind(pixels, pixels[13], pixels[pixels.length - 1]);
})
var pixels = []; //an array to hold all the objects(the grid)
var obstacles = []; //array to hold the obstacles
function pixel(X, Y, obs) {
this.X_co_ordinate = X;
this.Y_co_ordinate = Y;
this.state = obs; //availale states OPN, UND, OBS, DIS, NULL
this.g = 0;
this.h = 0;
this.f = 0;
this.last = null;
} //every block in the grid is a pixel
//01719372596
function populate(height, width, obs_val = "UND") {
pixels[0] = new pixel(0, 10, obs_val);
for (h = height, i = 0; h >= 0; h--) {
for (w = 0; w < width; w++, i++) {
var temp_obs = new pixel(w, h, obs_val);
temp_obs.last = pixels[0];
pixels[i] = temp_obs; //saving temp_pixel object to pixels array
}
}
} //populating the grid AKA pixels with pixel objects or blocks
// this funtion is where the problem shows up
function getG(current, start) {
let g = 1;
while (current != start && current.last != start && current) {
current = current.last;
g++;
}
return g;
} //get the g val(cost to come to this pixel from the start) of the current pixel
function getH(current, end) {
let I = Math.abs(current.X_co_ordinate - end.X_co_ordinate) + Math.abs(current.Y_co_ordinate - end.Y_co_ordinate);
return I;
} //get the h val(heuristic) of the current pixel
function getF(start, current, end) {
let G = getG(current, start);
let H = getH(current, end);
return G + H;
} //get the f val(total) of the current pixel
function lowFinArray(arr, start, end) {
// here arr is the grid/pixel
let current_low = arr[0];
for (let i = 0; i < arr.length; i++) {
let getF1 = getF(start, current_low, end);
let getF2 = getF(start, arr[i], end);
if (getF1 < getF2) {
current_low = arr[i];
}
}
console.log("current low");
console.log(current_low);
return current_low;
}
function getneighbours(grid, current) {
let neightbours = [];
neightbours.push(grid[getIndex(current.X_co_ordinate - 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate + 1, current.Y_co_ordinate)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate - 1)]);
neightbours.push(grid[getIndex(current.X_co_ordinate, current.Y_co_ordinate + 1)]);
/*
for(i=0; i<neightbours.length; i++){
neightbours[i].last = current;
}*/
console.log("neightbours");
console.log(neightbours);
return neightbours;
} //get the neighbour pixels of the current pixel
//main algo
function pathfind(grid, start, end) {
let closedSet = [];
let openSet = [];
openSet.push(start);
let current = start;
//trying to debug
console.log("low F in arr");
console.log(lowFinArray(grid, start, end));
console.log(start);
console.log(current);
console.log(end);
console.log(grid);
let x = 0;
while (openSet.length > 0) {
//trying to debug
console.log("executed " + (x++));
console.log("openset");
console.log(openSet);
current = lowFinArray(grid, start, end); //assigning the pixel with lowest f val to current
console.log("current");
console.log(current);
if (current == end) {
console.log(getPath(current));
}
let neighbours = getneighbours(grid, current);
for (let i = 0; i < neighbours.length; i++) {
let neighbour = neighbours[i];
if (closedSet.includes(neighbour)) {
continue;
}
if (!openSet.includes(neighbours)) {
openSet.push(neighbours);
}
//console.log(current);
let getg = getG(current, start);
let geth = getH(current, end);
//console.log(getg);
let tGscore = getg + geth; //here getH is being used as a distance funtion
if (tGscore >= getg) {
continue;
}
neighbour.last = current;
neighbour.g = tGscore;
neighbour.f = getF(neighbour);
}
if (x > 10) {
return 0;
}; //the loop was running forever so i tried this to stop the loop after 10 iterations
}
}
function getPath(current) {
let path = [current];
while (current.last != null) {
path.push(current.last);
}
return path;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
and here is what the console says,
Uncaught TypeError: Cannot read property 'last' of null
at getG (app.js:226)
at getF (app.js:241)
at lowFinArray (app.js:249)
at pathfind (app.js:292)
at HTMLDocument.<anonymous> (app.js:92)
at mightThrow (jquery-3.1.1.js:3570)
at process (jquery-3.1.1.js:3638)
You're doing your checks in the wrong order:
while (current != start && current.last != start && current) {
There's no point in using && current after you've already used current.last.
Perhaps changing the order would solve the problem. It will at least get rid of your current error:
while (current && current != start && current.last != start) {
Regarding the title of this question:
In javascript, after i pass a non null object to a funtion it says the object is null
It may very well be non-null 100% of the time you pass it into the function, but you are repeatedly overwriting its parameter within the function, so all bets are off.
I have a C# script like below:
public List<MazePath> BreakIntoConnectedPaths()
{
List<MazeVertex> remainVertices = new List<MazeVertex>(vertices);
List<MazePath> paths = new List<MazePath>();
while (remainVertices.Count > 0)
{
MazePath path = new MazePath();
path.entrancePosition = entrancePosition;
path.exitPosition = exitPosition;
VisitCell(path, remainVertices.First(), null, remainVertices);
paths.Add(path);
//Store the coordinate for entrance and exit
}
return paths;
}
void VisitCell(MazePath path, MazeVertex ver, MazeVertex parent, List<MazeVertex> remainVertices)
{
remainVertices.Remove(ver);
path.Add(ver);
for (int i = 0; i < ver.connectVertices.Count; i++)
{
MazeVertex ver2 = ver.connectVertices[i];
if (ver2 != parent)
{
VisitCell(path, ver2, ver, remainVertices);
}
}
}
I want to convert it to javascript as below
BreakIntoConnectedPaths = function() {
var remainVertices = _.cloneDeep(this.vertices);
var paths = [];
while (remainVertices.length > 0) {
var path = new Path();
path.entrancePos = this.entrancePos;
path.exitPos = this.exitPos;
this.VisitCell(path, remainVertices[0], null, remainVertices);
paths.push(path);
// Store the coordinate for entrance and exit
}
return paths;
}
VisitCell = function(path, vertex, parentVertex, remainVertices) {
_.remove(remainVertices, function(v) {
return v.x === vertex.x && v.z === vertex.z;
});
path.Add(vertex);
for (var i = 0; i < vertex.connectVertices.length; i++) {
var connectedVertex = vertex.connectVertices[i];
// if (parentVertex && (connectedVertex.x !== parentVertex.x || connectedVertex.z !== parentVertex.z)) {
if(parentVertex && _.isEqual(connectedVertex, parentVertex)) {
VisitCell(path, connectedVertex, vertex, remainVertices);
}
}
}
The _ symbol here is lodash sign.
After I convert to javascript code, the behavior of these functions is difference with the C# one. With the same vertices data, the paths array had returned with difference size.
Thanks you for reading and pls help me if you see my mistake here.
In the C# version, your VisitCell function has a condition that says if(ver2 != parent), but in the JS version you check that they are equal instead of not equal.
Also, that condition would never pass any way because in your first call to that function you pass in null for the parent, but in that condition you check that the parent is "truthy".
Lodash's isEqual can handle null values, so I'm not sure why you're checking if the parent is truthy there. Perhaps you meant to do this?
if(!_.isEqual(connectedVertex, parentVertex)) {
There are several ways to improve your JavaScript code. When transpiling code, it is better to not copy/paste and fix, but to rewrite using the target language instead.
I would prefer to have this written, for example:
var vertices;
var entrancePos;
var exitPos;
function Path(entrancePos, exitPos){
this.entrancePos = entrancePos;
this.exitPos = exitPos;
this.Add = function() {
// your Add() code here
}
}
function breakIntoConnectedPaths() {
var remainingVertices = _.cloneDeep(vertices);
var paths = [];
while (remainVertices.length) {
var path = new Path(entrancePos, exitPos);
visitCell(path, remainingVertices.shift());
// Store the coordinate for entrance and exit
paths.push(path);
}
return paths;
}
function visitCell(path, vertex, parentVertex) {
path.Add(vertex);
for (var i = 0; i < vertex.connectVertices.length; i++) {
var connectedVertex = vertex.connectVertices[i];
if(_.isEqual(connectedVertex, parentVertex)) {
visitCell(path, connectedVertex, vertex);
}
}
}
Keep in mind that the variables vertices, entrancePos, exitPos and Path are not available to me on your C# code, so I only declare them on JavaScript. Implement them as you may.
Does that fix it, by the way?
Here's what I'm trying to achieve. Draw a circle(first circle) on the screen on a mouse click. Then draw successive circles on successive mouse clicks and connect each to the first circle.
I've managed to get till here.
Now the task is if any of the circles have the same y-coordinate as the first one, the connection is a straight line, else it should be a s-curve/inverted s-curve depending on whether the next circle is above or below the first one based on its y-axis.
It may be assumed that all successive circle will be on the right of the first circle.
Here's my code
var app = angular.module('plunker', []);
app.controller('MainController', function($scope) {
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var radius=10;
var lastX,lastY;
ctx.fillStyle='red';
$("#canvas").mousedown(function(e){handleMouseDown(e);});
function drawCircle(cx,cy){
if(lastX){
ctx.globalCompositeOperation='destination-over';
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(cx,cy);
ctx.stroke();
ctx.globalCompositeOperation='source-over';
}else{
lastX=cx;
lastY=cy;
}
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
drawCircle(mx,my);
}
});
Here's a link to the plunk that will demonstrate the behavior
http://plnkr.co/edit/rYVLgB14IutNh1F4MN6T?p=preview
Any help appreciated.
I don't know exactly which kind of s-curve are you interested in. As I understand it will be always only two points to connect, the first point and the rest, and you are looking for some sort of quadratic curve to do so. Under this situation you can build a s-curve by joining two ctx.quadraticCurveTo calls.
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.quadraticCurveTo(
(lastX+cx)/2, lastY,
(lastX+cx)/2, (lastY+cy)/2
);
ctx.quadraticCurveTo(
(lastX+cx)/2, cy,
cx, cy
);
ctx.lineWidth = 3;
http://plnkr.co/edit/t10cMPcUtX5ifkWi2LBF?p=preview
To make each of your connectors avoid existing circles, you must use a pathfinding algorithm (A* for example).
Pathfinding algorithms will give you a set of points from Circle1 to Circle2 that avoid all other circles.
Then you can use that set of points to build a connector between those circles using a Spline. See this very good answer by Stackoverflow's Ken Fyrstenberg on how to draw a spline. Make sure you keep the tension on the spline tight (closer to zero) so that your spline connector doesn't stray too far from the unobstructed path:
how to draw smooth curve through N points using javascript HTML5 canvas?
This is a nice script implementing the A* algorithm by Brian Grinstead:
https://github.com/bgrins/javascript-astar/
And here's a Demo of Brian Grinstead's A* script:
http://www.briangrinstead.com/files/astar/
To avoid a link-only answer, I'm attaching Brian's script from GitHub below...
But seriously...if GitHub disappears many of us subscribers are in trouble!
// javascript-astar 0.4.0
// http://github.com/bgrins/javascript-astar
// Freely distributable under the MIT License.
// Implements the astar search algorithm in javascript using a Binary Heap.
// Includes Binary Heap (with modifications) from Marijn Haverbeke.
// http://eloquentjavascript.net/appendix2.html
(function(definition) {
/* global module, define */
if(typeof module === 'object' && typeof module.exports === 'object') {
module.exports = definition();
} else if(typeof define === 'function' && define.amd) {
define([], definition);
} else {
var exports = definition();
window.astar = exports.astar;
window.Graph = exports.Graph;
}
})(function() {
function pathTo(node){
var curr = node,
path = [];
while(curr.parent) {
path.push(curr);
curr = curr.parent;
}
return path.reverse();
}
function getHeap() {
return new BinaryHeap(function(node) {
return node.f;
});
}
var astar = {
/**
* Perform an A* Search on a graph given a start and end node.
* #param {Graph} graph
* #param {GridNode} start
* #param {GridNode} end
* #param {Object} [options]
* #param {bool} [options.closest] Specifies whether to return the
path to the closest node if the target is unreachable.
* #param {Function} [options.heuristic] Heuristic function (see
* astar.heuristics).
*/
search: function(graph, start, end, options) {
graph.cleanDirty();
options = options || {};
var heuristic = options.heuristic || astar.heuristics.manhattan,
closest = options.closest || false;
var openHeap = getHeap(),
closestNode = start; // set the start node to be the closest if required
start.h = heuristic(start, end);
openHeap.push(start);
while(openHeap.size() > 0) {
// Grab the lowest f(x) to process next. Heap keeps this sorted for us.
var currentNode = openHeap.pop();
// End case -- result has been found, return the traced path.
if(currentNode === end) {
return pathTo(currentNode);
}
// Normal case -- move currentNode from open to closed, process each of its neighbors.
currentNode.closed = true;
// Find all neighbors for the current node.
var neighbors = graph.neighbors(currentNode);
for (var i = 0, il = neighbors.length; i < il; ++i) {
var neighbor = neighbors[i];
if (neighbor.closed || neighbor.isWall()) {
// Not a valid node to process, skip to next neighbor.
continue;
}
// The g score is the shortest distance from start to current node.
// We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet.
var gScore = currentNode.g + neighbor.getCost(currentNode),
beenVisited = neighbor.visited;
if (!beenVisited || gScore < neighbor.g) {
// Found an optimal (so far) path to this node. Take score for node to see how good it is.
neighbor.visited = true;
neighbor.parent = currentNode;
neighbor.h = neighbor.h || heuristic(neighbor, end);
neighbor.g = gScore;
neighbor.f = neighbor.g + neighbor.h;
graph.markDirty(neighbor);
if (closest) {
// If the neighbour is closer than the current closestNode or if it's equally close but has
// a cheaper path than the current closest node then it becomes the closest node
if (neighbor.h < closestNode.h || (neighbor.h === closestNode.h && neighbor.g < closestNode.g)) {
closestNode = neighbor;
}
}
if (!beenVisited) {
// Pushing to heap will put it in proper place based on the 'f' value.
openHeap.push(neighbor);
}
else {
// Already seen the node, but since it has been rescored we need to reorder it in the heap
openHeap.rescoreElement(neighbor);
}
}
}
}
if (closest) {
return pathTo(closestNode);
}
// No result was found - empty array signifies failure to find path.
return [];
},
// See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
heuristics: {
manhattan: function(pos0, pos1) {
var d1 = Math.abs(pos1.x - pos0.x);
var d2 = Math.abs(pos1.y - pos0.y);
return d1 + d2;
},
diagonal: function(pos0, pos1) {
var D = 1;
var D2 = Math.sqrt(2);
var d1 = Math.abs(pos1.x - pos0.x);
var d2 = Math.abs(pos1.y - pos0.y);
return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2));
}
},
cleanNode:function(node){
node.f = 0;
node.g = 0;
node.h = 0;
node.visited = false;
node.closed = false;
node.parent = null;
}
};
/**
* A graph memory structure
* #param {Array} gridIn 2D array of input weights
* #param {Object} [options]
* #param {bool} [options.diagonal] Specifies whether diagonal moves are allowed
*/
function Graph(gridIn, options) {
options = options || {};
this.nodes = [];
this.diagonal = !!options.diagonal;
this.grid = [];
for (var x = 0; x < gridIn.length; x++) {
this.grid[x] = [];
for (var y = 0, row = gridIn[x]; y < row.length; y++) {
var node = new GridNode(x, y, row[y]);
this.grid[x][y] = node;
this.nodes.push(node);
}
}
this.init();
}
Graph.prototype.init = function() {
this.dirtyNodes = [];
for (var i = 0; i < this.nodes.length; i++) {
astar.cleanNode(this.nodes[i]);
}
};
Graph.prototype.cleanDirty = function() {
for (var i = 0; i < this.dirtyNodes.length; i++) {
astar.cleanNode(this.dirtyNodes[i]);
}
this.dirtyNodes = [];
};
Graph.prototype.markDirty = function(node) {
this.dirtyNodes.push(node);
};
Graph.prototype.neighbors = function(node) {
var ret = [],
x = node.x,
y = node.y,
grid = this.grid;
// West
if(grid[x-1] && grid[x-1][y]) {
ret.push(grid[x-1][y]);
}
// East
if(grid[x+1] && grid[x+1][y]) {
ret.push(grid[x+1][y]);
}
// South
if(grid[x] && grid[x][y-1]) {
ret.push(grid[x][y-1]);
}
// North
if(grid[x] && grid[x][y+1]) {
ret.push(grid[x][y+1]);
}
if (this.diagonal) {
// Southwest
if(grid[x-1] && grid[x-1][y-1]) {
ret.push(grid[x-1][y-1]);
}
// Southeast
if(grid[x+1] && grid[x+1][y-1]) {
ret.push(grid[x+1][y-1]);
}
// Northwest
if(grid[x-1] && grid[x-1][y+1]) {
ret.push(grid[x-1][y+1]);
}
// Northeast
if(grid[x+1] && grid[x+1][y+1]) {
ret.push(grid[x+1][y+1]);
}
}
return ret;
};
Graph.prototype.toString = function() {
var graphString = [],
nodes = this.grid, // when using grid
rowDebug, row, y, l;
for (var x = 0, len = nodes.length; x < len; x++) {
rowDebug = [];
row = nodes[x];
for (y = 0, l = row.length; y < l; y++) {
rowDebug.push(row[y].weight);
}
graphString.push(rowDebug.join(" "));
}
return graphString.join("\n");
};
function GridNode(x, y, weight) {
this.x = x;
this.y = y;
this.weight = weight;
}
GridNode.prototype.toString = function() {
return "[" + this.x + " " + this.y + "]";
};
GridNode.prototype.getCost = function(fromNeighbor) {
// Take diagonal weight into consideration.
if (fromNeighbor && fromNeighbor.x != this.x && fromNeighbor.y != this.y) {
return this.weight * 1.41421;
}
return this.weight;
};
GridNode.prototype.isWall = function() {
return this.weight === 0;
};
function BinaryHeap(scoreFunction){
this.content = [];
this.scoreFunction = scoreFunction;
}
BinaryHeap.prototype = {
push: function(element) {
// Add the new element to the end of the array.
this.content.push(element);
// Allow it to sink down.
this.sinkDown(this.content.length - 1);
},
pop: function() {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
var end = this.content.pop();
// If there are any elements left, put the end element at the
// start, and let it bubble up.
if (this.content.length > 0) {
this.content[0] = end;
this.bubbleUp(0);
}
return result;
},
remove: function(node) {
var i = this.content.indexOf(node);
// When it is found, the process seen in 'pop' is repeated
// to fill up the hole.
var end = this.content.pop();
if (i !== this.content.length - 1) {
this.content[i] = end;
if (this.scoreFunction(end) < this.scoreFunction(node)) {
this.sinkDown(i);
}
else {
this.bubbleUp(i);
}
}
},
size: function() {
return this.content.length;
},
rescoreElement: function(node) {
this.sinkDown(this.content.indexOf(node));
},
sinkDown: function(n) {
// Fetch the element that has to be sunk.
var element = this.content[n];
// When at 0, an element can not sink any further.
while (n > 0) {
// Compute the parent element's index, and fetch it.
var parentN = ((n + 1) >> 1) - 1,
parent = this.content[parentN];
// Swap the elements if the parent is greater.
if (this.scoreFunction(element) < this.scoreFunction(parent)) {
this.content[parentN] = element;
this.content[n] = parent;
// Update 'n' to continue at the new position.
n = parentN;
}
// Found a parent that is less, no need to sink any further.
else {
break;
}
}
},
bubbleUp: function(n) {
// Look up the target element and its score.
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);
while(true) {
// Compute the indices of the child elements.
var child2N = (n + 1) << 1,
child1N = child2N - 1;
// This is used to store the new position of the element, if any.
var swap = null,
child1Score;
// If the first child exists (is inside the array)...
if (child1N < length) {
// Look it up and compute its score.
var child1 = this.content[child1N];
child1Score = this.scoreFunction(child1);
// If the score is less than our element's, we need to swap.
if (child1Score < elemScore){
swap = child1N;
}
}
// Do the same checks for the other child.
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score < (swap === null ? elemScore : child1Score)) {
swap = child2N;
}
}
// If the element needs to be moved, swap it, and continue.
if (swap !== null) {
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
// Otherwise, we are done.
else {
break;
}
}
}
};
return {
astar: astar,
Graph: Graph
};
});