Javascript error on iterating through two arrays for hit detection - javascript

I'm making a small html5 canvas game in pure JS (with the standards of Ecmascript 6).
So far, everything went well, but now I am stuck on a recurring TypeError (Uncaught TypeError: Cannot read property 'position' of undefined).
This happens every so often, when the game checks for collisions between objects inside two arrays (to be more specific: collision detection between Bullets and Enemies).
Mostly, my collision detection function works fine. When I think I've fixed the issue, it just happens again, after a while.
This is my code:
const collisionCheckBulletEnemy = () => {
for(let i = bullets.length -1; i >= 0; i--){
for(let j = enemies.length -1; j >= 0; j--){
if(collisionCheck(bullets[i], enemies[j], 10, 10)){
bullets.splice(i, 1);
enemies.splice(j, 1);
}
}
}
}
This is the collision detection function:
const collisionCheck = (a, b, marginA, marginB) => {
// margins can be added to make things a little easier/harder on the user
if(!marginA){
marginA = 0;
}
if(!marginB){
marginB = 0;
}
return !(
a.position.x - marginA > b.position.x + b.width + marginB ||
a.position.x + a.width + marginA < b.position.x - marginB ||
a.position.y - marginA > b.position.y + b.height + marginB ||
a.position.y + a.height + marginA < b.position.y - marginB
);
}

This is happening because sometimes either the parameter a or b is passed into the function even though it's just an undefined value. Trying to do undefined.position will cause your type error.
The simple, hacky solution is simply putting a condition at the top:
if (!a || !b) {
return 0; // or whatever your default value is supposed to be
};
The real, better solution is figuring out why bullets and enemies contain some undefined values.
After reading your code I think this is the answer:
if this condition passes when i = bullets.length - 1:
if(collisionCheck(bullets[i], enemies[j], 10, 10)) {
bullets.splice(i, 1);
enemies.splice(j, 1);
}
specifically this part bullets.splice(i, 1); you shorten your array by 1, but you never decrement i.
So if bullets[i] was the very last element in your array now bullets[i] is undefined because javascript doesn't throw an error like indexOutOfBounds.
Now you begin to see that the huge flaw in your code is that it doesn't stop looping when a bullet is removed from the array, and that you only noticed when it was the very last index. Even if it isn't the very last index, it will continue to loop for another bullet which doesn't seem like your intention.
Instead you should break out of the loop since if are destroying your bullet when it hits, you shouldn't keep checking collisions for that same bullet:
if(collisionCheck(bullets[i], enemies[j], 10, 10)) {
bullets.splice(i, 1);
enemies.splice(j, 1);
break;
}

Related

Having a hard time getting the right probability outcome (js beginner)

I'm trying to make a simple rolling the dice mechanic with probabilities, if pass the level increase, if fail it decrease and if destroyed usually it resets to a certain level, but I'm having a hard time getting the right results, I am not sure if the outcome is supposed to be like this and just my intuition is wrong or something is actually messing it up.
Basically I am making a while loop that while below certain level it will roll the dice and given the results it will do something accordingly to the rates I input (40% for pass, 59.4% for fail and 0.6% to destroy). But when I do a test with 1000 tries, it always return me an average of destroyed way higher than 0.6%. I don't know if my test function is wrong, if the way I'm testing is wrong, if something on my loop is messing up the probabilities outcome.
function checkPass(successRate, failRate, destroyRate) {
let number = Math.random();
if (number < successRate) {
return 1;
} else if (number < failRate) {
return 0;
} else {
return 2;
}
}
function starforceSim(itemLevel) {
let newObj = {"level": 10, "totalMeso": 0, "destroyed": 0};
while (newObj.level < 11) {
if (newObj.level == 10) {
let passOutcome = checkPass(0.4, 0.994, 1)
if (passOutcome == 1) {
//newObj.totalMeso = newObj.totalMeso + (Math.round(1000 + (Math.pow(itemLevel, 3)) * (Math.pow(newObj.starlevel + 1, 2.7)) / 400));
newObj.level = newObj.level + 1;
} else if (passOutcome == 0) {
//newObj.totalMeso = newObj.totalMeso + (Math.round(1000 + (Math.pow(itemLevel, 3)) * (Math.pow(newObj.starlevel + 1, 2.7)) / 400));
//newObj.level = newObj.level - 1;
} else {
//newObj.totalMeso = newObj.totalMeso + (Math.round(1000 + (Math.pow(itemLevel, 3)) * (Math.pow(newObj.starlevel + 1, 2.7)) / 400));
newObj.destroyed = newObj.destroyed + 1
}
}
}
return newObj;
}
let counter = 0;
for (i=0; i<1000; i++) {
let n = starforceSim(140);
if (n.destroyed > 0) {
counter++
}
}
console.log(counter);
I disabled the decrease level when it fails just to focus on the destroy rates.
Is there a better way to code probabilities or to test them? Is there something wrong with my code?
Math.random is only pseudo-random1
1Source
This means you may not get a perfectly uniform distribution. In my own fiddling, it seems like randomness might get worse if you generate many values in rapid succession [citation needed].
If you want a better source of randomness, check out Crypto.getRandomValues.
I don't see anything wrong with your code. I think your expectations are just off. To verify that this is caused by lame randomness, take David Tansey's advice and study just the randomness output.
You may also notice different randomness quality in different browsers (or, different Javascript engines).

