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.
Related
So I am implementing a little circle around you cursor that expands when your mouse touches a button, an a, and an image. I have a lot of code to make this work, and I am not going to make you go through all of it. The gist is, i have a function: checkIfTouching. Here is the code (messy but idc):
function findTopLeft(element) {
var rec = element.getBoundingClientRect();
return {top: rec.top + window.scrollY, left: rec.left + window.scrollX};
} //call it like findTopLeft('#header');
function getElementBBox(element) {
var rec = element.getBoundingClientRect();
return {width: rec.width, height: rec.height};
}
function checkIfTouching(element, x, y) {
// check if the cursor is in the elementm (from top left to bottom right)
const elementBBox = getElementBBox(element);
const elementTopLeft = findTopLeft(element);
const elementBottomRight = {top: elementTopLeft.top + elementBBox.height, left: elementTopLeft.left + elementBBox.width};
// check if it is near the cursor
const cursorDistances = {
top: Math.abs(elementTopLeft.top - y),
left: Math.abs(elementTopLeft.left - x),
bottom: Math.abs(elementBottomRight.top - y),
right: Math.abs(elementBottomRight.left - x)
};
// if the cursor is within 100px of the element, return true
if (cursorDistances.top < 100 && cursorDistances.left < 100 && cursorDistances.bottom < 100 && cursorDistances.right < 100) {
if (x >= elementTopLeft.left && x <= elementBottomRight.left && y >= elementTopLeft.top && y <= elementBottomRight.top) {
return true;
} else {
return false;
}
} else {
return false;
}
}
It works perfectly. The issue I'm having is I get all elements on the page, filer them out, and then I loop through each one using a for loop. This stops the circle from expanding fully because I am hovered over one object, and it has to switch between both. My question is: Is there a way to run a function on all items in a list, or make the for loop run faster. You can see my entire cursor.js file here: https://gist.github.com/BlueFalconHD/eb8a246035e317bfc5d3b0a210b71b6c.
I really do not understand this enough, and have no idea half of what this code does honestly. It was written by copilot. Anyway, thanks for the help! :)
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).
I am trying to make a maze generator, and almost everything is working so far. I have been able to set my position to a random pos, and then I repeat the standard() function. In the function, I add pos to posList, and then I choose a random direction. Next, I check if the cell has been visited by running through all of the posList vectors in reverse. I haven't executed the code that backtracks yet. If visited = false then I move to the square and execute the yet-to-be-made path() function. However, for some reason, the mover just doesn't detect if a cell has been visited or not. I am using p5.js. What am I doing wrong?
var posList = [];
var pos;
var tempo;
var boole = false;
var direc;
var mka = 0;
function setup() {
createCanvas(400, 400);
//Set up position
pos = createVector(floor(random(3)), floor(random(3)));
frameRate(1)
}
//Choose a direction
function direct(dire) {
if(dire === 0) {
return(createVector(0, -1));
} else if(dire === 1) {
return(createVector(1, 0));
} else if(dire === 2) {
return(createVector(0, 1));
} else {
return(createVector(-1, 0));
}
}
/foLo stands fo forLoop
function foLo() {
//If we have checked less than three directions and know there is a possibility for moving
if(mka < 4) {
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
//Go through posList backwards
for(var i = posList.length - 1; i >= 0; i --) {
//If the cell has been visited or the cell is off of the screen
if(tempo === posList[i]) {
//Change the direction
direc ++;
//Roll over direction value
if(direc === 4) {
direc = 0;
}
//Re-execute on next frame
foLo();
//The cell has been visited
boole = false;
//Debugging
console.log(direc)
mka++;
} else if(tempo.x < 0 || tempo.x > 2 || tempo.y < 0 || tempo.y > 2) {
direc ++;
if(direc === 4) {
direc = 0;
}
foLo();
boole = false;
console.log(direc)
mka++;
}
}
//If it wasn't visited (Happens every time for some reason)
if(boole === true) {
//position is now the temporary value
pos = tempo;
console.log("works")
mka = 0;
}
}
}
function standard() {
//Add pos to posList
posList.push(pos);
//Random direction
direc = floor(random(4));
//Convert to vector
direct(direc);
foLo();
//Tracks pos
fill(255, 255, 0);
rect(pos.x*100+50, pos.y*100+50, 50, 50)
}
function draw() {
background(255);
fill(0);
noStroke();
//draw grid
for(var i = 0; i < 4; i ++) {
rect(i*100,0,50,350);
rect(0, i*100, 350, 50);
}
standard();
boole = true;
console.log(pos)
console.log(posList);
}
Your issue is on the line where you compare two vectors if(tempo === posList[i]) {: This will never be true.
You can verify that with the following code (in setup() for example):
const v1 = new p5.Vector(1, 0);
const v2 = new p5.Vector(1, 0);
const v3 = new p5.Vector(1, 1);
console.log(v1 === v2) // false
console.log(v1 === v3) // false
This is because despite having the same value v1 and v2 are referencing two different objects.
What you could do is using the p5.Vector.equals function. The doc has the following example:
let v1 = createVector(10.0, 20.0, 30.0);
let v2 = createVector(10.0, 20.0, 30.0);
let v3 = createVector(0.0, 0.0, 0.0);
print(v1.equals(v2)); // true
print(v1.equals(v3)); // false
This might not give you a working algorithm because I suspect you have other logical errors (but I could be wrong or you will debug them later on) but at least this part of the code will do what you expect.
Another solution is to use a Set instead of your list of positions. The cons of this solution is that you will have to adapt your code to handle the "out of grid" position situation. However when you want to keep track of visited items a Set is usually a great solution because the access time is constant. That this means is that to define is a position has already been visited it will always take the same time (you'll do something like visitedSet.has(positionToCheck), whereas with your solution where you iterate through a list the more cells you have visited to longer it will take to check if the cell is in the list.
The Set solution will require that you transform your vectors before adding them to the set though sine, has I explained before you cannot simply compare vectors. So you could check for their string representation with something like this:
const visitedCells = new Set();
const vectorToString = (v) => `${v.x},{$v.y}` // function to get the vector representation
// ...
visitedCells.add(vectorToString(oneCell)); // Mark the cell as visited
visited = visitedCells.has(vectorToString(anotherCell))
Also has a general advice you should pay attention to your variables and functions name. For example
// foLo stands fo forLoop
function foLo() {
is a big smell: Your function name should be descriptive, when you see your function call foLo(); having to find the comment next to the function declaration makes the code less readable. You could call it generateMaze() and this way you'll know what it's doing without having to look at the function code.
Same for
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
You could simply rename tempo to cellToVisit for example.
Or boole: naming a boolean boole doesn't convey a lot of information.
That could look like some minor details when you just wrote the code but when your code will be several hundred lines or when you read it again after taking several days of break, you'll thank past you for taking care of that.
This is my jsfiddle :http://jsfiddle.net/Z7a5h/
As you can see the animation of the sprite sheet when the player is not moving is too fast so I was trying to make it slow by declaring two variable lastRenderTime: 0,RenderRate: 50000
but my code is not working and it seem I have a misunderstanding of the algorithm I am using so can anyone lay me a hand to how can I fix it?
if (!this.IsWaiting) {
this.IsWaiting = true;
this.Pos = 1 + (this.Pos + 1) % 3;
}
else {
var now = Date.now();
if (now - this.lastRenderTime < this.RenderRate) this.IsWaiting = false;
this.lastRenderTime = now;
}
Yes, your logic is wrong. You were using the wrong operator < instead of >. Also, you needed to update the lastRenderTime only when the condition is statisfied, otherwise it keeps getting updated and the value of now - this.lastRenderTime never ends up becoming more than 20 or so.
if (!this.IsWaiting) {
this.IsWaiting = true;
this.Pos = 1 + (this.Pos + 1) % 3;
}
else {
var now = Date.now();
if (now - this.lastRenderTime > this.RenderRate) {
this.IsWaiting = false;
this.lastRenderTime = now;
}
}
Here is your updated fiddle.
I have a couple of fairly simple javascript functions which animate the transition of a number, going up and down based on user actions. There are a number of sliders on the page which within their callback they call recalculateDiscount() which animates the number up or down based on their selection.
var animationTimeout;
// Recalculate discount
function recalculateDiscount() {
// Get the previous total from global variable
var previousDiscount = totalDiscount;
// Calculate new total
totalDiscount = calculateDiscount().toFixed(0);
// Calculate difference
var difference = previousDiscount - totalDiscount;
// If difference is negative, count up to new total
if (difference < 0) {
updateDiscount(true, totalDiscount);
}
// If difference is positive, count down to new total
else if (difference > 0) {
updateDiscount(false, totalDiscount);
}
}
function updateDiscount(countUp, newValue) {
// Clear previous timeouts
clearTimeout(animationTimeout);
// Get value of current count
var currentValue = parseInt($(".totalSavingsHeader").html().replace("$", ""));
// If we've reached desired value, end
if (currentValue === newValue) { return; }
// If counting up, increase value by one and recursively call with slight delay
if (countUp) {
$(".totalSavingsHeader").html("$" + (currentValue + 1));
animationTimeout = setTimeout("updateDiscount(" + countUp + "," + totalDiscount + ")", 1);
}
// Otherwise assume we're counting down, decrease value by one and recursively call with slight delay
else {
$(".totalSavingsHeader").html("$" + (currentValue - 1));
animationTimeout = setTimeout("updateDiscount(" + countUp + "," + totalDiscount + ")", 1);
}
}
The script works really well for the most part however there are a couple of problems. Firstly, older browsers animate more slowly (IE6 & 7) and get confused if the user moves the slider again whilst it is still within the animation.
Newer browsers work great EXCEPT for on some occasions, if the user moves the slider mid-animation, it seems that it starts progressing in the wrong direction. So for updateDiscount() gets called with a new value and a directive to count up instead of down. As a result the animation goes the wrong direction on an infinite loop as it will never reach the correct value when it's counting in the wrong direction.
I'm stumped as to why this happens, my setTimeout() experience is quite low which may be the problem. If I haven't provided enough info, just let me know.
Thank you :)
Here is how you use setTimeout efficiently
animationTimeout = setTimeout(function {
updateDiscount(countUp,totalDiscount);
},20);
passing an anonymous function help you avoid using eval.
Also: using 1 millisecond, which is too fast and will freeze older browsers sometimes. So using a higher which will not even be noticed by the user can work better.
Let me know if this works out for you
OK think it's fixed...
Refactored code a little bit, here's final product which looks to have resolved bug:
var animationTimeout;
function recalculateDiscount() {
var previousDiscount = parseInt(totalDiscount);
totalDiscount = parseInt(calculateDiscount());
if (($.browser.msie && parseFloat($.browser.version) < 9) || $.browser.opera) {
$(".totalSavingsHeader").html("$" + totalDiscount);
}
else {
if (previousDiscount != totalDiscount) {
clearTimeout(animationTimeout);
updateDiscount(totalDiscount);
}
}
}
function updateDiscount(newValue) {
var currentValue = parseInt($(".totalSavingsHeader").html().replace("$", ""));
if (parseInt(currentValue) === parseInt(newValue)) {
clearTimeout(animationTimeout);
return;
}
var direction = (currentValue < newValue) ? "up" : "down";
var htmlValue = direction === "up" ? (currentValue + 1) : (currentValue - 1);
$(".totalSavingsHeader").html("$" + htmlValue);
animationTimeout = setTimeout(function () { updateDiscount(newValue); }, 5);
}
Will give points to both Ibu & prodigitalson, thank you for your help :)