My goal is to code a somewhat OK chess engine, in the following position it's a mate in 2 which the engine should easily find with its depth of 4-5.
The first move the AI makes is Ra2 to trap the white king, the white king goes to f1 and instead of mating the AI moves the Rook to c2.
var initial_depth = depth;
var bestMove = null;
var nodes = 0;
var ret = await minimax(position, depth, alpha, beta, maximizingPlayer);
console.log("nodes visited: " + nodes);
return ret;
async function minimax(position, depth, alpha, beta, maximizingPlayer) {
nodes++;
if (maximizingPlayer) {
var validMoves = await getValidMoves(position, ArrtoFEN(position) + " w");
} else {
var validMoves = await getValidMoves(position, ArrtoFEN(position) + " b");
}
if (validMoves.length < 1 || depth == 0) {
var eval = await getEval(position);
return [eval, null];
}
if (maximizingPlayer) {
var maxEval = Number.NEGATIVE_INFINITY;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var testbrd = makeMove(move, position) //not the actual code. shortend for Readability
var eval = await minimax(testbrd, depth - 1, alpha, beta, false);
if (eval[0] > maxEval) {
maxEval = eval[0];
if (initial_depth == depth) {
bestMove = move;
console.log("current bestmove: " + bestMove);
}
}
alpha = Math.max(alpha, eval[0]);
if (beta <= alpha) {
break;
}
}
return [maxEval, bestMove];
} else {
var minEval = Number.POSITIVE_INFINITY;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var testbrd = makeMove(move, position)//not the actual code. shortend for Readability
var eval = await minimax(testbrd, depth - 1, alpha, beta, true);
if (eval[0] < minEval) {
minEval = eval[0];
if (initial_depth == depth) {
bestMove = move;
console.log("current bestmove: " + bestMove);
}
}
beta = Math.min(beta, eval[0]);
if (beta <= alpha) {
break;
}
}
return [minEval, bestMove];
}
}
}
This is because it sees that any move will win, and you don't have a condition that tells the engine that it is better to do mate in 1 move than in 5 moves. If at the end of a search find that you have 0 legal moves and you are in check, then you are checkmated. In this case you want to send back a checkmate score (large negative value) and add the ply from this. This way you will make it better to mate in fewer moves than in a larger amount of moves.
I suggest you go for Negamax alogirthm in stead of minimax. It will mean much less code and much easier to debug.
Related
function algorithm(){
if(startPoint === true && endPoint === true){
//add the heuristic distance to the start position from the final position
startPosition.h = distance([startPosition.x, startPosition.y]);
let openList = []
openList.push(startPosition)
let closedList = []
while (openList.length > 0){
//print(openList)
lowPos = 0;
for(let i = 0; i < openList.length; i++){
if(openList[i].f < openList[lowPos].f){
lowPos = i;
}
}
let currentPosition = openList[lowPos];
//currentPosition.check()
//if the currentPosition is the endPosition, retrace steps and find the path, then return this path
if(currentPosition === endPosition){
let curr = currentPosition;
let ret = [];
while(curr.parent != null){
curr.path()
ret.push(curr);
curr = curr.parent;
}
endPosition.end()
return ret.reverse();
}
openList.splice(lowPos, 1);
closedList.push(currentPosition);
let neighbours = neighbors(currentPosition);
for(let i = 0; i < neighbours.length; i++){
let neighbour = neighbours[i];
if(closedList.includes(neighbour) || neighbour.colour == "black"){
continue;
}
neighbour.check()
let gScore = currentPosition.g + 1;
let gScoreBest = false;
if(openList.includes(neighbour) == false){
gScoreBest = true;
neighbour.h = distance([neighbour.x, neighbour.y]);
openList.push(neighbour);
}
else if(gScore < neighbour.g){
gScoreBest = true;
}
if(gScoreBest == true){
neighbour.parent = currentPosition;
neighbour.g = gScore;
neighbour.f = neighbour.g + neighbour.h;
}
}
}
}
//meaning that either the path is not possible or the final node/initial node
has not yet been placed.
return [];
}
this is my a star algorithm in p5, i'm trying to make an a star visualisation project, but for some reason a lot more of blocks are highlighted than expected.
[: https://i.stack.imgur.com/ILlOr.png
In reality it is supposed to be something like this: : https://i.stack.imgur.com/nsF5r.png
The second picture isn't mine, its from someone else's implementation: https://qiao.github.io/PathFinding.js/visual/ = link to the second picture
I think it's something to do with the order of the line: neighbour.check() which changes the colour of the block.
Here is a diagonal solution, as you can see for some reason there is purple in the top left, that is my issue. The top left should not be searched, but it is for some reason.
If you need more of my code, please let me know.
It looks like you are not checking the diagonals.
It is not a mistake. You are doing great.
I got it to fix, surprisingly the thing that was wrong was my distance formulae, I called the wrong variable.
here's what it looks like now! :)
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'm writing code for processing ANSI escape codes for cursor for jQuery Terminal. but have problems, not sure how it should work, I've got weird results.
I'm testing with ervy library.
and using this code:
function scatter_plot() {
const scatterData = [];
for (let i = 1; i < 17; i++) {
i < 6 ? scatterData.push({ key: 'A', value: [i, i], style: ervy.fg('red', '*') })
: scatterData.push({ key: 'A', value: [i, 6], style: ervy.fg('red', '*') });
}
scatterData.push({ key: 'B', value: [2, 6], style: ervy.fg('blue', '# '), side: 2 });
scatterData.push({ key: 'C', value: [0, 0], style: ervy.bg('cyan', 2) });
var plot = ervy.scatter(scatterData, { legendGap: 18, width: 15 });
// same as Linux XTERM where 0 code is interpreted as 1.
var formatting = $.terminal.from_ansi(plot.replace(/\x1b\[0([A-D])/g, '\x1b[1$1'));
return formatting;
}
$.terminal.defaults.formatters = [];
var term = $('body').terminal();
term.echo(scatter_plot());
it should look like in Linux Xterm:
But it looks like this, see codepen demo
While I was writing the question changing few +1 and -1 (see processing A-F ANSI escapes in the code) when moving cursor give this result (code snippet have latest code).
First line got overwritten by spaces and whole plot is one to top and one to right (except 0,0 cyan dot that should be below " |" and 2 characters wide, so you should see right half of it, this one is correct but the rest is not)
this is my new code for processing cursor, I'm doing this just before processing colors, so the code is not that complex.
// -------------------------------------------------------------------------------
var ansi_re = /(\x1B\[[0-9;]*[A-Za-z])/g;
var cursor_re = /(.*)\r?\n\x1b\[1A\x1b\[([0-9]+)C/;
var move_cursor_split = /(\x1b\[[0-9]+[A-G])/g;
var move_cursor_match = /^\x1b\[([0-9]+)([A-G])/;
// -------------------------------------------------------------------------------
function parse_ansi_cursor(input) {
/*
(function(log) {
console.log = function(...args) {
if (true || cursor.y === 11) {
return log.apply(console, args);
}
};
})(console.log);
*/
function length(text) {
return text.replace(ansi_re, '').length;
}
function get_index(text, x) {
var splitted = text.split(ansi_re);
var format = 0;
var count = 0;
var prev_count = 0;
for (var i = 0; i < splitted.length; i++) {
var string = splitted[i];
if (string) {
if (string.match(ansi_re)) {
format += string.length;
} else {
count += string.length;
if (count >= x) {
var rest = x - prev_count;
return format + rest;
}
prev_count = count;
}
}
}
return i;
}
// ansi aware substring, it just and add removed ansi escapes
// at the beginning we don't care if the were disabled with 0m
function substring(text, start, end) {
var result = text.substring(start, end);
if (start === 0 || !text.match(ansi_re)) {
return result;
}
var before = text.substring(0, start);
var match = before.match(ansi_re);
if (match) {
return before.match(ansi_re).join('') + result;
}
return result;
}
// insert text at cursor position
// result is array of splitted arrays that form single line
function insert(text) {
if (!text) {
return;
}
if (!result[cursor.y]) {
result[cursor.y] = [];
}
var index = 0;
var sum = 0;
var len, after;
function inject() {
index++;
if (result[cursor.y][index]) {
result[cursor.y].splice(index, 0, null);
}
}
if (cursor.y === 11) {
//debugger;
}
if (text == "[46m [0m") {
//debugger;
}
console.log({...cursor, text});
if (cursor.x === 0 && result[cursor.y][index]) {
source = result[cursor.y][0];
len = length(text);
var i = get_index(source, len);
if (length(source) < len) {
after = result[cursor.y][index + 1];
if (after) {
i = get_index(after, len - length(source));
after = substring(after, i);
result[cursor.y].splice(index, 2, null, after);
} else {
result[cursor.y].splice(index, 1, null);
}
} else {
after = substring(source, i);
result[cursor.y].splice(index, 1, null, after);
}
} else {
var limit = 100000; // infite loop guard
var prev_sum = 0;
// find in which substring to insert the text
while (index < cursor.x) {
if (!limit--) {
warn('[WARN] To many loops');
break;
}
var source = result[cursor.y][index];
if (!source) {
result[cursor.y].push(new Array(cursor.x - prev_sum).join(' '));
index++;
break;
}
if (sum === cursor.x) {
inject();
break;
}
len = length(source);
prev_sum = sum;
sum += len;
if (sum === cursor.x) {
inject();
break;
}
if (sum > cursor.x) {
var pivot = get_index(source, cursor.x - prev_sum);
var before = substring(source, 0, pivot);
var end = get_index(source, length(text));
after = substring(source, pivot + end);
if (!after.length) {
result[cursor.y].splice(index, 1, before);
} else {
result[cursor.y].splice(index, 1, before, null, after);
}
index++;
break;
} else {
index++;
}
}
}
cursor.x += length(text);
result[cursor.y][index] = text;
}
if (input.match(move_cursor_split)) {
var lines = input.split('\n').filter(Boolean);
var cursor = {x: 0, y: -1};
var result = [];
for (var i = 0; i < lines.length; ++i) {
console.log('-------------------------------------------------');
var string = lines[i];
cursor.x = 0;
cursor.y++;
var splitted = string.split(move_cursor_split).filter(Boolean);
for (var j = 0; j < splitted.length; ++j) {
var part = splitted[j];
console.log(part);
var match = part.match(move_cursor_match);
if (match) {
var ansi_code = match[2];
var value = +match[1];
console.log({code: ansi_code, value, ...cursor});
if (value === 0) {
continue;
}
switch (ansi_code) {
case 'A': // UP
cursor.y -= value;
break;
case 'B': // Down
cursor.y += value - 1;
break;
case 'C': // forward
cursor.x += value + 1;
break;
case 'D': // Back
cursor.x -= value + 1;
break;
case 'E': // Cursor Next Line
cursor.x = 0;
cursor.y += value - 1;
break;
case 'F': // Cursor Previous Line
cursor.x = 0;
cursor.y -= value + 1;
break;
}
if (cursor.x < 0) {
cursor.x = 0;
}
if (cursor.y < 0) {
cursor.y = 0;
}
} else {
insert(part);
}
}
}
return result.map(function(line) {
return line.join('');
}).join('\n');
}
return input;
}
The result = []; in code is array of lines where single line may be split into multiple sub strings when inserting the text at cursor, maybe the code would be simpler if they would be array of strings. Right now I want only fix the cursor position.
Here is the codepen demo with from_ansi function embeded (inside there is parse_ansi_cursor that is problematic). Sorry there is lot of code, but parsing ANSI escape codes is not simple.
What I'm not sure how should work is moving the cursor (right now it have + 1 or - 1, I'm not sure about this) I'm also not sure if I should increase cursor.y before each line. I'm not 100% sure how this should work. I've looked into Linux Xterm code but didn't found a clues. Looked at Xterm.js but the ervy plot is completely broken for those scatter plot.
my from_ansi function had original code that was processing some ANSI cursor codes like this one:
input = input.replace(/\x1b\[([0-9]+)C/g, function(_, num) {
return new Array(+num + 1).join(' ');
});
only C, forward just add blanks, it was working for ANSI art but not work with ervy scatter plot.
I think it's not too broad, it's just question about moving cursor and processing newlines using ANSI escape codes. Also it's suppose to be simple case, cursor should move only inside single string not outside like in real terminal (ervy plot output ANSI escape codes like that).
I'm fine with answers that explain how to process the string and how to move the cursor that will work, but if you can provide fixes to the code I would be great. I prefer fixes to my code now whole new implementation unless is much simpler and it's a function parse_ansi_cursor(input) and work the same with rest of the code but with fixed cursor movement.
EDIT:
I've found that my input.split('\n').filter(Boolean) was wrong it should be:
var lines = input.split('\n');
if (input.match(/^\n/)) {
lines.shift();
}
if (input.match(/\n$/)) {
lines.pop();
}
and it seems that some old spec for ANSI escapes say that 0 is not zero but placeholder for default which is 1. That was removed from spec but Xterm is still using this. So I've added this line for parsing code, if there is 0A or A got value 1.
var value = match[1].match(/^0?$/) ? 1 : +match[1];
the plot looks better, but there are still issues with the cursor. (I think it's cursor - I'm not 100% sure).
I've changed the +1/-1 again now it's closer (Almost the same as in XTerm). Buss still there's need to be bug in my code.
EDIT:
afer answer by #jerch I've tried to use node ansi parser, have the same issue don't know how to process the cursor:
var cursor = {x:0,y:0};
result = [];
var terminal = {
inst_p: function(s) {
var line = result[cursor.y];
if (!line) {
result[cursor.y] = s;
} else if (cursor.x === 0) {
result[cursor.y] = s + line.substring(s.length);
} else if (line.length < cursor.x) {
var len = cursor.x - (line.length - 1);
result[cursor.y] += new Array(len).join(' ') + s;
} else if (line.length === cursor.x) {
result[cursor.y] += s;
} else {
var before = line.substring(0, cursor.x);
var after = line.substring(cursor.x + s.length);
result[cursor.y] = before + s + after;
}
cursor.x += s.length;
console.log({s, ...cursor, line: result[cursor.y]});
},
inst_o: function(s) {console.log('osc', s);},
inst_x: function(flag) {
var code = flag.charCodeAt(0);
if (code === 10) {
cursor.y++;
cursor.x = 0;
}
},
inst_c: function(collected, params, flag) {
console.log({collected, params, flag});
var value = params[0] === 0 ? 1 : params[0];
switch(flag) {
case 'A': // UP
cursor.y -= value;
break;
case 'B': // Down
cursor.y += value - 1;
break;
case 'C': // forward
cursor.x += value;
break;
case 'D': // Back
cursor.x -= value;
break;
case 'E': // Cursor Next Line
cursor.x = 0;
cursor.y += value;
break;
case 'F': // Cursor Previous Line
cursor.x = 0;
cursor.y -= value;
break;
}
},
inst_e: function(collected, flag) {console.log('esc', collected, flag);},
inst_H: function(collected, params, flag) {console.log('dcs-Hook', collected, params, flag);},
inst_P: function(dcs) {console.log('dcs-Put', dcs);},
inst_U: function() {console.log('dcs-Unhook');}
};
var parser = new AnsiParser(terminal);
parser.parse(input);
return result.join('\n');
This is just simple example that ignore everything except newline and cursor movement.
Here is the output:
UPDATE:
It seems that every cursor movement should be just += value or -= value and my value - 1; was just correcting to bug in ervy library that was not working on clear terminal.
To begin with - a Regexp based approach is not ideal to handle escape sequences. The reason for this are complicated interactions between various terminal sequences, as some break a former not yet closed one while others keep working in the middle of another (like some control codes) and the "outer" sequence would still finish correctly. You would have to pull in all these edge cases into every single regexp (see https://github.com/xtermjs/xterm.js/issues/2607#issuecomment-562648768 for an illustration).
In general parsing escape sequences is quite tricky, we even have an issue regarding that in terminal-wg. Hopefully we manage to get some minimal parsing requirements from this in the future. Most certainly it will not be regexp-based ;)
All that said, its much easier to go with a real parser, that deals with all the edge cases. A good starting point for a DEC compatible parser is https://vt100.net/emu/dec_ansi_parser. For cursor handling you have to handle at least these states with all actions:
ground
escape
csi_entry
csi_ignore
csi_param
csi_intermediate
plus all other states as dummy entries. Also control codes need special care (action execute), as they might interfer anytime with any other sequence with different results.
To make things even worse, the official ECMA-48 specifiction slightly differs for certain aspects from the DEC parser. Still most emulators used these days try to aim for DEC VT100+ compatibility.
If you dont want to write the parser yourself, you can either use/modify my old parser or the one we have in xterm.js (the latter might be harder to integrate as it operates on UTF32 codepoints).
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.
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
};
});