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 :)
Related
I am currently trying to make a program where the text changes as the phone moves every couple value(s) using the P5.JS deviceMoved() function.
(the gif below displays how i wanted the text to change eventually as the device moved)
As seen on the code below, I've put all the text in the array and I wanted to change the index to +1 each time say the move value ads 30 and repeat until all the text is gone.
let button;
let permissionGranted = false;
let nonios13device = false;
let cx, cy
let value = 0;
var myMessages = ["The", "Quick", "Brown", "Fox", "Jumped", "Over", "The", "Lazy", "Dog"];
var index = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(255)
text(myMessages[index], width / 2, height / 2);
fill(value);
text(value, width / 3, height / 3);
textSize(30)
}
function deviceMoved() {
value = value + 5;
if (value > 255) {
value = 0;
}
}
function onMove() {
var currentValue = value + 30;
if (value = currentValue) {
index++;
return;
}
if (index >= myMessages.length) {
index = 0;
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
I think my problem is within the onMove function, where I need to define the current value and what values could change the text, I'm fairly new at this so any insight/solution to do this would be highly appreciated :)
Thank you!
There are several issues related to the onMove function. First and foremost it is never called, and unlike deviceMoved it is not a special function that p5.js automatically invokes. Additional issues:
function onMove() {
// You create a currentValue variable that is just value + 30.
// Within the same function, checking if value is >= currentValue,
// assuming that is what you intended, will be fruitless because it
// is never true.
// What you probably want to do is declare "currentValue" as a global
// variable and check the difference between value and currentValue.
var currentValue = value + 30;
// This is the assignment operator (single equal sign), I think you meant
// to check for equality, or more likely greater than or equal to.
if (value = currentValue) {
index++;
// You definitely do not want to return immediately here. This is where
// you need to check for the case where index is greater than or equal
// to myMessages.length
return;
}
if (index >= myMessages.length) {
index = 0;
}
}
Here's a fixed version:
function deviceMoved() {
value = value + 5;
if (value > 255) {
// When value wraps around we need to update currentValue as well to
// keep track of the relative change.
currentValue = 255 - value;
value = 0;
}
onMove();
}
let currentValue = 0;
function onMove() {
if (value - currentValue >= 30) {
// Update currentValue so that we will wait until another increment of
// 30 before making the next change.
currentValue = value;
index++;
// We only need to make this check after we've incremented index.
if (index >= myMessages.length) {
index = 0;
}
}
}
In order to test this out on my mobile device (iOS 14) I had to add some code to request access to the DeviceMotionEvent, and host it in an environment using HTTPS and not embedding in an iframe. You can see my code on glitch and run it live here.
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 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.
A brief run-down - I'm using the wad JavaScript library to create a random tone generator. I am trying to have the generator 'slide' between pitches, as follows in pseudocode:
Determine the current pitch (randomNum)
Determine the next pitch (randomNext)
Determine whether the next pitch is higher or lower
Play the current pitch and hold it for a second
Play all the pitches between the current pitch and the next pitch quickly to give a 'sliding' effect - this is the part that isn't working!
Play the next pitch and hold it for a second (and then reassign it as the current pitch).
I have got everything working except for step 5. I am trying to use a for loop (within an if/else statement according to whether the next pitch is higher or lower) to iterate over each pitch using a counter, but according to my logged console statements, the for loop never runs at all.
If I remove the for loop from the if statement and set its starter pitch manually from a variable, it runs as expected.
What am I doing wrong? Is there something wrong with my if statement? With my for loop?
The problematic bit of code is this:
// play the first pitch
function playFirst() {
if(!stopped){
window.randomNum = Math.round(Math.random()*200 + 100);
console.log("randomnum is now "+ randomNum);
}
playRandom();
}
// play and loop subsequent pitches
function playRandom(){
if(!stopped){
var randomNext = Math.round(Math.random()*200 + 100);
console.log("randomNext is now " + randomNext);
var howManyCents = randomNum - randomNext;
console.log(howManyCents + " cents");
// ascending note slide condition
if (randomNum < randomNext) {
console.log("randomnum is less!");
var inbetweenNum = randomNum + 1;
// for loop - the part with the problem!
for (var i = 0; i < howManyCents; i++) {
inbetweenNum = randomNum + i;
console.log("inbetween number is " + inbetweenNum);
inbetween.play({ pitch : inbetweenNum });
console.log("played inbetween up");
}
// descending note slide condition
} else {
console.log("randomnum is more!");
var inbetweenNum = randomNum - 1;
// another problematic for loop
for (var i = 0; i > howManyCents; i--) {
inbetweenNum = randomNum - i;
console.log("inbetween number is " + inbetweenNum);
inbetween.play({ pitch : inbetweenNum });
console.log("played inbetween down");
}
}
// actually play the note
bell.play({ pitch : randomNext, wait: 0 });
console.log("played randomnext" + randomNext);
// reassign the new note as the current note
randomNum = randomNext;
console.log("randomnum is now" + randomNum);
setTimeout(playRandom,1500); // and loop it
}
}
and I have made a JSFiddle of the full program here.
Any assistance would be very much appreciated!
The condition for that block is that randomNum < randomNext, but howManyCents is randomNum - randomNext, which will be negative in that case. The loop condition, then – i < howManyCents, with i starting at 0, will never be true.
You can use i < -howManyCents, or assign Math.abs(randomNum - randomNext) to howManyCents, or i > howManyCents and i--.
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.