flauwekeul / honeycomb

Create hex grids easily, in node or the browser.

Home Page:https://abbekeultjes.nl/honeycomb

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Pathfinding

Bejasc opened this issue · comments

Hi there,

I've been using the next version of this library, and have finally wrapped my head around representing my cells with it correctly.
The context I am using this in is for, loosely, a text based adventure game. No rendering is needed.

I have extended Hex to create my own, which includes properties to be used to calculate a cost to move through that tile.

The library seems to be missing (and mentioned as not yet completed or spoken as 'coming soon') the ability to find a path from one cell to another (and include the ability for cells to have different costs/weights).

I notice it has been some time since these comments were made or the library has been updated.

Is there any update on when or if this functionality might be coming?
If not, can anyone recommend any alternatives or similar solutions they have found?

Thanks!

You can use A* (A star) algorithm with adding two movement conditions (to have six possible directions) quite easily with honeycomb. I did this for a client some years ago, easily scripting blocks and weights (don't ask me for code, A* part was from well-known site, drawing SVG done with svg.js):

https://www.dropbox.com/s/3bri2lr2ximdgg2/pathfinding-javascript-console-cIds.png?dl=0

Thanks for your input @mattsrinc - but it is not helpful, especially as you have not actually shared any approach which you have taken. I believe I have already come across the sites in you mention, but that is not quite relevant to my question.

I've asked when (of if) this functionality is still planned as part of the Honeycomb API, as has been alluded to in a few places that I've linked above.

First of all: I'm sorry for replying this late. For some reason I didn't get an email someone opened an issue. I'll investigate what happened there.

It seems the A* algorithm is what you need. I've gotten less interested in continuing developing Honeycomb, but I can still try to help you. With the next version you should be able to achieve what you want. Can you share some code so I can do some suggestions?

No problems, @flauwekeul!

I've completed an attempt at it, using the articles published by redblobgames. (C# was easiest for me to apply, as I don't have a strong working knowledge of python of c++, being the other examples used.

Note that I've got a few different layers here, including (my attempt) at a custom tile using your package.

Honestly, my implementation is flawed. There are some cases where the path calculated by my attempt at a*, is actually longer than the path calculated in a straight line distance - and I'm not too sure why. (The straight line path is calculated using another implementation from redblobgames).

I struggled with this for a few days, it seems to have great results sometimes.
Usually, I compare the straight line distance and the astar distance, and pick the shorter of the two.

The code in this snippet is the result of several hours of frustration, it's working now to a point where I'm happy with it (for my purposes), but even the lines between my implementation, and what your package provides, are very, very blurred to me.

Regardless, hopefully the snippetprovides you with some insight.

I look forward to seeing what you come up with!

I started working on the project again and implementing the A* algorithm might be a good way to dogfood the new API. I'll let you know how it goes.

@Bejasc I can imagine I'm too late, but for what it's worth: I just pushed an example that showcases how to implement A* path finding using Honeycomb. The example isn't finished (backtracking isn't implemented yet) and it may contain bugs. Currently, the docs are (very) outdated, but hopefully it's clear from the example what's happening.

To run the example (assuming you have the repository cloned):

cd examples
npm install
npm start

Thanks for looking back on this @flauwekeul!

I did move away from this hobby project of mine - but will be very interested to check it out again, your developments may even inspire me to continue!

I'll be most interested to see if the solution accounts for "tile costs" so that one hex may be more expensive to move through than another.

Will check it out soon - thank you!

@Bejasc I am still subscribed to this great code repository. If it might help you, here's my modified version of astar.js (to search in 6 instead of 4 directions) attached.

astar-hex.zip

Then in my custom code - that I cannot share more than as follows, client work - I've used a code like this:

function createGraph() {
// creates array of arrays for A* graph search from map 
	var graphConv = createArray(gridHeight*2 + gridWidth, gridWidth*2 -1);
	// might work only for parallelogram!
	for (i=0; i<(gridHeight*2 + gridWidth); i++) {
		for (j=0; j<(gridWidth*2 -1); j++) {
			graphConv[i][j] = 0; // bug weight for non existing nodes (not on visible map)
		}
	}
//		createArray(gridHeight*2 + gridWidth, gridWidth - 1);
	for (i=0; i<gridHeight; i++) {
		for (j=0; j<gridWidth; j++) {
			// establish grid coordinates of this hexagon center for A* algorithm
			var coord = { row: i*2 + 1 + j, col: (gridHeight-i) + j };
//			console.log("coord[" + i + "][" + j + "]: " + coord.col + "," + coord.row);
			for (k=0; k<7; k++) {
				switch (k) {
					case 0: graphConv[coord.row][coord.col] = map[i][j][0];
						break; 
					case 1: graphConv[coord.row-1][coord.col] = map[i][j][6];
						break; 
					case 2: graphConv[coord.row-1][coord.col+1] = map[i][j][1];
						break; 
					case 3: graphConv[coord.row][coord.col+1] = map[i][j][2];
						break; 
					case 4: graphConv[coord.row+1][coord.col] = map[i][j][3];
						break; 
					case 5: graphConv[coord.row+1][coord.col-1] = map[i][j][4];
						break; 
					case 6: graphConv[coord.row][coord.col-1] = map[i][j][5];
						break;
				}
			}
		}
	}
	return graphConv;
}

...

function pathFind() {
// uses astar.js from the web
// changed possible movement to 6 instead of 4 directions
// allows to use the same algoritm including paths

	var graph = new Graph(createGraph());
	// resolve start node on grid
	var startPoints = getPos(startNode);
	var startCoord = { row: Number(startPoints[1])*2 + 1 + Number(startPoints[2]), col: (gridHeight-Number(startPoints[1])) + Number(startPoints[2]) };
	switch (Number(startPoints[3])) {
		case 0: startX = startCoord.row; startY = startCoord.col;
			break; 
		case 1: startX = startCoord.row-1; startY = startCoord.col+1;
			break; 
		case 2: startX = startCoord.row; startY = startCoord.col+1;
			break; 
		case 3: startX = startCoord.row+1; startY = startCoord.col;
			break; 
		case 4: startX = startCoord.row+1; startY = startCoord.col-1;
			break; 
		case 5: startX = startCoord.row; startY = startCoord.col-1;
			break; 
		case 6: startX = startCoord.row-1; startY = startCoord.col;
			break;
	}
//	console.log("startX: " + startX + ", startY: " + startY);
	var start = graph.grid[startX][startY];
	// resolve end node on grid
	var endPoints = getPos(endNode);
	var endCoord = { row: Number(endPoints[1])*2 + 1 + Number(endPoints[2]), col: (gridHeight-Number(endPoints[1])) + Number(endPoints[2]) };
	switch (Number(endPoints[3])) {
		case 0: endX = endCoord.row; endY = endCoord.col;
			break; 
		case 1: endX = endCoord.row-1; endY = endCoord.col+1;
			break; 
		case 2: endX = endCoord.row; endY = endCoord.col+1;
			break;
		case 3: endX = endCoord.row+1; endY = endCoord.col;
			break; 
		case 4: endX = endCoord.row+1; endY = endCoord.col-1;
			break; 
		case 5: endX = endCoord.row; endY = endCoord.col-1;
			break; 
		case 6: endX = endCoord.row-1; endY = endCoord.col;
			break;
	}
//	console.log("endX: " + endX + ", endY: " + endY);
	var end = graph.grid[endX][endY];//4,7;5,2;
	graph.diagonal = false;
	var result = astar.search(graph, start, end);
	// result is an array containing the shortest path
//	console.log("Result (points):");
//	console.log(result);
	// draw a SVG path
	if (result.length > 0) {
		newPath = constructSVGpath(result);
	}
	else { // no path found
		var removePath = SVG.adopt(document.getElementById("oldPath"));
		if (removePath !== null) { removePath.remove(); }
		var removePath = SVG.adopt(document.getElementById("newPath"));
		if (removePath !== null) { removePath.remove(); }
	}
}

In the constructSVGpath(points) - that I cannot give you - the solved path is constructed as SVG path element using the result from the A* pathfinding. The SVG.js library is perfect for that so at the end of constructSVGpath function where I've added nodes with

	coords = getPos(cId);
	var node = SVG.adopt(document.getElementById(cId));
	conPath += " L" + node.attr('cx') + "," + node.attr('cy');

(in a loop) I've just recreated a new solved path with this code:

	oldPath = SVG.adopt(document.getElementById('newPath'));
	if (oldPath) {
		oldPath.id('oldPath');
	}
	newPath = draw.path(conPath).id('newPath');
	newPath.stroke({ color: '#000', width: 3}).fill('none');
	if (oldPath !== null) { oldPath.remove(); }

Hope this will give all enough help to expand for different solutions. I allow the code above to be reused for your purpose.