Isometric topological sort issue - javascript

I've just implemented a topological sort algorithm on my isometric game using this guide: https://mazebert.com/2013/04/18/isometric-depth-sorting/
The issue
Here's a little example (this is just a drawing to illustrate my problem because as we say, a picture is worth a thousand words), what I'm expecting is in left and the result of the topological sorting algorithm is in right
So in the right image, the problem is that the box is drawn BEFORE the character and I'm expecting it to be drawn AFTER like in the left image.
Code of the topological sorting algorithm (Typescript)
private TopologicalSort2() {
// https://mazebert.com/2013/04/18/isometric-depth-sorting/
for(var i = 0; i < this.Stage.children.length; i++) {
var a = this.Stage.children[i];
var behindIndex = 0;
for(var j = 0; j < this.Stage.children.length; j++) {
if(i == j) {
continue;
}
var b = this.Stage.children[j];
if(!a.isoSpritesBehind) {
a.isoSpritesBehind = [];
}
if(!b.isoSpritesBehind) {
b.isoSpritesBehind = [];
}
if(b.posX < a.posX + a.sizeX && b.posY < a.posY + a.sizeY && b.posZ < a.posZ + a.sizeZ) {
a.isoSpritesBehind[behindIndex++] = b;
}
}
a.isoVisitedFlag = 0;
}
var _sortDepth = 0;
for(var i = 0; i < this.Stage.children.length; ++i) {
visitNode(this.Stage.children[i]);
}
function visitNode(n: PIXI.DisplayObject) {
if(n.isoVisitedFlag == 0) {
n.isoVisitedFlag = 1;
if(!n.isoSpritesBehind) {
return;
}
for(var i = 0; i < n.isoSpritesBehind.length; i++) {
if(n.isoSpritesBehind[i] == null) {
break;
} else {
visitNode(n.isoSpritesBehind[i]);
n.isoSpritesBehind[i] = null;
}
}
n.isoDepth = _sortDepth++;
}
}
this.Stage.children.sort((a, b) => {
if(a.isoDepth - b.isoDepth != 0) {
return a.isoDepth - b.isoDepth;
}
return 0;
});
}
Informations
Player:
posX: [the x coordinate of the player]
posY: [the y coordinate of the player]
posZ: 0
sizeX: 1
sizeY: 1
sizeZ: 1
Box:
posX: [the x coordinate of the box]
posY: [the y coordinate of the box]
posZ: 0
sizeX: 3
sizeY: 1
sizeZ: 1
X and Y axis
Do you have any idea of the source of this problem? and maybe how to solve it?

The way to determine whether one object is before the other requires a bit more linear algebra.
First of all, I would suggest to translate the coordinates from the "world" coordinates to the "view" 2D coordinates, i.e. to the rows and columns of the display.
Note also that the original Z coordinate does not influence the sort order (imagine that an object would be lifted up along the Z axis: we can find a sort order where this move would not have any impact). So the above-mentioned translation could assume all points are at Z=0.
Let's take this set-up, but depicted from "above", so when looking along the Z axis down to the game floor:
In the picture there are 7 objects, numbered from 0 to 6. The line of view in the game would be from the bottom-left of this picture. The coordinate system in which I would suggest to translate some points is depicted with the red row/col axis.
The white diagonals in each object link the two points that would be translated and used in the algorithm. The assumption is that when one object is in front of another, their diagonal lines will not intersect. If they would, it would mean that objects are overlapping each other in the game world, which would mean they are like gasses, not solids :) I will assume this is not the case.
One object A could be in front of another object B when in the new coordinate system, the left-most column coordinate of B falls between the two column coordinates of A (or vice versa). There might not really be such an overlap when their Z coordinates differ enough, but we can ignore that, because when there is no overlap we can do no harm in specifying a certain order anyway.
Now, when the coordinates indicate an overlap, the coordinates of diagonals (of A and B) must be compared with some linear algebra formula, which will determine which one is in front of the other.
Here is your adapted function that does that:
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
I add a snippet where I completed the class with a minimum of code, enough to make it run and output the final order in terms of object index numbers (as they were originally inserted):
class Game {
constructor() {
this.Stage = { children: [] };
}
addObject(posX, posY, posZ, sizeX, sizeY, sizeZ) {
this.Stage.children.push({posX, posY, posZ, sizeX, sizeY, sizeZ,
id: this.Stage.children.length}); // add a unique id
}
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
toString() { // Just print the ids of the children
return JSON.stringify(this.Stage.children.map( x => x.id ));
}
}
const game = new Game();
game.addObject( 2, 2, 0, 1, 1, 1 );
game.addObject( 1, 3, 0, 3, 1, 1 );
game.addObject( 6, 1, 0, 1, 3, 1 );
game.addObject( 9, 3, 0, 1, 1, 1 );
game.addObject( 5, 3, 0, 1, 3, 1 );
game.addObject( 7, 2, 0, 1, 1, 1 );
game.addObject( 8, 2, 0, 3, 1, 1 );
game.topologicalSort();
console.log(game + '');
The objects in the snippet are the same as in the picture with the same numbers. The output order is [0,1,4,2,5,6,3] which is the valid sequence for drawing the objects.

Related

Which algorithm should be used to find the point?