browser optimization of splice to slice

I have the following 2 functions:
//destructive
const getEveryX = (arr, x, offset) => {
const _arr = [...arr];
let _arrArr = [];
if (offset && offset >= arr.length) {
_arrArr.push(_arr.splice(0, offset));
}
while (_arr.length > x) {
_arrArr.push(_arr.splice(0, x));
}
if (_arr.length) {
_arrArr.push(_arr);
}
return _arrArr
}
and
//copying
const getEveryX2 = (arr, x, offset) => {
let _pointer = 0;
const _arrArr = [];
if (offset && offset >= arr.length) {
_arrArr.push(arr.slice(0, offset));
}
while (arr.length >= _pointer + x) {
_arrArr.push(arr.slice(_pointer, _pointer + x));
_pointer += x;
}
if (arr.length) {
_arrArr.push(arr.slice(_pointer, arr.length - 1));
}
return _arrArr;
};
I wrote the second function because I thougt it would be faster to copy the parts I need from the original array instead of copying the original and splicing out the beginning every time (both functions should do the same, first uses splice, second slice) - I tested it and this doesnt seem to be the case, they both take the same time.
My theory is that the compiler knows what I want to do in both cases and creates the same code.
I could also be completely wrong and the second version shouldnt be faster without optimizations.
Do you know what is going on here?
I tested it and this doesnt seem to be the case, they both take the same time.
No, your test case is broken. JSperf doesn't run setup and teardown for each of your snippets runs, it runs a your snippets in a loop between setup and teardown. You are emptying the testArr on the first run, the rest of the iterations only measures the while (testArr.length > 1) condition evaluation (yielding false).
I've updated the benchmark, and as expected slice is now performing better.

Javascript can this poker straight detection function be optimised?

Looking to optimise my code to get even more speed. Currently the code detects any poker hand and takes ~350ms to do 32000 iterations. The function to detect straights, however seems to be taking the biggest individual chunk of time at about 160ms so looking if any way to optimise it further.
The whole code was originally written in php since that is what I'm most familiar with but despite php 7's speed boost it still seems to be slower than javascript. What I found when translating to javascript though is that many of php's built in functions are not present in javascript which caused unforeseen slowdowns. It is still faster overall than the original php code though but I'm looking to see if it can be optimised more. Perhaps the answer is no, but I thought I'd check anyway.
I have written the functions range and arrays_equal since these are either missing from javascript or don't quite work properly.
function straight(handval) {
if (arrays_equal(handval.slice(0, 4),[2, 3, 4, 5]) && handval[handval.length-1] == 14) {//if is Ace 2345
return [4,14];
}
else {//if normal straight
for (let i = handval.length - 5; i >= 0; i--) {
let subhand = handval.slice(i, i + 5);
if (arrays_equal(subhand, range(subhand[0], subhand[subhand.length-1]))) {
return [4,subhand[4]];
}
} return [0]
}
}
function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); }
function range(start, end) {
let arr = [];
for (let i = start; i <= end; i++) {
arr.push(i);
}
return arr;
}
Handval comes in as a simple array of 5-7 elements of numbers from 2-14 representing the cards So for example it could be [6,8,4,11,13,2] or [8,4,13,8,10].
EDIT: The function is called and sorted at the same time with this code:
straight(handval.slice(0).sort(sortNumber));
function sortNumber(a,b) { return a - b; }
You could just go from right to left and count the number of sequential numbers:
function straight(handval) {
if([2, 3, 4, 5].every((el, i) => handval[i] === el) && handval[handval.length-1] === 14)
return [4, 14];
let count = 1;
for(let i = handval.length - 1; i >= 1; i -= 1) {
if(handval[i] === handval[i - 1] + 1) {
count += 1;
if(count === 5) return [ 4, handval[i + 3] ];
} else {
count = 1;
}
}
return [0];
}
That is way faster as it:
1) does not create intermediate arrays on every iteration, which you did with range and slice
2) does not compare arrays as strings, which requires a typecast and a string comparison, which is way slower than comparing two numbers against each other
3) It does not check all 3 ranges on its own (1 - 5, 2 - 6, 3 - 7), but does all that in one run, so it only iterates 5 positions instead of 3 x 5.

Infinite Loop with my binary search implementation in JavaScript

