The formula for distance between two points is (javascript):
var xDiff = ( point1x - point2x ),
yDiff = ( point1y - point2y ),
distance = Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );
Loop around your "proposed new point", starting at one x-1, y-1 to x+1, y+1
. At each point check to see that it's not a forbidden point, not the point you just came from, and not off the boundaries of the map. If it meets all those criteria, use the above formula to measure the distance and add it to an array. At the end of your "1-point out" loop, check if there are any distances in that array. If so, take the smallest one and you're done. If there aren't any, move onto x-2, y-2 to x+2, y+2
(2 points out).
This will be extremely fast for the small area you are referring to.
Demo: http://jsfiddle.net/ThinkingStiff/V7Bqm/
var X = 0,
Y = 1,
currentPoint = [5,5],
proposedPoint = [5,6],
forbiddenPoints = [[5,6],[6,6],[4,7],[5,7],[6,7],[4,8],[5,8]],
map = { left:1, top:1, right:10, bottom:10 };
function closestSafePoint( point ) {
var x = point[X], y = point[Y], safePoints = [];
for( var left = x - 1, top = y - 1, right = x + 1, bottom = y + 1;
left <= map.left || top <= map.top || right <= map.right || bottom <= map.bottom;
left--, top--, right++, bottom++) {
checkHorizontalPoints( safePoints, point, left, right, top );
checkHorizontalPoints( safePoints, point, left, right, bottom );
checkVerticalPoints( safePoints, point, top + 1, bottom - 1, left );
checkVerticalPoints( safePoints, point, top + 1, bottom - 1, right );
safePoints.sort( function( a, b ){ return a[1] - b[1] } );
return safePoints.length ? safePoints[0] : point;
};
};
function checkHorizontalPoints( points, fromPoint, startX, endX, y ) {
for( var x = startX; x <= endX ; x++ ) {
var toPoint = [x, y];
if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {
points.push( [toPoint, distance( fromPoint, toPoint )] );
};
};
};
function checkVerticalPoints( points, fromPoint, startY, endY, x ) {
for( var y = startY; y <= endY ; y++ ) {
var toPoint = [x, y];
if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {
points.push( [toPoint, distance( fromPoint, toPoint )] );
};
};
};
function isForbidden( point ) {
for( var index = 0; index < forbiddenPoints.length; index++ ) {
if( forbiddenPoints[index].toString() == point.toString() ) return true;
};
};
function isCurrent( point ) {
return currentPoint.toString() == point.toString() ? true : false;
};
function onMap( point ) {
var x = point[X], y = point[Y];
return x >= map.left && y >= map.top && x <= map.right && y <= map.bottom;
};
function distance( pointA, pointB ) {
var xDiff = ( pointA[X] - pointB[X] ),
yDiff = ( pointA[Y] - pointB[Y] );
return Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );
};
console.log(
'current: ' + currentPoint + ', '
+ 'proposed: ' + proposedPoint + ', '
+ 'closest: ' + closestSafePoint( proposedPoint )[0]
);
One optimization you could make to this, if you're fairly sure most of your safe spots will be one or two points away is to break out as soon as you get to a point thats distance is the same as the level you're on. So if you're on loop one, and you get a point that is distance = 1, stop, since you'll never get closer than that.
UPDATE: I noticed you added "same trajectory" to your question. But in one of the comments, you also say it can't jump over the forbidden area. Those statements seem to conflict.
Same trajectory is a little more tricky and requires some trig. Check out my demo of circular divs at http://jsfiddle.net/ThinkingStiff/uLu7v/. There is a "point on ray" function halfway down at:
$this.siblings( ".circle" ).each( function()
This calculates the distance to move the surrounding circles on a ray away from the selected circle. This could be used to calculate a point on your trajectory. But, I think my original function is actually what you're looking for and you didn't mean same trajectory.