You need to find some unknown, predetermined point in three-dimensional space, in the smallest number of attempts, using only a function that can return the distance from
any point you pass to it to the desired unknown point.
To solve the problem, first implement a function f that, by taking the coordinates of any point s(x, y, z), return the distance between that point and a conditionally unknown, randomly generated point
point you arbitrarily generate r(x, y, z), where x, y, z can be integers between
0 и 100.
For example, for an arbitrarily generated point r(0, 0, 10) and a point passed to the function
s(0, 0, 0), the result of the function would be as follows:
f(s) = 10 // the distance between s(0, 0, 0) and r(0, 0, 10) is 10
Next, implement the algorithm itself for the assignment. The algorithm should find the coordinates of
of an arbitrarily generated point with the least number of calls to the function f.
I have a randomizer instead of an algorithm, that's all I got. Help.
const pointToFound = {
x: 12,
y: 9,
z: 76,
};
let attemts = 0;
let isXFound = false;
let isYFound = false;
let isZFound = false;
const pointHistory = [];
const getRandomPoint = () => {
return {
x: isXFound ? isXFound : Math.floor(Math.random() * 101),
y: isYFound ? isYFound : Math.floor(Math.random() * 101),
z: isZFound ? isZFound : Math.floor(Math.random() * 101),
};
};
const getDifference = (point, pointToCompare) => {
return {
x:
Math.max(point.x, pointToCompare.x) - Math.min(point.x, pointToCompare.x),
y:
Math.max(point.y, pointToCompare.y) - Math.min(point.y, pointToCompare.y),
z:
Math.max(point.z, pointToCompare.z) - Math.min(point.z, pointToCompare.z),
};
};
const condition = !isXFound && !isYFound && !isZFound;
while (condition) {
const point = getRandomPoint();
const difference = getDifference(point, pointToFound);
pointHistory.push(point);
attemts += 1;
if (isXFound && isYFound && isZFound) {
console.log("Total attempts: ", attemts);
console.log(point);
break;
}
if (difference.x === 0 && !isXFound) {
isXFound = point.x;
}
if (difference.y === 0 && !isYFound) {
isYFound = point.y;
}
if (difference.z === 0 && !isZFound) {
isZFound = point.z;
}
}
console.log(pointHistory);
I have a randomizer instead of an algorithm, that's all I got. Help.
This can be done with at most 3 guesses and often with 2 guesses:
Let the first guess be [0, 0, 0], and ask for the distance
Find in the 100x100x100 cube all points that have that distance to [0, 0, 0]. There might be around 100-200 points that have that distance: consider all of these candidates.
Take the first candidate as the second guess and ask for the distance
Find among the other candidates the ones that have exactly that distance to the first candidate. Often there will be only one point that satisfies this condition. In that case we can return that candidate and only 2 guesses were necessary.
Otherwise (when there is more than one candidate remaining) repeat the previous step which will now certainly lead to a single point.
Here is an implementation that provides a blackbox function which chooses the secret point in a local variable, and which returns two functions: f for the caller to submit a guess, and report for the caller to verify the result of the algorithm and report on the number of guesses. This is not part of the algorithm itself, which is provided in the findPoint function.
const rnd = () => Math.floor(Math.random() * 101);
const distance = (a, b) =>
a.reduce((acc, x, i) => acc + (x - b[i]) ** 2, 0) ** 0.5;
function findPoint(f) {
// First guess is always the zero-point
let guess = [0, 0, 0];
let dist = f(guess);
if (dist === 0) return guess; // Extremely lucky!
// Find the points in the cube that have this distance to [0,0,0]
let candidates = [];
const limit = Math.min(100, Math.round(dist));
for (let x = 0; x <= limit; x++) {
const p = [x, limit, 0];
// Follow circle in X=x plane
while (p[1] >= 0 && p[2] <= limit) {
const d = distance(p, guess);
const diff = d - dist;
if (Math.abs(diff) < 1e-7) candidates.push([...p]);
if (diff >= 0) p[1]--;
else p[2]++;
}
}
// As long there are multiple candidates, continue with a guess
while (candidates.length > 1) {
const candidates2 = [];
// These guesses are taking the first candidate as guess
guess = candidates[0];
dist = f(guess);
if (dist === 0) return guess; // lucky!
for (const p of candidates) {
let d = distance(p, guess);
let diff = d - dist;
if (Math.abs(diff) < 1e-7) candidates2.push(p);
}
candidates = candidates2;
}
return candidates[0]; // Don't call f as we are sure!
}
function blackbox() {
const secret = [rnd(), rnd(), rnd()];
console.log("Secret", JSON.stringify(secret));
let guessCount = 0;
const f = guess => {
guessCount++;
const dist = distance(secret, guess);
console.log("Submitted guess " + JSON.stringify(guess) + " is at distance " + dist);
return dist;
};
const report = (result) => {
console.log("Number of guesses: " + guessCount);
console.log("The provided result is " + (distance(secret, result) ? "not" : "") + "correct");
}
return {f, report};
}
// Example run
const {f, report} = blackbox();
const result = findPoint(f);
console.log("Algorithm says the secret point is: " + JSON.stringify(result));
report(result);
Each run will generate a new secret point. When running this thousands of times it turns out that there is 1/9 probability that the algorithm needs a third guess. In the other 8/9 cases, the algorithm needs two guesses.
One idea is as follows:
You pick an initial random point, and for each dimension, find the exact value. How? For the sake of symmetry, suppose that you desire to find x of the target point. Increase by one the x, and compute the distance of the new point from the target point. If it goes further, it means that you should move in the opposite direction. Hence, you can run a binary search and get the distance to find the exact x of the target point. Otherwise, it means that you are going in the right direction along X-axis. So, do a binary search between all points with the same y and z such that their x values can change from x+1 to 100. A more formal solution comes in the following (just a pseudo-code).
You should also ask about the complexity of this solution. As the dimension of the point is constant (3) and checking these conditions take a constant time, the complexity of number of calling getDifference function is O(log(n)). What is n here? the length of valid range for coordinates (here is 100).
1. p: (x,y,z) <- Pick a random coordinate
2. dist: (dist_x, dist_y, dist_z) <- getDifference(p, TargetPoint)
3. For each dimension, do the following (i can be 0 (x), 1 (y), 2 (3)):
4. if(dist == 0):
5. isFound[i] <- p[i]
6. continue
7. new_i <- p[i] + 1
8. new_point <- p
9. new_point[i] <- new_i
10. new_dist <- getDifference(new_point, pointToFound)
11. if(new_dist == 0):
12. isFound[i] <- new_point[i];
13. continue
14. if(new_dist[i] > dist[i]):
15. isFound[i] <- binary_search for range [0, p[i]-1] to find the exact value of the pointToFound in dimension i
15. continue
16. else:
17. isFound[i] <- binary_search for range [p[i] + 1, 100] to find the exact value of the pointToFound in dimension i
18. continue
Following method will work for coordinates with positive or negative real values as well.
Let's say you are searching for the coordinates of point P. As the first query point, use origin O. Let the distance to the origin O be |PO|. At this point, you know that P is on the surface of sphere
(P.x)^2 + (P.y)^2 + (P.z)^2 = |PO|^2 (1)
As the second query point, use Q = (|PO|, 0, 0). Not likely but if you find the distance |PQ| zero, Q is the point you are looking for. Otherwise, you get another sphere equation, and you know that P is on the surface of this sphere as well:
(P.x - |PO|)^2 + (P.y)^2 + (P.z)^2 = |PQ|^2 (2)
Now, if you subtract (1) from (2), you get
(P.x - |PO|)^2 - (P.x)^2 = |PQ|^2 - |PO|^2 (3)
Since the only unknown in this equation is P.x you can get its value:
P.x = (((-|PQ|^2 + |PO|^2) / |PO|) + |PO|)/2)
Following similar steps, you can get P.y with R = (0, |PO|, 0) and P.z with S = (0, 0, |PO|). So, by using four query points O, Q, R, and S you can get the coordinates of P.

