How to calculate 'square' distance, in steps in 8 directions - javascript

Note: I probably could google this problem, but I don't know the right terminology to use.
So I'm working on a square grid where pieces are able to move in 8 directions (i.e. horizontal, vertical or diagonal), and for some decision making processes I need to calculate the "square" distance between spaces. Which is the number of steps it would take to get there in those 8 directions. It would look something like this (colours added for clarity.
So far, I've made this attempt to calculate the directions
distance_between(source, target) {
var x_dist = Math.abs(source.x - target.x);
var y_dist = Math.abs(source.y - target.y);
return x_dist + y_dist;
}
This is more of a diamond pattern, the steps it would take to get there in 4 directions (just horizontal or vertical). The result looks like this:
I feel like there should be a simple way to calculate the square distance, but short of taking repeated steps in that direction, I can't work out what that would be. How can I go about finding the square distance between two spaces?
It might also be useful to know what this kind of distance is called, so I can look for geometry resources on this.

The distance that you are looking for will be the greater of x_dist or y_dist in your current function.
function distance_between(source, target) {
var x_dist = Math.abs(source.x - target.x);
var y_dist = Math.abs(source.y - target.y);
return Math.max(x_dist, y_dist);
}
const
matrix = Array.from({ length: 7 }, (_, i) => Array.from({ length: 7 }, (_, j) => ({ x: j, y: i }))),
mapDistancesFrom = (source = { x: 0, y: 0 }) =>
matrix.map((row) => row.map((col) => distance_between(source, col)));
console.log('Matrix');
matrix.forEach(row => console.log(row.map(({ x, y }) => `(${x},${y})`).join(' ')));
console.log('\nDistances from (3,3)');
mapDistancesFrom({ x: 3, y: 3 }).forEach(row => console.log(row.join(' ')));
console.log('\nDistances from (2,2)');
mapDistancesFrom({ x: 2, y: 2 }).forEach(row => console.log(row.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Javascript Snake Game - too much recursion error

so I'm working on this snake game, and I'm basically trying to prevent the food from spawning on top of the snake tail. My setup variables:
let headX = 10; //snake starting position
let headY = 10;
let appleX = 5; //food starting position
let appleY = 5;
This is the function that checks head/food collision
function checkAppleCollision() {
if (appleX === headX && appleY === headY) {
generateApplePosition();
tailLength++;
score++;
}
}
And this is the function that randomizes the apple position after collision, and also returns the "too much recursion" error, after a couple of collisions:
function generateApplePosition() {
let collisionDetect = false;
let newAppleX = Math.floor(Math.random() * tileCount);
let newAppleY = Math.floor(Math.random() * tileCount);
for (let i = 0; i < snakeTail.length; i++) {
let segment = snakeTail[i];
if (newAppleX === segment.x && newAppleY === segment.y) {
collisionDetect = true;
}
}
while (collisionDetect === true) {
generateApplePosition();
}
appleX = newAppleX;
appleY = newAppleY;
}
Please help, I have no idea what to do here. Everything else works as intended.
Using recursions or do while is a bad idea (I'll explain later)
meanwhile, you could simplify your logic by creating:
reusable samePos() and collides() functions
a recursive createApple() function, which will return itself if the randomly generated x,y positions are occupied by the snake body
const world = {w:6, h:1}; // height set to 1 for this demo only
const snake = [{x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}];
const apple = {pos: {x:0, y:0}};
// Check if two object's x,y match
const samePos = (a, b) => a.x === b.x && a.y === b.y;
// Check if object x,y is inside an array of objects
const collides = (ob, arr) => arr.some(o => samePos(ob, o));
const createApple = () => {
const randPos = {
x: ~~(Math.random() * world.w),
y: ~~(Math.random() * world.h),
};
if (collides(randPos, snake)) {
console.log(`position ${randPos.x} ${randPos.y} is occupied by snake`);
return createApple(); // Try another position.
}
// Finally a free spot!
apple.pos = randPos;
console.log(`Apple to free position: ${apple.pos.x} ${apple.pos.y}`);
}
createApple();
Run this demo multiple times
The problem
Useless random guesswork!
As you can see from the example above, if you run it multiple times, very often the randomly generated number is the same as the previously generated one:
...
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
...
therefore, as your snake grows in size, the recursion might go wild — ad absurdum, iterating way too many times, repeating and failing on the same xy positions, until finally hitting a rare free spot...
This is a really bad design.
Solutions
One solution would be to keep track of the already used randomized positions inside an Array - but that implies unnecessarily to go trough such an Array.
A best solution would be to actually treat the game not as a 2D game, but as a 1D game:
Consider this 2D map of size 4x3 as indexes:
0 1 2 3
4 5 6 7
8 9 10 11
now, let's place a snake into this map:
0 ⬛ 2 3
4 ⬛ ⬛ 7
8 9 ⬛ 11
here's the linear map with the Snake as a 1D list:
[ 0 ⬛ 2 3 4 ⬛ ⬛ 7 8 9 ⬛ 11 ]
therefore, instead of using an array of objects {x:n, y:n} for the snake body positions, all you need is:
[1, 5, 6, 10] // Snake body as indexes
Now that you know all the indexes where you're not allowed to place an Apple, all you need to do when creating the new apple is:
Create an Array of 0-N indexes of length: world.w * world.h
Loop the snake body indexes and delete those indexes from the array of indexes to get an Array of free spots indexes
Simply get only once a random key from that array of free spots!
const indexToXY = (index, width) => ({ x: index%width, y: Math.trunc(index/width) });
const world = {w:4, h:3};
const snakeBody = [1, 5, 6, 10];
const createApple = () => {
const arr = [...Array(world.w * world.h).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
snakeBody.forEach(i => delete arr[i]);
const freeIndexes = arr.filter(k => k !== undefined); // [0, 2, 3, 4, 7, 8, 9, 11]
const appleIndex = freeIndexes[~~(Math.random() * freeIndexes.length)];
const applePos = indexToXY(appleIndex, world.w);
console.log("New apple position: %o", applePos);
};
createApple();
Run this demo multiple times
Having that free spot index simply draw your apple at the XY coordinates using this simple formula
X = index % mapWidth
Y = floor(index / mapWidth)
As others have said, this doesn't need to be recursive, and you should also take into account the (however unlikely) possibility where there are no more tiles to spawn on which would result in an infinite loop.
function generateApplePosition() {
// Count how many tiles are left for spawning in
const tilesLeft = (tileCount * tileCount) - snakeTail.length;
let collisionDetect;
if (tilesLeft > 0) {
do {
const newAppleX = Math.floor(Math.random() * tileCount);
const newAppleY = Math.floor(Math.random() * tileCount);
collisionDetect = false;
for (let i = 0; i < snakeTail.length; i++) {
const { x, y } = snakeTail[i];
if (newAppleX === x && newAppleY === y) {
collisionDetect = true; // Collision
break;
}
}
if (!collisionDetect) {
// Found spawn point
appleX = newAppleX;
appleY = newAppleY;
}
} while (collisionDetect);
}
}

How to find all elements between two coordinates in a 2d array

Essentially I'm trying to create the game reversi.
To cut it short if you don't know what it is, I have a 8x8 board of squares. There are 2 coordinates and I need to determine all the squares that are between the two coordinates and fill them in. The 2 coordinates are either on the same y, same x or diagonal to each other.
Can someone explain the logic behind how I would go about doing something like this? How can I determine the coordinates of all the elements between the 2 coordinates.
You need a simple for loop, starting at one of the coordinates and moving towards the other.
let connect = (c1, c2) => {
// Determine the distance between c1 & c2
let delta = c1.map((v, i) => c2[i] - v);
let distance = Math.max(...delta.map(v => Math.abs(v)));
// Determine the unit vector (e.g. [1, -1]) to move each iteration
let direction = delta.map(v => v / distance);
// Starting at `c1`, iterate for `distance` iterations, moving in `direction` each iteration.
return [...Array(distance + 1)].map((_, i) => c1.map((v, j) => v + direction[j] * i));
// Same as above, but exclude `c1` and `c2` from the return array.
// return [...Array(distance - 1)].map((_, i) => c1.map((v, j) => v + direction[j] * (i + 1)));
};
let c1 = [3, 6];
let c2 = [8, 1];
console.log(connect(c1, c2));

Getting two points on the edges of a rectangle

I have a rectangle and would like to:
Get a random point on one (any) of the sides.
Get a random point on one (except for the previously picked) side.
My initial approach is to create arrays for each possible side.
var arr:Array = [[{x:0,y:0}, // Top
{x:width,y:0}], //
[{x:width,y:0}, // Right
{x:width,y:height}], //
[{x:width,y:height}, // Bottom
{x:0,y:height}], //
[{x:0,y:height}, // Left
{x:0,y:0}]]; //
Then, I get the sides.
rand is an instance of Rand and has the methods:
.next() which provides a random number between 0 and 1
.between(x,y) which returns a random number between x and y.
var firstSide:Array = arr[rand.next() * arr.length];
var secondSide:Array;
do {
secondSide = arr[rand.next() * arr.length];
} while(secondSide.equals(firstSide));
Finally, I calculate my points.
var pointOnFirstSide:Object = {x:rand.between(firstSide[0].x, firstSide[1].x),
y:rand.between(firstSide[0].y, firstSide[1].y};
var pointOnSecondSide:Object = {x:rand.between(secondSide[0].x, secondSide[1].x),
y:rand.between(secondSide[0].y, secondSide[1].y};
I don't think this is the most efficient way to solve this.
How would you do it?
Assuming we have the following interfaces and types:
interface Rand {
next(): number;
between(x: number, y: number): number;
}
interface Point {
x: number;
y: number;
}
type PointPair = readonly [Point, Point];
and taking you at your word in the comment that the procedure is: first randomly pick two sides, and then pick random points on those sides... first let's see what's involved in picking two sides at random:
const s1 = Math.floor(rand.between(0, arr.length));
const s2 = (Math.floor(rand.between(1, arr.length)) + s1) % arr.length;
s1 and s2 represent the indices of arr that we are choosing. The first one chooses a whole number between 0 and one less than the length of the array. We do this by picking a real number (okay, floating point number, whatever) between 0 and the length of the array, and then taking the floor of that real number. Since the length is 4, what we are doing is picking a real number uniformly between 0 and 4. One quarter of those numbers are between 0 and 1, another quarter between 1 and 2, another quarter between 2 and 3, and the last quarter are between 3 and 4. That means you have a 25% chance of choosing each of 0, 1, 2 and 3. (The chance of choosing 4 is essentially 0, or perhaps exactly 0 if rand is implemented in the normal way which excludes the upper bound).
For s2 we now pick a number uniformly between 1 and the length of the array. In this case, we are picking 1, 2, or 3 with a 33% chance each. We add that number to s1 and then take the remainder when dividing by 4. Think of what we are doing as starting on the first side s1, and then moving either 1, 2, or 3 sides (say) clockwise to pick the next side. This completely eliminates the possibility of choosing the same side twice.
Now let's see what's involved in randomly picking a point on a line segment (which can be defined as a PointPair, corresponding to the two ends p1 and p2 of the line segment) given a Rand instance:
function randomPointOnSide([p1, p2]: PointPair, rand: Rand): Point {
const frac = rand.next(); // between 0 and 1
return { x: (p2.x - p1.x) * frac + p1.x, y: (p2.y - p1.y) * frac + p1.y };
}
Here what we do is pick a single random number frac, representing how far along the way from p1 to p2 we want to go. If frac is 0, we pick p1. If frac is 1, we pick p2. If frac is 0.5, we pick halfway between p1 and p2. The general formula for this is a linear interpolation between p1 and p2 given frac.
Hopefully between the two of those, you can implement the algorithm you're looking for. Good luck!
Link to code
jcalz already gave an excellent answer. Here is an alternate version for the variant I asked about in the comments: When you want your points uniformly chosen over two sides of the perimeter, so that if your w : h ratio was 4 : 1, the first point is four times as likely to lie on a horizontal side as a vertical one. (This means that the chance of hitting two opposite long sides is 24/45; two opposite short side, 1/45; and one of each, 20/45 -- by a simple but slightly tedious calculation.)
const rand = {
next: () => Math. random (),
between: (lo, hi) => lo + (hi - lo) * Math .random (),
}
const vertices = (w, h) => [ {x: 0, y: h}, {x: w, y: h}, {x: w, y: 0}, {x: 0, y: 0} ]
const edges = ([v1, v2, v3, v4]) => [ [v1, v2], [v2, v3], [v3, v4], [v4, v1] ]
const randomPoint = ([v1, v2], rand) => ({
x: v1 .x + rand .next () * (v2 .x - v1 .x),
y: v1 .y + rand .next () * (v2 .y - v1 .y),
})
const getIndex = (w, h, x) => x < w ? 0 : x < w + h ? 1 : x < w + h + w ? 2 : 3
const twoPoints = (w, h, rand) => {
const es = edges (vertices (w, h) )
const perimeter = 2 * w + 2 * h
const r1 = rand .between (0, perimeter)
const idx1 = getIndex (w, h, r1)
const r2 = (
rand. between (0, perimeter - (idx1 % 2 == 0 ? w : h)) +
Math .ceil ((idx1 + 1) / 2) * w + Math .floor ((idx1 + 1) / 2) * h
) % perimeter
const idx2 = getIndex (w, h, r2)
return {p1: randomPoint (es [idx1], rand), p2: randomPoint (es [idx2], rand)}
}
console .log (
// Ten random pairs on rectangle with width 5 and height 2
Array (10) .fill () .map (() => twoPoints (5, 2, rand))
)
The only complicated bit in there is the calculation of r2. We calculate a random number between 0 and the total length of the remaining three sides, by adding all four sides together and subtracting off the length of the current side, width if idx is even, height if it's odd. Then we add it to the total length of the sides up to and including the index (where the ceil and floor calls simply count the number of horizontal and vertical sides, these values multiplied by the width and height, respectively, and added together) and finally take a floating-point modulus of the result with the perimeter. This is the same technique as in jcalz's answer, but made more complex by dealing with side lengths rather than simple counts.
I didn't make rand an instance of any class or interface, and in fact didn't do any Typescript here, but you can add that yourself easily enough.

Which face is further in parallel projections?

I'm working with JavaScript(React) on a geometry program that creates the axonometry(better parallel projections) of the specified object defined by vertices and faces(a face can have different numbers of vertices).
It works perfectly when you do not need faces to be opaque otherwise there are faces above other that should be below.
So I want to order my list of faces from the further to the nearest:
[
[[100, 0, 100], [50, 50, 50], [120, 170, 120], [10, 200, 150]],
[[10, 20, 30], [10, 200, 250], [50, 50, 50], [100, 30, 30]],...
]
I will use faces.sort(sortingFunction).
I don't care about intersecting faces
(it will take the faces of all objects together)
How should sortingFunction be?
You have to consider how is the axonometry defined. It is defined by the X-, Y-axis rotation(Xrotation can be both greater and smaller than Yrotation), Z rotation is π / 4 (90°).
Here is an old version of the application that makes you understand what I mean: http://dev_proiezioni.surge.sh/
Sorry for my terrible English.
Thanks
What you are trying to do is called "back-face culling. One common technique is to determine if the list of the points in the representation of a polygon are in clockwise or counterclockwise order from the point of view of the camera. This requires that you are very careful about how you create the list of vertices. For more details, check out the wikipedia article: https://en.wikipedia.org/wiki/Back-face_culling. The Implementation section describes the mathematics involved which you will have to translate into JavaScript. Note that this technique is faster than sorting the list of faces because it requires checking each face only once rather than comparing each face against other faces.
I don't care about intersecting faces
That means that we can reduce the plains to points, by taking the point in the middle:
const vecOp = op => (a, b) => a.map((c, i) => op(c, b[i] || b));
const add = vecOp((a, b) => a + b);
const sub = vecOp((a, b) => a - b);
const mul = vecOp((a, b) => a * b);
const div = vecOp((a, b) => a / b);
const sum = v => v.reduce((a, b) => a + b, 0);
const middle = (a, b) => div(add(a, b), 2);
const planeToPoint = ([a, b, c, d]) => middle(
middle(a, b),
middle(c, d)
);
Now to sort by "closer to camera", one could draw a line between the centers of two planes, which will result in a direction:
const aToB = (a, b) =>
sub(
planeToPoint(b),
planeToPoint(a)
);
Now we could turn the camera rotation into a camera lookAt vector :
const rotToVec = (yaw, pitch) => ([
Math.cos(yaw) * Math.cos(pitch),
Math.sin(yaw) * Math.cos(pitch),
Math.sin(pitch)
]);
and that direction can be compared to the cameras direction resulting in an angle between them:
const angle = (a, b) => sum(mul(a, b)) / sum(a) * sum(b)
Now lets turn that alltogether:
const camVec = rotToVec(cam.yaw, cam.pitch);
planes.sort((a, b) =>
Math.abs(angle(aToB(a, b), camVec)) < Math.PI / 4 /*90°*/ ? 1 : -1
);
Disclaimer: I neither tried the code above, nor did I work with parallel projections, nor am I good at math, so take my words with caution, I have no idea what I'm talking about
For an approximate solution, use a 3D to 3D transform, and consider the Z coordinate. For every face, keep the nearest Z and sort the faces on this Z.
For a more exact solution, consider https://en.wikipedia.org/wiki/Newell%27s_algorithm.
Sort by the distance from where you camera is.
function distanceBetweenTwoPoints (p1, p2) {
return Math.hypot(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z)
}
function sortFunction (p1, p2) {
return distanceBetweenTwoPoints(camera, p1) > distanceBetweenTwoPoints(camera,p2) ? -1 : 1
}
Tweak the sign > depending on which order you'd like.

How calculate a 1/4 circle arc to move along (bezier curve)?

I'm using JQuery.path to move an object along a bezier curve. When the item is clicked, I can determine the start and end points. How do I calculate the angle and length to make the element move from point A to point B on an arc that's 1/4 of a circle intersecting the start and end point?
I essentially want it to move along a curve that never dips lower than the starting y position and never to the left of the end x position.
var path = {
start: {
x: currentLeft,
y: currentTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
},
end: {
x: endLeft,
y: endTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
}
};
jQuery(myElement).animate(
{
path: new jQuery.path.bezier(path)
}
);
Approx. what I want:
Approx what I'm getting (they're dipping too low):
A generalised solution is slightly tricky because it must handle diagonal movements in each of four diagonal directions, and horizontal, and vertical.
First, you need a couple of utility functions :
function r2d(x) {
/* radians to degrees */
return x * 180 / Math.PI;
}
function smaller(x, y) {
/* returns the closer of x|y to zero */
var x_ = Math.abs(x);
var y_ = Math.abs(y);
return (Math.min(x_, y_) === x_) ? x : y;
}
Now a main function, anim, accepts a jQuery object (containing the element of interest) and an end object (with properties .left and .top ).
function anim($el, end) {
var current = $el.position();
var slope1 = (end.top - current.top) / (end.left - current.left);
var slope2 = 1 / slope1;
var endAngle = r2d(Math.atan(smaller(slope1, slope2)));
var startAngle = -endAngle;
var length = 1/3; //Vary between 0 and 1 to affect the path's curvature. Also, try >1 for an interesting effect.
//For debugging
$("#endAngle").text(endAngle);
$("#startAngle").text(startAngle);
$("#length").text(length);
var path = {
start: {
x: current.left,
y: current.top,
angle: startAngle,
length: length
},
end: {
x: end.left,
y: end.top,
angle: endAngle,
length: length
}
};
$el.animate({ path: new jQuery.path.bezier(path) });
}
The calculation of endAngle is pretty simple for each individual case (the four diagonals, horizontal and vertical) but slightly tricky for a generalised solution. It took me a while to develop something that worked in all cases.
DEMO
If the "what you want" is really what you need, i.e. 90 degree departure and arrivals, then we can solve this problem pretty much instantly:
p_start = { X:..., Y:... }
p_end = { X:..., Y:... }
dx = p_end.X - p_start.X
dy = p_end.Y - p_start.Y
control_1 = { X: p_start.X, Y: p_start.Y + 0.55228 * dy }
control_2 = { X: p_end.X - 0.55228 * dx, Y: p_end.Y }
And done. What we've basically done is pretend that the start and end points lie on a circle, and computer the control points such that the resulting Bezier curve has minimal error wrt the quarter circular arc.
In terms of angles: The departure from start is always at angle π/2, and the arrival at the end points is always at angle 0.

Categories