this is my first question on stack overflow so please bear with me.
I wrote this binarySearch function and for some reason it hangs - I'm assuming because of an infinite loop. Can anyone find the faults with my code?
let binarySearch = (array, value) => {
let target = value,
start = 0,
end = array.length - 1,
middle = Math.floor( (end + start)/2 )
while (start <= end){
if ( array[middle] === target ){
return true
}else if (array[middle] < target){
start = middle + 1
}else if (array[middle] > target){
end = middle - 1
}
}
return false
}
The most obvious bug is that you need to calculate middle as the first operation inside the loop, not outside of it.
Without that change, you're always examining whichever element was "middle" when the function was first called, and never partitioning your search space.
With that fix in place, my tests indicate that the code works as required, although as suggested in the comments you should be returning the index of the found element, not simply a flag to say whether the element was found or not.
An infinite loop occurs because the implementation shown is an iterative binary search, but the middle value is not recalculated upon each iteration, as others have mentioned. Thus, the middle variable will never change beyond the first iteration.
Below is an implementation which returns the index of value, or null, if value is not found.
function binarySearch(array, value) {
let middle, start = 0,
end = array.length - 1;
while (start <= end) {
middle = Math.floor((start + end) / 2);
if (array[middle] > value) {
end = middle - 1;
} else if (array[middle] < value) {
start = middle + 1;
} else {
return middle;
}
}
return null;
}

jQuery Avoid Overlapping Containers

I recently tried to work on a jQuery-Plugin which is used to spread an amount of containers randomly in a container to afterwards be able to animate them in a special way. You can see my attempts here: http://www.manuelmaurer.at/randposplugin.php
The Problem is, that some of them are overlapping and I can't figure out why. At first I thought that the reason for this might be that $.each() is not waiting for one loop to finish before starting the next one but I also tried to solve that using recursive functions - didn't help. I hope someone could give me a little push to figure out where the problem actually is, thanks in advance! You can see the code either on the page itself or just have a look at the following important parts.
This code is used to loop over all the elements. This is not further important, but the function "NoCollision" is trying to figure out, if an element exists in that area. If yes, it returns false, if the space can be used, it returns true. If the space can not be used, some random other coordinates are chosen and it will be tried again.
var Counter2 = 0;
$(FlowContainer).children(':not(:last)').each(function(elem) {
Counter2++;
ElemNow = $(FlowContainer).children().eq(Counter2);
ElemWidth = $(ElemNow).data("animwidth")
ElemHeight = $(ElemNow).data("animheight")
var Tries = 0;
var TryNowX = ElemPrevLeft;
var TryNowY = ElemPrevTop;
while (!NoCollision(TryNowX, TryNowY, ElemWidth, ElemHeight, PositionsArray, Settings.MinSpreadX, Settings.MinSpreadY) && Tries <= Settings.MaxTries) {
if (TryNowY < 15) {
TryNowY += randomIntFromInterval(0, 10);
} else if (TryNowY > (FlowContainer.height() - ElemHeight - 15)) {
TryNowY += randomIntFromInterval(-10, 0);
} else {
TryNowY += randomIntFromInterval(-10, 10);
}
if (TryNowX < 15) {
TryNowX += randomIntFromInterval(0, 10);
} else if (TryNowX > (FlowContainer.width() - ElemWidth - 15)) {
TryNowX += randomIntFromInterval(-10, 0);
} else {
TryNowX += randomIntFromInterval(-10, 10);
}
Tries++;
}
if (Tries == Settings.MaxTries) {
console.log("Warning: Couldn't fit all elements - hiding some.")
$(ElemNow).remove();
} else {
$(ElemNow).css({ top: TryNowY, left: TryNowX });
ElemPrevLeft = TryNowX;
ElemPrevTop = TryNowY;
PositionArray = [TryNowY, TryNowX, ElemHeight, ElemWidth];
PositionsArray[Counter2] = PositionArray;
}
})
The actual check, if the space can be used, takes part in the NoCollision-Function, which you can see in the following code.
function NoCollision(X, Y, W, H, PositionsArray, SpreadX, SpreadY) {
var NoErrors = true;
//Jedes Element im PositionsArray durchgehen und Prüfen
$.each(PositionsArray, function(PositionArray) {
var ArrY = PositionsArray[PositionArray][0];
var ArrX = PositionsArray[PositionArray][1];
var ArrW = PositionsArray[PositionArray][3];
var ArrH = PositionsArray[PositionArray][2];
if ((X < (ArrX - W - SpreadX) || X > (ArrX + ArrW + SpreadX)) && (Y < (ArrY - H - SpreadY) || Y > (ArrY + ArrH + SpreadY))) {
//SHOULD BE OKAY HERE
} else {
NoErrors = false;
}
})
return NoErrors;
}
The array which I am using to save the coordinates of all the already positioned divs looks like this.
PositionsArray[
[Elem1PositionY, Elem1PositionX, Elem1Height, Elem1Width]
[Elem2PositionY, Elem2PositionX, Elem2Height, Elem2Width]
]
My thought was to do it like this. Is there something wrong with the way I'd do it or is there a mistake in my implementation?
I am absolutely thankful for every bit of help! Thanks in advance!
I finally solved the issue - my calculation of the overlapping was a bit wrong, just had to change the && (and) to || (or) - works like charm now.
Found a better solution using a plugin which was referenced on another stackoverflow-thread - works way better now.

Categories

Resources