Connect Four Questions

I am trying to learn how to code by looking a solutions for previous codes and finding out what each part is doing to learn how to use them.
I want to become a developer but I do not want to copy and paste everything , I want to actually know what is happening so I can code this myself. I can watch 100 videos but I have questions and I need help, hope someone out there can help me ....
I was wondering if someone could explain to me what is going on in the code below.
* Player 1 and 2 alternate turns. On each turn, a piece is dropped down a
* column until a player gets four-in-a-row (horiz, vert, or diag) or until
* board fills (tie)
*/
class Game {
constructor(p1, p2, height = 6, width = 7) {
this.players = [p1, p2];
this.height = height;
this.width = width;
this.currPlayer = p1;
this.makeBoard();
this.makeHtmlBoard();
this.gameOver = false;
}
/** makeBoard: create in-JS board structure:
* board = array of rows, each row is array of cells (board[y][x])
*/
**Question: So I believe that this is creating a board and making it empty by looping through it?**
makeBoard() {
this.board = [];
for (let y = 0; y < this.height; y++) {
this.board.push(Array.from({ length: this.width }));
}
}
**Question: Is this grabbing the board element from the HTML Page? board.innerHtml is blank, however
didnt we just make a blank a board? Why do we need this?**
makeHtmlBoard() {
const board = document.getElementById('board');
board.innerHTML = '';
// make column tops (clickable area
// for adding a piece to that column)
const top = document.createElement('tr');
top.setAttribute('id', 'column-top');
// store a reference to the handleClick bound function
// so that we can remove the event listener correctly later
this.handleGameClick = this.handleClick.bind(this);
top.addEventListener("click", this.handleGameClick);
for (let x = 0; x < this.width; x++) {
const headCell = document.createElement('td');
headCell.setAttribute('id', x);
top.append(headCell);
}
board.append(top);
// make main part of board
for (let y = 0; y < this.height; y++) {
const row = document.createElement('tr');
for (let x = 0; x < this.width; x++) {
const cell = document.createElement('td');
cell.setAttribute('id', `${y}-${x}`);
row.append(cell);
}
board.append(row);
}
}
/** findSpotForCol: given column x, return top empty y (null if filled) */
**Question: I have no idea what this line is doing**
findSpotForCol(x) {
for (let y = this.height - 1; y >= 0; y--) {
if (!this.board[y][x]) {
return y;
}
}
return null;
}
/** placeInTable: update DOM to
* place piece into HTML board */
**Question: Im not sure what place in table is doing, however I know the second line is creating a DIV on
the table , third line is styling it, however the last three lines i need help with it.**
placeInTable(y, x) {
const piece = document.createElement('div');
piece.classList.add('piece');
piece.style.backgroundColor = this.currPlayer.color;
piece.style.top = -50 * (y + 2);
const spot = document.getElementById(`${y}-${x}`);
spot.append(piece);
}
/** endGame: announce game end */
endGame(msg) {
alert(msg);
const top = document.querySelector("#column-top");
top.removeEventListener("click", this.handleGameClick);
}
/** handleClick: handle click of column top to play piece */
handleClick(evt) {
// get x from ID of clicked cell
const x = +evt.target.id;
The lines below, I have no idea how I could even think of this logic , please help.
****// get next spot in column (if none, ignore click)
const y = this.findSpotForCol(x);
if (y === null) {
return;**
}
// place piece in board and add to HTML table
this.board[y][x] = this.currPlayer;
this.placeInTable(y, x);
// check for tie
if (this.board.every(row => row.every(cell => cell))) {
return this.endGame('Tie!');
}
// check for win
if (this.checkForWin()) {
this.gameOver = true;
return this.endGame(`The ${this.currPlayer.color} player won!`);
}
// switch players
this.currPlayer =
this.currPlayer === this.players[0] ? this.players[1] : this.players[0];**
}
/** checkForWin: check board cell-by-cell for "does a win start here?" */
checkForWin() {
// Check four cells to see if they're all color of current player
// - cells: list of four (y, x) cells
// - returns true if all are legal coordinates & all match currPlayer
const _win = cells =>
cells.every(
([y, x]) =>
y >= 0 &&
y < this.height &&
x >= 0 &&
x < this.width &&
this.board[y][x] === this.currPlayer
);
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
// get "check list" of 4 cells (starting here) for each of the different
// ways to win
const horiz = [[y, x], [y, x + 1], [y, x + 2], [y, x + 3]];
const vert = [[y, x], [y + 1, x], [y + 2, x], [y + 3, x]];
const diagDR = [[y, x], [y + 1, x + 1], [y + 2, x + 2], [y + 3, x + 3]];
const diagDL = [[y, x], [y + 1, x - 1], [y + 2, x - 2], [y + 3, x - 3]];
// find winner (only checking each win-possibility as needed)
if (_win(horiz) || _win(vert) || _win(diagDR) || _win(diagDL)) {
return true;
}
}
}
}
}
class Player {
constructor(color) {
this.color = color;
}
}
document.getElementById('start-game').addEventListener('click', () => {
let p1 = new Player(document.getElementById('p1-color').value);
let p2 = new Player(document.getElementById('p2-color').value);
new Game(p1, p2);
});
A big part of programming is decomposing problems into smaller ones that you can understand. It's likely the syntax and size of this problem is a little advanced for a beginner.
In general - here is an overview:
Correct
There is a 'board' in javascript and a 'board' in HTML. The javascript one is for logic, while the HTML one is for display purposes. That's why you see two boards being created
findSpotForCol is finding the highest unfilled spot in a given column. This would be a good problem for you to really dive into and try to write it yourself. When you drop a token in a connect 4, it goes to highest spot in that column, that isn't currently filled. findSpotforCol is an algorithm to do that.
Each player has their own color. This is putting the checker in the right spot with the correct players color. This is taking our JavaScript logic board, and making it display correctly on the page when a player makes a move
It's okay to feel overwhelmed! ("I have no idea how I could even think of this logic"). Think about everything you do when you play connect 4. First you drop in a checker to a column. It goes to the lowest spot available in that column. Then - if the whole board is full, it's a tie. Then you might check if a player won. (Note - there is a bug in this code you pasted, if a player wins on the last checker, it will say 'Tie', since it checks for a tie first). Then, it's the other players turn.
It took me a long time of doing lots of beginner exercises for me to understand a problem like this, which is getting more advanced, so don't feel bad about having to review the beginner exercises or learn from other platforms/tools/books. If you feel 100% overwhelmed, that's not a good spot to learn from, and you should maybe look for a simpler problem. Tic-Tac-Toe for example would be one step easier than connect four.

