I need help with something I'm working on in JavaScript/jQuery
I'd like to give a set 'destination' number, and give it a set duration, and have it so it adds up in intervals of 1 randomly throughout the duration (not equally spaced, but not all at the start, end or middle), but reaching the 'destination' number by the duration of time is up.
So, if I set a duration of 20 seconds, and a 'destination' number of 10. It will start the timer, and randomly add in intervals of 1 (following no pattern), and the duration finishes at the same time as the last number is added.
I'm really stuck with this, and not sure where to even begin.
Any help at all would be greatly appreciated, thanks a lot!
My approach is:
Divide the duration to equal pieces (one for each increment, starting with 0, ending with the full duration)
Randomize the delays, but keep the same sum. If you add some random value to one delays, then subtract the same amount from delays interval.
Call window.setTimeout with all the delays. Give it a function witch increments the current value by one.
The code:
var start = parseInt($("#inStart").val(), 10),
end = parseInt($("#inEnd").val(), 10),
duration = parseInt($("#inDuration").val(), 10),
difference = end - start,
current = start - 1,
step = duration * 1000 / difference,
delays = [],
index, amount,
increment = function () {
current += 1;
$("#outCurrent").text(current);
};
// calculate equal delays
for (index = 0; index <= difference; index++) {
delays.push(step * index);
}
// randomize delays, without changing the sum
for (index = 1; index < delays.length - 2; index++) {
amount = (Math.random() - 0.5) * step;
delays[index] -= amount;
delays[index+1] += amount;
}
// schedule the increment calls
for (index = 0; index < delays.length; index++) {
window.setTimeout(increment, delays[index]);
}
Here is a demo fiddle, you can try it out.
Related
I have the following code that generates an initial random number between 1 and 10000, then repeatedly generates a second random number until it matches the first:
let upper = 10000;
let randomNumber = getRandomNumber(upper);
let guess;
let attempt = 0;
function getRandomNumber(upper) {
return Math.floor(Math.random() * upper) + 1;
}
while (guess !== randomNumber) {
guess = getRandomNumber(upper);
attempt += 1;
}
document.write('The randomNumber is ' + randomNumber);
document.write(' it took' + attempt);
I am confused at (attempt) variables. Why is it that the computer took this many attempts to get the randomNumber? Also, it didn't put attempt in the loop condition.
Just to give you a start. This is what your code does:
// define the maximum of randomly generated number. range = 0 - 10.000
let upper = 10000;
// generate a random number out of the range 0-10.000
let randomNumber = getRandomNumber(upper);
// predefine variable guess
let guess;
// set a counter to 0
let attempt = 0;
// generate and return a random number out of the range from 0 to `upper`
function getRandomNumber(upper) {
return Math.floor(Math.random() * upper) + 1;
}
// loop until guess equals randomNumber
while (guess !== randomNumber) {
// generate a new random number and assign it to the variable guess
guess = getRandomNumber(upper);
// increase the counter by 1
attempt += 1;
}
// output the initially generated number
document.write('The randomNumber is ' + randomNumber);
// output the number of repetitions
document.write(' it took' + attempt);
So, once again. You generate a random number at start. And then you repeat generating another random number until this second random number matches the first. As you don't set any limits e.g. "each random number can only appear once" or "no more than 10.000 tries" your program might need millions of tries until the number matches, because you have a range of 10.000 possible numbers and they might repeat a hundreds of times each before the match is finally there.
Try to optimize your program by limiting the number of tries to 10.000. And you could your computer just let count upwards from 0 to 10.000 instead of guessing with a randomly generated number.
When I repeatedly run your snippet, I am seeing your code take anywhere from a few thousand to a few tens of thousands of repetitions to get a given value for a random number sampled between 1 and 10000. But this is not surprising -- it is expected.
Assuming your getRandomNumber(upper) function does indeed return a number between 1 and upper with a uniform distribution, the expected probability that the number returned will not be the initial, given value randomNumber is:
1 - (1/upper)
And the chance that the first N generated numbers will not include the given value is:
(1 - (1/upper)) ^ N
And so the chance P that the first N generated numbers will include given value is:
P = 1 - (1 - (1/upper)) ^ N
Thus the following formula gives the number of repetitions you will need to make to generate your initial value with a given probability P:
N = ln(1.0 - P) / ln(1.0 - (1.0/upper))
Using this formula, there is only a 50% chance of getting randomValue after 6932 repetitions, and a 95% chance after 29956 repetitions.
let upper = 10000;
function numberOfRepetitionsToGetValueWithRequiredProbability(upper, P) {
return Math.ceil(Math.log(1.0 - P) / Math.log(1.0 - (1.0/upper)))
}
function printNumberOfRepetitionsToGetValueWithRequiredProbability(upper, P) {
document.write('The number of tries to get a given value between 1 and ' + upper + ' with a ' + P + ' probability: ' + numberOfRepetitionsToGetValueWithRequiredProbability(upper, P) + ".<br>");
}
var probabilities = [0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 0.95, 0.99, 0.9999];
probabilities.forEach((p) => printNumberOfRepetitionsToGetValueWithRequiredProbability(upper, p));
This is entirely consistent with the observed behavior of your code. And of course, assuming Math.random() is truly random (which it isn't, it's only pseudorandom, according to the docs) there is always going to be a vanishingly small probability of never encountering your initial value no matter how many repetitions you make.
I'm creating a countdown, which i need to count down it to 0 but in random numbers.
Ex- countdown from 4 minutes by second by second, but i need to show a value between 300 to 390 countdown to 0 with random numbers within above 4 minutes period.
I created random number count down, but still cannot able to figure out how to target that become 0 within given time.
<script>
var count = 350; //this is a random value between(300-390)
var timer = setInterval(function() {
//this will generate number between 0 to 10 and reduce it from count randimly
count -= Math.floor(Math.random()*9);
//set it to html element
jQuery('#maindvpart').html(count);
//when number become 0
if( count <= 0) {
jQuery('#maindvpart').html(0);
count = 0;
clearInterval(timer);
}
//but i need to run this countdown within 4 minutes
//(so when 0 minutes ends above count should zero, until 0 it should count down from random number)
},1000);
</script>
<div id="maindvpart"> </div>
anyone have idea or example how to do this, thank you
Your "timer" runs each second. When you do "count -= Math.floor(Math.random()*9);", it reduces "count" variable value much faster, so you will always reach "count <= 0" much faster than 4 minutes. If you want to run your timer for 4 minutes, you need to run your timer per second - 240 times, and "display a random number", but do not subtract that random number from count. Does this help?
Editing with an example, hoping it would point you towards your goal:
<script>
var count = 240; //4 minutes
var displayNumber = 350; //or whatever number you want to start with
var timer = setInterval(function() {
//this will generate number between 0 to 10 and reduce it from displayNumber randomly
displayNumber -= Math.floor(Math.random()*9);
count--;
console.log(displayNumber);
// exit if either the display number is <= 0 or the time is up
if( displayNumber <= 0 || count <= 0) {
console.log(0);
displayNumber = 0;
clearInterval(timer);
}
},1000);
</script>
Solution 1:
simply modify the time interval after which the random number is reduced by unit step (ie:1) to indicate the time step necessary for the random number to equal 0 when the time is up . the equation would be :
{delay before subtracting 1 from rand# (in sec) = time elapsed till rand# reaches 0 (in sec)/rand#}
ex:
1) rand# = 300 , needed to count down till reaches 0 in 2 minutes (120sec) , then 300 needs to count down by 1 each 120/300 sec
var count = 300 // your randomly generated number;
var time = 60 //time elapsed before the random number to equal 0;
var timer = setInterval(function() {
count = count -1;
console.log(count);
if( count <= 0) {
count = 0;
clearInterval(timer);
}
},(time/count)*1000);
Solution 2:
modify the unit step by which the random number is decreased every second till it reaches 0 after the specified time is elapsed . the equation would be :
{random # decrement step = rand#/time elapsed till rand# reaches 0 (in sec)}
ex:
1) rand# = 300 , needed to count down till reaches 0 in 1 minute (60sec) , then 300 needs to count down by 300/60 each 1 sec
var count = 300 // your randomly generated number;
var time = 20 //time elapsed before the random number to equal 0;
var decrementStep=count/time;
var timer = setInterval(function() {
count = count - decrementStep;
console.log(count);
if( count <= 0) {
count = 0;
clearInterval(timer);
}
},1000);
I wrote some JavaScript code to animate CSS properties of elements. I pass the following arguments to the function: amount, interval, and duration; amount being the change in the property (for example 200 could mean add 200 pixels to the element's width), interval being the time between two consecutive changes, and duration being the total duration of the animation.
The code works fine unless I pass the arguments in a way that the change in each interval becomes very small (like a tiny fraction of a pixel).
I know the code is working fine theoretically, as I get the change in console.
Any ideas about the problem?
Cheers.
UPDATE: the code:
function handleTimer (amount, interval, duration, execute, element) {
let i = 0;
let current = 0;
let stepsCount = countSteps(interval, duration);
let stepLength = calcStepLength(stepsCount, amount);
let count = setTimeout(function addOneMore () {
if ( i < stepsCount -1 ){
i++;
current += stepLength;
execute(stepLength, element);
if (current < amount) {
count = setTimeout(addOneMore, interval)
}
} else {
current = amount;
execute(amount - (stepsCount -1) * stepLength, element);
}
}, interval)
}
function countSteps (interval, duration) {
let remainder = duration % interval;
let stepsCount;
if (remainder) {
stepsCount = Math.floor(duration / interval) + 1;
} else {
stepsCount = duration / interval;
}
return stepsCount;
}
function calcStepLength(stepsCount, amount) {
return amount / stepsCount;
}
function resizeWidth (amount, element) {
let widthSTR = $(element).css('width');
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
$(element).css('width', `${width + amount}px`);
}
So this:
handleTimer(218, 5, 200, resizeWidth, '.box');
works fine, but this:
handleTimer(218, 5, 2000, resizeWidth, '.box');
doesn't.
UPDATE 2:
I know browsers are super accurate with pixels, like when you use percentages. Of course the value will be rounded before rendering since displays cant display half pixels, but the value is still calculated accurately.
I don't know at what decimal the rounding occurs.
This happens because parseInt is rounding your number up.
Pay attention to this line:
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
if width is a decimal number, like 22.5px, it will be rounded up to 22.
If amount is less than 1, it won't reach 23 and when you round up the number again, you'll get 22 again and it becomes a loop.
You have two solutions:
Use another variable to save the width value, avoiding to writing and reading it from CSS:
let initialWidth = $(element).css('width');
let savedWidth = widthSTR.substr(0, initialWidth, initialWidth.length - 2 ) );
function resizeWidth (amount, element) {
savedWidth += amount;
$(element).css('width', `${savedWidth}px`);
}
Just use parseFloat in place of parseInt to don't round your number up:
let width = parseFloat( widthSTR.substr( 0 , widthSTR.length - 2 ) );
I am working on simple script that should animate given value (for example 6345.23) to 0 by counting it down, it should also end up at 0 if specified amount of time have passed (for example 2 seconds.
I started by simple logic:
given config: initial value, time in sec, interval
time is given in seconds so convert it to milliseconds
calculate amount of ticks by dividing time in ms by interval
calculate amount of decreased value per tick by dividing initial value by amount of ticks
once above are known we can simply do: (simple model, not actual code)
intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
clearInterval(intId);
} else {
value -= amount_per_tick;
// update view
}
}, interval);
actual code:
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
Link do fiddle (in console you can observe how it works)
If you set time to 2 (2 seconds) its ok, but if you set time to for example 2.55 (2.55 seconds) it doesnt stop at all at 0, its passing by and going indefinitely in negative values.
How i can fix it so no matter what is set in seconds its always go precisly one by one until reaches perfectly 0?
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
You're relying on ticks_made === amount_of_ticks being an exact match. Chances are, due to rounding, you won't get an exact match, so you'd be better off doing:
if(ticks_made >= amount_of_ticks) {
kshetline's answer correctly addresses why you get into negative values. When dealing with fractional IEEE-754 double-precision binary numbers (in the normal range, or even whole numbers in very high ranges), == and === can be problematic (for instance, 0.1 + 0.2 == 0.3 is false). Dealing with values as small as the fractional values here are, accumulated imprecision is also a factor. It's inevitable to have to fudge the final step.
But there's a larger issue: You can't rely on timers firing on a precise schedule. Many, many things can prevent their doing so — other UI rendering work, other scripts, CPU load, the tab being inactive, etc.
Instead, the fundamental technique for animation on browsers is:
Update when you can
Update based on where you should be in the animation based on time, not based on how many times you've animated
Use requestAnimationFrame so your update synchronizes with the browser's refresh
Here's your code updated to do that, see comments:
// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});
var value = 212.45,
time = 2.55, // in seconds
time_in_ms = time * 1000,
amount_per_ms = value / time_in_ms,
interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
ticks_made = 0;
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
// Remember when we started
var started = now();
// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;
// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
requestAnimationFrame(render);
}, interval);
// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
console.log("************DELAY************");
var stop = now() + 200;
while (now() < stop) {
// Busy-loop, preventing anything else from happening
}
}, time_in_ms / 2);
// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
if (done) {
return;
}
++ticks_made;
var elapsed = now() - started;
if (elapsed >= time_in_ms) {
console.log(ticks_made, "done");
done = true;
clearInterval(intId);
} else {
var current_value = value - (amount_per_ms * elapsed);
console.log(ticks_made, current_value);
}
}
/* Maximize in-snippet console */
.as-console-wrapper {
max-height: 100% !important;
}
If you run that, then scroll up to the "************DELAY************" line, you'll see that even though rendering was held up by "another process", we continue with the appropriate next value to render.
It would make sense to convert the result of .toFixed() to a number right away:
let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);
(note the + signs)
Then you will never have to worry about type coercion or anything, and instead just focus on math.
I'm working on a game loop and can't get past a specific issue: A bunch of objects start with an incremented delay and should move a certain distance.
The expected behaviour is that all objects should move in an even diagonal line, yet they move in uneven groups.
I realize the issue lies in 16.667ms interval updates which "groups" objects in update cycles. Is it possible to achieve sub-17ms precision?
I have tried separating update and render methods and run the update inside a delta while loop - all to no avail.
Here's the relevant part from the tick function:
function tick() {
if (this._stopped) return;
let now = performance.now();
if (now < this._lastTick + this._interval - 1) {
this._rafId = requestAnimationFrame(this.tick);
return;
}
this._rafId = requestAnimationFrame(this.tick);
let frameTime = (now - this._lastTick);
this._lastTick = now;
this._delta += frameTime;
let acc = 0;
while (this._delta >= this._interval) {
this._delta -= this._interval;
//this.update(this._interval);
acc++;
}
this.update(acc * this._interval);
//this.render(time);
this.count++;
}
Here's the codepen.
Would really appreciate any input.