I am trying to return the value of the closest object to my position in a function.
I tried to put the entities in a array and than I tried return closest object to my position with a for loop, but it did not work. How can I do this?
function getMyEntity() {
return Game.currentGame.world.localPlayer.entity.getFromTick();
}
function getOtherEntity() {
var MyPlayerEntity = getMyEntity();
var entities = Game.currentGame.world.entities;
for (var uid in entities) {
// how i get closest entity to my player entity here?
var gameObject = entities[uid].fromTick;
console.log(entities[uid].fromTick.position, MyPlayerEntity.position)
if (gameObject.entityClass == "Prop" && gameObject.uid !== MyPlayerEntity.uid) {
return gameObject;
}
}
}
function aimAssist() {
var MyPlayerEntity = getMyEntity();
var OtherEntity = getOtherEntity();
if (OtherEntity == undefined) return
var aimPosition = {
x: OtherEntity.position.x - MyPlayerEntity.position.x,
y: OtherEntity.position.y - MyPlayerEntity.position.y
}
return aimPosition;
}
I'll give you a bad advice, for now it'll work, as your game grows it will be bad because of O(n^2) complexity. Read a bit about quadtrees and see if you can do that. Meanwhile you can compare the euclidean distance, do not need to take the square root:
Object.keys(entities)
.map(function(d,i){
var dx = entities[d].fromTick.position.x - MyPlayerEntity.position.x,
dy = entities[d].fromTick.position.y - MyPlayerEntity.position.y,
result = {D:(dx * dx) + (dy + dy), obj:entities[d] , valueOf: function(){return this.D};
return result;
}).sort(function(a,b){
return a-b;
})[0].obj; //this returns the closest one
So your original function becomes this:
function getOtherEntity() {
var MyPlayerEntity = getMyEntity();
var entities = Game.currentGame.world.entities;
return Object.keys(entities)
.map(function(d,i){
var dx = entities[d].fromTick.position.x - MyPlayerEntity.position.x,
dy = entities[d].fromTick.position.y - MyPlayerEntity.position.y,
result = {D:(dx * dx) + (dy + dy), obj:entities[d] , valueOf: function(){return this.D};
return result;
}).sort(function(a,b){
return a-b;
})[0].obj; //this returns the closest one
}
I'd create an array of objects which contain the Object-ID and distance, and sort it by distance.
The first array-item is the closest to the player.
The array may look like
[{uid: 1234, distance: 12}, {uid: 1235, distance: 16}, ...]
You can sort arrays with arrayName.sort(sortingFunction)
assuming you can get x and y coordinates from gameObject.position and MyPlayerEntity.position you could use a bit of Pitagoras: c^2 = a^2 +b^2, with c being the distance
let a = gameObject.position.x - MyPlayerEntity.position.x;
let b = gameObject.position.y - MyPlayerEntity.position.y;
let distanceSquared = a*a + b*b;
Since you don't seem to need the exact distance and sqrt() is expensive, you can use the value in distanceSquared and other variables declared outside of the loop to keep track
let closestDistance;
let closest;
to make the proper comparisons
if(distanceSquared < closestDistance){
closestDistance = distanceSquared;
closest = gameObject;
}
After you loop through the array, the closest entity reference should be:
return closest;
Related
I've got a problem sorting arrays. I'm currently trying to optimize a thing in a strategy game I play, and for that I need to calculate distance between all members of my alliance, the first towards the others and so on. No problem doing that actually. But now, what I want to do is sort the array of distance "ascending" and problem is, I need to write the corresponding nickname to match the distance. I've been searching for 2 days and I can't figure out a working solution.
I tried to copy the array before sorting it, but I need the unsorted array and with that sort function, it sorts the copy too !
Actually the code provided is good, speaking of distance accuracy but not sorted ascending. If I sort the distances, the nicknames are no longer corresponding. I don't know why they appear in the order of the pseudo_list because It's supposed to be sorted through nSort2()
This is what I've ended up with so far :
//Sorting Distance[i] Array List
function nSort(arr)
{
return arr.sort((a, b) => a - b);
}
//Calculating Distance
function calcDist(xA, yA, xB, yB)
{
return Math.sqrt(Math.pow((xB-xA), 2)+Math.pow((yB-yA), 2));
}
//Here i'm trying to retrieved unsorted position of distance by index to sort the nicknames by their respective distances
function nSort2(arr_str, arr_nbr)
{
var arr_nbr2 = arr_nbr.splice(0);
var arr_sort = nSort(arr_nbr2);
var str_sort = [];
arr_str.forEach(function(element, i)
{
j = arr_sort.indexOf(arr_nbr2[i], i);
str_sort[i] = arr_str[j];
});
console.log(str_sort);
return str_sort;
}
var pseudo_list = ["teddy95", "gabrielc", "ngozi"]; //The list (I just put the first 3 to not to write to much unnecessary code)
var x_ = [29, 26, 4]; // The X Coordinate list
var y_ = [519, 461, 143]; // The Y Coordinate list
var distance = [[]]; // The 2D Array for distance (distance[0][0] being the member's distance tower himself (which is obviously 0).
//Calculating Distances And Storing them in the 2D Array
y_.forEach(function(element, i)
{
distance[i] = [];
x_.forEach(function(element, j)
{
distance[i][j] = Math.ceil(calcDist(x_[i], y_[i], x_[j], y_[j]));
});
});
//Displaying Sorted Array ascending (Trying)
y_.forEach(function(element, i)
{
x_.forEach(function(element, j)
{
document.write(pseudo_list[i] + ' -> ' + nSort2(pseudo_list, distance[i])[j] + ': ' + distance[i][j] + '<br>');
});
});
I think your problem come from over complicating the data structures (I'm not insulting you just sharing an opinion).
In the code below all the input (pseudo, x, y) is stored in an object so player data is easier to manipulate.
Then I'm not using a matrix because you end up creating new issues namely I'd expect distance[1][2] = distance[2][1] so sorting will create duplicate results (and the diagonal doesn't help since it represents the distance from yourself). Instead I have a 1D array constructed with no duplicates, i.e. it contains the distance from the first element to all the others (i.e. second, third, ...), then the second element from the "ones on the right" (i.e. third, fourth, ...), ...
Once you have all the distance information, sorting is a trivial task so is displaying the result.
//Calculating Distance
function calcDist(xA, yA, xB, yB) {
return Math.sqrt(Math.pow((xB - xA), 2) + Math.pow((yB - yA), 2));
}
let players = [{
pseudo: "teddy95",
x: 29,
y: 519
},
{
pseudo: "gabrielc",
x: 26,
y: 461
},
{
pseudo: "ngozi",
x: 4,
y: 143
}]
let distances = []
players.forEach(function (element, i) {
for (let j = i + 1; j < players.length; ++j) {
distances.push({
player1: element,
player2: players[j],
distance: Math.ceil(calcDist(element.x, element.y, players[j].x, players[j].y))
})
}
})
distances.sort(function (a, b) { return a.distance - b.distance })
distances.forEach(function (element, i) {
document.write(element.player1.pseudo + ' - ' + element.player2.pseudo + ' dist ' + element.distance + '<br>')
})
I created a function which takes the inner of the tag S in a XML string:
'<C><P /><Z><S>[Grounds here]</S><D /><O /></Z></C>'
, containing grounds data, obviously related to a game. Then I consume every ground data in this inner until I reach the number z with i in my for loop and return the data of the first/one remaining ground as a object.
The problem: the function returns undefined instead of a object.
This is the function:
/**
* Get a string between 2 strings.
*/
String.prototype.between = function(left, right) {
var sub = this.substr(this.lastIndexOf(left) + left.length);
return sub.substr(0, sub.indexOf(right));
}
/**
* #param {Number} z The inner position of the ground I want to read, e.g, 1, the first.
*/
function readGround(z) {
// string containing all existent grounds
var groundsData = xml.substr(STG.indexOf('<S>') + 3, STG.lastIndexOf('</S>'));
// Iterate the grounds while z isn't reached
for(var i = 1; i < z; i++) {
// Get the ground inner
var outer = groundsData.substr(groundsData.indexOf('<S') + 3, groundsData.indexOf('/>'));
// Check if i reached z
if(i === z) {
// Get grounds properties
var a = [
outer.between('L="', '"'),
outer.between('H="', '"'),
outer.between('X="', '"'),
outer.between('Y="', '"')
];
return {
L: a[0], H: a[1],
X: a[2], Y: a[3]
};
// Else skip this ground
} else groundsData = groundsData.substr(groundsData.indexOf('/>'), groundsData.length);
}
}
Your loop makes i go from 1 to num-1. But within the loop you have a condition if(i==num). This condition is never true, so the programme never reaches the return statement. If the programme flow inside a function means that no return statement is ever reached, then the function simply returns undefined. (This isn't javascript-specific - similar rules apply in many languages.)
Instead, you can move the return statement outside of the loop.
function readGround(num) {
var grounds = stg.substr(stg.indexOf('<S>') + 3, stg.lastIndexOf('</S>')),
gr;
for (var i = 1; i < num; i++) {
grounds = grounds.substr(grounds.indexOf('/>') + 2, grounds.length);
}
gr = grounds.substr(grounds.indexOf('<S') + 3, grounds.indexOf('/>'));
var a = [stringBetween(gr, 'L="', '"'), stringBetween(gr, 'H="', '"'), stringBetween(gr, 'X="', '"'), stringBetween(gr, 'Y="', '"')];
return {
L: a[0],
H: a[1],
X: a[2],
Y: a[3]
};
}
(A few other things also had to be tweaked to make your code work, such as that a should read from the section of text in gr rather than from the longer string grounds)
jsfiddle
I am currently using FreeCodeCamp to try to learn basic JavaScript scripting. The problem that I am currently working on is:
http://www.freecodecamp.com/challenges/bonfire-map-the-debris.
The problem involves using OOP to solve a specific task (calculating orbital periods from the given altitude).
My code is as follows:
function orbitalPeriod(arr) {
var GM = 398600.4418;
var earthRadius = 6367.4447;
this.arr = arr;
for(var i = 0; i < arr.length; i++){
var altitude = this.arr[i]["avgAlt"] + earthRadius;
var calc = Math.round((2*Math.PI) * Math.sqrt(Math.pow(altitude,3) / GM),1);
this.arr[i]["avgAlt"] = calc;
}
return this.arr;
}
orbitalPeriod([{name : "sputkin", avgAlt : 35873.5553}]);
The issue is not with my calculations. Rather, when I submit my code, I get: "expected [ { name: 'sputkin', avgAlt: 86400 } ] to deeply equal [ Array (1) ]". Does anyone know why it is telling me that I should return an Array (1)?
The test suite is expecting the return array to contain an object with the properties name and orbitalPeriod - yours is returning an array containing an object with the properties name and avgAlt.
Side note, don't use the this keyword unless you're sure as to what it does - and I promise you it does not do what you think it does here.
Here's the solution, compare it with yours. Your calculations were correct, so good job on that part.
function orbitalPeriod(arr) {
var GM = 398600.4418,
earthRadius = 6367.4447,
output = [], altitude, calc;
for (var i = 0; i < arr.length; i++){
altitude = arr[i].avgAlt + earthRadius;
calc = Math.round((2*Math.PI) * Math.sqrt(Math.pow(altitude,3) / GM));
output.push({
name: arr[i].name,
orbitalPeriod: calc
});
}
return output;
}
orbitalPeriod([{name : "sputkin", avgAlt : 35873.5553}]);
Bonus note: Math.round() only takes one parameter.
Bonus answer:
Array.prototype.map() makes this super clean, if we're not tuning for performance.
function orbitalPeriod(arr) {
var GM = 398600.4418,
earthRadius = 6367.4447;
return arr.map(function (o) {
return {
name: o.name,
orbitalPeriod: Math.round((2 * Math.PI) * Math.sqrt(Math.pow(o.avgAlt + earthRadius, 3) / GM))
};
});
}
orbitalPeriod([{name : "sputkin", avgAlt : 35873.5553}]);
I have a working code, here:
function simpleDist(pointA, pointB) {
var x = pointA.x - pointB.x,
y = pointA.y - pointB.y;
return Math.sqrt(x*x + y*y);
}
function sortByDist(pointRef, pointArray) {
var distancePairs = [],
output = [];
for(var p in pointArray) {
var pointComp = pointArray[p];
distancePairs.push([simpleDist(pointRef,pointComp), p]);
}
distancePairs.sort(function(a,b) {
return a[0]-b[0];
});
for(var p in distancePairs) {
var pair = distancePairs[p];
output.push(pointArray[pair[1]]);
}
return output;
}
And this works, for what I'm doing. However, I'm wondering if there is a more simplified way to do it using Array.sort(). I have looked at several explanations of Array.sort() and it seems like it would need to accept 3 functions to perform what I need without the workaround here. I feel like I'm just being dense, though. Can you see any way to make this function faster, or more simplified?
I think that you are pretty much on the right track.
If you are trying to optimize for lines of code, then you can use Array.prototype.map to reduce some the boiler-plate array code.
Using a similar sorting implementation
function simpleDist(pointA, pointB) {
var x = pointA.x - pointB.x,
y = pointA.y - pointB.y;
return Math.sqrt(x*x + y*y);
}
var sortByDist = (function() {
var comparator = function(a,b) { return a.value - b.value; };
return function (pointRef, pointArray) {
var reorder = function(e) { return pointArray[e.index]; };
var distanceFromArray = function(b,i) {
return { index: i, value: simpleDist(pointRef, b) };
};
return pointArray.map(distanceFromArray).sort(comparator).map(reorder);
};
}());
The only "direct" way I can think to simplify the implementation would be to use memoization to optimize calls to simpleDist.
function simpleDist(pointA, pointB) {
var x = pointA.x - pointB.x,
y = pointA.y - pointB.y;
return Math.sqrt(x*x + y*y);
}
// fn(point) memoizer
var memoizer = (function(fn) {
var cache = {};
return function(pointB) {
var key = pointB.x + "|" + pointB.y; // simple key hashing
return cache[key] || (cache[key] = fn(pointB));
};
}());
function sortByDist (pointRef, pointArray) {
// partially apply pointRef to simpleDist and memoize
var distanceFrom = memoizer(function(pointB) { return simpleDist(pointRef, pointB); });
// sort
return pointArray.sort(function(a,b) {
return distanceFrom(a) - distanceFrom(b);
});
}
Given that simpleDist is a pure function -- the same inputs always produce the same outputs -- the memoizer here prevents you from incurring the cost of repeated distance calculations. With a little more setup overhead, then the sort becomes a simple comparator.
UPDATE
Here is how my code can be modified to use memoize decorator.
The good thing about it that memoization doesn't depend much on implementation of compared things.
I am not sure though how fast is JSON.stringify() (but fast enough it seems, and hashing function can be changed after all).
function memoized(fn) {
var lookupTable = {};
var keymaker = function (args) {
console.log(JSON.stringify(args));
return JSON.stringify(args)
};
return function () {
var key = keymaker.call(this, arguments);
return lookupTable[key] || (
lookupTable[key] = fn.apply(this, arguments))
}
}
function simpleDist(pointA, pointB) {
var x = pointA.x - pointB.x,
y = pointA.y - pointB.y;
return Math.sqrt(x * x + y * y);
}
function distanceFrom(pointC) {
return function (pointA, pointB) {
var distA = simpleDist(pointA, pointC);
var distB = simpleDist(pointB, pointC);
if (distA < distB) return -1;
if (distA > distB) return 1;
return 0;
}
}
var distanceFromC = memoized(distanceFrom({x:0, y:0}));
[{x:10, y:10}, {x:1, y:2},{x:3, y:1}, {x:3, y:2}].sort(distanceFromC);
OLD
function simpleDist(pointA, pointB) {
var x = pointA.x - pointB.x,
y = pointA.y - pointB.y;
return Math.sqrt(x*x + y*y);
}
//returns comparator function for specified point
function distanceFrom(pointC) {
return function(pointA, pointB) {
var distA = simpleDist(pointA, pointC);
var distB = simpleDist(pointB, pointC);
if (distA < distB)
return -1;
if (distA > distB)
return 1;
return 0;
}
}
//now we can create separator functions for each ponts we like
var distanceFromC = distanceFrom({x:0, y:0});
var distanceFromD = distanceFrom({x:10, y:10});
//just keep in mind that built-in sort modifies initial list
console.log([{x:10, y:10}, {x:1, y:2}].sort(distanceFromC)); //{1,2}, {10,10}
console.log([{x:10, y:10}, {x:1, y:2}].sort(distanceFromD)); //{10,10}, {1,2}
Here I used the fact that closures and the fact that they can access outer function parameters even after outer function returns.
You can read more about it, for example, in David Herman's Effective Javascript chapter 2.11 or here
http://jsfiddle.net/goldrunt/SeAGU/52/
Line 49 checks for "false" on isOnCircle function before creating the new object. Function is on line 32. When creating more object, the function is passing when it should not pass.
if (isOnCanvas(location) && !isOnCircle(location)) {
console.log(location, isOnCanvas(location), isOnCircle(location));
create(location);
In fact I can't get the collision detection to register true no matter what values are passed to it
(Math.pow((a.x - i.x), 2) + Math.pow((a.y - i.y), 2) <= Math.pow((a.radius + i.radius), 2))
here I've fixed and given more descriptive variable names so you can see what's going on.
EDIT: I've noticed you don't always feed a circle but sometimes a point as A, which does not have a .radius property resulting in NaN, which also screws up your comparison.
function circleTest(a,b) {
var DistanceX = a.x - b.x;
var DistanceY = a.y - b.y;
var DistanceCenter = Math.sqrt(DistanceX * DistanceX + DistanceY * DistanceY);
var CollisionDistance = b.radius;
if (a.radius) CollisionDistance += a.radius
return DistanceCenter <= CollisionDistance;
}
I also noticed a problem in your function called "isOnCircle" where you are using i (a number) as if it were a circle object, with the above function this can be fixed like:
function isOnCircle(a) {
for (var i = 0; i < circles.length; i++) {
if (circleTest(a, circles[i])) return true;
}
return false;
}
Two problems:
i is the numerical index you are using to iterate through the circles array but you are using it as if it was a circle object; you need to use circles[i] to get the circle at each iteration.
a is a point and does not have a radius (in the code below I've left a.radius in just in-case you pass in a circle rather than a point and have ORed it with 0 so you get a valid number).
Defining some additional variables (for clarity) then you can replace the isOnCircle function with this:
function isOnCircle(a) {
var i=0,l=circles.length,x,y,d,c;
for (; i < l; ++i) {
c = circles[i];
x = a.x-c.x;
y = a.y-c.y;
d = (a.radius||0)+c.radius;
if (x*x+y*y <= d*d) {
return true;
}
}
return false;
}