find the largest area in this 2d array

i need help in that
You are again the owner of a coworking space like WeWork and your office building is rectangular. You team just created many wall partitions to create mini offices for startups. Your office campus is represented by a 2D array of 1s (floor spaces) and 0s (walls). Each point on this array is a one foot by one foot square. Before renting to tenants, you want to reserve an office for yourself. You wish to fit the largest possible rectangular table in your office, and you will select the office that fits this table. The table sides will always be parallel to the boundaries of the office building. What is the area of the biggest table that can fit in your office?
Functions
biggestTable() has one parameter:
grid: a 2D grid/array of 1s and 0s
Input Format
For some of our templates, we have handled parsing for you. If we do not provide you a parsing function, you will need to parse the input directly. In this problem, our input format is as follows:
The first line is the number of rows in the 2D array
The second line is the number of columns in the 2D array
The rest of the input contains the data to be processed
Here is an example of the raw input:
4
5
11110
11010
11000
00000
Expected Output
Return the area of the biggest area made of 1s in the grid. Assume the grid is surrounded by 0s (walls).
Constraints
Assume that the bounds of the array are the following:
The total amount of elements in the array: width x height <= 10^6
Example
Example biggestTable() Input
grid:
[[1, 0, 1, 1, 1],
[1, 0, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 0, 0, 1, 0]]
Example Output
9
Solution
The top right of the grid consists of a rectangle with nine 1s in it, the biggest possible space for our table.
The problem can be approached in a logical way where you loop through the building and check for potential space where tables can be placed, then just return the biggest table found:
function biggestTable(grid) {
const tableExist = (x, y, w, h) => {
let exist = 1;
for(let i = 0; i < w ; i++) {
for(let j = 0; j < h ; j++) {
exist &= grid[j + y] !== undefined && grid[j + y][i + x] == 1;
}
}
return exist;
};
const biggestTableAt = (x, y) => {
let max = 0;
for(let w = 1; w <= grid[0].length; w++) {
for(let h = 1; h <= grid.length; h++) {
const table_size = w * h;
if (tableExist(x, y, w, h) && table_size>max) {
max = table_size;
}
}
}
return max;
};
let max = 0;
for(let x = 0; x < grid[0].length; x++) {
for(let y= 0; y < grid.length; y++) {
const table_size = biggestTableAt(x, y);
if (table_size > max) {
max = table_size;
}
}
}
return max;
}

Compute the closest point from a specific point

I create a panorama has a compass in the middle, the compass is pointing towards the top center of the image, initially.
Now I want to move the compass with respect to this point, since it is a 360 panorama, it creates an adjacent duplicate. So what I wanted is to point the compass to the point to whichever point is closer as you move from left to right or right to left.
Here is the what I have done so far, which doesn't behave like what I wanted.
So here is the code for that: https://gist.github.com/joeyhipolito/8678bf35dba7795de4d5
What I did is that I created two points:
points.push({
x: target.offset().left + (windowWidth) / 2,
y: target.offset().top
});
points.push({
x: (target.offset().left + (windowWidth) / 2) + (common.width / 2),
y: target.offset().top
});
And then try to calculate which points is closer to the reference by pythagorean theorem
var closestPoint = points[0];
var closestValue = Math.sqrt(Math.pow(points[0].x, 2) + Math.pow(points[0].y, 2));
for (var i = 1; i >= points.length; i++) {
var z = Math.sqrt(Math.pow(points[i].x, 2) + Math.pow(points[i].y, 2));
if(z < closestValue) {
closestPoint = points[i];
closestValue = z;
}
};
What do you think I am missing out?
You seem to have an issue with the control of your for loop. This loop:
for (var i = 1; i >= points.length; i++) {
};
(note the exit condition) does not run, because 1 (initial value of i) is immediately below the array's length, which is presumably at least 2. On the other hand, if the array length was at most 1, then this loop will never terminate, as i will keep increasing, and the i>=length statement remains true.
You probably want to change that exit condition to i<=length, then the minimum-finding logic should be fine.
Change
// ↓↓
for ( var i = 1; i >= points.length; i++ ) {
// ...
};
to
// ↓
for ( var i = 1; i < points.length; i++ ) {
// ...
};
Another way to do this is using Array.reduce(). It may not be as quick, but it may be more semantic:
function distFromOrigin( point ) {
return Math.sqrt( Math.pow( point, 2 ) + Math.pow( point, 2 ) );
}
var closestPoint = points.reduce(
function( closestPoint, currPoint, index, array ) {
var currDist = distFromOrigin( currPoint ),
closestDist = distFromOrigin( closestDist );
if ( currDist < closestDist ) {
return currPoint;
} else {
return closestPoint;
}
}
);

How to straighten unneeded turns in a A* graph search result?

I have been working on a JavaScript implementation of the early 90's adventure games and specifically plotting a path from the place the hero is standing to the location the player has clicked on. My approach is to first determine if a strait line (without obstructions) can be drawn, if not then to search for a path of clear way-points using Brian Grinstead's excellent javascript-astar. The problem I'm facing however is the path (while optimal will veer into spaces that would seem to the user an unintended. Here is a classic example of what I'm talking about (the green path is the generated path, the red dots are each turns where the direction of the path changes):
Now I know that A* is only guaranteed to return a path that cannot be simpler (in terms of steps), but I'm struggling to implement a heuristic that weights turns. Here is a picture that show two other paths that would also qualify as just as simple (with an equal number of steps)
The Blue path would present the same number of steps and turns while the red path has the same number of steps and fewer turns. In my code I have a simplifyPath() function that removes steps where the direction changes, so if I could get all possible paths from astar then I could select the one with the least turns, but that's not how A* fundamentally works, so I'm looking for a way to incorporate simplicity into the heuristic.
Here is my current code:
var img,
field = document.getElementById('field'),
EngineBuilder = function(field, size) {
var context = field.getContext("2d"),
graphSettings = { size: size, mid: Math.ceil(size/2)},
engine = {
getPosition: function(event) {
var bounds = field.getBoundingClientRect(),
x = Math.floor(((event.clientX - bounds.left)/field.clientWidth)*field.width),
y = Math.floor(((event.clientY - bounds.top)/field.clientHeight)*field.height),
node = graph.grid[Math.floor(y/graphSettings.size)][Math.floor(x/graphSettings.size)];
return {
x: x,
y: y,
node: node
}
},
drawObstructions: function() {
context.clearRect (0, 0, 320, 200);
if(img) {
context.drawImage(img, 0, 0);
} else {
context.fillStyle = 'rgb(0, 0, 0)';
context.fillRect(200, 100, 50, 50);
context.fillRect(0, 100, 50, 50);
context.fillRect(100, 100, 50, 50);
context.fillRect(0, 50, 150, 50);
}
},
simplifyPath: function(start, complexPath, end) {
var previous = complexPath[1], simplePath = [start, {x:(previous.y*graphSettings.size)+graphSettings.mid, y:(previous.x*graphSettings.size)+graphSettings.mid}], i, classification, previousClassification;
for(i = 1; i < (complexPath.length - 1); i++) {
classification = (complexPath[i].x-previous.x).toString()+':'+(complexPath[i].y-previous.y).toString();
if(classification !== previousClassification) {
simplePath.push({x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid});
} else {
simplePath[simplePath.length-1]={x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid};
}
previous = complexPath[i];
previousClassification = classification;
}
simplePath.push(end);
return simplePath;
},
drawPath: function(start, end) {
var path, step, next;
if(this.isPathClear(start, end)) {
this.drawLine(start, end);
} else {
path = this.simplifyPath(start, astar.search(graph, start.node, end.node), end);
if(path.length > 1) {
step = path[0];
for(next = 1; next < path.length; next++) {
this.drawLine(step, path[next]);
step = path[next];
}
}
}
},
drawLine: function(start, end) {
var x = start.x,
y = start.y,
dx = Math.abs(end.x - start.x),
sx = start.x<end.x ? 1 : -1,
dy = -1 * Math.abs(end.y - start.y),
sy = start.y<end.y ? 1 : -1,
err = dx+dy,
e2, pixel;
for(;;) {
pixel = context.getImageData(x, y, 1, 1).data[3];
if(pixel === 255) {
context.fillStyle = 'rgb(255, 0, 0)';
} else {
context.fillStyle = 'rgb(0, 255, 0)';
}
context.fillRect(x, y, 1, 1);
if(x === end.x && y === end.y) {
break;
} else {
e2 = 2 * err;
if(e2 >= dy) {
err += dy;
x += sx;
}
if(e2 <= dx) {
err += dx;
y += sy;
}
}
}
},
isPathClear: function(start, end) {
var x = start.x,
y = start.y,
dx = Math.abs(end.x - start.x),
sx = start.x<end.x ? 1 : -1,
dy = -1 * Math.abs(end.y - start.y),
sy = start.y<end.y ? 1 : -1,
err = dx+dy,
e2, pixel;
for(;;) {
pixel = context.getImageData(x, y, 1, 1).data[3];
if(pixel === 255) {
return false;
}
if(x === end.x && y === end.y) {
return true;
} else {
e2 = 2 * err;
if(e2 >= dy) {
err += dy;
x += sx;
}
if(e2 <= dx) {
err += dx;
y += sy;
}
}
}
}
}, graph;
engine.drawObstructions();
graph = (function() {
var x, y, rows = [], cols, js = '[';
for(y = 0; y < 200; y += graphSettings.size) {
cols = [];
for(x = 0; x < 320; x += graphSettings.size) {
cols.push(context.getImageData(x+graphSettings.mid, y+graphSettings.mid, 1, 1).data[3] === 255 ? 0 : 1);
}
js += '['+cols+'],\n';
rows.push(cols);
}
js = js.substring(0, js.length - 2);
js += ']';
document.getElementById('Graph').value=js;
return new Graph(rows, { diagonal: true });
})();
return engine;
}, start, end, engine = EngineBuilder(field, 10);
field.addEventListener('click', function(event) {
var position = engine.getPosition(event);
if(!start) {
start = position;
} else {
end = position;
}
if(start && end) {
engine.drawObstructions();
engine.drawPath(start, end);
start = end;
}
}, false);
#field {
border: thin black solid;
width: 98%;
background: #FFFFC7;
}
#Graph {
width: 98%;
height: 300px;
overflow-y: scroll;
}
<script src="http://jason.sperske.com/adventure/astar.js"></script>
<code>Click on any 2 points on white spaces and a path will be drawn</code>
<canvas id='field' height
='200' width='320'></canvas>
<textarea id='Graph' wrap='off'></textarea>
After digging into Michail Michailidis' excellent answer I've added the following code to my simplifyPath() function) (demo):
simplifyPath: function(start, complexPath, end) {
var previous = complexPath[1],
simplePath = [start, {x:(previous.y*graphSettings.size)+graphSettings.mid, y:(previous.x*graphSettings.size)+graphSettings.mid}],
i,
finalPath = [simplePath[0]],
classification,
previousClassification;
for(i = 1; i < (complexPath.length - 1); i++) {
classification = (complexPath[i].x-previous.x).toString()+':'+(complexPath[i].y-previous.y).toString();
if(classification !== previousClassification) {
simplePath.push({x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid});
} else {
simplePath[simplePath.length-1]={x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid};
}
previous = complexPath[i];
previousClassification = classification;
}
simplePath.push(end);
previous = simplePath[0];
for(i = 2; i < simplePath.length; i++) {
if(!this.isPathClear(previous, simplePath[i])) {
finalPath.push(simplePath[i-1]);
previous = simplePath[i-1];
}
}
finalPath.push(end);
return finalPath;
}
Basically after it reduces redundant steps in the same direction, it tries to smooth out the path by looking ahead to see if it can eliminate any steps.
Very very intresting problem! Thanks for this question! So...some observations first:
Not allowing diagonal movement fixes this problem but since you are interested in diagonal movement I had to search more.
I had a look at path simplifying algorithms like:
Ramer Douglas Peuker
(http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm)
and an implementation: https://gist.github.com/rhyolight/2846020.
I added an implementation to your code without success. This algorithm doesn't take into account obstacles so it was difficult to adapt it.
I wonder what would the behavior be (for diagonal movements) if you had used Dijkstra instead of A* or if you used an 'all shortest paths between a pair of nodes' algorithm and then you sorted them by increasing changes in direction.
After reading a bit about A* here http://buildnewgames.com/astar/ I thought that the implementation of A-star you are using is the problem or the heuristics. I tried all the heuristics on the a-star of your code including euclidean that I coded myself and tried also all the heuristics in the http://buildnewgames.com/astar code Unfortunately all of the diagonal allowing heuristics were having the same issue you are describing.
I started working with their code because it is a one-to-one grid and yours was giving me issues drawing. Your simplifyPath that I tried to remove was also causing additional problems. You have to keep in mind that since
you are doing a remapping this could be an issue based on that
On a square grid that allows 4 directions of movement, use Manhattan distance (L1).
On a square grid that allows 8 directions of movement, use Diagonal distance (L∞).
On a square grid that allows any direction of movement, you might or might not want Euclidean distance (L2). If A* is finding paths on the grid but you are allowing movement not on the grid, you may want to consider other representations of the map. (from http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html)
What is my pseudocode algorithm:
var path = A-star();
for each node in path {
check all following nodes till some lookahead limit
if you find two nodes in the same row but not column or in the same column but not row {
var nodesToBeStraightened = push all nodes to be "straightened"
break the loop;
}
skip loop index to the next node after zig-zag
}
if nodesToBeStraightened length is at least 3 AND
nodesToBeStraightened nodes don't form a line AND
the resulting Straight line after simplification doesn't hit an obstruction
var straightenedPath = straighten by getting the first and last elements of nodesToBeStraightened and using their coordinates accordingly
return straightenedPath;
Here is the visual explanation of what is being compared in the algorithm:
Visual Explanation:
How this code will be used with yours (I did most of the changes - I tried my best but there are so many problems like with how you do drawing and because of the rounding of the grid etc - you have to use a grid and keep the scale of the paths accurate - please see also assumptions below):
var img,
field = document.getElementById('field'),
EngineBuilder = function(field, size) {
var context = field.getContext("2d"),
graphSettings = { size: size, mid: Math.ceil(size/2)},
engine = {
//[...] missing code
removeZigZag: function(currentPath,lookahead){
//for each of the squares on the path - see lookahead more squares and check if it is in the path
for (var i=0; i<currentPath.length; i++){
var toBeStraightened = [];
for (var j=i; j<lookahead+i+1 && j<currentPath.length; j++){
var startIndexToStraighten = i;
var endIndexToStraighten = i+1;
//check if the one from lookahead has the same x xor the same y with one later node in the path
//and they are not on the same line
if(
(currentPath[i].x == currentPath[j].x && currentPath[i].y != currentPath[j].y) ||
(currentPath[i].x == currentPath[j].y && currentPath[i].x != currentPath[j].x)
) {
endIndexToStraighten = j;
//now that we found something between i and j push it to be straightened
for (var k = startIndexToStraighten; k<=endIndexToStraighten; k++){
toBeStraightened.push(currentPath[k]);
}
//skip the loop forward
i = endIndexToStraighten-1;
break;
}
}
if (toBeStraightened.length>=3
&& !this.formsALine(toBeStraightened)
&& !this.lineWillGoThroughObstructions(currentPath[startIndexToStraighten], currentPath[endIndexToStraighten],this.graph?????)
){
//straightening:
this.straightenLine(currentPath, startIndexToStraighten, endIndexToStraighten);
}
}
return currentPath;
},
straightenLine: function(currentPath,fromIndex,toIndex){
for (var l=fromIndex; l<=toIndex; l++){
if (currentPath[fromIndex].x == currentPath[toIndex].x){
currentPath[l].x = currentPath[fromIndex].x;
}
else if (currentPath[fromIndex].y == currentPath[toIndex].y){
currentPath[l].y = currentPath[fromIndex].y;
}
}
},
lineWillGoThroughObstructions: function(point1, point2, graph){
var minX = Math.min(point1.x,point2.x);
var maxX = Math.max(point1.x,point2.x);
var minY = Math.min(point1.y,point2.y);
var maxY = Math.max(point1.y,point2.y);
//same row
if (point1.y == point2.y){
for (var i=minX; i<=maxX && i<graph.length; i++){
if (graph[i][point1.y] == 1){ //obstacle
return true;
}
}
}
//same column
if (point1.x == point2.x){
for (var i=minY; i<=maxY && i<graph[0].length; i++){
if (graph[point1.x][i] == 1){ //obstacle
return true;
}
}
}
return false;
},
formsALine: function(pointsArray){
//only horizontal or vertical
if (!pointsArray || (pointsArray && pointsArray.length<1)){
return false;
}
var firstY = pointsArray[0].y;
var lastY = pointsArray[pointsArray.length-1].y;
var firstX = pointsArray[0].x;
var lastX = pointsArray[pointsArray.length-1].x;
//vertical line
if (firstY == lastY){
for (var i=0; i<pointsArray.length; i++){
if (pointsArray[i].y!=firstY){
return false;
}
}
return true;
}
//horizontal line
else if (firstX == lastX){
for (var i=0; i<pointsArray.length; i++){
if (pointsArray[i].x!=firstX){
return false;
}
}
return true;
}
return false;
}
//[...] missing code
}
//[...] missing code
}
Assumptions and Incompatibilities of the above code:
obstacle is 1 and not 0
the orginal code I have in the demo is using array instead of {x: number, y:number}
in case you use the other a-star implementation, the point1 location is on the column 1 and row 2.
converted to your {x: number, y:number} but haven't checked all the parts:
I couldn't access the graph object to get the obstacles - see ?????
You have to call my removeZigZag with a lookahead e.g 7 (steps away) in the place where you were
doing your path simplification
admittedly their code is not that good compared to the a-star implementation from Stanford but for our purposes it should be irelevant
possibly the code has bugs that I don't know of and could be improved. Also I did my checks only in this specific world configuration
I believe the code has complexity O(N x lookahead)~O(N) where N is the number of steps in the input A* shortest path.
Here is the code in my github repository (you can run the demo)
https://github.com/zifnab87/AstarWithDiagonalsFixedZigZag
based on this A* Javascript implementation downloaded from here: http://buildnewgames.com/astar/
Their clickHandling and world boundary code is broken as when you click on the right side of the map the path calculation is not working sometimes. I didn't have time to find their bug. As a result my code has the same issue
Probably it is because the map I put from your question is not square - but anyway my algorithm should be unaffected. You will see this weird behavior is happening if non of my remove ZigZag code runs. (Edit: It was actually because the map was not square - I updated the map to be square for now)
Feel free to play around by uncommenting this line to see the before-after:
result = removeZigZag(result,7);
I have attached 3 before after image sets so the results can be visualized:
(Keep in mind to match start and goal if you try them - direction DOES matter ;) )
Case 1: Before
Case 1: After
Case 2: Before
Case 2: After
Case 3: Before
Case 3: After
Case 4: Before
Case 4: After
Resources:
My code (A* diagonal movement zig zag fix demo): https://github.com/zifnab87/AstarWithDiagonalsFixedZigZag
Original Javascript A* implementation of my demo can be found above (first commit) or here: - http://buildnewgames.com/astar/
A* explanation: http://buildnewgames.com/astar/
A* explanation from Stanford: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
JavaScript A* implementation used by OP's question (Github):
Ramer Douglas Peuker Algorithm (Wikipedia): http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
Javascript implementation of Douglas Peuker Algorithm: https://gist.github.com/rhyolight/2846020
A* Algorithm (Wikipedia): http://en.wikipedia.org/wiki/A*_search_algorithm
You can use a modified A* algorithm to account for changes in direction. While simplifying the result of the standard A* algorithm may yield good results, it may not be optimal. This modified A* algorithm will return a path of minimal length with the least number of turns.
In the modified A* algorithm, each position corresponds to eight different nodes, each with their own heading. For example, the position (1, 1) corresponds to the eight nodes
(1,1)-up, (1,1)-down, (1,1)-right, (1,1)-left,
(1,1)-up-left, (1,1)-up-right, (1,1)-down-left, and (1,1)-down-right
The heuristic distance from a node to the goal is the the heuristic distance from the corresponding point to the goal. In this case, you probably want to use the following function:
H(point) = max(abs(goal.xcor-point.xcor), abs(goal.ycor-point.ycor))
The nodes that correspond to a particular position are connected to the nodes of the neighboring positions with the proper heading. For example, the nodes corresponding to the position (1,1) are all connected to the following eight nodes
(1,2)-up, (1,0)-down, (2,1)-right, (0,1)-left,
(0,2)-up-left, (2,2)-up-right, (0,0)-down-left, and (2,0)-down-right
The distance between any two connected nodes depends on their heading. If they have the same head, then the distance is 1, otherwise, we have made a turn, so the distance is 1+epsilon. epsilon represents an arbitrarily small value/number.
We know need to have a special case for the both the start and goal. The start and goal are both represented as a single node. At the start, we have no heading, so the distance between the start node and any connected node is 1.
We can now run the standard A* algorithm on the modified graph. We can map the returned path to a path in the original grid, by ignoring the headings. The total length of the returned path will be of the form n+m*epsilon. n is the total length of the corresponding path in the original grid, and m is the number of turns. Because A* returns a path of minimal length, the path in the original grid is a path of minimal length that makes the least turns.
I have come up with somewhat of a fix that is a simple addition to your original code, but it doesn't work in all situations (see image below) because we are limited to what the A* returns us. You can see my jsfiddle here
I added the following code to your simplifyPath function right before the return. What it does is strips out extra steps by seeing if there is a clear path between non-adjacent steps (looking at larger gaps first). It could be optimized, but you should get the gist from what I've got.
do{
shortened = false;
loop:
for(i = 0; i < simplePath.length; i++) {
for(j = (simplePath.length - 1); j > (i + 1); j--) {
if(this.isPathClear(simplePath[i],simplePath[j])) {
simplePath.splice((i + 1),(j - i - 1));
shortened = true;
break loop;
}
}
}
} while(shortened == true);
You can see below that this removes the path that goes in on the left (as in the question) but also that not all the odd turns are removed. This solution only uses the points provided from the A*, not points in between on the path - for example, because the 2nd point does not have a straight unobstructed line to the 4th or 5th points, it cannot optimize point 3 out. It happens a lot less than the original code, but it still does give weird results sometimes.
In edition to nodes having references to their parent nodes. Also store which direction that node came from inside a variable. In my case there was only two possibilities horizontally or vertically. So I created two public static constants for each possibility. And a helper function named "toDirection" which takes two nodes and return which direction should be taken in order to go from one to another:
public class Node {
final static int HORIZONTALLY = 0;
final static int VERTICALLY = 1;
int col, row;
boolean isTravelable;
int fromDirection;
double hCost;
double gCost;
double fCost;
Node parent;
public Node(int col, int row, boolean isTravelable) {
this.col = col;
this.row = row;
this.isTravelable = isTravelable;
}
public static int toDirection(Node from, Node to) {
return (from.col != to.col) ? Node.HORIZONTALLY : Node.VERTICALLY;
}
}
Then you can change your weight calculation function to take turns into account. You can now give a small punishment for turns like:
public double calcGCost(Node current, Node neighbor) {
if(current.fromDirection == Node.toDirection(current, neighbor)) {
return 1;
} else{
return 1.2;
}
}
Full code: https://github.com/tezsezen/AStarAlgorithm
At the risk of potential down voting, I will try my best to suggest an answer. If you weren't using a third party plugin I would suggest a simple pop/push stack object be built however since you are using someone else's plugin it might be best to try and work along side it rather than against it.
That being said I might just do something simple like track my output results and try to logically determine the correct answer. I would make a simple entity type object literal for storage within an array of all possible path's? So the entire object's life span is only to hold position information. Then you could later parse that array of objects looking for the smallest turn count.
Also, since this third party plugin will do most of the work behind the scenes and doesn't seem very accessible to extract, you might need to feed it criteria on your own. For example if its adding more turns then you want, i.e. inside the door looking square, then maybe sending it the coordinates of the start and end arent enouugh. Perhaps its better to stop at each turn and send in the new coordinates to see if a straight line is now possible. If you did this then each turn would have a change to look and see if there is an obstruction stopping a straight line movement.
I feel like this answer is too simple so it must be incorrect but I will try nonetheless...
//Entity Type Object Literal
var pathsFound = function() {
//Path Stats
straightLine: false,
turnCount: 0,
xPos: -1, //Probably should not be instantiated -1 but for now it's fine
yPos: -1,
//Getters
isStraightLine: function() { return this.straightLine; },
getTurnCount: function() { return this.turnCount; },
getXPos: function() { return this.xPos; },
getYPos: function() { return this.yPos; },
//Setters
setStraightLine: function() { this.straightLine = true; },
setCrookedLine: function() { this.straightLine = false; },
setXPos: function(val) { this.xPos = val; },
setYPos: function(val) { this.yPos = val; },
//Class Functionality
incrementTurnCounter: function() { this.turnCount++; },
updateFullPosition: function(xVal, yVal) {
this.xPos = xVal;
this.yPos = yVal.
},
}
This way you could report all the data every step of the way and before you draw to the screen you could iterate through your array of these object literals and find the correct path by the lowest turnCount.
var img,
field = document.getElementById('field'),
EngineBuilder = function(field, size) {
var context = field.getContext("2d"),
graphSettings = { size: size, mid: Math.ceil(size/2)},
engine = {
getPosition: function(event) {
var bounds = field.getBoundingClientRect(),
x = Math.floor(((event.clientX - bounds.left)/field.clientWidth)*field.width),
y = Math.floor(((event.clientY - bounds.top)/field.clientHeight)*field.height),
node = graph.grid[Math.floor(y/graphSettings.size)][Math.floor(x/graphSettings.size)];
return {
x: x,
y: y,
node: node
}
},
drawObstructions: function() {
context.clearRect (0, 0, 320, 200);
if(img) {
context.drawImage(img, 0, 0);
} else {
context.fillStyle = 'rgb(0, 0, 0)';
context.fillRect(200, 100, 50, 50);
context.fillRect(0, 100, 50, 50);
context.fillRect(100, 100, 50, 50);
context.fillRect(0, 50, 150, 50);
}
},
simplifyPath: function(start, complexPath, end) {
var previous = complexPath[1], simplePath = [start, {x:(previous.y*graphSettings.size)+graphSettings.mid, y:(previous.x*graphSettings.size)+graphSettings.mid}], i, classification, previousClassification;
for(i = 1; i < (complexPath.length - 1); i++) {
classification = (complexPath[i].x-previous.x).toString()+':'+(complexPath[i].y-previous.y).toString();
if(classification !== previousClassification) {
simplePath.push({x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid});
} else {
simplePath[simplePath.length-1]={x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid};
}
previous = complexPath[i];
previousClassification = classification;
}
simplePath.push(end);
return simplePath;
},
drawPath: function(start, end) {
var path, step, next;
if(this.isPathClear(start, end)) {
this.drawLine(start, end);
} else {
path = this.simplifyPath(start, astar.search(graph, start.node, end.node), end);
if(path.length > 1) {
step = path[0];
for(next = 1; next < path.length; next++) {
this.drawLine(step, path[next]);
step = path[next];
}
}
}
},
drawLine: function(start, end) {
var x = start.x,
y = start.y,
dx = Math.abs(end.x - start.x),
sx = start.x<end.x ? 1 : -1,
dy = -1 * Math.abs(end.y - start.y),
sy = start.y<end.y ? 1 : -1,
err = dx+dy,
e2, pixel;
for(;;) {
pixel = context.getImageData(x, y, 1, 1).data[3];
if(pixel === 255) {
context.fillStyle = 'rgb(255, 0, 0)';
} else {
context.fillStyle = 'rgb(0, 255, 0)';
}
context.fillRect(x, y, 1, 1);
if(x === end.x && y === end.y) {
break;
} else {
e2 = 2 * err;
if(e2 >= dy) {
err += dy;
x += sx;
}
if(e2 <= dx) {
err += dx;
y += sy;
}
}
}
},
isPathClear: function(start, end) {
var x = start.x,
y = start.y,
dx = Math.abs(end.x - start.x),
sx = start.x<end.x ? 1 : -1,
dy = -1 * Math.abs(end.y - start.y),
sy = start.y<end.y ? 1 : -1,
err = dx+dy,
e2, pixel;
for(;;) {
pixel = context.getImageData(x, y, 1, 1).data[3];
if(pixel === 255) {
return false;
}
if(x === end.x && y === end.y) {
return true;
} else {
e2 = 2 * err;
if(e2 >= dy) {
err += dy;
x += sx;
}
if(e2 <= dx) {
err += dx;
y += sy;
}
}
}
}
}, graph;
engine.drawObstructions();
graph = (function() {
var x, y, rows = [], cols, js = '[';
for(y = 0; y < 200; y += graphSettings.size) {
cols = [];
for(x = 0; x < 320; x += graphSettings.size) {
cols.push(context.getImageData(x+graphSettings.mid, y+graphSettings.mid, 1, 1).data[3] === 255 ? 0 : 1);
}
js += '['+cols+'],\n';
rows.push(cols);
}
js = js.substring(0, js.length - 2);
js += ']';
document.getElementById('Graph').value=js;
return new Graph(rows, { diagonal: true });
})();
return engine;
}, start, end, engine = EngineBuilder(field, 10);
field.addEventListener('click', function(event) {
var position = engine.getPosition(event);
if(!start) {
start = position;
} else {
end = position;
}
if(start && end) {
engine.drawObstructions();
engine.drawPath(start, end);
start = end;
}
}, false);
#field {
border: thin black solid;
width: 98%;
background: #FFFFC7;
}
#Graph {
width: 98%;
height: 300px;
overflow-y: scroll;
}
<script src="http://jason.sperske.com/adventure/astar.js"></script>
<code>Click on any 2 points on white spaces and a path will be drawn</code>
<canvas id='field' height
='200' width='320'></canvas>
<textarea id='Graph' wrap='off'></textarea>

Categories

Resources